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"
}
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
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
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"
}
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"
}
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
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
With ECST:
Consumer
↓
Event
↓
Process Locally
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"
}
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": "..."
}
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
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)