Today we'll dive into a narrower and pretty interesting aspect of using events and figure out how they help decouple microservices, make them more autonomous, and improve resilience.
Microservices and loose coupling: how events change the game
Imagine your microservices are like a few twins living on the same planet. Each one seems to live its own life, but internally they're tightly tied to each other, so they're constantly poking each other: "Hey, give me the data!", "Did you forget something?", "Why can't I find this?". That slows them down a lot. To make the twins more independent and productive, you can introduce a messaging system where they just post their thoughts to a common channel and others pick them up only if they care. This analogy explains the main goal of events: to break direct coupling between components.
Microservices, as we know, should be loosely coupled (loose coupling). The lower their dependency on each other, the easier they are to develop, test, and scale. Events act as the glue that connects components without hard dependencies. Instead of one microservice "yelling" at another via REST or gRPC, it can simply publish an event and other services decide whether they're interested.
Example: microservices interacting via events
Let's walk through a real business example — an e-commerce platform.
Use case: Order management and notifications
We have two microservices:
- Order Service — responsible for creating orders.
- Notification Service — sends customers notifications (email, SMS) about order status.
Traditional approach: REST API
If you design this app the usual way, Order Service will call Notification Service directly over HTTP. That causes these problems:
- If Notification Service is down (server crashed, deploying), Order Service will hang or return an error.
- Updating Notification Service, e.g. adding a new notification channel, will require changes in Order Service because the interaction logic is tightly coupled.
- Scaling Notification Service becomes harder since the dependency between services remains direct.
Event-driven approach
Now, if Order Service just publishes an "OrderCreated" event to a message broker (for example, Kafka), Notification Service can subscribe to that event and handle it when it's ready. Advantages of this approach:
- Separation of concerns: Order Service no longer worries about what happens to the event. Its job is just to tell the world an order was created.
- Failures don't block: if Notification Service is down, the order still gets created, and the notification will be sent later when the service recovers.
- Event reuse: other services, like Analytics Service, can also subscribe to the
"OrderCreated"event to collect metrics without touching Order Service code.
Reducing load on central components
One common problem in microservice architecture is the load on central components like the API Gateway or the database. Imagine 100 microservices trying to hit the same server at once if they have direct links. Events solve this by acting as a buffer between components.
Message brokers like Kafka act as intermediaries that distribute the load between producers and consumers. If one consumer is overloaded, the broker can hold the event until the consumer is ready to process it. That makes event processing asynchronous and non-blocking.
Separation of responsibility: the foundation of loose coupling
By using events, we can split responsibilities between microservices so they never have to "chase each other" for data or tasks. Let's see how to do that.
Take a payment processing example. Consider a system where a user places an order and then the payment processing flow starts.
- Order Service publishes an
"OrderCreated"event. - Payment Service, subscribed to that event, receives it and starts processing the payment.
- After a successful payment, Payment Service publishes a
"PaymentProcessed"event. - Shipping Service subscribes to
"PaymentProcessed"and kicks off the shipping process.
What we get:
- Complete isolation of logic: services only know about events, not about each other.
- Flexibility to change: we can easily add a new service, like
"LoyaltyPointsService", that consumes"OrderCreated"and awards points without touching other microservices' code. - Reduced system fragility: one service failing doesn't necessarily affect others (if event queues are configured correctly).
Pitfalls and anti-patterns
Although event-driven architecture might seem like a silver bullet, using it wrong can cause chaos and the opposite effect. Here are some problems to watch out for:
- Too many events. If every microservice starts publishing loads of events and subscribers subscribe to everything, you'll get a deluge of data that no one will ever process. For example, publishing an
"UserClickedButton"event for every single button click on your site is a bad idea. - Difficulty in monitoring. It's easy to get lost in the event stream. For example, figuring out "why didn't Shipping Service start shipping after the order was created" can be hard if you don't have proper logs and tracing.
- Dependence on brokers. The failure surface shifts from services to brokers. If Kafka goes down, no messages get delivered. That requires setting up broker clusters and backups.
Recommendations for using events correctly
To avoid problems and get the most out of event-driven architecture, follow these recommendations:
- Model events as business entities, not just technical notifications. For example,
"OrderCreated"instead of"InsertToDatabase". - Use schemas to describe events (for example, Avro in Kafka) so all services know the structure and attributes of an event.
- Add metadata to events, like creation time, transaction ID, status.
- Monitor your message brokers: for example, use Prometheus/Grafana to track Kafka health.
Visualizing microservice interaction via events
+------------------+ "OrderCreated" +--------------------+
| Order Service |--------------------------->| Notification |
+------------------+ | Service |
| (Sending email) |
"OrderCreated" +--------------------+
|
| +--------------------+
------------------------------------------------>| Analytics Service |
| (Collecting statistics) |
+--------------------+
In this diagram the "OrderCreated" event is published by Order Service and handled by two independent services: Notification Service and Analytics Service. They use the same event but perform completely different tasks.
Event-driven architecture builds a strong foundation for scalable, independent, and resilient systems. But like any technology, its success depends on good design and understanding of its principles.
GO TO FULL VERSION