Documentation
¶
Overview ¶
Package warren owns application bootstrap, the module system, and the run loop.
A module declaration is a value, not a side effect: NewModule returns an inert data structure and registers nothing. The bootstrapper walks the whole graph first, then materialises one DI scope per module, copies in only what each module's imports export, validates the whole graph, and only then instantiates anything — every error the framework can detect surfaces at boot, never on request 1.
Index ¶
- type App
- func (a *App) Invoke(module string, fn any) error
- func (a *App) Run() error
- func (a *App) Start(ctx context.Context) error
- func (a *App) Stop(ctx context.Context) error
- func (a *App) Substitute(subs ...Substitution) error
- func (a *App) Telemetry(t app.Telemetry) error
- func (a *App) Validator(v validate.Validator) error
- type Module
- type ModuleOption
- func Consumers(consumers ...any) ModuleOption
- func Controllers(controllers ...any) ModuleOption
- func Eager[T any]() ModuleOption
- func Exports[T any]() ModuleOption
- func Imports(modules ...Module) ModuleOption
- func OnStart(fn func(context.Context) error) ModuleOption
- func OnStop(fn func(context.Context) error) ModuleOption
- func Optional[T any]() ModuleOption
- func Providers(constructors ...any) ModuleOption
- type Substitution
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is a bootstrapped application: the flattened module graph, its scoped containers, and its run loop.
func New ¶
New builds an App from the given module declarations. It does no fallible work — it collects the inert values and allocates the lifecycle; the boot sequence runs in Run or Start.
func (*App) Invoke ¶
Invoke resolves fn's parameters from the named module's scope and calls fn — the seam tests and pre-transport mains reach the components the boot built, without constructing second instances. Module encapsulation holds: fn sees exactly what the module's own constructors see, own bindings and imported exports, nothing else. It is boot-time machinery (invariant 7 is about the request path); a transport adapter, once one exists, is the production caller of your handlers.
func (*App) Run ¶
Run boots the application and blocks until SIGINT or SIGTERM, then runs the shutdown sequence and returns. It returns the boot error if boot fails, otherwise whatever Stop returns. A second signal during shutdown cancels the drain — the force-exit short-circuit.
func (*App) Start ¶
Start runs boot steps 0–7 — flatten, scope, copy exports, validate, instantiate, hook up, open readiness — and returns once the application is serving. It exists so tests can drive boot without signals. Failure at any step is a startup failure; nothing is left half-started.
func (*App) Stop ¶
Stop runs the shutdown sequence: readiness closes first, then hooks stop in reverse order, bounded by the force-exit deadline.
func (*App) Substitute ¶
func (a *App) Substitute(subs ...Substitution) error
Substitute applies substitutions before boot: Substitute[T] replaces every provider of T, Bind[T] adds one in the root scope. It must be called before Start.
func (*App) Telemetry ¶ added in v0.2.0
Telemetry sets the instrumentation compiled into every route at boot: with one bound, boot step 5 wraps app.Traced and app.Metered around every handler once, and the request path decides nothing.
It is the non-DI path — a test, or a main that constructs its own. A service that lists observability.Module needs none of it: the bootstrapper resolves an exported app.Telemetry from the graph and uses that. It must be called before Start.
func (*App) Validator ¶ added in v0.2.0
Validator sets the validator whose rules are compiled into every route closure at boot step 5. The default is validate.Required(). It must be called before Start.
It is the reachable form of the fix transport's own diagnostic promises: "transport.WithValidator(validate.None())" names a Builder that, until this existed, only the bootstrapper held.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is an inert declaration of one module: its name, its imports, its providers, controllers, consumers, exports, and its lifecycle hooks. Constructing a Module registers nothing and performs no work.
Because Imports carries Module VALUES rather than names, a cycle between modules cannot reach the bootstrapper: closing one is recursion in the user's own declarations, before New is ever called. Go rejects both shapes it can take, at COMPILE time — verified, not assumed:
modules in different packages → import cycle not allowed modules in one package → initialization cycle for A
So the failure is a build error naming both sides, which is a better diagnostic than Warren could produce anyway. What Warren does NOT catch is an arrangement that defeats both checks — an indirection through a function variable, say — and that fails as recursion or a deadlock in package init, with a Go stack and no mention of modules. Cycles between PROVIDERS are a different thing entirely and are detected by warren/di, which reports them as a Warren diagnostic naming the loop.
func NewModule ¶
func NewModule(name string, opts ...ModuleOption) Module
NewModule returns an inert Module value named name, configured by opts. Nothing is registered and no container is touched. The call site is recorded: it is the "declared in module.go:14" line of the missing-provider diagnostic.
Declare each module ONCE. Modules are deduplicated by identity, so the natural `func Module() warren.Module` factory produces two distinct modules the moment two features import it, and two modules sharing a name is a boot error. The idiom that reads like a function and yields one identity:
var Module = sync.OnceValue(func() warren.Module {
return warren.NewModule("platform", ...)
})
type ModuleOption ¶
type ModuleOption func(*Module)
ModuleOption configures a Module during NewModule.
func Consumers ¶
func Consumers(consumers ...any) ModuleOption
Consumers declares the constructors of this module's message consumers. They are instantiated at boot; broker adapters register them at boot step 5. Like controllers, a consumer's constructor is also a provider — list it in one place only.
func Controllers ¶
func Controllers(controllers ...any) ModuleOption
Controllers declares the constructors of this module's controllers. They are instantiated at boot; transport adapters register their routes at boot step 5. A controller's constructor is also a provider — list it here only, not in Providers too, or the duplicate registers as an ambiguous binding.
func Eager ¶
func Eager[T any]() ModuleOption
Eager declares that T is materialised at boot even when nothing in the graph consumes it — for modules whose provider's construction IS the point. config.Module uses it so a bad config fails the boot even if no constructor injects the struct; without it, an unconsumed provider is simply never built.
func Exports ¶
func Exports[T any]() ModuleOption
Exports makes T resolvable by modules that import this module. Anything not exported stays private. T must be a declared return type of one of the module's providers, controllers, or consumers — exporting anything else is a boot error, and a constructor returning a concrete type does not match an exported interface: declare the constructor's return type as the interface.
func Imports ¶
func Imports(modules ...Module) ModuleOption
Imports declares the modules this module depends on. Only the imported modules' exported bindings become visible to it.
func OnStart ¶
func OnStart(fn func(context.Context) error) ModuleOption
OnStart registers a startup hook for this module, run in dependency order at boot step 6. The hook is a plain closure fixed at declaration time and resolves nothing from the container; a hook that needs something built at boot — a consumer pipeline's drain func, a connection opened by a constructor — is registered the other way: the constructor injects lifecycle.Lifecycle (provided in the root scope) and appends its own lifecycle.Hook.
func OnStop ¶
func OnStop(fn func(context.Context) error) ModuleOption
OnStop registers a shutdown hook for this module, run in reverse order at shutdown step 10. See OnStart for the boot-time-created alternative — the injected-Lifecycle pattern is how a consumer registers its drain.
func Optional ¶ added in v0.2.0
func Optional[T any]() ModuleOption
Optional declares that a nil T from one of this module's providers is MEANT, and must not fail the boot the way an undeclared nil does.
A provider returning nil is normally a boot error: warren.md §1.3's rule is that every detectable error surfaces at boot, and a nil interface otherwise booted clean and became a 500 on the first request to touch it. But some capabilities are legitimately absent. warren/observability returns a nil app.Telemetry when no collector is configured, and app.WithTelemetry drops a nil so the uninstrumented request path stays a pass-through — a no-op value instead would ride every request context and cost real work per request, which is the property the nil exists to preserve.
Optional is per TYPE, not per module: declaring one absence does not disarm the check for anything else the module provides. Consumers of an optional binding must handle the nil — that is the contract they are opting into.
func Providers ¶
func Providers(constructors ...any) ModuleOption
Providers declares constructors owned by this module. A provider is private to its module unless its result type is also named in Exports.
Constructors wire; OnStart acquires. A constructor that opens a connection or starts a goroutine owns a resource the boot sequence cannot release if a later module fails to build — put acquisition in an OnStart hook, whose rollback the lifecycle guarantees.
type Substitution ¶
type Substitution struct {
// contains filtered or unexported fields
}
Substitution replaces or adds a binding before boot. It is the seam test harnesses use to inject fakes, and main can use it to provide a value it computed itself.
func Bind ¶
func Bind[T any](v T) Substitution
Bind provides v as T in the root scope, where every module can see it.
If the graph already provides T, Bind REPLACES that provider rather than colliding with it: a harness binding a fake broker into an application whose platform module provides a real one is the normal case, and an ambiguous-binding failure there would be useless. Use Substitute when the replacement is required — it fails the boot if nothing matched.
func Substitute ¶
func Substitute[T any](v T) Substitution
Substitute replaces every provider of T with v. An unmatched substitution is a boot error naming T — a typo'd fake is never silently ignored, which is the failure mode that makes test doubles untrustworthy.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package app defines Warren's central abstraction: a transport-agnostic use case, and the core-ring middleware shape that decorates it.
|
Package app defines Warren's central abstraction: a transport-agnostic use case, and the core-ring middleware shape that decorates it. |
|
internal/exampledomain
Package domain is the user-side domain of the §10 example, so the app tests can compile warren.md's handler verbatim.
|
Package domain is the user-side domain of the §10 example, so the app tests can compile warren.md's handler verbatim. |
|
Package broker defines Warren's messaging ports: one driver-neutral message envelope, a publisher, a subscriber, and a message handler.
|
Package broker defines Warren's messaging ports: one driver-neutral message envelope, a publisher, a subscriber, and a message handler. |
|
brokertest
Package brokertest is the contract suite every broker driver must pass: the in-process one, Kafka, RabbitMQ, NATS.
|
Package brokertest is the contract suite every broker driver must pass: the in-process one, Kafka, RabbitMQ, NATS. |
|
memory
Package memory is the in-process broker: the default in tests, and the driver a modular monolith runs in production before its modules are extracted into services.
|
Package memory is the in-process broker: the default in tests, and the driver a modular monolith runs in production before its modules are extracted into services. |
|
kafka
module
|
|
|
cli
module
|
|
|
Package config owns layered configuration resolution: struct defaults, then file sources, then environment variables, then command-line flags — later layers win, and the merged result is checked before boot continues.
|
Package config owns layered configuration resolution: struct defaults, then file sources, then environment variables, then command-line flags — later layers win, and the merged result is checked before boot continues. |
|
Package di owns Warren's dependency-injection container: scoping, graph validation, and diagnostics.
|
Package di owns Warren's dependency-injection container: scoping, graph validation, and diagnostics. |
|
internal/fixture/domain
Package domain is the contracts side of the §1.2 fixture graph the di tests run against.
|
Package domain is the contracts side of the §1.2 fixture graph the di tests run against. |
|
internal/fixture/postgres
Package postgres is the providing module of the §1.2 fixture graph.
|
Package postgres is the providing module of the §1.2 fixture graph. |
|
internal/fixture/user
Package user is the consuming module of the §1.2 fixture graph.
|
Package user is the consuming module of the §1.2 fixture graph. |
|
Package domain provides the DDD building blocks Warren's other contracts are expressed in terms of: identity, aggregates, events, and specifications.
|
Package domain provides the DDD building blocks Warren's other contracts are expressed in terms of: identity, aggregates, events, and specifications. |
|
Package errors defines Warren's semantic error vocabulary: a closed set of codes that describe what went wrong in terms a domain expert would use, with no reference to any transport.
|
Package errors defines Warren's semantic error vocabulary: a closed set of codes that describe what went wrong in terms a domain expert would use, with no reference to any transport. |
|
Package health holds the registry of checks a service can be asked about and renders the two probe verdicts.
|
Package health holds the registry of checks a service can be asked about and renders the two probe verdicts. |
|
Package inbox defines the dedupe-store port the consumer chain's Deduplicate stage records processed Message.IDs in, and ships the stdlib-only memory store that makes dedupe-by-default cost neither Docker nor a database.
|
Package inbox defines the dedupe-store port the consumer chain's Deduplicate stage records processed Message.IDs in, and ships the stdlib-only memory store that makes dedupe-by-default cost neither Docker nor a database. |
|
inboxtest
Package inboxtest is the contract suite every inbox.Store must pass: the in-process one, and the durable stores that arrive with the persistence adapters.
|
Package inboxtest is the contract suite every inbox.Store must pass: the in-process one, and the durable stores that arrive with the persistence adapters. |
|
internal
|
|
|
panics
Package panics contains one recovered panic and renders it as a Warren diagnostic.
|
Package panics contains one recovered panic and renders it as a Warren diagnostic. |
|
Package lifecycle owns ordered startup and shutdown, readiness gating, and drain.
|
Package lifecycle owns ordered startup and shutdown, readiness gating, and drain. |
|
Package log carries a *slog.Logger on the context and propagates the correlation ID that ties one request's records together.
|
Package log carries a *slog.Logger on the context and propagates the correlation ID that ties one request's records together. |
|
observability
module
|
|
|
Package outbox implements the transactional outbox: the pattern that makes a state change and the events announcing it atomic without a distributed transaction.
|
Package outbox implements the transactional outbox: the pattern that makes a state change and the events announcing it atomic without a distributed transaction. |
|
Package persistence is the port repositories and units of work are written against: load and store aggregates by identity, and make the state a handler wrote and the events its aggregates raised commit together or not at all.
|
Package persistence is the port repositories and units of work are written against: load and store aggregates by identity, and make the state a handler wrote and the events its aggregates raised commit together or not at all. |
|
postgres
module
|
|
|
Package warrentest boots a Warren module for a test with dependencies substituted, invokes handlers by request and response type, and asserts on what was published.
|
Package warrentest boots a Warren module for a test with dependencies substituted, invokes handlers by request and response type, and asserts on what was published. |
|
Package transport is the port through which a controller exposes a use case over HTTP, gRPC, and events.
|
Package transport is the port through which a controller exposes a use case over HTTP, gRPC, and events. |
|
http
module
|
|
|
Package validate is the port transport adapters validate decoded requests through, plus the standard-library implementation of validate:"required".
|
Package validate is the port transport adapters validate decoded requests through, plus the standard-library implementation of validate:"required". |
|
playground
module
|