Skip to content

Dynamic Consistency Boundary

EvidentSource implements the Dynamic Consistency Boundary (DCB) specification, enabling cross-entity consistency without the complexity of sagas or distributed transactions.

Traditional event sourcing enforces consistency at stream boundaries—typically one stream per aggregate. This creates challenges when business invariants span multiple entities.

Example: Course Enrollment

  • A course cannot exceed 30 students
  • A student cannot enroll in more than 10 courses

In traditional event sourcing, enforcing both constraints requires:

  • Saga orchestration across Course and Student aggregates
  • Multiple events representing the same business fact
  • Temporary inconsistency windows between saga steps
  • Complex compensation logic for every failure mode
Traditional Saga Approach:
──────────────────────────
1. Student requests enrollment
2. Course aggregate checks capacity → emits CourseEnrollmentRequested
3. Student aggregate checks limit → emits StudentEnrollmentConfirmed
4. Course aggregate confirms → emits CourseEnrollmentConfirmed
5. If step 3 or 4 fails → compensating events required
Problems:
- 4+ events for one business action
- Temporary inconsistency between steps
- Compensation logic for every failure mode
- Debugging spans multiple streams

DCB shifts consistency from stream-based to query-based:

  1. Single event per business fact — One StudentEnrolled event, not a saga of confirmations
  2. Tagged events — Events carry tags (via CloudEvents subject) for all affected entities
  3. Query-based constraints — At write time, query: “How many enrollments exist?”
  4. Atomic evaluation — Constraints are checked atomically with the write
DCB Approach:
─────────────
1. Student requests enrollment
2. Query: count(enrollments WHERE course=X) < 30?
3. Query: count(enrollments WHERE student=Y) < 10?
4. If both pass → emit StudentEnrolled { course: X, student: Y }
5. Constraint violation? → reject immediately, no compensation needed
Benefits:
- 1 event for one business action
- No temporary inconsistency
- No compensation logic
- Single point of debugging

EvidentSource implements DCB through two core types:

EventSelector — Composable queries for matching events:

// Match events by subject, type, or stream
let selector = EventSelector::subject_equals(customer_id)
.and(EventSelector::event_type_equals("account.opened"));

AppendCondition — Constraints on query results:

// Fail if matching events exist (uniqueness check)
AppendCondition::fail_if_events_match(selector)
// Require matching events to exist (prerequisite check)
AppendCondition::must_exist(selector)
// Optimistic concurrency (no changes since last read)
AppendCondition::unchanged_since(selector, last_seen_revision)

A bank requires that customers have no more than 5 active accounts:

// When opening a new account, define the constraint:
let condition = AppendCondition::fail_if_events_match(
EventSelector::subject_equals(&customer_id)
.and(EventSelector::event_type_equals("account.opened")),
).at_revision(4); // Max 4 previous account.opened events
// Submit the transaction with the constraint:
// - If customer already has 5 accounts → constraint fails → batch rejected
// - If customer has < 5 accounts → constraint passes → batch committed
// - No saga, no compensation, no temporary inconsistency
AspectTraditional SagasDCB
Events per action4+ (request, confirm, complete)1
Temporary inconsistencyYes, between saga stepsNo
Compensation logicRequired for every failureNot needed
Debugging scopeMultiple streamsSingle batch
Cross-entity invariantsComplex orchestrationSimple constraints