Documentation
¶
Overview ¶
Package module defines what a module is.
A module is a plain Go package that exports a Module value. main constructs each one with its typed dependencies, in dependency order, and passes the list to app.New. There is no registry to enrol in, no group to join and no reflection to resolve: the wiring graph is the argument list, and the compiler checks it. See docs/adr/0002.
The manifest below is what the kernel needs to know about a module that it cannot learn from a function call: the permissions it defines, the events it emits and consumes, its periodic work, where it appears in navigation, what makes it healthy, its SQL, and its routes. Nothing else belongs in it — a field the kernel never reads is a field a module will fill in and no one will honour.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Validate ¶
Validate checks a set of modules against the rules that make the namespacing real: names are unique and well-formed, no two modules define the same permission, every nav entry points at a permission somebody declared, every event a module emits is namespaced by that module's name, every subscription names an event somebody emits, and every job has a schedule that parses.
It reports every violation in one error rather than the first, because a composition is fixed once and the whole list is what a person needs.
Types ¶
type Module ¶
type Module struct {
// Name is the module's namespace: its events are prefixed with it, so an
// event name says which module emitted it. Permissions are named after the
// resource they guard, not after the module, because a permission outlives
// the module that first defined it.
Name string
// Permissions are the permission keys this module defines, "<resource>:<action>".
Permissions []Permission
// Events are the event names this module emits, "<name>.<event>". Every
// event a rest.Spec would publish has to appear here, or the app refuses to
// start: a module that emits something it never promised is an integration
// nobody can find.
Events []string
// Subscriptions are the events this module handles. The worker role
// subscribes each one; the name has to be an event some module emits.
Subscriptions []events.Subscription
// SubscribeAll says this module handles every event the application emits,
// whichever module emits it: the kernel expands the one subscription above
// into one per declared event, after every manifest has been read.
//
// There is one such module, modules/audit, and the field exists because the
// alternative failed quietly. main used to compute the list and hand it over
// as a dependency, which was correct only while audit was composed last: a
// module listed after it was a module nothing recorded, and nothing said so.
//
// A manifest that sets it declares exactly one subscription, with no Name:
// the name is what the kernel fills in, once per event.
SubscribeAll bool
// Jobs are this module's periodic work. The worker role schedules each one,
// and exactly one instance in the cluster runs it per tick.
Jobs []jobs.Job
Nav []NavEntry
// Migrations is this module's SQL, with the files at the root of the given
// fs.FS. Every module in this repository leaves it nil and puts its SQL in
// migrations/ instead, which is where ARCHITECTURE.md says SQL lives; the
// field is the door for a module that ships its own, and the files it
// carries are still numbered uniquely across the whole application, because
// there is one ledger. app.Run refuses a collision.
Migrations fs.FS
// Routes registers this module's operations, each with its authorization.
Routes func(api *httpx.API)
}
Module is one business capability, described to the kernel.
func Expand ¶
Expand turns every SubscribeAll manifest's one subscription into one per event the composition emits, and returns the modules with that done.
One subscription per event rather than one wildcard, because the kernel's durable consumers are named after the subscription: a wildcard would be one consumer whose backlog is every event in the system, and one slow payload would hold up the trail of everything else.
It runs before Validate and before anything is constructed, so a module that arrives after the subscriber in the list is still subscribed to. The argument is not modified: the returned slice is a copy, sorted so every replica registers the same consumers, and in it SubscribeAll is cleared — the flag is a request and this is it answered.
type NavEntry ¶
type NavEntry struct {
}
NavEntry is one link in the application's navigation, shown to a caller who holds Permission. There is no Order: nav is rendered in composition order, which is the order main lists the modules in, and a second ordering nothing reads is a number every module would guess at.
type Permission ¶
type Permission struct {
Key string
// Operator says this permission belongs to the installation rather than to
// a customer: only the operator's own tenant may exercise it at all, and no
// wildcard satisfies it — a role has to name it.
//
// It is a field on the manifest as well as a route declaration
// (httpx.OperatorPermission) because the two have to agree, and a check
// needs both sides to read. kit/app refuses to start when a route and the
// manifest that defines its permission disagree, naming both: a control
// plane route that declared the ordinary kind would be reachable by every
// customer's administrator, and an ordinary route that declared this one
// would be reachable by nobody but the operator.
Operator bool
}
Permission is one thing a role can be granted.