app

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package app assembles a running application from a configuration and a list of modules.

New checks the composition; Run migrates, opens the connection, and then does whichever half of the work this process's role names: web builds the API, proves that every operation declared its authorization and every event it publishes was promised, and serves; worker relays the outbox, consumes subscriptions, schedules the periodic jobs and answers the two probes; all does both in one process. The order is written here, once, in the order it happens — which is the whole argument of docs/adr/0002: a startup sequence that is read rather than derived.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bootstrap

func Bootstrap(ctx context.Context, cfg config.Config, mods []module.Module, fn func(context.Context, db.Tx[db.System]) error) error

Bootstrap migrates the database and runs fn in one cross-tenant transaction.

It is the only way an installation with no tenants gets its first one: every other write in the application happens inside a tenant, and a tenant is what this creates. The migration is part of it because an empty database is the ordinary case here — a person who has just cloned the repository has run `make up` and nothing else — and it is the same ledger the application applies, modules included, because a bootstrap that migrated less than the process it is bootstrapping would leave a schema the app then has to finish.

The lock makes "it refuses once any tenant exists" true of two bootstraps racing and not only of two run in sequence: both would otherwise read an empty tenants table in their own snapshot and both would create one, leaving two first tenants and two administrators who each believe they are the only one. pg_advisory_xact_lock serializes them, so the second reads the first one's tenant and refuses.

func MigrationSources

func MigrationSources(mods []module.Module) (fs.FS, error)

MigrationSources presents migrations/ and every module's SQL to kit/db as one directory.

It is one directory because there is one ledger. golang-migrate records a single version number, so running a second source after the first would compare that source's files against the first source's version and silently apply nothing. Merging the file lists instead keeps the ledger honest and makes E2's move of module SQL into migrations/ a file rename with no change in behaviour.

The price is that version numbers are global. A collision is refused here, naming both owners, rather than becoming a migration that never runs.

It is exported because a repository composing its own modules on top of this one has to build the same union to test them: db.Migrate run once per source does not work — the second call compares its own files against the first's recorded version and fails on the version it cannot find — so a test that migrates a mixed composition needs the union this builds. The catalogue repository wrote its own copy of it, which is one implementation too many for the thing that decides what the schema is.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is a composed application that has not started yet.

func New

func New(ctx context.Context, cfg config.Config, mods []module.Module, opts Options) (*App, error)

New checks the composition and returns the application it describes. Every error it can return is a wiring mistake, so they all surface before anything is opened, listened on or migrated.

func (*App) Run

func (a *App) Run(ctx context.Context) error

Run migrates, then serves or works or both, and returns when ctx is done.

type Options

type Options struct {
	Tenants      httpx.TenantLoader
	Authorize    httpx.Authorizer
	Authenticate func(ctx context.Context, tx db.Tx[db.Tenant], r *http.Request) (tenancy.Principal, bool, error)
	Log          *slog.Logger

	// Role defaults to All.
	Role Role

	// Transport carries events between the relay and the handlers. It defaults
	// to events.Memory() for All, which needs no broker because there is no
	// second process, and to JetStream on config's nats.url otherwise.
	Transport events.Transport
}

Options are the cross-cutting implementations main chooses: the three questions the kernel cannot answer for itself — which host is which tenant, who is calling, and what they may do — plus the role, the event transport, the tenant list the periodic jobs walk, and where to log.

type Role

type Role string

Role is which half of the application a process runs. One binary and one image; the role is a flag. See docs/adr/0005.

const (
	// Web serves the API and nothing else.
	Web Role = "web"
	// Worker relays the outbox, consumes events and runs the periodic jobs.
	Worker Role = "worker"
	// All is both in one process, which is what a laptop and a small
	// deployment want. It is the default, so a deployment that says nothing
	// gets a whole application.
	All Role = "all"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL