Domain Modeling

Do not model the current state of the world, model how you get there

event sourcing by example

Discover

We can illustrate a system’s expected behavior from start to finish on a timeline, without branching, using a concrete example.

Event Modeling is a powerful technique for exploring a domain. It describes systems by tracing how information changes over time through key events.

the Fraktalio logo: a ledger with the shape of letter F
specification by example
lib/domain_test.ts
Deno.test(function changeRestaurantMenuDeciderTest() {
  const changeRestaurantMenuCommand: RestaurantCommand = restaurantCommandSchema
    .parse(
      JSON.parse(changeRestaurantMenuCommandJson),
    );

  const restaurantCreatedEvent: RestaurantEvent = restaurantEventSchema
    .parse(
      JSON.parse(restaurantCreatedEventJson),
    );

  const restaurantMenuChangedEvent: RestaurantEvent = restaurantEventSchema
    .parse(
      JSON.parse(restaurantMenuChangedEventJson),
    );

  DeciderSpecification.for(restaurantDecider)
    .given([restaurantCreatedEvent])
    .when(changeRestaurantMenuCommand)
    .then([restaurantMenuChangedEvent]);
});

Distill

Requirements are expressed as scenarios.

A scenario represents the system's behavior from the user's perspective and follows the Given-When-Then structure to create a testable specification.

The specification is written as executable tests, which are automated and run continuously. These tests cover a full range of scenarios, from happy paths to error cases.

Develop

The f { model } library is implementing the event model in a general way. It promotes clear separation between data and behaviour.

The flow is parametrized with Command, Event, and State parameters. The responsibility of the business is to specialize in their case by specifying concrete Commands, Events, and State. For example, Commands=CreateOrder, AddItemToOrder; Events=OrderCreated, ItemAdded; State=Order (with list of Items).

the Fraktalio logo: a ledger with the shape of letter F
lib/domain.ts
// Restaurant Commands
export type RestaurantCommand =
  | CreateRestaurantCommand
  | ChangeRestaurantMenuCommand
  | PlaceOrderCommand;
  
export type CreateRestaurantCommand = {
  readonly decider: "Restaurant";
  readonly kind: "CreateRestaurantCommand";
  readonly id: RestaurantId;
  readonly name: RestaurantName;
  readonly menu: RestaurantMenu;
};
export type ChangeRestaurantMenuCommand = {
  readonly decider: "Restaurant";
  readonly kind: "ChangeRestaurantMenuCommand";
  readonly id: RestaurantId;
  readonly menu: RestaurantMenu;
};
// A Restaurant Decider
export const restaurantDecider: Decider<
  RestaurantCommand,
  Restaurant | null,
  RestaurantEvent
> = new Decider<RestaurantCommand, Restaurant | null, RestaurantEvent>(
  (command, currentState) => {
    // Ehaustive switch/pattern matching
    switch (command.kind) {
      case "CreateRestaurantCommand":
      ...
      
Relation model for Event Sourcing and Stream Processing
Relation model for Event Sourcing and Stream Processing

Database

event-sourcing and event-streaming with PostgreSQL, FoundationDB, DenoKV, TiKV database.

  • Optimistic Locking - for concurrency control
  • Concurrent Consumers - for efficient streaming
  • At-Least-Once Delivery - to ensure reliability

No additional tools, frameworks, or programming languages are required at this level.