diff --git a/examples/course_subscriptions/README.md b/examples/course_subscriptions/README.md new file mode 100644 index 0000000..35a9378 --- /dev/null +++ b/examples/course_subscriptions/README.md @@ -0,0 +1,69 @@ +# Course subscriptions + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB course subscriptions example](https://dcb.events/examples/course-subscriptions/). + +## Challenge + +Students subscribe to courses under constraints that cross traditional aggregate +boundaries: + +- course identifiers must be unique; +- a course cannot exceed its current capacity; +- a student cannot subscribe to the same course twice; +- a student cannot subscribe to more than five courses. + +Course capacity can also change after the course is defined. + +The source article uses a ten-course student limit. This compact runnable +package sets the same configurable constraint to five. + +## DCB approach + +`StudentSubscribedToCourse` is tagged with both `course:` and +`student:`. A subscription command builds one query from two items: +the course history needed to calculate capacity and occupancy, and the student's +history needed to calculate their subscription count. + +Factos folds those matching events into command-specific state. Factos Pog then +runs the read, decision, and conditional append in a serializable PostgreSQL +transaction. Concurrent attempts therefore enforce both sides of the constraint +without a read model, reservation saga, or aggregate spanning every course and +student. + +The package also demonstrates: + +- unique course definition; +- course-capacity changes; +- domain errors for missing, full, unchanged, and duplicate cases; +- synchronized concurrent subscription attempts against the same course; +- JSON event payloads and course/student tags. + +The command-to-state flow is diagrammed in the module documentation in +[`src/course_subscription.gleam`](src/course_subscription.gleam). + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise the example, and remove the +container. No developer-managed database is required. + +## Package layout + +- [`src/course_subscription.gleam`](src/course_subscription.gleam) contains the + commands, events, decider, DCB queries, PostgreSQL codec, and dispatch API. +- [`test/course_subscriptions_test.gleam`](test/course_subscriptions_test.gleam) + verifies the source scenarios and concurrent constraint enforcement. +- [`dev/course_subscriptions_dev.gleam`](dev/course_subscriptions_dev.gleam) is + the runnable demonstration. diff --git a/examples/dynamic_product_price/README.md b/examples/dynamic_product_price/README.md new file mode 100644 index 0000000..b877033 --- /dev/null +++ b/examples/dynamic_product_price/README.md @@ -0,0 +1,64 @@ +# Dynamic product price + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB dynamic product price example](https://dcb.events/examples/dynamic-product-price/). + +## Challenge + +An order must use the prices shown to the customer. Prices may change at any +time, and the previous price remains valid for a ten-minute grace period. A cart +containing several products must be accepted or rejected as one decision. + +## DCB approach + +Price events are tagged with `product:`. An order query contains one +item for each product in the cart, so the decision state contains only the price +history relevant to that cart. The decider reconstructs the stable price and all +prices still inside the grace period, then validates every displayed price before +emitting one `ProductsOrdered` event. + +Factos Pog performs the read, decision, and conditional append in a serializable +PostgreSQL transaction. A concurrent price change that affects the query causes +the order decision to retry against the new history; a partially validated cart +is never persisted. + +The package demonstrates: + +- initial and changed product prices; +- acceptance of old and new prices during the grace period; +- rejection of prices that were never valid or have expired; +- atomic multi-product cart validation; +- reporting the first invalid product; +- JSON events with product tags. + +The source example uses relative `minutesAgo` metadata for illustration. This +implementation stores an absolute `recorded_minute` and supplies +`current_minute` in the command, keeping the decider deterministic across +serializable retries. + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise the example, and remove the +container. No developer-managed database is required. + +## Package layout + +- [`src/dynamic_product_price.gleam`](src/dynamic_product_price.gleam) contains + the commands, events, price decision model, DCB queries, codec, and dispatch + API. +- [`test/dynamic_product_price_test.gleam`](test/dynamic_product_price_test.gleam) + verifies the price and cart boundaries. +- [`dev/dynamic_product_price_dev.gleam`](dev/dynamic_product_price_dev.gleam) is + the runnable demonstration. diff --git a/examples/invoice_number/README.md b/examples/invoice_number/README.md new file mode 100644 index 0000000..312c173 --- /dev/null +++ b/examples/invoice_number/README.md @@ -0,0 +1,59 @@ +# Invoice number + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB invoice number example](https://dcb.events/examples/invoice-number/). + +## Challenge + +Every invoice needs a unique number, and those numbers must form one monotonic, +gapless sequence even when several invoices are created concurrently. + +## DCB approach + +The decision query matches every `InvoiceCreated` event. Folding that history +produces the next number, starting at `1`. Although each command writes to its +own invoice stream, the untagged global query is the dynamic consistency +boundary shared by every invoice-number allocation. + +Factos Pog runs the read, number allocation, and conditional append in a +serializable PostgreSQL transaction. Competing commands cannot commit the same +number: a serialization conflict retries one command against the newly +committed invoice and assigns the following number. + +The package demonstrates: + +- allocation beginning at invoice `1`; +- sequential gapless numbering; +- synchronized concurrent allocations; +- a global consistency boundary independent of write-stream identity; +- JSON event payloads and per-invoice tags. + +This straightforward version replays all `InvoiceCreated` events for each +allocation. The source article discusses snapshots and last-event reads as +possible optimizations for event stores that support them. + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise the example, and remove the +container. No developer-managed database is required. + +## Package layout + +- [`src/invoice_number.gleam`](src/invoice_number.gleam) contains the command, + event, sequence decider, global query, codec, and dispatch API. +- [`test/invoice_number_test.gleam`](test/invoice_number_test.gleam) verifies + sequential and concurrent allocation. +- [`dev/invoice_number_dev.gleam`](dev/invoice_number_dev.gleam) is the runnable + demonstration. diff --git a/examples/opt_in_token/README.md b/examples/opt_in_token/README.md new file mode 100644 index 0000000..5fa344c --- /dev/null +++ b/examples/opt_in_token/README.md @@ -0,0 +1,62 @@ +# Opt-in token + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB opt-in token example](https://dcb.events/examples/opt-in-token/). + +## Challenge + +A double opt-in sign-up must confirm that an email address and one-time password +belong to the same pending request. A token can be used once and expires after +60 minutes, without maintaining a separate token read model. + +## DCB approach + +`SignUpInitiated` and `SignUpConfirmed` carry both `email:` and +`otp:` tags. Confirmation queries the conjunction of those tags and folds +the matching facts into one of three useful states: no pending sign-up, unused +token, or already-used token. + +The decider rejects an unknown email/token pair, a replayed token, or an expired +token. A valid decision copies the name from the initiation event into +`SignUpConfirmed`. Factos Pog executes the read, decision, and conditional +append in a serializable PostgreSQL transaction, so concurrent confirmations +cannot consume the same token twice. + +The package demonstrates: + +- sign-up initiation and confirmation; +- matching a token to its email address; +- one-time consumption under sequential and concurrent requests; +- a 60-minute validity boundary; +- JSON events with email and token tags. + +The source example uses relative `minutesAgo` metadata for illustration. This +implementation stores an absolute `initiated_minute` and supplies +`current_minute` in the command, keeping the decider deterministic across +serializable retries. + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise the example, and remove the +container. No developer-managed database is required. + +## Package layout + +- [`src/opt_in_token.gleam`](src/opt_in_token.gleam) contains the commands, + events, token decision model, DCB queries, codec, and dispatch API. +- [`test/opt_in_token_test.gleam`](test/opt_in_token_test.gleam) verifies valid, + invalid, expired, replayed, and concurrent confirmations. +- [`dev/opt_in_token_dev.gleam`](dev/opt_in_token_dev.gleam) is the runnable + demonstration. diff --git a/examples/performance/README.md b/examples/performance/README.md new file mode 100644 index 0000000..d6e14a1 --- /dev/null +++ b/examples/performance/README.md @@ -0,0 +1,54 @@ +# Factos Pog performance + +A standalone PostgreSQL benchmark for the Factos Pog dispatch path. Unlike the +domain packages beside it, this directory is not a port of a specific +[dcb.events example](https://dcb.events/examples/); it measures the backend used +by those examples. + +## What it measures + +The benchmark starts an isolated PostgreSQL container, installs the Factos Pog +event-store migration, and performs a heterogeneous preload of: + +- 8 concurrent workers; +- 20,000 dispatches; +- 100 events per dispatch; +- 2,000,000 total events across four event types and 20,000 streams. + +It reports elapsed time, dispatches per second, and events per second, verifies +the preload counts, then runs four matrices: + +1. **Event count:** fresh-stream dispatches containing 1, 5, 10, or 100 events. +2. **Worker count:** groups of 1, 2, 4, 8, or 16 concurrent 100-event dispatches. +3. **Stream history:** a fresh stream compared with a stream containing 10,000 + events. +4. **Dispatcher write amplification:** no tags, one tag per event, and one tag + plus one durable outbox effect per event. + +The matrix tables report iterations per second, minimum latency, mean latency, +and p99 latency. The benchmark also verifies that the tagged and durable cases +actually persist tag and outbox rows. + +## Run it + +Requirements: [Gleam](https://gleam.run/), a Docker-compatible container +runtime, and enough local PostgreSQL/container capacity for more than two +million inserted events. + +From this directory: + +```sh +gleam deps download +gleam run +``` + +The run creates and removes its own PostgreSQL container. No developer-managed +database is required. Results describe the current machine, container runtime, +PostgreSQL image, and repository revision; compare runs only when those inputs +are controlled. + +## Package layout + +- [`src/factos_pog_performance.gleam`](src/factos_pog_performance.gleam) + contains the benchmark decider, event codec, worker orchestration, preload, + verification queries, and benchmark matrices. diff --git a/examples/prevent_record_duplication/README.md b/examples/prevent_record_duplication/README.md new file mode 100644 index 0000000..69f321e --- /dev/null +++ b/examples/prevent_record_duplication/README.md @@ -0,0 +1,58 @@ +# Prevent record duplication + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB record duplication example](https://dcb.events/examples/prevent-record-duplication/). + +## Challenge + +Clients repeat state-changing requests after timeouts, connection failures, or +accidental double submission. The backend must process one idempotency token at +most once while retaining control of the domain entity identifier. + +## DCB approach + +Every `OrderPlaced` event is tagged with both `order:` and +`idempotency:`. A placement command queries only +`OrderPlaced` events carrying its idempotency tag. The folded state therefore +answers one question: has this token already been used? + +Factos Pog runs that query, the pure decision, and the conditional append in a +serializable PostgreSQL transaction. When concurrent requests use the same +token, only one can append `OrderPlaced`; the other retries against the committed +fact and returns `Resubmission`. + +The order ID remains independent from the client-provided idempotency token. +No token table, pre-issued server token, or read model is required. + +This example protects against accidental resubmission. It does not authenticate +a request or make an attacker-provided token trustworthy; the +[opt-in token example](../opt_in_token/) demonstrates binding a token to +additional domain data. + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise sequential and synchronized +concurrent submissions, and remove the container. No developer-managed database +is required. + +## Package layout + +- [`src/prevent_record_duplication.gleam`](src/prevent_record_duplication.gleam) + contains the command, event, idempotency decider, DCB query, codec, and + dispatch API. +- [`test/prevent_record_duplication_test.gleam`](test/prevent_record_duplication_test.gleam) + verifies new, repeated, and concurrent token submissions. +- [`dev/prevent_record_duplication_dev.gleam`](dev/prevent_record_duplication_dev.gleam) + is the runnable demonstration. diff --git a/examples/unique_username/README.md b/examples/unique_username/README.md new file mode 100644 index 0000000..cb0fc37 --- /dev/null +++ b/examples/unique_username/README.md @@ -0,0 +1,66 @@ +# Unique username + +A runnable Gleam, Factos, and PostgreSQL implementation of the +[DCB unique username example](https://dcb.events/examples/unique-username/). + +## Challenge + +Usernames must be globally unique even when registrations race. The example also +allows username changes and account closure while retaining a released username +for three days before another account can claim it. + +## DCB approach + +Every fact that affects a claim is tagged with the username: `AccountRegistered` +and `AccountClosed` carry one `username:` tag, while `UsernameChanged` +carries tags for both the old and new values. + +Registration queries the complete history for the requested tag and folds it +into `Available`, `Claimed`, or `RetainedUntil`. Factos Pog runs that read, the +pure decision, and the conditional append in a serializable PostgreSQL +transaction. Concurrent registrations for one username therefore produce one +accepted registration and one `UsernameClaimed` result. + +The package demonstrates: + +- globally unique registration; +- synchronized concurrent claims; +- release after account closure; +- moving a claim from an old username to a new username; +- three-day retention for closed or changed usernames; +- JSON events with claim tags. + +The source example uses relative `daysAgo` metadata for illustration. This +implementation stores an absolute `recorded_day` and supplies `current_day` in +the registration command, keeping the decider deterministic across serializable +retries. + +The example deliberately tags the raw username. A production system should +normalize usernames before deciding uniqueness and may hash tag values when the +event store's tag index should not expose them. + +## Run it + +Requirements: [Gleam](https://gleam.run/) and a Docker-compatible container +runtime. + +From this directory: + +```sh +gleam deps download +gleam test +gleam dev +``` + +Both commands start an isolated PostgreSQL container with Testcontainers, apply +the Factos Pog event-store migration, exercise the example, and remove the +container. No developer-managed database is required. + +## Package layout + +- [`src/unique_username.gleam`](src/unique_username.gleam) contains the commands, + events, claim decision model, DCB queries, codec, and dispatch API. +- [`test/unique_username_test.gleam`](test/unique_username_test.gleam) verifies + registration, release, retention, username changes, and concurrency. +- [`dev/unique_username_dev.gleam`](dev/unique_username_dev.gleam) is the + runnable demonstration.