A lightweight yet powerful IoC container for Go projects

GoDoc Build Status Go Report Card Awesome Coverage Status

Container

A lightweight yet powerful IoC container for Go projects. It provides a simple, fluent and easy-to-use interface to make dependency injection in GoLang easier.

Documentation

Required Go Versions

It requires Go v1.11 or newer versions.

Installation

To install this package, run the following command in the root of your project.

go get github.com/golobby/container

Introduction

GoLobby Container like any other IoC container is used to bind abstractions to their implementations. Binding is a process of introducing an IoC container that which concrete (implementation) is appropriate for an abstraction. In this process, you also determine how it must be resolved, singleton or transient. In singleton binding, the container provides an instance once and returns it for each request. In transient binding, the container always returns a brand new instance for each request. After the binding process, you can ask the IoC container to get the appropriate implementation of the abstraction that your code depends on. In this case, your code depends on abstractions, not implementations.

Binding

Singleton

Singleton binding using Container:

err := container.Singleton(func() Abstraction {
  return Implementation
})

It takes a resolver function which its return type is the abstraction and the function body configures the related concrete (implementation) and returns it.

Example for a singleton binding:

err := container.Singleton(func() Database {
  return &MySQL{}
})

Transient

Transient binding is also similar to singleton binding.

Example for a transient binding:

err := container.Transient(func() Shape {
  return &Rectangle{}
})

Resolving

Container resolves the dependencies with the method make().

Using References

One way to get the appropriate implementation you need is to declare an instance of the abstraction type and pass its reference to Container this way:

var a Abstraction
err := container.Make(&a)
// "a" will be implementation of the Abstraction

Example:

var m Mailer
err := container.Make(&m)
if err != nil {
    panic(err)
}

m.Send("[email protected]", "Hello Milad!")

Using Closures

Another way to resolve the dependencies is by using a function (receiver) that its arguments are the abstractions you need. Container will invoke the function and pass the related implementations for each abstraction.

err := container.Make(func(a Abstraction) {
  // "a" will be implementation of the Abstraction
})

Example:

err := container.Make(func(db Database) {
  // "db" will be the instance of MySQL
  db.Query("...")
})

You can also resolve multiple abstractions this way:

err := container.Make(func(db Database, s Shape) {
  db.Query("...")
  s.Area()
})

Binding time

You can also resolve a dependency at the binding time in your resolver function like the following example.

// Bind Config to JsonConfig
err := container.Singleton(func() Config {
    return &JsonConfig{...}
})

// Bind Database to MySQL
err := container.Singleton(func(c Config) Database {
    // "c" will be the instance of JsonConfig
    return &MySQL{
        Username: c.Get("DB_USERNAME"),
        Password: c.Get("DB_PASSWORD"),
    }
})

Notice: You can only resolve the dependencies in a binding resolver function that has already bound.

Standalone instance

Container works without any initialization keeping your bindings in the default instance. Sometimes you may want to create a standalone instance for a part of application. If so, create a new instance:

c := container.NewContainer() // returns container.Container
_ = c.Singleton(binding)
_ = c.Make(&resolver)

The rest stays the same. The default container is still available.

Usage Tips

Performance

The package Container inevitably uses reflection in binding and resolving processes. If performance is a concern, you should use this package more carefully. Try to bind and resolve the dependencies out of the processes that are going to run many times (for example, on each request), put it where that run only once when you run your applications like main and init functions.

License

GoLobby Container is released under the MIT License.

Owner
GoLobby
GoLobby publishes open source packages with simplicity and fluent APIs in mind for GoLang
GoLobby
Comments
  • Adds lazy loading support to binding methods

    Adds lazy loading support to binding methods

    Inspired from work by @place1. Adds new Lazy methods that can optionally be called to lazy bind a resolver to an abstract. The resolver will not be called until the first time a resolve call is made.

    A strong effort was made to not cause regressions or otherwise change the behavior of the existing API. Minor changes were made to how validation occurs and in some situations a more verbose error can be presented earlier in a binding process.

  • Lazy singleton construction

    Lazy singleton construction

    This PR makes singleton construction lazy. It also adds a lock to each container binding so that multiple goroutines can safely work with the container at the same time.

    This feature is very helpful if you have multiple programs/entrypoints that share the same container but only use a subset of the dependencies.

    In my use-case I have a server and cli application that share the same container; there are many shared dependencies but not everything is used by both programs. For example the TLS configuration used by the server isn't used by the cli application and attempting to resolve it will fail because the certificates won't be available.

    With this PR the problem is solved because only the dependencies (bindings) that are requested are constructed; if the cli app never requests the TLS configuration then it's resolver is never executed.

  • Support named bindings

    Support named bindings

    This allows to create many bindings with different identifiers for the same interface. This is mostly helpful when implementing abstract factories where identifiers of concrete factories are stored somewhere as strings.

  • added generic ResolveT and

    added generic ResolveT and "must" methods

    This PR adds the following methods:

    c := container.New()
    
    db, err := container.ResolveT[Database](c)
    if err != nil {
        // error
    }
    
    db := container.MustResolveT[Database](c)
    
    container.MustSingleton(c, resolver)
    container.MustNamedSingleton(c, "", resolver)
    container.MustTransient(c, resolver)
    container.MustNamedTransient(c, "", resolver)
    container.MustCall(c, receiver)
    
  • Get the receiver/instance from the concrete / AutoBind based on tags

    Get the receiver/instance from the concrete / AutoBind based on tags

    Hi, I am working on a project where we use the container library.

    We are trying to "tune" the usage of the DI container with custom tags, to get rid of call-chains like

    func NewInstance() *Instance {
        var repo repository.Notifications
        var bridge fcm.Bridge
        var timeProvider tp.TimeProvider
    
        main.DI.Make(&repo)
        main.DI.Make(&bridge)
        main.DI.Make(&timeProvider)
    
        // return instance...
    }
    

    What I want to do is to mark some struct fields with a custom tag, let's say di:"inject" to automatically inject the fields at some point (like execute call).

    What I've already achieved is the following code:

    type Instance struct {
        context ctx.Context
        
        repo repository.Notifications `di:"bind"`
        ...
    }
    
    func inject(gc GoCase) {
    	rv := reflect.ValueOf(gc).Elem()
    	t := rv.Type()
    
    	for i := 0; i < t.NumField(); i++ {
    		field := t.Field(i)
    		rvField := rv.Field(i)
    		if value, ok := field.Tag.Lookup("di"); ok {
    			if value == "bind" {
    
    				y := reflect.NewAt(rvField.Type(), unsafe.Pointer(rvField.UnsafeAddr())).Elem()
    				receiverType := y.Type()
    				concrete, ok := DI[receiverType] // DI is the app-container
    				if !ok {
    					panic("concrete not found for the abstraction" + receiverType.String())
    				}
    
    				// Concrete found, but how do I get instance/receiver from the concrete?
    				// Do I need to use the reflection here? Or maybe it's better to add/expose
    				// a possibility to do that in the library?
    				
    				fmt.Print(concrete) // just for testing
    			}
    		}
    	}
    }
    

    My wonderings are explained in the comment.

    And I see a few possibilities:

    • use reflection to find the concrete (doesn't sound good to me, to be honest)
    • expose proper fields or add a proper method in the library (like GetConcrete(Type))
    • add an "extension" to the library that allows doing the bindings based on a tag, let's say di:"inject" (I can implement it, but first I would like to know what you think about it).
    • or, maybe you see another way to do that? :)

    Thanks for your input!

  • Possible outdated README documentaion

    Possible outdated README documentaion

    I just installed the package and gave it a go. I was following the README code examples, but I got some type errors. Installation:

    $ go get github.com/golobby/container/v3
    

    go.sum:

    github.com/golobby/container v1.3.0 h1:Pgk8fK9fJHuZqU924Bl8+DY2/n9jCUtsSKfiOctdQ9A=
    github.com/golobby/container v1.3.0/go.mod h1:6yAH4QK+Hi8HxGuCJuAGiqS/a5n8YP+4bXNpPdKzLVM=
    github.com/golobby/container/v3 v3.3.0 h1:ohPSWUUY67yvOhIBVC9Wv1pnsh8iOwRS0U1M1bjKBG8=
    github.com/golobby/container/v3 v3.3.0/go.mod h1:RDdKpnKpV1Of11PFBe7Dxc2C1k2KaLE4FD47FflAmj0=
    

    My code following README example:

    type Test interface {
    	Test()
    }
    type TestImpl struct {
    }
    
    func (iml *TestImpl) Test() {
    	fmt.Println("Test")
    }
    
    func main() {
    	// Bind Config interface to JsonConfig struct
    	err := container.Singleton(func() Test {
    		return &TestImpl{}
    	})
    
    	var c TestImpl
    	err := container.Resolve(&c)
    	// `c` will be the instance of JsonConfig
    }
    

    Errors:

    container.Singleton((func() Test literal)) (no value) used as valuecompilerTooManyValues

    Resolve not declared by package containercompilerUndeclaredImportedName

    It looks like there:

    • Instead of resolve there's a Make function defined
    • sIngleton function doesn't return an error?

    Is it an issue with outdated documentation, or am I doing something weird here?

    Thanks :)

  • Error returning except panic

    Error returning except panic

    In case of any error on this package, we decide to use the built-in panic() function that stops the normal execution of the current goroutine. Based on golang builtin.go we have:

    ... This continues until all functions in the executing goroutine have stopped, in reverse order. At that point, the program is terminated with a non-zero exit code. This termination sequence is called panicking and can be controlled by the built-in function recover.

    As an injected part of any project, it's not a good deal to call panic() and terminate the program with a non-zero exit code. Also, there is no place to explain this important notice to users that they can make any recovery plan for error situations! I believe Panics are not exceptions and I think it's better to returning an error on error cases and removing panic() in all of the repo. And panic is useful for sometimes the program simply cannot continue. The library has not this ownership. The regexp.MustCompile() maybe can a good example of something that may panic. On the other hand, Golang built-in libs used panic in internal packages but it recovers panic in itself too! Take a look on json.marshal().

    And I think this link prepared a good article for using panics in projects. Advanced Go Panic Programming

    Maybe it's not bad to remember that error handling is one of the in-progress improvements on Golang Go 2 Draft Designs

  • How we can get property of an implementation?

    How we can get property of an implementation?

    I am using golobby/container/v3 :

    type Database interface {
    }
    
    type DatabaseImpl struct {
        MySql interface{}
        PgSql interface{}
    }
    
    container.Singleton(func() Database {
        return &DatabaseImpl {
            MySql: mysql.CreateConnection(),
            PgSql: pgsql.CreateConnection(),
        }
    })
    

    Now I want to get an instance of MySql connection, so how can I get it?

    I try to do:

    var d Database
    container.Resolve(&d)
    
    result, _ := d.MySql.Query(...)
    

    but i get an error type Database has no field or method MySql

  • Singleton's are created twice when resolver returns (T, error)

    Singleton's are created twice when resolver returns (T, error)

    i've added some example code below and i've found that the singleton is created twice.

    c := container.New()
    
    c.Singleton(func() (ExampleService, error) {
    	fmt.Println("singleton created")
    	return CreateExampleService()
    })
    

    the issue only occurs because the resolver function returns a 2-tuple of (ExampleService, error)

    the issue is happening because of the invoke call happening in this loop (I think) https://github.com/golobby/container/blob/master/container.go#L101

  • Singleton always cast second parameter as error

    Singleton always cast second parameter as error

    First of all, thanks for this amazing library. Here is what my singleton call is like

    container.Singleton(func() (*gorm.DB, error) {
            dbFile := "/tmp/test.db"
    	dialector := sqlite.Open(dbFile)
    	db, err := gorm.Open(dialector)
    
    	if err != nil {
    		return nil, err
    	}
    
    	return db.Debug(), nil
    })
    

    But I am always getting panic: interface conversion: interface is nil, not error even when error is nil.

    I think there should be a check for nil before casting to error type in https://github.com/golobby/container/blob/800c8e19e5cc8d7e9471a1b9faf66367294d5e55/pkg/container/container.go#L74

    Can you please look into it?

    Thanks

  • Enable to Get Error on Binding

    Enable to Get Error on Binding

    Hello, I am using your great library for Dependency injection.
    However, I want to retrieve errors when binding.
    For instance, my code below is to open SQLite DB, but when Opening DB failed, I have to call panic() to show an error.

    container.Singleton(func() *gorm.DB {
    		DBFile := "/tmp/test.db"
    		dialector := sqlite.Open(DBFile)
    		db, err := gorm.Open(dialector)
    
    		if err != nil {
    			panic(err)
    		}
    
    		return db.Debug()
    	})
    

    So, I want you to enable me to run the code like below to retrieve the error.
    Right now, when error occurs, the code below just cause nil reference error.

    container.Singleton(func() (*gorm.DB, error) {
    		DBFile := "/tmp/test.db"
    		dialector := sqlite.Open(DBFile)
    		db, err := gorm.Open(dialector)
    
    		if err != nil {
    			return nil, err
    		}
    
    		return db.Debug(), nil
    	})
    

    Thanks,

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
🛠 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
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
Serverless Container Workflows
Serverless Container Workflows

direktiv event-based serverless container workflows Check out our online demo: wf.direktiv.io What is Direktiv? Direktiv is a specification for a serv

Dec 20, 2022
A lightweight yet powerful config package for Go projects

Config GoLobby Config is a lightweight yet powerful config package for Go projects. It takes advantage of env files and OS variables alongside config

Dec 11, 2022
Simple, yet powerful Adcell go client to import data feeds into you projects.
 Simple, yet powerful Adcell go client to import data feeds into you projects.

adcell-go Simple, yet powerful Adcell go client to import data feeds into you projects. Explore the docs » View Demo · Report Bug · Request Feature Ta

Oct 31, 2021
基于 IoC 的 Go 后端一站式开发框架 🚀
基于 IoC 的 Go 后端一站式开发框架 🚀

Go-Spring 的愿景是让 Go 程序员也能用上如 Java Spring 那般威力强大的编程框架。 其特性如下: 提供了完善的 IoC 容器,支持依赖注入、属性绑定; 提供了强大的启动器框架,支持自动装配、开箱即用; 提供了常见组件的抽象层,支持灵活地替换底层实现; Go-Spring 当前使

Nov 7, 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
Fast, powerful, yet easy to use template engine for Go. Optimized for speed, zero memory allocations in hot paths. Up to 20x faster than html/template

quicktemplate A fast, powerful, yet easy to use template engine for Go. Inspired by the Mako templates philosophy. Features Extremely fast. Templates

Dec 26, 2022
Simple Yet Powerful Logger

sypl sypl provides a Simple Yet Powerful Logger built on top of the Golang sypl. A sypl logger can have many Outputs, and each Output is responsible f

Sep 23, 2022
Fast and powerful Git hooks manager for any type of projects.
Fast and powerful Git hooks manager for any type of projects.

Lefthook The fastest polyglot Git hooks manager out there Fast and powerful Git hooks manager for Node.js, Ruby or any other type of projects. Fast. I

Jan 4, 2023
Powerful CLI written in GO to generate projects in various technologies
Powerful CLI written in GO to generate projects in various technologies

Barca CLI is a project generator written in GO and its purpose is to build and configure HTTP servers, web proxy, SPA/PWA, Blog and custom landing page. It's easy, fast and productive.

Aug 26, 2022
Clones github projects into ~/Projects/github/{org}/{repo}

Tidy clone Github cli extension (gh extension) to clone repos into ~/Projects/github/{org}/{repo} on the local filesystem Install gh extension install

Jan 19, 2022
xyr is a very lightweight, simple and powerful data ETL platform that helps you to query available data sources using SQL.

xyr [WIP] xyr is a very lightweight, simple and powerful data ETL platform that helps you to query available data sources using SQL. Supported Drivers

Dec 2, 2022
Best lightweight, powerful and really fast Api with Golang (Fiber, REL, Dbmate) PostgreSqL

Best lightweight, powerful and really fast Api with Golang (Fiber, REL, Dbmate) PostgreSqL

Dec 26, 2021
Best simple, lightweight, powerful and really fast Api with Golang (Fiber, REL, Dbmate) PostgreSqL Database and Clean Architecture

GOLANG FIBER API (CLEAN ARCHITECTURE) Best simple, lightweight, powerful and really fast Api with Golang (Fiber, REL, Dbmate) PostgreSqLDatabase using

Sep 2, 2022
skr: The lightweight and powerful web framework using the new way for Go.Another go the way.
skr: The lightweight and powerful web framework using the new way for Go.Another go the way.

skr Overview Introduction Documents Features Install Quickstart Releases Todo Pull Request Issues Thanks Introduction The lightweight and powerful web

Jan 11, 2022