Skip to content

Dependency injection for Go using generics and reflection

License

Notifications You must be signed in to change notification settings

tiendc/autowire

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Version GoDoc Build Status Coverage Status GoReport

Dependency injection for Go 1.18+ using Generics and reflection

Functionalities

  • Automatically wiring/injecting objects on creation.
  • Easy to use (see Usage section).
  • Shared mode by default that creates only one instance for a type (this option is configurable).
  • Ability to overwrite objects of specific types on building which is convenient for unit testing.
  • No code generation.

Installation

go get github.com/tiendc/autowire

Usage

General usage

    // Suppose you have ServiceA and a creator function
    type ServiceA interface {}
    func NewServiceA(srvB ServiceB, repoX RepoX) ServiceA {
        return <ServiceA-instance>
    }

    // and ServiceB
    type ServiceB interface {}
    func NewServiceB(ctx context.Context, repoX RepoX, repoY RepoY) (ServiceB, error) {
        return <ServiceB-instance>, nil
    }

    // and RepoX
    type RepoX interface {}
    func NewRepoX(s3Client S3Client) (RepoX, error) {
        return <RepoX-instance>, nil
    }

    // and RepoY
    type RepoY interface {}
    func NewRepoY(redisClient RedisClient) RepoY {
        return <RepoY-instance>
    }

    // and a struct provider
    type ProviderStruct struct {
        RedisClient RedisClient
        S3Client    S3Client
    }

    var (
        // init the struct value
        providerStruct = &ProviderStruct{...}

        // create a container with passing providers
        container = MustNewContainer([]any{
            // Service providers
            NewServiceA,
            NewServiceB,
            // Repo providers
            NewRepoX,
            NewRepoY,
            // Struct provider (must be a pointer)
            providerStruct,
        })
    )

    func main() {
        // Update some values of the struct provider
        providerStruct.RedisClient = newRedisClient()
        providerStruct.S3Client = newS3Client()

        // Create ServiceA
        serviceA, err := autowire.BuildWithCtx[ServiceA](ctx, container)
        // Create RepoX
        repoX, err := autowire.Build[RepoX](container)
    }

Non-shared mode

    // Set sharedMode when create a container
    container = MustNewContainer([]any{
        // your providers
    }, SetSharedMode(false))

    // Activate non-shared mode inline for a specific build only
    serviceA, err := Build[ServiceA](container, NonSharedMode())

Overwrite values of specific types

This is convenient in unit testing to overwrite specific types only.

    // In unit testing, you may want to overwrite some `repos` and `clients` with fake instances.
    // NOTE: you may need to use `non-shared mode` to make sure cached objects are not used.
    serviceA, err := Build[ServiceA](container, NonSharedMode(),
            ProviderOverwrite[RepoX](fakeRepoX),
            ProviderOverwrite[RepoY](fakeRepoY))
    serviceA, err := Build[ServiceA](container, NonSharedMode(),
            ProviderOverwrite[RedisClient](fakeRedisClient),
            ProviderOverwrite[S3Client](fakeS3Client))

Reclaim memory after use

Typically, dependency injection is only used at the initialization phase of a program. However, it can take some space in memory which will become wasted after the phase. Use the below trick to reclaim the memory taken by the autowire variables.

    var (
        // create a global container
        container = MustNewContainer(...)
    )

    func autowireCleanUp() {
        // Assign nil to allow the garbage collector to reclaim the taken memory
        container = nil
    }

    func main() {
        // Initialization phase
        // Create services and use them
        serviceA, _ := autowire.Build[ServiceA](container)
        serviceB, _ := autowire.Build[ServiceB](container)

        // Clean up the usage, make sure you won't use the vars any more
        autowireCleanUp()
    }

Contributing

  • You are welcome to make pull requests for new functions and bug fixes.

License