GithubHelp home page GithubHelp logo

golobby / container Goto Github PK

View Code? Open in Web Editor NEW
533.0 8.0 35.0 122 KB

A lightweight yet powerful IoC dependency injection container for the Go programming language

License: MIT License

Go 100.00%
ioc ioc-container container dependency-injection go golang golang-package di-container di-framework inversion-of-control

container's Introduction

Go Reference CI CodeQL Go Report Card Coverage Status Mentioned in Awesome Go

Container

GoLobby Container is a lightweight yet powerful IoC (dependency injection) container for Go projects. It's built neat, easy-to-use, and performance-in-mind to be your ultimate requirement.

Features:

  • Singleton and Transient bindings
  • Named dependencies (bindings)
  • Resolve by functions, variables, and structs
  • Must helpers that convert errors to panics
  • Optional lazy loading of bindings
  • Global instance for small applications
  • 100% Test coverage!

Documentation

Required Go Versions

It requires Go v1.11 or newer versions.

Installation

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

go get github.com/golobby/container/v3

Next, include it in your application:

import "github.com/golobby/container/v3"

Introduction

GoLobby Container is used to bind abstractions to their implementations. Binding is the process of introducing appropriate concretes (implementations) of abstractions to an IoC container. In this process, you also determine the resolving type, singleton or transient. In singleton bindings, the container provides an instance once and returns it for all the requests. In transient bindings, the container always returns a brand-new instance for each request. After the binding process, you can ask the IoC container to make the appropriate implementation of the abstraction that your code needs. Then your code will depend on abstractions, not implementations!

Quick Start

The following example demonstrates a simple binding and resolving.

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

var c Config
err := container.Resolve(&c)
// `c` will be the instance of JsonConfig

Typed Binding

Singleton

The following snippet expresses singleton binding.

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

// If you might return an error...

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

It takes a resolver (function) whose return type is the abstraction and the function body returns the concrete (implementation).

The example below shows a singleton binding.

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

Transient

The example below shows a transient binding.

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

Named Bindings

You may have different concretes for an abstraction. In this case, you can use named bindings instead of typed bindings. Named bindings take the dependency name into account as well. The rest is similar to typed bindings. The following examples demonstrate some named bindings.

// Singleton
err := container.NamedSingleton("square", func() Shape {
    return &Rectangle{}
})
err := container.NamedSingleton("rounded", func() Shape {
    return &Circle{}
})

// Transient
err := container.NamedTransient("sql", func() Database {
    return &MySQL{}
})
err := container.NamedTransient("noSql", func() Database {
    return &MongoDB{}
})

Resolver Errors

The process of creating concrete (resolving) might face an error. In this case, you can return the error as the second return value like the example below.

err := container.Transient(func() (Shape, error) {
  return nil, errors.New("my-app: cannot create a Shape implementation")
})

It could be applied to other binding types.

Resolving

Container resolves the dependencies with the Resolve(), Call(), and Fill() methods.

Using References

The Resolve() method takes reference of the abstraction type and fills it with the appropriate concrete.

var a Abstraction
err := container.Resolve(&a)
// `a` will be an implementation of the Abstraction

Example of resolving using references:

var m Mailer
err := container.Resolve(&m)
// `m` will be an implementation of the Mailer interface
m.Send("[email protected]", "Hello Milad!")

Example of named-resolving using references:

var s Shape
err := container.NamedResolve(&s, "rounded")
// `s` will be an implementation of the Shape that named rounded

Using Closures

The Call() method takes a receiver (function) with arguments of abstractions you need. It calls it with parameters of appropriate concretes.

err := container.Call(func(a Abstraction) {
    // `a` will be an implementation of the Abstraction
})

Example of resolving using closures:

err := container.Call(func(db Database) {
  // `db` will be an implementation of the Database interface
  db.Query("...")
})

You can also resolve multiple abstractions like the following example:

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

You are able to raise an error in your receiver function, as well.

err := container.Call(func(db Database) error {
  return db.Ping()
})
// err could be `db.Ping()` error.

Caution: The Call() method does not support named bindings.

Using Structs

The Fill() method takes a struct (pointer) and resolves its fields.

The example below expresses how the Fill() method works.

type App struct {
    mailer Mailer   `container:"type"`
    sql    Database `container:"name"`
    noSql  Database `container:"name"`
    other  int
}

myApp := App{}

err := container.Fill(&myApp)

// [Typed Bindings]
// `myApp.mailer` will be an implementation of the Mailer interface

// [Named Bindings]
// `myApp.sql` will be a sql implementation of the Database interface
// `myApp.noSql` will be a noSql implementation of the Database interface

// `myApp.other` will be ignored since it has no `container` tag

Binding time

You can resolve dependencies at the binding time if you need previous dependencies for the new one.

The following example shows resolving dependencies at binding time.

// 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 an instance of `JsonConfig`
    return &MySQL{
        Username: c.Get("DB_USERNAME"),
        Password: c.Get("DB_PASSWORD"),
    }
})

Standalone Instance

By default, the Container keeps your bindings in the global instance. Sometimes you may want to create a standalone instance for a part of your application. If so, create a standalone instance like the example below.

c := container.New()

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

err := c.Call(func(db Database) {
    db.Query("...")
})

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

Must Helpers

You might believe that the container shouldn't raise any error and/or you prefer panics. In this case, Must helpers are for you. Must helpers are global methods that panic instead of returning errors.

c := container.New()
// Global instance:
// c := container.Global

container.MustSingleton(c, func() Shape {
    return &Circle{a: 13}
})

container.MustCall(c, func(s Shape) {
    // ...
})

// Other Must Helpers:
// container.MustSingleton()
// container.MustSingletonLazy()
// container.MustNamedSingleton()
// container.MustNamedSingletonLazy()
// container.MustTransient()
// container.MustTransientLazy()
// container.MustNamedTransient()
// container.MustNamedTransientLazy()
// container.MustCall()
// container.MustResolve()
// container.MustNamedResolve()
// container.MustFill()

Lazy Binding

Both the singleton and transient binding calls have a lazy version. Lazy versions defer calling the provided resolver function until the first call. For singleton bindings, It calls the resolver function only once and stores the result.

Lazy binding methods:

  • container.SingletonLazy()
  • container.NamedSingletonLazy()
  • container.TransientLazy()
  • container.NamedTransientLazy()

Performance

The package Container inevitably uses reflection for binding and resolving processes. If performance is a concern, try to bind and resolve the dependencies where it runs only once, like the main and init functions.

License

GoLobby Container is released under the MIT License.

container's People

Contributors

amirrezaask avatar cililing avatar dependabot[bot] avatar fifsky avatar miladrahimi avatar pavel-durov avatar place1 avatar sedhossein avatar vanodevium avatar wirecat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

container's Issues

Add Must method variants

great library, would you reconsider this issue #4 and add "must" variants for the container methods.

container.MustSingleton
container.MustTransient

etc.

all container methods that can return an error and are typically called in main() or init() could have a "must" variant.

the must variant panics if an error occurs instead of returning it.

for example, in my application I register dependencies with the container in main() and the "if err != nil { panic(err) }" handling makes the code harder to read but if we leave it out then invalid container configuration can silently occur.

Support synchronization of accessing container bindings

It will be nice to be able to register and access container bindings asynchronously (e.g. registration of some new binding during application run while some another parts of app resolves existent bindings). Probably some synchronized version of Container can be created for such a purposes instead of changing existent one, but anyway it will be useful feature.

README documentation is not up to date

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

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 :)

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,

Support multiple binding

The resolver function could return multiple implementations so it can bind some abstractions at once and prevent panic.

go.work issue

not work if i add go.work file with inner dependencies

Implicit binding

It could be nice if it supports receiving implementations when the requested type is implementation not interface.

Singleton resolvers that return error during resolution should not cache the concrete instance

Description

Singleton resolvers that return error during resolution should not cache the concrete instance

Steps to Reproduce

Given the following Go struct

type Foo struct {
    Value string
}

func NewFoo(value string) *Foo {
  return &Foo{ Value: value }
}

When using the container in this package if I register a resolver for a singleton where it could conditionally fail based on some expectation:

container.Singleton(func() (*Foo, error) {
  if expectationsAreMet {
    return NewFoo("test"), nil
  } else {
    return nil, errors.New("cannot create this type right now")
  }
})

Then when the caller attempts to re-resolve the type the cached nil value is returned instead of the resolver trying again.

var instance *Foo
err := container.Resolve(&instance) // Resolution can fail here if external expectation is not met
if err != nil {
  // Perform some bootstrapping to meeting required expectation
  external.MeetExpectation()
  
  // Try to resolve the type again after expectations are met
  err = container.Resolve(&instance) // Resolver should try again now that expectation has been met.
}

Expected

The registered singleton resolver should run again since the original resolution failed and return an error

Actual

The nil value is stored as the concrete value for the singleton and is returned on subsequent Resolve(...) calls.

Any plans to support child or cloned containers?

A feature that would be extremely useful would be extremely useful would be to support the creation of child or cloned containers. A child container is more complex, being an empty container with a reference to a parent container and walking up the hierarchy as it tries to resolve types, but a clone would just duplicate existing bindings at the point it was cloned and then set itself as the Container object binding in place of the original container.

The use case for this is to allow for a "scoped" container to be created. For example when handling a HTTP request objects like a context.Context generated by a HTTP request, as well as the Request and ResponseWriter could be populated to a child/clone container while handling the request and then the Call method could be used to populate required Classes for a handler method.

will this project detect circular dependency?

i am looking for an ioc container that can solve circular dependency.

i cannot find source code which do detect circular dependency from the project.

anyway, this project design is a very useful example for me, thank you!

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

Integration with Viper

Hi there,

This is a great component to what I'm looking for... :) I'd like to integrate VIper with Container:

  1. Users create configuration files, say in yaml
  2. In order to load the properties, I'd like to have a strongly-typed representation using structs that directly maps to the files
  3. As a developer, I'd like to get the singleton instance of this struct so I can use it

I'd like to do something similar to ConfigurationProperties of SpringBoot from https://docs.spring.io/spring-boot/docs/2.1.13.RELEASE/reference/html/boot-features-external-config.html

Has someone worked on something similar?

Thank you

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!

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

Question: Resolve multiple objects

Is there a way to resolve multiple items by its common interface?

Example:
IPlugin abstract
PluginA concrete
PluginB concrete
register both plugins by IPlugin
resolve []IPlugin where both plugins will be returned

How to get all named instances?

We have some bindings of named implementations:

container.NamedSingleton("square", func() Shape {
    return &Rectangle{}
})
container.NamedSingleton("rounded", func() Shape {
    return &Circle{}
})

How to get all registered implementations of Shape?
Something like this:

var shapes map[string]Shape
container.ResolveAll(&shapes)

if this is not implemented, then it seems to me it would be a cool feature.

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 :)

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

return values[0].Interface(), values[1].Interface().(error)

Can you please look into it?

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.