Help us shape the future of Go! Take the 2026 Go Developer Survey

framework

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

Package framework provides the core application lifecycle management for Forge microservices.

The framework package implements Clean Architecture principles with a component-based design that enables building production-ready microservices with built-in observability, health checks, graceful shutdown, and lifecycle management.

Basic Usage

Create a service by implementing the Component interface and using the App builder:

cfg := config.DefaultBaseConfig()
cfg.ServiceName = "my-service"

myComponent := &MyComponent{}

app, err := framework.New(
	framework.WithConfig(&cfg),
	framework.WithVersion("1.0.0"),
	framework.WithComponent(myComponent),
)
if err != nil {
	log.Fatal(err)
}

if err := app.Run(context.Background()); err != nil {
	log.Fatal(err)
}

Architecture

The framework follows Clean Architecture with these key concepts:

  • Components: Your business logic implementing Component interface
  • Bundles: Pre-built integrations (PostgreSQL, Redis, etc.)
  • App: Main application orchestrating lifecycle and dependencies
  • Health: Comprehensive health check system with liveness/readiness
  • Observability: Built-in OpenTelemetry tracing and metrics

Lifecycle Management

The App handles sophisticated startup and shutdown orchestration:

  1. Initialize observability (tracing, metrics)
  2. Initialize bundles in registration order
  3. Execute startup hooks
  4. Start gRPC and HTTP servers
  5. Start components in registration order
  6. Mark service as ready

Shutdown happens in reverse order with proper timeout handling and graceful termination.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewHealthLogger

func NewHealthLogger(logging *LoggingManager) *healthLoggerAdapter

NewHealthLogger wraps a LoggingManager in an adapter satisfying the health package's logger interface, so the health registry's background checks log through the same structured, service-tagged zerolog output as the rest of the application instead of a separate logging path.

Types

type App

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

App represents the main service application with lifecycle management. App orchestrates the entire application lifecycle including initialization, startup, runtime, and graceful shutdown of all registered components and services.

The App follows a sophisticated startup sequence:

  1. Configuration validation
  2. Logging and observability initialization
  3. Bundle initialization (in registration order)
  4. Startup hook execution
  5. gRPC and HTTP server startup
  6. Component startup (in registration order)
  7. Health check registration and readiness marking

Shutdown happens in reverse order with proper timeout handling. The App ensures all resources are cleaned up gracefully on termination.

func New

func New(options ...AppOption) (*App, error)

New creates a new service application with the given options.

func (*App) AddHTTPMiddleware added in v0.3.0

func (a *App) AddHTTPMiddleware(middleware func(http.Handler) http.Handler)

AddHTTPMiddleware registers HTTP middleware from a bundle's Initialize method (or any other code that runs before the HTTP server is built). Middleware is applied outermost-first in registration order — the first middleware added sees the request first and the response last — and wraps the complete handler built by startHTTPServer, including the health, metrics, pprof, and any user HTTPRegistrar routes.

The HTTP server is built partway through Start, in startHTTPServer, which runs after all bundles' Initialize methods, so calling AddHTTPMiddleware from Initialize is safe. Calling it after the server has already been built is a no-op.

func (*App) AddStreamInterceptor added in v0.3.0

func (a *App) AddStreamInterceptor(interceptor grpc.StreamServerInterceptor)

AddStreamInterceptor registers a gRPC stream server interceptor. See AddUnaryInterceptor for the ordering and timing guarantees.

func (*App) AddUnaryInterceptor added in v0.3.0

func (a *App) AddUnaryInterceptor(interceptor grpc.UnaryServerInterceptor)

AddUnaryInterceptor registers a gRPC unary server interceptor from a bundle's Initialize method (or any other code that runs before the gRPC server is built). Interceptors are applied in registration order — combined with any interceptors passed to New via WithUnaryInterceptor, which run first — so a bundle wanting to be outermost (e.g. to time the whole call for metrics) should be registered as one of the first bundles.

The gRPC server is built partway through Start, in startGRPCServer, which runs after all bundles' Initialize methods. Since Initialize always runs before that point, calling AddUnaryInterceptor from Initialize is safe. Calling it after the server has already been built is a no-op: interceptors are baked into grpc.NewServer at construction time and cannot be added afterward.

func (*App) Config

func (a *App) Config() *config.BaseConfig

Config returns the application configuration.

func (*App) HealthRegistry

func (a *App) HealthRegistry() *forgeHealth.Registry

HealthRegistry returns the health registry.

func (*App) IsRunning

func (a *App) IsRunning() bool

IsRunning returns true if the application is running.

func (*App) IsStopping

func (a *App) IsStopping() bool

IsStopping returns true if the application is in the process of stopping.

func (*App) Logger

func (a *App) Logger() *LoggingManager

Logger returns the logging manager.

func (*App) Observability added in v0.3.0

func (a *App) Observability() *ObservabilityManager

Observability returns the observability manager.

func (*App) Run

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

Run starts the application and blocks until shutdown.

func (*App) Start

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

Start starts all application components.

The app mutex is held only long enough to claim the starting state, not across the (I/O-bound) bundle, server, and component startup below: a component that calls IsRunning/IsStopping from its own Start would otherwise deadlock against this method holding the lock for the whole startup sequence. The lock is re-acquired only to record the outcome.

func (*App) Stop

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

Stop stops all application components gracefully using the shutdown orchestrator.

func (*App) Uptime

func (a *App) Uptime() time.Duration

Uptime returns how long the service has been running.

type AppOption

type AppOption func(*App) error

AppOption configures the App during creation.

func WithBundle

func WithBundle(bundle Bundle) AppOption

WithBundle adds a bundle to the application.

func WithComponent

func WithComponent(component Component) AppOption

WithComponent adds a component to be managed by the app.

func WithConfig

func WithConfig(config *config.BaseConfig) AppOption

WithConfig sets the base configuration.

func WithGRPCRegistrar

func WithGRPCRegistrar(registrar Registrar) AppOption

WithGRPCRegistrar adds a gRPC service registrar.

func WithHTTPServerConfig added in v0.3.0

func WithHTTPServerConfig(cfg HTTPServerConfig) AppOption

WithHTTPServerConfig sets a custom HTTP server configuration, overriding the framework's default endpoint, CORS, and request logging settings.

func WithHealthContributor

func WithHealthContributor(contributor HealthContributor) AppOption

WithHealthContributor adds health checks from a contributor.

func WithHealthRegistry added in v0.3.0

func WithHealthRegistry(registry *forgeHealth.Registry) AppOption

WithHealthRegistry injects a custom health registry, overriding the framework's built-in default (a forgeHealth.Registry logging through the app's logging manager).

func WithLogging added in v0.3.0

func WithLogging(logging *LoggingManager) AppOption

WithLogging injects a custom logging manager, overriding the framework's built-in default (a LoggingManager derived from the app config).

func WithObservability added in v0.3.0

func WithObservability(observability *ObservabilityManager) AppOption

WithObservability injects a custom observability manager, overriding the framework's built-in default (an ObservabilityManager derived from the app config and version).

func WithShutdownHook

func WithShutdownHook(hook ShutdownHook) AppOption

WithShutdownHook adds a shutdown hook.

func WithStartupHook

func WithStartupHook(hook StartupHook) AppOption

WithStartupHook adds a startup hook.

func WithStreamInterceptor

func WithStreamInterceptor(interceptor grpc.StreamServerInterceptor) AppOption

WithStreamInterceptor adds a gRPC stream server interceptor.

func WithUnaryInterceptor

func WithUnaryInterceptor(interceptor grpc.UnaryServerInterceptor) AppOption

WithUnaryInterceptor adds a gRPC unary server interceptor.

func WithVersion

func WithVersion(version string) AppOption

WithVersion sets the service version.

type Bundle

type Bundle interface {
	// Name returns a unique identifier for this bundle.
	Name() string

	// Initialize sets up the bundle's functionality within the application.
	// This method is called during application startup before components are started.
	Initialize(app *App) error

	// Stop gracefully shuts down the bundle and cleans up resources.
	// This method is called during application shutdown in reverse initialization order.
	// Bundles should respect the context deadline for graceful termination.
	Stop(ctx context.Context) error
}

Bundle represents a reusable collection of functionality that can be added to an app. Bundles encapsulate common integrations like database connections, message queues, monitoring, etc. They are initialized in registration order during startup and stopped in reverse order during shutdown.

Example bundle for PostgreSQL integration:

type PostgreSQLBundle struct {
	config *DatabaseConfig
	db     *sql.DB
}

func (b *PostgreSQLBundle) Name() string {
	return "postgresql"
}

func (b *PostgreSQLBundle) Initialize(app *App) error {
	db, err := sql.Open("postgres", b.config.URL)
	if err != nil {
		return err
	}
	b.db = db
	return nil
}

func (b *PostgreSQLBundle) Stop(ctx context.Context) error {
	if b.db != nil {
		return b.db.Close()
	}
	return nil
}

type Component

type Component interface {
	// Start initializes the component and starts any background processes.
	// This method is called during application startup after all bundles
	// have been initialized and servers have been started.
	Start(ctx context.Context) error

	// Stop gracefully shuts down the component and cleans up resources.
	// This method is called during application shutdown with a timeout context.
	// Components should respect the context deadline for graceful termination.
	Stop(ctx context.Context) error
}

Component represents a service component that can be started and stopped. Components are the primary way to integrate your business logic with the Forge framework.

Components are started in registration order during application startup and stopped in reverse order during graceful shutdown. Each component should handle its own resource management and cleanup.

Example implementation:

type UserService struct {
	db *sql.DB
}

func (s *UserService) Start(ctx context.Context) error {
	// Initialize connections, start background workers, etc.
	return s.db.PingContext(ctx)
}

func (s *UserService) Stop(ctx context.Context) error {
	// Clean up resources, stop workers, close connections
	return s.db.Close()
}

type HTTPRegistrar

type HTTPRegistrar interface {
	// RegisterHTTPRoutes registers HTTP routes with the provided mux.
	RegisterHTTPRoutes(mux *http.ServeMux)
}

HTTPRegistrar represents a component that can register HTTP routes.

type HTTPServerBuilder

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

HTTPServerBuilder builds an enhanced HTTP server with configurable endpoints.

func NewHTTPServerBuilder

func NewHTTPServerBuilder(app *App, config HTTPServerConfig) *HTTPServerBuilder

NewHTTPServerBuilder creates a new HTTP server builder.

func (*HTTPServerBuilder) Build

func (b *HTTPServerBuilder) Build() (*http.Server, error)

Build constructs the HTTP server with all configured endpoints and middleware.

func (*HTTPServerBuilder) RegisterCustomRoutes

func (b *HTTPServerBuilder) RegisterCustomRoutes(registerFunc func(*http.ServeMux))

RegisterCustomRoutes registers custom HTTP routes with the server.

type HTTPServerConfig

type HTTPServerConfig struct {
	// CORS configuration
	EnableCORS      bool     `yaml:"enable_cors" env:"ENABLE_CORS"`
	CORSOrigins     []string `yaml:"cors_origins" env:"CORS_ORIGINS"`
	CORSMethods     []string `yaml:"cors_methods" env:"CORS_METHODS"`
	CORSHeaders     []string `yaml:"cors_headers" env:"CORS_HEADERS"`
	CORSCredentials bool     `yaml:"cors_credentials" env:"CORS_CREDENTIALS"`

	// Endpoint configuration
	EnableMetrics    bool   `yaml:"enable_metrics" env:"ENABLE_METRICS"`
	MetricsPath      string `yaml:"metrics_path" env:"METRICS_PATH"`
	HealthPathPrefix string `yaml:"health_path_prefix" env:"HEALTH_PATH_PREFIX"`

	// MetricsBasicAuthUser and MetricsBasicAuthPass, when both set, gate the
	// metrics endpoint behind HTTP Basic Auth. Left empty (the default) the
	// endpoint is unauthenticated, matching prior behavior.
	MetricsBasicAuthUser string `yaml:"metrics_basic_auth_user" env:"METRICS_BASIC_AUTH_USER"`
	MetricsBasicAuthPass string `yaml:"metrics_basic_auth_pass" env:"METRICS_BASIC_AUTH_PASS"`

	// Request logging
	EnableRequestLogging bool `yaml:"enable_request_logging" env:"ENABLE_REQUEST_LOGGING"`
}

HTTPServerConfig contains configuration for the HTTP server endpoints.

func DefaultHTTPServerConfig

func DefaultHTTPServerConfig() HTTPServerConfig

DefaultHTTPServerConfig returns HTTP server configuration with sensible defaults.

func (*HTTPServerConfig) Validate

func (c *HTTPServerConfig) Validate() error

Validate performs comprehensive validation on HTTP server configuration.

type HealthContributor

type HealthContributor interface {
	// HealthChecks returns a slice of health checks that this component provides.
	// These checks will be automatically registered during application startup.
	HealthChecks() []forgeHealth.Check
}

HealthContributor represents a service that contributes health checks. Components implementing this interface can provide health checks that will be automatically registered with the health registry.

Example:

func (s *UserService) HealthChecks() []health.Check {
	return []forgeHealth.Check{
		forgeHealth.NewBasicCheck(
			forgeHealth.DefaultCheckConfig("database"),
			func(ctx context.Context) error {
				return s.db.PingContext(ctx)
			},
			func(ctx context.Context) error {
				return s.db.PingContext(ctx)
			},
		),
	}
}

type LoggingManager

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

LoggingManager owns the application's zerolog configuration and hands out logger instances tagged with service and component context. Call Initialize before using Logger, WithService, or WithContext so the underlying logger has its output format (pretty console in development, JSON in production) and log level configured.

func NewLoggingManager

func NewLoggingManager(config *config.BaseConfig) *LoggingManager

NewLoggingManager creates a LoggingManager bound to the given configuration. The returned manager's logger is not yet configured; call Initialize before using it.

func (*LoggingManager) Initialize

func (lm *LoggingManager) Initialize() error

Initialize configures the manager's base logger from its BaseConfig: it sets the global zerolog level (falling back to Info if LogLevel doesn't parse), switches between a human-readable console writer in development and JSON output otherwise, and tags every log line with the service name and environment. The App calls this automatically during New, before observability and bundles are initialized.

func (*LoggingManager) Logger

func (lm *LoggingManager) Logger() *zerolog.Logger

Logger returns a pointer to the manager's base zerolog.Logger, already tagged with service and environment fields by Initialize.

func (*LoggingManager) WithContext

func (lm *LoggingManager) WithContext(fields map[string]any) zerolog.Logger

WithContext returns a copy of the base logger with an arbitrary set of key-value fields attached, one call to Interface per map entry, for callers that need structured fields beyond the fixed service/component pair provided by WithService.

func (*LoggingManager) WithService

func (lm *LoggingManager) WithService(service, component string) zerolog.Logger

WithService returns a copy of the base logger with additional "service" and "component" fields attached, for use by a specific subsystem (e.g. the gRPC or HTTP server) so its log lines can be filtered independently.

type ObservabilityConfig

type ObservabilityConfig struct {
	ServiceName    string
	ServiceVersion string
	Environment    string
	OTELEndpoint   string
	SampleRate     float64
}

ObservabilityConfig contains the settings used to initialize OpenTelemetry tracing and metrics: the service's identity (name, version, environment), the OTLP collector endpoint to export to, and the trace sampling rate.

func NewObservabilityConfig

func NewObservabilityConfig(baseConfig *config.BaseConfig, version string) *ObservabilityConfig

NewObservabilityConfig derives an ObservabilityConfig from the application's BaseConfig and the given service version, copying over the service name, environment, OTEL endpoint, and sample rate.

type ObservabilityManager

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

ObservabilityManager owns the OpenTelemetry tracing and metrics providers for the life of the application. Call Initialize once during startup to wire up exporters from the manager's config, use Tracer and Meter to obtain instrumentation handles, and call Shutdown during graceful shutdown to flush pending telemetry and release exporter resources.

func NewObservabilityManager

func NewObservabilityManager(config *ObservabilityConfig) *ObservabilityManager

NewObservabilityManager creates an ObservabilityManager for the given configuration. The returned manager is not yet initialized; call Initialize before relying on Tracer or Meter to return non-no-op instrumentation.

func (*ObservabilityManager) Initialize

func (om *ObservabilityManager) Initialize(ctx context.Context) error

Initialize sets up OpenTelemetry tracing and metrics using the manager's configuration. It is idempotent: once initialization has succeeded, subsequent calls return nil without doing any work. The App calls this automatically during Start, before bundles are initialized, so components and bundles can safely assume tracing/metrics are ready by the time they run.

func (*ObservabilityManager) Meter added in v0.3.0

func (om *ObservabilityManager) Meter(name string) otelmetric.Meter

Meter returns an OpenTelemetry Meter for the given instrumentation name (conventionally the calling package or component name). Use it to create instruments — counters, histograms, gauges, etc. — for recording metrics. If Initialize has not configured a meter provider (no OTEL endpoint set), this falls back to the global otel.Meter, which produces no-op instruments rather than erroring.

func (*ObservabilityManager) Shutdown

func (om *ObservabilityManager) Shutdown(ctx context.Context) error

Shutdown flushes and shuts down the trace and meter providers created by Initialize, returning a joined error if either fails to shut down cleanly. It is a no-op if Initialize was never called or did not complete successfully.

func (*ObservabilityManager) Tracer added in v0.3.0

func (om *ObservabilityManager) Tracer(name string) oteltrace.Tracer

Tracer returns an OpenTelemetry Tracer for the given instrumentation name (conventionally the calling package or component name). Use it to start spans around units of work you want traced, e.g. om.Tracer("myservice").Start(ctx, "operation-name"). If Initialize has not configured a trace provider (no OTEL endpoint set), this falls back to the global otel.Tracer, which produces no-op spans rather than erroring.

type OrchestrationHook

type OrchestrationHook struct {
	Name string
	Func ShutdownHookFunc
}

OrchestrationHook represents a named shutdown hook with a cleanup function.

func ComponentShutdownHook

func ComponentShutdownHook(name string, component Component) OrchestrationHook

ComponentShutdownHook creates a shutdown hook for a Component.

type Registrar

type Registrar interface {
	// RegisterGRPC registers gRPC service handlers with the provided server.
	// This method is called during application startup before the gRPC server starts.
	RegisterGRPC(server *grpc.Server) error
}

Registrar represents a service that can register gRPC handlers. Components that expose gRPC services should implement this interface to register their handlers with the framework's gRPC server.

Example:

func (s *UserService) RegisterGRPC(server *grpc.Server) error {
	userpb.RegisterUserServiceServer(server, s)
	return nil
}

type ShutdownHook

type ShutdownHook func(ctx context.Context, app *App) error

ShutdownHook is called during application shutdown. Use shutdown hooks for custom cleanup logic that needs to run before components are stopped. Hooks are executed in reverse registration order.

type ShutdownHookFunc

type ShutdownHookFunc func(ctx context.Context) error

ShutdownHookFunc is a function that performs cleanup during shutdown.

type ShutdownOrchestrator

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

ShutdownOrchestrator manages the graceful shutdown of service components.

func NewShutdownOrchestrator

func NewShutdownOrchestrator(timeout time.Duration) *ShutdownOrchestrator

NewShutdownOrchestrator creates a new shutdown orchestrator with the given timeout.

func (*ShutdownOrchestrator) RegisterHook

func (so *ShutdownOrchestrator) RegisterHook(name string, hookFunc ShutdownHookFunc)

RegisterHook adds a shutdown hook to be executed during shutdown. Hooks are executed in the order they were registered.

func (*ShutdownOrchestrator) RegisterHookWithTimeout

func (so *ShutdownOrchestrator) RegisterHookWithTimeout(name string, timeout time.Duration, hookFunc ShutdownHookFunc)

RegisterHookWithTimeout adds a shutdown hook with a specific timeout.

func (*ShutdownOrchestrator) Shutdown

func (so *ShutdownOrchestrator) Shutdown(ctx context.Context) error

Shutdown executes all registered hooks in order and waits for completion. Returns an error if any hook fails or if the overall timeout is exceeded.

type StartupHook

type StartupHook func(ctx context.Context, app *App) error

StartupHook is called during application startup. Use startup hooks for custom initialization logic that needs to run after bundles are initialized but before the service is marked as ready.

Jump to

Keyboard shortcuts

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