[Python] Dependency Injector 란?

김지환·2023년 2월 17일
1

Depecndency Injection에 대해

의존성 주입이란 내부가 아닌 외부에서 객체를 주입시켜서 서로간의 의존성을 낮추는 방식이다.
이렇게 의존성 주입을 하게 되면 얻어지는 여러 장점들이 있다.

  • 테스트 용이성
  • 재사용성을 높여준다.
  • 코드의 단순화
  • 결합도를 낮춘다.
  • 객체간의 의존관계를 설정할 수 있다.

좀 더 자세히 DI 에 대해 알아보려면 객체지향의 원리중 하나인 DIP(Dependency Inversion Principle) 의존성 역전 원칙을 알아야한다.
DIP란 추상적 개념이 구체적인 객체에 의존하면 안되고, 구체적인 객체가 추상화된 개념에 의존하도록 설계하는 것을 의미한다.

이해하기에 좋은 예시로 자동차가 있다.

class CommonTier:
    ...

class SnowTier:
	...
    
class Car:
    
    def __init__(self):
        self.tier = CommonTier()
 
if __name__ == "__main__":
    common_car = Car()
    snow_car = # ...? 새로 class를 정의해줘야함.

위처럼 자동차를 만들 때 타이어가 생성자에서 생성이 되게 된다. 이렇게 되면 자동차는 타이어에 의존성이 생기게 되고 새로운 종류의 타이어를 갖는 자동차를 만들려면 다시 class 정의를 해줘야한다.

그렇다면 DIP를 지키기 위해서 객체를 추상화하고 그 추상화된 객체를 통해서 다양한 타이어를 갖는 자동차를 자동차 class를 변환하지 않고 만들기 위해서는 DI ( 의존성 주입 ) 이 이뤄져야한다.


from abc import abstractmethod, ABC


class Tier(ABC):

    @abstractmethod
    def get_name(self):
        ...


class SnowTier(Tier):

    def get_name(self):
        print("스노우 타이어")


class CommonTier(Tier):

    def get_name(self):
        print("일반 타이어")


class Car:

    def __init__(self, tier: Tier):
        self.tier = tier

    def get_tier(self):
        return self.tier.get_name()


if __name__ == "__main__":
    common_car = Car(CommonTier()) # 내부에서 생성하는게 아닌 외부에서 객체를 전달
    snow_car = Car(SnowTier())

즉 DIP를 지키기 위한 방법중의 하나가 DI라고 볼 수 있다.

Java의 Spring Python 에서는?

Java에서는 Spring을 통해서 DI 를 사용자가 하는것이 아닌 Spring container가 대신 관리해준다. 이를 IoC ( Inversion of Control ) 제어의 역전이라 부른다. Python 에서는 Dependency Injector를 통해서 IoC의 구현이 가능하다.

Dependency Injector 는 크게 provider, container, wiring 에 대해서 이해하면 사용에 무리가 없을 것 같다.

  • provider: 객체를 생성해주고 관리해준다.
  • container: provider의 집합
  • wiring: Container 가 의존성 주입을 할 수 있도록 연결을 해줘야한다. 연결을 위해서는 4가지 조건이 만족 되야함

wiring을 위한 조건

  1. @Inject 데코레이터를 붙여줘야한다.
  2. container의 구현체들을 연결할 module, package를 container.wire()를 통해서 연결해줘야한다.
  3. Provide 를 통해서 wiring marker를 만들어줘야 한다.
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject


class Service:
    ...


class Container(containers.DeclarativeContainer):

    service = providers.Factory(Service)


@inject
def main(service: Service = Provide[Container.service]) -> None:
    ...


if __name__ == "__main__":
    container = Container()
    container.wire(modules=[__name__])

    main()

위 자동차 예제에 Dependency Injector를 적용해보면 아래와 같다.

from abc import abstractmethod, ABC
from dependency_injector import containers, providers


class Tier(ABC):

    @abstractmethod
    def get_name(self):
        ...


class SnowTier(Tier):

    def get_name(self):
        print("스노우 타이어")


class CommonTier(Tier):

    def get_name(self):
        print("일반 타이어")


class Car:

    def __init__(self, tier: Tier):
        self.tier = tier

    def get_tier(self):
        return self.tier.get_name()


class Container(containers.DeclarativeContainer):

    common_tier_factory = providers.Factory(CommonTier)

    car_factory = providers.Factory(
        Car,
        tier=common_tier_factory,
    )


if __name__ == "__main__":
    container = Container()

    snow_car = container.car_factory(tier=SnowTier())
    common_car = container.car_factory()

    common_car.get_tier()
    snow_car.get_tier()

마무리

Python 도 IoC의 구현이 충분히 가능하고 웹 애플리케이션과 같이 layer가 나뉘어 지고 객체간의 의존성 관리가 필요할 때에 유용하게 사용될 수 있음을 확인했다.
다음은 FastAPI 에 Dependency Injector를 활용하여 적용을 해보도록 하겠다.

profile
Developer

0개의 댓글