silbaram 2025. 6. 24. 21:13
728x90

 

 

 

1. DIP란 무엇인가?

1-1. 공식 정의

  • 정의 ― “고수준 모듈은 저수준 모듈의 구현 세부사항이 아니라 추상화(인터페이스·추상 클래스) 에 의존해야 한다”.
  • 추가 규칙 ― “추상화는 세부사항에 의존하지 말고, 세부사항이 추상화에 의존하도록 뒤집어라”.

1-2. 역사와 기원

Robert C. Martin(‘Uncle Bob’)이 Xerox 프린터 관리 시스템에서 “Job” 클래스가 하위 드라이버 로직에 엉겨 붙어 빌드·배포가 지연되던 문제를 해결하며 제안.


2. ‘전통적 의존성’의 문제점

증상 설명

수정 전파 하위 모듈 내부 변경이 상위 서비스까지 연쇄 수정·재배포를 유발
테스트 어려움 구체 클래스 결합으로 목(mock)‧스텁 교체가 번거로움
재사용성 저하 DB·파일·네트워크 등 구현에 종속된 로직은 다른 환경 재사용이 어려움

3. DIP 적용 전략 4선

3-1. 추상화 계층 도입

기능 경계마다 Port(interface) ↔ Adapter(implementation) 구조를 만들어 상·하위 모듈을 분리한다. 이는 헥사고날 아키텍처의 핵심 아이디어이기도 하다.

3-2. DI(Dependency Injection)로 추상화 주입

DIP가 “무엇을”(추상화에 의존) 말한다면 DI는 “어떻게” 객체를 주입할지(생성자/메서드/필드) 해법을 제공한다. 스프링·Guice 같은 IoC 컨테이너를 사용하면 DIP를 자연스럽게 실천할 수 있다.

3-3. 레이어 경계 맞춤

서비스 → 도메인 → 인프라 순으로 “안쪽이 바깥을 모른다”는 방향성을 지키면 하위 기술 교체가 상위 모듈에 영향을 주지 않는다.

3-4. 패키지·모듈 분리

core(비즈니스 규칙)·infra(구현) 모듈을 분리하고, 공통 인터페이스만 core에 위치시켜 교차 의존을 차단한다.


4. 코드로 보는 DIP

4-1. Java 예제 (결제 시스템)

// 추상화 (Port)
public interface PaymentGateway {
    void pay(int amount);
}

// 고수준 모듈
public class CheckoutService {
    private final PaymentGateway gateway;
    public CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
    public void checkout(int price) { gateway.pay(price); }
}

// 저수준 모듈 (Adapter)
public class StripeGateway implements PaymentGateway {
    public void pay(int amount) { /* Stripe API 호출 */ }
}

CheckoutService(비즈니스) 는 StripeGateway 가 아닌 PaymentGateway 인터페이스에만 의존한다.

4-2. Kotlin 예제 (모바일 알림)

// Port
interface Notifier { fun send(msg: String) }

// Core
class AlarmService(private val notifier: Notifier) {
    fun trigger() = notifier.send("Wake up!")
}

// Adapters
class SmsNotifier : Notifier { override fun send(msg: String) { /* SMS */ } }
class EmailNotifier : Notifier { override fun send(msg: String) { /* Email */ } }

알림 채널 교체가 AlarmService 에 영향 없이 가능해 테스트·확장이 쉬워진다.


5. DIP와 DI 컨테이너의 시너지

스프링 Boot 예를 들면 @Configuration 에서 PaymentGateway 구현을 주입(Bean 등록)함으로써 상위 모듈이 구현체를 몰라도 동작한다. 컨테이너는 객체 생명주기·스코프·프로파일 분기를 제공해 DIP를 실용적으로 구현한다.


6. 다른 SOLID 원칙과의 관계

원칙 시너지 포인트

SRP 비즈니스 로직과 기술 세부사항을 분리해 각자의 단일 책임 유지
OCP 추상화 추가만으로 새 구현을 플러그인할 수 있어 폐쇄·확장성 달성
ISP 세분화된 인터페이스가 DIP 적용 시 과도한 의존을 방지

7. 실전 체크리스트

  • 비즈니스 레이어가 jdbc.*, aws.* 같은 구체 패키지를 직접 import 하고 있지 않은가?
  • 단위 테스트에서 실제 DB·네트워크를 꼭 띄워야만 동작하는가?
  • 인터페이스가 구현체와 같은 패키지에 뒤섞여 있지 않은가?
  • 컨테이너 없는 ‘순수 객체’ 상태에서도 비즈니스 로직이 실행 가능한가?

 

728x90