Messaging Fundamentals
What messaging is and why it exists — synchronous vs asynchronous communication, tight vs loose coupling, and the job a message broker does.
Two services need to work together: an Orders service takes a customer's order, and a dozen other things have to happen because of it — charge a card, reserve stock, email a receipt, update a dashboard. How do those services talk? You can have the Orders service call each of the others directly, or you can put something in the middle that carries the messages for it. That "something in the middle" is what this whole track is about.
Think of it like a busy office. People could walk to each other's desks every time they need something — fast when the other person is there, useless when they're out. Or they could drop notes in a central mailroom that sorts and delivers them. The mailroom never forgets to deliver, doesn't care who's at their desk right now, and lets one announcement reach a hundred people at once. Messaging is software's mailroom.
Synchronous vs asynchronous — the idea
The first choice in any conversation between two services is whether the sender waits.
Synchronous communication is a phone call. You dial, the other person picks up, you talk, you get an answer, and only then do you hang up and move on. The caller is blocked the whole time — if the other side is slow or down, the caller is stuck. It is simple and immediate, and you get an answer right away.
Asynchronous communication is leaving a voicemail or sending a letter. You say your piece and move on with your day; the recipient picks it up when they can and acts on it later. The sender is not blocked, the recipient does not have to be available at the same moment, and the work happens in the background.
Top: a synchronous call — the caller waits for a reply. Bottom: an asynchronous message — the sender hands off and continues; delivery happens when the receiver is ready.
Neither is "better." A phone call is right when you genuinely need the answer now to continue (checking whether a credit card is valid). A voicemail is right when you just need the other side to eventually know something (a receipt should be emailed). Most real systems use both.
Tight vs loose coupling — the idea
When the Orders service calls the Payments service directly, it has to know Payments exists, where it lives, that it is up, and how to talk to it. If Payments moves, scales, slows down, or fails, Orders feels it immediately. That is tight coupling: the two are wired straight to each other, and a change or failure in one ripples into the other.
Now add a fifth, sixth, and seventh thing that must happen on every order — fulfillment, analytics, fraud checks, loyalty points. With direct calls, the Orders service grows a hard-wired dependency on each one, and every new consumer means editing and redeploying Orders.
Put a broker in the middle and the picture changes. Orders publishes "an order was placed" to a channel and stops caring who listens. Payments, fulfillment, and analytics each subscribe on their own terms. Orders does not know they exist; they do not know Orders exists. That is loose coupling: services depend on a shared channel, not on each other. New consumers slot in without touching the producer, and one service being down no longer takes the sender down with it.
Left: direct calls — the producer is wired to every consumer and must change when the set of consumers changes. Right: via a broker — the producer publishes to one channel; consumers come and go independently.
Pitfall: loose coupling is not free. Asynchronous, broker-mediated messaging adds a hop, makes end-to-end flows harder to trace, and means "done" no longer means "everyone who cares has finished." You trade immediate, all-or-nothing simplicity for independence and resilience. Reach for it when services must scale, fail, and evolve separately — not for a single call that needs an answer right now.
What a message broker is
A message broker is the piece of infrastructure in the middle. Its job is narrow and important: accept messages from producers, hold them in named channels, and deliver them to the right consumers — then get out of the way.
A broker does the work that every messaging system would otherwise reinvent:
- Decouples producers from consumers in space (they need not know each other's location), in time (they need not run at the same moment), and in number (one producer, many consumers — or the reverse).
- Buffers bursts so a fast producer does not overwhelm a slow consumer.
- Routes each message to the consumers that asked for it, by channel name and pattern.
- Applies delivery rules — try once, try until acknowledged, preserve order, allow replay — depending on the channel type.
A simple topology: producers send to channels through one broker, which delivers to the consumers that subscribed — over whatever transport each client speaks.
Why distinct messaging patterns exist
If a broker just "delivers messages," why does this track have four different patterns? Because one size does not fit all. Different jobs need different delivery contracts, and trying to serve them all with one mechanism makes every job worse.
Consider what changes from job to job:
| Question | Notifications | Order processing | Audit log | "Is the card valid?" |
|---|---|---|---|---|
| Does the sender need a reply? | No | No | No | Yes, now |
| Must every message survive a crash? | No | Yes | Yes | No |
| Should each message go to one worker or all subscribers? | All | One | Replayable by many | One responder |
| Does order matter? | No | Often | Yes | N/A |
| Can old messages be replayed later? | No | No | Yes | No |
No single delivery rule answers all of these well. A pattern that guarantees nothing is lost and lets you replay history is overkill (and slower) for a fleeting "user is typing" notification. A fire-and-forget broadcast is dangerous for a payment that must not be processed twice. So messaging gives you a small set of patterns, each a deliberate trade-off between speed, durability, ordering, and shape of delivery. Picking the right one is most of the skill — and the rest of this track teaches you how.
In KubeMQ
In KubeMQ: KubeMQ is the broker — a single engine that hosts every channel type. Your services connect once, then publish to and subscribe from named channels using one client SDK. The same connection speaks Events, Events Store, Queues, and RPC; the channel type you choose decides the delivery contract. The channels in the topology above are just KubeMQ channels of different types behind one address (localhost:50000).
Connecting and publishing a single message is the smallest possible "hello, broker." Here the Orders service sends one order event to a channel — it does not know or care who is listening.
package main
import (
"context"
"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()
err = client.SendEvent(ctx, kubemq.NewEvent().
SetChannel("order-placed").
SetBody([]byte(`{"orderId":"ORD-1234","status":"placed"}`)),
)
if err != nil {
log.Fatal(err)
}
log.Println("Message sent — Orders does not wait for any consumer")
}from kubemq.pubsub import Client as PubSubClient
from kubemq.pubsub import EventMessage
client = PubSubClient(address="localhost:50000")
client.send_event(
EventMessage(
channel="order-placed",
body=b'{"orderId":"ORD-1234","status":"placed"}',
)
)
print("Message sent — Orders does not wait for any consumer")
client.close()const { KubeMQClient } = require("kubemq-js");
const client = new KubeMQClient({ address: "localhost:50000" });
await client.sendEvent({
channel: "order-placed",
body: Buffer.from(JSON.stringify({ orderId: "ORD-1234", status: "placed" })),
});
console.log("Message sent — Orders does not wait for any consumer");PubSubClient client = PubSubClient.builder()
.address("localhost:50000")
.clientId("orders-service")
.build();
client.sendEventsMessage(EventMessage.builder()
.channel("order-placed")
.body("{\"orderId\":\"ORD-1234\",\"status\":\"placed\"}".getBytes())
.build());
System.out.println("Message sent — Orders does not wait for any consumer");
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
await client.SendEventAsync(new EventMessage
{
Channel = "order-placed",
Body = Encoding.UTF8.GetBytes("{\"orderId\":\"ORD-1234\",\"status\":\"placed\"}")
});
Console.WriteLine("Message sent — Orders does not wait for any consumer");val client = PubSubClient("localhost:50000")
client.sendEvent(EventMessage(
channel = "order-placed",
body = """{"orderId":"ORD-1234","status":"placed"}""".toByteArray()
))
println("Message sent — Orders does not wait for any consumer")
client.close()#include <kubemq/client.h>
#include <iostream>
auto client = kubemq::PubSubClient("localhost:50000");
kubemq::EventMessage event;
event.channel = "order-placed";
event.body = R"({"orderId":"ORD-1234","status":"placed"})";
client.sendEvent(event);
std::cout << "Message sent — Orders does not wait for any consumer" << std::endl;use kubemq::prelude::*;
use kubemq::EventBuilder;
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let event = EventBuilder::new()
.channel("order-placed")
.body(br#"{"orderId":"ORD-1234","status":"placed"}"#.to_vec())
.build();
client.send_event(event).await?;
println!("Message sent — Orders does not wait for any consumer");
client.close().await?;
Ok(())
}require 'kubemq'
client = KubeMQ::PubSubClient.new(address: "localhost:50000", client_id: "orders-service")
msg = KubeMQ::PubSub::EventMessage.new(
channel: "order-placed",
body: '{"orderId":"ORD-1234","status":"placed"}'
)
client.send_event(msg)
puts "Message sent — Orders does not wait for any consumer"
client.close{:ok, client} = KubeMQ.Client.start_link(address: "localhost:50000", client_id: "orders-service")
event = KubeMQ.Event.new(channel: "order-placed", body: ~s({"orderId":"ORD-1234","status":"placed"}))
case KubeMQ.Client.send_event(client, event) do
:ok -> IO.puts("Message sent — Orders does not wait for any consumer")
{:error, err} -> IO.puts("Send failed: #{err.message}")
end
KubeMQ.Client.close(client)That same client connection can also persist events, queue work for a single worker, or make a request and wait for a reply — each is one of the four patterns below.
How KubeMQ does this →
Events
Real-time pub/sub — fire-and-forget broadcast to every active subscriber, at-most-once.
Events Store
Persistent pub/sub — events stored on disk so late subscribers can replay from any position.
Queues
Point-to-point work distribution — one message to one worker, at-least-once with acknowledgment.
RPC
Request/reply — send a command or query and wait for a response through the broker.
Where to go next
You now have the vocabulary: synchronous vs asynchronous, tight vs loose coupling, what a broker does, and why patterns differ. Next, learn the small set of shapes every messaging system reduces to — then how delivery, ordering, scaling, and routing actually work.
Interaction Styles
The three shapes: pub/sub, point-to-point, and request/reply.
Delivery Guarantees
at-most-once, at-least-once, exactly-once; ack/nack, idempotency, and dead-letter queues.
Ordering & Replay
FIFO and per-key order, sequence numbers and offsets, replay, and event sourcing.
Scaling & Flow
Competing consumers vs fan-out, consumer groups, backpressure, and visibility.
Channels & Routing
Channels as named destinations, wildcards, and multicast routing.
Was this page helpful?
Messaging Patterns
A learning track for messaging on KubeMQ — the fundamentals, the four patterns (Events, Events Store, Queues, RPC), then composing them into architectures.
Interaction Styles
The three shapes every messaging pattern reduces to — pub/sub fan-out, point-to-point competing consumers, and request/reply round-trips.