GitHub - NVIDIA/gontainer: Simple but powerful dependency injection container for Go projects! (original) (raw)
Gontainer
Simple but powerful dependency injection container for Go projects!
Features
- 🎯 Automatic dependency injection based on function signatures.
- ✨ Super simple interface to register and run services.
- 🚀 Lazy service creation only when actually needed.
- 🔄 Lifecycle management with proper cleanup in reverse order.
- 🤖 Clean and tested implementation using reflection and generics.
- 🧩 No external packages, no code generation, zero dependencies.
Quick Start
The example shows how to build the simplest app using service container.
package main
import ( "context" "log" "github.com/NVIDIA/gontainer/v2" )
// Your services. type Database struct{ connString string } type UserService struct{ db *Database }
func main() { err := gontainer.Run( context.Background(),
// Register Database.
gontainer.NewFactory(func() *Database {
return &Database{connString: "postgres://localhost/myapp"}
}),
// Register UserService - Database is auto-injected!
gontainer.NewFactory(func(db *Database) *UserService {
return &UserService{db: db}
}),
// Use your services.
gontainer.NewEntrypoint(func(users *UserService) {
log.Printf("UserService ready with DB: %s", users.db)
}),
)
if err != nil {
log.Fatal(err)
}}
Examples
- Console command example – demonstrates how to build a simple console command.
12:51:32 Executing service container
12:51:32 Hello from the Hello Service Bob
12:51:32 Service container executed - Daemon service example – demonstrates how to maintain background services.
12:48:22 Executing service container
12:48:22 Starting listening on: http://127.0.0.1:8080
12:48:22 Starting serving HTTP requests
------ Application was started and now accepts HTTP requests -------------
------ CTRL+C was pressed or a TERM signal was sent to the process -------
12:48:28 Exiting from serving by signal
12:48:28 Service container executed - Complete webapp example – demonstrates how to organize web application with multiple services.
15:19:48 INFO msg="Starting service container" service=logger
15:19:48 INFO msg="Configuring app endpoints" service=app
15:19:48 INFO msg="Configuring health endpoints" service=app
15:19:48 INFO msg="Starting HTTP server" service=http address=127.0.0.1:8080
------ Application was started and now accepts HTTP requests -------------
15:19:54 INFO msg="Serving home page" service=app remote-addr=127.0.0.1:62640
15:20:01 INFO msg="Serving health check" service=app remote-addr=127.0.0.1:62640
------ CTRL+C was pressed or a TERM signal was sent to the process -------
15:20:04 INFO msg="Terminating by signal" service=app
15:20:04 INFO msg="Closing HTTP server" service=http - Transient service example – demonstrates how to return a function that can be called multiple times to produce transient services.
11:19:22 Executing service container
11:19:22 New value: 8767488676555705225
11:19:22 New value: 5813207273458254863
11:19:22 New value: 750077227530805093
11:19:22 Service container executed Installation
go get github.com/NVIDIA/gontainer/v2
Requirements: Go 1.21+
Core Concepts
1. Define Services
Services are just regular Go types:
type EmailService struct { smtp string }
func (s *EmailService) SendWelcome(email string) error { log.Printf("Sending welcome email to %s via %s", email, s.smtp) return nil }
2. Register Factories
Factories create your services. Dependencies are declared as function parameters:
// Simple factory. gontainer.NewFactory(func() *EmailService { return &EmailService{smtp: "smtp.gmail.com"} })
// Factory with dependencies - auto-injected! gontainer.NewFactory(func(config *Config, logger *Logger) *EmailService { logger.Info("Creating email service") return &EmailService{smtp: config.SMTPHost} })
// Factory with a cleanup callback. gontainer.NewFactory(func() (*Database, func() error) { db, _ := sql.Open("postgres", "...")
return db, func() error {
log.Println("Closing database")
return db.Close()
}})
3. Run Container
err := gontainer.Run( context.Background(), gontainer.NewFactory(...), gontainer.NewFactory(...), gontainer.NewEntrypoint(func(/* dependencies */) { // application entry point }), )
Advanced Features
Resource Cleanup
Return a cleanup function from your factory to handle graceful shutdown:
gontainer.NewFactory(func() (*Server, func() error) { server := &http.Server{Addr: ":8080"} go server.ListenAndServe()
// Cleanup function called on container shutdown.
return server, func() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return server.Shutdown(ctx)
}})
Optional Dependencies
Use when a service might not be registered:
gontainer.NewFactory(func(metrics gontainer.Optional[*MetricsService]) *API { api := &API{}
// Use metrics if available
if m := metrics.Get(); m != nil {
api.metrics = m
}
return api})
Multiple Dependencies
Get all services implementing an interface:
type Middleware interface { Process(http.Handler) http.Handler }
gontainer.NewFactory(func(middlewares gontainer.Multiple[Middleware]) *Router { router := &Router{} for _, mw := range middlewares { router.Use(mw) } return router })
Dynamic Resolution
Resolve services on-demand:
gontainer.NewEntrypoint(func(resolver *gontainer.Resolver) error { // Resolve service dynamically. var userService *UserService if err := resolver.Resolve(&userService); err != nil { return err }
return userService.DoWork()})
Transient Services
Create new instances on each call:
// Factory returns a function that creates new instances. gontainer.NewFactory(func(db *Database) func() *Transaction { return func() *Transaction { return &Transaction{ id: uuid.New(), db: db, } } })
// Use the factory function. gontainer.NewEntrypoint(func(newTx func() *Transaction) { tx1 := newTx() // new instance tx2 := newTx() // another new instance })
API Reference
Module Functions
Gontainer module interface is really simple:
// Run creates and runs a container with provided options. func Run(ctx context.Context, opts ...Option) error
// NewFactory registers a service factory. func NewFactory(fn any) Option
// NewService registers a pre-created service. func NewService[T any](service T) Option
// NewEntrypoint registers an entrypoint function. func NewEntrypoint(fn any) Option
Factory Signatures
Factory is a function that creates one service. It can have dependencies as parameters, and can optionally return an error and/or a cleanup function for the factory.
Dependencies are other services that the factory needs which are automatically injected.
Service is a user-provided type. It can be any type except untyped any, context and error.
// The simplest factory. func() *Service
// Factory with dependencies. func(dep1 *Dep1, dep2 *Dep2) *Service
// Factory with error. func() (*Service, error)
// Factory with cleanup. func() (*Service, func() error)
// Factory with cleanup and error. func() (*Service, func() error, error)
Built-in Services
Gontainer provides several built-in services that can be injected into factories and functions. They provide access to container features like context, dynamic resolution, and invocation.
// context.Context - The factory's context. func(ctx context.Context) *Service
// *gontainer.Resolver - Dynamic service resolution. func(resolver *gontainer.Resolver) *Service
// *gontainer.Invoker - Dynamic function invocation. func(invoker *gontainer.Invoker) *Service
Special Types
Gontainer provides special types for optional and multiple dependencies.
// Optional[T] - Optional dependency declaration. type Optional[T any] struct{} func (o Optional[T]) Get() T
// Multiple[T] - Multiple services of the same interface. type Multiple[T any] []T
Error Handling
Gontainer provides typed errors for different failure scenarios:
err := gontainer.Run(ctx, factories...)
switch { case errors.Is(err, gontainer.ErrFactoryReturnedError): // Factory returned an error. case errors.Is(err, gontainer.ErrEntrypointReturnedError): // Entrypoint returned an error. case errors.Is(err, gontainer.ErrNoEntrypointsProvided): // No entrypoints were provided. case errors.Is(err, gontainer.ErrCircularDependency): // Circular dependency detected. case errors.Is(err, gontainer.ErrDependencyNotResolved): // Service type not registered. case errors.Is(err, gontainer.ErrFactoryTypeDuplicated): // Service type was duplicated. }
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
License
Apache 2.0 – See LICENSE for details.
Documentation for v1
Documentation for the previous major version v1 is available at v1 branch.
