GitHub - NVIDIA/gontainer: Simple but powerful dependency injection container for Go projects! (original) (raw)

License GoDoc Test Report

Gontainer

Simple but powerful dependency injection container for Go projects!

Features

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

12:51:32 Executing service container  
12:51:32 Hello from the Hello Service Bob  
12:51:32 Service container executed  
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  
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  
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.