Various concepts such as clean architecture and onion architecture have been proposed, I studied that and was frustrated. Because I didn't know much about DI and addiction in the first place. Yes, I was studying in the wrong order. Well, I'm glad I noticed it.
Therefore, I tried to summarize DI in as much detail as possible. After reading this article, you will definitely be able to understand and implement the benefits of ** DI **
DI is an abbreviation for Dependency Injection
To briefly explain the DI design pattern, Define the object as an interface, and the user should use the interface instead of the implementation object. Implementation objects can be replaced by injecting them into the interface from the outside.
DI container is a ** framework ** to help realize DI
As I was learning, there were words I didn't understand and I was very confused. So let's match the definitions of words first.
・ Where is it from the outside? From other objects!
・ What is addiction? A B object that requires an A object In other words, the A object knows the contents of the B object. For the time being, you may think that you are dependent on New
・ You say you don't depend on it, but you depend on it, right? You're using an A object with a B object injected into the A interface! !! Is this dependent? Right? I've always thought. This was the most annoying thing. To be precise, the B object depends on the A interface. So the B object does not depend on the A object. (Sometimes it depends on abstraction) What's good about this is that B objects can be implemented without knowing A objects. Details will be described later.
** "Why do you use DI?" This is the most important. ** ** I don't think it can be used even if I use DI without knowing this. It is important to always be aware of the ** purpose of using the method "for what". Then I will write that you can be happy by using DI.
** ・ Because it does not depend on the external DB, it is resistant to changes. ** ** Reason: Inject the implementation object through the interface, so The logic side that uses the interface can use it without being aware of the implementation object.
** ・ Easy unit testing ** Reason: Same as above, but by mocking the DB (like a fake DB) You can test without changing the logic on the side that uses the interface. Both of the results are problematic even if you switch because you do not know the outside (depend on it).
** ・ Simply makes coding easier. ** ** Reason: Suppose you have classes A and B. A needs B (dependence). In that case, A cannot be implemented without B. However, if A implements using B's interface, it can be implemented without B. Injecting a B object through the interface is the same as A using B.
So far, we have explained the concept and mechanism of DI. From here, we will look at the actual code and understand it. Even if you don't understand at first, you should be able to understand it if you read it many times! Actually I was.
The code to be created is made up of the following three. ・ Repository layer ・ ・ ・ Layer for connecting to DB ・ Service layer ・ ・ ・ Layer that calls repository ・ Call layer ・ ・ ・ Layer that operates the whole
First, let's take a look at the messy code that has dependencies. (* I think I wrote this code when I started programming.)
repository layer The created DB is given directly to User Repository. (Bad thing: repository layer depends on db. I know mysql)
package repository
type UserRepository struct {
db sql.DB
}
func NewUserRepository() *UserRepository {
//DB creation
db, _ := sql.Open("mysql", "root:@/database")
//Created DB directly to repository
return &UserRepository{*db}
}
service layer The repository is new and given directly to the service. (Bad point: service layer depends on repository.)
package service
type UserService struct {
ur repository.UserRepository
}
func NewUserService() *UserService {
//Repository creation
ur := repository.NewUserRepository()
//Created repository directly to service
return &UserService{*ur}
}
Call layer If you new service, repository and db will be created.
package main
func main() {
us := service.NewUserService()
//Gonyo Gonyo using us
}
I've seen bad code once. You can see that it depends a lot. Reconfirmed just in case, a dependency is a state in which an object knows the contents of an object.
From here, we will look at a very beautiful code that introduces DI. (* When I first see the code that uses DI, it looks very dirty ...)
I forgot one important thing before looking at the code. There are four ways to achieve DI. 1.Constructor Injection 2.Setter Injection 3.Interface Injection 4.Field Injection
This time we will use 1 Constructor Injection. I think this is the best for me.
repository layer In NewUserRepository, db is used as the constructor. Therefore, it does not depend on db. Also, return returns the interface. This also eliminates the dependency between the repository layer and the service layer.
package repository
type UserRepository interface {
CreateUser(ctx context.Context, user *model.User)
}
type userRepository struct {
db sql.DB
}
func NewUserRepository(db *sql.DB) UserRepository {
return &userRepository{*db}
}
func (ur *userRepository) CreateUser(ctx context.Context, user *model.User) {
ur.db.Query("INSERT INTO table name (column name 1),Column name 2,……)")
}
service layer NewUserService has a repository interface in the constructor. Therefore, it does not depend on the repository. Also, return returns the interface. This also eliminates the dependency between the service layer and the layer on the user side.
package service
type UserService interface {
CreateUser(ctx context.Context, user *model.User)
}
type userService struct {
ur repository.UserRepository
}
func NewUserService(ur repository.UserRepository) UserService {
return &userService{ur}
}
func (us *userService) CreateUser(ctx context.Context, user *model.User) {
us.ur.CreateUser(ctx, user)
}
Layer on the user side ur := repository.NewUserRepository(db) Any db can be accepted as long as it is a db.
us := service.NewUserService(ur) As long as ur (repository) is a repository, any repository can be accepted.
This makes it easy to replace it with another repository or DB at the time of testing. Since they do not know each other, there are no dependencies and changes can be made easily.
(* For convenience of explanation, the creation of db is written in main)
package main
func main() {
db, err := sql.Open("mysql", "root:@/database")
if err != nil {
panic(err.Error())
}
defer db.Close()
ur := repository.NewUserRepository(db)
us := service.NewUserService(ur)
//Gonyo Gonyo using us
}
I have explained about DI. By actually implementing it or putting it together in an article like this, The inside of my head was very refreshing. Also, even if I think I understand it, I feel that it may not be possible if it is implemented unexpectedly. If you are studying DI, please try to implement it yourself.
https://recruit-tech.co.jp/blog/2017/12/11/go_dependency_injection/ https://github.com/akito0107/dicon http://inukirom.hatenablog.com/entry/di-in-go
Recommended Posts