[GO] If there were no DI containers in the world.

This article is the 9th day article of Just a Group Advent Calendar 2019.

There are so many articles on the net that I rarely talk about DI containers. (~~ Fishing title ~~)

In this article, the purpose of DI container is,

Separation of object creation and use

Inversion of control

Talk about mm

If there is no DI container in the world and you have to manually do what the DI container is doing, let's actually write the code to see what kind of implementation is possible.

Prerequisite knowledge

What is a DI container?

A framework that provides applications with DI (Dependency Injection) functionality.

For details, please refer to the following articles.

What is a DI container DI / DI container, do you understand properly ...? -Qiita

What will you do

Why do you need DI in the first place? The very important purposes of DI are as follows.

1. Separate the creation and use of objects

This can also be called a separation of interests.

2. Inversion of control

In SOLID, it is the principle of dependency inversion.

DI is one way to achieve these goals.

Besides DI, there are various ways to achieve these.

In this article, I'll actually introduce some of the different ways to achieve the above objectives in the codebase.

What's wrong without "separating the creation and use of objects"?

1. The lack of separation of single responsibilities makes testing very difficult.

2. Object creation logic is scattered all over the place, sometimes creating significant duplication.

When I test the search method, a JobRepositoryImpleWithMysql instance is actually created, so If JobRepositoryImpleWithMysql is linked with DB, it is very difficult to test.


class SearchUseCase {
  def search = {
    val repositoryImpleWithMysql = new JobRepositoryImpleWithMysql
    ...........
  }
}

What's wrong without "inversion of control"?

1. By relying on the concrete class, if the concrete class is changed, it will need to be modified.

2. It is difficult to test when the concrete class has a connection with the DB.

If you change JobRepositoryImpleWithMysql to jobRepositoryImpleWithElasticSearch, The user side (SearchUseCase) also needs to make corrections.



class SearchUseCase {
  def search = {
    val repositoryImpleWithMysql = new JobRepositoryImpleWithMysql
    ...........
  }
}


class JobRepositoryImpleWithMysql {
  def getJobs: Seq[String] = Seq("job1", "job2")
}

A pattern that realizes "separation of object creation and use"

There are three main patterns. In this, I will introduce the ones in bold with the actual code.

--Dependency injection (DI) -** Constructor pattern **

** cake pattern ** and ** abstract factory pattern ** also realize "inversion of control", so let's first look at only the ** constructor pattern **.

The cake pattern seems to be a pattern peculiar to scala. (Reference)

Constructor pattern

package study.ConstractPattern

class Main {

  def main(args: Array[String]): Unit = {
    val useCase = new SearchUseCase(new JobRepositoryImpleWithMysql)
    useCase.search
  }
}

class SearchUseCase(jobRepositoryImpleWithMysql: JobRepositoryImpleWithMysql) {
  //JobRepositoryImple is delegated to SearchUseCase in the constructor argument.
  //Instead of injecting an object with a new JobRepositoryImple without searchUseCase
  //By injecting from the place where the SearchUseCase object is declared
  //Dependency injection can be done from the outside.
  //You can use this from the outside! Injecting an object.
  
  val jobRepositoryImpleInstance = jobRepositoryImpleWithMysql

  def search: Seq[String] = {
    jobRepositoryImpleInstance.getJobs
  }
}

trait JobRepository {
  def getJobs: Seq[String]
}

class JobRepositoryImpleWithMysql extends JobRepository {
  override def getJobs: Seq[String] = Seq("job1", "job2")
}

class jobRepositoryImpleWithElasticSearch extends JobRepository {
  override def getJobs: Seq[String] = Seq("job1", "job2")
}


As I wrote in the comment, by injecting the object to be used as the constructor argument, it is possible to separate the creation and use of the object.

What you can do

--By separating the object configuration logic from the normal execution process, the law of single responsibility is maintained. ――So it's easier to test

Things impossible

――In the end, you have to create a new object at the application execution timing, so the responsibility is concentrated there. --Since the concrete class is visible on the useCase side (directly calling the concrete class), if the implementation of the concrete class changes, the useCase side will also be affected. (This alone does not allow inversion of control)

ex) When jobRepositoryImpleWithMysql is changed to jobRepositoryImpleWithElasticSearch, you have to make some changes.

Pattern that realizes inversion of control

There are the following methods.

--Dependency injection (DI)

abstract factory pattern

package study.abstractFactoryPattern

class Main {
  def main(args: Array[String]): Unit = {
    val searchUseCase = new SearchUseCase
    searchUseCase.search
  }
}


//The user does not need to know about the concrete class.
//Control the creation of objects on the application side

class SearchUseCase {
  def search = {
    val repository: JobRepository = JobRepositoryFactory.createJobRepository
    repository.getJobs
  }
}

// abstract factory
trait AbstractFactory {
  def createJobRepository: JobRepository
}

// abstract product
trait JobRepository {
  def getJobs: Seq[String]
}

// concrete factory
object JobRepositoryFactory extends AbstractFactory {
  override def createJobRepository: JobRepository = new JobRepositoryImple
}

// concrete product
class JobRepositoryImple extends JobRepository {
  override def getJobs: Seq[String] = Seq("job1", "job2")
}


What you can do

--In factory.createJobRepository, by separating the execution process of the object creation part, it is possible to realize the separation for each interest. --As shown in the example below, the caller of the persistence layer does not need to know the concrete class. --repository.getJobs, so the caller can change the concrete class (for example, JobRepositoryImpleWithMysql-> JobRepositoryImpleWithElasticSearch) Not affected.

cake pattern

package study.cakePattern

class Main {
  def main(args: Array[String]): Unit = {
    //Injecting searchUseCase itself
    val useCase = ComponentRegistry.searchUseCase
    useCase.search
  }
}


//Abstract component
//Enclose it in components and create a namespace for each
trait JobRepositoryComponent {
  val jobRepository: JobRepository

  trait JobRepository {
    def search: Unit
  }
}

//Concrete components
//Enclose it in components and create a namespace for each
trait JobRepositoryComponentImple extends JobRepositoryComponent {
  class JobRepositoryImple extends JobRepository {
    override def search: Unit = println("create user")
  }
}

//Client components
//Declare dependency on UserRepository using self-type annotation
trait SearchUseCaseComponent { self: JobRepositoryComponent =>
  class SearchUseCase {
    def search = {
      //I want this jobRepository to be injected
      self.jobRepository.search
    }
  }
}

//Injector (role of DI container)
//SearchUseCaseComponent declares JobRepositoryComponent with self-type annotation, so
//JobRepositoryComponentImple must also be mixed in
object ComponentRegistry extends SearchUseCaseComponent with JobRepositoryComponentImple {
  override val jobRepository = new JobRepositoryImple
  val searchUseCase = new SearchUseCase
}

What you can do

In the search method of the SearchUseCase class, inversion of control is realized by relying on the abstract class (JobRepository) instead of depending on the concrete class (JobRepositoryImple).

Summary

We have seen several ways as described above. By using the DI container, I realized the convenience of the DI container because it performs complicated processing internally.

Are there any disadvantages to introducing a DI container?

Thank you until the end mm

Reference article

[Dependency Injection-Wikipedia](https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3% A8% E5% 85% A5)

Thinking: Design pattern (Abstract Factory pattern) --Qiita

Actual Scala: Dependency Injection using Cake pattern (DI) | eed3si9n

[DI (Cake Pattern Introduction) in Scala | TECHSCORE BLOG](https://www.techscore.com/blog/2012/03/27/scala%E3%81%A7di-%EF%BC%88cake-pattern% E5% B0% 8E% E5% 85% A5% E7% B7% A8% EF% BC% 89 /)

Recommended Posts

If there were no DI containers in the world.
Determining if there are birds in the image
Programming to fight in the world ~ 5-1
Programming to fight in the world ~ 5-5,5-6
Programming to fight in the world 5-3
Unfortunately there is no sense of unity in the where method
There is no switch in python
The most cited patent in the world
Programming to fight in the world ~ 5-2
If you get a no attribute error in boto3, check the version
Check if the URL exists in Python
Is there NaN in the pandas DataFrame?
If branch depending on whether there is a specific element in the list
Check if the characters are similar in Python
In bash, "Delete the file if it exists".
Isn't there a default value in the dictionary?
I want to win if there is the most useless visualization grand prix in the world ・ Learn visualization by evolving the OP function