As applications become larger and more complex, the way different services communicate with each other becomes increasingly important. Traditional request-response communication works well in many situations, but it can create tight dependencies between services and make scaling more difficult.
Event-Driven Architecture offers a different approach. Instead of services directly communicating with one another, they react to events that occur within the system. This makes applications more flexible, scalable, and easier to extend over time.
To understand this better, let’s take the example of a multi-vendor marketplace with an affiliate system.
When a customer places an order, the application publishes an event called OrderPlaced.
Multiple services can subscribe to this event and perform their own tasks independently:
- The Affiliate Service listens for the
OrderPlacedevent and checks whether the order was referred by an affiliate. If it was, the affiliate commission can be calculated and recorded. - The Vendor Service also listens for the same event. It identifies which vendor owns the product, updates inventory levels, and performs any vendor-related processing.
- The Email Service listens for the event as well and sends notifications to the customer and vendor about the new order.
Notice that the service publishing the OrderPlaced event does not need to know what the Affiliate Service, Vendor Service, or Email Service are doing. Its only responsibility is to publish the event.
Could this be implemented using the traditional approach of calling functions inside functions? Absolutely. For example, after creating an order, you could call an affiliate function, then a vendor function, then an email function.
The problem is that this creates tight coupling between components. Each step becomes dependent on the previous one. If one function fails, the entire flow may fail as well. As more features are added, the code becomes harder to maintain and extend.
With Event-Driven Architecture, services are decoupled from one another. Each service reacts to events independently, making the system more resilient, scalable, and easier to evolve over time.
The Only Nuance to Keep in Mind
If Affiliate Service or Vendor Service needs to block the order (e.g., “vendor is out of stock, reject the order”), then you might still need a synchronous check before publishing OrderPlaced.
Event-driven works best for:
- Things that can happen after (email, analytics, affiliate tracking)
- Independent checks (vendor can prepare shipment without blocking the checkout)
If something must happen before the order is confirmed, keep it in the main flow. Everything else — push it to events.
Event Sourcing
Event Sourcing is a pattern where changes to an application’s state are stored as a sequence of events instead of storing only the latest state.
In a traditional application, if a customer’s order status changes from Pending to Processing and then to Delivered, the database usually stores only the current status (Delivered). The previous states are lost unless additional auditing is implemented.
With Event Sourcing, every change is stored as an event:
- OrderCreated
- OrderProcessingStarted
- OrderShipped
- OrderDelivered
Instead of saving only the current state, the application saves the complete history of events that led to the current state. The current state can then be reconstructed at any time by replaying these events in order.
For example, imagine a bank account. Rather than storing only the current balance, the system stores every transaction:
- AccountCreated
- DepositMade ($500)
- WithdrawalMade ($100)
- DepositMade ($200)
The current balance can be calculated by replaying all these events. This provides a complete audit trail and makes it possible to see exactly how the balance reached its current value.
Event Sourcing is particularly useful in systems where tracking historical changes, auditing, debugging, or rebuilding application state is important.
Think of Event Sourcing as keeping every receipt from every transaction instead of only looking at the final account balance