---
title: Keeping decisions correct under concurrency
description: "Checking a read model before you append an event does not hold a rule when two requests run at once. Learn the Dynamic Consistency Boundary idea and the four tools Cratis gives you."
---

Rules such as "a member can borrow at most three books" and "an email address is used once" are easy to write and easy to get wrong. The usual code reads current state, decides, then appends an event. Those are three separate steps, and another request can run between them.

## Why read, decide, append is a race

Two requests both read "two books borrowed". Both decide that a third is allowed. Both append `BookBorrowed`. The member now has four books, and neither request did anything wrong by itself.

Checking a read model first does not fix this. A read model is built from events after they are appended, so it can lag behind the event log. Even a read model that is fully up to date describes the state at the moment you read it, not at the moment you append. Under concurrency, a rule holds only if the append itself fails when the facts the decision depended on have changed.

## The Dynamic Consistency Boundary idea

Traditional event sourcing protects a decision by locking one stream or aggregate: the append succeeds only if nobody else wrote to that stream since you loaded it. That works when a rule fits inside one stream. It is too narrow when a rule spans several entities, and too wide when the stream holds events the rule does not care about.

The [Dynamic Consistency Boundary](https://dcb.events) (DCB) draws the boundary per decision instead. You name the events the decision depends on, read them, and append with a condition: reject the append if any matching event has been written since that read. Only events that matter to the decision cause a conflict. Unrelated writes, even to the same entity, do not.

## How Cratis fits DCB

Chronicle supports DCB-style decision models and append conditions within its own event model. It is not a DCB event store, and the difference shapes how you model a rule.

| DCB | Chronicle |
| --- | --- |
| One event can carry tags for several entities | Each event belongs to exactly one event source |
| Tags select the events a decision depends on | Stream metadata does the same job: event source id, event source type, event stream type and event stream id |
| A query item combines event types and tags | A concurrency scope combines event types and those metadata dimensions |
| Several query items in one append condition | Several scopes, one per event source, checked in one atomic append |

An [event source definition](/chronicle/event-sources/) is the typed way to declare those dimensions: what an event source is, which streams it has, and which dimensions guard an append to each stream. See [event sources and DCB](/chronicle/event-sources/dcb/). A guard derived from a definition covers every event in that stream, whatever its type, so it acts like an aggregate boundary. When a rule depends on only some events, use a decision read.

A rule that spans two entities is written as one fact per entity, each with its own event type, appended together. For example, a course subscription appends a seat-taken event to the course and a joined-course event to the student. DCB would record one event tagged with both. The [canonical DCB examples](/chronicle/dynamic-consistency-boundary/dcb-examples/) show the Chronicle answer for each case.

## The four tools Cratis gives you

Pick the lightest tool that makes the rule hold.

- **Constraints** enforce uniqueness. Chronicle checks them as part of the append, so two requests cannot both claim the same value.
- **Decision reads and protected decisions** guard a rule over state on one or more keys. The decision reads state in a way that records where in the event log it read, and the append fails if a relevant event arrived after that point.
- **Stream decisions and aggregates** guard a rule by the events of a stream or of an aggregate's event source. The append fails if a relevant event was appended after you loaded it: any event in the stream for a stream decision, or an event of a type the aggregate handles on its event source.
- **Repair** accepts that a rare violation can happen. You detect it afterwards and compensate with a new event. Use it only when a short-lived violation is acceptable to the business.

A guarded decision that loses the race is rejected, not retried for you. The caller reads the new state and resubmits deliberately.

## Where to go next

Choose by how you work with Cratis.

| You are using | Go to |
| --- | --- |
| The Chronicle client directly | [Choosing a consistency approach in Chronicle](/chronicle/dynamic-consistency-boundary/choosing/) |
| Arc commands | [Consistency in Arc](/arc/backend/csharp/chronicle/consistency/) |
| Screenplay models | [What `reads` guarantees](/screenplay/commands/#what-reads-guarantees) |
| Event source definitions and streams | [Event sources](/chronicle/event-sources/) and, for Arc commands, [event source definitions](/arc/backend/csharp/chronicle/commands/event-source-definitions/) |

For the ideas underneath, see [what is event sourcing?](/concepts/event-sourcing/) and [event modeling](/concepts/event-modeling/).
