0% found this document useful (0 votes)
4 views

dependancy_injection

dependency injection

Uploaded by

kurush kosimi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

dependancy_injection

dependency injection

Uploaded by

kurush kosimi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 4

Dependency Injection is a design pattern, that helps you to decouple the external

logic of your implementation. It's common an implementation needs an external API,


or a database, etc. It is not responsibility of the implementation to know these
things, it should receive its dependencies and use them as it needs. Let's say you
have an implementation with the dependencies below.
Logging Message Broker
Service/Use Case

Data Access External Service Time Provider

You could just create the dependencies and set them into your Service/Use case. But
it would be bad especially for testing, how would you change the behavior for an
external service? Or a database? You can't. Your only option is to write an
integration test instead of unit tests.
An important point of injecting dependencies is to avoid injecting implementations
(structs), you should inject abstractions (interfaces). It's the letter of
S.O.L.I.D: Dependency Inversion Principle. It allows you to switch easily the
implementation of some depedency and, you could change the real implementation for
a mock implementation. It's fundamental for unit testing.

Kinds of Dependency Injection


There are some kinds of dependency injection, and each one has its own use case.
Here we'll cover three of them: Constructor, Property and, Method(or Setter).
The most common kind is the Constructor Injection. It allows you to make your
implementation immutable, nothing can change the dependencies(if your properties
are private). Also, it requires all dependencies to be ready to create something.
If they are'nt, it usually will generate an error.

//Constructor Injection
func MyService(logger MyLogger, repository MyRepository, broker MyMessageBroker)
*MyService{
return &MyService{
logger: logger,
broker: broker,
repository: repository,
}
}

Property and Method injection are pretty similar, I think their adoption is a
question of a language feature. In Go, we will see the usage of both. These kinds
allow you to change dependencies runtime, so by design, they aren't immutable. But
if you need to change the implementation of some dependency, you don't need to
recreate everything. You can just override what you need. It may be useful if you
have a feature flag that changes an implementation inside your service.

//Method Injection
type MyService struct {
logger MyLogger
repository MyRepository
broker MyMessageBroker
}

func (s *Service) SetLogger(logger MyLogger) {


s.Logger = logger
}

How do I do it?
There are two main ways to get it done in practice. The first one is manually, and
the other (and prettier) is using a dependency injection container.
Manually
Manually construction is an objective way to do it. You declare, create, and inject
your dependencies step by step. I think it’s clean and there isn’t any magic
happening behind the scenes. The problem is as your dependencies get complex you
need to deal with complexity by yourself. You may see your func main() getting with
hundreds of lines of code and harder to maintain.

type MyService struct{


logger MyLogger
repository MyRepository
broker MyMessageBroker
}

func MyService(logger MyLogger, repository MyRepository, broker MyMessageBroker)


*MyService{
return &MyService{
logger: logger,
broker: broker,
repository: repository,
}
}

func main(){
logger := MyLogger{}
broker := MyMessageBroker{}
repository := MyRepository{}

service := MyNewService(logger, repository, broker)


...
}

Container
In a container style, you'll need to teach your container how to create a
dependency and then it'll create your dependency graph to discover how to create
dependencies. Once you ask for a dependency, it'll follow the graph creating
everything related to it. Let's imagine a scenario where we need to create two
Services/Use cases, and they have dependencies between them and across them:

Service A(repository A -> MySQL Connection -> Cloud Provider Client) -> Time
Provider
Service B(repository B -> MongoDB Connection -> Cloud Provider Client) -> Cloud
Provider Client

Once you've taught your container how to create each dependency, it'll create a
dependency graph, which would be something like that:

Cloud Provider Client -> MySQL Connection -> repository A -> Service A
Cloud Provider Client -> MongoDB Connection -> repository B -> Service B -> Service
A
Cloud Provider Client -> Service B
Time Provider -> Service A

uber-go/dig is a dependency injection toolkit developed by Uber to resolve this


kind of problem, with reflection. It’s really powerful and helps you to reduce the
code you write to setup your service. It’s important to say it’s focused on the
application startup, there are other containers such sarulabs/di, that may also
deal with dependency lifecycle (e.g. one instance per request). If you’re
interested in this kind of dependency injection, take a look at it. By design,
uber-go/dig understands that everything is a Singleton and it'll create your
dependencies just once.
All parameters for uber-go/dig are interface{}, but you can't pass what you want.
It must be a func that receives N parameters and it may return N results too, also
an error. By reflection, it reads the type of each parameter your func receives and
returns, and with that, it can build the dependency graph we discussed above. If
some func returns an error, it'll return an error when you try to call it too. It's
useful if you tried to connect to some database and it isn't up.

Using Constructor Injection, we could write something like that:

func main(){
container := dig.New()

container.Provide(ProvideMyLogger)
container.Provide(ProvideMyRepository)
container.Provide(ProvideMyMessageBroker)

container.Provide(ProvideMyService)

myProcess := func(myService MyService){


myService.Do(context.Background())
}

if err := container.Invoke(myProcess); err := nil{


panic(err)
}
}

func ProvideMyService(l MyLogger, r MyRepository, b MyMessageBroker) MyService{


return &SomeService{
logger: l,
repo: r,
broker: b,
}
}

func ProvideMyLogger() MyLogger{


return &SomeLogger{}
}

func ProvideMyRepository(logger MyLogger) MyRepository{


return &SomeRepository{}
}

func ProvideMyMessageBroker(logger MyLogger) MyMessageBroker{


return &SomeMessageBroker{}
}

uber-go/dig also supports Property Injection, this strategy allows you to create
modules of your base dependencies. Every service needs some logging configuration,
some database connection, messages brokers, a cloud provider client and etc. You
could abstract these kinds of dependency to an internal library of your company,
and let your services provide just their own dependencies. It'll remove a lot of
duplicated code from your microservices. To do it, you need to create a struct with
dig.In embedded, it'll say to the container that it must fill all properties with
the values you provided. In the example below, when the container tries to create
MyArgs it'll see dig.In is embedded on it, so it'll set the value you provided in
Service and Logger properties.
type MyArgs struct{
dig.In

Logger MyLogger
Service MyService
}

func main(){
container := dig.New()

container.Provide(ProvideMyLogger)
container.Provide(ProvideMyRepository)
container.Provide(ProvideMyMessageBroker)

container.Provide(ProvideMyService)

myProcess := func(myService MyService){


myService.Do(context.Background())
}

if err := container.Invoke(myProcess); err := nil{


panic(err)
}
}

You might also like