본문 바로가기
IT/DDD

DIP : Dependency Inversion Principle

by happyMoons 2023. 1. 1.

의존성 역전 원칙 (SOLID 원칙 중 “D” 에 속한다)

 

  • DIP : Dependency Inversion Principle
    • 1. DIP 용어를 알아본다.
    • 2. DIP 를 적용하지 않은 사례를 알아본다.
    • 3. DIP 를 적용한 사례를 알아본다.
    • 4. DIP 적용 시 주의사항을 알아본다.

 

1. DIP 용어를 알아본다.

DIP 에는 “고수준 모듈”과 “저수준 모듈” 이라는 용어가 나오기 때문에 해당 용어에 대한 의미를 알아야 한다.

  • 고수준 모듈

의미 있는 단일 기능을 제공하는 모듈이다.

  • 저수준 모듈

고수준 모듈에서 정의한 기능을 실제로 구현하는 모듈이다.

 

2. DIP 를 적용하지 않은 사례를 알아본다.

위 용어를 특정 사례로 보면 아래와 같다.

DIP 원칙을 지키지 않고, 위 사례에 대해 코드를 작성한다면 아래와 같이 코드를 작성할 것이다.

  • 1번 기능을 위해 고수준 모듈 service 를 만든다.
  • 2, 3번 기능을 처리하기 위한 서비스 코드를 각각 만든다.
  • 1번 service 에서 2번, 3번 코드를 호출한다.

위 글을 그림으로 보면 아래와 같다.

 

위 그림을 코드로 보면 아래와 같다.

class 고수준모듈service(
	private val jpaService: JpaService,
	private val emailSender: EmailSender
) {

	fun 계좌내역을_조회하고_전송한다() {
		// 계좌 내역 조회
		val 계좌내역리스트 = jpaService.get()

		// 계좌 내역 전송
		emailSender.send(계좌내역리스트)
	}	

}

만약, 요구사항이 바뀌어 fax 로도 보내야한다면?

혹은 계좌내역 리스트 조회를 Jpa 가 아닌 Mybatis 를 이용해야한다면?

 

고수준모듈service 클래스의 코드를 변경해야 한다.

 

3. DIP 를 적용한 사례를 알아본다.

보통 저수준의 기능에 대한 요구사항이 자주 바뀌게 된다. (보통 그렇다는 것이지 100%는 아니다.)

고수준 모듈 → 저수준 모듈 의존

 

그래서 고수준 모듈을 변경할 일이 생기게 되는데 DIP 를 적용하면 고수준 모듈을 변경하지 않도록 할 수 있다.

저수준 모듈 → 고수준 모듈 의존

 

위 특정 사례를 DIP 로 적용한 그림을 먼저 보자.

 

고수준 서비스에서 필요한 기능들을 인터페이스로 추상화 한 뒤에 의존한다. (고수준 → 고수준 의존)

저수준은 이제 고수준의 인터페이스를 의존하게 된다. (저수준 → 고수준 의존)

 

코드로 보면 아래와 같다.

 

class 고수준모듈을 호출해서 사용하는 service {
	...
	// jpa, email 
	val foo = 고수준모듈service(JpaRepository, EmailSender)

	// jpa, fax
	val bar = 고수준모듈service(JpaRepository, FaxSender)
}

// 요구사항이 추가되어도 고수준모듈service 내부는 전혀 바꿀 필요가 없다.
class 고수준모듈service(
	private val db조회용Repo: Db조회용Repo,
	private val 계좌내역전송Sender: 계좌내역전송Sender
) {

	fun 계좌내역을_조회하고_전송한다() {
		// 계좌 내역 조회
		val 계좌내역리스트 = db조회용Repo.get()

		// 계좌 내역 전송
		계좌내역전송Sender.send(계좌내역리스트)
	}	

}

 

4. DIP 적용 시 주의사항을 알아본다.

그런데, 인터페이스로 추상화 레이어를 추가했다고 해서 갑자기 고수준이 되는것인가? 라고 의문이 들 수도 있다.

조금 애매할 수도 있는데… 관점에 따라서 다르다고 한다.

 

위 고수준 service 를 관점으로 “계좌 내역을 조회한다” , “계좌내역을 보낸다” 라는 기능을 위해

인터페이스를 추출했다면 “고수준” 이 되는것이고,

(왜냐하면, “조회한다”와 “보낸다” 는 고수준 모듈의 정의인 의미있는 단일 기능이기 때문이다.)

 

만약, 저수준 EmailSender 의 관점에서 인터페이스를 만든다면 EmailI 가 되었을 것이고,

고수준 service 에서는 fax 보내기 기능이 필요하면 고수준service 코드 내부에

또 FaxI 를 추가해서 사용해야 될 수도 있을것이다.