go-bootstrapify
A lightweight Go library for bootstrapping services and CLI applications with a modular architecture, dependency management, and lifecycle orchestration.
Features
- Module System: Define reusable modules with clear lifecycle hooks (Configure, Start, Shutdown)
- Dependency Management: Automatic topological sorting of modules based on dependencies
- Circular Dependency Detection: Prevents invalid module configurations at startup
- Graceful Shutdown: Handles SIGTERM, SIGINT, and SIGHUP with proper cleanup in reverse order
- Logger Integration: Built-in support for zerolog with per-module logging
- RunModule Helper: Quick module creation with lifecycle hooks without full Module implementation
- Type-Safe Configuration: Functional options pattern for clean, composable configuration
Installation
go get gitlab.com/bitval/go-bootstrapify
Quick Start
Basic Service
package main
import (
"github.com/rs/zerolog"
"gitlab.com/bitval/go-bootstrapify"
)
func main() {
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
service := bootstrapify.NewService(
bootstrapify.Config{Logger: &logger},
bootstrapify.Run("startup",
bootstrapify.RunAfterConfigure(func(m *bootstrapify.RunModule) error {
m.Logger().Info().Msg("Service configured")
return nil
}),
bootstrapify.RunAfterStart(func(m *bootstrapify.RunModule) error {
m.Logger().Info().Msg("Service started")
return nil
}),
bootstrapify.RunAfterShutdown(func(m *bootstrapify.RunModule) error {
m.Logger().Info().Msg("Service shutting down")
return nil
}),
),
)
if err := service.Start(); err != nil {
logger.Fatal().Err(err).Msg("Service failed")
}
}
Module with Dependencies
type DatabaseModule struct {
modules.Base
db *sql.DB
}
func (m *DatabaseModule) Configure(opts ...modules.Option) error {
// Apply options (logger, etc.)
for _, opt := range opts {
if err := opt.Apply(m); err != nil {
return err
}
}
// Initialize database
var err error
m.db, err = sql.Open("postgres", "...")
return err
}
func (m *DatabaseModule) Start() error {
return m.db.Ping()
}
func (m *DatabaseModule) Shutdown() error {
return m.db.Close()
}
type APIModule struct {
modules.Base
server *http.Server
}
func (m *APIModule) Configure(opts ...modules.Option) error {
for _, opt := range opts {
if err := opt.Apply(m); err != nil {
return err
}
}
// Access database module
db := m.Dependency("database").(*DatabaseModule)
m.server = &http.Server{Addr: ":8080"}
return nil
}
func (m *APIModule) Start() error {
go m.server.ListenAndServe()
return nil
}
func (m *APIModule) Shutdown() error {
return m.server.Close()
}
func main() {
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
dbModule := &DatabaseModule{}
dbModule.SetName("database")
apiModule := &APIModule{}
apiModule.SetName("api")
apiModule.AddDependencies("database") // API depends on database
service := bootstrapify.NewService(
bootstrapify.Config{Logger: &logger},
bootstrapify.Module(dbModule),
bootstrapify.Module(apiModule),
)
if err := service.Start(); err != nil {
logger.Fatal().Err(err).Msg("Service failed")
}
}
Module Lifecycle
Modules follow a three-phase lifecycle:
- Configure: Initialize resources, load configuration, set up dependencies
- Start: Begin active operations (start servers, open connections, etc.)
- Shutdown: Clean up resources in reverse dependency order
The Runtime automatically:
- Orders modules based on dependencies using topological sort
- Detects circular dependencies at startup
- Calls Configure → Start in dependency order
- Calls Shutdown in reverse dependency order
- Propagates errors with context
Architecture
Module Interface
type Module interface {
Configure(...Option) error
Start() error
Shutdown() error
SetName(string)
Name() string
AddDependencies(...string)
SetDependency(Module)
Dependency(string) Module
Dependencies() map[string]Module
SetLogger(*zerolog.Logger)
Logger() *zerolog.Logger
Log(zerolog.Level, string, ...any)
}
Base Module
The modules.Base struct provides default implementations for common Module interface methods. Embed it in your custom modules:
type MyModule struct {
modules.Base
// your fields
}
RunModule
For simple modules, use RunModule instead of implementing the full Module interface:
bootstrapify.Run("mymodule",
bootstrapify.RunAfter("database"), // Dependencies
bootstrapify.RunAfterConfigure(func(m *bootstrapify.RunModule) error {
// Configuration logic
return nil
}),
bootstrapify.RunAfterStart(func(m *bootstrapify.RunModule) error {
// Startup logic
return nil
}),
bootstrapify.RunAfterShutdown(func(m *bootstrapify.RunModule) error {
// Cleanup logic
return nil
}),
)
Status
⚠️ Early Alpha - API may change. Not recommended for production use yet.
License
MIT License - Copyright (c) 2025 Tomaz Lovrec
Contributing
This project was extracted from go-auth-api. Contributions welcome!