의존성 주입이란 내부가 아닌 외부에서 객체를 주입시켜서 서로간의 의존성을 낮추는 방식이다.
이렇게 의존성 주입을 하게 되면 얻어지는 여러 장점들이 있다.
좀 더 자세히 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을 통해서 DI 를 사용자가 하는것이 아닌 Spring container가 대신 관리해준다. 이를 IoC ( Inversion of Control ) 제어의 역전이라 부른다. Python 에서는 Dependency Injector를 통해서 IoC의 구현이 가능하다.
Dependency Injector 는 크게 provider, container, wiring 에 대해서 이해하면 사용에 무리가 없을 것 같다.
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를 활용하여 적용을 해보도록 하겠다.