Documentation
¶
Overview ¶
Package module is the unit of organisation: a feature slice that declares what it provides, routes, owns and needs checked.
The shape is three levels and no more:
App a bounded domain catalog, billing, identity Module one resource in it products, categories, reviews Layer controller / service / repository / dto / entity
A module owns exactly one resource. It declares what it provides, what it routes, which tables it owns and what it needs checked for health — and nothing outside it may reach into its service or its entities. The enforcement is social, plus `icpt lint`, plus the fact that a module's dependencies are explicit in its constructor rather than implicit in a shared package.
Why bother, in a language with packages? Because a package boundary says nothing about ordering, lifecycle or ownership. This says: these providers are constructed before those routes exist, this module's shutdown runs before its dependencies', and these two tables belong to this code and no other.
Index ¶
- func MountApp(r fiber.Router, a App, opt MountOptions) error
- func MountModules(r fiber.Router, appName, prefix string, modules []Module, opt MountOptions) error
- func OwnedEntities(apps ...App) map[string][]any
- func Register(c *di.Container, descriptors ...Descriptor) error
- func Seed(ctx context.Context, apps ...App) error
- type App
- type Base
- type BaseApp
- type Descriptor
- type EntityOwner
- type Guarded
- type HealthContributor
- type Intercepted
- type Module
- type MountOptions
- type Option
- type Seeder
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func MountApp ¶
func MountApp(r fiber.Router, a App, opt MountOptions) error
MountApp mounts one domain at /<app name> and each of its modules at /<app name>/<module name>.
The path is derived from the names rather than configured per route, which is the trade this framework makes everywhere: give up the freedom to put a route anywhere, get a URL you can predict from the directory you are standing in.
func MountModules ¶ added in v1.1.0
MountModules mounts a domain's modules directly on r without adding the domain name to the URL. This is useful for a host application whose public API is already rooted at a version prefix while modules retain native ownership, guards, health, lifecycle, and route metadata.
func OwnedEntities ¶
OwnedEntities maps "app.module" to the tables it declares.
Two modules claiming one table is a design error that shows up as a mysterious write from the "wrong" service months later; this is what makes it checkable.
func Register ¶
func Register(c *di.Container, descriptors ...Descriptor) error
Register runs every descriptor's Providers, in order, before anything is constructed.
Registration is separate from construction and construction is lazy, so two domains may depend on each other's services regardless of the order they are listed in — and a genuine cycle between them is reported as a cycle rather than resolved by luck.
Types ¶
type Base ¶
type Base struct {
// contains filtered or unexported fields
}
Base implements the boilerplate of Module. Embed it:
type Module struct {
module.Base
ctrl *controller.Product
}
func New(s *di.Scope) *Module {
ctrl := controller.New(di.MustResolve[*service.Product](s))
return &Module{Base: module.NewBase("products",
module.WithControllers(ctrl),
module.WithEntities(&entity.Product{}),
)}
}
func (Base) Controllers ¶
func (b Base) Controllers() []router.Controller
Controllers implements Module.
func (Base) HealthChecks ¶
HealthChecks implements HealthContributor.
func (Base) Interceptors ¶
func (b Base) Interceptors() []interceptor.Interceptor
Interceptors implements Intercepted.
type BaseApp ¶
type BaseApp struct {
// contains filtered or unexported fields
}
BaseApp implements the boilerplate of App.
type Descriptor ¶
type Descriptor struct {
// Providers registers everything the domain's modules resolve. It runs
// for every descriptor before any Build runs.
Providers func(c *di.Container) error
// Build constructs the domain. By the time it runs, every provider in
// the application is registered.
Build func(s *di.Scope) App
}
Descriptor is how a domain is handed to the application: as two functions rather than as a constructed value.
The split is not ceremony, it is a correctness requirement. Building a module constructs its controller, which resolves its service out of the container — so every provider in the WHOLE application must be registered before ANY module is built. One value that both registers and constructs cannot express that ordering: whichever domain is listed first would be built before the second had registered anything, and would fail to resolve a dependency that exists.
func App() module.Descriptor {
return module.Descriptor{
Providers: Providers,
Build: func(s *di.Scope) module.App {
return &App{BaseApp: module.NewApp("catalog", products.New(s))}
},
}
}
type EntityOwner ¶
type EntityOwner interface {
Entities() []any
}
EntityOwner declares the tables this module owns.
It is metadata, not migration: schema comes from versioned SQL, because AutoMigrate against a production database is a way to lose a column. What the list buys you is an answerable question — "who owns this table?" — and a lint rule that catches two modules claiming the same one.
type HealthContributor ¶
HealthContributor declares the dependencies this module needs in order to work. They are aggregated into the service-wide readiness report.
type Intercepted ¶
type Intercepted interface {
Interceptors() []interceptor.Interceptor
}
Intercepted declares interceptors wrapping every route in the module.
type Module ¶
type Module interface {
// Name is the path segment and the registry key. Lower-case, singular or
// plural but consistent across the codebase: "products", not "ProductsModule".
Name() string
// Controllers declare the routes. A module with no HTTP surface — a pure
// background worker — returns nil and implements lifecycle.Ready instead.
Controllers() []router.Controller
}
Module is one resource: its routes, and whatever of the optional interfaces below it needs.
type MountOptions ¶
type MountOptions struct {
// Prefix is the path r already sits at, for the recorded route table.
Prefix string
// Routes records every mounted route. Strongly recommended: it is what
// `icpt routes` and the unguarded-route test read.
Routes *router.Registry
// Health collects each module's readiness checks.
Health *health.Registry
// Lifecycle collects each module's init/ready/shutdown hooks.
Lifecycle *lifecycle.Registry
// Guards apply to every route of every module mounted in this call.
Guards []guard.Guard
// Interceptors wrap every route of every module mounted in this call.
Interceptors []interceptor.Interceptor
}
MountOptions configures mounting.
type Option ¶
type Option func(*Base)
Option configures a Base.
func WithControllers ¶
func WithControllers(c ...router.Controller) Option
WithControllers attaches controllers.
func WithGuards ¶
WithGuards applies guards to every route in the module.
func WithHealthChecks ¶
WithHealthChecks contributes readiness checks.
func WithInterceptors ¶
func WithInterceptors(i ...interceptor.Interceptor) Option
WithInterceptors wraps every route in the module.