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) []db.MigrationSource
MigrationSources keeps the foundation first and modules in composition order. Module names own their histories; no global version allocation is needed. A module's Adopts travels with its files, so an installation migrated before the module owned its SQL is re-owned on the way past and nothing re-runs.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is a composed application that has not started yet.
type Options ¶
type Options struct {
Tenants httpx.TenantLoader
Authorize httpx.Authorizer
// Entitle answers what a tenant's plan includes, for the operations whose
// declaration names a feature. It is optional: an application that sells
// nothing declares no feature and is never asked. One that does declare a
// feature and leaves this nil fails to start, which is httpx's check and
// not this one.
Entitle httpx.Entitler
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. An explicit
// value overrides nats.transport and Transports. Otherwise All defaults to
// memory and a separate Worker to JetStream; nats.transport can opt All
// into JetStream, and Transports says how each is built.
Transport events.Transport
// Transports are the constructors behind nats.transport's two names. New
// refuses a composition whose selected name has no constructor, so a
// worker that would have found out at Run finds out before anything is
// opened; the check runs in every role, because one image must answer
// "will this start?" the same way whichever half it runs.
Transports Transports
}
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 constructors, 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" )
type Transports ¶ added in v1.1.0
type Transports struct {
// Memory builds the in-process transport the combined role defaults to.
Memory func() events.Transport
// JetStream builds the shared broker transport a separate role defaults
// to, from the NATS settings New has already validated.
JetStream func(config.NATS) (events.Transport, error)
}
Transports is how the two event transports nats.transport can name are built. The kernel knows the names and the rule between them — memory is one process talking to itself, so a separate worker cannot use it — and nothing about how either is made: the composing application supplies both and is the one importer of the provider packages, so kit/app links neither the NATS client nor the in-process queue (scripts/check_packages.sh records that). apps/platformkit passes memory.New and nats.Connect; a product that runs one process forever may leave JetStream nil and set nats.transport to memory, and is refused the day a separate worker is deployed against it.