Producers, Consumers, Brokers, and Event Logs

LESSON

Event-Driven Architecture and Streaming Foundations

003 30 min intermediate

Producers, Consumers, Brokers, and Event Logs

By the end of this lesson, you will be able to...

  • Trace how a produced domain fact moves through a broker or event log to several consumers.

  • Explain which state is owned by the producer, broker, and consumer at each step.

  • Predict what can still fail after the broker has accepted an event.

Idea in one sentence: A broker or event log does not make an event-driven system magic; it gives producers and consumers a shared place to hand off facts while each side keeps its own responsibilities.

Core Insight

Suppose the order service has just stored order O-812.

From the previous lesson, we know the useful message is a fact:

OrderPlaced

Payment wants to react. Email wants to react. Analytics wants to react. The naive picture is simple:

order service publishes OrderPlaced
everyone receives it
done

That picture hides the mechanism.

Who has the event before the broker accepts it? Who has it after? What if payment is offline? What if email handles the event twice? What if analytics starts later and wants old events?

The core idea in this lesson is that an event flow has moving parts with different ownership:

producer -> broker or event log -> consumer

The producer owns the fact it is allowed to publish. The broker or log owns durable handoff and delivery position. Each consumer owns its reaction, its progress, and its side effects.

If you blur those responsibilities, event-driven design becomes hard to debug. A team may say "the event was published" when the producer only tried to send it. Another team may say "payment received the event" when the broker only made it available. Those are different states.

The Situation

Our checkout system has four services:

Order service
Payment service
Email service
Analytics service

The order service owns the order record. When it commits order O-812, it can publish this integration event:

{
  "type": "OrderPlaced",
  "event_id": "evt-1001",
  "order_id": "O-812",
  "customer_id": "C-44",
  "total": "42.00",
  "currency": "EUR",
  "occurred_at": "2026-07-08T10:15:00Z"
}

Payment should attempt capture. Email should prepare a receipt. Analytics should count the order.

The first design a team often imagines is direct fanout:

Order service -> Payment
Order service -> Email
Order service -> Analytics

That can work in small systems, but it couples order creation to every downstream service. If email is down, does order placement fail? If analytics is slow, does checkout wait? If payment needs retries, does the order service own those retries?

An event broker or event log adds a handoff point:

Order service -> broker/log -> Payment
                         -> Email
                         -> Analytics

This does not remove coupling. It changes where the coupling is visible.

The Moving Parts

Plain meaning:

A producer is the part that writes a message into the event system.

In this scenario:

The order service is the producer for OrderPlaced.

Technical name:

Producer means "the component that sends or appends the event." It does not mean "the owner of every reaction." The order service owns the fact that an order was placed. It does not own payment capture, receipt delivery, or analytics counting.

Plain meaning:

A consumer is the part that reads the message and decides what to do with it.

In this scenario:

Payment, email, and analytics are consumers of OrderPlaced.

Technical name:

Consumer means "the component that observes the event and advances its own state." A consumer may update a database, send another command, write a projection, or ignore the event if it is not relevant.

Plain meaning:

A broker is the handoff system between producers and consumers.

In this scenario:

The broker accepts OrderPlaced, stores enough information to deliver it, and makes it available to interested consumers.

Technical name:

Broker is the middleware that receives messages and coordinates delivery, routing, retention, acknowledgments, or offsets. Different products do this differently. This lesson stays at the application-architecture level.

Plain meaning:

An event log is an ordered record of events that consumers can read from a position.

In this scenario:

OrderPlaced is appended at position 1057. Payment may read up to 1057, email may be behind at 1053, and analytics may replay from 1000.

Technical name:

An event log is an append-oriented stream. Consumers track progress through offsets or similar positions. A log makes replay and independent consumer progress easier to reason about, but it also makes retention, ordering scope, and duplicate handling explicit.

The Mechanism Step by Step

Here is the small mechanism.

Starting state:

Order database:
  O-812 does not exist yet

Broker/log:
  topic or stream: order-events
  latest position: 1056

Consumers:
  Payment offset: 1056
  Email offset: 1056
  Analytics offset: 1050

The customer clicks "Place order."

Step 1: the order service commits its own state.

Order database:
  O-812 = PLACED

At this moment, the order service owns a new fact. No consumer has seen it yet.

Step 2: the order service produces the event.

produce OrderPlaced(evt-1001, O-812)

The producer is asking the broker or log to accept a durable handoff. Until the broker confirms, the event may still be only in the producer's memory, request buffer, or outbox table.

Step 3: the broker or log accepts it.

order-events position 1057:
  OrderPlaced(evt-1001, O-812)

Now the handoff state has changed. The event is no longer just something the order service tried to send. It is available in the event system.

Step 4: each consumer reads from its own position.

Payment reads from 1057
Email reads from 1057
Analytics reads from 1051, then eventually reaches 1057

The consumers do not all move together. Payment may be current. Email may be paused. Analytics may be replaying old events.

Step 5: each consumer performs its own reaction.

Payment:
  sees OrderPlaced
  creates payment attempt P-55
  records "handled evt-1001"
  advances offset to 1057

Email:
  sees OrderPlaced
  enqueues receipt email
  records "handled evt-1001"
  advances offset to 1057

Analytics:
  eventually sees OrderPlaced
  increments order count
  advances offset to 1057

The broker can help deliver the event. It cannot decide whether payment capture is correct. It cannot make email idempotent. It cannot guarantee that analytics chose the right metric.

So far, we have a better picture than "publish and done." An event moves through several states:

fact committed -> event produced -> broker accepted -> consumer read -> consumer side effect -> consumer progress recorded

Each arrow can fail in a different way.

A Worked Trace

Now trace one event with an uneven system. Payment is healthy. Email is down for five minutes. Analytics is behind because it is replaying.

Input:

O-812 is stored as PLACED

Transition:

The order service produces OrderPlaced(evt-1001).

Intermediate states:

Time Producer state Broker/log state Payment state Email state Analytics state
T0 Order O-812 committed. Latest position 1056. Offset 1056. Offset 1056. Offset 1050.
T1 Sends evt-1001. Accepts event at 1057. Has not read it. Offline. Reading 1051.
T2 Done with publish request. Holds evt-1001. Reads 1057, creates payment attempt. Offline. Reading 1052.
T3 No longer involved. Still retains 1057. Records handled event and advances offset. Offline. Reading 1053.
T4 No longer involved. Still retains 1057. Offset 1057. Comes back, reads 1057. Reading 1054.
T5 No longer involved. Still retains 1057. Offset 1057. Enqueues receipt and advances offset. Eventually reaches 1057.

Output or decision:

The order service did not wait for every consumer. The broker or log held the event long enough for independent consumers to catch up. Each consumer recorded its own progress.

Naive failure contrast:

In the direct-call design, email downtime could force the order service to choose between blocking checkout, dropping the receipt request, or owning email retry logic. With broker mediation, email can be unavailable while the order fact remains available for later handling.

That is the benefit. But notice the boundary: the broker did not prove the receipt was sent. It only made the event available and let email resume from a known position.

Check: At T2, can the order service honestly say "payment captured the money"?

Think first, then reveal.

Answer: No. At T2, the order service can say it committed the order and the broker accepted OrderPlaced. Payment has read the event and created an attempt, but the payment service owns whether capture succeeds. A later PaymentCaptured fact would need to come from the payment service.

Where It Breaks

The mechanism helps, but it has sharp edges.

Producer failure before broker acceptance

The order service may commit O-812, then crash before the broker accepts OrderPlaced.

Now the database says the order exists, but consumers never see the event.

This is not solved just by adding a broker. Later in the track, the outbox pattern will make this gap visible and repairable. For now, name the state precisely:

fact committed
event not yet durably handed off

Duplicate consumer handling

The broker may deliver the same event more than once, or a consumer may crash after doing work but before recording its progress.

Email might enqueue a receipt, crash, restart, and read evt-1001 again.

The better model is:

broker delivery can be repeated
consumer side effects must be safe enough for repeats

That usually means the consumer records an event id, uses an idempotency key, or designs the side effect so repetition is harmless.

Consumer lag

Analytics may fall behind:

latest broker position: 1057
analytics offset: 1051
lag: 6 events

Lag is not automatically a bug. It may be expected during replay or a traffic spike. It becomes a problem when the product promise depends on fresh analytics, retention is about to expire, or downstream storage cannot catch up.

Retention and replay limits

An event log is not infinite unless you pay for it and operate it that way.

If analytics starts from offset 1000 but the log only retains events from 1030, replay cannot reconstruct the missing slice from the broker alone.

This is a boundary of event logs: they make replay possible within the retained history. They do not guarantee that old history exists forever.

Cost, Limits, and Signals

Broker or log mediation improves independent progress. Payment, email, and analytics can move at different speeds.

It costs operational complexity. You now have broker availability, retention policy, consumer offsets, lag, duplicate delivery, schema compatibility, and replay behavior to review.

It can still fail when the producer commits local state but never hands off the event, when a consumer performs a side effect twice, or when a slow consumer falls behind retention.

It does not solve ownership. The order service still cannot publish PaymentCaptured just because it wants payment to happen. The broker carries messages; it does not change which service owns a fact.

Signals to watch:

The trade-off is clear: the event system reduces direct timing coupling between services, but it introduces a durable handoff surface that must be designed, monitored, and tested.

Common Confusions

Confusion: "Published" means "processed"

Why it is tempting:

The producer receives a successful publish response and the team wants a simple status word.

Better model:

Published means the event system accepted the event. Processed is a consumer-owned fact. Different consumers may process the same event at different times.

Confusion: The broker owns business correctness

Why it is tempting:

The broker is central in the diagram, so it feels like the broker controls the workflow.

Better model:

The broker owns handoff mechanics. Business correctness still lives in producers and consumers: what fact was emitted, who is allowed to emit it, which side effects are safe, and how duplicates are handled.

Confusion: A log means every consumer sees the same system

Why it is tempting:

The log gives one ordered record, so it feels like everyone has the same view.

Better model:

The log may contain one ordered record, but each consumer has its own position. Payment may know about 1057 while analytics is still at 1052. Shared history does not mean shared current knowledge.

Trace It Yourself

Check: Email reads OrderPlaced(evt-1001), sends the receipt, then crashes before recording offset 1057. What should you expect after restart?

Think first, then reveal.

Answer: Email may read evt-1001 again. The broker or log may only know that email did not record progress. The email consumer needs a safe side-effect strategy, such as storing evt-1001 as already handled or using a receipt idempotency key.

Now try a nearby case.

A fulfillment service consumes OrderPlaced and creates a warehouse pick request.

Trace this event:

OrderPlaced(evt-2040, order_id=O-991)

Starting state:

broker latest position: 2200
fulfillment offset: 2199
warehouse API: sometimes times out after creating the pick request

Write a five-step trace that includes:

A good answer should mention that the broker can retain evt-2040, but fulfillment owns the warehouse side effect. If the warehouse API times out after creating the pick request, fulfillment needs an idempotency key or a way to check whether the pick request already exists before retrying. The lag signal is the distance between the broker's latest position and fulfillment's recorded position.

Resources

Key Takeaways

PREVIOUS Events, Commands, Messages, and Domain Facts NEXT Event Boundaries, Ownership, and Publication Responsibility