f { store }

Postgres Supabase Starter Kit For Event-Driven Applications

Foundation for building compositional, safe and ergonomic applications

#EventSourcing #EdgeFunctions

Sign Up / Sign In   Buy for $399 (available soon)   Documentation

Configuration in code

Supabase migration scripts and edge functions included.

Store all of your table schemas, SQL functions and rules in code.
$ supabase start

Admin Application

Technology: Next.js 14, React, TypeScript, TailwindCSS, Shadcn/Ui

Manage components. Browse events and observe the business metrics.
$ yarn dev

Build Command Handlers

Focus on the business logic - the Decider, and run it as Edge function. Combine many deciders to evolve command handling capabilities.

Deno.serve(async (cmd: Request) => await edgeCommandHandler(
    cmd,
    commandAndMetadataSchema,
    restaurantDecider.combine(orderDecider),
    ),
);

Build Event Handlers

Focus on the business logic - the View, and run it as Edge function. Combine many views to evolve event handling capabilities.

Deno.serve(async (evt: Request) => await edgeEventHandler(
    evt,
    eventAndMetadataSchema,
    restaurantView.combine(orderView),
    new RestaurantViewStateRepository(
      authenticatedClient(evt),
    )));

Register Command Handlers

Specify the event types that comand handler can publish.

Control the design, so `bad` events are not stored by mistake.

Register Event Handlers

Stream events to concurent consumers, and track the progress.

Control how the events are consumed, replayed, and projected.

Achieve complete automation of information flow and business processes

#Discover #Design #Develop #Deploy

Consult us (available soon)   Looking for an IT partner (available soon)?

Event Modeling

Event Modeling

Event Modeling is an effective technique to discover a domain. It is a method of describing systems using an example of how information has changed within them over time. Specifically this omits transient details and looks at what is durably stored and what the user sees at any particular point in time. These are the events on the timeline that form the description of the system.


We can show, by example, what a system is supposed to do from start to finish, on a time line and with no branching.

Specification By Example

Specification By Example is a collaborative approach to software analysis and testing. It is the fastest way to align people from different roles on what exactly we need to build and how to test it.


The requirements are presented as scenarios. A scenario is an example of the system’s behavior from the users’ perspective, and they are specified using the Given-When-Then structure to create a testable specification.

specification by example
decider kotlin

f { model } / Software Model

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).


Edge Functions

You can run three types of functions on the platform:

1. Command-handlers (deciders) - responsible for handling commands and producing new events/decisions.

2. Event-handlers responsible for:
  a) handling events and evolving the materiliazed view(s) state based on these events
  b) automating integration with other systems, like payment providers.

3. Query-handlers (views) - responsible for querying the materilized view(s) state.


Moreover, it`s worth noting that these functions can be executed in a conventional manner, either on the server or directly within the database. They are not restricted solely to edge processing.

CQRS

Decider / Command Handler

A pure command handling algorithm, responsible for making decisions/events based on the commands and the current state. It does not produce any side effects, such as I/O, logging, etc. It is written in the TypeScript programming language and utilizes type narrowing to make sure that the command is handled exhaustively.


The infrastructure is already implemented for you and it is supporting the Edge scenario, in where we use Supabase API to store and fetch events, so you can focus on the business logic and the core domain model!


export const restaurantDecider: Decider<
RestaurantCommand,
Restaurant | null,
RestaurantEvent
> = new Decider<RestaurantCommand, Restaurant | null, RestaurantEvent>(
(command, currentState) => {
  switch (command.kind) {
    case "CreateRestaurantCommand":
      return (currentState === null ||
          currentState.restaurantId === undefined)
        ? [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantCreatedEvent",
            id: command.id,
            name: command.name,
            menu: command.menu,
            final: false,
          },
        ]
        : [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantNotCreatedEvent",
            id: command.id,
            name: command.name,
            menu: command.menu,
            reason: "Restaurant already exist!",
            final: false,
          },
        ];
    case "ChangeRestaurantMenuCommand":
      return (currentState !== null &&
          currentState.restaurantId === command.id)
        ? [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantMenuChangedEvent",
            id: currentState.restaurantId,
            menu: command.menu,
            final: false,
          },
        ]
        : [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantMenuNotChangedEvent",
            id: command.id,
            menu: command.menu,
            reason: "Restaurant does not exist!",
            final: false,
          },
        ];
    case "PlaceOrderCommand":
      return (currentState !== null &&
          currentState.restaurantId === command.id)
        ? [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantOrderPlacedEvent",
            id: command.id,
            orderId: command.orderId,
            menuItems: command.menuItems,
            final: false,
          },
        ]
        : [
          {
            version: 1,
            decider: "Restaurant",
            kind: "RestaurantOrderNotPlacedEvent",
            id: command.id,
            orderId: command.orderId,
            menuItems: command.menuItems,
            reason: "Restaurant does not exist!",
            final: false,
          },
        ];
    default:
      // Exhaustive matching of the command type
      const _: never = command;
      return [];
  }
},
(currentState, event) => {
  switch (event.kind) {
    case "RestaurantCreatedEvent":
      return { restaurantId: event.id, name: event.name, menu: event.menu };
    case "RestaurantNotCreatedEvent":
      return currentState;
    case "RestaurantMenuChangedEvent":
      return currentState !== null
        ? {
          restaurantId: currentState.restaurantId,
          name: currentState.name,
          menu: event.menu,
        }
        : currentState;
    case "RestaurantMenuNotChangedEvent":
      return currentState;
    case "RestaurantOrderPlacedEvent":
      return currentState;
    case "RestaurantOrderNotPlacedEvent":
      return currentState;
    default:
      // Exhaustive matching of the event type
      const _: never = event;
      return currentState;
  }
},
null,
);
                
cqrs

export const restaurantView: View<RestaurantView | null, RestaurantEvent> =
new View<RestaurantView | null, RestaurantEvent>(
  (currentState, event) => {
    switch (event.kind) {
      case "RestaurantCreatedEvent":
        return { restaurantId: event.id, name: event.name, menu: event.menu };
      case "RestaurantNotCreatedEvent":
        return currentState;
      case "RestaurantMenuChangedEvent":
        return currentState !== null
          ? {
            restaurantId: currentState.restaurantId,
            name: currentState.name,
            menu: event.menu,
          }
          : currentState;
      case "RestaurantMenuNotChangedEvent":
        return currentState;
      case "RestaurantOrderPlacedEvent":
        return currentState;
      case "RestaurantOrderNotPlacedEvent":
        return currentState;
        // deno-lint-ignore no-case-declarations
      default:
        // Exhaustive matching of the event type
        const _: never = event;
        return currentState;
    }
  },
  null,
);
                

View / Event Handler

A pure event handling algorithm, responsible for evolving the state of the view/projection. It does not produce any side effects, such as I/O, logging, etc. It is written in the TypeScript programming language and utilizes type narrowing to make sure that the event is handled exhaustively.

Query Handler

As your event handler is responsible for evolving the state of the materilized view, the query handler is responsible for querying the materilized view state. Supabase will create the API on top of the Postgres database, so you can query the materilized view state. You can choose to create an edge function to handle the query or use the Supabase API directly.

Process Manager

Event Handlers track the progress of the process by storing the position/offset of the last event that was processed successfully. This is what makes them Process Managers, and they can be used to implement Sagas, Orchestrators, and other process management/integration patterns.

Consult us (available soon)   Looking for an IT partner (available soon)?