Skip to content

PubSub

In the world of microservice architecture, the event driven approach is indistinguishable and zero framework has built-in support for the accessing the message queue systems.

At times the app we develop has to rely on external service signal through API call and through events and let us decide the state of the data/action.

Alike, other built-in solutions, the PubSub clients will be automatically added to container once the needed service configurations available.

zero app tries to connect, captures the ping status and attaches to the app life-time, otherwise app explicitly calls the PubSub is disabled.

zig
try ctx.pubsub.Publish("zero", "publisher 1 says hello! via NATS");

try app.addPubSubSubscription("zero", onMessage);
zig
ctx.KF.publish(ctx, "topic", "message-key", "payload"); #publishes message to a topic on the subscribed client

app.addKafkaSubscription("topic", subscriberHandler); #listens for upcoming event and injects into subscriber handler for further actions.
zig
ctx.MQ.Publish("topic", "payload"); #publishes message to a topic on the subscribed client

app.addSubscription("topic", subscriber-handler); #listens for upcoming event and injects into subscriber handler.
zig
ctx.pubsub.Publish("subject", "payload"); #publishes message to a NATS subject

app.addPubSubSubscription("subject", subscriber-handler); #listens for upcoming event and injects into subscriber handler.

Resilience

All brokers degrade gracefully when the broker is unhealthy:

  • Reconnect & re-subscribe — on a broker drop the client reconnects and re-subscribes automatically; no restart required.
  • Retry — a failing message handler is retried up to 3× with a 500 ms backoff before the message is given up.
  • Dead-letter — poison messages that keep failing are dead-lettered to a side topic so they don't block the stream:
    • Kafka: <topic>__dlq
    • MQTT / NATS / Redis: <topic>/dlq

Each dead-lettered message increments the app_pubsub_dlq_total counter (labels topic, consumer) — see Observability. The X-Correlation-ID set by the inbound request is propagated into Kafka/NATS record headers and the outbound HTTP client, so a single id flows across services and brokers.

Support

zero framework supports following brokers to publish and subscriber to.

Message BrokerSupport
Kafka
MQTT
NATS
Redis

Configurations

This list of configurations help the developer to prefer either Kafka or MQTT pubsub per instance.

kafka configRemarksDefault* / OthersRequired
PUBSUB_BACKENDChoose kafka, mqtt or nats as pubsub mqNone (KAFKA / MQTT / NATS)Yes
PUBSUB_BROKERSet the addresses of the Kafka clusterlocalhost:9092 [one or multiple host address]Yes
PUBSUB_OFFSETAllow the subscription to begin fromNoneNo
CONSUMER_IDUnique identifier of the subscribing groupNoneNo
KAFKA_BATCH_SIZENumber of messages added in one messageSet100*No
KAFKA_BATCH_BYTESThe overall size of the messageSet that includes one or more message1048576*No
KAFKA_BATCH_TIMEOUTMessageSet flush timeout1000*No
KAFKA_SECURITY_PROTOCOLProtocol used to communicate with kafka clusterplaintext*,ssl,sasl_plaintext, sasl_sslNo
KAFKA_SASL_MECHANISMSASL Authentication mechanismplain*No
KAFKA_SASL_USERNAMESASL Authentication usernameApplicable only with sasl plain modeNo
KAFKA_SASL_PASSWORDSASL Authentication passwordApplicable only with sasl plain modeNo
KAFKA_TLS_CERT_FILEPath to client's public key (PEM) used for authentication.NoneNo
KAFKA_TLS_KEY_FILEPath to client's private key (PEM) used for authentication.NoneNo
KAFKA_TLS_CA_CERT_FILEPath to client's CA cert (PEM) used for authentication.NoneNo
KAFKA_TLS_INSECURE_SKIP_VERIFYSkip client certificate verificationstrue*, falseNo
MQTT configRemarksDefault* / OthersRequired
MQTT_PROTOCOLProtocol used to communicate with MQTT servertcp*Yes
MQTT_HOSTIP Address of the MQTT ServerNoneYes
MQTT_PORTPort of the MQTT ServerNoneYes
MQTT_CLIENT_ID_SUFFIXClient ID name for the debug messagesNoneNo
NATS configRemarksDefault* / OthersRequired
PUBSUB_BACKENDSet to NATS to use the NATS brokerNATSYes
PUBSUB_BROKERNATS server URLnats://localhost:4222Yes
NATS_STREAMJetStream stream nameNoneNo
NATS_SUBJECTSComma-separated subjects to subscribe toNoneNo
NATS_CONSUMERDurable consumer nameNoneNo
NATS_MAX_WAITMax wait (ms) for a pull subscriptionNoneNo
NATS_MAX_PULL_WAITMax pull wait (ms)5000*No
NATS_CREDS_FILEPath to a NATS credentials fileNoneNo
Redis configRemarksDefault* / OthersRequired
PUBSUB_BACKENDSet to REDIS to use the Redis brokerREDISYes
REDIS_HOSTRedis server host127.0.0.1Yes
REDIS_PORTRedis server port6379Yes
REDIS_USERRedis usernameNoneNo
REDIS_PASSWORDRedis passwordNoneNo
REDIS_DBRedis logical database0No

Redis

Select Redis with PUBSUB_BACKEND=REDIS. Redis Pub/Sub uses the same REDIS_* connection settings as the cache/KV store.

Publish through the unified ctx.pubsub interface (works across Kafka, MQTT, NATS and Redis); subscribe with app.addPubSubSubscription(...).

In the handler the message is available on ctx.message.?.redis, which exposes .subject and .payload ([]const u8).

zig
// from a handler or cron job
try ctx.pubsub.Publish("zero", "publisher 1 says hello! via Redis");
zig
fn onMessage(ctx: *Context) !void {
    if (ctx.message) |message| {
        const m = message.redis;
        ctx.info(m.payload); // m.subject and m.payload are []const u8
    }
}

// register at startup
try app.addPubSubSubscription("zero", onMessage);

NATS

Select NATS with PUBSUB_BACKEND=NATS.

Publish through the unified ctx.pubsub interface (works across Kafka, MQTT, NATS and Redis); subscribe with app.addPubSubSubscription(...).

In the handler the message is available on ctx.message.?.nats, which exposes .subject and .payload ([]const u8).

zig
// from a handler or cron job
try ctx.pubsub.Publish("zero", "publisher 1 says hello! via NATS");
zig
fn onMessage(ctx: *Context) !void {
    if (ctx.message) |message| {
        const m = message.nats;
        ctx.info(m.payload); // m.subject and m.payload are []const u8
    }
}

// register at startup
try app.addPubSubSubscription("zero", onMessage);

Kafka

Select Kafka with PUBSUB_BACKEND=KAFKA.

Publish through the ctx.KF interface — resolve the topic handler once with ctx.KF.getTopicHandler(ctx, topic), then ctx.KF.publish(ctx, topic, key, payload); subscribe with app.addKafkaSubscription(...).

In the handler the message is available on ctx.message.?.kafka, which exposes .topic and .payload (?[]const u8).

zig
// from a handler or cron job
const topic = try ctx.KF.getTopicHandler(ctx, "zero-topic");
try ctx.KF.publish(ctx, topic, "publisher-1", "publisher message!");
zig
fn subscribeTask(ctx: *Context) !void {
    if (ctx.message) |message| {
        const k = message.kafka;
        ctx.info(k.payload); // k.topic and k.payload are []const u8 (?[]const u8)
    }
}

// register at startup
try app.addKafkaSubscription("zero-topic", subscribeTask);

MQTT

Select MQTT with PUBSUB_BACKEND=MQTT.

Publish through the ctx.MQ interface (ctx.MQ.Publish(topic, payload)); subscribe with app.addSubscription(...).

In the handler the message is available on ctx.message.?.mqtt, which exposes .topic and .payload (?[]const u8).

zig
// from a handler or cron job
const id = try ctx.MQ.Publish("zero", "publisher 1 says hello!");
zig
fn subscribeTask(ctx: *Context) !void {
    if (ctx.message) |message| {
        const mq = message.mqtt;
        ctx.info(mq.payload); // mq.topic and mq.payload are []const u8 (?[]const u8)
    }
}

// register at startup
try app.addSubscription("zero", subscribeTask);