DBRaven
Pattern · messaging

Event-Carried State Transfer

established

Summary

Embed the current state of the changed entity directly in each event payload ("fat event"), so consuming services can update their local read models without making synchronous API calls back to the source service: eliminating downstream query fan-out and reducing coupling between services.

Problem

Thin events (ID-only) require consuming services to call back to the source service for entity data, creating synchronous coupling, a query fan-out under high event rates, and cascading failures if the source service is unavailable. ECST eliminates this by carrying the data in the event itself.

Description

In event-driven systems, services emit events when their state changes. A "thin event" carries only a reference: {"type": "order.updated", "order_id": "abc123"}. Consuming services that need order details must call the Order service synchronously to fetch them. Under high event rates, this produces a downstream query storm: every event triggers one or more round-trip API calls back to the source.

Event-Carried State Transfer (ECST) embeds the current entity state in the event: {"type": "order.updated", "order_id": "abc123", "status": "shipped", "total": 99.99, ...}. Consuming services update their local projections directly from the event payload with no callback needed. The Order service emits once; all consumers derive state locally.

ECST is particularly valuable for read model projections in CQRS architectures: the query side maintains a denormalized read model rebuilt from event streams. When an order is updated, the search service, analytics service, and notification service all update their own representations using the event payload: no synchronous call back to the Order service.

Trade-off: events become larger. For entities with large state (documents, configurations), the event payload can grow to hundreds of kilobytes. Kafka's default message size limit (1 MB) must be respected. For very large entities, a reference to a content-addressed snapshot store (S3 object key) may replace the inline payload.

Distinction from event sourcing: ECST events carry current state, not deltas. Event sourcing events carry the change (what happened). ECST consumers get the full current state and overwrite their projection; event sourcing consumers apply deltas incrementally.

An Avro or Protobuf schema for a fat order event typically looks like:

message OrderUpdatedEvent {

string event_id = 1;

string order_id = 2;

google.protobuf.Timestamp occurred_at = 3;

OrderStatus status = 4;

repeated OrderItem items = 5;

int64 total_cents = 6;

string customer_id = 7;

}

A schema registry (Confluent or Apicurio) registers the event schema to enforce backward compatibility, so consumers can read events produced by both older and newer schema versions. For large entities, store the full payload in S3 or GCS with a content-addressed key and embed only the key and metadata in the event: {"type": "document.updated", "doc_id": "...", "version": 42, "payload_key": "s3://bucket/docs/abc123/v42.json"}.

Tradeoffs

Consumer decoupling
+0.8

Eliminates the synchronous callback from consumer to source; fully decoupled asynchronous processing

Source availability isolation
+0.7

Source service unavailability does not block consumers from processing events already in the log

Replayability
+0.6

Event replay from the log is sufficient to rebuild any consumer's projection

Projection latency
+0.6

Reduces latency for downstream projections since there is no network round trip for a data fetch

Payload size
-0.3

Larger event payloads increase message broker storage and bandwidth costs

Data exposure
-0.3

Events may carry sensitive fields that not all consumers should see, requiring event filtering or schema evolution

In-flight staleness
-0.2

If the entity updates multiple times in rapid succession, in-flight events carry stale state for intervening versions

Schema evolution cost
-0.3

Schema evolution for embedded state is harder than for thin events, since all consumers must handle old and new event schemas

When to use

Multiple downstream services need to maintain read projections of the same entity

Each service can update its projection directly from the event without a callback

Source service availability should not block downstream processing

If the source is unavailable, thin events result in blocked consumers; ECST events are self-contained

Event rate is high and synchronous callbacks from consuming services would overload the source

1000 events/sec × 5 consumers = 5000 synchronous API calls back to the source

Entity state is small enough to fit in the event broker's message size limit

Entities larger than ~100KB may require a hybrid approach (inline metadata + S3 reference for large fields)

When not to use

Entity state is large (megabytes), making fat events expensive to produce and replicate

Embed a reference to a snapshot store and fetch only when needed

Consumers only need the event notification, not the entity state

If consumers react to events but don't need the data (e.g., cache invalidation), thin events are simpler

The event represents an action or command semantics, not a state update

ECST is appropriate for state change events; command events have different semantics

Operational Requirements

mandatory

Keep event schema evolution backward compatible

Old consumers must read new events, and new consumers must handle replayed old events.

mandatory

Monitor event payload sizes

A sudden increase in entity size can cause broker rejection at the message size limit.

recommended

Apply field-level security for sensitive event data

Required if events carry PII or other sensitive data that not all consumers should access.

Characteristics

Scales on
Implementation complexitymedium
Operational complexitymedium

Relationships

Complements

event sourcingcqrsoutbox patternpublisher subscriber

Basis

Event-Carried State Transfer is documented in Enterprise Integration Patterns and Martin Fowler's bliki; widely discussed in microservices and event-driven architecture literature

Related Architecture Knowledge

Outbound: this entity affects

ComplementsPattern
outbox pattern
Grounded

Fat events produced via the outbox pattern carry entity state that consumers can use to update projections without calling back to the source service.

Full relationship →

Inbound: affects this entity

SupportsTechnology
kafka
Grounded

Kafka topics serve as the delivery mechanism for fat event payloads; Kafka's compacted topics can retain the latest state per key, enabling exactly the event-carried state transfer pattern.

Full relationship →

Used In Architecture Scenarios

Event-Carried State Transfer: DBRaven