Domain model vs. application model?

Domain model is the data as close to how it appears in real life, e.g. User registered.

Application model is how the application chooses to interpret this data, perhaps even optimized for a narrower use case, e.g. INSERT INTO users SET (username, password) VALUES .... That User registered event had a timestamp associated with it (all events in the domain have a timestamp), but our application model might not care about it. The data and how the application uses the data are two different things - they are decoupled from each other.

Why separate models? The domain is supposed to outlive the application model. We could rewrite this application in a different programming language and use a different database, but the domain model should not change in any way - only the application model changes.

Overview

This is an CQRS + EventSourcing application.

CQRS applied here means that there are totally separate REST endpoints for reading and writing data.

EventSourcing applied here means that all writes to the database are stored as events, and the events are the only source of data used to derive the read model in the database.

To best understand the big picture, here's a journey of a write request, with source code locations:

  • UI invokes HTTP POST /command/account.ChangeDescription with body {"Account": 13, "Description": "This is an example"}
  • Generic command HTTP handler intercepts this request. This handler knows about this command because the command was defined here.
  • It deserializes the JSON, performs basic validations (like is this input field required etc.), and invokes the command handler
  • The command handler raises account.DescriptionChanged event (defined here)
  • Control is returned to the generic command HTTP handler, which appends any raised events to the event log.
  • Event log appending eventually means that an event handler will be invoked in the state package

Here's the most important source code locations:

cmd/

Contains the entrypoint for the single binary that makes up the backend and also serves frontend resources.

frontend/

Contains the source code for the frontend, which is written in React + TypeScript.

pkg/

Contains the backend packages.

pkg/apitypes/apitypes.json

Contains HTTP endpoint definitions and their input/output data structures for the query layer. This is used to autogenerate code for the frontend and backend. This makes both the frontend and the backend typesafe, i.e. Go or TypeScript compilers guarantee that your HTTP endpoint URLs, input and output data structures are kept in-sync with all the code.

Implementations for the endpoints are found in pkg/restqueryapi. Calls come from the UI.

The query endpoints are documented (autogenerated) in a prettier format in this page.

pkg/domain/domain.json

Contains all the event definitions (names and their payload data) that can be raised in this domain.

Implementations for the event listeners are found in pkg/state. These events are mainly raised from pkg/commandhandlers.

The domain information is documented (autogenerated) in a prettier format in this page.

pkg/commandhandlers/commands.json

This file essentially contains all the actions that can be done from the UI. This is used to code generate as much as possible in frontend and backend, so the code should be typesafe at both sides of the HTTP layer.

Implementations for the command handlers are found in pkg/commandhandlers

Also, pkg/commandhandlers/handlers_test.go is a good place to see how the command tests test for command + event + read model interaction.

The commands are documented (autogenerated) in a prettier format in this page.