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,
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.
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
Why do you need DI in the first place? The very important purposes of DI are as follows.
This can also be called a separation of interests.
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.
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
...........
}
}
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")
}
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)
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.
--By separating the object configuration logic from the normal execution process, the law of single responsibility is maintained. ――So it's easier to test
――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.
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")
}
--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
}
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).
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
[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