DEV Community

Cover image for Stop Making API Calls After Every Event: Understanding Event-Carried State Transfer (ECST)
Himanshu Gupta
Himanshu Gupta

Posted on

Stop Making API Calls After Every Event: Understanding Event-Carried State Transfer (ECST)

Event-Driven Architecture (EDA) is one of the most popular approaches for building scalable distributed systems.

Instead of tightly coupling services through synchronous APIs, services communicate by publishing and consuming events.

It sounds perfect.

Until your consumers start making API calls for every event they receive.

At that point, you've reintroduced the very coupling Event-Driven Architecture was supposed to eliminate.

This is where Event-Carried State Transfer (ECST) comes in.


The Hidden Problem in Event-Driven Architecture

Imagine an e-commerce platform.

A customer places an order.

The Order Service publishes an event:

{
  "event": "OrderCreated",
  "orderId": "ORD-10234"
}
Enter fullscreen mode Exit fullscreen mode

Several services subscribe to this event:

  • Inventory Service
  • Notification Service
  • Analytics Service
  • Shipping Service
  • Billing Service

Everything looks asynchronous.

But here's what actually happens.

Order Created Event
        │
        ▼
Inventory Service
        │
GET /orders/ORD-10234
        │
Order Service
Enter fullscreen mode Exit fullscreen mode

Notification Service does the same.

Analytics Service does the same.

Shipping Service does the same.

Suddenly, one event generates dozens of synchronous API calls.


Congratulations, You've Recreated a Monolith

Your architecture now looks like this:

              Order Service
                    ▲
        ┌───────────┼───────────┐
        │           │           │
 Inventory      Shipping   Notification
        │           │           │
        └───────────┼───────────┘
            API Requests
Enter fullscreen mode Exit fullscreen mode

Although events are being used, every consumer still depends on the Order Service.

If the Order Service is unavailable:

  • Notifications fail
  • Inventory updates fail
  • Analytics stop processing
  • Shipping cannot continue

The event broker isn't the bottleneck anymore.

The originating service is.


Event-Carried State Transfer Solves This

Instead of publishing only an identifier, publish the data consumers actually need.

Instead of this:

{
  "event": "OrderCreated",
  "orderId": "ORD-10234"
}
Enter fullscreen mode Exit fullscreen mode

Publish:

{
  "event": "OrderCreated",
  "orderId": "ORD-10234",
  "customerId": "USR-1001",
  "customerName": "John Doe",
  "totalAmount": 249.99,
  "currency": "USD",
  "items": [
    {
      "productId": "P101",
      "quantity": 2
    }
  ],
  "shippingAddress": {
    "city": "New York",
    "country": "USA"
  },
  "createdAt": "2026-07-21T10:30:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Now every consumer has everything it needs.

No additional API calls.


How ECST Works

The flow becomes much simpler.

Order Service
      │
Publish Event
      │
Kafka / RabbitMQ
      │
────────┬─────────┬─────────┐
        │         │         │
Inventory  Shipping  Analytics
        │         │         │
 Update    Process    Store
 Local DB  Shipment   Metrics
Enter fullscreen mode Exit fullscreen mode

Each service updates its own local state using the event payload.

There is no dependency on the producer after the event is published.


Why This Improves Decoupling

Without ECST:

Consumer
    │
API Call
    │
Producer
Enter fullscreen mode Exit fullscreen mode

With ECST:

Consumer

↓

Event

↓

Process Locally
Enter fullscreen mode Exit fullscreen mode

Consumers become completely independent.

The producer no longer needs to stay online after publishing the event.


Real-World Example

Imagine a food delivery platform.

When an order is placed:

Without ECST:

  • Delivery Service requests order details
  • Billing Service requests customer details
  • Loyalty Service requests purchase amount
  • Notification Service requests delivery address

Four consumers.

Four API calls.

One event.

With ECST:

The event already contains:

  • Customer information
  • Delivery address
  • Ordered items
  • Total price
  • Payment status

Each service processes the event independently.


Benefits of ECST

Better Decoupling

Consumers don't depend on the producer after receiving an event.


Improved Scalability

Removing synchronous calls significantly reduces load on the originating service.

Instead of serving thousands of API requests, the producer only publishes events.


Better Resilience

If the producer goes offline after publishing the event, consumers can still complete their work.

This makes the system more fault tolerant.


Lower Latency

Consumers don't wait for network requests.

Processing begins immediately after the event is received.


Local Read Models

Each service can maintain its own database optimized for its use case.

For example:

Inventory Service stores:

  • Product ID
  • Available Quantity

Analytics Service stores:

  • Revenue
  • Region
  • Category
  • Sales Trends

Neither service depends on querying the Order database.


But Nothing Is Free

ECST introduces several trade-offs.

Larger Events

Instead of sending:

{
  "orderId": "123"
}
Enter fullscreen mode Exit fullscreen mode

You may now send several kilobytes of data.

Large payloads increase network usage and storage costs.


Schema Evolution

What happens if a new field is added?

{
  "customerPhone": "..."
}
Enter fullscreen mode Exit fullscreen mode

Older consumers may not recognize it.

This makes schema versioning essential.

Tools like Apache Avro, Protocol Buffers, and Schema Registry become important in production.


Event Ordering

Imagine two events:

Order Updated

Order Created
Enter fullscreen mode Exit fullscreen mode

If they arrive out of order, consumers may end up with incorrect state.

Solutions include:

  • Event version numbers
  • Sequence IDs
  • Partition ordering (Kafka)
  • Idempotent consumers

Stale Data

Events represent data at a specific point in time.

If a customer's address changes later, previous events still contain the old address.

Consumers must decide whether historical accuracy or current state is more important.


Security & Privacy

Embedding user information in every event can expose sensitive data.

Best practices include:

  • Encrypt sensitive fields
  • Mask personal information
  • Publish only the data consumers actually need
  • Apply least-privilege principles

ECST and Event Sourcing

ECST works particularly well with Event Sourcing.

Since events already carry business state, services can rebuild their local databases simply by replaying events.

This enables:

  • Auditing
  • Time travel
  • State reconstruction
  • Disaster recovery

When Should You Use ECST?

ECST is a great fit when:

  • Multiple services consume the same events.
  • Consumers frequently make follow-up API calls.
  • High throughput is required.
  • Loose coupling is a priority.
  • Services maintain their own read models.

Avoid ECST when:

  • Events become excessively large.
  • Data changes too frequently.
  • Consumers only require a small identifier.
  • Sensitive information should not be widely distributed.

Best Practices

A production-ready ECST implementation should follow these guidelines:

  • Include only the data consumers actually need.
  • Version event schemas from day one.
  • Design consumers to be idempotent.
  • Use durable message brokers such as Kafka or RabbitMQ.
  • Validate event contracts before deployment.
  • Monitor event size and broker throughput.
  • Protect sensitive data with encryption or masking.

Final Thoughts

Event-Driven Architecture helps services communicate asynchronously, but Event-Carried State Transfer takes decoupling one step further.

Instead of forcing every consumer to query the producer, ECST packages the required business context inside the event itself.

The result is fewer API calls, better scalability, improved resilience, and truly independent services.

Like every architectural pattern, ECST comes with trade-offs—larger event payloads, schema evolution, and eventual consistency—but when applied correctly, it can dramatically simplify distributed systems.

The goal isn't to eliminate APIs.

It's to ensure that events contain enough context so consumers rarely need them.


Have you used Event-Carried State Transfer in production? Do you prefer lean events with follow-up API calls, or rich events that carry business state? Share your experience in the comments!

#systemdesign #microservices #eventdriven #kafka #backend #softwarearchitecture #distributedsystems #cloud #developers #programming

Top comments (0)