본문 바로가기

Book

의존성 주입(그리고 부트스트래핑)

13.2 명시적 의존성은 완전히 이상하고 자바스러운가?

파이썬에서는 의존성을 처리하는 표준 방법은 임포트를 통해 모든 의존성을 암시적으로 선언함. 테스트를 위해 무언가를 바꿔야 한다면 동적 언어인 파이썬에서는 멍키 패치를 할 수 있음. 그니까 모킹 해서 테스트할 수 있다는 거임. 근데 이게 모킹 한 모든 테스트마다 mock.patch 같은 걸 호출해야 함. 그리고 이러한 코드는 구현이랑 밀접하게 묶어줌. 그니까 나중에 리팩터링으로 모킹 하는 함수를 수정하면 다 수정할 수도 있음. 따라서 이 또한 트레이드오프임. 명시적 의존성을 사용하면 애플리케이션이 더 복잡해질 수도 있음. 하지만 테스트를 더 쉽게 작성하고 관리할 수 있다고 함. 그리고 명시적 의존성 정의는 의존성 역전 원칙의 예라고 함. 구체적인 세부 사항에 대한 의존성을 사용하는 대신 추상화에 대한 (명시적인) 의존성을 사용하는 것이 의존성임.
그렇다면 모든 의존성을 명시적으로 바꾼다면 누가 이 의존성을 주입할 것이며, 어떻게 주입하는가? 지금까지는 UoW를 여기저기 전달하는 것에만 신경을 씀. 테스트는 FakeUnitOfWork를 사용하지만, 플라스크와 레디스 eventconsumer 진입점은 실제 UoW를 사용하고 메시지 버스는 이 UoW를 커맨드 핸들러에게 전달함. 실제와 가짜 이메일 클래스를 추가하면 누가 이 클래스를 생성하고 전달 할까?
의존성을 올바른 핸들러들에게 전달하는 모든 책임을 메시지 버스에 전가하는 것은 SRP에 위배되는 것처럼 보임.

그래서 여기서 구성루트라는 패턴을 도입함. 쉽게 말하면 진입점을 추가하는 거임. 그리고 직접 의존성을 주입함. 다음과 같음

진입점과 메시지 버스 사이에 있는 부트스트래퍼

13.3 핸들러 준비: 클로저와 부분함수를 사용한 수동 DI

의존성이 있는 함수를 의존성이 이미 주입된 나중에 호출될 수 있는 함수로 변환하는 한 가지 방법으로, 클로저나 부분함수를 사용해서 함수와 의존성을 합성하는 방법.

def allocate(
             cmd: commands.Allocate, uow:unit_of_work.AbstractUnitOfWork
):
    line = OrderLine(cmd.orderid, cmd.sku, cmd.qty)
    with uow:
        ...
def bootstrap(..):
    uow = unit_of_work.SqlAlchemyUnitOfWork()

    #UoW 의존성이 클로저에 포획되어 있는 allocate 함수 버전을 준비한다.
    allocate_composed = lambda cmd: allocate(cmd, uow)

    #위 코드와 같은 역할을 하는 다른 방식의 코드(스택 트레이스가 잘 드러남)
    def allocate_composed(cmd):
        return allocate(cmd,uow)
    import functools
    allocate_composed = functools.partial(allocate, uow=uow)

partial 이랑 클로저는 비슷하다 느꼈는데, 여기서 말하기론 클로저는 변수를 나중에 바인딩하므로 의존성이 변경 가능한 변수인 경우 혼동을 야기할 수 있다고 함.

13.4 클래스를 사용한 대안

이건 기존 핸들러 함수들을 클래스로 다시 작성해야 함. 그냥 의존성 주입된 핸들러 함수를 만드는 걸 클래스로 했다고 보면 됨. 굳이 클래스를 만들 필요가 있나 싶기도 함. 나는 함수형이 더 효율적인 거 같아 보임.

class AllocateHandler;
    def __init__(self,uow:unit_of_work.AbstractUnitOfWord):
        self.uow = uow
    def __call__(self, cmd: commands.Allocate):
        line = OrderLine(cmd.orderid, cmd.sku, cmd.qty)
        with self.uow:
            #이전과 같은 나머지 핸들러 메서드

#실제 UoW를 준비하는 부트스트랩 스크립트
uow = unit_of_work.SqlAlchemyUnitOfWork()
# 여기서 의존성 주입된 함수버전을 준비
allocate = AllocateHandler(uow)
#나중에 사용
allocate(cmd)

13.5 부트스트랩 스크립트

부트스트랩 스크립트는 다음과 같은 일을 함

  1. 디폴트 의존성을 선언하지만 원하는 경우 이를 오버라이드할 수 있어야 한다.
  2. 앱을 시작하는 데 필요한 '초기화'를 수행.
  3. 모든 의존성을 핸들러에 주입한다.
  4. 앱의 핵심 객체인 메시지 버스를 반환한다.

13. 9 어댑터 '적절히' 구축하기:실제 사례

13.5부터 8절 까지는 구체적으로 어떤 식으로 부트스트랩 스크립트를 작성하는지 보여줌. 여기서는 새로운 어댑터가 추가될 때 부트스트래퍼를 어떻게 수정하는지 보여줌. 통지 관련 클래스를 추가하고 (이건 후에 슬랙등 추가가능) 부트스트랩퍼를 초기화할 때 단순히 의존성 주입가능하게 추가해 줌. 이러면 테스트할 때도 대체가 가능함.

13.10 마치며

의존성 주입을 설정하는 것은 앱을 시작할 때 한 번만 수행하면 되는 전형적인 설정/초기화 활동의 일부. 이 모두를 부트스트랩 스크립트에 넣는 것이 좋음.
부트스트랩 스크립트는 어댑터에 대해 타당한 디폴트 설정을 제공하기 좋은 장소임. 그리고 테스트를 위해 어댑터를 오버라이드 할 때 수정해야 하는 유일한 장소임.
DI를 여러 수준에서 진행해야 한다면 의존성 주입 프레임워크가 유용함. 의존성이 필요한 컴포넌트들의 의존성이 연쇄적인 경우 DI프레임워크가 필요함.

 

기존에 여러 의존성을 관리하는 라이브러리들을 보고 그냥 지나쳤는데 구체적으로 어떤 방향으로 설계되어 있는지 알게 되었다.

 

참고 및 출처: <파이썬으로 살펴보는 아키텍처 패턴: TDD, DDD, EDM 적용하기)> (해리 퍼시벌, 밥 그레고리 지음, 오현석 옮김, 한빛미디어 , 2021)

'Book' 카테고리의 다른 글

프로그래밍 패러다임  (0) 2023.12.22
계층형 설계 1,2  (2) 2023.12.04
명령-질의 책임 분리(CQRS)  (0) 2023.11.03
커맨드와 커맨드 핸들러  (0) 2023.10.29
메시지 버스로 통합하기  (2) 2023.10.22