← Back to list

Stop Tight Coupling Your Services

The Power of Async Communication (Events & Streams)

Pasindu Thejan · 2025-11-05 13:04 · 0 claps · 3.0 min read
#microservices #microservices-pattern #event-driven-architecture #asynchronouscommunication #design-systems
Open on Medium ↗
Wiki topics: PRD · Product Design 🏛️ · Architecture

Stop Tight Coupling Your Services

The Power of Async Communication (Events & Streams)

The Problem

Most developers start with synchronous REST calls between microservices; simple and predictable. But as systems grow, this approach becomes fragile and slow. You quickly run into issues:

  • Every service depends directly on others being online.
  • Traffic spikes overwhelm downstream APIs.
  • Transaction boundaries become impossible to manage across services.

In short — your “microservices” behave like a distributed monolith.

The Bad Way

Tightly coupled, request-response communication between services. Each service calls another directly using HTTP. This creates runtime dependency chains; one failure cascades through the entire system.

Symptoms:

  • Slow response times under load.
  • Hard dependencies between services.
  • Retrying logic duplicated everywhere.
  • Transactions that fail halfway through (because one call timed out).

Imagine: OrderService → calls PaymentService → calls InventoryService If InventoryService is down, the whole chain collapses.

The Good Way

Instead of calling other services directly, each service publishes events about what happened. Other services subscribe to these events and react when they’re ready.

This is decoupling by design; producers don’t know who’s listening, and consumers decide what to do when they hear something relevant.

When to Use:

  • You want decoupled services that don’t rely on each other being online.
  • You need high throughput or real-time processing.
  • You can tolerate eventual consistency instead of strict transactions.
  • You want an audit trail of everything that happened (for replay/debugging).

Common Technologies:

  • Kafka: For high-throughput event streams and replayability.
  • RabbitMQ: For reliable message delivery with routing and queues.

Key Patterns

  1. Event-Carried State Transfer Each event carries enough information for other services to update their own state. *Example: When OrderService emits OrderCreated, it includes customer and product details. InventoryService can immediately decrement stock — no extra API calls needed. Goal: Keep services independent by sharing state* through events, not synchronous calls.
  2. Outbox Pattern A common challenge: what if you save data in your database and publish an event — but your app crashes between the two? The Outbox pattern ensures reliability: a) The service writes both the business data and the event record to the same database transaction. b) A background process (or tool like Debezium) reads the “outbox” table and publishes the event to Kafka or RabbitMQ. Result: Guaranteed at-least-once event delivery with no data loss.
  3. Saga Pattern When a process spans multiple services (e.g., create order → reserve stock → charge payment), use a Saga. There are two main styles: - Choreography (Event-based): Each service listens for events and emits follow-up events. Example: OrderCreatedInventoryReservedPaymentCompleted. If one step fails, compensating events roll back previous steps.
  • Orchestration (Central Controller): A dedicated orchestrator coordinates the saga by sending commands to each service and listening for responses. It maintains the overall process state and triggers compensations if something fails.

Spring Boot Integrations

Spring Boot provides robust tooling for building event-driven systems:

  • **spring-kafka** – for Kafka producers, consumers, and transactional publishing.
  • **spring-amqp** – for RabbitMQ queues, routing, and acknowledgment handling.
  • Transactional producers — ensure that messages are only published if the local transaction succeeds.
  • Outbox via Debezium — use change-data-capture (CDC) to stream DB changes to Kafka.

Together, these enable event-driven microservices that are both resilient and consistent.

Key Takeaways

  • Decoupled: Services communicate via durable events, not synchronous API calls. Failures in one service don’t break others.
  • Scalable: Kafka and RabbitMQ handle millions of messages per second, supporting horizontal scalability and load spikes.
  • Auditable & Replayable: Every event is a record of “what happened,” enabling recovery, analytics, and debugging.
  • Consistent (Eventually): You trade immediate consistency for reliability and availability — and gain massive resilience.

Important Note: This discussion is intended for theoretical understanding of asynchronous communication and event-driven architecture.

Whether you should implement these patterns in your system is a design decision, not a universal rule.

The right choice depends on your project’s context and requirements, including factors like:

Consistency vs. Availability: Do you need strict, immediate consistency, or can your system tolerate eventual consistency?

Complexity vs. Scalability: Is the added operational complexity of Kafka or RabbitMQ justified by your scalability or reliability needs?

Team Maturity & Tooling: Do your developers and DevOps teams have the experience and infrastructure to manage distributed event systems effectively?

In other words — use asynchronous communication when it solves a real problem, not just because it’s popular. Good architecture is always about trade-offs, not trends.


메타데이터
post_id
0dd6ef73cc09
slug
stop-tight-coupling-your-services-0dd6ef73cc09
url
https://medium.com/@pasinduthejan/stop-tight-coupling-your-services-0dd6ef73cc09
canonical_url
https://medium.com/@pasinduthejan/stop-tight-coupling-your-services-0dd6ef73cc09
author_url
https://medium.com/@pasinduthejan
status
ok
fetched_at
2026-07-20 16:49:03