의존성 주입 (Dependency Injection)
쉽게 풀면
카페 바리스타가 커피를 내리려면 원두가 필요합니다. 그런데 바리스타가 매번 직접 원두를 재배하고 수확한다면 커피 한 잔 만드는 데 몇 달이 걸릴 것입니다. 대신 원두 공급 업체가 필요한 원두를 가져다주면, 바리스타는 커피를 내리는 본연의 일에만 집중할 수 있습니다.
의존성 주입도 마찬가지입니다. 코드 안의 한 부품(객체)이 다른 부품을 직접 만들어 쓰는 대신, 외부에서 완성된 부품을 "주입"받아 사용하게 만들어서, 각 부품이 자기 역할에만 집중하고 부품 교체도 쉬워지게 합니다.
왜 중요한가
소프트웨어공학 논문에서 의존성 주입은 단위 테스트 가능성, 유지보수성, 모듈 재사용성을 논할 때 빠지지 않는 개념입니다. 대규모 시스템일수록 모듈 간 결합도가 낮아야 요구사항 변경이나 컴포넌트 교체에 유연하게 대응할 수 있기 때문에, 마이크로서비스 아키텍처나 프레임워크 설계 연구에서 핵심 설계 원리로 다뤄집니다. 또한 자동화된 테스트, 지속적 통합(CI) 파이프라인의 신뢰성을 높이는 실무적 기반으로도 자주 인용됩니다.
어떤 절차인가
- 의존 대상을 추상(인터페이스)으로 정의합니다. 예컨대 “저장소”라는 인터페이스만 정해 두고 실제로 데이터베이스인지 파일인지는 구분하지 않습니다.
- 사용하는 객체는 그 의존 대상을 스스로 만들지 않고 밖에서 받습니다. 받는 방식이 세 가지입니다. 생성자 주입(객체를 만들 때 인수로 받음), 세터 주입(만든 뒤 메서드로 설정), 인터페이스 주입(Fowler, 2004). 객체가 완성될 때 의존 관계가 확정되는 생성자 주입이 널리 권장됩니다.
- 조립은 한 곳에서 합니다. 프로그램 시작 지점이나 DI 컨테이너(스프링, 대거, Hilt 등 프레임워크)가 어떤 구현을 연결할지 정하고 주입합니다(Seemann & van Deursen, 2019).
- 테스트에서는 실제 구현 대신 가짜(목, 스텁) 객체를 주입해 단위 테스트를 합니다.
의존성 주입은 ‘제어의 역전(IoC)’의 한 형태입니다. 객체가 자기 부품을 직접 고르는 제어를 바깥으로 넘긴다는 뜻입니다.
언제 쓰고 무엇을 얻나
얻는 것은 결합도 낮추기, 교체 가능성, 테스트 용이성입니다(응집도와 결합도). 구현을 바꿔도 사용하는 쪽 코드를 고치지 않아도 되고, 테스트 때 데이터베이스나 네트워크 없이 모듈을 검증할 수 있습니다. 의존성 역전 원칙(고수준 모듈이 저수준 구현이 아닌 추상에 의존해야 한다는 원칙)을 실현하는 대표 수단으로 설명됩니다(Martin, 2003).
비용도 있습니다. 코드가 어디서 어떻게 조립되는지 한눈에 보기 어려워지고, 컨테이너 설정이 복잡해지며, 순환 의존이나 설정 오류가 실행 시점에야 드러날 수 있습니다. 의존이 단순하고 바뀔 일이 없는 작은 프로그램에서는 과한 설계가 될 수 있습니다. 필요한 곳에 쓰는 설계 기법이지 그 자체가 목적은 아닙니다(디자인 패턴).
논문에는 이렇게 적는다
- 어떤 주입 방식(생성자/세터/인터페이스)과 어떤 컨테이너·프레임워크(이름과 버전)를 썼는지
- 어떤 객체가 어떤 구현으로 조립되는지를 보이는 구성도와, 테스트에서 가짜 객체로 바꾼 방법
- 효과를 말할 때의 측정 지표: 결합도 지표, 테스트 커버리지, 변경 시 수정 범위, 실행 시간·메모리 오버헤드 등 무엇을 비교했는지
- DI를 쓰지 않은 대조 구현과 비교했는지, 비교했다면 같은 기능 조건인지
논문에서는 이렇게 쓰입니다
실제 논문에서
이 용어를 직접 쓴 국내 논문 문장을 아직 수집하지 못했습니다. 수집되는 대로 출처와 함께 싣겠습니다.
예시 문장
아래는 용어의 쓰임을 보여 주려고 만든 예시로, 실제 논문에서 가져온 문장이 아닙니다.
이 문장은 모듈이 사용할 부품(예: 데이터베이스 연결)을 코드 내부에서 직접 만들지 않고 외부에서 전달받게 만들어서, 테스트할 때는 가짜 부품으로 손쉽게 바꿔 끼울 수 있었다는 뜻입니다.
이 문장은 여러 개의 서비스가 서로를 직접 참조하지 않고, 컨테이너라는 중간 관리자가 필요한 서비스를 대신 연결해 주기 때문에 각 서비스를 따로 배포하거나 규모를 늘릴 수 있었다는 뜻입니다.
이 문장은 모바일 앱 개발에서 여러 화면이 공통으로 쓰는 객체를 매번 새로 만드는 대신 한 곳에서 관리하고 필요할 때 전달받도록 바꾸어, 코드 중복과 메모리 낭비를 줄였다는 뜻입니다.
조금 더 깊게 보면
의존성 주입은 흔히 생성자 주입, 세터(setter) 주입, 인터페이스 주입 세 가지 방식으로 구현되며, 이 중 생성자 주입이 객체 생성 시점에 의존 관계를 확정할 수 있어 널리 권장됩니다.
실제로는 스프링(Spring), 대거(Dagger), Hilt 같은 DI 프레임워크가 이러한 주입과 객체 생명주기 관리를 자동화해 주는 "제어의 역전(Inversion of Control, IoC)" 컨테이너 역할을 합니다.
논문을 읽을 때는 저자가 어떤 주입 방식을 택했는지, 그리고 그것이 테스트 대체(mocking)나 순환 의존성 방지와 어떻게 연결되는지를 함께 살펴보면 이해가 깊어집니다.
주의할 점
의존성 주입은 코드 간 결합도를 낮추는 대표적인 디자인 패턴이지만, 무분별하게 남용하면 어떤 부품이 어디서 어떻게 조립되는지 한눈에 파악하기 어려워지고 설정(configuration)이 복잡해질 수 있습니다. 이는 객체지향 프로그래밍의 4대 원칙 중 캡슐화·추상화를 지키기 위한 수단이지, 그 자체가 목적은 아니라는 점을 기억해야 합니다.
헷갈리기 쉬운 용어
비슷한 말이 여러 개 섞여 쓰입니다.
| 용어 | 의미 | 의존성 주입과의 관계 |
|---|---|---|
| 의존성 주입(DI) | 필요한 객체를 밖에서 전달받게 하는 기법 | — |
| 제어의 역전(IoC) | 객체 생성·연결의 제어를 프레임워크나 바깥에 넘기는 원리 | DI는 IoC를 구현하는 한 방법 |
| 서비스 로케이터 | 객체가 중앙 저장소에 스스로 의존 대상을 요청 | 주입이 아니라 ‘끌어오기’이며 대안으로 비교됨(Fowler, 2004) |
| 단위 테스트 | 작은 단위를 따로 검증하는 테스트 | DI가 가짜 객체 교체를 쉽게 해 줌 |
| 결합도 | 모듈이 서로 얼마나 얽혀 있는가 | DI가 낮추려는 대상 |
참고 문헌
- Fowler, M. (2004). Inversion of Control Containers and the Dependency Injection pattern [online article].
- Seemann, M., & van Deursen, S. (2019). Dependency Injection Principles, Practices, and Patterns. Manning.
- Martin, R. C. (2003). Agile Software Development: Principles, Patterns, and Practices. Prentice Hall.
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
표의 기준값은 위 문헌에 근거한 관례이며, 분야·학술지마다 다른 기준을 쓸 수 있습니다.