파이썬으로 IoC(DI) 컨테이너 만들고 써보기

qodot
4 min readOct 6, 2022

커리어의 대부분을 파이썬을 사용해오면서, 복잡한 비즈니스 로직을 가지는 애플리케이션을 만들어야 하는 일도 많았습니다. 개인적으로는 이런 프로젝트를 경험했었네요.

거대하고 복잡한 코드를 작성하다 보면 자연스럽게 ‘아 이거 한 달만 지나면 나도 이해 못하겠는데?’, ‘테스트는 어디서부터 어떻게 작성해야 하지?’ 같은 불안감에 휩싸여서, ‘이게… 맞아?’라는 질문을 연거푸 던지는 자신을 발견하게 되더라구요. 그리고 실제로 코드가 배포된 이후에 쏟아지는 버그 리포트와 디버깅은 너무 고통스러웠습니다.

이런 문제를 해결하기 위해 헤매던 저는 OOP나 DDD 같은 키워드에 관심을 가지게 되었고, ‘계층형 아키텍쳐’ 등 DDD의 전술적 패턴의 일부(흔히 ‘DDD-Lite’라고 부르는)를 도입하기 위한 노력을 기울였습니다. 그리고 패턴에 맞추어 복잡한 객체를 역할에 맞게 나누다 보니, 자연스럽게 나뉘어진 객체를 합성하는 방법을 고민하게 되었습니다. 합성은 곧 객체 사이의 의존 관계를 설정하는 것이라고 볼 수 있을텐데요, ‘IoC 컨테이너’는 이를 도와주는 개념입니다.

IoC 컨테이너란?

  • ‘의존성 주입’을 구현하는 방법 중의 하나로서,
  • 필요한 객체들의 의존 관계 설정과, 생애 주기를 관리를 위임하는 도구입니다.
  • 저는 IoC 컨테이너라는 이름 대신, 그냥 ‘DI 컨테이너’라고 부르는 것을 더 좋아합니다. 좀 더 직관적이라는 느낌…
  • 하지만 동시에 ‘서비스 로케이터’로써 사용할 수도 있습니다.

왜 쓰지

의존성 주입이 적용된 객체라면, 의존성 관리가 필요합니다. 의존성 관리를 직접 하기 위해서는 주로 다음의 과정을 거치게 됩니다.

한두개야 괜찮겠지만, 의존성이 복잡할 수록 점점 반복 작업이 늘어나 귀찮아지고, 그러다보면 실수를 할 가능성도 커집니다. 개인적으로 이 정도 규모의 복잡한 의존성 주입은 자주 경험했습니다.

잘못된 의존성을 주입하는 실수는 mypy 같은 타입 검증 도구의 도움을 받으면 해결할 수 있을 것입니다. 그럼 타입 문제를 해결한 이후의 IoC 컨테이너를 적용했을 때 장점은 뭘까요?

  • 객체의 의존 관계를 공개 API(생성자)를 통해 명시적으로 드러냅니다.
  • 객체가 ‘실제로’ 동작하기 위한 구현체 주입을 한 곳에서 관리합니다.
  • 객체의 생애주기 관리를 위임하고, 객체 사용에 집중하기 쉽습니다.
  • 같은 인터페이스를 가진 다른 구현 객체로 교체가 간편합니다.

사실 엄밀히 말하자면 IoC 컨테이너의 장점이라기 보다는 ‘의존성 주입’과 ‘의존성 역전 원칙’의 장점이라고 하는 것이 맞겠습니다. IoC 컨테이너는 원칙을 구현하기 위한 도구이니까요. 기회가 되면 위 원칙에 대해서도 정리해볼 수 있으면 좋겠네요.

만들어 보자

파이썬으로 간단한 DI 컨테이너를 만들어 보겠습니다.

간단하게 생성한 객체를 등록(register)하고 타입으로 조회(get)할 수 있는 DI 컨테이너를 선언했습니다. 이제 이걸 실제로 사용해 볼까요?

사용해 보자

아래와 같은 객체가 존재한다고 가정해보고,

그리고 컨테이너를 통해 이 객체들이 실제로 동작하기 위한 구현체를 주입해줍니다.

등록(객체 생성)이 완료 되었다면 compose 메소드 실행 이후에, 아래처럼 단순하게 사용도 할 수 있습니다.

실제 HTTP 핸들러에서의 사용법도 유사합니다. 파이썬의 유명한 웹 프레임워크 FastAPI로 구현한 핸들러를 예시로 들어보겠습니다.

여담 1

위의 예시처럼 단순히 container.get을 이용해 객체를 조회/사용하는 경우, 이를 두고 '서비스 로케이터' 패턴을 사용하고 있다고 할 수 있습니다. 이 때, my_http_handler 함수와 container 객체는 암시적 의존 관계에 놓이게 됩니다. 그러다보니 이 서비스 로케이터 패턴이 안티 패턴이라는 주장도 있으니 읽어보시길 권합니다.

하지만 FastAPI의 경우(대부분의 파이썬 웹 프레임워크가 그러하듯) 라우팅 핸들러에 다른 객체를 주입하기 쉽지 않습니다. 물론 FastAPI로 한정하면 Depends 같은 의존성 주입 기능을 지원하고 있습니다. 하지만 저는 ‘앱 빌드시 의존성 주입’이라는 중요한 기능을 특정 기술에 의존해서 구현하고 싶지는 않았습니다. 그렇다고 파이썬과 표준 라이브러리만을 이용해 HTTP 핸들러와 비즈니스 객체를 합성하는 방법을 직접-복잡하게 구현하기 보다, 저는 그냥 서비스 로케이터 패턴을 이용하기로 결정했습니다.

여담 2

그런데 객체간의 의존 관계를 명시하거나, 실제로 객체를 조회할 때, 꼭 인터페이스(위 예시의 IMain, IDependency)를 사용해야 할까요? 그냥 구현체의 타입(Main, Dependency)을 직접 사용하면 안 되는 걸까요? 아래 글을 읽어보시면 참고가 될 것 같습니다.

--

--