Event Publishing
SCHEMABOUND provides a trait-based event publishing system that lets you distribute domain events to external systems such as message queues, analytics pipelines, or compliance archives. The reference implementation uses Kafka for high-throughput pub/sub, but the abstraction supports any transport.
Trait Contract
The public SDK exposes an EventPublisher trait that implementations must satisfy:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, PublishError};
use async_trait::async_trait;
#[async_trait]
pub trait EventPublisher: Send + Sync {
async fn publish(&self, topic: &str, key: &[u8], value: &[u8]) -> Result<(), PublishError>;
async fn close(&self);
}
}
publish— Send an event to a named topic with optional key-based partitioning.close— Gracefully shut down the publisher (flush buffers, release connections).
Implementations are responsible for connection management, retry logic, and transport-specific error handling. The trait returns a generic PublishError that wraps underlying failures.
Reference Implementation: Kafka
The built-in KafkaEventPublisher implements the trait using Apache Kafka. It supports:
- Per-topic partitioning — Events are distributed across partitions based on configurable strategies (key-based, round-robin, or custom).
- Configurable consumer groups — When used as a subscriber, events can be consumed by named groups with offset management.
- Heartbeat timeout — Configurable heartbeat interval for membership renewal in consumer groups.
Configuration
The Kafka publisher loads configuration from environment variables:
| Variable | Description |
|---|---|
KAFKA_BOOTSTRAP_SERVERS | Comma-separated list of Kafka broker addresses (e.g., broker1:9092,broker2:9092) |
KAFKA_SECURITY_PROTOCOL | Transport security: PLAINTEXT, SSL, or SASL_PLAINTEXT (default: PLAINTEXT) |
KAFKA_SASL_MECHANISM | SASL mechanism if using SASL_PLAINTEXT or SASL_SSL (e.g., SCRAM-SHA-256, SCRAM-SHA-512) |
KAFKA_SASL_USERNAME | Username for SASL authentication |
KAFKA_SASL_PASSWORD | Password for SASL authentication |
Topic Partitioning by Organization
For multi-tenant deployments, the publisher supports per-organization topic partitioning. Events are published to topics named {prefix}-{org_id}-events, where prefix is configurable and org_id comes from the event’s runtime context.
This ensures:
- Isolation: Each organization’s events land in their own topic.
- Scalability: Topics can be distributed across multiple Kafka brokers.
- Compliance: Audit requirements that mandate per-org data separation are satisfied at the transport layer.
Using the Publisher
Register the publisher with SCHEMABOUND’s event bus:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, KafkaEventPublisher};
use std::sync::Arc;
let kafka = Arc::new(KafkaEventPublisher::from_env()?);
schemabound::get_event_bus().register_publisher(kafka)?;
}
The publisher receives all events emitted on the bus and forwards them to the configured Kafka cluster.
Custom Implementations
To publish events to a different system (e.g., RabbitMQ, AWS SNS, or a custom HTTP webhook), implement EventPublisher for your type:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, PublishError};
use async_trait::async_trait;
struct MyCustomPublisher { /* ... */ }
#[async_trait]
impl EventPublisher for MyCustomPublisher {
async fn publish(&self, topic: &str, key: &[u8], value: &[u8]) -> Result<(), PublishError> {
// Forward to your custom system.
Ok(())
}
async fn close(&self) {
// Release resources.
}
}
}
Register it the same way as the Kafka implementation. The event bus does not care about the underlying transport — only that the trait contract is satisfied.
Error Handling
Publish failures are non-fatal by default: the event bus logs the error and continues processing other events. If you need strict delivery guarantees, implement retry logic within your EventPublisher and surface errors through the PublishError type so the caller can decide how to handle them (e.g., by triggering a dead-letter queue or alerting).
Performance Considerations
- Batch publishing: For high-throughput scenarios, consider buffering events in memory and flushing in batches. The trait does not enforce synchronous publishing — implementations can use internal queues.
- Partitioner selection: Choose a partitioner that aligns with your consumer’s parallelism needs. Key-based partitioning ensures ordering per key; round-robin distributes load evenly.
- Backpressure: If your Kafka cluster is under heavy load, the publisher should apply backpressure (e.g., by blocking or dropping events with a configurable policy) rather than queuing indefinitely in memory.