00 / 06 Why talk with messages
  1. ← Talking with messages: reliability in distributed systems
  2. 00 Why talk with messages
  3. 01 The three families of messages
  4. 02 Broker versus log
  5. 03 Delivery semantics
  6. 04 Ordering and idempotency
  7. 05 Dual write and the transactional outbox
Talking with messages: reliability in distributed systems · 00 / 06

Why talk with messages

When one service calls five others to confirm an order, a single one going down makes everything fail. The asynchronous message changes that: here is why, and what it really promises.

You click “Pay”. Behind that click, your order service must notify payment, reserve stock, schedule shipping, send a confirmation email and credit the loyalty program. The question that opens this whole course starts right here: how do these services talk to each other without falling together?

The direct call, or the phone call

The most natural way to make two services talk is the direct call. Your order service calls the payment service, just as you would make a phone call: you dial the number, and you wait for the other side to pick up and answer before hanging up.

As long as everyone picks up quickly, this is perfect. But look at what happens when your order has to notify all five services, one by one, waiting for each reply:

Synchronous call chain: the order waits for each service before moving to the next

Two troubles arise. First, time: if each one takes a hundred milliseconds, the user waits for the sum, so half a second, just to see “Order accepted”. Second, fragility: if the email service is down, the call fails, and the whole order fails with it, even though the email is just a comfort detail. One weak link breaks the entire chain.

The message, or the mailbox

Change the image. Instead of phoning, your order writes a note and drops it into a mailbox. It leaves at once, without waiting for anyone to have read it. Each service checks the mailbox when it can and processes the note at its own pace.

That is what a message is: a piece of information dropped somewhere, that the sender does not wait to see processed. Asynchronous communication Asynchronous messaging An exchange mode where the sender drops a message and carries on without waiting for the recipient to process it. The message waits in a queue or a log until a consumer picks it up. It contrasts with synchronous communication, where the caller stays blocked until the reply arrives. is this exchange mode where you drop and carry on, as opposed to the synchronous call where you stay blocked until the reply.

The email service is down? Its note waits in the mailbox. The order, meanwhile, is already accepted. The email service will handle its mail as soon as it restarts. The weak link no longer breaks anything: it simply falls behind, on its own, in its corner.

Three ways of being coupled

To understand what the message buys us, we must name what it frees us from: coupling. Two services are coupled when one cannot work properly without some property of the other. There are three useful forms.

The direct synchronous call accumulates all three. The asynchronous message loosens them: it inserts a mailbox (a queue, a log) that accepts the drop even when the recipient is absent. The sender no longer needs to know who will read, nor when, nor even whether anyone will read at all.

Command or event: two intentions, two messages

Not all messages are alike. Two very different intentions hide behind the word “message”, and confusing them is the first classic mistake.

A command message Command message A message that expresses an intention, an order addressed to a specific recipient asking it to do something (for example "Charge the payment"). It is named in the imperative, in principle has a single handler, and the sender expects an effect to happen. To be distinguished from an event message, which states a fact that already occurred. expresses an order addressed to a specific recipient: “Charge the payment”. It is named in the imperative, in principle targets a single handler, and the sender expects an effect to happen. It is still close to the phone call, but dropped in a mailbox.

An event message Event message A message that announces a fact that already happened (for example "Order paid"). It is named in the past tense, broadcast to whoever wants to listen, and the sender does not know who consumes it, or even whether anyone does. Several subscribers can react to the same event. To be distinguished from a command message, which asks a single recipient for a future action. announces a fact that already happened: “Order paid”. It is named in the past tense, it is broadcast to whoever wants to listen, and the sender does not know who consumes it, or even whether anyone does. Stock, shipping, email and loyalty can all react to the same event, each on its own side.

This switch runs deep. With commands, your order still knows who to call: it drives. With events, it just announces “I am paid” and loses interest in the rest: it is the others who subscribe. This is the ultimate degree of decoupling, and it is what lets you add a sixth service tomorrow without touching the order service.

A single OrderPaid event, several independent subscribers: the sender does not know them

Play with coupling

The component below puts both worlds side by side. You place an order that must notify five services. Choose the mode (synchronous or asynchronous), take a service down by clicking on it, then watch two things: how long the caller stays blocked, and what happens to each service.

Place an order
Mode

Services to notify

  • Payment50 ms
  • Stock30 ms
  • Shipping20 ms
  • Email10 ms
  • Loyalty15 ms

Three questions to ask yourself while playing:

  • In synchronous mode, what happens to the blocking time when you add healthy services? And in asynchronous mode?
  • Take the email service down. Why does synchronous mode make the whole order fail, while asynchronous mode still accepts it?
  • In synchronous mode, take payment down (the first in the list). What happens to the services placed after it? Why do they stay “not reached”?

In code: “sending a message” in two frameworks

The idea of command versus event is not just a mental view: messaging frameworks make it explicit right down to their method names. Look at the same intention in two tools, one in Rust, the other in .NET.

With Hexeract, a messaging framework in Rust, a command awaits a result and an event simply gets published:

// Command: an addressed order, we await the result.
impl Command for ProcessPayment {
    type Output = PaymentId;
}
let payment_id = mediator.send(ProcessPayment { order_id, amount_cents }).await?;

// Event: a past fact, broadcast. The sender does not know who listens.
impl Event for OrderPaid {
    const EVENT_TYPE: &'static str = "orders.paid";
}
mediator.publish(OrderPaid { order_id }).await?;

With Wolverine, its .NET counterpart, the distinction is read in the choice of the bus method:

// Command: execute now and await the result.
var status = await bus.InvokeAsync<PaymentStatus>(new ProcessPayment(orderId, amountCents));

// Event: publish to whoever wants to listen, without waiting.
await bus.PublishAsync(new OrderPaid(orderId));

The pair is the same on both sides: a verb that awaits a result for the command (send, InvokeAsync), a verb that broadcasts without waiting for the event (publish, PublishAsync). Remember the pattern, not the syntax: it is the message’s intention that chooses the method, never the other way around.

Exercises

Grab a pen and paper. The solutions are right below, to be looked at only after you have tried.

Exercise 1: command or event?

Classify each of these four messages as a command or an event, relying on the verb tense and on the recipient: (a) “Charge the payment”, (b) “Order paid”, (c) “Send the confirmation email”, (d) “Stock reserved”.

Exercise 2: how long does the user wait?

Five services must be notified, with processing times of 50, 30, 20, 10 and 15 milliseconds. How long does the user wait in synchronous mode, where the caller handles the services in series? And in asynchronous mode, where dropping a message costs about 5 milliseconds?

In one sentence

Talking with messages means replacing a direct call, which couples two services in time and in failure, with an asynchronous drop that decouples them: the sender carries on with its work, and we distinguish the command, which orders and waits, from the event, which states and broadcasts.

Quiz
  1. 1. Why is a direct synchronous call fragile when a called service goes down?

  2. 2. What is the essential difference between a command message and an event message?

  3. 3. In asynchronous mode, what happens to the message destined for a momentarily down service?

Towards the next chapter

We contrasted two families of messages: the command that orders, the event that states. But one common case is missing: when a service only needs to ask for information, without changing or ordering anything. “What is this customer’s loyalty balance?” is neither an order nor a past fact. It is a third family, the query, and it is through it that the next chapter will complete the taxonomy of messages before we tackle delivery guarantees.

Sources

  • Hohpe, G. & Woolf, B. (2003). Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions. Addison-Wesley. Publisher reference
  • Hohpe, G. & Woolf, B. “Command Message” and “Event Message”. Enterprise Integration Patterns. Command Message, Event Message
  • Wolverine. Message Bus Basics: InvokeAsync, SendAsync, PublishAsync. Official documentation. wolverinefx.net