Architecture patterns
Events, commands and notifications
Commands ask one receiver to act, while events announce facts that anyone can react to. Learn the difference in coupling, and how thin and fat events trade consistency for independence.
Many teams say "event-driven" when they mean different things. The starting point is the difference between a command and an event. The difference is about who depends on whom, and that decides how easy the system is to change.
Concept
A message is anything sent through a broker. A command asks one specific receiver to do something. An event states that something has already happened.
Commands and events
- A command is imperative:
ChargeCard,SendEmail. It has one intended handler and can be rejected. The sender cares about the outcome. - An event is past tense:
OrderPlaced,PaymentFailed. It is an immutable fact. It can have zero or many subscribers, and it can't be rejected. You can't refuse a fact. The publisher doesn't know or care who listens.
The distinction is about coupling direction. With a command, the sender depends on the receiver: "I need Payments to charge this card." With an event, the receivers depend on the publisher: "I care when orders are placed." The publisher is unaware of them. Adding a fraud-check consumer to OrderPlaced requires no change to the Orders service at all.
Thin events and fat events
Events differ in how much data they carry.
A thin notification carries only an identifier:
{"type": "OrderPlaced", "orderId": 8812}Consumers that need details call back to the Orders service. The source of truth stays in one place, and the event stays small. But every consumer now makes a synchronous call. Ten consumers mean ten extra reads per order, which brings back the runtime coupling you wanted to remove.
A fat event, or event-carried state transfer, carries what consumers need:
{"type": "OrderPlaced", "orderId": 8812, "customerId": 310,
"items": [{"sku": "A1", "qty": 2}], "totalCents": 4900, "status": "paid"}Consumers keep their own copy and never call back. The email service keeps working even when Orders is down. The cost is larger events, duplicated data, and consumers that see data that is only eventually consistent with the source.
Most production systems lean toward fat events for core business facts. A useful middle ground is to carry the full current state of the changed entity. That makes events self-healing: a consumer that missed an update gets the latest state with the next event.
Common mistakes
- Naming commands as events.
ChargeCardis a request.CardChargedis a fact. Keep the tense honest. - Thin events with a callback per consumer. You have rebuilt the synchronous coupling you were trying to escape.
- Fat events with no owner. Someone must own the schema, or every consumer's assumptions become hidden coupling.
Check yourself
1. Why can a fraud-check consumer subscribe to OrderPlaced without changing the Orders service?
Events don't name a receiver. The publisher doesn't know its subscribers, so new ones need no change to it.
2. Ten consumers receive a thin event. How many calls can return to the Orders service? Up to ten extra reads per order. That reintroduces runtime coupling and load on Orders.
Key takeaways
- A command asks one receiver to act. An event announces a fact that anyone can use.
- Events invert the dependency. Publishers don't know their consumers.
- Thin events keep data central but cause callbacks. Fat events decouple availability and duplicate data.
- Carry the full current state of the changed entity when you can.
Further learning
Martin Fowler's article untangles the four meanings people give to "event-driven". It is the best reading before designing one.
What do you mean by "Event-Driven"? (Martin Fowler)What do you mean by “Event-Driven”?martinfowler.comMartin Fowler's GOTO talk covers the same ground in twenty minutes.
Next, see how events are produced from an existing database in change data capture.
Related
Event-driven architecture, CQRS and event sourcing
Building systems around events, from change data capture to event sourcing and read models, and knowing when it simplifies a system versus creating a distributed mess.
Change data capture
CDC turns committed database changes into events by reading the database's own replication log, which avoids dual writes. Learn how it works, and why an outbox table usually sits in front of it.
Event streaming and Kafka
How a durable log lets many consumers read the same events at their own pace, and how Kafka builds that log from partitions, replicas and consumer groups.