Module 15 · Lesson 1 of 4
Pub/sub
One message, many subscribers.
When a fan books a seat, five Gatepass services need to know: payments, seats, email, SMS and analytics. Right now the booking service calls them one after another, so the slowest and least reliable of the five decides whether anyone can buy a ticket at all. This lesson replaces that chain with one message that everyone can hear.
The chain that breaks
Here is the booking request today. The booking service saves the booking, then makes five calls in a row before it answers the phone that is waiting.
Three things go wrong with this shape, and they get worse as Gatepass grows.
Latency adds up. The user waits for the sum of every call. On a good day that is about 400 ms. On fest night, when the SMS provider slows to four seconds, every booking takes four seconds, and the load balancer starts timing requests out.
Availability multiplies. If each of the five services is up 99.9% of the time, the chance that all five are up at once is 0.999 multiplied by itself five times, about 99.5%. That is roughly 3.6 hours a month in which bookings fail because of a service that only had to send a text.
Every new listener is a code change. When marketing asks for a loyalty-points service, someone has to edit the booking service, add a sixth call, decide what happens if it fails, and redeploy the most important code path in the company.
The root problem is that booking is doing work that isn't booking. Email, SMS and analytics don't change whether the booking happened. They only need to hear that it did.
Publish a fact, not a command
Pub/sub (publish/subscribe) flips the arrows around. The booking service publishes a single message, BookingCreated, to a named channel called a topic. Services that care subscribe to the topic, and the messaging system, the broker, gives each subscriber its own copy.
Notice the event's name. BookingCreated is in the past tense. It states a fact: this booking exists. It does not say "send an email", because the booking service no longer knows or cares who is listening. Commands have one intended receiver. Facts can have any number.
Notice also what stayed a direct call: payments. The fan needs to know, before we say "booked", whether their card was charged. Anything the response depends on stays synchronous. Anything that only reacts to the result can listen instead. Sorting your calls into those two piles is most of the design work.
Call what the answer depends on. Publish everything else as a fact, and let the services that care subscribe.
Topics, subscriptions and queues
In module 7 you met the work queue: many workers share one queue, and each job goes to exactly one of them. That is the competing consumers pattern, and it is how you spread load.
Pub/sub is the other direction. Each subscription is its own lane. Email has one, SMS has one, analytics has one. A message published to the topic is copied into every lane, and each lane is read at its own pace. Inside a lane you can still run several workers that compete for messages, so email can scale to ten workers without SMS noticing.
That separation is what makes the system resilient. If SMS is down, its lane fills up. The broker keeps the messages until SMS comes back and works through the backlog. Booking never noticed. Try it below: switch a service off, keep publishing, then switch it back on.
Try it
One booking, four services that care
Publish bookings, then switch a service off. Compare a chain of direct calls with publishing one event.
Publisher
Booking service
Saves the booking, publishes one event.
Topic
booking-events
Seat map
SMS
Analytics
Publish a booking to see who hears about it.
Bookings ok
0
Bookings failed
0
User waited
–
Events queued
0
Run the same experiment in "Direct calls" mode. One service down turns every booking into a failure, and the user waits two seconds to find that out. In pub/sub mode the user waits about 30 ms, the time to save the booking and hand one message to the broker.
What goes in the event
A subscriber can only act on what the event carries, so its contents are a contract, just like an API.
A thin event carries an id: { "type": "BookingCreated", "bookingId": "b_8812" }. Every subscriber then calls the booking service to fetch the details, which puts load back on booking and brings back the coupling you were trying to remove.
A fat event carries what subscribers usually need: booking id, show, seats, amount, the fan's email and phone, and when it happened. Subscribers can work on their own, even when booking is down. The cost is size and the risk of exposing fields some subscribers shouldn't see.
Most teams land in the middle: enough for the common subscribers, ids for the rest. Give every event a unique eventId, a timestamp and a version number. Then treat the schema like a public API: adding a field is safe, but renaming or removing one breaks a subscriber you may not even know exists.
Check yourself
Which part of booking should stay a direct, synchronous call rather than a subscriber to BookingCreated?
What you give up
Pub/sub is not free. Be honest about the costs before you reach for it.
- No answer comes back. The publisher can't know whether the email went out. If it matters, the subscriber publishes its own event, such as
TicketEmailed, and someone watches for missing ones. - Things happen later. For a few seconds the booking exists but the email hasn't been sent and the dashboard doesn't show it. That is eventual consistency, and every screen that reads a subscriber's data has to tolerate it.
- The flow is harder to see. In the call chain you could read the booking code top to bottom. Now the behaviour is spread across subscribers, and you need tracing and a list of who subscribes to what.
- Duplicates and ordering. Most brokers deliver at least once, so a subscriber may see the same event twice. Events can also arrive in a different order than they were published. Both have answers, and they are the subject of the rest of this module.
Your turn
Gatepass also lets fans cancel bookings and lets organisers change a show's time. List the events you would publish and which services would subscribe to each.
Stuck? Name each event as a past-tense fact, then ask which services would have to react if it happened.
Key takeaways
- A chain of calls adds latencies and multiplies failure rates. Five services at 99.9% give you about 99.5%.
- Keep the calls the response depends on. Publish everything else as a past-tense fact on a topic.
- Every subscription is its own lane. A slow or dead subscriber builds a backlog and catches up later, without slowing the publisher.
- Events are a contract. Include an id, a timestamp and a version, and only ever add fields.
- You trade a simple call chain for eventual consistency, a critical broker, and duplicates and ordering to deal with.
Finished reading?
Mark it complete to tick it off in the course.