Component is a lightweight library for declaring, validating, and orchestrating stateful application components (databases, queues, servers, workers).
The API is split into three explicit phases:
Registry: declare constructors and dependencies.Plan: compile and validate a deterministic dependency graph.Runtime: execute lifecycle start/stop.
- Type-safe component keys with Go generics.
- Explicit dependency graph with cycle and missing-dependency validation.
- Parallel
Start/Stopwithin the same dependency level. - Reverse-order shutdown by dependency level.
- Rollback on startup failure.
- Panic capture with contextual errors.
go get github.com/jacoelho/componentpackage main
import (
"context"
"fmt"
"log"
"time"
"github.com/jacoelho/component"
)
type Logger struct{}
func (l *Logger) Start(context.Context) error { fmt.Println("logger started"); return nil }
func (l *Logger) Stop(context.Context) error { fmt.Println("logger stopped"); return nil }
type Service struct {
logger *Logger
}
func (s *Service) Start(context.Context) error { fmt.Println("service started"); return nil }
func (s *Service) Stop(context.Context) error { fmt.Println("service stopped"); return nil }
func main() {
reg := component.NewRegistry()
loggerKey := component.NewKey[*Logger]("default")
serviceKey := component.NewKey[*Service]("main")
if err := component.Provide(reg, loggerKey, func(_ *component.Runtime) (*Logger, error) {
return &Logger{}, nil
}); err != nil {
log.Fatal(err)
}
if err := component.Provide(reg, serviceKey, func(rt *component.Runtime) (*Service, error) {
logger, err := component.Get(rt, loggerKey)
if err != nil {
return nil, err
}
return &Service{logger: logger}, nil
}, loggerKey); err != nil {
log.Fatal(err)
}
plan, err := reg.Compile()
if err != nil {
log.Fatal(err)
}
rt := plan.NewRuntime()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := rt.Start(ctx); err != nil {
log.Fatal(err)
}
defer rt.Stop(ctx)
}See the runnable sample in example/main.go.
Registry stores component declarations only.
reg := component.NewRegistry()
err := component.Provide(reg, key, constructor, deps...)Compile validates and freezes the dependency graph.
plan, err := reg.Compile()Validation includes:
- Duplicate registration checks.
- Cycle detection.
- Missing dependency detection.
A Runtime is created from a compiled plan.
rt := plan.NewRuntime()
err := rt.Start(ctx)
err = rt.Stop(ctx)During constructors, dependencies can be resolved with:
dep, err := component.Get(rt, depKey)- Components start by level (
0 -> N). - Components in the same level start concurrently.
- On failure, already-started components are rolled back.
- Components stop in reverse level order (
N -> 0). - Components in the same level stop concurrently.
- Stop attempts continue even if some components fail.
A compiled plan can be exported to Graphviz DOT:
dot := plan.DotGraph()Example:
digraph G {
rankdir=TB;
compound=true;
subgraph cluster_0 {
label="Level 0";
style=dashed;
"*main.Logger(default)";
}
subgraph cluster_1 {
label="Level 1";
style=dashed;
"*main.Service(main)";
}
"*main.Logger(default)" -> "*main.Service(main)";
}make test
make staticcheckMIT