Publish & Subscribe
Learn the basic pub/sub pattern with KubeMQ Events including multiple subscribers and message metadata.
What You Will Build
A publisher sending order events with metadata and tags, and two subscribers both receiving every event (fan-out).
One publisher fans out every order event to both subscribers at-most-once.
Prerequisites
- KubeMQ server running on
localhost:50000 - SDK installed (Getting Started)
Steps
Set Up the Publisher
The publisher sends order events with a JSON body, metadata, and tags for downstream filtering.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/kubemq-io/kubemq-go/v2"
)
func main() {
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
orders := []struct {
ID string
Amount float64
Region string
}{
{"ORD-001", 99.99, "us-east"},
{"ORD-002", 249.50, "eu-west"},
{"ORD-003", 15.00, "us-east"},
}
for _, order := range orders {
body := fmt.Sprintf(
`{"orderId":"%s","amount":%.2f,"region":"%s"}`,
order.ID, order.Amount, order.Region)
err = client.SendEvent(ctx, kubemq.NewEvent().
SetChannel("order-events").
SetMetadata("order.created").
SetBody([]byte(body)).
SetTags(map[string]string{"region": order.Region}),
)
if err != nil {
log.Printf("Failed to send event for %s: %v", order.ID, err)
continue
}
log.Printf("Published order event: %s", order.ID)
time.Sleep(500 * time.Millisecond)
}
}import json
import time
from kubemq.pubsub import Client as PubSubClient
from kubemq.pubsub import EventMessage
orders = [
{"orderId": "ORD-001", "amount": 99.99, "region": "us-east"},
{"orderId": "ORD-002", "amount": 249.50, "region": "eu-west"},
{"orderId": "ORD-003", "amount": 15.00, "region": "us-east"},
]
client = PubSubClient(address="localhost:50000")
for order in orders:
try:
client.send_event(
EventMessage(
channel="order-events",
metadata="order.created",
body=json.dumps(order).encode("utf-8"),
tags={"region": order["region"]},
)
)
print(f"Published order event: {order['orderId']}")
except Exception as e:
print(f"Failed to send event for {order['orderId']}: {e}")
time.sleep(0.5)
client.close()const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
const orders = [
{ orderId: "ORD-001", amount: 99.99, region: "us-east" },
{ orderId: "ORD-002", amount: 249.50, region: "eu-west" },
{ orderId: "ORD-003", amount: 15.00, region: "us-east" },
];
for (const order of orders) {
try {
await client.sendEvent({
channel: "order-events",
metadata: "order.created",
body: Buffer.from(JSON.stringify(order)),
tags: { region: order.region },
});
console.log(`Published order event: ${order.orderId}`);
} catch (err) {
console.error(`Failed to send event for ${order.orderId}:`, err);
}
await new Promise((r) => setTimeout(r, 500));
}PubSubClient client = PubSubClient.builder()
.address("localhost:50000")
.clientId("order-publisher")
.build();
String[][] orders = {
{"ORD-001", "99.99", "us-east"},
{"ORD-002", "249.50", "eu-west"},
{"ORD-003", "15.00", "us-east"},
};
for (String[] order : orders) {
String body = String.format(
"{\"orderId\":\"%s\",\"amount\":%s,\"region\":\"%s\"}",
order[0], order[1], order[2]);
try {
client.sendEventsMessage(EventMessage.builder()
.channel("order-events")
.metadata("order.created")
.body(body.getBytes())
.tags(Map.of("region", order[2]))
.build());
System.out.println("Published order event: " + order[0]);
} catch (Exception e) {
System.err.println("Failed to send: " + e.getMessage());
}
Thread.sleep(500);
}
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
var orders = new[]
{
new { Id = "ORD-001", Amount = 99.99, Region = "us-east" },
new { Id = "ORD-002", Amount = 249.50, Region = "eu-west" },
new { Id = "ORD-003", Amount = 15.00, Region = "us-east" },
};
foreach (var order in orders)
{
var body = $"{{\"orderId\":\"{order.Id}\",\"amount\":{order.Amount},\"region\":\"{order.Region}\"}}";
await client.SendEventAsync(new EventMessage
{
Channel = "order-events",
Metadata = "order.created",
Body = Encoding.UTF8.GetBytes(body),
Tags = new Dictionary<string, string> { ["region"] = order.Region },
});
Console.WriteLine($"Published order event: {order.Id}");
await Task.Delay(500);
}val client = PubSubClient("localhost:50000")
data class Order(val id: String, val amount: Double, val region: String)
val orders = listOf(
Order("ORD-001", 99.99, "us-east"),
Order("ORD-002", 249.50, "eu-west"),
Order("ORD-003", 15.00, "us-east"),
)
for (order in orders) {
val body = """{"orderId":"${order.id}","amount":${order.amount},"region":"${order.region}"}"""
client.sendEvent(EventMessage(
channel = "order-events",
metadata = "order.created",
body = body.toByteArray(),
tags = mapOf("region" to order.region),
))
println("Published order event: ${order.id}")
Thread.sleep(500)
}
client.close()#include <kubemq/client.h>
#include <iostream>
#include <thread>
#include <chrono>
auto client = kubemq::PubSubClient("localhost:50000");
struct Order { std::string id; double amount; std::string region; };
std::vector<Order> orders = {
{"ORD-001", 99.99, "us-east"},
{"ORD-002", 249.50, "eu-west"},
{"ORD-003", 15.00, "us-east"},
};
for (const auto& order : orders) {
kubemq::EventMessage event;
event.channel = "order-events";
event.metadata = "order.created";
event.body = "{\"orderId\":\"" + order.id + "\",\"amount\":" +
std::to_string(order.amount) + ",\"region\":\"" + order.region + "\"}";
event.tags["region"] = order.region;
client.sendEvent(event);
std::cout << "Published order event: " << order.id << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}use kubemq::prelude::*;
use kubemq::EventBuilder;
use std::collections::HashMap;
use std::time::Duration;
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let orders = [
("ORD-001", 99.99, "us-east"),
("ORD-002", 249.50, "eu-west"),
("ORD-003", 15.00, "us-east"),
];
for (id, amount, region) in orders {
let body = format!(
r#"{{"orderId":"{id}","amount":{amount},"region":"{region}"}}"#
);
let mut tags = HashMap::new();
tags.insert("region".to_string(), region.to_string());
let event = EventBuilder::new()
.channel("order-events")
.metadata("order.created")
.body(body.into_bytes())
.tags(tags)
.build();
match client.send_event(event).await {
Ok(_) => println!("Published order event: {id}"),
Err(e) => eprintln!("Failed to send event for {id}: {e}"),
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
client.close().await?;
Ok(())
}require 'kubemq'
require 'json'
client = KubeMQ::PubSubClient.new(address: 'localhost:50000', client_id: 'order-publisher')
orders = [
{ orderId: 'ORD-001', amount: 99.99, region: 'us-east' },
{ orderId: 'ORD-002', amount: 249.50, region: 'eu-west' },
{ orderId: 'ORD-003', amount: 15.00, region: 'us-east' },
]
orders.each do |order|
begin
msg = KubeMQ::PubSub::EventMessage.new(
channel: 'order-events',
metadata: 'order.created',
body: order.to_json,
tags: { 'region' => order[:region] }
)
client.send_event(msg)
puts "Published order event: #{order[:orderId]}"
rescue KubeMQ::Error => e
puts "Failed to send event for #{order[:orderId]}: #{e.message}"
end
sleep 0.5
end
client.close{:ok, client} =
KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-publisher")
orders = [
%{order_id: "ORD-001", amount: 99.99, region: "us-east"},
%{order_id: "ORD-002", amount: 249.50, region: "eu-west"},
%{order_id: "ORD-003", amount: 15.00, region: "us-east"}
]
for order <- orders do
body =
~s({"orderId":"#{order.order_id}","amount":#{order.amount},"region":"#{order.region}"})
event =
KubeMQ.Event.new(
channel: "order-events",
metadata: "order.created",
body: body,
tags: %{"region" => order.region}
)
case KubeMQ.Client.send_event(client, event) do
:ok -> IO.puts("Published order event: #{order.order_id}")
{:error, err} -> IO.puts("Failed to send event for #{order.order_id}: #{err.message}")
end
Process.sleep(500)
end
KubeMQ.Client.close(client)Set Up Subscriber A
The notification service processes every order event.
package main
import (
"context"
"fmt"
"log"
"github.com/kubemq-io/kubemq-go/v2"
)
func main() {
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
sub, err := client.SubscribeToEvents(ctx, "order-events", "",
kubemq.WithOnEvent(func(event *kubemq.Event) {
fmt.Printf("[Notification] New order: %s | metadata: %s\n",
string(event.Body), event.Metadata)
}),
kubemq.WithOnError(func(err error) {
log.Println("[Notification] Error:", err)
}),
)
if err != nil {
log.Fatal(err)
}
defer sub.Unsubscribe()
log.Println("[Notification] Service listening...")
<-ctx.Done()
}import time
from kubemq.pubsub import Client as PubSubClient
from kubemq.pubsub import EventsSubscription, CancellationToken
def on_event(event):
print(f"[Notification] New order: "
f"{event.body.decode('utf-8')} | metadata: {event.metadata}")
client = PubSubClient(address="localhost:50000")
client.subscribe_to_events(
subscription=EventsSubscription(
channel="order-events",
on_receive_event_callback=on_event,
on_error_callback=lambda e: print(f"[Notification] Error: {e}"),
),
cancel=CancellationToken(),
)
print("[Notification] Service listening...")
time.sleep(300)
client.close()const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
client.subscribeToEvents({
channel: "order-events",
onEvent: (msg) =>
console.log(
`[Notification] New order: ${Buffer.from(msg.body).toString()}`
),
onError: (err) => console.error("[Notification] Error:", err.message),
});
console.log("[Notification] Service listening...");PubSubClient client = PubSubClient.builder()
.address("localhost:50000")
.clientId("notification-service")
.build();
client.subscribeToEvents(EventsSubscription.builder()
.channel("order-events")
.onReceiveEventCallback(event ->
System.out.println("[Notification] New order: "
+ new String(event.getBody())))
.onErrorCallback(err ->
System.err.println("[Notification] Error: " + err.getMessage()))
.build());
System.out.println("[Notification] Service listening...");
Thread.sleep(300_000);
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
Console.WriteLine("[Notification] Service listening...");
await foreach (var msg in client.SubscribeToEventsAsync(
new EventsSubscription { Channel = "order-events" }))
{
Console.WriteLine($"[Notification] New order: "
+ $"{Encoding.UTF8.GetString(msg.Body.Span)}");
}val client = PubSubClient("localhost:50000")
client.subscribeToEvents(
channel = "order-events",
onEvent = { event ->
println("[Notification] New order: ${String(event.body)}")
},
onError = { err ->
System.err.println("[Notification] Error: ${err.message}")
}
)
println("[Notification] Service listening...")
Thread.sleep(300_000)
client.close()auto client = kubemq::PubSubClient("localhost:50000");
client.subscribeToEvents("order-events", "",
[](const kubemq::Event& event) {
std::cout << "[Notification] New order: " << event.body << std::endl;
},
[](const std::string& err) {
std::cerr << "[Notification] Error: " << err << std::endl;
}
);
std::cout << "[Notification] Service listening..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(300));use kubemq::prelude::*;
use kubemq::Subscription;
use std::time::Duration;
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
// Subscribe with an empty group -- every subscriber receives every event
let sub: Subscription = client
.subscribe_to_events(
"order-events",
"",
|event| {
Box::pin(async move {
println!(
"[Notification] New order: {} | metadata: {}",
String::from_utf8_lossy(&event.body),
event.metadata
);
})
},
None,
)
.await?;
println!("[Notification] Service listening...");
tokio::time::sleep(Duration::from_secs(300)).await;
sub.unsubscribe().await;
client.close().await?;
Ok(())
}require 'kubemq'
client = KubeMQ::PubSubClient.new(address: 'localhost:50000', client_id: 'notification-service')
cancel = KubeMQ::CancellationToken.new
sub = KubeMQ::PubSub::EventsSubscription.new(channel: 'order-events')
client.subscribe_to_events(
sub,
cancellation_token: cancel,
on_error: ->(e) { puts "[Notification] Error: #{e.message}" }
) do |event|
puts "[Notification] New order: #{event.body} | metadata: #{event.metadata}"
end
puts '[Notification] Service listening...'
sleep 300
cancel.cancel
client.close{:ok, client} =
KubeMQ.Client.start_link(address: "localhost:50000", client_id: "notification-service")
{:ok, _sub} =
KubeMQ.Client.subscribe_to_events(client, "order-events",
on_event: fn event ->
IO.puts("[Notification] New order: #{event.body} | metadata: #{event.metadata}")
end,
on_error: fn err -> IO.puts("[Notification] Error: #{err.message}") end
)
IO.puts("[Notification] Service listening...")
Process.sleep(300_000)
KubeMQ.Client.close(client)Set Up Subscriber B
A second subscriber receives the same events independently for analytics processing.
sub, err := client.SubscribeToEvents(ctx, "order-events", "",
kubemq.WithOnEvent(func(event *kubemq.Event) {
fmt.Printf("[Analytics] Processing: %s\n", string(event.Body))
}),
kubemq.WithOnError(func(err error) {
log.Println("[Analytics] Error:", err)
}),
)def on_event(event):
print(f"[Analytics] Processing: {event.body.decode('utf-8')}")
client.subscribe_to_events(
subscription=EventsSubscription(
channel="order-events",
on_receive_event_callback=on_event,
on_error_callback=lambda e: print(f"[Analytics] Error: {e}"),
),
cancel=CancellationToken(),
)
print("[Analytics] Service listening...")client.subscribeToEvents({
channel: "order-events",
onEvent: (msg) =>
console.log(
`[Analytics] Processing: ${Buffer.from(msg.body).toString()}`
),
onError: (err) => console.error("[Analytics] Error:", err.message),
});
console.log("[Analytics] Service listening...");client.subscribeToEvents(EventsSubscription.builder()
.channel("order-events")
.onReceiveEventCallback(event ->
System.out.println("[Analytics] Processing: "
+ new String(event.getBody())))
.onErrorCallback(err ->
System.err.println("[Analytics] Error: " + err.getMessage()))
.build());
System.out.println("[Analytics] Service listening...");Console.WriteLine("[Analytics] Service listening...");
await foreach (var msg in client.SubscribeToEventsAsync(
new EventsSubscription { Channel = "order-events" }))
{
Console.WriteLine($"[Analytics] Processing: "
+ $"{Encoding.UTF8.GetString(msg.Body.Span)}");
}client.subscribeToEvents(
channel = "order-events",
onEvent = { event ->
println("[Analytics] Processing: ${String(event.body)}")
},
onError = { err ->
System.err.println("[Analytics] Error: ${err.message}")
}
)
println("[Analytics] Service listening...")client.subscribeToEvents("order-events", "",
[](const kubemq::Event& event) {
std::cout << "[Analytics] Processing: " << event.body << std::endl;
},
[](const std::string& err) {
std::cerr << "[Analytics] Error: " << err << std::endl;
}
);
std::cout << "[Analytics] Service listening..." << std::endl;// Same channel, empty group -- this subscriber receives the same events
// independently of the notification service.
let sub: Subscription = client
.subscribe_to_events(
"order-events",
"",
|event| {
Box::pin(async move {
println!(
"[Analytics] Processing: {}",
String::from_utf8_lossy(&event.body)
);
})
},
None,
)
.await?;
println!("[Analytics] Service listening...");# Same channel, no group -- this subscriber receives the same events
# independently of the notification service.
sub = KubeMQ::PubSub::EventsSubscription.new(channel: 'order-events')
client.subscribe_to_events(
sub,
cancellation_token: cancel,
on_error: ->(e) { puts "[Analytics] Error: #{e.message}" }
) do |event|
puts "[Analytics] Processing: #{event.body}"
end
puts '[Analytics] Service listening...'# Same channel, no group -- this subscriber receives the same events
# independently of the notification service.
{:ok, _sub} =
KubeMQ.Client.subscribe_to_events(client, "order-events",
on_event: fn event ->
IO.puts("[Analytics] Processing: #{event.body}")
end,
on_error: fn err -> IO.puts("[Analytics] Error: #{err.message}") end
)
IO.puts("[Analytics] Service listening...")Observe Fan-Out
Start both subscribers in separate terminals, then run the publisher. Both subscribers receive every event. No acknowledgment needed.
Notification Service output:
[Notification] New order: {"orderId":"ORD-001","amount":99.99,"region":"us-east"}
[Notification] New order: {"orderId":"ORD-002","amount":249.50,"region":"eu-west"}
[Notification] New order: {"orderId":"ORD-003","amount":15.00,"region":"us-east"}Analytics Service output:
[Analytics] Processing: {"orderId":"ORD-001","amount":99.99,"region":"us-east"}
[Analytics] Processing: {"orderId":"ORD-002","amount":249.50,"region":"eu-west"}
[Analytics] Processing: {"orderId":"ORD-003","amount":15.00,"region":"us-east"}How Fan-Out Works
KubeMQ delivers each published event to every active subscriber independently — no acknowledgment, no ordering coordination between subscribers.
Key Points
- No acknowledgment needed — fire-and-forget delivery
- Missed messages — if a subscriber is offline, events sent while it was disconnected are lost
- Metadata and tags — propagated to all subscribers for downstream filtering and routing
If a subscriber is slow to process events, messages may be dropped after the write deadline (default 2 seconds). See Handle Slow Consumers for mitigation strategies.
Next Steps
Was this page helpful?