dependancy_injection
dependancy_injection
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.
//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
}
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.
func main(){
logger := MyLogger{}
broker := MyMessageBroker{}
repository := MyRepository{}
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
func main(){
container := dig.New()
container.Provide(ProvideMyLogger)
container.Provide(ProvideMyRepository)
container.Provide(ProvideMyMessageBroker)
container.Provide(ProvideMyService)
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)