# Start Here: Why Factos Exists Factos is a Gleam library for building event-sourced systems around the facts a business decision actually needs. The short version is: ```text command + relevant accepted facts -> new facts or a domain error ``` Your application defines the commands, events, rules, errors, event codecs, and read models. Factos supplies the small shared model that connects those pieces to an event store, plus backends that make the read-decide-append flow safe under concurrent use. This page explains the problem Factos solves, the value of its approach, and where each part of the library fits. No prior knowledge of Domain-Driven Design (DDD), Event Sourcing, or Dynamic Consistency Boundaries (DCB) is assumed. ## The problem: a rule rarely belongs to one row Consider a course-subscription system with these rules: - a course cannot exceed its capacity; - a student cannot subscribe to the same course twice; - a student cannot subscribe to more than five courses. To decide whether student `s1` may join course `c1`, the application needs facts about both that course and that student. Two requests may also race for the last seat. Reading two current database rows and updating them later is not enough: the answer may become stale between the read and the write. A traditional event-sourced design often puts each entity in its own event stream. That makes a rule local to one stream easy to protect with an expected stream revision, but a rule spanning a student and a course now spans two streams. The usual remedies—larger aggregates, process managers, reservations, or compensation—can add coordination that the business rule did not ask for. Factos takes a different approach: the consistency boundary is the set of facts used by this command. It does not have to be a permanent stream or object boundary. ## The Factos approach For `SubscribeStudentToCourse(s1, c1)`, the application declares a decision context containing: 1. course-definition, capacity-change, and subscription events tagged `course:c1`; and 2. subscription events tagged `student:s1`. Factos then supports this flow: ```text select relevant events | v fold them into temporary decision state | v run a pure business decision | v append new events only if no matching event appeared meanwhile ``` The accepted fact can be one event: ```text StudentSubscribedToCourse(student: s1, course: c1) ``` It carries both `student:s1` and `course:c1` tags. Future commands can find the same fact from either point of view. There is no need to record two copies of one business fact merely because two concepts are involved. This is the core idea behind **Dynamic Consistency Boundaries**. “Dynamic” means the protected set is chosen for each command. “Consistency boundary” means a concurrent change to that set invalidates the decision before its events can be committed. ## What each layer owns | Concern | Your application | Factos | | --- | --- | --- | | Business language | Commands, events, errors, state names | No generic domain vocabulary | | Business rules | Pure `try` and `evolve` functions | `Model` and dispatch flow | | Relevant history | A `DecisionContext` per command | Matching semantics and append condition | | Persistence format | JSON encoders, version-aware decoders, stable type names and tags | Event envelope and recorded-event types | | Storage | Backend choice and connection lifecycle | PostgreSQL, SQLite, and Cloudflare D1 adapters | | Read models | Projection schema and update logic | Transactional subscription hooks where supported | | Testing | Scenarios and expected domain behavior | Store-free simulator and backend projection simulators | Factos deliberately does not own your domain model. There is no aggregate base class, repository interface, global command bus, projection framework, HTTP layer, or message broker hidden behind the API. ## The five pieces of a model ### 1. Commands ask for change A command is an intent, usually named in the imperative: ```gleam pub type Command { SubscribeStudentToCourse(student_id: String, course_id: String) } ``` A command can be rejected. It is not part of the permanent history just because someone requested it. ### 2. Events record accepted facts An event is a past-tense fact that the domain accepted: ```gleam pub type Event { StudentSubscribedToCourse(student_id: String, course_id: String) } ``` Events are the authoritative history. Their stored type names, versions, payloads, and selection tags form a long-lived data contract. ### 3. A model contains the rule A `Model` has three pure decision parts: - `initial` is the temporary empty decision state; - `evolve` folds relevant historical events into that state; - `try` accepts the state and command, then returns new events or a domain error. The state is a calculation aid, not a stored mutable domain object. A backend may retry the transaction, so decision code must be deterministic and perform no IO. ### 4. A decision context names relevant facts Factos provides three context shapes: - `NoContext` when no prior event can change the answer; - `Matching(items:)` for selective event-type and tag predicates; - `AllEvents` when every event really is relevant. Use the narrowest context that fully protects the business invariant. Narrow contexts allow unrelated commands to proceed independently. ### 5. A backend makes the decision safe The core `factos` package is store-independent. A backend reads the context, applies the model's decision functions, checks the append condition, commits the accepted event batch, and returns globally ordered `Recorded` events. | Package | Use it for | | --- | --- | | `factos` | Domain model, event envelope, context types, and simulation | | `factos_pog` | PostgreSQL with serializable dispatch transactions | | `factos_sqlight` | SQLite through Sqlight | | `factos_cf` | Cloudflare D1 transaction plans | Backend guarantees and transaction APIs differ. Read the selected package's API documentation before choosing where projections or other transactional work runs. ## What value does this provide? ### Business rules stay visible The command, relevant facts, decision, accepted events, and domain errors remain ordinary application types and pure functions. Storage mechanics do not become the domain language. ### Consistency follows the rule A command can protect facts about one entity, several entities, or a unique value without forcing every command through the same coarse boundary. This is particularly useful for uniqueness, capacity, quotas, reservations, and other cross-entity invariants. ### One fact can serve several viewpoints Tags let a single event participate in multiple future decisions. Projections can independently turn the same history into query-oriented views. ### Domain scenarios run without infrastructure `factos/simulate` can add existing events, dispatch real commands through the real model and codecs, and inspect accepted events or errors. A database is still required to prove a backend's transaction and concurrency guarantees. ### Storage remains replaceable at the model boundary The core model does not depend on a database connection. Backends share the same commands, events, decisions, codecs, and contexts while exposing their actual transaction semantics rather than pretending every store behaves identically. ## When Factos is a good fit Factos is worth considering when: - accepted business facts are valuable as an audit and decision history; - important rules span concepts that do not fit one fixed stream boundary; - you want pure, deterministic domain decisions in Gleam; - you can design and maintain event schemas and projections deliberately; and - your chosen backend can provide the consistency required by those rules. It is probably unnecessary when a CRUD model already expresses the domain well, history has no business or operational value, or eventual consistency is acceptable for every cross-record rule. Event Sourcing adds schema evolution, projection recovery, storage growth, and operational responsibilities; Factos does not make those costs disappear. ## A practical learning path 1. Read [Domain-Driven Design: a practical primer](domain-driven-design.html) for the modelling vocabulary. 2. Read [Event Sourcing and command dispatch](event-sourcing.html) for the read-decide-append lifecycle. 3. Read [Dynamic Consistency Boundaries](dynamic-consistency-boundaries.html) for the concurrency model and the course example. 4. Use [The Factos core model](core-model.html) as the API-oriented reference. 5. Run one of the repository's [DCB examples](https://tangled.org/renatillas.dev/factos/tree/main/examples), then test the same model through the backend you intend to deploy.