Cohesive Systems logoCOHESIVE SYSTEMS

Persistence & Coordination Patterns

Modern systems are often described through implementation styles: CRUD, DDD, Event Sourcing, CQRS, Actors, RPC, Pub/Sub, Sagas, Durable Execution, Outbox, and so on.

These are usually presented as competing architectural choices. Cohesive treats them differently.

They are not separate worlds. They are different realizations of a deeper system model: entities, shapes, relations, transitions, effects, invariants, histories, and coordination constraints.

The important question is not:

Which architecture should this system use?

The better question is:

What semantic structure does the system have, and which realization preserves that structure under the required operational constraints?

Cohesive makes this correspondence explicit. A transition can be realized through a simple state update, an event stream, a transactional outbox, an actor message, a durable workflow step, or a projection pipeline. The semantic model remains the source of truth, while the realization model determines durability, ordering, isolation, replay, distribution, and recovery.

Not Separation, But Correspondence

Traditional software architecture often separates domain logic from infrastructure. That separation is useful, but incomplete.

Cohesive models the relationship more tightly:

Semantic system  <->  Realization system

The semantic side describes what the system is:

  • Shapes
  • Entities
  • Identity
  • Relations
  • Transitions
  • Preconditions
  • Invariants
  • Effects
  • Workflows
  • Permissions
  • Observations

The realization side describes how the system is executed:

  • Storage layout
  • Transaction boundary
  • Message delivery
  • Runtime identity
  • Retry behavior
  • Replay behavior
  • Projection strategy
  • Coordination protocol
  • Failure handling
  • Compensation behavior

The two sides are systematically connected. A transition's read/write set implies concurrency constraints. A concurrency constraint implies possible execution strategies. A workflow's compensation requirements imply a process model. A query shape implies a projection. A partitioning strategy constrains which transitions can be executed atomically.

Cohesive makes these correspondences first-class.

Four Realization Dimensions

Instead of treating CRUD, DDD, Event Sourcing, Outbox, CQRS, Actors, RPC, Pub/Sub, Sagas, and Durable Execution as mutually exclusive architectures, Cohesive separates the realization problem into four dimensions.

Each dimension answers a different question:

  • Persistence: what is made durable, and what is authoritative?
  • Reconstitution: how is usable state recovered when execution begins again?
  • Coordination: how do nodes, effects, messages, and workflows interact over time?
  • Runtime: what execution substrate enforces those choices?

Named patterns are common bundles of mechanisms across these dimensions. A real system usually composes several bundles at different points in the graph.

Persistence

Persistence describes the durable record of the system. It can preserve current state, history, process progress, pending integration work, or derived query surfaces.

Common mechanisms include:

  • Current-state rows or documents.
  • Aggregate state behind repositories or persistence adapters.
  • Append-only event histories.
  • Transactional state records plus outbox records in one write boundary.
  • Command-side stores and separate read-model projections.
  • Identity-scoped actor state providers, snapshots, or event histories.
  • Retained streams, logs, and consumer checkpoints.
  • Durable process state, step state, timers, and compensation records.
  • Workflow history, checkpoints, activity results, and signal records.
  • Search indexes, denormalized views, and other derived query stores.

Not every runtime that owns state provides persistence. For example, actors own identity-scoped runtime state, but durability requires a state provider, snapshot store, event history, or another external persistence mechanism.

Reconstitution

Reconstitution describes how executable state is recovered from durable records, runtime identity, or another system's own storage model.

Common mechanisms include:

  • Loading the latest row, document, or record.
  • Loading an aggregate root and enforcing invariants through its behavior.
  • Replaying an event stream.
  • Loading a snapshot and replaying later events.
  • Resuming outbox publication from unprocessed records.
  • Rebuilding read models from events, change feeds, or command-side changes.
  • Activating an actor by identity, then rehydrating durable state only when a state provider or event history is configured.
  • Reconstructing state through a called service's own persistence mechanism.
  • Resuming from consumer offsets or replaying retained messages.
  • Reloading a saga or process instance with its current step, pending effects, and compensation plan.
  • Replaying workflow history or resuming from durable checkpoints.

Reconstitution is separate from persistence because the same durable record can support more than one reconstruction strategy. An event history can rehydrate an aggregate, rebuild a projection, drive an audit view, or resume downstream consumers.

Coordination

Coordination describes orchestration and interaction between nodes. It covers the ordering of effects, the handoff between components, the boundaries of atomic work, and the recovery behavior when part of the interaction fails.

Common mechanisms include:

  • No specified coordination beyond a local transaction.
  • Ad hoc service-method side effects around CRUD updates, often creating dual-write risk.
  • Aggregate-local command handling and invariant enforcement.
  • Direct RPC or request/response interaction.
  • Transactional outbox handoff after a state change commits.
  • Event-sourced append followed by handlers, subscribers, and projectors.
  • Command-side mutation followed by asynchronous projection updates.
  • Brokered Pub/Sub through topics, streams, partitions, and consumer groups.
  • Identity-scoped actor mailboxes with per-identity message ordering.
  • Lock-guarded or lease-guarded critical sections.
  • Saga or process-manager coordination with compensating actions.
  • Durable workflow orchestration with timers, retries, signals, activities, and recovery.

This is where many architectural names become precise. CRUD does not define cross-node coordination. DDD does not define it by itself either; DDD can be paired with CRUD, outbox, event sourcing, CQRS, actors, workflows, or another realization style. Outbox and event sourcing are salient partly because they couple persistence to coordination instead of treating side effects as loose code after a write.

Runtime

Runtime describes the substrate that executes the selected mechanisms. It is where storage clients, dispatch loops, schedulers, queues, workers, actors, workflows, projections, and protocols become operational.

Common mechanisms include:

  • Application servers and database transaction managers.
  • Repository, unit-of-work, and persistence-adapter layers.
  • Event stores, stream processors, projectors, and subscribers.
  • Outbox dispatchers, pollers, queue publishers, and change-feed processors.
  • Command handlers, projection pipelines, read-model stores, and query APIs.
  • Actor runtimes with placement, mailboxes, supervision, and state providers.
  • HTTP, gRPC, service meshes, SDK clients, and transport-specific runtimes.
  • Brokers and stream runtimes with producers, consumers, partitions, and subscriptions.
  • Process managers, workflow engines, schedulers, activity workers, and timer services.
  • Lock services, lease managers, and fencing-token providers.

Infrastructure components provide runtime capabilities, but they are not architectures by themselves. A database, broker, actor runtime, or workflow engine becomes part of an architecture only when it is bound to semantic requirements.

Pattern Compositions

A named pattern is a reusable composition across the four dimensions, not a complete description of a system.

CRUD
  Persistence: current-state rows or documents
  Reconstitution: load the latest record
  Coordination: none specified, or ad hoc side effects around service methods
  Runtime: application server plus database transaction manager
DDD
  Persistence: aggregate state behind repositories or adapters
  Reconstitution: load an aggregate root and enforce invariants through behavior
  Coordination: aggregate-local commands; cross-node coordination supplied by another style
  Runtime: application services, domain objects, repositories, and units of work
Event Sourcing
  Persistence: append-only event history
  Reconstitution: replay events, optionally after loading a snapshot
  Coordination: appended events drive handlers, subscribers, and projections
  Runtime: event store, stream processors, projectors, and subscribers
Outbox
  Persistence: domain state and integration messages in one atomic write boundary
  Reconstitution: resume publication from unprocessed outbox records
  Coordination: transactional write first, durable asynchronous dispatch later
  Runtime: database transaction plus dispatcher, poller, queue, or change-feed processor
CQRS
  Persistence: command-side state plus separate read-model projections
  Reconstitution: rebuild read models from events, change feeds, or command-side changes
  Coordination: commands mutate write models; projections update query models asynchronously
  Runtime: command handlers, projection workers, read-model stores, and query APIs
Actors
  Persistence: identity-scoped runtime state; durability requires a state provider or event history
  Reconstitution: activate actor by identity and optionally rehydrate durable state
  Coordination: messages route through identity-scoped mailboxes with ordering
  Runtime: actor runtime with placement, mailboxes, supervision, and state providers
RPC
  Persistence: none specified; each callee owns its own persistence
  Reconstitution: callee reconstructs state through its own mechanism
  Coordination: direct request/response interaction between caller and callee
  Runtime: HTTP, gRPC, service mesh, SDK client, or transport-specific runtime
Pub/Sub
  Persistence: retained streams or logs when the broker supports retention
  Reconstitution: resume from offsets or replay retained messages
  Coordination: producers and consumers interact indirectly through topics and streams
  Runtime: broker or stream runtime with producers, consumers, partitions, and subscriptions
Saga
  Persistence: process state, step state, pending effects, and compensation records
  Reconstitution: reload the saga instance and its current compensation plan
  Coordination: process manager coordinates distributed steps and compensating actions
  Runtime: process manager, workflow engine, actor, or message-driven coordinator
Durable Execution
  Persistence: workflow history, checkpoints, timers, and activity results
  Reconstitution: replay workflow history or resume from checkpoints
  Coordination: orchestrator coordinates activities, timers, retries, signals, and recovery
  Runtime: durable workflow engine with schedulers and activity workers

A real system often uses several compositions simultaneously. For example, a freight tender workflow might use:

  • A document store for the current tender state.
  • A transactional outbox to publish domain events.
  • A durable workflow to coordinate accept/reject deadlines.
  • A projection into Elastic for search.
  • A Pub/Sub stream to notify downstream integrations.
  • An actor runtime for identity-scoped operational state.

Without a higher-level model, these mechanisms become tangled implementation choices. With Cohesive, they become realizations of explicit semantics.

Infrastructure as Realization Layer

Infrastructure components are not architectures by themselves. They are runtime layers where architectural styles can be realized.

Durable Task

Primary Realization Roles
Durable Execution, SAGA, process manager, long-running orchestration.
Good Fit
Timers, retries, checkpoints, activity execution, recovery, multi-step workflows.
Not Sufficient By Itself
Does not define the domain model, shape graph, transition semantics, or query model.

Temporal

Primary Realization Roles
Durable Execution, SAGA, process manager, reliable workflow runtime.
Good Fit
Long-running workflows, activity orchestration, crash recovery, retries, human-in-the-loop processes.
Not Sufficient By Itself
Does not by itself define the entity model, relation model, storage schema, or UI/API surface.

Orleans

Primary Realization Roles
Actors, virtual actors, identity-scoped stateful services.
Good Fit
Per-entity coordination, single-identity message ordering, distributed object identity, stateful grains.
Not Sufficient By Itself
Cross-entity workflows, projections, and persistence semantics still need explicit modeling.

Akka.NET

Primary Realization Roles
Actors, supervision, message-driven concurrency, clustered actor systems.
Good Fit
Concurrent and distributed systems where behavior is naturally modeled as communicating actors.
Not Sufficient By Itself
Does not automatically solve durable business workflows, projections, schema relations, or external consistency.

PostgreSQL

Primary Realization Roles
CRUD, DDD repositories, transactional outbox, relational read models, CQRS projections.
Good Fit
ACID transactions, relational constraints, joins, transactional consistency, reporting-oriented models.
Not Sufficient By Itself
Not automatically an event bus, workflow runtime, actor runtime, or semantic model.

Cosmos DB

Primary Realization Roles
Document persistence, aggregate snapshots, outbox/change-feed patterns, distributed read models.
Good Fit
Partitioned document state, multi-tenant operational data, change-feed-driven projections.
Not Sufficient By Itself
Cross-partition invariants and long-running coordination require additional semantics and runtime support.

EventStoreDB

Primary Realization Roles
Event Sourcing, event streams, event replay, projections.
Good Fit
Append-only histories, stream-level optimistic concurrency, event-sourced aggregate reconstitution.
Not Sufficient By Itself
Query models, process coordination, and UI/API projections must be built around it.

Elasticsearch

Primary Realization Roles
CQRS read model, search projection, denormalized query surface.
Good Fit
Full-text search, faceted search, operational views, denormalized read models.
Not Sufficient By Itself
Should usually not be the authoritative transition store for domain invariants.

Kafka

Primary Realization Roles
Pub/Sub, event streaming, integration log, projection pipeline, event-carried state transfer.
Good Fit
High-throughput event distribution, replayable streams, decoupled consumers, integration backbones.
Not Sufficient By Itself
Does not define command validation, aggregate consistency, compensation semantics, or UI/API shape.

Event Hubs

Primary Realization Roles
Pub/Sub, event ingestion, event streaming, integration backbone.
Good Fit
Large-scale event ingestion, telemetry, integration streams, downstream processing.
Not Sufficient By Itself
Does not define the semantic model, transition model, or authoritative persistence model.

A Cohesive View

Cohesive does not ask developers to choose one architectural style globally.

Instead, Cohesive defines the system graph first.

Shape
  -> Entity
  -> Transition
  -> Effect
  -> Relation
  -> Projection
  -> Runtime realization

A transition might say:

AcceptTender
  Reads:
    Tender.Status
    Tender.Expiration
    PartnerPolicy.AcceptanceRules
 
  Writes:
    Tender.Status = Accepted
    Tender.AcceptedAt
    Load
 
  Emits:
    TenderAccepted
    LoadCreated
 
  Effects:
    Send990Accept
    StartTrackingFlow
 
  Preconditions:
    Tender.Status == Pending
    Tender.Expiration > now

From this one semantic definition, different realizations become possible.

CRUD Realization

Load current Tender
Validate preconditions
Create Load
Update Tender
Commit transaction
Return response

This is suitable when the transition is local, synchronous, and transactionally simple.

CRUD + Outbox Realization

Load current Tender
Validate preconditions
Create Load
Update Tender
Append Outbox(TenderAccepted, LoadCreated)
Commit transaction
Dispatch messages asynchronously

This is suitable when domain state and external propagation must not be dual-written unsafely.

Event-Sourced Realization

Load Tender event stream
Reconstitute Tender state
Validate preconditions
Append TenderAccepted
Append LoadCreated
Project current state and read models

This is suitable when history, replay, auditability, or temporal reasoning are core requirements.

Actor Realization

Route AcceptTender command to Tender actor
Actor loads or already owns Tender state
Actor validates transition
Actor persists state or events
Actor emits effects

This is suitable when identity-scoped concurrency control and message ordering are central.

Durable Execution Realization

Start AcceptTender workflow
Validate tender state
Execute Send990Accept activity
Create Load activity
Record progress
Retry failed activities
Compensate if required
Resume after crash or delay

This is suitable when the transition expands into a long-running, failure-prone, multi-step process.

Pattern Selection Becomes Compilation

In a conventional architecture, choosing between CRUD, Event Sourcing, Actors, CQRS, and Durable Execution is often a manual design decision.

In Cohesive, the system model provides enough structure to make the choice more systematic.

A transition with only one entity, one partition, no external effects, and no long-running steps can be realized as a direct transactional update.

A transition with external messages should use an outbox or equivalent reliable propagation mechanism.

A transition whose history is semantically significant should be event-sourced or emit durable domain events.

A transition with identity-local concurrency requirements can be realized through an actor.

A transition with timers, retries, human actions, or compensations should be realized through a durable workflow or process manager.

A query shape that diverges from the write shape should be realized as a projection.

A high-volume integration stream should be realized through Pub/Sub or event streaming infrastructure.

The semantic model does not erase architectural choice. It makes the choice inspectable, explainable, and eventually compilable.