di

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2025 License: MIT Imports: 11 Imported by: 0

README

Dependency Injection Package

The di package provides a container-based dependency injection system for Go applications. It helps manage dependencies between components, making your code more modular, testable, and maintainable.

Features

  • Container Types:

    • Base container
    • Service container
    • Repository container
    • Generic container
  • Features:

    • Constructor injection
    • Singleton instances
    • Lazy initialization
    • Scoped instances
    • Circular dependency detection

Installation

go get github.com/abitofhelp/servicelib/di

Usage

Basic Container Usage
package main

import (
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

func main() {
    // Create a new DI container
    container := di.NewContainer()

    // Register a simple value
    container.Register("greeting", "Hello, World!")

    // Register a function that returns a value
    container.Register("counter", func() int {
        return 42
    })

    // Retrieve values from the container
    greeting, err := container.Get("greeting")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    counter, err := container.Get("counter")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Println(greeting.(string))  // Output: Hello, World!
    fmt.Println(counter.(int))      // Output: 42
}
Dependency Resolution
package main

import (
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

// Define some interfaces and implementations
type Logger interface {
    Log(message string)
}

type ConsoleLogger struct{}

func (l *ConsoleLogger) Log(message string) {
    fmt.Println("LOG:", message)
}

type Service interface {
    DoSomething()
}

type MyService struct {
    logger Logger
}

func NewMyService(logger Logger) Service {
    return &MyService{logger: logger}
}

func (s *MyService) DoSomething() {
    s.logger.Log("Doing something...")
}

func main() {
    // Create a new DI container
    container := di.NewContainer()

    // Register the logger
    container.Register("logger", func(c di.Container) (interface{}, error) {
        return &ConsoleLogger{}, nil
    })

    // Register the service with a dependency on the logger
    container.Register("service", func(c di.Container) (interface{}, error) {
        // Resolve the logger dependency
        logger, err := c.Get("logger")
        if err != nil {
            return nil, err
        }

        // Create and return the service
        return NewMyService(logger.(Logger)), nil
    })

    // Resolve and use the service
    service, err := container.Get("service")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    // Use the service
    service.(Service).DoSomething()  // Output: LOG: Doing something...
}
Singleton vs. Transient Instances
package main

import (
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c *Counter) Value() int {
    return c.value
}

func main() {
    // Create a new DI container
    container := di.NewContainer()

    // Register a singleton counter
    container.RegisterSingleton("singletonCounter", func(c di.Container) (interface{}, error) {
        return &Counter{}, nil
    })

    // Register a transient counter
    container.Register("transientCounter", func(c di.Container) (interface{}, error) {
        return &Counter{}, nil
    })

    // Get the singleton counter twice and increment it
    counter1, _ := container.Get("singletonCounter")
    counter1.(*Counter).Increment()

    counter2, _ := container.Get("singletonCounter")
    fmt.Printf("Singleton counter value: %d\n", counter2.(*Counter).Value())  // Output: 1 (shared instance)

    // Get the transient counter twice and increment it
    counter3, _ := container.Get("transientCounter")
    counter3.(*Counter).Increment()

    counter4, _ := container.Get("transientCounter")
    fmt.Printf("Transient counter value: %d\n", counter4.(*Counter).Value())  // Output: 0 (new instance)
}
Scoped Containers
package main

import (
    "context"
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

type RequestContext struct {
    UserID string
}

type UserService struct {
    requestContext *RequestContext
}

func (s *UserService) GetUserID() string {
    return s.requestContext.UserID
}

func main() {
    // Create a parent container
    parentContainer := di.NewContainer()

    // Handle a request for user1
    handleRequest(parentContainer, "user1")

    // Handle a request for user2
    handleRequest(parentContainer, "user2")
}

func handleRequest(parentContainer di.Container, userID string) {
    // Create a scoped container for this request
    scopedContainer := parentContainer.CreateScope()

    // Register request-specific dependencies
    scopedContainer.Register("requestContext", &RequestContext{UserID: userID})

    // Register a service that depends on the request context
    scopedContainer.Register("userService", func(c di.Container) (interface{}, error) {
        ctx, err := c.Get("requestContext")
        if err != nil {
            return nil, err
        }
        return &UserService{requestContext: ctx.(*RequestContext)}, nil
    })

    // Resolve and use the service
    service, _ := scopedContainer.Get("userService")
    userService := service.(*UserService)

    fmt.Printf("Request for user: %s\n", userService.GetUserID())
}
Using with Context
package main

import (
    "context"
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

func main() {
    // Create a base context
    ctx := context.Background()

    // Create a container with context
    container, err := di.NewContainerWithContext(ctx)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    // Register a service that uses the context
    container.Register("contextAwareService", func(c di.Container) (interface{}, error) {
        // Get the context from the container
        ctx := c.GetContext()
        
        // Use the context
        return &ContextAwareService{ctx: ctx}, nil
    })

    // Use the service
    service, _ := container.Get("contextAwareService")
    service.(*ContextAwareService).DoSomething()
}

type ContextAwareService struct {
    ctx context.Context
}

func (s *ContextAwareService) DoSomething() {
    // Use the context for cancellation, timeouts, etc.
    select {
    case <-s.ctx.Done():
        fmt.Println("Context cancelled")
    default:
        fmt.Println("Context is still valid")
    }
}

Advanced Usage

Circular Dependency Detection

The DI container automatically detects circular dependencies:

package main

import (
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

func main() {
    container := di.NewContainer()

    // Register service A which depends on service B
    container.Register("serviceA", func(c di.Container) (interface{}, error) {
        b, err := c.Get("serviceB")
        if err != nil {
            return nil, err
        }
        return &ServiceA{b: b.(*ServiceB)}, nil
    })

    // Register service B which depends on service A
    container.Register("serviceB", func(c di.Container) (interface{}, error) {
        a, err := c.Get("serviceA")
        if err != nil {
            return nil, err
        }
        return &ServiceB{a: a.(*ServiceA)}, nil
    })

    // This will result in a circular dependency error
    _, err := container.Get("serviceA")
    fmt.Printf("Error: %v\n", err)
    // Output: Error: circular dependency detected: serviceA -> serviceB -> serviceA
}

type ServiceA struct {
    b *ServiceB
}

type ServiceB struct {
    a *ServiceA
}
Lazy Initialization
package main

import (
    "fmt"
    "github.com/abitofhelp/servicelib/di"
)

func main() {
    container := di.NewContainer()

    // Register a service that will be initialized lazily
    container.RegisterLazy("expensiveService", func(c di.Container) (interface{}, error) {
        fmt.Println("Initializing expensive service...")
        // Simulate expensive initialization
        return &ExpensiveService{}, nil
    })

    fmt.Println("Container created, but expensive service not yet initialized")

    // Service is only initialized when requested
    service, _ := container.Get("expensiveService")
    fmt.Println("Service retrieved:", service != nil)
}

type ExpensiveService struct{}

Best Practices

  1. Use Interfaces: Register interfaces rather than concrete types to make your code more flexible and testable.

  2. Singleton vs. Transient: Use singletons for stateless services and transient instances for stateful ones.

  3. Scoped Containers: Use scoped containers for request-specific dependencies.

  4. Avoid Service Locator Pattern: Inject dependencies directly rather than passing the container around.

  5. Constructor Injection: Prefer constructor injection over property or method injection.

  6. Circular Dependencies: Avoid circular dependencies by redesigning your components.

  7. Testing: Use the DI container to easily mock dependencies in tests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package di provides a generic dependency injection container that can be used across different applications.

Package di provides a generic dependency injection container that can be used across different applications.

Package di provides generic database initializers that can be used across different applications.

Copyright (c) 2025 A Bit of Help, Inc.

Package di provides a generic dependency injection container that can be used across different applications.

Package di provides a generic dependency injection container that can be used across different applications.

Package di provides generic repository interfaces that can be used across different applications.

Package di provides a generic dependency injection container that can be used across different applications.

Package di provides repository initializers for different database types.

Package di provides a generic dependency injection container that can be used across different applications.

Index

Constants

View Source
const DefaultTimeout = 30 * time.Second

Default timeout for database operations

Variables

This section is empty.

Functions

func GenericMongoInitializer

func GenericMongoInitializer(
	ctx context.Context,
	uri string,
	databaseName string,
	collectionName string,
	zapLogger *zap.Logger,
) (interface{}, error)

GenericMongoInitializer initializes a MongoDB collection and returns it This can be used by applications to create their own repository initializers

func GenericPostgresInitializer

func GenericPostgresInitializer(
	ctx context.Context,
	dsn string,
	zapLogger *zap.Logger,
) (interface{}, error)

GenericPostgresInitializer initializes a PostgreSQL connection pool and returns it This can be used by applications to create their own repository initializers

func GenericSQLiteInitializer

func GenericSQLiteInitializer(
	ctx context.Context,
	uri string,
	zapLogger *zap.Logger,
) (interface{}, error)

GenericSQLiteInitializer initializes a SQLite database connection and returns it This can be used by applications to create their own repository initializers

func MongoDBInitializer

func MongoDBInitializer(
	ctx context.Context,
	uri string,
	databaseName string,
	collectionName string,
	logger *zap.Logger,
) (*mongo.Collection, error)

MongoDBInitializer initializes a MongoDB client and collection

func PostgresInitializer

func PostgresInitializer(
	ctx context.Context,
	dsn string,
	logger *zap.Logger,
) (*pgxpool.Pool, error)

PostgresInitializer initializes a PostgreSQL connection pool

func SQLiteInitializer

func SQLiteInitializer(
	ctx context.Context,
	uri string,
	logger *zap.Logger,
) (*sql.DB, error)

SQLiteInitializer initializes a SQLite database connection

Types

type AppRepositoryInitializer

type AppRepositoryInitializer[T any] func(ctx context.Context, connectionString string, logger *zap.Logger) (T, error)

AppRepositoryInitializer is a function type that initializes a repository

type ApplicationService

type ApplicationService interface {
	// GetID returns the application service ID
	GetID() string
}

ApplicationService is a generic interface for application services

type ApplicationServiceInitializerFunc

type ApplicationServiceInitializerFunc[R Repository, D DomainService, A ApplicationService] func(
	domainService D,
	repository R,
) (A, error)

ApplicationServiceInitializerFunc is a function type that initializes an application service

type BaseContainer

type BaseContainer[C any] struct {
	// contains filtered or unexported fields
}

BaseContainer is a generic dependency injection container that can be embedded in other containers

func NewBaseContainer

func NewBaseContainer[C any](ctx context.Context, logger *zap.Logger, cfg C) (*BaseContainer[C], error)

NewBaseContainer creates a new base dependency injection container

func (*BaseContainer[C]) Close

func (c *BaseContainer[C]) Close() error

Close closes all resources

func (*BaseContainer[C]) GetConfig

func (c *BaseContainer[C]) GetConfig() C

GetConfig returns the configuration

func (*BaseContainer[C]) GetContext

func (c *BaseContainer[C]) GetContext() context.Context

GetContext returns the context

func (*BaseContainer[C]) GetContextLogger

func (c *BaseContainer[C]) GetContextLogger() *logging.ContextLogger

GetContextLogger returns the context logger

func (*BaseContainer[C]) GetLogger

func (c *BaseContainer[C]) GetLogger() *zap.Logger

GetLogger returns the logger

func (*BaseContainer[C]) GetValidator

func (c *BaseContainer[C]) GetValidator() *validator.Validate

GetValidator returns the validator

type Container

type Container struct {
	*BaseContainer[interface{}]
}

Container is a generic dependency injection container for backward compatibility It uses the BaseContainer with an interface{} config type

func NewContainer

func NewContainer(ctx context.Context, logger *zap.Logger, cfg interface{}) (*Container, error)

NewContainer creates a new generic dependency injection container This function is kept for backward compatibility

func (*Container) GetRepositoryFactory

func (c *Container) GetRepositoryFactory() interface{}

GetRepositoryFactory returns the repository factory This is a placeholder method that should be overridden by derived containers

type DomainService

type DomainService interface {
	// GetID returns the domain service ID
	GetID() string
}

DomainService is a generic interface for domain services

type DomainServiceInitializerFunc

type DomainServiceInitializerFunc[R Repository, D DomainService] func(repository R) (D, error)

DomainServiceInitializerFunc is a function type that initializes a domain service

type GenericAppContainer

type GenericAppContainer[R any, D any, A any, C any] struct {
	*BaseContainer[C]
	// contains filtered or unexported fields
}

GenericAppContainer is a generic dependency injection container for any application

func NewGenericAppContainer

func NewGenericAppContainer[R any, D any, A any, C any](
	ctx context.Context,
	logger *zap.Logger,
	cfg C,
	connectionString string,
	initRepo AppRepositoryInitializer[R],
	initDomainService GenericDomainServiceInitializer[R, D],
	initAppService GenericApplicationServiceInitializer[R, D, A],
) (*GenericAppContainer[R, D, A, C], error)

NewGenericAppContainer creates a new generic application container

func (*GenericAppContainer[R, D, A, C]) Close

func (c *GenericAppContainer[R, D, A, C]) Close() error

Close closes all resources

func (*GenericAppContainer[R, D, A, C]) GetApplicationService

func (c *GenericAppContainer[R, D, A, C]) GetApplicationService() A

GetApplicationService returns the application service

func (*GenericAppContainer[R, D, A, C]) GetDomainService

func (c *GenericAppContainer[R, D, A, C]) GetDomainService() D

GetDomainService returns the domain service

func (*GenericAppContainer[R, D, A, C]) GetRepository

func (c *GenericAppContainer[R, D, A, C]) GetRepository() R

GetRepository returns the repository

func (*GenericAppContainer[R, D, A, C]) GetRepositoryFactory

func (c *GenericAppContainer[R, D, A, C]) GetRepositoryFactory() interface{}

GetRepositoryFactory returns the repository as an interface{}

type GenericApplicationServiceInitializer

type GenericApplicationServiceInitializer[R any, D any, A any] func(domainService D, repository R) (A, error)

GenericApplicationServiceInitializer is a function type that initializes an application service

type GenericDomainServiceInitializer

type GenericDomainServiceInitializer[R any, D any] func(repository R) (D, error)

GenericDomainServiceInitializer is a function type that initializes a domain service

type GenericRepositoryContainer

type GenericRepositoryContainer[T any, C any] struct {
	*BaseContainer[C]
	// contains filtered or unexported fields
}

GenericRepositoryContainer is a generic dependency injection container for any repository type

func NewGenericRepositoryContainer

func NewGenericRepositoryContainer[T any, C any](
	ctx context.Context,
	logger *zap.Logger,
	cfg C,
	entityType string,
	initRepo GenericRepositoryInitializer[T],
) (*GenericRepositoryContainer[T, C], error)

NewGenericRepositoryContainer creates a new generic repository container

func (*GenericRepositoryContainer[T, C]) Close

func (c *GenericRepositoryContainer[T, C]) Close() error

Close closes all resources

func (*GenericRepositoryContainer[T, C]) GetRepository

func (c *GenericRepositoryContainer[T, C]) GetRepository() T

GetRepository returns the repository

func (*GenericRepositoryContainer[T, C]) GetRepositoryFactory

func (c *GenericRepositoryContainer[T, C]) GetRepositoryFactory() interface{}

GetRepositoryFactory returns the repository as an interface{}

type GenericRepositoryInitializer

type GenericRepositoryInitializer[T any] func(ctx context.Context, connectionString string, logger *zap.Logger) (T, error)

GenericRepositoryInitializer is a function type that initializes a repository

type Repository

type Repository interface {
	// GetID returns the repository ID
	GetID() string
}

Repository is a generic interface for repositories

type RepositoryContainer

type RepositoryContainer[T any] struct {
	// contains filtered or unexported fields
}

RepositoryContainer is a generic dependency injection container for any repository type

func NewRepositoryContainer

func NewRepositoryContainer[T any](
	ctx context.Context,
	logger *zap.Logger,
	cfg config.Config,
	entityType string,
	initRepo RepositoryInitializerFunc[T],
) (*RepositoryContainer[T], error)

NewRepositoryContainer creates a new repository container

func (*RepositoryContainer[T]) Close

func (c *RepositoryContainer[T]) Close() error

Close closes all resources

func (*RepositoryContainer[T]) GetConfig

func (c *RepositoryContainer[T]) GetConfig() config.Config

GetConfig returns the configuration

func (*RepositoryContainer[T]) GetContext

func (c *RepositoryContainer[T]) GetContext() context.Context

GetContext returns the context

func (*RepositoryContainer[T]) GetLogger

func (c *RepositoryContainer[T]) GetLogger() *zap.Logger

GetLogger returns the logger

func (*RepositoryContainer[T]) GetRepository

func (c *RepositoryContainer[T]) GetRepository() T

GetRepository returns the repository

func (*RepositoryContainer[T]) GetRepositoryFactory

func (c *RepositoryContainer[T]) GetRepositoryFactory() interface{}

GetRepositoryFactory returns the repository as an interface{}

type RepositoryInitializerFunc

type RepositoryInitializerFunc[T any] func(
	ctx context.Context,
	connectionString string,
	databaseName string,
	collectionName string,
	logger *zap.Logger,
) (T, error)

RepositoryInitializerFunc is a function type that initializes a repository

type ServiceContainer

type ServiceContainer[R Repository, D DomainService, A ApplicationService, C config.Config] struct {
	*BaseContainer[C]
	// contains filtered or unexported fields
}

ServiceContainer is a generic dependency injection container for services

func NewServiceContainer

func NewServiceContainer[R Repository, D DomainService, A ApplicationService, C config.Config](
	ctx context.Context,
	logger *zap.Logger,
	cfg C,
	repository R,
	initDomainService DomainServiceInitializerFunc[R, D],
	initAppService ApplicationServiceInitializerFunc[R, D, A],
) (*ServiceContainer[R, D, A, C], error)

NewServiceContainer creates a new service container

func (*ServiceContainer[R, D, A, C]) Close

func (c *ServiceContainer[R, D, A, C]) Close() error

Close closes all resources

func (*ServiceContainer[R, D, A, C]) GetApplicationService

func (c *ServiceContainer[R, D, A, C]) GetApplicationService() A

GetApplicationService returns the application service

func (*ServiceContainer[R, D, A, C]) GetDomainService

func (c *ServiceContainer[R, D, A, C]) GetDomainService() D

GetDomainService returns the domain service

func (*ServiceContainer[R, D, A, C]) GetRepository

func (c *ServiceContainer[R, D, A, C]) GetRepository() R

GetRepository returns the repository

func (*ServiceContainer[R, D, A, C]) GetRepositoryFactory

func (c *ServiceContainer[R, D, A, C]) GetRepositoryFactory() interface{}

GetRepositoryFactory returns the repository as an interface{}

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL