Skip to content

Architecture and modules

SeeAMess is built from a few modules with one-way dependencies and no shared in-process state. One binary runs any subset of them.

SeeAMess modulesEditors use the Editor UI, which sends commands and queries to Authoring. Authoring writes outbox events that the Projector turns into the published read model. Delivery reads it and serves visitors and front-ends. Authoring uses the Schema, Policy, Media, Jobs and external extension host modules. PostgreSQL and a blob store hold all state.EditorsVisitors, front-endsEditor UIstatic React SPADeliveryread-only, cacheablecommands, queriesreads publishedAuthoringthe only writerProjectorbuilds publishedoutbox eventsin process, or HTTP when splitSchemaimmutable snapshotPolicyauthorize()Mediauploads, renditionsJobsleased, run onceExtension hosts.NET or Rust, externalStatePostgreSQLauthoring schema · published schema · outboxBlob storeS3 API or disk, addressed by SHA-256SeeAMess modulesEditors use the Editor UI, which sends commands to Authoring. Authoring writes outbox events that the Projector turns into the published read model, which Delivery serves to visitors. Schema, Policy, Media, Jobs and extension hosts support Authoring. PostgreSQL and a blob store hold all state.EditorsEditor UIstatic React SPAcommands, queriesAuthoringthe only writeroutbox eventsProjectorbuilds publishedpublishedDeliveryread-only, cacheableVisitors, front-endsModulesSchemasnapshotPolicyauthorize()MediarenditionsJobsleasedExtension hosts.NET or Rust, outside the coreStatePostgreSQLauthoring · published · outboxBlob storeS3 API or disk
Module Owns Scales with Notes
Schema Content types, fields, schema revisions, form metadata Rarely changes Loaded into memory as an immutable snapshot. A new revision swaps the snapshot.
Authoring Content identity, tree, versions, publishing, references, access lists Number of editors The only writer of content. Every command is one transaction and appends to the outbox.
Projector The published read model, routes, search feed Publish volume Consumes the outbox. Can be rebuilt from versions at any time.
Delivery Nothing. Reads published. Visitor traffic Can run against a read replica, in another region, with no access to authoring tables.
Media Blob metadata, uploads, image renditions Upload and transform volume Content-addressed blobs (SHA-256). Renditions are made on demand, signed, and cached.
Jobs Scheduled publish, cleanup, extension jobs Fixed A job is claimed with a database lease, so it runs once however many nodes exist.
Editor UI Nothing. Static files. CDN One React application. Forms come from the schema.

No module reads another module’s tables, except Delivery reading published, which is the Projector’s public output.

The seeamess binary has six roles: authoring, projector, delivery, media, jobs and editor. Choose them with SEEAMESS_ROLES or --roles:

seeamess --roles authoring,projector,delivery,media,jobs

Without either, every role runs: the shape for development and small sites. Production starts the same binary several times with different roles. Modules call each other through traits in process, and through HTTP when they are split.

Roles in one process Behaviour
authoring and projector Commands project inside their own transaction, so a publish is visible at once.
authoring without projector Commands only write the outbox; a process with projector projects the events.
projector alone Consumes the outbox and projects every event written elsewhere. Two projectors do not double-apply.
delivery alone Needs neither migrations nor the schema. Keeps answering while authoring is stopped.
jobs Runs the job runner and one event consumer per subscribed extension.
media Needs a migrated database; reads the schema itself. A process without it serves no files.
editor Serves the editor’s static files at /editor/.

Measured with authoring, projector and delivery as three processes on one database and a 250 ms outbox poll: a publish is visible on the delivery process after 245 ms (median of 20). See Performance results.

  • Rust for everything on a request path that must be fast or small: authoring commands, delivery, media, jobs.
  • Rust or .NET 10, the integrator’s choice, for what integrators write: content types, extension hosts, typed clients.
  • .NET for the Optimizely adapter, because it sits next to Optimizely code.

The boundary is always a wire contract (JSON over HTTP, events), never a shared library.

Goal What it rules out
Fast, memory efficient Runtime type scanning, runtime proxies, an unbounded object cache, a first-request initializer
Modular A static service locator, modules that reach into each other’s tables
Scalable Sticky sessions, per-node scheduled jobs, node-local caches that need a custom invalidation bus
Secure A built-in user table, a first-admin registration page, module-level policies that can be forgotten
Easy to extend In-process plugins, a second UI framework
A reversible path from Optimizely Any Optimizely concept in the core, writing into Optimizely’s tables