DEV Community

Dinesh Dunukedeniya
Dinesh Dunukedeniya

Posted on

Fixing Microservice Dependencies with Self-Contained Events (aka ECST)

Microservices are supposed to be independent, autonomous, and scalable. But in reality, they often end up tightly coupled—relying on synchronous API calls, complex orchestration, and fragile dependencies.

In this post, we’ll explore how to break microservice dependencies using a powerful pattern known as Event-Carried State Transfer (ECST)—or as I like to call it, Self-Contained Events. It’s a simple yet impactful idea: put all the necessary data in the event.

The Problem: API-Based Microservice Dependency

Many microservice systems fall into a trap: they rely heavily on synchronous API calls between services to share data. This creates tight coupling, reduces resilience, and introduces latency.This API dependencies hurt reliability and scalability.

Scenario: Payment Service Needs Customer Info

Let’s say a customer places an order. The Order Service emits an event like:

{
  "eventType": "OrderPlaced",
  "orderId": "ORD-1234",
  "customerId": "CUST-5678",
  "items": [ ... ]
}

Enter fullscreen mode Exit fullscreen mode

So far, so good. But now the Payment Service needs to:

Validate the customer’s billing status.

Access their preferred payment method.

Maybe even apply discounts based on customer type.

To do that, it has to call the Customer Service:

Payment Service → GET /customers/CUST-5678 → Customer Service

Enter fullscreen mode Exit fullscreen mode

Here’s the problem:

If Customer Service is slow or down, the Payment Service fails.

You’ve created a runtime dependency between two services.

The system becomes fragile and hard to scale.

Despite using events, the services are still tightly coupled, relying on synchronous communication to function.

The Fix: ECST (Self-Contained Events)

Instead of making an API call, the Customer Service emits events when relevant data changes:

{
  "eventType": "CustomerUpdated",
  "customerId": "CUST-5678",
  "name": "Alice Smith",
  "email": "alice@example.com",
  "loyaltyTier": "gold",
  "preferredPaymentMethod": "visa"
}

Enter fullscreen mode Exit fullscreen mode

The Payment Service subscribes to these events and stores only the fields it needs—either in a local database or an in-memory cache.

Now it looks like this:

  • Customer Service emits CustomerCreated and CustomerUpdated events.

  • Payment Service listens and stores a local copy of needed customer data.

  • When OrderPlaced is received, Payment uses local customer data—no API call required.

Benefits of This Approach

  • No runtime dependency on Customer Service.
  • Improved resilience—each service can function independently.
  • Faster, async processing using pre-synced local data.
  • Better boundaries—each service owns its domain and data shape.

Bonus Tip: Design Events with Purpose

  • Design your events with the consumer’s use case in mind.
  • Use versioned schemas to evolve safely.
  • Make handlers idempotent to handle retries.
  • Instead of emitting minimal "something happened" events:
{ "eventType": "CustomerUpdated", "customerId": "CUST-5678" }

Enter fullscreen mode Exit fullscreen mode

Emit rich, meaningful events:

{
  "eventType": "CustomerUpdated",
  "customerId": "CUST-5678",
  "email": "alice@example.com",
  "loyaltyTier": "gold"
}

Enter fullscreen mode Exit fullscreen mode

That’s how you enable autonomy.

Conclusion

Microservices shine when services can operate independently. But if every service depends on synchronous calls to others, you haven’t truly decoupled.

Event-Carried State Transfer (ECST) is a simple yet powerful technique to fix that.
✅ Push state with events
✅ Keep local, read-only data
✅ Break the web of runtime dependencies

Top comments (0)