component

package
v0.3.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 8 Imported by: 11

README

component

Lifecycle-managed components with ordered startup/shutdown, health checks, and lazy initialization.

Install

go get github.com/kbukum/gokit

Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/kbukum/gokit/component"
)

func main() {
    registry := component.NewRegistry()

    // Create a lazy component
    comp := component.NewBaseLazyComponent("cache", func(ctx context.Context) error {
        fmt.Println("initializing cache...")
        return nil
    }).WithHealthCheck(func(ctx context.Context) error {
        return nil // health check logic
    }).WithCloser(func() error {
        fmt.Println("closing cache")
        return nil
    })

    registry.Register(comp)

    ctx := context.Background()
    registry.StartAll(ctx)  // starts in registration order
    defer registry.StopAll(ctx)  // stops in reverse order

    // Check health
    for _, h := range registry.HealthAll(ctx) {
        fmt.Printf("%s: %s\n", h.Name, h.Status)
    }
}

Key Types & Functions

Name Description
Component Interface: Name(), Start(), Stop(), Health()
Health Health status with name, status, message
Registry Manages component lifecycle with deterministic ordering
BaseLazyComponent Thread-safe lazy initialization wrapper
NewRegistry() Create component registry
StartAll() / StopAll() / HealthAll() Batch lifecycle operations

⬅ Back to main README

Documentation

Overview

Package component defines the core interfaces for lifecycle-managed infrastructure services in gokit.

Components represent services that require initialization, startup, shutdown, and health monitoring. They are registered with the bootstrap package for automatic lifecycle management.

Interfaces

  • Component: Core lifecycle interface (Init/Start/Stop)
  • HealthChecker: Health status reporting
  • Describable: Bootstrap summary descriptions

Index

Constants

View Source
const DefaultStartTimeout = 30 * time.Second

DefaultStartTimeout bounds a single component's Start call when the caller-supplied context has no deadline. The bound is cooperative — Start must return when its context is canceled (see Component.Start); it caps a well-behaved Start, not one that ignores ctx.

View Source
const DefaultStopTimeout = 10 * time.Second

DefaultStopTimeout is applied to a component's Stop call only when the caller-supplied context has no deadline. A bounded fallback prevents a stuck Stop from blocking shutdown forever, while still letting callers pass a tighter deadline by attaching one to ctx.

Variables

This section is empty.

Functions

This section is empty.

Types

type BaseLazyComponent

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

BaseLazyComponent provides thread-safe lazy initialization for components that defer expensive setup until first use.

func NewBaseLazyComponent

func NewBaseLazyComponent(name string, initializer func(context.Context) error) *BaseLazyComponent

NewBaseLazyComponent creates a lazy component with the given initializer.

func (*BaseLazyComponent) Close

func (b *BaseLazyComponent) Close() error

Close shuts down the component and marks it as uninitialized.

func (*BaseLazyComponent) HealthCheck

func (b *BaseLazyComponent) HealthCheck(ctx context.Context) error

HealthCheck verifies the component is initialized and optionally runs a custom check.

func (*BaseLazyComponent) Initialize

func (b *BaseLazyComponent) Initialize(ctx context.Context) error

Initialize performs thread-safe lazy initialization using double-check locking.

func (*BaseLazyComponent) IsInitialized

func (b *BaseLazyComponent) IsInitialized() bool

IsInitialized returns whether the component has been successfully initialized.

func (*BaseLazyComponent) Name

func (b *BaseLazyComponent) Name() string

Name returns the component name.

func (*BaseLazyComponent) WithCloser

func (b *BaseLazyComponent) WithCloser(fn func() error) *BaseLazyComponent

WithCloser sets a custom close function.

func (*BaseLazyComponent) WithHealthCheck

func (b *BaseLazyComponent) WithHealthCheck(fn func(context.Context) error) *BaseLazyComponent

WithHealthCheck sets a custom health check function.

type Component

type Component interface {
	// Name returns the unique name of the component for registration.
	Name() string

	// Start initializes and starts the component. Implementations must honor ctx:
	// return promptly when ctx is canceled or its deadline passes. The registry derives a
	// bounded context for each Start (RegistryConfig.StartTimeout) and cancels peers when a
	// sibling fails, but Go cannot interrupt a synchronous call that ignores its context, so
	// a Start that blocks without observing ctx will stall the boot sequence.
	Start(ctx context.Context) error

	// Stop gracefully shuts down the component and releases resources.
	// Implementations must drain inflight work within the context deadline.
	Stop(ctx context.Context) error

	// Health returns the current health status of the component.
	Health(ctx context.Context) Health
}

Component represents a lifecycle-managed infrastructure component. Each infrastructure module (database, redis, kafka, etc.) implements this interface.

The canonical lifecycle state machine is:

Created → Starting → Running → Stopping → Stopped
                ↘ Failed

Stop() is responsible for draining any inflight work before releasing resources. The framework enforces a per-component timeout via the context.

type Describable

type Describable interface {
	Describe() Description
}

Describable is optionally implemented by Components to provide startup summary information for the bootstrap display.

When a component implements this interface, the bootstrap system automatically includes it in the infrastructure section of the startup summary — no manual TrackInfrastructure calls needed.

IMPROVE-RSKIT: rskit's component trait is lifecycle-only; these optional self-description capabilities (Describable/RouteProvider) are a gokit enhancement. Consider adding equivalent optional metadata traits to rskit-component so the two kits report startup summaries at parity.

type Description

type Description struct {
	// Name is the human-readable display name (e.g., "HTTP Server", "PostgreSQL"). If empty,
	// the component's Name() is used.
	Name string
	// Type categorizes the component: "database", "server", "kafka", "redis", etc.
	Type string
	// Details is a human-readable one-liner shown in the startup summary. Examples:
	// "localhost:5432 pool=25/5", "localhost:6379 db=0 pool=10"
	Details string
	// Port is the primary port, 0 if not applicable.
	Port int
}

Description holds summary information for the bootstrap display. Components that implement Describable return this to self-report what they are and how they're configured.

type Health

type Health struct {
	Name    string       `json:"name"`
	Status  HealthStatus `json:"status"`
	Message string       `json:"message,omitempty"`
}

Health holds health information for a component.

func Degraded

func Degraded(name, message string) Health

Degraded returns a degraded report for the named component with an explanatory message.

func Healthy

func Healthy(name string) Health

Healthy returns a healthy report for the named component.

func Unhealthy

func Unhealthy(name, message string) Health

Unhealthy returns an unhealthy report for the named component with an explanatory message.

func (Health) IsHealthy

func (h Health) IsHealthy() bool

IsHealthy reports whether the status is StatusHealthy.

type HealthStatus

type HealthStatus string

HealthStatus represents the health state of a component.

const (
	StatusHealthy   HealthStatus = "healthy"
	StatusUnhealthy HealthStatus = "unhealthy"
	StatusDegraded  HealthStatus = "degraded"
)

type LazyComponent

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

LazyComponent wraps a component factory so the underlying Component is not constructed until Start is first called. This defers expensive construction (opening connections, allocating pools) out of registration and into the boot sequence, while still presenting a normal Component to the Registry.

It differs from BaseLazyComponent, which defers an initializer on an already-constructed component; LazyComponent defers construction of the Component itself.

func NewLazyComponent

func NewLazyComponent(name string, factory func() Component) *LazyComponent

NewLazyComponent returns a LazyComponent that builds its delegate via factory on first Start.

func (*LazyComponent) Health

func (l *LazyComponent) Health(ctx context.Context) Health

Health reports the delegate's health, or a degraded "not started" report before first Start.

func (*LazyComponent) Name

func (l *LazyComponent) Name() string

Name returns the component name.

func (*LazyComponent) Start

func (l *LazyComponent) Start(ctx context.Context) error

Start constructs the delegate (once) and starts it. A nil factory, or a factory that returns a nil Component — including a typed nil such as a nil *T stored in the interface, which a plain == nil check misses — yields a typed error rather than a panic: Go interfaces permit nil values that the Rust Arc<dyn Component> counterpart cannot, so this lifecycle path guards them explicitly.

func (*LazyComponent) Stop

func (l *LazyComponent) Stop(ctx context.Context) error

Stop stops the delegate if it was constructed; a never-started LazyComponent is a no-op.

type Registry

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

Registry manages component lifecycle with deterministic ordering. Components are started in registration order and stopped in reverse order. Each component tracks a formal lifecycle state (Created → Starting → Running → Stopping → Stopped | Failed).

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new component registry with default configuration.

func NewRegistryWithConfig

func NewRegistryWithConfig(cfg RegistryConfig) *Registry

NewRegistryWithConfig creates a component registry with the given configuration. Zero fields fall back to the registry defaults.

func (*Registry) All

func (r *Registry) All() []Component

All returns all registered components in registration order.

func (*Registry) Get

func (r *Registry) Get(name string) Component

Get returns a registered component by name, or nil if not found.

func (*Registry) HealthAll

func (r *Registry) HealthAll(ctx context.Context) []Health

HealthAll returns health status for all registered components. The snapshot is taken under the read lock, but Health() calls are made without holding the lock to avoid blocking registration or lifecycle ops.

func (*Registry) Register

func (r *Registry) Register(c Component) error

Register adds a component to the registry. Components are started in the order they are registered, so register dependencies first.

func (*Registry) StartAll

func (r *Registry) StartAll(ctx context.Context) error

StartAll starts all not-yet-started components in registration order.

It is safe to call multiple times — already-running components are skipped. This supports two-phase startup where infrastructure components are started first and application-layer components (registered during configure) are started in a second pass.

If a component fails to start, all components that were successfully started during this call are rolled back (stopped in reverse order). Components started by a previous call are NOT rolled back.

The Component.Start call runs without holding any registry lock so readers (Get / All / HealthAll) and concurrent Register calls are not blocked for the duration of the boot sequence.

Each Component.Start is given a context bounded by RegistryConfig.StartTimeout when the supplied context carries no deadline; pass a context with an explicit deadline to override that bound. The bound is cooperative: Start must observe its context and return when it is canceled (see Component.Start). Go cannot interrupt a synchronous call that ignores its context, so a component that never returns on cancellation can still stall the boot sequence — the timeout bounds well-behaved components, it is not a hard kill.

func (*Registry) StartAllConcurrent

func (r *Registry) StartAllConcurrent(ctx context.Context) error

StartAllConcurrent starts all not-yet-started components in parallel, bounded by RegistryConfig.Concurrency (zero means no limit). Unlike StartAll it does not impose a registration order between components, so use it only when the registered components have no inter-dependencies. On any failure it stops every component started during this call (in reverse completion order) and returns the first start error.

func (*Registry) State

func (r *Registry) State(name string) (State, bool)

State returns the lifecycle state of a named component. Returns StateCreated and false if the component is not registered.

func (*Registry) StopAll

func (r *Registry) StopAll(ctx context.Context) error

StopAll gracefully stops all running components in reverse registration order.

Each Component.Stop runs with the caller's ctx; if ctx has no deadline, DefaultStopTimeout is applied per-component as a safety net. Errors are aggregated via errors.Join so callers can inspect individual failures with errors.Is/errors.As.

func (*Registry) StopAllDetailed

func (r *Registry) StopAllDetailed(ctx context.Context) []StopResult

StopAllDetailed gracefully stops all running components and returns per-component results. This provides structured error information for callers that need to know which specific components failed.

type RegistryConfig

type RegistryConfig struct {
	// Concurrency is the maximum number of components started in parallel by
	// StartAllConcurrent. Zero means "no limit" (start every candidate at once).
	// Sequential StartAll ignores this field.
	Concurrency int

	// StartTimeout bounds each component's Start call when ctx has no deadline.
	StartTimeout time.Duration

	// StopTimeout bounds each component's Stop call when ctx has no deadline.
	StopTimeout time.Duration
}

RegistryConfig configures a component Registry: how many components may start in parallel and the per-component start/stop timeouts applied when the caller's context carries no deadline.

func DefaultRegistryConfig

func DefaultRegistryConfig() RegistryConfig

DefaultRegistryConfig returns the registry defaults: sequential start (Concurrency 1) with bounded start and stop timeouts.

type Route

type Route struct {
	Method  string
	Path    string
	Handler string
}

Route holds a single HTTP route for the startup summary.

type RouteProvider

type RouteProvider interface {
	Routes() []Route
}

RouteProvider is optionally implemented by server components to auto-report registered HTTP routes for the startup summary.

type State

type State int

State represents the lifecycle state of a component.

const (
	// StateCreated is the initial state after registration.
	StateCreated State = iota
	// StateStarting indicates the component is currently starting.
	StateStarting
	// StateRunning indicates the component started successfully and is operational.
	StateRunning
	// StateStopping indicates the component is currently shutting down.
	StateStopping
	// StateStopped indicates the component has been shut down.
	StateStopped
	// StateFailed indicates the component failed to start or encountered a fatal error.
	StateFailed
)

func (State) String

func (s State) String() string

String returns the human-readable name of a lifecycle state.

type StopResult

type StopResult struct {
	// Name of the component.
	Name string
	// Err is nil on success, non-nil on failure.
	Err error
}

StopResult holds the outcome of stopping a single component.

Jump to

Keyboard shortcuts

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