Game Changer: Procyon — Dependency Injection in Go (Part 2)
If you haven’t read my previous article about Procyon yet, I recommend you to read it before getting started with this article.
One of the most challenging aspects of developing applications in Go is managing the many dependencies an application uses. In this article, we’re gonna talk about how we can manage our dependencies by using Procyon and look into how it is provided.
I assume most of you have a piece of knowledge about dependency injection. Even if you haven’t heard of the term, likely, you have already used it. Before going deeper, I’m gonna explain it briefly.
What is Dependency Injection?
Dependency injection (DI) is a design pattern in which all dependencies should be given from outside so that it can be easy to manage and test the application. It achieves that by decoupling the usage of an object from its creation. This helps you to easily follow and implement SOLID principles.
To sum up, implementing dependency injection (DI) provides you the following advantages:
- Reusability of code
- Ease of code refactoring
- Ease of testing
Nowadays, several frameworks provide it so that you can focus on the implementation of your business logic.
However, it’s not easy to manage all your dependencies as you think while developing an application in Go. As you develop your application, it becomes bigger and not manageable.
Here’s how you would create an object dependency traditionally.
In the example above, we need to instantiate an instance of the Address within the NewUser function itself.
By using Dependency Injection, we can rewrite the example without creating an instance of Address that we want:
In the next parts, we’ll see how we can provide the instance of Address by using Procyon.
Here is one of the points where Procyon could be game changer.
How can we manage our dependencies by using Procyon?
In this part, we will look into how we can manage our dependencies in Procyon. Before going deeper, we better know a new term, Pea. Instances created or managed by the framework are called Pea, they are pretty similar to Spring Beans in Java.
Also, as I explained in the previous article, all types are considered as components and you need to register them to be managed by the framework. The only thing you need to is to invoke the core.Register function in the init function.
Note: It allow us to register the functions returning only one type .
So, let’s get started…
Dependency Injection by using types of struct
Now, let’s say we have a User struct containing an Address struct. Also, we assume we have functions to create their instances as follows.
So, how can we create our instances and provide their dependencies by using Procyon? I think this part is really easy. The only thing you need to do is to register your functions by using core.Register in the init function as follows.
While your application is started, your instances are created and their dependencies are provided by the framework. You can have a Procyon application as it’s shown in the following code snippet.
Note: The dependency injection is done through the types of function parameters, not the parameter names due to the restrictions of the reflection library.
Note: You can access your Peas from everywhere.
I assume you have done all steps which need to be done. As you can see, it’s easy to use the dependency injection in Procyon :) They are automatically managed and provided from the framework.
Dependency Injection by using embedded struct
Now, let’s say we have a Dog struct embedding an Animal struct. Also, we assume we have functions to create their instances as follows.
After that, we are defining the test structs and creating the functions that take the instances of Animal and Dog structs.
In the example above, we had registered our functions creating the instances. We also need to register our functions by using the core.Register in the same way.
What if we have Dog and Cat structs embedding the Animal struct as follows?
Besides, we assume that we have the functions taking the instances of Animal, Dog, and Cat structs as you can see in the following code snippet.
This time we run into some problems. Let’s examine what it is and how it can be solved. As I said before, the dependencies are injected through the types of function parameters due to the restrictions of the reflection library.
In this case, We have more than one instance embedding the Animal struct, thus the framework cannot distinguish which instance will be injected. Then, how can we solve this issue? Don’t be worried about it. I’m gonna explain it in the next articles as I want to keep this article simple and short.
Dependency Injection by using interfaces
Let’s say we have a Dog struct implementing an Animal interface as you can see in the following code snippet.
After that, we are defining the test structs and creating the functions that take the instances of Animal and Dog.
As I said before, while your application is started, your instances are created and their dependencies are provided by the framework. Just, don’t forget to register your functions by using core.Register.
Note: If we had multiple structs implementing the same interface, we would have the same problem we ran into in the example above. I’m gonna talk about how we can solve it in the next articles.
So, Can we manipulate our instances?
The most important thing is the fact that the framework allows us to manipulate and change them while our instances are created. However, I’m not gonna talk about it today. We will be talking about it in detail in the next articles :).
I hope you liked it.
You can find the code on Github. If you would like to contribute to the library or you find a bug, please report it because I’m looking for help on it.
I’m thinking of releasing its first stable version soon.
Thanks for reading.
To be continued…