의존성 주입 (Dependency Injection) 패턴

[원본 링크]

의존성 주입(이하 DI) 패턴은 현재 가장 널리 사용되고 있는 패턴 중 하나라고 할 수 있다.
보통은 Spring이나 NestJS 등의 백엔드 프레임워크를 통해서 접해보았을 것이다.



기본 원리

DI는 기본적으로 느슨한 결합 구조를 지향하는 패턴이다.

만약 백엔드에서 Contoller-Service 레이어의 구조를 기반으로 시스템을 구성한다면 그냥 이렇게 할 수도 있다.

이러한 방법 자체에는 사실 별다른 문제는 없다.
다만 Service가 컨트롤러 코드 내에 강하게 결합되어 있다는 것이 꽤나 불편한 상황으로 작용할 수는 있다.

만약 저 health라는 컨트롤러에 대해서 단위테스트를 하고 싶다면 어떻게 해야할까?
보통은 사이드이펙트를 유발하는 모든 구성요소를 Fake Object로 교체해서 돌리는 것이 바람직한데, 저 코드는 그런 대처가 용이하지는 않다.

이런 문제를 좀 개선하고자 나온게 DI 패턴이다.
인터페이스 등을 활용해서 느슨하게 연결하고, 세부적인 구현체는 상황에 따라서 그때그때 집어넣어서 사용할 수 있게 하는 것이다.
DI에서는 그를 위해서 레이어가 좀 추가된다.

Controller가 interface 등을 통해서 Service를 추상화된 형태로 DI에서 가져와 사용하게 되는 것이다.
그리고 Service는 각각 DI Context에 등록해서 DI에 의해 load되게 된다.

컨트롤러가 꼭 인터페이스를 통해서만 DI를 해야하는건 아니지만, 인터페이스를 쓰지 않으면 DI의 많은 장점이 사라진다.

저기서 DI될 수 있는 Service 단위들을 Spring에서는 Bean이라고 부르고, Nest.js에서는 provider 등으로 부르곤 한다.
그리고 Dependency Manager는 Factory라는 이름으로 불린다.




장단점

장점

  1. 인터페이스를 통한 추상화로, 상황에 따른 구현체의 교체가 편리하다.

  2. 1번 사유과 연관되어, 단위테스트 등을 수행할 때도 Fake 구현체와의 교체가 수월하다.



단점

  1. 코드 자체가 장황하고 복잡해진다.

  2. 보통 데코레이터와 리플렉션 등을 활용한 메타프로그래밍을 통해 구현되어, IDE 자동화나 가독성을 저해하는 부분이 있다.