Dependency Injection
객체가 협력자를 직접 생성하지 않고 외부에서 전달받아 결합을 명시한다.
이 문서의 목차
Overview
DI는 객체가 사용할 협력자를 내부에서 생성·탐색하지 않고 Constructor, Setter, Method 등을 통해 외부에서 받는 방식이다. 핵심은 Interface 사용 여부가 아니라 의존성이 명시되고 교체 가능한 구성이다.
한 문장 설명: DI는 객체가 필요한 도구를 직접 사러 가지 않고, 계약된 방식으로 외부에서 건네받게 만드는 설계다.
핵심 용어
| 용어 | 설명 |
|---|---|
| Dependency | 객체가 역할 수행에 필요로 하는 다른 객체 |
| Constructor Injection | 생성 시 필수 의존성을 받아 완전한 상태 보장 |
| Setter Injection | 선택적·변경 가능한 의존성을 나중에 주입 |
| Field Injection | Field에 직접 주입해 의존성을 숨기는 방식 |
| Qualifier | 같은 Type 후보 중 의도한 Bean을 선택하는 표식 |
왜 필요한가
Service 안에서 new PaymentClient()를 하면 환경별 구현 교체와 Unit Test가 어렵다. Constructor Parameter로 받으면 무엇이 필요한지 Signature에 드러나고 Fake를 쉽게 넣을 수 있다.
면접에서는 세 가지 효과로 정리한다. 첫째, 객체가 생성 책임과 업무 책임을 동시에 갖지 않는다. 둘째, 의존성이 생성자에 드러나므로 설계가 읽힌다. 셋째, 테스트에서 Fake나 Mock을 넣어 외부 시스템 없이 검증할 수 있다.
핵심 원리와 내부 동작
Spring은 Bean 생성 시 Constructor Parameter Type과 Qualifier를 기준으로 후보를 찾는다. 생성자가 하나면 @Autowired 없이 주입할 수 있다. 순환 의존성은 설계 경계가 얽혔다는 신호이며 Constructor Injection에서는 즉시 드러난다.
Example
@Service
class OrderService {
private final PaymentGateway paymentGateway;
OrderService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}Test에서는 new OrderService(fakeGateway)로 Container 없이 검증할 수 있다. 다형성이 필요하지 않은 내부 Component까지 억지로 Interface를 만들 필요는 없다.
주입 방식 비교
| 방식 | 적합한 경우 | 주의점 |
|---|---|---|
| Constructor Injection | 필수 의존성, 불변 객체, 일반적인 Service | Parameter가 많으면 책임 분리 신호 |
| Setter Injection | 선택적 의존성, Runtime 교체가 필요한 구성 | 객체가 한동안 불완전할 수 있음 |
| Field Injection | Framework가 간단히 주입하는 데모성 코드 | 의존성이 숨고 순수 Unit Test가 불편함 |
실무 기본값은 Constructor Injection이다. 필수 의존성을 final로 고정할 수 있고, 객체 생성 시점에 완전한 상태가 보장되며, 순환 의존성이 설계 문제로 빠르게 드러난다.
실무에서 발생하는 문제와 Trade-off
Constructor Parameter가 지나치게 많으면 Service 책임이 큰 신호다. 같은 Type Bean이 여러 개면 모호성 오류가 나므로 역할 이름이나 Qualifier를 명시한다. @Lazy로 순환 의존성을 숨기면 Runtime 복잡성이 커진다.
흔한 오해
- DI를 쓰려면 모든 Class에 Interface가 필수는 아니다.
- Field Injection도 동작하지만 필수 의존성이 Signature에 없고 일반 Unit Test가 불편하다.
- DI가 자동으로 낮은 결합도를 만드는 것은 아니다. 거대한 Interface와 양방향 의존은 여전히 강결합이다.
@Autowired가 DI의 본질은 아니다.
Production Considerations
Composition Root에서 환경별 구현을 선택하고 Business Layer가 Container API를 직접 조회하지 않게 한다. Optional Dependency는 정말 선택인지 검토한다. Configuration Test로 Bean 후보·조건을 검증하고 Secret이나 Network Client의 Lifecycle도 함께 관리한다.
다른 사람에게 설명한다면
30초: “DI는 객체가 협력자를 직접 만들지 않고 외부에서 전달받는 방식입니다. 생성자 주입은 필수 의존성을 명시하고 불완전한 객체를 막으며, Test에서 Fake를 쉽게 넣게 합니다.”
2분: 직접 생성의 변경 비용, Constructor Resolution, Qualifier, 순환 의존성, Interface가 항상 필수는 아니라는 점까지 코드로 설명한다. “DI는 낮은 결합을 보장하는 마법이 아니라, 결합을 명시하고 교체 가능하게 만드는 도구”라고 마무리한다.
Interview Questions
- 생성자 주입을 권장하는 이유는?
- DI와 Service Locator의 차이는?
- 순환 의존성이 나타내는 설계 문제는?
- Interface가 없는 DI도 가능한가?
Related Topics
SOURCE REFERENCES
이 문서의 근거
본문은 Dev Atlas 안에서 완결되며, 검증이 필요할 때만 원문을 확인할 수 있습니다.