Generated dependency injection containers in go (golang)

DINGO

Generation of dependency injection containers for go programs (golang).

Dingo is a code generator. It generates dependency injection containers based on sarulabs/di.

It is better than sarulabs/di alone because:

  • Generated containers have typed methods to retrieve each object. You do not need to cast them before they can be used. That implies less runtime errors.
  • Definitions are easy to write. Some dependencies can be guessed, allowing shorter definitions.

The disadvantage is that the code must be generated. But this can be compensated by the use of a file watcher.

Table of contents

Build Status GoDoc codebeat badge goreport

Dependencies

This module depends on github.com/sarulabs/di/v2. You will need it to generate and use the dependency injection container.

Similarities with di

Dingo is very similar to sarulabs/di as it mainly a wrapper around it. This documentation mostly covers the differences between the two libraries. You probably should read the di documentation before going further.

Setup

Code structure

You will have to write the service definitions and register them in a Provider. The recommended structure to organize the code is the following:

- services/
    - provider/
        - provider.go
    - servicesA.go
    - servicesB.go

In the service files, you can write the service definitions:

// servicesA.go
package services

import (
    "github.com/sarulabs/dingo/v4"
)

var ServicesADefs = []dingo.Def{
    {
        Name: "definition-1",
        // ...
    },
    {
        Name: "definition-2",
        // ...
    },
}

In the provider file, the definitions are registered with the Load method.

// provider.go
package provider

import (
    "github.com/sarulabs/dingo/v4"
    services "YOUR_OWN_SERVICES_PACKAGE"
)

// Redefine your own provider by overriding the Load method of the dingo.BaseProvider.
type Provider struct {
    dingo.BaseProvider
}

func (p *Provider) Load() error {
    if err := p.AddDefSlice(services.ServicesADefs); err != nil {
        return err
    }
    if err := p.AddDefSlice(services.ServicesBDefs); err != nil {
        return err
    }
    return nil
}

An example of this can be found in the tests directory.

Generating the code

You will need to create your own command to generate the container. You can adapt the following code:

package main

import (
    "fmt"
    "os"

    "github.com/sarulabs/dingo/v4"
    provider "YOUR_OWN_PROVIDER_PACKAGE"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Println("usage: go run main.go path/to/output/directory")
        os.Exit(1)
    }

    err := dingo.GenerateContainer((*provider.Provider)(nil), os.Args[1])
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

Running the following command will generate the code in the path/to/generated/code directory:

go run main.go path/to/generated/code

Definitions

Name and scope

Dingo definitions are not that different from sarulabs/di definitions.

They have a name and a scope. For more information about scopes, refer to the documentation of sarulabs/di.

The default scopes are di.App, di.Request and di.SubRequest.

Build based on a structure

Def.Build can be a pointer to a structure. It defines the type of the registered object.

type MyObject struct{}

dingo.Def{
    Name: "my-object",
    Build: (*MyObject)(nil),
}

You can use a nil pointer, like (*MyObject)(nil), but it is not mandatory. &MyObject{} is also valid.

If your object has fields that must be initialized when the object is created, you can configure them with Def.Params.

type OtherObject struct {
    FieldA *MyObject
    FieldB string
    FieldC int
}

dingo.Def{
    Name: "other-object",
    Build: (*OtherObject)(nil),
    Params: dingo.Params{
        "FieldA": dingo.Service("my-object"),
        "FieldB": "value",
    },
}

dingo.Params is a map[string]interface{}. The key is the name of the field. The value is the one that the associated field should take.

You can use dingo.Service to use another object registered in the container.

Some fields can be omitted like FieldC. In this case, the field will have the default value 0. But it may have a different behaviour. See the parameters section to understand why.

Build based on a function

Def.Build can also be a function. Using a pointer to a structure is a simple way to declare an object, but it lacks flexibility.

To declare my-object you could have written:

dingo.Def{
    Name: "my-object",
    Build: func() (*MyObject, error) {
        return &MyObject{}, nil
    },
}

It is similar to the Build function of sarulabs/di, but without the container as an input parameter, and with *MyObject instead of interface{} in the output.

To build the other-object definition, you need to use the my-object definition. This can be achieved with Def.Params:

dingo.Def{
    Name: "other-object",
    Build: func(myObject *MyObject) (*OtherObject, error) {
        return &OtherObject{
            FieldA: myObject,
            FieldB: "value",
        }, nil
    },
    Params: dingo.Params{
        "0": dingo.Service("my-object"),
    },
}

The build function can actually take as many input parameters as needed. In Def.Params you can define their values.

The key is the index of the input parameter.

Parameters

As explained before, the key of Def.Params is either the field name (for build structures) or the index of the input parameter (for build functions).

When an item is not defined in Def.Params, there are different situations:

  • If there is exactly one service of this type also defined in the container, its value is used.
  • If there is none, the default value for this type is used.
  • If there are more than one service with this type, the container can not be compiled. You have to specify the value for this parameter.

With these properties, it is possible to avoid writing some parameters. They will be automatically filled. This way you can have shorter definitions:

dingo.Def{
    Name: "other-object",
    Build: func(myObject *MyObject) (*OtherObject, error) {
        return &OtherObject{
            FieldA: myObject, // no need to write the associated parameter
            FieldB: "value",
        }, nil
    },
}

It works well for specific structures. But for basic types it can become a little bit risky. Thus it is better to only store pointers to structures in the container and avoid types like string or int.

It is possible to force the default value for a parameter, instead of using the associated object. You have to set the parameter with dingo.AutoFill(false):

dingo.Def{
    Name: "other-object",
    Build: (*OtherObject)(nil),
    Params: dingo.Params{
        // *MyObject is already defined in the container,
        // so you have to use Autofill(false) to avoid
        // using this instance.
        "FieldA": dingo.AutoFill(false),
    },
}

Close function

Close functions are identical to those of sarulabs/di. But they are typed. No need to cast the object anymore.

dingo.Def{
    Name: "my-object",
    Build: func() (*MyObject, error) {
        return &MyObject{}, nil
    },
    Close: func(obj *MyObject) error {
        // Close object.
        return nil
    }
}

Avoid automatic filling

Each definition in the container is a candidate to automatically fill another (if its parameters are not specified).

You can avoid that with Def.NotForAutoFill:

dingo.Def{
    Name: "my-object",
    Build: (*MyObject)(nil),
    NotForAutoFill: true,
}

This can be useful if you have more than one object of a given type, but one should be used by default to automatically fill the other definitions. Use Def.NotForAutoFill on the definition you do not want to use automatically.

Generated container

Basic container

The container is generated in the dic package inside the destination directory. The container is more or less similar to the one from sarulabs/di.

It implements this interface:

interface {
    Scope() string
    Scopes() []string
    ParentScopes() []string
    SubScopes() []string
    Parent() *Container
    SubContainer() (*Container, error)
    SafeGet(name string) (interface{}, error)
    Get(name string) interface{}
    Fill(name string, dst interface{}) error
    UnscopedSafeGet(name string) (interface{}, error)
    UnscopedGet(name string) interface{}
    UnscopedFill(name string, dst interface{}) error
    Clean() error
    DeleteWithSubContainers() error
    Delete() error
    IsClosed() bool
}

To create the container, there is the NewContainer function:

func NewContainer(scopes ...string) (*Container, error)

You need to specify the scopes. By default di.App, di.Request and di.SubRequest are used.

A NewBuilder function is also available. It allows you to redefine some services (Add and Set methods) before generating the container with its Build method. It is not recommended but can be useful for testing.

Additional methods

For each object, four other methods are generated. These methods are typed so it is probably the one you will want to use.

They match the SafeGet, Get, UnscopedSafeGet and UnscopedGet methods. They have the name of the definition as suffix.

For example for the my-object definition:

interface {
    SafeGetMyObject() (*MyObject, error)
    GetMyObject() *MyObject
    UnscopedSafeGetMyObject() (*MyObject, error)
    UnscopedGetMyObject() *MyObject
}

my-object has been converted to MyObject.

The name conversion follow these rules:

  • only letters and digits are kept
  • it starts with an uppercase character
  • after a character that is not a letter or a digit, there is another uppercase character

For example --apple--orange--2--PERRY-- would become AppleOrange2PERRY.

Note that you can not have a name beginning by a digit.

C function

There is also a C function in the dic package. Its role is to turn an interface into a *Container.

By default, the C function can:

  • cast a container into a *Container if it is possible
  • retrieve a *Container from the context of an *http.Request (the key being dingo.ContainerKey("dingo"))

This function can be redefined to fit your use case:

dic.C = func(i interface{}) *Container {
    // Find and return the container.
}

The C function is used in retrieval functions.

Retrieval functions

For each definition, another function is generated.

Its name is the formatted definition name. It takes an interface as input parameter, and returns the object.

For my-object, the generated function would be:

func MyObject(i interface{}) *MyObject {
    // ...
}

The generated function uses the C function to retrieve the container from the given interface. Then it builds the object.

It can be useful if you do not have the *Container but only an interface wrapping the container:

type MyContainer interface {
    // Only basic untyped methods.
    Get(string) interface{}
}

func (c MyContainer) {
    obj := c.Get("my-object").(*MyObject)

    // or

    obj := c.(*dic.Container).GetMyObject()

    // can be replaced by

    obj := dic.MyObject(c)
}

It can also be useful in an http handler. If you add a middleware to store the container in the request context with:

// Create a new request req, which is like request r but with the container in its context.
ctx := context.WithValue(r.Context(), dingo.ContainerKey("dingo"), container)
req := r.WithContext(ctx)

Then you can use it in the handler:

func (w http.ResponseWriter, r *http.Request) {
    // The function can find the container in r thanks to dic.C.
    // That is why it can create the object.
    obj := dic.MyObject(r)
}

Upgrade from v3

  • You need to register the definitions in a Provider. The dingo binary is also not available anymore as it would depends on the Provider now. So you have to write it yourself. See the Setup section.
  • dingo.App, dingo.Request and dingo.SubRequest have been removed. Use di.App, di.Request and di.SubRequest instead.
Owner
Comments
  • Link to the constructor in GetService() method comment

    Link to the constructor in GetService() method comment

    Sometimes when I watch at some GetService() method generated by container I want to check the constructor which used by this method. Not the build func from definition, but the constructor of the Service itself.

    Would be nice to have comment for the GetService() which will contain link to the constructor of the service. Something like: // This method uses package.NewService method

    What do you think about this?

  • Param should be Interface, realization given

    Param should be Interface, realization given

    It looks like it's correct behaviour, when concrete realization passed as parameter to method, which waits for interface that realization implements.

    I think it could be fixed with adding reflect.TypeOf().Implements() check. What do you think?

  • Add Put method, which will replace key in map, but not value by pointer

    Add Put method, which will replace key in map, but not value by pointer

    Sarulabs, i really grateful for adding the Fill method to container. I found out how does it works. But it is not the thing i need.

    I have the test function, where i want to get some destination service with all dependencies resolved. But i don't need real repository to be resolved as dependency of userFetcher. What i do want is to place userRepoMock to container before resolving my service. So when later i will get my destination service, it will depend on Mock service instead of real one.

    func test() error {
    	container, err := dic.NewContainer(dingo.App)
    	if err != nil {
    		return err
    	}
    
    	userRepoMock := user.RepoMock{}
    
    	if err := container.Fill("user-repository", &userRepoMock); err != nil {
    		return err
    	}
    
    	fetcher := container.GetUserFetcher()
            // here fetcher depend on mock, not on real userRepository
    	fmt.Println(fetcher)
    
    	return nil
    }
    

    Is that possible to add some kind of Put method, which will replace existing object?

    I see 2 possible ways to achieve that:

    1. Add to definition field like: Redefinable. If that field is true, type safe Put method will be generated for that definition.
    2. Add generic Put method, which will check for the same type or for the same interface. Checking is optional, but it will make errors more verbose.
  • What about functional tests?

    What about functional tests?

    In https://github.com/sarulabs/di there is a great method Fill, which can be used to put object, for example mock by key. It is very useful, when it comes to functional testing. Do this package has any functions to make the same thing?

    It looks like that If the Definition returns an interface type, each type, that implements that interface could safely replace existing in the container object.

  • Add ability to use slice of services in Params field

    Add ability to use slice of services in Params field

    Hello there. It would be really nice to be able to use in Params not map, but slice. If Params field used for services - map keys are just indexes. The slice can be used. It is pretty annoying to edit all indexes if some param has been added to the middle of the params list. I believe it is done in that way to use Params for different cases, but if you can add another field like SliceParams - it would be fantastic.

  • Missing functionality

    Missing functionality

    Hello,

    Currently missing in sarulabs/dingo :

    By default the objects are shared that means what they stored as singletons in the Container. You will retrieve the exact same object every time you call the Get method on the same Container. The Build function will only be called once. In case when you don't want the objects to be shared set the Unshared property of object definition to true.

    dingo service definitions do not have an Unshared property:

    
    package dingo
    
    // Def is the structure containing a service definition.
    type Def struct {
    	Name  string
    	Scope string
    	// NotForAutoFill should be set to true if you
    	// do not want to use this service automatically
    	// as a dependency in other services.
    	NotForAutoFill bool
    	// Build defines the service constructor. It can be either:
    	// - a pointer to a structure: (*MyStruct)(nil)
    	// - a factory function: func(any, any, ...) (any, error)
    	Build interface{}
    	// Params are used to assist the service constructor.
    	Params Params
    	// Close should be a function: func(any) error.
    	// With any being the type of the service.
    	Close interface{}
    }
    

    Any plans on adding this?

  • Error on attempt to cast function with `ed25519.PrivateKey` argument

    Error on attempt to cast function with `ed25519.PrivateKey` argument

    Hello,

    Thank you very much for this container, it helps a lot with decreasing client's code amount.

    I faced with the problem of using ed25519.PrivateKey type (wrapper for []byte type) within Builder function. My Builder function looks like:

    func(config *infra.Config) (ed25519.PrivateKey, error)
    

    When I'm trying to build container, I get error:

    could not cast build function to func(*infra.Config) ([]uint8, error)

    Possible workaround: change argument type from ed25519.PrivateKey to []byte.

  • Automatic DI?

    Automatic DI?

    Hi! I saw this in a medium post and realised that you did a nicer version of what I did in http://github.com/j7mbo/Goij, in utilising the AST for strings -> structs.

    The best thing for me was that I used the New convention to automatically initialise structs, recursively. Allow mappings from interfaces to concretes. But the rest basically can be fully automatic.

    Would you consider allowing Dingo to become fully automatic, so no configuration needed except for when explicitness is required (interface -> impl)? Any chance of a collaboration on that?

  • Package incapsulation with Dingo

    Package incapsulation with Dingo

    I have some package, where interface is public, but the structure, that implements that interface is private.

    When i try to declare definition function with return type of public interface, but return pointer to private structure, that implement interface - getting error:

    could not scan definitions: could not scan definition <definition-name>: object type is <Interface> but <pointer to structure> is used is Close

    To solve that i must return the same type, that was declared in return type of definition of function which adds some artificial limitations

  • could not load definition with type *dingo.Def

    could not load definition with type *dingo.Def

    After updating dingo binary to v3.1.0 getting error on code generation.

    dingo -src="def" -dest="def/gen"
    [KO] An error occurred while running the generated compiler:
    could not load definition with type *dingo.Def (allowed types: dingo.Def, *dingo.Def, []dingo.Def, []*dingo.Def, func() dingo.Def, func() *dingo.Def, func() []dingo.Def, func() []*dingo.Def)
    

    On version 2.0.0 generator was working OK

  • Tweaking the output package name would be useful

    Tweaking the output package name would be useful

    Could you please add package name as parameter to GenerateContainer func ? Like:

    func GenerateContainer(provider Provider, outputDirectory, packageName string) error
    

    or

    func GenerateContainer(provider Provider, packageName, outputDirectory string) error
    
Strict Runtime Dependency Injection for Golang

wire Wire is runtime depedency injection/wiring for golang. It's designed to be strict to avoid your go application running without proper dependency

Sep 27, 2022
golang-runtime-di is a framework for runtime dependency injection in go

golang-runtime-di description golang-runtime-di is a framework for runtime dependency injection in go. usage quickstart add it to your go.mod: go get

Aug 1, 2022
🛠 A full-featured dependency injection container for go programming language.

DI Dependency injection for Go programming language. Tutorial | Examples | Advanced features Dependency injection is one form of the broader technique

Dec 31, 2022
A reflection based dependency injection toolkit for Go.

⚒️ dig A reflection based dependency injection toolkit for Go. Good for: Powering an application framework, e.g. Fx. Resolving the object graph during

Jan 1, 2023
Go Dependency Injection Framework

Dingo Dependency injection for go Hello Dingo Dingo works very very similiar to Guice Basically one binds implementations/factories to interfaces, whi

Dec 25, 2022
A dependency injection based application framework for Go.

?? Fx An application framework for Go that: Makes dependency injection easy. Eliminates the need for global state and func init(). Installation We rec

Jan 3, 2023
Simple Dependency Injection Container
Simple Dependency Injection Container

?? gocontainer gocontainer - Dependency Injection Container ?? ABOUT Contributors: Rafał Lorenz Want to contribute ? Feel free to send pull requests!

Sep 27, 2022
Simple and yet powerful Dependency Injection for Go
Simple and yet powerful Dependency Injection for Go

goioc/di: Dependency Injection Why DI in Go? Why IoC at all? I've been using Dependency Injection in Java for nearly 10 years via Spring Framework. I'

Dec 28, 2022
Dependency Injection and Inversion of Control package

Linker Linker is Dependency Injection and Inversion of Control package. It supports the following features: Components registry Automatic dependency i

Sep 27, 2022
Compile-time dependency injection for Go

Dihedral Dihedral is a compile-time injection framework for Go. Getting started > go get -u github.com/dimes/dihedral Create a type you want injected

Jun 1, 2022
Compile-time Dependency Injection for Go

Wire: Automated Initialization in Go Wire is a code generation tool that automates connecting components using dependency injection. Dependencies betw

Jan 2, 2023
A dependency injection library that is focused on clean API and flexibility

Dependency injection DI is a dependency injection library that is focused on clean API and flexibility. DI has two types of top-level abstractions: Co

Oct 13, 2022
Golang PE injection on windows

GoPEInjection Golang PE injection on windows See: https://malwareunicorn.org/workshops/peinjection.html Based on Cryptowall's PE injection technique.

Jan 6, 2023
two scripts written in golang that will help you recognize dependency confusion.
two scripts written in golang that will help you recognize dependency confusion.

two scripts written in golang that will help you recognize dependency confusion.

Mar 3, 2022
golang auto wire code generator

Go-AutoWire helps you to generate wire files with easy annotate 中文文档 this project is base on wire but it did simplify the wire usage and make wire muc

Dec 2, 2022
How we can run unit tests in parallel mode with failpoint injection taking effect and without injection race

This is a simple demo to show how we can run unit tests in parallel mode with failpoint injection taking effect and without injection race. The basic

Oct 31, 2021
An additive dependency injection container for Golang.

Alice Alice is an additive dependency injection container for Golang. Philosophy Design philosophy behind Alice: The application components should not

Oct 16, 2022
Strict Runtime Dependency Injection for Golang

wire Wire is runtime depedency injection/wiring for golang. It's designed to be strict to avoid your go application running without proper dependency

Sep 27, 2022
Minimalistic, pluggable Golang evloop/timer handler with dependency-injection

Anagent Minimalistic, pluggable Golang evloop/timer handler with dependency-injection - based on codegangsta/inject - go-macaron/inject and chuckpresl

Sep 27, 2022
golang-runtime-di is a framework for runtime dependency injection in go

golang-runtime-di description golang-runtime-di is a framework for runtime dependency injection in go. usage quickstart add it to your go.mod: go get

Aug 1, 2022