Dynamic Consistency Boundary
EvidentSource implements the Dynamic Consistency Boundary (DCB) specification, enabling cross-entity consistency without the complexity of sagas or distributed transactions.
The Problem: Saga Complexity
Section titled “The Problem: Saga Complexity”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 enrollment2. Course aggregate checks capacity → emits CourseEnrollmentRequested3. Student aggregate checks limit → emits StudentEnrollmentConfirmed4. Course aggregate confirms → emits CourseEnrollmentConfirmed5. 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 streamsThe DCB Solution
Section titled “The DCB Solution”DCB shifts consistency from stream-based to query-based:
- Single event per business fact — One
StudentEnrolledevent, not a saga of confirmations - Tagged events — Events carry tags (via CloudEvents
subject) for all affected entities - Query-based constraints — At write time, query: “How many enrollments exist?”
- Atomic evaluation — Constraints are checked atomically with the write
DCB Approach:─────────────1. Student requests enrollment2. 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 debuggingEvidentSource Implementation
Section titled “EvidentSource Implementation”EvidentSource implements DCB through two core types:
EventSelector — Composable queries for matching events:
// Match events by subject, type, or streamlet 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)Example: Customer Account Limit
Section titled “Example: Customer Account Limit”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 inconsistencyBenefits
Section titled “Benefits”| Aspect | Traditional Sagas | DCB |
|---|---|---|
| Events per action | 4+ (request, confirm, complete) | 1 |
| Temporary inconsistency | Yes, between saga steps | No |
| Compensation logic | Required for every failure | Not needed |
| Debugging scope | Multiple streams | Single batch |
| Cross-entity invariants | Complex orchestration | Simple constraints |
Next Steps
Section titled “Next Steps”- Constraints API — Implementation details and patterns
- Event Sourcing — Foundational concepts
- Bi-Temporal Indexing — Temporal query capabilities