Skip to content

Commit

Permalink
v3
Browse files Browse the repository at this point in the history
v3
  • Loading branch information
miladrahimi authored Aug 1, 2021
1 parent e7dfae9 commit d0f798f
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 203 deletions.
110 changes: 79 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,47 @@
[![Coverage Status](https://coveralls.io/repos/github/golobby/container/badge.svg?branch=master)](https://coveralls.io/github/golobby/container?branch=master)

# Container
A lightweight yet powerful IoC dependency injection container for Go projects.
It provides an easy-to-use interface and performance-in-mind dependency injection container to be your ultimate requirement.
GoLobby Container is a lightweight yet powerful IoC dependency injection container for Go projects.
It is an easy-to-use and performance-in-mind dependency injection container to be your ultimate requirement.

## 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.
To install this package, run the following command in your project directory.

```bash
go get github.com/golobby/container/v2
go get github.com/golobby/container/v3
```

### Introduction
This package is an IoC (dependency injection) container. It can bind abstractions to implementations.
Binding is the process of introducing appropriate concretes (implementations) to an IoC container.
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 reuses in the rest of the injections.
In transient bindings, the container always returns a brand new instance for each injection.
After the binding process, you can ask the IoC container for the appropriate implementation of the abstraction.
Then your code will depend on abstractions, not implementations.
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.

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

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

### Binding
### Typed Binding

#### Singleton

Expand All @@ -56,7 +57,7 @@ err := container.Singleton(func() Abstraction {
})
```

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

Example for singleton binding:

Expand All @@ -66,7 +67,7 @@ err := container.Singleton(func() Database {
})
```

In the example above, the container makes a MySQL instance once and returns it for all requests.
In the example above, the container makes a MySQL instance once and returns it for all the requests.

#### Transient

Expand All @@ -80,34 +81,70 @@ err := container.Transient(func() Shape {
})
```

In the example above, the container always returns a brand new Rectangle instance for each request.
In the example above, the container always returns a brand-new Rectangle instance for each request.

### 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 a name into account as well.
The rest is similar to typed bindings.
The following examples demonstrate named bindings.

```go
// 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{}
})
```

### Resolving

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

#### Using References

The `Bind()` method takes a reference of the abstraction type and binds it to the appropriate concrete from the container.
The `Resolve()` method takes reference of the abstraction type and fills it with the appropriate concrete.

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

Example of resolving using refrences:
Example of resolving using references:

```go
var m Mailer
err := container.Bind(&m)
err := container.Resolve(&m)
// `m` will be an implementation of the Mailer interface
m.Send("contact@miladrahimi.com", "Hello Milad!")
```

Example of named resolving using references:

```go
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 function (receiver) with arguments of abstractions you need and invokes it with parameters of appropriate concretes from the container.
The `Call()` method takes a function (receiver) with arguments of abstractions you need.
It will invoke it with parameters of appropriate concretes.

```go
err := container.Call(func(a Abstraction) {
Expand All @@ -124,7 +161,7 @@ err := container.Call(func(db Database) {
})
```

You can also resolve multiple abstractions like tho follwing example:
You can also resolve multiple abstractions like the following example:

```go
err := container.Call(func(db Database, s Shape) {
Expand All @@ -133,6 +170,8 @@ err := container.Call(func(db Database, s Shape) {
})
```

Since Go reflection doesn't let us know function parameter names, the `Call()` method cannot resolve named concretes.

#### Using Structs

The `Fill()` method takes a struct (pointer) with fields of abstractions you need and fills the fields.
Expand All @@ -141,19 +180,27 @@ Example of resolving using Structs:

```go
type App struct {
m Mailer `container:"inject"`
d Database `container:"inject"`
mailer Mailer `container:"type"`
sql Database `container:"name"`
noSql Database `container:"name"`
x int
}

myApp := App{}

err := container.Fill(&myApp)
// `myApp.m` will be an implementation of the Mailer interface
// `myApp.s` will be an implementation of the Database interface
// `myApp.x` will be ignored since it has no `container:"inject"` tag
// [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.x` will be ignored since it has no `container` tag
```

As for named bindings (struct fields with `container: "name"` tag), field names must be the same binding name.

#### Binding time

You can resolve dependencies at the binding time if you need previous bindings for the new one.
Expand All @@ -176,14 +223,15 @@ err := container.Singleton(func(c Config) Database {
})
```

### Standalone instance
### Standalone Instance

In default, the Container keeps your bindings in the global 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 this example:

```go
c := container.New() // returns a container.Container
// returns a container.Container (a Container instance)
c := container.New()

err := c.Singleton(func() Database {
return &MySQL{}
Expand Down
32 changes: 19 additions & 13 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package container

import (
internal "github.com/golobby/container/v2/pkg/container"
internal "github.com/golobby/container/v3/pkg/container"
)

// New creates a new standalone instance of Container
Expand All @@ -21,36 +21,42 @@ func Singleton(resolver interface{}) error {
return container.Singleton(resolver)
}

// NamedSingleton binds like the Singleton method but for named bindings.
func NamedSingleton(name string, resolver interface{}) error {
return container.NamedSingleton(name, resolver)
}

// Transient will bind an abstraction to a concrete for further transient resolves.
// It takes a resolver function which returns the concrete and its return type matches the abstraction (interface).
// The resolver function can have arguments of abstraction that have bound already in Container.
func Transient(resolver interface{}) error {
return container.Transient(resolver)
}

// NamedTransient binds like the Transient method but for named bindings.
func NamedTransient(name string, resolver interface{}) error {
return container.NamedTransient(name, resolver)
}

// Reset will reset the container and remove all the existing bindings.
func Reset() {
container.Reset()
}

// Make will resolve the dependency and return a appropriate concrete of the given abstraction.
// It can take an abstraction (interface reference) and fill it with the related implementation.
// It also can takes a function (receiver) with one or more arguments of the abstractions (interfaces) that need to be
// resolved, Container will invoke the receiver function and pass the related implementations.
// Deprecated: Make is deprecated.
func Make(receiver interface{}) error {
return container.Make(receiver)
}

// Call takes a function with one or more arguments of the abstractions (interfaces) that need to be
// resolved, Container will invoke the receiver function and pass the related implementations.
func Call(receiver interface{}) error {
return container.Call(receiver)
}

// Bind takes an abstraction (interface reference) and fill it with the related implementation.
func Bind(receiver interface{}) error {
return container.Bind(receiver)
// Resolve takes an abstraction (interface reference) and fill it with the related implementation.
func Resolve(abstraction interface{}) error {
return container.Resolve(abstraction)
}

// NamedResolve resolves like the Resolve method but for named bindings.
func NamedResolve(abstraction interface{}, name string) error {
return container.NamedResolve(abstraction, name)
}

// Fill takes a struct and fills the fields with the tag `container:"inject"`
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/golobby/container/v2
module github.com/golobby/container/v3

go 1.11

Expand Down
Loading

0 comments on commit d0f798f

Please sign in to comment.