app

package
v0.66.0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Overview

Package app provides the core application structure and lifecycle management.

Index

Constants

This section is empty.

Variables

View Source
var DefaultModuleRegistry = &MetadataRegistry{
	modules: make(map[string]ModuleInfo),
}

DefaultModuleRegistry is the global module metadata registry

View Source
var ErrNoTenantInContext = multitenant.ErrNoTenant

ErrNoTenantInContext is multitenant.ErrNoTenant under the name app's accessors have always returned. One value, so errors.Is matches through either name whichever layer produced the error.

View Source
var ErrStreamsNotLinked = streamruntime.ErrNotLinked

ErrStreamsNotLinked is returned at startup when messaging.streams.uri is set but messaging/streams was never imported into the build. The lane is opt-in at the build graph (ADR-091); a leftover URI must not boot as a silent no-op.

Functions

func RegisterStreamRuntime added in v0.61.0

func RegisterStreamRuntime(r StreamRuntime)

RegisterStreamRuntime installs the streams lane implementation. A blank import of messaging/streams does this from init; an explicit call is the same seam. A second registration panics.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App represents the main application instance. It manages the lifecycle and coordination of all application components.

func New

func New() (*App, logger.Logger, error)

New creates a new application instance with dependencies determined by configuration. It initializes only the services that are configured, failing fast if configured services cannot connect. Returns the app instance, a logger (always available even on failure), and any error.

func NewWithConfig added in v0.4.0

func NewWithConfig(cfg *config.Config, opts *Options) (*App, logger.Logger, error)

NewWithConfig creates a new application instance with the provided config and optional overrides. This factory method allows for dependency injection while maintaining fail-fast behavior. Returns the app instance, a logger (always available even on failure), and any error.

func NewWithOptions added in v0.5.0

func NewWithOptions(opts *Options) (*App, logger.Logger, error)

NewWithOptions creates a new application instance allowing overrides for config loading and dependencies. Returns the app instance, a logger (always available even on failure), and any error.

func (*App) CacheManager added in v0.65.0

func (a *App) CacheManager() *cache.CacheManager

CacheManager returns the framework-built cache manager; nil only on an App the framework did not build.

func (*App) DBManager added in v0.65.0

func (a *App) DBManager() *database.DbManager

DBManager returns the framework-built database manager; nil only on an App the framework did not build.

func (*App) MessagingDeclarations added in v0.19.0

func (a *App) MessagingDeclarations() *messaging.Declarations

MessagingDeclarations returns the captured messaging declarations. This is used by tenant managers to replay infrastructure for each tenant.

func (*App) RegisterModule

func (a *App) RegisterModule(module Module) error

RegisterModule registers a new module with the application. It adds the module to the registry for initialization and route registration.

func (*App) Run

func (a *App) Run() error

Run starts the application and blocks until a shutdown signal is received. It handles graceful shutdown with a timeout.

func (*App) Shutdown

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

Shutdown gracefully shuts down the application with the given context. It closes database connections, messaging client, observability, and stops the HTTP server. Returns an aggregated error if any components fail to shut down.

type Builder added in v0.9.0

type Builder struct {
	// contains filtered or unexported fields
}

Builder orchestrates the step-by-step construction of an App instance using a fluent interface pattern. Each step is responsible for a single aspect of initialization, making the process clear and testable.

func NewAppBuilder added in v0.9.0

func NewAppBuilder() *Builder

NewAppBuilder creates a new app builder instance.

func (*Builder) Build added in v0.9.0

func (b *Builder) Build() (*App, logger.Logger, error)

Build returns the completed App instance, logger, or any error encountered during building. The logger is always returned, even on error, to enable proper error logging.

func (*Builder) ConfigureRuntimeHelpers added in v0.9.0

func (b *Builder) ConfigureRuntimeHelpers() *Builder

ConfigureRuntimeHelpers prepares helper components used during runtime.

func (*Builder) CreateApp added in v0.9.0

func (b *Builder) CreateApp() *Builder

CreateApp creates the core App instance with basic configuration.

func (*Builder) CreateBootstrap added in v0.9.0

func (b *Builder) CreateBootstrap() *Builder

CreateBootstrap creates the bootstrap helper for dependency resolution.

func (*Builder) CreateHealthProbes added in v0.9.0

func (b *Builder) CreateHealthProbes() *Builder

CreateHealthProbes installs the readiness judge over the slot list (ADR-067 decision 5 keeps the step's name). It collects nothing: each slot seals its own description inside the startSlots walk (ADR-066 as amended).

func (*Builder) CreateLogger added in v0.9.0

func (b *Builder) CreateLogger() *Builder

CreateLogger creates and configures the application logger.

func (*Builder) Error added in v0.19.0

func (b *Builder) Error() error

Error returns any error encountered during the building process.

func (*Builder) InitializeRegistry added in v0.9.0

func (b *Builder) InitializeRegistry() *Builder

InitializeRegistry creates and configures the module registry.

func (*Builder) RegisterClosers added in v0.9.0

func (b *Builder) RegisterClosers() *Builder

RegisterClosers registers all components that need cleanup on shutdown.

func (*Builder) RegisterReadyHandler added in v0.9.0

func (b *Builder) RegisterReadyHandler() *Builder

RegisterReadyHandler registers the health check handler with the server.

func (*Builder) ResolveDependencies added in v0.9.0

func (b *Builder) ResolveDependencies() *Builder

ResolveDependencies creates and configures all application dependencies.

func (*Builder) WithConfig added in v0.9.0

func (b *Builder) WithConfig(cfg *config.Config, opts *Options) *Builder

WithConfig sets the configuration and options for the app.

type DatabaseRequirer added in v0.56.0

type DatabaseRequirer interface {
	RequiresDatabase() bool
}

DatabaseRequirer is an optional interface that modules can implement to declare that they cannot function without a database. Registration fails — and startup therefore aborts — when a module requires a database and none is configured.

This exists because an empty database: block carries no intent: it is byte-identical whether the service is deliberately database-free or its configuration failed to reach the process (a dropped secret mount). No amount of config inspection separates those, so the module supplies the missing fact. Deployments that resolve database config at runtime are exempt — see rootDatabaseAbsent for that set.

Implementing the interface is not itself the declaration: RequiresDatabase may return false, so a module can gate the requirement on its own construction-time config (e.g. a module that only touches the database when a feature is enabled).

type DebugHandlers added in v0.11.0

type DebugHandlers struct {
	// contains filtered or unexported fields
}

DebugHandlers manages debug endpoints

func NewDebugHandlers added in v0.11.0

func NewDebugHandlers(app *App, cfg *config.DebugConfig, log logger.Logger) *DebugHandlers

NewDebugHandlers creates a new debug handlers instance

func (*DebugHandlers) RegisterDebugEndpoints added in v0.11.0

func (d *DebugHandlers) RegisterDebugEndpoints(r server.RouteRegistrar) error

RegisterDebugEndpoints registers all debug endpoints if enabled. It returns an error — fatal at startup — when enabling them would expose the group with no access control.

type Describer added in v0.5.0

type Describer interface {
	DescribeRoutes() []server.RouteDescriptor
	DescribeModule() ModuleDescriptor
}

Describer is an optional interface that modules can implement to provide additional metadata for documentation generation and introspection.

func IsDescriber added in v0.5.0

func IsDescriber(m Module) (Describer, bool)

IsDescriber checks if a module implements the Describer interface

type FactoryResolver added in v0.9.0

type FactoryResolver struct {
	// contains filtered or unexported fields
}

FactoryResolver encapsulates the logic for resolving factory functions from Options, providing default implementations when not specified.

func NewFactoryResolver added in v0.9.0

func NewFactoryResolver(opts *Options) *FactoryResolver

NewFactoryResolver creates a new factory resolver with the given options.

func (*FactoryResolver) CacheConnector added in v0.18.0

func (f *FactoryResolver) CacheConnector(resourceSource TenantStore, log logger.Logger) cache.Connector

CacheConnector returns the appropriate cache connector function. If no custom connector is provided in options, returns a Redis connector that reads configuration from the resourceSource for the given tenant/key.

Whichever connector is in play, the instance it produces comes back behind the key-namespace decorator (ADR-117): the cache a custom Options.CacheConnector dials is namespaced exactly like the framework's own. That is the one thing such a connector does inherit — cache.redis.username and cache.redis.mode never reach it, because it owns its own dial, and no field of the resolved cache config applies to it. This method is CreateCacheManager's only caller, so the decorator is installed exactly once per pooled instance.

func (*FactoryResolver) DatabaseConnector added in v0.9.0

func (f *FactoryResolver) DatabaseConnector() database.Connector

DatabaseConnector returns the appropriate database connector function. If no custom connector is provided in options, returns the default implementation.

func (*FactoryResolver) HasCustomFactories added in v0.9.0

func (f *FactoryResolver) HasCustomFactories() bool

HasCustomFactories returns true if any custom factories are provided in options. This can be useful for logging or debugging purposes.

func (*FactoryResolver) MessagingClientFactory deprecated added in v0.9.0

func (f *FactoryResolver) MessagingClientFactory(connectionTimeout time.Duration, maxPublishAttempts int) messaging.ClientFactory

MessagingClientFactory returns the appropriate messaging client factory function. The default factory creates AMQPClient instances configured with the supplied per-publish connection timeout and bounded publish-retry attempts. If a custom Options.MessagingClientFactory is set it owns construction and receives only (url, log) — neither connectionTimeout nor maxPublishAttempts applies to it.

Deprecated: kept for backward compatibility (its signature cannot change without breaking apidiff). Use MessagingClientFactoryWithOptions, which also carries ReadyTimeout and the four reconnect delays (messaging.reconnect.*) — clients built through this method keep the hardcoded client defaults for those.

func (*FactoryResolver) MessagingClientFactoryWithOptions added in v0.49.0

func (f *FactoryResolver) MessagingClientFactoryWithOptions(opts MessagingClientFactoryOptions) messaging.ClientFactory

MessagingClientFactoryWithOptions is the options-struct successor to MessagingClientFactory. Internal bootstrap wiring (CreateMessagingManager) uses this method so every messaging.reconnect.* client knob reaches the client.

Same custom-factory precedence as MessagingClientFactory: if Options.MessagingClientFactory is set it owns construction and receives only (url, log) — NO field of opts applies to it, so none of the messaging.reconnect.* config (timeouts, attempts, and the four reconnect delays) reaches it. Such a factory owns construction outright: whatever timeouts, retry bound, reconnect delays and app id its client ends up with are the factory's own, not the framework's. In particular it never reaches WithAppName, so its clients publish no app_id unless the factory sets one itself.

func (*FactoryResolver) ResourceSource added in v0.9.0

func (f *FactoryResolver) ResourceSource(cfg *config.Config) TenantStore

ResourceSource returns the appropriate tenant resource source. If no custom resource source is provided in options, creates one from config.

type GlobalMiddlewareRegisterer added in v0.48.0

type GlobalMiddlewareRegisterer interface {
	GlobalMiddleware() []server.MiddlewareFunc
}

GlobalMiddlewareRegisterer is an optional interface that modules can implement to contribute middleware to the root request chain. It runs once per request after tenant resolution, before handlers, and cannot be skipped per-route.

type HealthStatus added in v0.9.0

type HealthStatus struct {
	// Name is interpolated into the unauthenticated /ready body by publicProbeError.
	// Keep it a fixed component identifier — never a tenant, host, or database name.
	Name string
	// Status is one of "healthy", "unhealthy", "not_configured", "disabled", "per_tenant". A
	// component is failing iff Status == "unhealthy"; /ready answers 503 (and the debug
	// summary counts an error) on failing && Critical — the gate keys off Status, not Err;
	// framework probes always set Err alongside "unhealthy".
	Status  string
	Details map[string]any
	Err     error
	// PublicErr overrides the error text on the unauthenticated /ready body. Empty
	// synthesizes "<Name> unavailable"; Err never reaches that body either way.
	PublicErr string
	Critical  bool
}

HealthStatus captures the outcome of a readiness probe.

type HeldMessage added in v0.61.0

type HeldMessage = streamruntime.HeldMessage

HeldMessage is one parked stream delivery as the hold ledger sees it. It lives on this seam so inbox can implement the hold port without importing messaging/streams (and therefore without pulling the vendor client).

type HoldLedger added in v0.61.0

type HoldLedger = streamruntime.HoldLedger

HoldLedger is the port stream consumers park through. Inbox implements it when inbox.hold.enabled is set.

type HoldReplayer added in v0.61.0

type HoldReplayer = streamruntime.HoldReplayer

HoldReplayer is what the hold drain drives to put a held message back through the lane.

type IPWhitelist added in v0.11.0

type IPWhitelist struct {
	// contains filtered or unexported fields
}

IPWhitelist manages a list of allowed IP networks for access control

func NewIPWhitelist added in v0.11.0

func NewIPWhitelist(ips []string, log logger.Logger) *IPWhitelist

NewIPWhitelist creates a new IP whitelist from a list of IP strings

func (*IPWhitelist) Contains added in v0.11.0

func (w *IPWhitelist) Contains(ip net.IP) bool

Contains checks if the given IP is allowed by this whitelist

type InboxProcessor added in v0.40.0

type InboxProcessor interface {
	// ProcessOnce runs fn inside a transaction exactly once per key. A
	// redelivery of an already-processed key short-circuits (fn is not run) and
	// returns nil. The tenant is resolved from ctx. Take key from
	// messaging.Metadata.DedupKey, or build one with messaging.WireDedupKey.
	ProcessOnce(ctx context.Context, key messaging.DedupKey, fn func(ctx context.Context, tx dbtypes.Tx) error) error
}

InboxProcessor runs a handler exactly once per event id, recording the id in a durable, tenant-aware ledger atomically with the handler's writes. It is the consumer-side complement to the transactional outbox. Defined here to avoid an app<->inbox import cycle; the inbox package implements it, and the key's type makes app import messaging.

type InboxProvider added in v0.40.0

type InboxProvider interface {
	InboxProcessor() InboxProcessor
}

InboxProvider is an optional interface that modules can implement to provide an InboxProcessor for dependency injection into other modules. When a module implements this interface, the ModuleRegistry automatically wires its InboxProcessor into ModuleDeps.Inbox.

type Info added in v0.11.0

type Info struct {
	Name        string    `json:"name"`
	Environment string    `json:"environment"`
	Version     string    `json:"version"`
	StartTime   time.Time `json:"start_time"`
	Uptime      string    `json:"uptime"`
	PID         int       `json:"pid"`
	Goroutines  int       `json:"goroutines"`
	MemoryUsage uint64    `json:"memory_usage"`
}

Info contains application information

type JobProvider added in v0.14.0

type JobProvider interface {
	RegisterJobs(JobRegistrar) error
}

JobProvider is an optional interface that modules can implement to register scheduled jobs. Modules implementing this interface will have RegisterJobs() called automatically after all module Init() methods have completed, making module registration order irrelevant.

Example:

type JobsModule struct{}

func (m *JobsModule) RegisterJobs(reg JobRegistrar) error {
    reg.FixedRate("cleanup", &CleanupJob{}, 30*time.Minute)
    reg.DailyAt("report", &ReportJob{}, scheduler.ParseTime("03:00"))
    return nil
}

The scheduler parameter is guaranteed to be non-nil when this method is called. If no scheduler module is registered, this method will not be called.

type JobRegistrar added in v0.14.0

type JobRegistrar interface {
	// FixedRate schedules a job to run every interval duration
	FixedRate(jobID string, job any, interval time.Duration) error

	// DailyAt schedules a job to run daily at the given wall-clock time,
	// interpreted in the scheduler's configured timezone (scheduler.timezone, default UTC).
	DailyAt(jobID string, job any, localTime time.Time) error

	// WeeklyAt schedules a job to run weekly on the given day and wall-clock
	// time, interpreted in the scheduler's configured timezone.
	WeeklyAt(jobID string, job any, dayOfWeek time.Weekday, localTime time.Time) error

	// HourlyAt schedules a job to run hourly at the specified minute, within the
	// scheduler's configured timezone (matters only for sub-hour-offset zones).
	HourlyAt(jobID string, job any, minute int) error

	// MonthlyAt schedules a job to run monthly on the given day and wall-clock
	// time, interpreted in the scheduler's configured timezone.
	MonthlyAt(jobID string, job any, dayOfMonth int, localTime time.Time) error
}

JobRegistrar defines the interface for scheduling jobs. This interface is defined here to avoid circular imports between app and scheduler packages. The scheduler package implements this interface via its Module type.

type KeyStore added in v0.27.0

type KeyStore interface {
	// PublicKey returns the parsed RSA public key for the given certificate name.
	// Returns an error if the name is not configured.
	PublicKey(name string) (*rsa.PublicKey, error)

	// PrivateKey returns the parsed RSA private key for the given certificate name.
	// Returns an error if the name is not configured or no private key was provided.
	PrivateKey(name string) (*rsa.PrivateKey, error)

	// Secret returns a defensive copy of the raw symmetric key material for the
	// given name (HMAC/CMAC key, HKDF input). The caller owns the returned slice
	// and may zeroize it after use. Returns an error if the name is not
	// configured or the entry holds an RSA pair rather than a secret.
	Secret(name string) ([]byte, error)
}

KeyStore provides access to named RSA key pairs loaded at startup. Keys are loaded from DER files or base64-encoded values during module initialization. All methods are safe for concurrent use (the store is read-only after init). An implementation must not return key material alongside an error: on the error path the returned key or secret is nil. This interface is defined here to avoid circular imports between app and keystore packages.

type KeyStoreProvider added in v0.27.0

type KeyStoreProvider interface {
	KeyStore() KeyStore
}

KeyStoreProvider is an optional interface that modules can implement to provide a KeyStore for dependency injection into other modules. When a module implements this interface, the ModuleRegistry automatically wires its KeyStore into ModuleDeps.KeyStore.

type ManagerConfigBuilder added in v0.9.0

type ManagerConfigBuilder struct {
	// contains filtered or unexported fields
}

ManagerConfigBuilder creates configuration options for database and messaging managers based on deployment mode (single-tenant vs multi-tenant).

func NewManagerConfigBuilder added in v0.9.0

func NewManagerConfigBuilder(multiTenantEnabled bool, tenantLimit int) *ManagerConfigBuilder

NewManagerConfigBuilder creates a new manager configuration builder.

func (*ManagerConfigBuilder) BuildCacheOptions added in v0.18.0

func (b *ManagerConfigBuilder) BuildCacheOptions() cache.ManagerConfig

BuildCacheOptions creates cache manager options from validated config.

func (*ManagerConfigBuilder) BuildDatabaseOptions added in v0.9.0

func (b *ManagerConfigBuilder) BuildDatabaseOptions() database.DbManagerOptions

BuildDatabaseOptions creates database manager options from validated config.

func (*ManagerConfigBuilder) BuildMessagingOptions added in v0.9.0

func (b *ManagerConfigBuilder) BuildMessagingOptions() messaging.ManagerOptions

BuildMessagingOptions creates messaging manager options from validated config.

func (*ManagerConfigBuilder) IsMultiTenant added in v0.9.0

func (b *ManagerConfigBuilder) IsMultiTenant() bool

IsMultiTenant returns true if the builder is configured for multi-tenant mode.

func (*ManagerConfigBuilder) StaticTenantCount added in v0.43.0

func (b *ManagerConfigBuilder) StaticTenantCount() int

StaticTenantCount returns the number of statically-configured tenants (multitenant.tenants). It is 0 for single-tenant or dynamic tenant sources.

func (*ManagerConfigBuilder) TenantLimit added in v0.9.0

func (b *ManagerConfigBuilder) TenantLimit() int

TenantLimit returns the configured tenant limit for multi-tenant mode.

type MessagingClientFactoryOptions added in v0.49.0

type MessagingClientFactoryOptions struct {
	ConnectionTimeout  time.Duration
	MaxPublishAttempts int
	ReadyTimeout       time.Duration
	// PublishTimeout is the aggregate per-publish bound (messaging.publishtimeout);
	// zero leaves the publish unbounded.
	PublishTimeout    time.Duration
	ReconnectDelay    time.Duration
	ReconnectMaxDelay time.Duration
	ReinitDelay       time.Duration
	ResendDelay       time.Duration
}

MessagingClientFactoryOptions bundles the per-publish tuning knobs threaded into the default messaging client factory. Introduced alongside the existing MessagingClientFactory (kept byte-identical for apidiff compatibility) so ReadyTimeout could be added without breaking that method's exported signature.

type MessagingDeclarer added in v0.27.0

type MessagingDeclarer interface {
	DeclareMessaging(decls *messaging.Declarations)
}

MessagingDeclarer is an optional interface that modules can implement to declare AMQP exchanges, queues, bindings, publishers, and consumers. Modules that implement this interface will have DeclareMessaging called automatically during application startup.

Detection is a runtime type assertion, so a drifted method name or signature is a silent no-op: the module is skipped, contributes no collection log line, and startup still succeeds. Pin the implementation at compile time with

var _ app.MessagingDeclarer = (*Module)(nil)

which turns that drift into a build failure.

The startup line naming the module and what it declared is emitted after DeclareMessaging returns, so a declarer that panics or blocks leaves no line of its own — the last module named in the log is the one before it.

type MetadataRegistry added in v0.5.0

type MetadataRegistry struct {
	// contains filtered or unexported fields
}

MetadataRegistry tracks discovered modules for introspection

func (*MetadataRegistry) Clear added in v0.5.0

func (r *MetadataRegistry) Clear()

Clear removes all registered modules (useful for testing)

func (*MetadataRegistry) Count added in v0.5.0

func (r *MetadataRegistry) Count() int

Count returns the number of registered modules

func (*MetadataRegistry) Module added in v0.19.0

func (r *MetadataRegistry) Module(name string) (ModuleInfo, bool)

Module returns information for a specific module

func (*MetadataRegistry) Modules added in v0.19.0

func (r *MetadataRegistry) Modules() map[string]ModuleInfo

Modules returns a copy of all registered module information

func (*MetadataRegistry) RegisterModule added in v0.5.0

func (r *MetadataRegistry) RegisterModule(name string, module Module, pkg string)

RegisterModule adds a module to the metadata registry

type Module

type Module interface {
	Name() string
	Init(deps *ModuleDeps) error
	Shutdown() error
}

Module defines the core interface that all application modules must implement. It provides hooks for initialization and cleanup. Route registration and messaging declaration are optional — implement RouteRegisterer and/or MessagingDeclarer only if your module needs them.

type ModuleDeps

type ModuleDeps struct {
	Logger logger.Logger
	Config *config.Config

	// Tracer provides distributed tracing capabilities.
	// Creates spans for tracking operations across services.
	// This is a no-op tracer if observability is disabled.
	Tracer trace.Tracer

	// MeterProvider provides metrics collection capabilities.
	// Use this to create custom meters for application-specific metrics.
	// This is a no-op provider if observability is disabled.
	MeterProvider metric.MeterProvider

	// Scheduler provides job scheduling capabilities.
	// Modules can register jobs using methods like FixedRate, DailyAt, WeeklyAt, etc.
	// This field is nil if no scheduler module is registered.
	// Example: deps.Scheduler.DailyAt("cleanup-job", &CleanupJob{}, time.Date(0, 0, 0, 3, 0, 0, 0, time.Local))
	Scheduler JobRegistrar

	// Outbox provides transactional event publishing.
	// Events are written to the outbox table atomically with business data,
	// then reliably delivered to the message broker by a background relay.
	// This field is nil if no outbox module is registered or outbox.enabled is false.
	// Example: deps.Outbox.Publish(ctx, tx, &app.OutboxEvent{EventType: "order.created", ...})
	Outbox OutboxPublisher

	// Inbox provides durable consumer-side idempotency (exactly-once processing).
	// ProcessOnce records the event id in a ledger atomically with the handler's writes.
	// This field is nil if no inbox module is registered or inbox.enabled is false.
	// Example: key, err := meta.DedupKey(); deps.Inbox.ProcessOnce(ctx, key, func(ctx, tx) error { ... })
	Inbox InboxProcessor

	// KeyStore provides access to named RSA key pairs for encryption/signing.
	// Keys are loaded at startup from DER files or base64-encoded values.
	// This field is nil if no KeyStoreModule is registered or no keys are configured.
	// Example: key, err := deps.KeyStore.PrivateKey("signing")
	KeyStore KeyStore

	// DB returns a database interface for the current context.
	// In single-tenant mode, returns the global database instance.
	// In multi-tenant mode, resolves tenant from context and returns tenant-specific database.
	// Never nil; absence comes back as a config.IsNotConfigured error — see DBConfigured.
	DB func(_ context.Context) (database.Interface, error)

	// DBByName returns a named database interface for explicit database selection.
	// Use this when working with multiple databases in single-tenant mode.
	// The name must match a key in the 'databases:' config section.
	// Example: db, err := deps.DBByName(ctx, "legacy") for databases.legacy config.
	// Named databases are shared across all tenants in multi-tenant mode.
	// Never nil; a named database's presence is per name and is reported by this
	// accessor's error. DBConfigured speaks for DB only — a deployment with no root
	// database: block and a databases: section reads false there while this resolves.
	DBByName func(ctx context.Context, name string) (database.Interface, error)

	// Messaging returns a messaging client for the current context.
	// In single-tenant mode, returns the global messaging client.
	// In multi-tenant mode, resolves tenant from context and returns tenant-specific client.
	// Never nil; absence comes back as a config.IsNotConfigured error — see MessagingConfigured.
	Messaging func(_ context.Context) (messaging.AMQPClient, error)

	// Cache returns a cache instance for the current context.
	// In single-tenant mode, returns the global cache instance.
	// In multi-tenant mode, resolves tenant from context and returns tenant-specific cache.
	// Never nil; `if deps.Cache == nil` is dead code. Detect absence on the error,
	// or read CacheConfigured when you only need to know:
	//
	//	c, err := deps.Cache(ctx)
	//	if config.IsNotConfigured(err) { /* run without a cache */ }
	Cache func(_ context.Context) (cache.Cache, error)

	// DBConfigured, MessagingConfigured and CacheConfigured answer "is this kind
	// configured?" for DB, Messaging and Cache without a resolve (DBByName is per name,
	// see above). The three accessors are never nil — with nothing configured each is a
	// function whose every call returns an error satisfying config.IsNotConfigured
	// (Scheduler, Outbox, Inbox and KeyStore differ: they are nil when absent).
	//
	// The flags speak for the ROOT config, which is the only thing knowable before a
	// request carries a tenant. False is definitive: the framework's own root resolver
	// would fail every call. True means the root is wired, or that the answer is per key
	// at runtime — multi-tenant, a dynamic config source, a caller-supplied
	// ResourceSource, a custom CacheConnector. In every per-key mode the accessor can
	// still return IsNotConfigured for the tenant in hand, so a true flag never replaces
	// the error path; it only spares a throwaway resolve when the answer is already no.
	DBConfigured        bool
	MessagingConfigured bool
	CacheConfigured     bool
}

ModuleDeps contains the dependencies that are injected into each module. It provides access to core services like database, logging, messaging, caching, observability, and job scheduling.

type ModuleDescriptor added in v0.5.0

type ModuleDescriptor struct {
	Name        string   // Module name
	Version     string   // Module version
	Description string   // Module description
	Tags        []string // Module tags for grouping
	BasePath    string   // Base path for all module routes
}

ModuleDescriptor captures module-level metadata

type ModuleInfo added in v0.5.0

type ModuleInfo struct {
	Module     Module           // The actual module instance
	Descriptor ModuleDescriptor // Module metadata
	Package    string           // Go package path
}

ModuleInfo contains both the module instance and its metadata

type ModuleRegistry

type ModuleRegistry struct {
	// contains filtered or unexported fields
}

ModuleRegistry manages the registration and lifecycle of application modules. It handles module initialization, route registration, messaging setup, and shutdown.

func NewModuleRegistry

func NewModuleRegistry(deps *ModuleDeps) *ModuleRegistry

NewModuleRegistry creates a new module registry with the given dependencies. It initializes an empty registry ready to accept module registrations.

func (*ModuleRegistry) CollectGlobalMiddleware added in v0.48.0

func (r *ModuleRegistry) CollectGlobalMiddleware() []server.MiddlewareFunc

CollectGlobalMiddleware gathers middleware from modules that implement GlobalMiddlewareRegisterer, in registration order. Modules without global middleware are silently skipped.

func (*ModuleRegistry) DeclareMessaging added in v0.9.0

func (r *ModuleRegistry) DeclareMessaging(decls *messaging.Declarations) error

DeclareMessaging calls DeclareMessaging on modules that implement MessagingDeclarer. Modules without messaging declarations are silently skipped.

func (*ModuleRegistry) Register

func (r *ModuleRegistry) Register(module Module) error

Register adds a module to the registry and initializes it. It calls the module's Init method with the injected dependencies. Returns an error if a module with the same name is already registered, or if the module implements DatabaseRequirer on a deployment with no database — the one special case that rejects a module rather than wiring it. Special handling: modules implementing JobRegistrar, OutboxProvider, InboxProvider, or KeyStoreProvider are automatically wired into the corresponding ModuleDeps fields (Scheduler, Outbox, Inbox, KeyStore) so subsequent modules can use them.

IMPORTANT: Duplicate module errors are unrecoverable and must be handled with log.Fatal().

func (*ModuleRegistry) RegisterJobs added in v0.14.0

func (r *ModuleRegistry) RegisterJobs() error

RegisterJobs calls RegisterJobs on modules that implement JobProvider interface. This method is called after all modules have been initialized, making module registration order irrelevant for job scheduling. If no scheduler is registered, this method skips silently.

func (*ModuleRegistry) RegisterRoutes

func (r *ModuleRegistry) RegisterRoutes(registrar server.RouteRegistrar)

RegisterRoutes calls RegisterRoutes on modules that implement RouteRegisterer. Modules without routes are silently skipped.

If a KeyStore-providing module has registered (and r.deps.KeyStore is therefore populated), a jose.KeyStoreResolver is wired into the handler registry so any route declaring jose: tags can resolve its kids at registration time. Logger, tracer, and meter from deps are also threaded into the registry so JOSE failures get audit-grade structured logs and OTEL telemetry. Routes without jose tags are unaffected.

func (*ModuleRegistry) Shutdown

func (r *ModuleRegistry) Shutdown() error

Shutdown gracefully shuts down all registered modules. It calls each module's Shutdown method (continuing past failures), logs each error, and returns them joined via errors.Join (nil if all shut down cleanly). Messaging shutdown is handled by the messaging manager.

type MultiTenantResourceProvider added in v0.9.0

type MultiTenantResourceProvider struct {
	// contains filtered or unexported fields
}

MultiTenantResourceProvider provides database, messaging, and cache resources for multi-tenant deployments using tenant ID from context.

func NewMultiTenantResourceProvider added in v0.9.0

func NewMultiTenantResourceProvider(
	dbManager *database.DbManager,
	messagingManager *messaging.Manager,
	cacheManager *cache.CacheManager,
	declarations *messaging.Declarations,
) *MultiTenantResourceProvider

NewMultiTenantResourceProvider creates a resource provider for multi-tenant mode.

func (*MultiTenantResourceProvider) Cache added in v0.19.0

Cache returns the cache instance for the tenant specified in context.

func (*MultiTenantResourceProvider) DB added in v0.19.0

DB returns the database interface for the tenant specified in context.

func (*MultiTenantResourceProvider) DBByName added in v0.22.0

DBByName returns a named database interface for multi-tenant mode. Named databases are shared across all tenants (tenant-agnostic configuration). Use this for explicit database selection when working with multiple databases. The name must match a key in the 'databases:' config section.

func (*MultiTenantResourceProvider) Messaging added in v0.19.0

Messaging returns the messaging client for the tenant specified in context. It ensures tenant-specific consumers are initialized before returning the publisher.

func (*MultiTenantResourceProvider) SetDeclarations added in v0.9.0

func (p *MultiTenantResourceProvider) SetDeclarations(declarations *messaging.Declarations)

SetDeclarations updates the declaration store used for ensuring consumers.

func (*MultiTenantResourceProvider) SetMessagingTenancy added in v0.61.0

func (p *MultiTenantResourceProvider) SetMessagingTenancy(tenancy string)

SetMessagingTenancy tells the provider which key the messaging kind resolves under. It is a setter rather than a constructor parameter so the exported constructor keeps its four arguments.

type OSSignalHandler added in v0.4.0

type OSSignalHandler struct{}

OSSignalHandler implements SignalHandler using the real OS signal package

func (*OSSignalHandler) Notify added in v0.4.0

func (osh *OSSignalHandler) Notify(c chan<- os.Signal, sig ...os.Signal)

func (*OSSignalHandler) WaitForSignal added in v0.4.0

func (osh *OSSignalHandler) WaitForSignal(c <-chan os.Signal)

type Options added in v0.4.0

type Options struct {
	SignalHandler          SignalHandler
	TimeoutProvider        TimeoutProvider
	Server                 ServerRunner
	ConfigLoader           func() (*config.Config, error)
	DatabaseConnector      func(*config.DatabaseConfig, logger.Logger) (database.Interface, error)
	MessagingClientFactory func(string, logger.Logger) messaging.AMQPClient
	CacheConnector         cache.Connector
	ResourceSource         TenantStore

	// LoggerFilterConfig fully replaces the sensitive-data FilterConfig used by
	// the framework logger. When nil, the framework falls back to
	// config.LogConfig.SensitiveFields (additive to logger.DefaultFilterConfig)
	// or, if that is also empty, to logger.DefaultFilterConfig itself.
	//
	// Use this when you need code-level control beyond a field-name list, e.g.
	// a custom MaskValue, opting out of every default field, or composing
	// values from a secret manager at startup. To opt out entirely, set
	// &logger.FilterConfig{SensitiveFields: nil}. Doing so now emits a WARN at
	// logger construction (suppressed when log.level is above warn).
	//
	// To extend the defaults from code, call logger.DefaultFilterConfig()
	// and append your custom names to SensitiveFields.
	LoggerFilterConfig *logger.FilterConfig

	// PostRegisterRoutes, when set, is called once per Run with every route this App
	// registered — module routes, debug endpoints, and the health/ready probes (one
	// descriptor per method) — after the duplicate-route check and before the listener
	// opens. A non-nil error aborts startup. ModuleName on each descriptor is the
	// registering module's Name(), unless the route set its own with server.WithModule,
	// and is empty for routes the framework registers itself. With an injected Server the
	// slice carries no probe descriptors. Nil means no hook.
	PostRegisterRoutes func(routes []server.RouteDescriptor) error
}

Options contains optional dependencies for creating an App instance

type OutboxEvent added in v0.27.0

type OutboxEvent struct {
	// EventType identifies the kind of event (e.g., "order.created").
	EventType string

	// AggregateID identifies the entity this event relates to (e.g., "order-123").
	AggregateID string

	// Payload is the event data. If []byte, stored as-is. Otherwise, JSON-marshaled.
	Payload any

	// Headers are optional AMQP headers propagated to the published message.
	Headers map[string]any

	// Exchange is the target AMQP exchange. If empty, uses the default from outbox config.
	Exchange string

	// RoutingKey overrides the default routing key. If empty, uses EventType.
	RoutingKey string

	// Stream targets a super stream on the native streams lane instead of an exchange;
	// the partition key is the tenant stamp from ctx (a tenant is required), and
	// Exchange and RoutingKey must be empty. The name must be listed in
	// outbox.superstreams.
	Stream string
}

OutboxEvent represents a domain event to be reliably published via the outbox pattern. This type is defined here to avoid circular imports. The outbox package provides additional utilities for working with events.

type OutboxProvider added in v0.27.0

type OutboxProvider interface {
	OutboxPublisher() OutboxPublisher
}

OutboxProvider is an optional interface that modules can implement to provide an OutboxPublisher for dependency injection into other modules. When a module implements this interface, the ModuleRegistry automatically wires its OutboxPublisher into ModuleDeps.Outbox.

This follows the same pattern as JobRegistrar for the scheduler module.

type OutboxPublisher added in v0.27.0

type OutboxPublisher interface {
	// Publish writes an event to the outbox table within the given transaction.
	// Returns the generated event ID (UUID) for correlation and idempotency.
	Publish(ctx context.Context, tx dbtypes.Tx, event *OutboxEvent) (string, error)
}

OutboxPublisher defines the interface for writing events to the transactional outbox. This interface is defined here to avoid circular imports between app and outbox packages. The outbox package implements this interface via an internal publisher returned from Module.OutboxPublisher (see OutboxProvider).

Events are written to the outbox table within the caller's database transaction, ensuring atomic consistency with business data. A background relay publishes them to the message broker after the transaction commits.

type Prober added in v0.26.0

type Prober interface {
	Run(ctx context.Context) HealthStatus
}

Prober is the probe description's own contract, implemented by the framework's own descriptions (probeDescription) and by nothing else — there is no registration door for a foreign Prober, and the judge only ever walks the slot list (ADR-066 as amended). SECURITY: the /ready body is unauthenticated, so publicProbeError never renders HealthStatus.Err — a description that wants wording other than the synthesized "<name> unavailable" sets HealthStatus.PublicErr, which must be a fixed string and never derived from config. The same constraint binds Name, which the synthesized default interpolates.

type ResourceManagerFactory added in v0.9.0

type ResourceManagerFactory struct {
	// contains filtered or unexported fields
}

ResourceManagerFactory creates database and messaging managers using resolved factories and configuration options.

func NewResourceManagerFactory added in v0.9.0

func NewResourceManagerFactory(
	factoryResolver *FactoryResolver,
	configBuilder *ManagerConfigBuilder,
	log logger.Logger,
) *ResourceManagerFactory

NewResourceManagerFactory creates a new resource manager factory.

func (*ResourceManagerFactory) CreateCacheManager added in v0.18.0

func (f *ResourceManagerFactory) CreateCacheManager(
	resourceSource TenantStore,
) (*cache.CacheManager, error)

CreateCacheManager creates a cache manager using the resolved factory and appropriate configuration options for the deployment mode.

It fails closed: a nil manager registers no cache readiness probe, so /ready reports the cache "disabled" and answers 200 — a service that asked for a cache, got none, and joined the rotation anyway. Returning the error instead of logging it still matters after WithConfig's config.Validate call (ADR-064): normalizeCache only fills Manager.MaxSize/IdleTTL/CleanupInterval when cache.enabled is true, so a negative value on a disabled cache reaches here unvalidated.

func (*ResourceManagerFactory) CreateDatabaseManager added in v0.9.0

func (f *ResourceManagerFactory) CreateDatabaseManager(
	resourceSource TenantStore,
) *database.DbManager

CreateDatabaseManager creates a database manager using the resolved factory and appropriate configuration options for the deployment mode.

func (*ResourceManagerFactory) CreateMessagingManager added in v0.9.0

func (f *ResourceManagerFactory) CreateMessagingManager(
	resourceSource TenantStore,
) *messaging.Manager

CreateMessagingManager creates a messaging manager using the resolved factory and appropriate configuration options for the deployment mode.

func (*ResourceManagerFactory) LogFactoryInfo added in v0.9.0

func (f *ResourceManagerFactory) LogFactoryInfo()

LogFactoryInfo logs information about which factories are being used. This is useful for debugging and operational visibility.

type ResourceProvider added in v0.9.0

type ResourceProvider interface {
	DB(ctx context.Context) (database.Interface, error)
	DBByName(ctx context.Context, name string) (database.Interface, error)
	Messaging(ctx context.Context) (messaging.AMQPClient, error)
	Cache(ctx context.Context) (cache.Cache, error)
}

ResourceProvider abstracts database, messaging, and cache access with support for both single-tenant and multi-tenant deployment modes.

type RouteRegisterer added in v0.27.0

type RouteRegisterer interface {
	RegisterRoutes(hr *server.HandlerRegistry, r server.RouteRegistrar)
}

RouteRegisterer is an optional interface that modules can implement to register HTTP routes. Modules that implement this interface will have RegisterRoutes called automatically during application startup.

type ServerRunner added in v0.5.0

type ServerRunner interface {
	Start() error
	Shutdown(ctx context.Context) error
	RootGroup() server.RouteRegistrar
	ModuleGroup() server.RouteRegistrar
	RegisterReadyHandler(handler server.Handler)
}

ServerRunner abstracts the HTTP server to allow injecting test-friendly implementations

type SharedTxRunner added in v0.54.0

type SharedTxRunner interface {
	RunInSharedTx(ctx context.Context, fn func(ctx context.Context, tx dbtypes.Tx) error) error
}

SharedTxRunner is implemented by the outbox publisher when the deployment can use shared-ledger tenancy. In outbox.tenancy=shared, obtain the business+ledger transaction from it:

if r, ok := deps.Outbox.(app.SharedTxRunner); ok {
    err = r.RunInSharedTx(ctx, func(ctx context.Context, tx dbtypes.Tx) error { ... })
}

type SignalHandler added in v0.4.0

type SignalHandler interface {
	Notify(c chan<- os.Signal, sig ...os.Signal)
	WaitForSignal(c <-chan os.Signal)
}

SignalHandler interface allows for injectable signal handling for testing

type SingleTenantResourceProvider added in v0.9.0

type SingleTenantResourceProvider struct {
	// contains filtered or unexported fields
}

SingleTenantResourceProvider provides database, messaging, and cache resources for single-tenant deployments using a fixed empty key.

func NewSingleTenantResourceProvider added in v0.9.0

func NewSingleTenantResourceProvider(
	dbManager *database.DbManager,
	messagingManager *messaging.Manager,
	cacheManager *cache.CacheManager,
	declarations *messaging.Declarations,
) *SingleTenantResourceProvider

NewSingleTenantResourceProvider creates a resource provider for single-tenant mode.

func (*SingleTenantResourceProvider) Cache added in v0.19.0

Cache returns the cache instance for single-tenant mode.

func (*SingleTenantResourceProvider) DB added in v0.19.0

DB returns the database interface for single-tenant mode.

func (*SingleTenantResourceProvider) DBByName added in v0.22.0

DBByName returns a named database interface for single-tenant mode. Use this for explicit database selection when working with multiple databases. The name must match a key in the 'databases:' config section.

func (*SingleTenantResourceProvider) Messaging added in v0.19.0

Messaging returns the messaging client for single-tenant mode. It ensures consumers are initialized before returning the publisher.

func (*SingleTenantResourceProvider) SetDeclarations added in v0.9.0

func (p *SingleTenantResourceProvider) SetDeclarations(declarations *messaging.Declarations)

SetDeclarations updates the declaration store used for ensuring consumers.

type StandardTimeoutProvider added in v0.4.0

type StandardTimeoutProvider struct{}

StandardTimeoutProvider implements TimeoutProvider using context.WithTimeout

func (*StandardTimeoutProvider) WithTimeout added in v0.4.0

func (stp *StandardTimeoutProvider) WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

type StreamRuntime added in v0.61.0

type StreamRuntime = streamruntime.Runtime

StreamRuntime is the registered streams implementation. messaging/streams registers one from init. This is a link-time factory, not a second Manager (ADR-045): the concrete manager still lives in messaging/streams.

type TenantStore added in v0.9.0

type TenantStore interface {
	database.DBConfigProvider
	messaging.BrokerURLProvider
	cache.ConfigProvider

	// IsDynamic returns true if this store loads tenant configurations dynamically
	// from external sources (e.g., AWS Secrets Manager, Vault). Returns false for
	// stores that use static YAML configuration. This controls pre-initialization behavior.
	IsDynamic() bool
}

TenantStore combines the interfaces required by the database, messaging, and cache managers.

type TimeoutProvider added in v0.4.0

type TimeoutProvider interface {
	WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
}

TimeoutProvider interface allows for injectable timeout creation for testing

Jump to

Keyboard shortcuts

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