gaz

package module
v0.0.0-...-4dd1f93 Latest Latest
Warning

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

Go to latest
Published: Apr 4, 2026 License: MIT Imports: 24 Imported by: 0

README

gaz

Go Reference Go Version

Simple, type-safe dependency injection with lifecycle management for Go applications. No code generation, no reflection magic.

Installation

go get github.com/petabytecl/gaz

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/petabytecl/gaz"
)

type Server struct {
	addr string
}

func (s *Server) OnStart(ctx context.Context) error {
	fmt.Println("server starting on", s.addr)
	return nil
}

func (s *Server) OnStop(ctx context.Context) error {
	fmt.Println("server stopped")
	return nil
}

func main() {
	app := gaz.New()

	// Register a singleton provider using the type-safe For[T]() API
	err := gaz.For[*Server](app.Container()).Provider(func(c *gaz.Container) (*Server, error) {
		return &Server{addr: ":8080"}, nil
	})
	if err != nil {
		log.Fatal(err)
	}

	if err := app.Build(); err != nil {
		log.Fatal(err)
	}

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

Features

Core DI
  • Type-safe container - Compile-time type checking via generics
  • Singleton and transient scopes - One instance or new instance per resolution
  • Lifecycle hooks - OnStart/OnStop interfaces for startup/shutdown logic
  • Discovery - ResolveAll[T] and ResolveGroup[T] for plugin-style architectures
  • Module organization - Group related providers into reusable modules
Application Framework
  • Graceful shutdown - Configurable timeout with per-hook limits and signal handling
  • Configuration loading - YAML/JSON/TOML files, environment variables, CLI flags
  • Struct validation - validate tags with go-playground/validator
  • Cobra CLI integration - Build CLI apps with dependency injection
  • Background workers - Supervised workers with restart, circuit breaker, lifecycle integration
Server & Transport (v4.1)
  • gRPC Server - Interceptors, reflection, service discovery, native health checks
  • HTTP Server - Configurable timeouts, graceful shutdown
  • Vanguard - Unified server: gRPC, Connect, gRPC-Web, REST on a single port
  • OpenTelemetry - TracerProvider with OTLP export and server instrumentation
  • Health checks - Readiness/liveness probes, gRPC health protocol, builtin checks
CLI Flags
  • Logger flags - --log-level, --log-format, --log-output, --log-add-source
  • Config flags - --config, --env-prefix, --config-strict with XDG auto-search

Core Concepts

App vs Container

App is the high-level API for building applications. It manages the container, configuration, lifecycle, and signal handling:

app := gaz.New()

// Register services using the type-safe For[T]() API
gaz.For[*Database](app.Container()).Provider(NewDatabase)
gaz.For[*UserService](app.Container()).Provider(NewUserService)

app.Build()
app.Run(ctx)

Container is the low-level DI container. Use it directly for testing or advanced scenarios:

c := gaz.NewContainer()
gaz.For[*Database](c).Provider(NewDatabase)
c.Build()
db, _ := gaz.Resolve[*Database](c)
Service Scopes
// Singleton (default): one instance for container lifetime
gaz.For[*Database](c).Provider(NewDatabase)

// Transient: new instance on every resolution
gaz.For[*Request](c).Transient().Provider(NewRequest)

// Eager: singleton instantiated at Build() time
gaz.For[*ConnectionPool](c).Eager().Provider(NewConnectionPool)

// Instance: register a pre-built value
gaz.For[*Config](c).Instance(cfg)
Lifecycle Hooks

Services implementing Starter or Stopper interfaces get automatic lifecycle management:

type Starter interface {
	OnStart(context.Context) error
}

type Stopper interface {
	OnStop(context.Context) error
}

Hooks are called in dependency order (dependencies start first, stop last).

Documentation

Examples

See the examples directory:

  • basic - Minimal working application
  • http-server - HTTP server with graceful shutdown
  • config-loading - Configuration files and environment variables
  • lifecycle - Services with OnStart/OnStop hooks
  • modules - Organizing providers into modules
  • cobra-cli - CLI application with Cobra
  • background-workers - Background task processing
  • discovery - Plugin-style architecture with ResolveAll
  • vanguard - Unified server: gRPC, Connect, gRPC-Web, REST on a single port
  • microservice - Complete microservice with health, workers, eventbus

License

MIT

Documentation

Overview

Package gaz provides a simple, type-safe dependency injection container with lifecycle management for Go applications.

Quick Start

Create an application, register providers, build, and run:

app := gaz.New()
gaz.For[*Database](app.Container()).Provider(func(c *gaz.Container) (*Database, error) {
    return &Database{DSN: "postgres://..."}, nil
})
if err := app.Build(); err != nil {
    log.Fatal(err)
}
app.Run(context.Background())

Service Scopes

Services can be registered with different scopes using the For fluent API:

  • Singleton: One instance for container lifetime (default). Use For[T].Provider().
  • Transient: New instance on every resolution. Use For[T].Transient().Provider().
  • Eager: Singleton instantiated at [Container.Build] time. Use For[T].Eager().Provider().

Examples:

gaz.For[*Service](c).Provider(NewService)           // Lazy singleton (default)
gaz.For[*Service](c).Transient().Provider(NewService) // New instance each time
gaz.For[*Pool](c).Eager().Provider(NewPool)         // Created at Build() time

Lifecycle Management

Services implementing Starter or Stopper get automatic lifecycle hooks. [Starter.OnStart] is called after App.Build, and [Stopper.OnStop] is called during graceful shutdown.

type Server struct{}

func (s *Server) OnStart(ctx context.Context) error {
    return s.listen()
}

func (s *Server) OnStop(ctx context.Context) error {
    return s.shutdown()
}

Hooks are called in dependency order: dependencies start first and stop last. Shutdown timeout is configurable via WithShutdownTimeout, with per-hook limits via WithPerHookTimeout.

Configuration

Load configuration from files, environment variables, and CLI flags:

type Config struct {
    Port int    `mapstructure:"port" validate:"required,min=1"`
    Host string `mapstructure:"host" validate:"required"`
}

import "github.com/petabytecl/gaz/config"

app := gaz.New()
app.WithConfig(&Config{}, config.WithEnvPrefix("APP"))

The config struct is automatically registered in the container. Use [ConfigManager] for advanced scenarios. Config values are validated using struct tags with go-playground/validator.

Health Checks

The health subpackage provides HTTP health check endpoints:

import healthmod "github.com/petabytecl/gaz/health/module"

app.Use(healthmod.New())

See the health package for health.Manager, readiness, and liveness probes.

Resolution

Resolve dependencies from the container using Resolve:

db, err := gaz.Resolve[*Database](c)
if errors.Is(err, gaz.ErrDINotFound) {
    // Handle missing dependency
}

For named registrations, use the Named option:

primary, _ := gaz.Resolve[*sql.DB](c, gaz.Named("primary"))
replica, _ := gaz.Resolve[*sql.DB](c, gaz.Named("replica"))

Modules

Group related providers into reusable modules:

app.Module("database",
    func(c *gaz.Container) error {
        return gaz.For[*Pool](c).Provider(NewPool)
    },
    func(c *gaz.Container) error {
        return gaz.For[*UserRepo](c).Provider(NewUserRepo)
    },
)

See App.Module for details.

Example (Lifecycle)

Example_lifecycle demonstrates registering services with lifecycle hooks. Services implementing Starter and Stopper interfaces have their OnStart/OnStop methods called automatically during App lifecycle.

package main

import (
	"context"
	"fmt"

	"github.com/petabytecl/gaz"
)

// Server demonstrates a service with lifecycle hooks.
type Server struct {
	started bool
	port    int
}

// OnStart is called when the service starts.
func (s *Server) OnStart(_ context.Context) error {
	s.started = true
	return nil
}

// OnStop is called when the service stops.
func (s *Server) OnStop(_ context.Context) error {
	s.started = false
	return nil
}

func main() {
	app := gaz.New()

	// Register Server - it implements Starter and Stopper interfaces
	err := gaz.For[*Server](app.Container()).Provider(func(c *gaz.Container) (*Server, error) {
		return &Server{port: 8080}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if err := app.Build(); err != nil {
		fmt.Println("error:", err)
		return
	}

	svc, _ := gaz.Resolve[*Server](app.Container())
	fmt.Println("server registered, port:", svc.port)
}
Output:
server registered, port: 8080
Example (LifecycleOrder)

Example_lifecycleOrder demonstrates that dependencies start before dependents. When Service A depends on Service B, B's OnStart is called before A's OnStart. During shutdown, the order is reversed: A stops before B.

package main

import (
	"context"
	"fmt"

	"github.com/petabytecl/gaz"
)

// Database demonstrates a dependency that starts first.
type Database struct {
	connected bool
}

func (d *Database) OnStart(_ context.Context) error {
	d.connected = true
	return nil
}

func (d *Database) OnStop(_ context.Context) error {
	d.connected = false
	return nil
}

// AppServer depends on Database, demonstrating startup order.
type AppServer struct {
	db *Database
}

func (s *AppServer) OnStart(_ context.Context) error {

	return nil
}

func (s *AppServer) OnStop(_ context.Context) error {
	return nil
}

func main() {
	c := gaz.NewContainer()

	// Register Database (no dependencies)
	err := gaz.For[*Database](c).Provider(func(_ *gaz.Container) (*Database, error) {
		return &Database{}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// Register AppServer (depends on Database)
	err = gaz.For[*AppServer](c).Provider(func(c *gaz.Container) (*AppServer, error) {
		db, err := gaz.Resolve[*Database](c)
		if err != nil {
			return nil, err
		}
		return &AppServer{db: db}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// Resolve to establish dependency graph
	server, _ := gaz.Resolve[*AppServer](c)
	fmt.Println("server has database:", server.db != nil)
}
Output:
server has database: true

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrDINotFound is returned when a requested service is not registered in the container.
	// Check with: errors.Is(err, gaz.ErrDINotFound).
	ErrDINotFound = di.ErrNotFound

	// ErrDICycle is returned when a circular dependency is detected during resolution.
	// Check with: errors.Is(err, gaz.ErrDICycle).
	ErrDICycle = di.ErrCycle

	// ErrDIDuplicate is returned when attempting to register a service that already exists.
	// Check with: errors.Is(err, gaz.ErrDIDuplicate).
	ErrDIDuplicate = di.ErrDuplicate

	// ErrDINotSettable is returned when a struct field cannot be set during injection.
	// Check with: errors.Is(err, gaz.ErrDINotSettable).
	ErrDINotSettable = di.ErrNotSettable

	// ErrDITypeMismatch is returned when a resolved service cannot be assigned to the target type.
	// Check with: errors.Is(err, gaz.ErrDITypeMismatch).
	ErrDITypeMismatch = di.ErrTypeMismatch

	// ErrDIAlreadyBuilt is returned when attempting to register after Build() was called.
	// Check with: errors.Is(err, gaz.ErrDIAlreadyBuilt).
	ErrDIAlreadyBuilt = di.ErrAlreadyBuilt

	// ErrDIInvalidProvider is returned when a provider function has invalid signature.
	// Check with: errors.Is(err, gaz.ErrDIInvalidProvider).
	ErrDIInvalidProvider = di.ErrInvalidProvider

	// ErrDIAmbiguous is returned when multiple services are registered for the same key.
	// Check with: errors.Is(err, gaz.ErrDIAmbiguous).
	ErrDIAmbiguous = di.ErrAmbiguous
)

DI subsystem errors. These are re-exports from the di package with standardized ErrDI* naming. Use errors.Is(err, gaz.ErrDI*) to check for these errors.

Note: Due to Go's import cycle constraints (gaz imports di), the canonical source of DI errors is di/errors.go. These are aliases that point to the same error values, ensuring errors.Is compatibility.

View Source
var (
	// ErrConfigValidation is returned when config struct validation fails.
	// Check with: errors.Is(err, gaz.ErrConfigValidation) or errors.Is(err, config.ErrConfigValidation).
	ErrConfigValidation = config.ErrConfigValidation

	// ErrConfigNotFound is returned when a config key/namespace doesn't exist.
	// Check with: errors.Is(err, gaz.ErrConfigNotFound) or errors.Is(err, config.ErrKeyNotFound).
	ErrConfigNotFound = config.ErrKeyNotFound
)

Config subsystem errors. These are re-exports from the config package with standardized ErrConfig* naming. Use errors.Is(err, gaz.ErrConfig*) to check for these errors.

Note: Due to Go's import cycle constraints, the canonical source of config errors is config/errors.go. These are aliases that point to the same error values, ensuring errors.Is compatibility.

View Source
var (
	// ErrWorkerCircuitTripped indicates a worker exhausted its restart attempts
	// within the configured circuit window. The worker will not be restarted
	// until the application is restarted.
	ErrWorkerCircuitTripped = worker.ErrCircuitBreakerTripped

	// ErrWorkerStopped indicates a worker stopped normally without error.
	// This is not an error condition - it signals clean shutdown.
	ErrWorkerStopped = worker.ErrWorkerStopped

	// ErrWorkerCriticalFailed indicates a critical worker failed and exhausted
	// its restart attempts. This error triggers application shutdown.
	ErrWorkerCriticalFailed = worker.ErrCriticalWorkerFailed

	// ErrWorkerManagerRunning indicates an attempt to register a worker
	// after the manager has started.
	ErrWorkerManagerRunning = worker.ErrManagerAlreadyRunning
)

Worker subsystem errors. These are re-exports from the worker package with standardized ErrWorker* naming. Use errors.Is(err, gaz.ErrWorker*) to check for these errors.

Note: Due to Go's import cycle constraints, the canonical source of worker errors is worker/errors.go. These are aliases that point to the same error values, ensuring errors.Is compatibility.

View Source
var (
	// ErrModuleDuplicate is returned when a module with the same name is registered twice.
	ErrModuleDuplicate = errors.New("gaz: duplicate module")

	// ErrConfigKeyCollision is returned when two providers register the same config key.
	ErrConfigKeyCollision = errors.New("gaz: config key collision")
)

Module errors (gaz-specific).

View Source
var (
	// ErrCronNotRunning indicates an operation was attempted on a scheduler
	// that is not running.
	ErrCronNotRunning = cron.ErrNotRunning
)

Cron subsystem errors. These are re-exports from the cron package with standardized ErrCron* naming. Use errors.Is(err, gaz.ErrCron*) to check for these errors.

Note: Due to Go's import cycle constraints, the canonical source of cron errors is cron/errors.go. These are aliases that point to the same error values, ensuring errors.Is compatibility.

Functions

func ComputeShutdownOrder

func ComputeShutdownOrder(startupOrder [][]string) [][]string

ComputeShutdownOrder reverses the startup order for safe shutdown.

func ComputeStartupOrder

func ComputeStartupOrder(
	graph map[string][]string,
	services map[string]di.ServiceWrapper,
) ([][]string, error)

ComputeStartupOrder calculates the order in which services should be started. It returns a list of layers, where each layer contains services that can be started in parallel.

graph: A map where key is the service name and value is the list of dependencies. services: A map of service wrappers to check for lifecycle hooks.

func For

func For[T any](c *Container) *di.RegistrationBuilder[T]

For returns a registration builder for type T.

Example:

gaz.For[*MyService](c).Provider(NewMyService)
Example (Provider)

ExampleFor_provider demonstrates dependency injection with For[T](). The provider function receives the Container for resolving dependencies.

package main

import (
	"fmt"

	"github.com/petabytecl/gaz"
)

// Logger is a service that other services depend on.
type Logger struct {
	Level string
}

// UserService demonstrates dependency injection.
type UserService struct {
	logger *Logger
}

func main() {
	app := gaz.New()

	// Register Logger first
	err := gaz.For[*Logger](app.Container()).Provider(func(c *gaz.Container) (*Logger, error) {
		return &Logger{Level: "info"}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// UserService depends on Logger
	err = gaz.For[*UserService](app.Container()).Provider(func(c *gaz.Container) (*UserService, error) {
		logger, resolveErr := gaz.Resolve[*Logger](c)
		if resolveErr != nil {
			return nil, resolveErr
		}
		return &UserService{logger: logger}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if err := app.Build(); err != nil {
		fmt.Println("error:", err)
		return
	}

	svc, _ := gaz.Resolve[*UserService](app.Container())
	fmt.Println("logger level:", svc.logger.Level)
}
Output:
logger level: info
Example (Singleton)

ExampleFor_singleton demonstrates registering a singleton service. Singletons return the same instance on every resolution.

package main

import (
	"fmt"

	"github.com/petabytecl/gaz"
)

// Counter tracks resolution calls for singleton vs transient demo.
type Counter struct {
	value int
}

func (c *Counter) Next() int {
	c.value++
	return c.value
}

func main() {
	c := gaz.NewContainer()

	counter := &Counter{}
	err := gaz.For[*Counter](c).Instance(counter)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// Resolve twice - same instance returned
	c1, _ := gaz.Resolve[*Counter](c)
	c2, _ := gaz.Resolve[*Counter](c)

	fmt.Println("same instance:", c1 == c2)
}
Output:
same instance: true
Example (Transient)

ExampleFor_transient demonstrates registering a transient service. Transient services return a new instance on every resolution.

package main

import (
	"fmt"

	"github.com/petabytecl/gaz"
)

// Counter tracks resolution calls for singleton vs transient demo.
type Counter struct {
	value int
}

func (c *Counter) Next() int {
	c.value++
	return c.value
}

func main() {
	c := gaz.NewContainer()

	callCount := 0
	err := gaz.For[*Counter](c).Transient().Provider(func(_ *gaz.Container) (*Counter, error) {
		callCount++
		return &Counter{value: callCount * 100}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// Resolve twice - different instances created
	c1, _ := gaz.Resolve[*Counter](c)
	c2, _ := gaz.Resolve[*Counter](c)

	fmt.Println("different instances:", c1 != c2)
	fmt.Println("c1 value:", c1.value)
	fmt.Println("c2 value:", c2.value)
}
Output:
different instances: true
c1 value: 100
c2 value: 200

func GetArgs

func GetArgs(c *di.Container) []string

GetArgs retrieves the CLI arguments from the DI container. It returns nil if CommandArgs is not available in the container.

func Has

func Has[T any](c *Container) bool

Has returns true if a service of type T is registered.

func MustResolve

func MustResolve[T any](c *Container, opts ...di.ResolveOption) T

MustResolve resolves a service or panics if resolution fails.

func Named

func Named(name string) di.ResolveOption

Named resolves a service by its registered name instead of type.

func Resolve

func Resolve[T any](c *Container, opts ...di.ResolveOption) (T, error)

Resolve retrieves a service of type T from the container.

Example

ExampleResolve demonstrates resolving a registered service.

package main

import (
	"fmt"

	"github.com/petabytecl/gaz"
)

// Logger is a service that other services depend on.
type Logger struct {
	Level string
}

func main() {
	c := gaz.NewContainer()

	// Register a configuration instance
	err := gaz.For[*Logger](c).Instance(&Logger{Level: "debug"})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	// Resolve the service
	logger, err := gaz.Resolve[*Logger](c)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("level:", logger.Level)
}
Output:
level: debug

func ResolveAll

func ResolveAll[T any](c *Container) ([]T, error)

ResolveAll retrieves all registered services of type T.

func ResolveGroup

func ResolveGroup[T any](c *Container, group string) ([]T, error)

ResolveGroup retrieves all services belonging to the specified group. It filters services that are assignable to T.

func TypeName

func TypeName[T any]() string

TypeName returns the fully-qualified type name for T.

Types

type App

type App struct {

	// Logger instance - nil until Build() is called
	Logger *slog.Logger
	// contains filtered or unexported fields
}

App is the application runtime wrapper. It orchestrates dependency injection, lifecycle management, and signal handling.

func FromContext

func FromContext(ctx context.Context) *App

FromContext retrieves the App from a context. Returns nil if no App is found. Use this in Cobra command handlers to access the DI container.

Example:

var serveCmd = &cobra.Command{
    Use: "serve",
    RunE: func(cmd *cobra.Command, args []string) error {
        app := gaz.FromContext(cmd.Context())
        server, err := gaz.Resolve[*HTTPServer](app.Container())
        if err != nil {
            return err
        }
        return server.ListenAndServe()
    },
}

func New

func New(opts ...Option) *App

New creates a new App with the given options. Use the For[T]() fluent API to register services, then call Build() and Run().

Logger, WorkerManager, Scheduler, and EventBus are NOT created here. They are initialized in Build() after config is loaded and flags are parsed. This allows logger CLI flags to take effect.

Example:

app := gaz.New(gaz.WithShutdownTimeout(10 * time.Second))
gaz.For[*Database](app.Container()).Provider(NewDatabase)
gaz.For[*Request](app.Container()).Transient().Provider(NewRequest)
if err := app.Build(); err != nil {
    log.Fatal(err)
}
app.Run(ctx)
Example

ExampleNew demonstrates basic app creation and provider registration.

package main

import (
	"fmt"

	"github.com/petabytecl/gaz"
)

// MyService is a simple service for demonstration.
type MyService struct {
	Name string
}

func main() {
	app := gaz.New()
	err := gaz.For[*MyService](app.Container()).Provider(func(c *gaz.Container) (*MyService, error) {
		return &MyService{Name: "example"}, nil
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	if err := app.Build(); err != nil {
		fmt.Println("error:", err)
		return
	}
	svc, err := gaz.Resolve[*MyService](app.Container())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(svc.Name)
}
Output:
example

func (*App) AddFlagsFn

func (a *App) AddFlagsFn(fn func(*pflag.FlagSet))

AddFlagsFn registers a function that adds flags to the application. Flags are stored and applied when WithCobra option is processed or when a Cobra command is attached. If a Cobra command is already attached, flags are applied immediately.

func (*App) Build

func (a *App) Build() error

Build validates all registrations and instantiates eager services. It aggregates all errors and returns them using errors.Join. Build is idempotent - calling it multiple times after success returns nil.

func (*App) Container

func (a *App) Container() *Container

Container returns the underlying container. This is useful for advanced use cases or testing.

func (*App) EventBus

func (a *App) EventBus() *eventbus.EventBus

EventBus returns the application's EventBus for pub/sub. Returns nil if called before Build(). Prefer injecting *eventbus.EventBus as a dependency instead.

func (*App) MergeConfigMap

func (a *App) MergeConfigMap(cfg map[string]any) error

MergeConfigMap merges raw config values into the app's configuration. This is primarily intended for testing scenarios where you want to inject config values without loading from files.

Must be called before Build(). Panics if called after Build().

func (*App) Module

func (a *App) Module(name string, registrations ...func(*Container) error) *App

Module registers a named group of providers. The name is used for debugging and error messages. Duplicate module names result in ErrModuleDuplicate error during Build().

Each registration should be a function that accepts *Container and returns error. This allows using the For[T]() fluent API within modules.

Example:

app.Module("database",
    func(c *gaz.Container) error { return gaz.For[*DB](c).Provider(NewDB) },
    func(c *gaz.Container) error { return gaz.For[*UserRepo](c).Provider(NewUserRepo) },
).Module("http",
    func(c *gaz.Container) error { return gaz.For[*Server](c).Provider(NewServer) },
)

func (*App) RegisterCobraFlags

func (a *App) RegisterCobraFlags(cmd *cobra.Command) error

RegisterCobraFlags registers ConfigProvider flags as persistent pflags on the command. This must be called BEFORE cmd.Execute() for flags to appear in --help output.

The method: 1. Loads configuration (so defaults are available) 2. Registers ProviderValues for provider dependency injection 3. Collects ConfigProvider flags from registered services 4. Registers typed pflags on cmd.PersistentFlags() 5. Binds each flag to viper with the original dot-notation key

Example:

app := gaz.New()
gaz.For[*ServerConfig](app.Container()).Provider(NewServerConfig)
app.RegisterCobraFlags(rootCmd)  // Register before Execute
app.WithCobra(rootCmd)
rootCmd.Execute()

func (*App) Run

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

Run executes the application lifecycle. It builds the container, starts services in order, and waits for a signal or stop call.

func (*App) Start

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

Start initiates the application lifecycle. This is called automatically by WithCobra() or can be called manually. It executes OnStart hooks for all services in dependency order.

func (*App) Stop

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

Stop initiates graceful shutdown of the application. It executes OnStop hooks for all services in reverse dependency order. Safe to call even if Run() was not used (e.g., Cobra integration). Stop is idempotent - calling it multiple times returns the same result.

func (*App) Use

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

Use applies a module to the app's container. Modules bundle providers, configs, and other modules for reuse.

Use accepts both gaz.Module (built via gaz.NewModule().Build()) and di.Module (returned by subsystem packages like worker.NewModule()). This allows subsystem packages to export modules without importing gaz, avoiding import cycles.

Child modules bundled via ModuleBuilder.Use() are applied BEFORE the parent module's providers. This is for composition convenience, not dependency ordering (which is handled by the DI container).

If the module provides CLI flags (via ModuleBuilder.Flags()) and the app has a Cobra command attached (via WithCobra), the flags are registered on the command's PersistentFlags.

Returns error on duplicate module name (collected during Build()). Panics if called after Build().

Example:

module := gaz.NewModule("database").
    Provide(func(c *gaz.Container) error {
        return gaz.For[*DB](c).Provider(NewDB)
    }).
    Build()

app := gaz.New().
    Use(module).
    Use(worker.NewModule()).    // di.Module from subsystem
    Use(cacheModule).
    Build()

func (*App) UseDI

func (a *App) UseDI(m di.Module) *App

UseDI applies a di.Module to the app's container. This is for subsystem packages (health, worker, cron, eventbus) that return di.Module to avoid import cycles with the gaz package.

The di.Module interface has Register(c *Container) instead of Apply(app *App), which allows subsystem packages to export modules without importing gaz.

Example:

app := gaz.New().
    UseDI(worker.NewModule()).
    Build()

func (*App) WithConfig

func (a *App) WithConfig(target any, opts ...config.Option) *App

WithConfig configures the application to load configuration into the target struct. The target must be a pointer to a struct, or nil to only customize config options.

The configuration is loaded from: 1. Defaults (via Defaulter interface) 2. Config files (yaml, json, toml) in specified paths 3. Environment variables (if WithEnvPrefix is set) 4. Flags (if WithCobra is used)

By default, gaz looks for config.yaml in the current directory. Use this method to: - Load config into a struct (target != nil) - Customize config options (change search paths, env prefix, etc.)

If you only use ConfigProvider pattern for config, you don't need to call this method.

type AppOptions

type AppOptions struct {
	ShutdownTimeout time.Duration
	PerHookTimeout  time.Duration
	LoggerConfig    *logger.Config
}

AppOptions configuration for App.

type CommandArgs

type CommandArgs struct {
	Command *cobra.Command
	Args    []string
}

CommandArgs holds the Cobra command and arguments for the current execution. It is available in the DI container for services that need access to CLI inputs.

type ConfigFlag

type ConfigFlag struct {
	// Key is the config key relative to the provider's namespace.
	// For example, if a provider declares namespace "redis" and key "host",
	// the full config key becomes "redis.host".
	Key string

	// Type specifies how the config value should be parsed.
	// String values are used as-is, while int, bool, duration, and float
	// values are parsed from their string representation.
	Type ConfigFlagType

	// Default is the default value to use if the config key is not set.
	// Set to nil for no default. The type should match the Type field
	// (e.g., int for ConfigFlagTypeInt, time.Duration for ConfigFlagTypeDuration).
	Default any

	// Required indicates whether this config key must be set.
	// If true and the key is not set (via env, file, or flag),
	// the application will fail to start during Build().
	Required bool

	// Description provides help text for this config key.
	// Used in --help output and documentation generation.
	Description string
}

ConfigFlag defines a configuration key that a provider needs. Providers return a slice of ConfigFlag from their ConfigFlags() method to declare their configuration requirements.

Example:

func (r *RedisProvider) ConfigFlags() []gaz.ConfigFlag {
    return []gaz.ConfigFlag{
        {Key: "host", Type: gaz.ConfigFlagTypeString, Default: "localhost", Description: "Redis server host"},
        {Key: "port", Type: gaz.ConfigFlagTypeInt, Default: 6379, Description: "Redis server port"},
        {Key: "password", Type: gaz.ConfigFlagTypeString, Required: true, Description: "Redis password"},
    }
}

type ConfigFlagType

type ConfigFlagType string

ConfigFlagType represents the type of a configuration flag value. The framework uses this to parse and validate config values correctly.

const (
	// ConfigFlagTypeString represents a string configuration value.
	ConfigFlagTypeString ConfigFlagType = "string"

	// ConfigFlagTypeInt represents an integer configuration value.
	ConfigFlagTypeInt ConfigFlagType = "int"

	// ConfigFlagTypeBool represents a boolean configuration value.
	ConfigFlagTypeBool ConfigFlagType = "bool"

	// ConfigFlagTypeDuration represents a time.Duration configuration value.
	// Values are parsed using time.ParseDuration (e.g., "30s", "5m", "1h").
	ConfigFlagTypeDuration ConfigFlagType = "duration"

	// ConfigFlagTypeFloat represents a float64 configuration value.
	ConfigFlagTypeFloat ConfigFlagType = "float"
)

type ConfigProvider

type ConfigProvider interface {
	// ConfigNamespace returns the namespace prefix for this provider's config keys.
	// All keys returned by ConfigFlags() are automatically prefixed with this namespace.
	// For example, if namespace is "redis" and a key is "host", the full key is "redis.host".
	ConfigNamespace() string

	// ConfigFlags returns the configuration flags this provider needs.
	// Each flag describes a config key with its type, default value, and whether it's required.
	ConfigFlags() []ConfigFlag
}

ConfigProvider is implemented by providers that need configuration. When a provider implements this interface, the framework will:

  1. Call ConfigNamespace() to get the prefix for all config keys
  2. Call ConfigFlags() to collect configuration requirements
  3. Auto-prefix each key with the namespace (e.g., "redis" + "host" = "redis.host")
  4. Translate keys for environment variables (e.g., "redis.host" → "REDIS_HOST")
  5. Validate required flags are set during Build()

Providers define their config needs but do not receive values directly. Config values are accessible via ProviderValues, which is injectable.

Example:

type RedisProvider struct{}

func (r *RedisProvider) ConfigNamespace() string {
    return "redis"
}

func (r *RedisProvider) ConfigFlags() []gaz.ConfigFlag {
    return []gaz.ConfigFlag{
        {Key: "host", Type: gaz.ConfigFlagTypeString, Default: "localhost", Description: "Redis server host"},
        {Key: "port", Type: gaz.ConfigFlagTypeInt, Default: 6379, Description: "Redis server port"},
    }
}

type Container

type Container = di.Container

Container is a type alias for di.Container. This allows provider functions to use *gaz.Container in their signatures.

func NewContainer

func NewContainer() *Container

NewContainer creates a new empty Container. For standalone DI usage, you may also use di.New() directly.

type CronJob

type CronJob = cron.CronJob

CronJob is a type alias for cron.CronJob for convenience. Users can import this from the root gaz package instead of gaz/cron.

type FieldError

type FieldError = config.FieldError

FieldError represents a single field validation failure. This is a type alias for config.FieldError.

func NewFieldError

func NewFieldError(namespace, tag, param, message string) FieldError

NewFieldError creates a new FieldError with the given parameters. This is a convenience wrapper for config.NewFieldError.

type HookConfig

type HookConfig = di.HookConfig

HookConfig holds configuration for lifecycle hooks.

type HookFunc

type HookFunc = di.HookFunc

HookFunc is a function that performs a lifecycle action.

type HookOption

type HookOption = di.HookOption

HookOption configures a lifecycle hook.

func WithHookTimeout

func WithHookTimeout(d time.Duration) HookOption

WithHookTimeout sets a custom timeout for this specific hook. If not set, the hook uses the App's default PerHookTimeout.

type LifecycleError

type LifecycleError struct {
	// ServiceName is the service that failed during lifecycle.
	ServiceName string

	// Phase is the lifecycle phase that failed: "start" or "stop".
	Phase string

	// Cause is the underlying error.
	Cause error
}

LifecycleError represents a failure during service start or stop. Use errors.As to extract which service failed and in which phase.

func (*LifecycleError) Error

func (e *LifecycleError) Error() string

Error implements the error interface.

func (*LifecycleError) Unwrap

func (e *LifecycleError) Unwrap() error

Unwrap returns the underlying cause.

type Module

type Module interface {
	// Name returns the module's identifier for debugging and error messages.
	Name() string

	// Apply applies the module's providers to the app.
	// Child modules are applied before the parent module's providers.
	Apply(app *App) error
}

Module represents a reusable bundle of providers. Modules can be composed using the Use() method to bundle child modules. When a module is applied, its child modules are applied first.

type ModuleBuilder

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

ModuleBuilder constructs Module instances via fluent API. Use NewModule(name) to start building a module.

Example:

module := gaz.NewModule("database").
    Provide(func(c *gaz.Container) error {
        return gaz.For[*DB](c).Provider(NewDB)
    }).
    Build()

app.Use(module)

func NewModule

func NewModule(name string) *ModuleBuilder

NewModule creates a new ModuleBuilder with the given name. The name is used for debugging, error messages, and duplicate detection.

Example:

module := gaz.NewModule("redis").
    Provide(RedisProvider).
    Build()

func (*ModuleBuilder) Build

func (b *ModuleBuilder) Build() Module

Build creates the final Module. After Build() is called, the ModuleBuilder should not be reused.

func (*ModuleBuilder) Flags

func (b *ModuleBuilder) Flags(fn func(*pflag.FlagSet)) *ModuleBuilder

Flags registers CLI flags for this module. The flags function receives a FlagSet to register module-specific flags. Flags should be namespaced by module name (e.g., "redis-host" not "host") to avoid collisions with other modules.

The flags function is called when the module is applied to an App that has a Cobra command attached (via WithCobra). If no Cobra command is attached, the flags function is not called.

Example:

module := gaz.NewModule("redis").
    Flags(func(fs *pflag.FlagSet) {
        fs.String("redis-host", "localhost", "Redis server host")
        fs.Int("redis-port", 6379, "Redis server port")
    }).
    Build()

func (*ModuleBuilder) Provide

func (b *ModuleBuilder) Provide(fns ...func(*Container) error) *ModuleBuilder

Provide adds provider functions to the module. Each function receives *Container and returns error. This method is chainable.

Example:

module := gaz.NewModule("http").
    Provide(
        func(c *gaz.Container) error { return gaz.For[*Router](c).Provider(NewRouter) },
        func(c *gaz.Container) error { return gaz.For[*Server](c).Provider(NewServer) },
    ).
    Build()

func (*ModuleBuilder) Use

func (b *ModuleBuilder) Use(m Module) *ModuleBuilder

Use bundles another module to be applied when this module is applied. Child modules are applied BEFORE this module's providers. This is for composition/bundling convenience, not dependency ordering (dependency ordering is handled by the DI container).

Example:

logging := gaz.NewModule("logging").Provide(LoggerProvider).Build()
metrics := gaz.NewModule("metrics").Provide(MetricsProvider).Build()

observability := gaz.NewModule("observability").
    Use(logging).
    Use(metrics).
    Build()

func (*ModuleBuilder) WithEnvPrefix

func (b *ModuleBuilder) WithEnvPrefix(prefix string) *ModuleBuilder

WithEnvPrefix sets the config key prefix for this module. When combined with service-level env prefix, the module prefix becomes a sub-prefix. For example:

Service prefix: "MYAPP_"
Module prefix:  "redis"
Config key:     "host"
Result:         "MYAPP_REDIS_HOST"

Example:

module := gaz.NewModule("redis").
    WithEnvPrefix("redis").
    Build()

type Option

type Option func(*App)

Option configures App settings.

func WithCobra

func WithCobra(cmd *cobra.Command) Option

WithCobra attaches the App lifecycle to a Cobra command. This is an Option passed to gaz.New() that hooks into: - PersistentPreRunE: applies stored flags, Build() and Start() the app - PersistentPostRunE: Stop() the app

The App is stored in the command's context, accessible via FromContext().

Existing hooks on the command are preserved and chained (not replaced).

Example:

rootCmd := &cobra.Command{Use: "myapp"}
app := gaz.New(gaz.WithCobra(rootCmd))
gaz.For[*Database](app.Container()).Provider(NewDatabase)

// In subcommand:
app := gaz.FromContext(cmd.Context())
db, _ := gaz.Resolve[*Database](app.Container())

func WithLoggerConfig

func WithLoggerConfig(cfg *logger.Config) Option

WithLoggerConfig sets the logger configuration.

func WithPerHookTimeout

func WithPerHookTimeout(d time.Duration) Option

WithPerHookTimeout sets the default timeout for each shutdown hook. Default is 10 seconds. Individual hooks can override via WithHookTimeout.

func WithShutdownTimeout

func WithShutdownTimeout(d time.Duration) Option

WithShutdownTimeout sets the timeout for graceful shutdown. Default is 30 seconds.

func WithStrictConfig

func WithStrictConfig() Option

WithStrictConfig enables strict configuration validation. If enabled, Build() fails if the config file contains any keys that are not mapped to fields in the config struct. This helps catch typos and obsolete configuration.

Strict validation is only applied when a config target is set via WithConfig(). It has no effect on ConfigProvider pattern.

type ProviderValues

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

ProviderValues provides access to provider-registered configuration values. Inject this via the DI container to retrieve config values by their full key.

Example:

func NewRedisClient(c *gaz.Container) (*RedisClient, error) {
    pv := gaz.MustResolve[*gaz.ProviderValues](c)
    host := pv.GetString("redis.host")
    port := pv.GetInt("redis.port")
    return &RedisClient{Host: host, Port: port}, nil
}

func (*ProviderValues) GetBool

func (pv *ProviderValues) GetBool(key string) bool

GetBool returns a bool config value by its full key.

func (*ProviderValues) GetDuration

func (pv *ProviderValues) GetDuration(key string) time.Duration

GetDuration returns a duration config value by its full key.

func (*ProviderValues) GetFloat64

func (pv *ProviderValues) GetFloat64(key string) float64

GetFloat64 returns a float64 config value by its full key.

func (*ProviderValues) GetInt

func (pv *ProviderValues) GetInt(key string) int

GetInt returns an int config value by its full key.

func (*ProviderValues) GetString

func (pv *ProviderValues) GetString(key string) string

GetString returns a string config value by its full key (e.g., "redis.host").

func (*ProviderValues) Unmarshal

func (pv *ProviderValues) Unmarshal(target any) error

Unmarshal unmarshals the entire config into target struct. Uses "gaz" struct tags for field mapping.

Example:

type AppConfig struct {
    Redis RedisConfig `gaz:"redis"`
    DB    DBConfig    `gaz:"database"`
}

var cfg AppConfig
if err := pv.Unmarshal(&cfg); err != nil {
    return err
}

func (*ProviderValues) UnmarshalKey

func (pv *ProviderValues) UnmarshalKey(key string, target any) error

UnmarshalKey unmarshals config at the given key/namespace into target struct. Uses "gaz" struct tags for field mapping. Returns config.ErrKeyNotFound if the key/namespace doesn't exist.

Example:

type RedisConfig struct {
    Host string `gaz:"host"`
    Port int    `gaz:"port"`
}

var cfg RedisConfig
if err := pv.UnmarshalKey("redis", &cfg); err != nil {
    if errors.Is(err, config.ErrKeyNotFound) {
        // Handle missing namespace
    }
    return err
}

type RegistrationBuilder

type RegistrationBuilder[T any] = di.RegistrationBuilder[T]

RegistrationBuilder provides a fluent API for configuring services.

type ResolutionError

type ResolutionError struct {
	// ServiceName is the service that failed to resolve.
	ServiceName string

	// Chain is the resolution chain leading to the failure.
	// For example: ["App", "UserService", "Database"] shows that
	// App depends on UserService which depends on Database which failed.
	Chain []string

	// Cause is the underlying error that caused the resolution to fail.
	Cause error
}

ResolutionError represents a DI resolution failure with context about which service failed and the resolution chain leading to the failure. Use errors.As to extract resolution context for debugging.

func (*ResolutionError) Error

func (e *ResolutionError) Error() string

Error implements the error interface.

func (*ResolutionError) Unwrap

func (e *ResolutionError) Unwrap() error

Unwrap returns the underlying cause, enabling errors.Is and errors.As to work through the error chain.

type ResolveOption

type ResolveOption = di.ResolveOption

ResolveOption modifies resolution behavior.

type ServiceWrapper

type ServiceWrapper = di.ServiceWrapper

ServiceWrapper is the interface for service lifecycle management.

type Starter

type Starter = di.Starter

Starter is an interface for services that need to perform action on startup. Implementing this interface is the sole mechanism for lifecycle participation. OnStart is called automatically after container Build() when the service is first instantiated. Hooks are called in dependency order: dependencies start first.

This interface is auto-detected by the DI container. No registration of lifecycle hooks is needed - simply implement the interface.

type Stopper

type Stopper = di.Stopper

Stopper is an interface for services that need to perform action on shutdown. Implementing this interface is the sole mechanism for lifecycle participation. OnStop is called automatically during graceful shutdown. Hooks are called in reverse dependency order: dependents stop first, then their dependencies.

This interface is auto-detected by the DI container. No registration of lifecycle hooks is needed - simply implement the interface.

type ValidationError

type ValidationError = config.ValidationError

ValidationError holds multiple validation errors from config struct validation. It implements the error interface and provides access to individual field errors. Use errors.Is(err, ErrConfigValidation) to check for validation errors.

This is a type alias for config.ValidationError, ensuring compatibility between gaz and config packages.

func NewValidationError

func NewValidationError(errs []FieldError) ValidationError

NewValidationError creates a ValidationError from a slice of FieldErrors. This is a convenience wrapper for config.NewValidationError.

type Worker

type Worker = worker.Worker

Worker is a background task that runs continuously. Alias for worker.Worker for convenience.

Directories

Path Synopsis
Package backoff provides exponential backoff algorithms for retry logic.
Package backoff provides exponential backoff algorithms for retry logic.
Package config provides standalone configuration management for Go applications.
Package config provides standalone configuration management for Go applications.
module
Package module provides a gaz.Module for configuring config loading via CLI flags.
Package module provides a gaz.Module for configuring config loading via CLI flags.
viper
Package viper provides a viper-based Backend implementation for the config package.
Package viper provides a viper-based Backend implementation for the config package.
Package cron provides scheduled task support for Go applications using cron/internal.
Package cron provides scheduled task support for Go applications using cron/internal.
internal
Package internal provides internal cron expression parsing and schedule calculation.
Package internal provides internal cron expression parsing and schedule calculation.
module
Package module provides the gaz.Module for cron integration.
Package module provides the gaz.Module for cron integration.
Package di provides a lightweight, type-safe dependency injection container.
Package di provides a lightweight, type-safe dependency injection container.
Package eventbus provides type-safe in-process pub/sub for Go applications.
Package eventbus provides type-safe in-process pub/sub for Go applications.
module
Package module provides the gaz.Module for eventbus integration.
Package module provides the gaz.Module for eventbus integration.
examples
background-workers command
Package main demonstrates background worker patterns with gaz.
Package main demonstrates background worker patterns with gaz.
basic command
Package main demonstrates the minimal usage of the gaz DI framework.
Package main demonstrates the minimal usage of the gaz DI framework.
cobra-cli command
Package main demonstrates Cobra CLI integration with gaz.
Package main demonstrates Cobra CLI integration with gaz.
config-loading command
Package main demonstrates the ConfigProvider pattern for flag-based configuration.
Package main demonstrates the ConfigProvider pattern for flag-based configuration.
discovery command
Package main demonstrates the discovery pattern using ResolveAll.
Package main demonstrates the discovery pattern using ResolveAll.
http-server command
Package main demonstrates an HTTP server with graceful shutdown and health checks.
Package main demonstrates an HTTP server with graceful shutdown and health checks.
lifecycle command
Package main demonstrates gaz lifecycle hooks (OnStart/OnStop).
Package main demonstrates gaz lifecycle hooks (OnStart/OnStop).
microservice command
Package main demonstrates a complete microservice with gaz.
Package main demonstrates a complete microservice with gaz.
modules command
Package main demonstrates organizing providers into modules.
Package main demonstrates organizing providers into modules.
vanguard command
Package main demonstrates the Vanguard unified server with gaz.
Package main demonstrates the Vanguard unified server with gaz.
Package gaztest provides test utilities for gaz applications.
Package gaztest provides test utilities for gaz applications.
Package health provides health check management for gaz applications.
Package health provides health check management for gaz applications.
checks
Package checks provides reusable health check implementations for common infrastructure dependencies.
Package checks provides reusable health check implementations for common infrastructure dependencies.
checks/disk
Package disk provides a health check for disk space using gopsutil.
Package disk provides a health check for disk space using gopsutil.
checks/dns
Package dns provides a health check for DNS hostname resolution.
Package dns provides a health check for DNS hostname resolution.
checks/http
Package httpcheck provides a health check for HTTP upstream services.
Package httpcheck provides a health check for HTTP upstream services.
checks/pgx
Package pgx provides a health check for PostgreSQL databases using pgx/v5.
Package pgx provides a health check for PostgreSQL databases using pgx/v5.
checks/redis
Package redis provides a health check for Redis/Valkey using valkey-go.
Package redis provides a health check for Redis/Valkey using valkey-go.
checks/runtime
Package runtime provides health checks based on Go runtime metrics.
Package runtime provides health checks based on Go runtime metrics.
checks/sql
Package sql provides a health check for SQL databases using database/sql.
Package sql provides a health check for SQL databases using database/sql.
checks/tcp
Package tcp provides a health check for TCP port connectivity.
Package tcp provides a health check for TCP port connectivity.
internal
Package internal provides internal health check execution with parallel processing.
Package internal provides internal health check execution with parallel processing.
module
Package module provides the gaz.Module for health configuration with CLI flags.
Package module provides the gaz.Module for health configuration with CLI flags.
Package logger provides structured logging with context support.
Package logger provides structured logging with context support.
module
Package module provides a gaz.Module for configuring the logger via CLI flags.
Package module provides a gaz.Module for configuring the logger via CLI flags.
tint
Package tint provides a colored console handler for slog.
Package tint provides a colored console handler for slog.
Package server provides a unified transport layer for gaz applications, combining gRPC and Vanguard on a single port with lifecycle management.
Package server provides a unified transport layer for gaz applications, combining gRPC and Vanguard on a single port with lifecycle management.
connect
Package connect provides the Registrar interface for auto-discovery of Connect-Go services and the InterceptorBundle interface for auto-discovered interceptor chains within the gaz framework.
Package connect provides the Registrar interface for auto-discovery of Connect-Go services and the InterceptorBundle interface for auto-discovered interceptor chains within the gaz framework.
grpc
Package grpc provides a production-ready gRPC server with auto-discovery, interceptors, and lifecycle integration for the gaz framework.
Package grpc provides a production-ready gRPC server with auto-discovery, interceptors, and lifecycle integration for the gaz framework.
http
Package http provides a production-ready HTTP server with DI integration.
Package http provides a production-ready HTTP server with DI integration.
otel
Package otel provides OpenTelemetry integration for the gaz framework.
Package otel provides OpenTelemetry integration for the gaz framework.
vanguard
Package vanguard provides a unified server that serves gRPC, Connect, gRPC-Web, and REST protocols on a single port via Vanguard transcoder with h2c support.
Package vanguard provides a unified server that serves gRPC, Connect, gRPC-Web, and REST protocols on a single port via Vanguard transcoder with h2c support.
Package worker provides background worker lifecycle management for Go applications.
Package worker provides background worker lifecycle management for Go applications.
module
Package module provides the gaz.Module for worker integration.
Package module provides the gaz.Module for worker integration.

Jump to

Keyboard shortcuts

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