host

package module
v0.34.0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package host is MicroJet's application orchestrator: a fluent builder with a dependency-injection container, composable modules, background workers, and a managed lifecycle with graceful shutdown.

Index

Constants

View Source
const (
	EnvTest        = "test"
	EnvDevelopment = "development"
	EnvProduction  = "production"
)
View Source
const (
	// CloseEdge closes first: inbound traffic sources that must stop accepting and
	// drain, such as HTTP servers and message subscribers.
	CloseEdge = 0
	// CloseDefault is the order for services that do not implement CloseOrderer —
	// ordinary application services.
	CloseDefault = 50
	// CloseBackend closes last: shared resources others depend on during their own
	// shutdown, such as databases, caches, the message broker, and tracing.
	CloseBackend = 100
)

Close ordering bands. Services are closed in ascending order, so inbound "edges" stop accepting work and drain before the "backends" they depend on are torn down — e.g. an HTTP server finishes serving in-flight requests while its database is still open. Services that do not implement CloseOrderer close at CloseDefault. The values are plain ints, so a service needing finer control can return any value between or beyond these (lower = earlier).

Within a single band, services close in reverse registration order — the last one registered closes first — mirroring construction so a service tears down before the ones it was built on top of.

Variables

View Source
var ErrServiceNotRegistered = errorx.NewInternalError("General", "Service is not registered")

Functions

func MustResolveService

func MustResolveService[T any](a *App, name ...string) T

MustResolveService returns the service for type T (and optional name), panicking if none is registered.

func ProvideService

func ProvideService[T any](a *App, service T, name ...string)

ProvideService registers service in the container under its type T and an optional name. Re-providing the same (type, name) replaces the earlier value; distinct names register side by side.

func ResolveAllServices added in v0.28.0

func ResolveAllServices[T any](a *App) map[string]T

ResolveAllServices returns every service registered under type T, keyed by the name it was provided with ("" for the default, unnamed instance). Use it to enumerate all implementors of an interface — registered side by side under one interface type via distinct names — and pick one by a runtime criterion:

host.ProvideService[Notifier](a, email, "email")
host.ProvideService[Notifier](a, sms, "sms")
for name, n := range host.ResolveAllServices[Notifier](a) { ... }

Only services registered under T itself are returned; an instance registered under a concrete type is not discoverable through its interface. The result is a fresh map (never nil) and safe to mutate.

func ResolveService

func ResolveService[T any](a *App, name ...string) (T, bool)

ResolveService returns the service registered for type T and the optional name, reporting whether one was found.

func ResolveServiceBy added in v0.28.0

func ResolveServiceBy[T any](a *App, pred func(T) bool) (T, bool)

ResolveServiceBy returns the first service registered under type T that satisfies pred, reporting whether one matched. Iteration order is unspecified, so pred should identify at most one service (or the caller must not depend on which match wins). It is a convenience over ResolveAllServices for criteria- based selection:

h, ok := host.ResolveServiceBy[Handler](a, func(h Handler) bool {
    return h.CanHandle(contentType)
})

func WaitForExitSignal

func WaitForExitSignal()

WaitForExitSignal blocks until the process receives SIGINT or SIGTERM.

Types

type App

type App struct {
	Config *Config
	Logger *slog.Logger
	Clock  core.TimeProvider
	// contains filtered or unexported fields
}

App is the central runtime object for a service. Build it with the fluent New().With*() chain at service startup.

func MustNew

func MustNew(opts ...Option) *App

MustNew is like New but panics on error. Convenient for main().

func New

func New(opts ...Option) (*App, error)

New constructs an App, loading the standard configuration sections and the logger. Returns an error instead of panicking so callers can handle config failures gracefully. To load service-specific config sections call app.Configure after construction.

func (*App) Close

func (a *App) Close()

Close gracefully shuts down all managed resources. Safe to call more than once.

func (*App) Configure added in v0.11.0

func (a *App) Configure(cfgs ...configx.Configurable) *App

Configure calls ReadConfig on each Configurable using the app's shared config reader, so the config file is only parsed once. Call this right after New() to populate service-specific config structs before starting services.

func (*App) Err

func (a *App) Err() error

Err returns the first error recorded while building the App via the fluent With*/Setup methods, or nil if the chain succeeded.

func (*App) InitServices

func (a *App) InitServices() *App

func (*App) MustRun

func (a *App) MustRun()

MustRun is like Run but logs and exits the process on error.

func (*App) ProvideKey

func (a *App) ProvideKey(key string, value any) *App

ProvideKey stores an arbitrary value under a string key — an escape hatch for values not identified by Go type. Prefer ProvideService for services.

func (*App) RangeServices added in v0.19.0

func (a *App) RangeServices(fn func(service any) bool)

RangeServices calls fn for each registered service until fn returns false. Iteration order is unspecified — callers must not depend on it, even though it happens to follow registration order today. It lets satellite packages and modules inspect the container — e.g. aggregate health checks across services — without needing access to its internals.

func (*App) ResolveKey

func (a *App) ResolveKey(key string) (any, bool)

ResolveKey returns the value stored under a string key by ProvideKey.

func (*App) Run

func (a *App) Run() error

Run initializes services, starts workers and the HTTP server (if configured), then blocks until a termination signal or fatal server error. On exit it cancels workers, waits for them, and gracefully shuts down.

func (*App) Setup

func (a *App) Setup(handler ...HandlerFunc) *App

Setup queues a setup handler (e.g. migrations or route registration). Handlers run after services are initialized — so connected resources (databases, caches, brokers) are available — but before the HTTP server starts serving. Within the chain they run in registration order. If services are already initialized (the manual InitServices path) the handler runs immediately. Errors are deferred and surfaced by Run/MustRun/Err.

func (*App) Shutdown added in v0.31.0

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

Shutdown gracefully stops the app: it unblocks Wait, flips readiness to not-ready, waits the configured drain delay (App.ShutdownDelay), cancels the workers, waits for them, and closes services. The ctx deadline bounds the drain and the wait for workers — cutting a slow drain or a stuck worker short — in addition to the WithCloseTimeout that bounds Close. It returns ctx.Err() if the ctx expired before the workers stopped, otherwise nil. Safe to call more than once; later calls return the first outcome without re-running.

func (*App) Start added in v0.31.0

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

Start brings the app fully up — init, setup, start, workers — without blocking. The supplied ctx becomes the root of the app's worker context; cancelling it begins graceful shutdown, which Wait observes. Start runs the same phases as Run and wraps their errors identically ("initializing services: ...", etc.), closing the app if a phase fails. It is guarded, so calling it more than once is a no-op that returns the original result.

Use Start with Wait and Shutdown to embed an App in a process that already owns cancellation — a monolith, CLI, test, or supervisor — where Run's built-in signal handling and blocking are not wanted.

func (*App) Wait added in v0.31.0

func (a *App) Wait() error

Wait blocks until the app begins stopping: the Start context is cancelled, a service's background loop exits (an ErrSource delivery), or Shutdown is called. It returns the fatal service error, if any — nil for a context-cancellation or Shutdown-initiated stop. Repeated calls return the same value. Wait does not itself stop the app; pair it with Shutdown.

func (*App) WithModule added in v0.10.0

func (a *App) WithModule(m Module) *App

WithModule installs a module, running its Register hook. Struct modules are deduplicated so a shared module imported by several parents (the "diamond" case) installs exactly once; the dedup key is the module's ModuleKey if it implements KeyedModule, otherwise its Go type. ModuleFunc values are anonymous and never deduplicated. Errors are deferred to Run/MustRun/Err.

func (*App) WithModules added in v0.10.0

func (a *App) WithModules(mods ...Module) *App

WithModules installs several modules in order, short-circuiting on the first error like the rest of the fluent chain.

func (*App) WithPeriodicWorker

func (a *App) WithPeriodicWorker(name string, interval time.Duration, fn func(ctx context.Context, app *App) error) *App

WithPeriodicWorker registers a worker that calls fn immediately on start, then waits interval before calling again. The next tick never starts until the previous call has returned.

func (*App) WithProvider

func (a *App) WithProvider(fn HandlerFunc) *App

WithProvider runs fn to register services imperatively within the fluent chain, deferring any error to Run/MustRun/Err.

func (*App) WithWorker

func (a *App) WithWorker(name string, fn func(ctx context.Context, app *App) error) *App

WithWorker registers a long-running background goroutine. fn receives a context that is cancelled when the app shuts down; fn should return when ctx.Done() is closed.

type AppConfig added in v0.4.0

type AppConfig struct {
	Namespace   string `mapstructure:"namespace"`
	Environment string `mapstructure:"environment"`
	Name        string `mapstructure:"name"`
	Version     string `mapstructure:"version"`
	Debug       bool   `mapstructure:"debug"`
	// ShutdownDelay is how long the host waits after flipping readiness to
	// not-ready before it cancels workers and closes services. Defaults to 0
	// (flip and continue immediately). On Kubernetes set it to roughly the pod's
	// terminationGracePeriodSeconds headroom (e.g. "5s") so kube-proxy removes the
	// pod from its endpoints before in-flight requests are drained.
	ShutdownDelay time.Duration `mapstructure:"shutdownDelay"`
}

func (*AppConfig) GetEnvironment added in v0.4.0

func (a *AppConfig) GetEnvironment() string

func (*AppConfig) IsDevelopment added in v0.4.0

func (a *AppConfig) IsDevelopment() bool

func (*AppConfig) IsProduction added in v0.4.0

func (a *AppConfig) IsProduction() bool

func (*AppConfig) IsTest added in v0.4.0

func (a *AppConfig) IsTest() bool

type AsyncWorker

type AsyncWorker interface {
	Run(ctx context.Context, app *App) error
}

AsyncWorker is implemented by DI-registered services that should run as a background goroutine. Run is called in a goroutine and should block until ctx is cancelled.

type CloseOrderer added in v0.19.0

type CloseOrderer interface {
	CloseOrder() int
}

CloseOrderer lets a service control when Close is called relative to other services. Lower values close earlier; use the CloseEdge/Default/Backend constants. Services that do not implement it close at CloseDefault.

type Config added in v0.4.0

type Config struct {
	App *AppConfig      `mapstructure:"app"`
	Log *logx.LogConfig `mapstructure:"log"`
}

Config is the full application configuration loaded at startup.

func ReadConfig added in v0.11.0

func ReadConfig(envPrefix string) (*Config, error)

ReadConfig reads the standard host configuration sections as a standalone call.

func (*Config) ReadConfig added in v0.11.0

func (c *Config) ReadConfig(l configx.Reader) error

ReadConfig implements configx.Configurable, loading all standard host sections. It sets app and server defaults before unmarshaling so they apply when no config file is present.

type ErrSource added in v0.19.0

type ErrSource interface {
	ErrCh() <-chan error
}

ErrSource is an optional interface for services that run a background loop and report its fatal outcome on a channel (e.g. the HTTP server's listener). Run merges every started service's channel and treats a delivery like a shutdown trigger, so a crashing long-running service brings the app down cleanly.

type HandlerFunc

type HandlerFunc func(app *App) error

HandlerFunc is a setup/lifecycle callback that receives the App and may fail.

type KeyedModule added in v0.10.0

type KeyedModule interface {
	ModuleKey() string
}

KeyedModule is an optional interface for modules that may be installed more than once with different configuration. The returned key identifies the instance for deduplication; two instances with distinct keys both install. Modules that do not implement it are deduplicated by their Go type.

type Module added in v0.10.0

type Module interface {
	Register(app *App) error
}

Module is a composable unit of functionality. Register installs the module's services, routes, workers, config, and any child modules into the App. Modules compose: a module's Register may install further modules via app.WithModule, forming a tree. Composition happens at build time — by the time services are initialized, everything a module declared is in the container.

Convention: Register only *provides* (ProvideService) and *imports* child modules; it must not resolve dependencies, because sibling and child modules may register afterwards. Cross-service wiring belongs in each service's Init(app)/Start(app), which runs in a later phase once every module has registered.

func KeyedModuleFunc added in v0.19.0

func KeyedModuleFunc(key string, fn func(*App) error) Module

KeyedModuleFunc adapts fn into a Module that deduplicates by key: installing two modules with the same key runs Register only once (first wins), exactly like the struct-module diamond dedup. Satellite Module constructors use it so that installing, say, httpx.Module() twice yields one server instead of silently registering a second that clobbers the first. The key also names the module in logs, so make it readable and unique per slot (e.g. include the instance name).

type ModuleFunc added in v0.10.0

type ModuleFunc func(app *App) error

ModuleFunc adapts a plain function into a Module for small, inline modules that do not need their own type. ModuleFunc values are anonymous and never deduplicated — install them at most once, or use KeyedModuleFunc when the module fills a single slot and double-installation should be a no-op.

func (ModuleFunc) Register added in v0.10.0

func (f ModuleFunc) Register(app *App) error

Register implements Module.

type NamedModule added in v0.10.0

type NamedModule interface {
	ModuleName() string
}

NamedModule is an optional interface; when a module implements it, the returned name is used in logs and error messages instead of the Go type name.

type Option

type Option func(*App)

Option configures an App at construction time.

func WithClock

func WithClock(clock core.TimeProvider) Option

WithClock injects the time source used by the App and its components. Pass core.UTC in production (the default) or a *core.FixedClock in tests to make time-dependent behavior deterministic.

func WithCloseTimeout added in v0.19.0

func WithCloseTimeout(d time.Duration) Option

WithCloseTimeout bounds how long Close waits for managed resources (HTTP server, databases, messaging, services) to stop. Defaults to 15s.

func WithConfigReader added in v0.31.0

func WithConfigReader(r configx.Reader) Option

WithConfigReader injects the configuration source, bypassing the default TOML file discovery. Use it to embed the App in a host process that already owns configuration, or to supply fixed config in tests (see configx.NewMapReader).

Precedence note: whatever the injected Reader returns is authoritative. The default env-var override shim applies only to the built-in Viper file reader, so an injected reader is not subject to it unless it implements that itself.

func WithConfigValue added in v0.31.0

func WithConfigValue(key string, value any) Option

WithConfigValue sets a single configuration value in code, keyed by its dotted path (e.g. "app.debug", "app.shutdownDelay", "http.port"). Programmatic values are authoritative: they win over config files, environment variables, and defaults. Apply several at once with WithConfigValues.

It works with the default file reader and with any injected reader that implements configx.Setter (both the file reader and configx.NewMapReader do); New returns an error if the active reader does not support it.

func WithConfigValues added in v0.31.0

func WithConfigValues(values map[string]any) Option

WithConfigValues sets several configuration values in code at once, each keyed by its dotted path. Equivalent to calling WithConfigValue per entry; when keys collide the last write wins. See WithConfigValue for precedence and reader requirements.

func WithEnvPrefix

func WithEnvPrefix(prefix string) Option

WithEnvPrefix overrides the environment-variable prefix used for config overrides (defaults to "APP", e.g. APP_HTTP_PORT).

type PeriodicWorker

type PeriodicWorker interface {
	Interval() time.Duration
	Run(ctx context.Context, app *App) error
}

PeriodicWorker is implemented by DI-registered services that should run on a fixed interval. Run is called immediately on start, then again after each Interval. The next call never starts until the previous one has returned.

type ServiceCloser

type ServiceCloser interface {
	Close(app *App) error
}

type ServiceIniter

type ServiceIniter interface {
	Init(app *App) error
}

type ServiceSetupper added in v0.14.0

type ServiceSetupper interface {
	Setup(app *App) error
}

type ServiceStarter added in v0.4.0

type ServiceStarter interface {
	Start(app *App) error
}

Jump to

Keyboard shortcuts

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