Course subscriptions #
A runnable Gleam and Factos simulation of the DCB course subscriptions example.
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:<course_id> and
student:<student_id>. A subscription command builds one decision context 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 matching events into command-specific state. The tests use
factos/simulate for deterministic scenarios, while the development app
dispatches the same commands through factos_sqlight into in-memory SQLite.
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;
- a JSON encoder returning
factos.Event(json.Json)and a schema decoder selected by event type and version.
The dispatch builder receives the encoder and decoder factory directly.
Malformed payloads become factos.DecodeError; unsupported event types or
versions become factos.InvalidSchema.
The server component uses lustre.application; its init and update
functions return managed Lustre effects instead of performing database work
directly. Effects dispatch projection results back through messages. The
command message wraps course_subscription.Command, so the UI exercises the
domain model directly. Strong subscriptions update both projections in the
same transaction as the event append.
The command-to-state flow is diagrammed in the module documentation in
src/course_subscription.gleam.
Run it #
Requirements: Gleam.
Run the tests:
gleam test
Run the Ewe development server, then open http://127.0.0.1:8080:
gleam run -m course_subscriptions_dev
No database or container runtime is required.
Package layout #
src/course_subscription.gleamcontains the commands, events, decision functions, DCB contexts, JSON codec, and model.test/course_subscriptions_test.gleamverifies capacity, duplicate definition, and duplicate student scenarios.dev/course_subscriptions_dev.gleamserves the document, Lustre runtime, and server-component WebSocket with Ewe.dev/course_subscription_component.gleamwraps domain commands in Lustre messages and renders the unstyled semantic UI.dev/course_subscription_projection.gleammaintains and queries the in-memory SQLite projections.