Loading...
Loading...
Golang application framework using uber-go/fx — fx.New, fx.Provide, fx.Invoke, fx.Module, fx.Lifecycle hooks, fx.Annotate (name/group/As), fx.Decorate, fx.Supply, fx.Replace, fx.WithLogger, and signal-aware Run(). Apply when using or adopting uber-go/fx, when the codebase imports `go.uber.org/fx`, or when wiring services with fx.New. For raw DI without lifecycle, see `samber/cc-skills-golang@golang-uber-dig` skill.
npx skill4agent add samber/cc-skills-golang golang-uber-fxinit()uber-go/diggo get go.uber.org/fxfx vs. dig. fx wraps dig and adds lifecycle hooks (), modules (fx.Lifecycle),fx.Modulewith signal handling, structured event logs (Run()), and ergonomic helpers. Use raw dig (fxeventskill) only when you don't need lifecycle or app boot — most production services should use fx.samber/cc-skills-golang@golang-uber-dig
import "go.uber.org/fx"
app := fx.New(
fx.Provide(NewLogger, NewDatabase, NewServer),
fx.Invoke(RegisterRoutes),
)
app.Run() // blocks until SIGINT/SIGTERM, then runs OnStop hooksfx.Newapp.Start(ctx)fx.Invokeapp.Done()app.Stop(ctx)fx.StartTimeoutfx.StopTimeoutfx.New(
fx.Provide(NewLogger, NewDatabase, NewServer), // lazy
fx.Invoke(RegisterRoutes, StartMetricsExporter), // always run during Start
)fx.Providefx.Invokefx.LifecycleOnStartfunc NewHTTPServer(lc fx.Lifecycle, log *zap.Logger, cfg *Config) *http.Server {
srv := &http.Server{Addr: cfg.Addr}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
ln, err := net.Listen("tcp", srv.Addr)
if err != nil { return err }
go srv.Serve(ln) // blocking work in a goroutine
return nil
},
OnStop: func(ctx context.Context) error {
return srv.Shutdown(ctx)
},
})
return srv
}StartTimeoutStopTimeoutfx.StartHookfx.StopHookfx.StartStopHooklc.Append(fx.StartStopHook(srv.Start, srv.Stop)) // matched pairdig.Indig.Outfx.Infx.Outnamegroupoptionaltype ServerParams struct {
fx.In
Logger *zap.Logger
DB *sql.DB
Cache *redis.Client `optional:"true"`
Routes []http.Handler `group:"routes"`
}
func NewServer(p ServerParams) *Server { /* ... */ }fx.Annotatefx.Outfx.Provide(
fx.Annotate(NewPrimaryDB, fx.ResultTags(`name:"primary"`)),
fx.Annotate(NewPostgresDB, fx.As(new(Database))), // expose interface
fx.Annotate(NewUserHandler,
fx.As(new(http.Handler)),
fx.ResultTags(`group:"routes"`),
),
)type RouteResult struct {
fx.Out
Handler http.Handler `group:"routes"`
}
type ServerParams struct {
fx.In
Routes []http.Handler `group:"routes"`
},flattengroup:"routes,flatten"fx.Modulefx.Module("db", ...)var DatabaseModule = fx.Module("database",
fx.Provide(NewConnection, NewUserRepository),
fx.Decorate(func(log *zap.Logger) *zap.Logger {
return log.Named("db")
}),
)
func main() {
fx.New(
fx.Provide(NewConfig, NewLogger),
DatabaseModule,
HTTPModule,
).Run()
}fx.Supplyfx.Replacefx.Decoratemain()Run()init()init()ctx.Done()fx.Annotatefx.Outfx.Providefx.Supplyfx.New(...).Err()| Mistake | Fix |
|---|---|
| Long-running work directly in OnStart | Spawn a goroutine inside OnStart; the hook itself must return quickly so dependent hooks can run. |
| Pre-built values (config, secrets) belong in |
| Module decorator leaking to siblings | Decorate inside |
| Group order assumed | Groups are unordered. If order matters, provide an ordered slice from one constructor. |
| Constructors with side effects | Side effects belong in OnStart — constructors should be cheap and pure-ish, since they may run concurrently and lazily. |
Forgotten | Without an Invoke (or downstream consumer), constructors never run. Add at least one Invoke per app. |
go.uber.org/fx/fxtest*testing.Tt.FatalRequireStopt.Cleanupfx.Populate(&target)fx.Replacefx.Replacefx.Populatesamber/cc-skills-golang@golang-uber-digdig.Indig.Outsamber/cc-skills-golang@golang-dependency-injectionsamber/cc-skills-golang@golang-samber-dosamber/cc-skills-golang@golang-google-wiresamber/cc-skills-golang@golang-structs-interfacessamber/cc-skills-golang@golang-contextsamber/cc-skills-golang@golang-testing