11# Dynamic Consistency Boundary
22
3- In our little DCB getting-started example, we decide things atomically across multiple event streams without loading aggregates.
4- We keep the example small and show how to validate a course subscription and how to generate invoice numbers using the DCB API.
3+ ??? example "Experimental"
4+
5+ This feature is still experimental and may change in the future.
6+ Use it with caution.
7+
8+ Dynamic Consistency Boundary (DCB) is an event‑sourcing approach for making consistent,
9+ cross‑stream decisions without loading full aggregates.
10+ For each decision, it builds a minimal, purpose‑built state from a targeted subset of events selected via tags.
11+ Lightweight projections compute just the values needed to validate commands and derive new events.
12+ The decision evaluation and event append are coupled by an optimistic append condition to prevent race conditions;
13+ if the queried subset changes concurrently, the write is rejected and can be retried.
14+ This makes handlers simple, fast, and scalable, since only relevant events are processed.
15+ DCB is a great fit when business rules span multiple streams.
516
617!!! note
718
8- DCB is marked as experimental. APIs may change.
19+ You can read more about Dynamic Consistency Boundary on page [dcb.events](https://dcb.events/).
20+
21+ Since this approach differs slightly from the standard "aggregate" event sourcing principle,
22+ we will use the [ Getting Started] ( ./getting_started.md ) example and build it as a DCB variant.
23+
24+ In our litt le getting started example, we manage hotels.
25+ We keep the example small, so we can only create hotels and let guests check in and check out.
926
1027## Define some events
1128
1229First we define the events that happen in our system.
1330
14- A course can be defined with a capacity :
31+ A hotel can be created with a ` name ` and a ` id ` :
1532
1633``` php
34+ use Patchlevel\EventSourcing\Aggregate\Uuid;
1735use Patchlevel\EventSourcing\Attribute\Event;
1836use Patchlevel\EventSourcing\Attribute\EventTag;
1937
20- #[Event('course.defined ')]
21- final class CourseDefined
38+ #[Event('hotel.created ')]
39+ final class HotelCreated
2240{
2341 public function __construct(
24- #[EventTag(prefix: 'course ')]
25- public CourseId $courseId ,
26- public int $capacity ,
42+ #[EventTag(prefix: 'hotel ')]
43+ public readonly Uuid $hotelId ,
44+ public readonly string $hotelName ,
2745 ) {
2846 }
2947}
3048```
31- A course capacity can change later :
49+ A guest can check in by ` name ` :
3250
3351``` php
3452use Patchlevel\EventSourcing\Attribute\Event;
35- use Patchlevel\EventSourcing\Attribute\EventTag;
3653
37- #[Event('course.capacity_changed ')]
38- final class CourseCapacityChanged
54+ #[Event('hotel.guest_checked_in ')]
55+ final class GuestIsCheckedIn
3956{
4057 public function __construct(
41- #[EventTag(prefix: 'course')]
42- public CourseId $courseId,
43- public int $capacity,
58+ public readonly string $guestName,
4459 ) {
4560 }
4661}
4762```
48- A student can subscribe to a course. We tag the event with both student and course, so that DCB projections can select the exact subset of events :
63+ And also check out again :
4964
5065``` php
5166use Patchlevel\EventSourcing\Attribute\Event;
52- use Patchlevel\EventSourcing\Attribute\EventTag;
5367
54- #[Event('course.student_subscribed ')]
55- final class StudentSubscribedToCourse
68+ #[Event('hotel.guest_checked_out ')]
69+ final class GuestIsCheckedOut
5670{
5771 public function __construct(
58- #[EventTag(prefix: 'student')]
59- public readonly StudentId $studentId,
60- #[EventTag(prefix: 'course')]
61- public readonly CourseId $courseId,
72+ public readonly string $guestName,
6273 ) {
6374 }
6475}
6576```
66- And finally, an invoice can be created with a monotonically increasing number:
77+ !!! note
6778
68- ``` php
69- use Patchlevel\EventSourcing\Attribute\Event;
70- use Patchlevel\EventSourcing\Attribute\EventTag;
79+ You can find out more about events [here](events.md).
7180
72- #[Event('invoice.created')]
73- final class InvoiceCreated
74- {
75- public function __construct(
76- #[EventTag(prefix: 'invoice')]
77- public readonly int $invoiceNumber,
78- public readonly int $money,
79- ) {
80- }
81- }
82- ```
8381
8482## Define projections
8583
@@ -93,13 +91,10 @@ Course exists:
9391``` php
9492use Patchlevel\EventSourcing\Attribute\Apply;
9593use Patchlevel\EventSourcing\DCB\BasicProjection;
96- use Patchlevel\EventSourcing\DCB\Projection;
9794
98- /** @implements Projection <bool > */
99- final class CourseExists implements Projection
95+ /** @extends BasicProjection <bool > */
96+ final class CourseExists extends BasicProjection
10097{
101- use BasicProjection;
102-
10398 public function __construct(private readonly CourseId $courseId) {}
10499
105100 public function initialState(): bool
@@ -126,12 +121,9 @@ Current capacity of a course:
126121``` php
127122use Patchlevel\EventSourcing\Attribute\Apply;
128123use Patchlevel\EventSourcing\DCB\BasicProjection;
129- use Patchlevel\EventSourcing\DCB\Projection;
130124
131- final class CourseCapacityProjection implements Projection
125+ final class CourseCapacityProjection extends BasicProjection
132126{
133- use BasicProjection;
134-
135127 public function __construct(private readonly CourseId $courseId) {}
136128
137129 public function initialState(): int
@@ -164,12 +156,9 @@ Count subscriptions of a course:
164156``` php
165157use Patchlevel\EventSourcing\Attribute\Apply;
166158use Patchlevel\EventSourcing\DCB\BasicProjection;
167- use Patchlevel\EventSourcing\DCB\Projection;
168159
169- final class NumberOfCourseSubscriptionsProjection implements Projection
160+ final class NumberOfCourseSubscriptionsProjection extends BasicProjection
170161{
171- use BasicProjection;
172-
173162 public function __construct(private readonly CourseId $courseId) {}
174163
175164 public function initialState(): int
@@ -198,10 +187,8 @@ use Patchlevel\EventSourcing\Attribute\Apply;
198187use Patchlevel\EventSourcing\DCB\BasicProjection;
199188use Patchlevel\EventSourcing\DCB\Projection;
200189
201- final class NumberOfStudentSubscriptionsProjection implements Projection
190+ final class NumberOfStudentSubscriptionsProjection extends BasicProjection
202191{
203- use BasicProjection;
204-
205192 public function __construct(private readonly StudentId $studentId) {}
206193
207194 public function initialState(): int
@@ -228,13 +215,10 @@ Has the student already subscribed to this course:
228215``` php
229216use Patchlevel\EventSourcing\Attribute\Apply;
230217use Patchlevel\EventSourcing\DCB\BasicProjection;
231- use Patchlevel\EventSourcing\DCB\Projection;
232218
233- /** @implements Projection <bool > */
234- final class StudentAlreadySubscribedProjection implements Projection
219+ /** @extends BasicProjection <bool > */
220+ final class StudentAlreadySubscribedProjection extends BasicProjection
235221{
236- use BasicProjection;
237-
238222 public function __construct(
239223 private readonly StudentId $studentId,
240224 private readonly CourseId $courseId,
@@ -267,12 +251,9 @@ Next invoice number from the last event only:
267251``` php
268252use Patchlevel\EventSourcing\Attribute\Apply;
269253use Patchlevel\EventSourcing\DCB\BasicProjection;
270- use Patchlevel\EventSourcing\DCB\Projection;
271254
272- final class NextInvoiceNumberProjection implements Projection
255+ final class NextInvoiceNumberProjection extends BasicProjection
273256{
274- use BasicProjection;
275-
276257 public function initialState(): int
277258 {
278259 return 1;
0 commit comments