[FastAPI] 11. Dependency Injector๋ฅผ ์ด์ฉํ ์์กด์ฑ ๊ด๋ฆฌ
์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ค๋ณด๋ฉด ๊ท๋ชจ๊ฐ ์ปค์ง๊ฒ ๋์ด ์ด๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ํ์๊ฐ ์๊ธฐ๊ฒ ๋ฉ๋๋ค. ์ด์ ์ ์ฉํ๋ ๊ฒ์ผ๋ก ๋ํ์ ์ธ ์ํคํ ์ฒ์ธ Layered Architecture๊ฐ ์์ต๋๋ค.
Layered Architecture๋ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด์ฉํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ํ๋ ๋ํ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก, Application, Domain, Infrastructure์ 3๊ฐ๋ก ๋๋ ์ ์๋๋ฐ, ์ด๋ค์ ์ ๋ถ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ๊ตฌํํ๊ฒ ๋๋ฉด ๊ฐ๊ฐ์ ์์กด์ฑ์ด ๋์ด๋๊ฒ ๋๊ณ , ๊ทธ ๋ก์ง์ด ์ปค์ง๋ฉด ์ด ์ญ์ ๊ด๋ฆฌ๊ฐ ํ๋ค์ด์ง๋๋ค.
Python์์๋ ์ด๋ฌํ ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ์ ์ฐํ๊ฒ ํ๊ธฐ ์ํด ๋ค์ํ DI ํ๋ ์์ํฌ๊ฐ ์กด์ฌํ๋๋ฐ, ๊ทธ ์ค์์๋ Dependency Injector๋ฅผ ์ฌ์ฉ ํด๋ณด๊ณ ์ ํฉ๋๋ค.
Dependency Injector
Python์์ Django ํน์ DRF(Django REST Framework)๋ฅผ ์ฌ์ฉํด๋ณด์ ๋ถ๋ค์ด๋ผ๋ฉด ๊ทธ๋ค์ ๋ด์ฅ๋ DI ํ๋ ์์ํฌ๋ฅผ ๋ณด์ จ์ ๊ฒ์ ๋๋ค. Django์ ๊ฒฝ์ฐ๋ Dictionary๋ฅผ ์ด์ฉํ์ฌ ์ฃผ์ ํ๊ณ , DRF์ ๊ฒฝ์ฐ๋ class๋ฅผ ์ด์ฉํ์ฌ ์ฃผ์ ํ๊ฒ ๋๋๋ฐ์. ๋ฌธ์ ๋ ํ๋ ์์ํฌ์ ๊ฐํ๊ฒ ์ข ์์ ์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ํ๋ ์์ํฌ์์๋ ์ฌ์ฉํ๊ธฐ ์ด๋ ต๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
Dependency Injector๋ ์ด๋ ํ ํ๋ ์์ํฌ์ ์ข ์์ ์ด์ง ์๊ณ , ์ผ๋ฐ Python์์๋ ์ ์ฉํ ์ฌ์ฉํ ์ ์๋ Python์ DI ํ๋ ์์ํฌ์ด๋ฉฐ Dependency Injector์ ์ฃผ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
https://python-dependency-injector.ets-labs.org/introduction/di_in_python.html
Dependency injection and inversion of control in Python โ Dependency Injector 4.36.0 documentation
Dependency injection and inversion of control in Python Originally dependency injection pattern got popular in the languages with a static typing, like Java. Dependency injection is a principle that helps to achieve an inversion of control. Dependency inje
python-dependency-injector.ets-labs.org
- ์ ์ฐ์ฑ (Flexibility)
๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋์จํ ๊ฒฐํฉ์ผ๋ก ๋์ด ์์ด ๊ธฐ๋ฅ์ ๋ณ๊ฒฝ๊ณผ ํ์ฅ์ด ์ฌ์. - ํ
์คํธ ๊ฐ๋ฅ์ฑ (Testability)
์ปดํฌ๋ํธ์ ์ค์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ง ์๊ณ Mockingํ์ฌ ์ฃผ์ ํด ํ ์คํธ์ ์ฉ์ด - ๋ช
ํ์ฑ๊ณผ ์ ์ง๋ณด์ (Clearness and maintainability)
๋ช ์์ ์์กด์ฑ ์ฃผ์ ์ ์ด์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ๋ฅผ ์ฝ๊ฒ ํ์ ํด ์ ์ง๋ณด์ํ๊ธฐ ์ฉ์ด
๊ฐ ํน์ง์ ๋ํด ์ข ๋ ๋ถ์ฐ์ค๋ช ํ์๋ฉด, ๋น์ฆ๋์ค ๋ก์ง์ด ๊ตฌํ๋ ํด๋์ค์์ Database์ ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Database ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. ์ด ๋ ๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํด์ ์ฌ์ฉํ๋ฉด ํด๋น ๊ฐ์ฒด์ ๊ฐํ๊ฒ ์์กดํ๊ธฐ ๋๋ฌธ์ ์ด ๊ฐ์ฒด์ ๋ง๋ ์ธํฐํ์ด์ค ํน์ ํ๋กํ ์ฝ์ ๊ตฌํํด๋๊ณ , ๊ฐ์ฒด๋ฅผ ์ธ์คํด์คํํ์ฌ ์ฌ์ฉํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋์จํ๊ฒ ๊ฒฐํฉ๋์ด ๋ณ๊ฒฝ์ ์ฉ์ดํด์ง๋๋ค.
Python์์๋ Unittest๋ผ๋ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์กด์ฌํ๊ณ , ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Mocking(๋ชจ์กฐํํ) ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ฌ๊ธฐ์ Mocking์ด๋, ์ฐ๋ฆฌ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์คํ์ ์ฌ์ฉํ๋ ์ธ์คํด์ค์ ์ค์ฒด๊ฐ ์๋ ๋ชจ์กฐํ์ ์ ๊ณตํ๋ฉฐ ์ฐ๋ฆฌ๊ฐ ์ฌ๊ธฐ์ ๊ตฌํํด์ผ ํ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ์ ๊ณตํด์ฃผ์ง ์๊ณ , ๊ทธ ๊ฒ๋ชจ์ต๋ง์ ์ ๊ณตํ์ฌ ํ ์คํธํ ์ ์์ด Flow ํ ์คํธ์ ์ ๋ฆฌํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋ช ์์ ์์กด์ฑ ์ฃผ์ (explicit dependency)์ ์ด์ผ๊ธฐํ๋๋ฐ, ์ ๊น ์ค๋ช ์ ๋๋ฆฌ์๋ฉด, ๋ช ์์ ์์กด์ฑ ์ฃผ์ ์ ์์กด ๋์์ ์์ฑ์์ ์ธ์๋ก ์ ๋ฌํ์ฌ ์ฃผ์ ํ๋ ๋ฐฉ์์ ๋งํฉ๋๋ค. ๋ฐ๋๋ง๋ก ์๋ฌต์ ์์กด์ฑ(hidden dependency)์ด ์๋๋ฐ, ์๋ฌต์ ์์กด์ฑ์ ์์ฑ์ ๋ด ๋๋ค๋ฅธ ์ธ์คํด์ค๋ฅผ ์์ฑํด ์ด๋ค ๊ฒ์ ์์กดํ๋์ง๋ฅผ ๊ฐ์ถ๋ ์ฃผ์ ๋ฐฉ์์ ๋๋ค.
ํ์๋ ๋ ๋ค์ ๊ทธ ์์กด์ฑ์ ์ฝ๋๊ฐ ์ด๋ป๊ฒ ๊ตฌํ๋์๋์ง๋ฅผ ํ์ ํด์ผํ๊ธฐ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผธ์ด์ ์ด ๋ณต์กํด์ง์ ๋ฐ๋ผ ๊ทธ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ๋ฅผ ํ์ ํ๊ธฐ ์ด๋ ต๊ณ ์ ์ง๋ณด์๊ฐ ๊ทธ๋งํผ ์ด๋ ค์์ง๋ฏ๋ก ๋ช ์์ ์์กด์ฑ ์ฃผ์ ์ ํตํด ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๋๋ก ์ฝ๋๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค.
with FastAPI
Dependency Injector๋ FastAPI ํ๋ ์์ํฌ์์ ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ํ๊ธฐ ์์ฃผ ์ข์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ๋จผ์ FastAPI์ ๊ธฐ๋ณธ ์ ํ๋ฆฌ์ผ์์ ๊ตฌ์กฐ๋ฅผ ์๋์ ๊ฐ์ด ์์ฑํ๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.

src์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ค, tests์๋ ์ด์ ๋ํ ํ ์คํธ ์ฝ๋๋ฅผ ๊ตฌํํ๋ ๊ณณ์ ๋๋ค. Dependency Injector๋ ํ ์คํธ ์ฝ๋ ์์ฑ์ ์ฉ์ดํ๋ค๋ ํน์ง์ ๊ฐ์ง๊ณ ์์ผ๋ ํ ์คํธ ์ฝ๋๋ ๊ฐ์ด ์์ฑํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์๋ํฌ์ธํธ(API), ๋น์ฆ๋์ค ๋ก์ง(Service), ์ธํ๋ผ์คํธ๋ญ์ณ(Repository ์ดํ DB) 3 Layer์ ์ํคํ ์ฒ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ Dependency Injector๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๋ํด ์์๋ณผ ๊ฒ์ ๋๋ค.
๋จผ์ containers.py ํ์ผ์ ๋ง๋ค์ด ์์กด์ฑ์ ๊ด๋ฆฌํ IoC๋ฅผ ๋ง๋ค์ด์ค์ผ ํฉ๋๋ค.
Declarative Container
Declarative Container๋ Dependency Injector์์ ์์กด์ฑ์ ๊ด๋ฆฌํ๋ ๊ธฐ๋ณธ ์ปจํ ์ด๋์ ๋๋ค. ์ด ์ปจํ ์ด๋์์๋ ์์กด์ฑ ๊ด๋ฆฌ ๋ฟ ์๋๋ผ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ ๋ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
from dependency_injector.containers import DeclarativeContainer | |
from dependency_injector.providers import Configuration | |
class BaseContainer(DeclarativeContainer): | |
config = Configuration() |
DelclarativeContainer๋ฅผ ์์ํ์ฌ ์ปจํ ์ด๋๋ฅผ ๊ตฌํํ ์ ์์ผ๋ฉฐ ์ฌ๊ธฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ ์ ์ถ๊ฐํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ Provider ํจํค์ง์ ์๋ Configuration ํด๋์ค๋ฅผ ์ด์ฉํ ์ ์์ต๋๋ค.
Provider์์ ์ ๊ณตํ๋ Configuration์์๋ ์๋์ 5๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ํ๊ฒฝ์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
- ini ํ์ผ
- Python์ Dict ์๋ฃํ
- yaml ํ์ผ
- Pydantic์ Settings ํด๋์ค
- OS Environment
Python์ผ๋ก ๊ฐ๋ฐํด๋ณด์ ๋ถ๋ค์ด๋ผ๋ฉด Pydantic์ ์ ์๊ฒ ์ง๋ง Pydantic์ Python์์ Type annotation์ ์ฌ์ฉํด ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ์ ์ค์ ์ ๊ด๋ฆฌํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ค์ ๋ก FastAPI์์๋ ์์ฒญ๊ณผ ์๋ต ํด๋์ค์ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉฐ ์ด ์ธ์๋ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํ ํด๋์ค๊ฐ ํ์ํ๋ค๋ฉด Python์์๋ ์ด๋ฅผ ๋ง์ด ์ฌ์ฉํฉ๋๋ค.
import os | |
from dependency_injector.containers import DeclarativeContainer | |
from dependency_injector.providers import Configuration | |
from pydantic import BaseSettings | |
class DatabaseSettings(BaseSettings): | |
host: str = Field(default="localhost", env="DB_HOST") | |
port: int = Field(default=5432, env="DB_PORT") | |
name: str = Field(default=None, env="DB_NAME") | |
username: str = Field(default="postgres", env="DB_USER") | |
password: str = Field(default="postgres", env="DB_PASSWORD") | |
class ApplicationSettings(BaseSettings): | |
db: DatabaseSettings = DatabaseSettings() | |
class BaseContainer(DeclarativeContainer): | |
config = Configuration() | |
config.from_pydantic(ApplicationSettings()) |
์ ์ฝ๋๋ Pydantic์ Settings ํด๋์ค๋ฅผ ์ฌ์ฉํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ค์ ํ ์ฝ๋์ ๋๋ค. Database ๋ฟ๋ง ์๋๋ผ CORS, AUTH-KEY ๋ฑ์ ์ค์ ๋ ํด๋์คํํ์ฌ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ณ ๋ฐ์ดํฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ง์ํ๊ธฐ ๋๋ฌธ์ dotenv์ OS environment๋ฅผ ๊ฐ์ด ์จ์ ์ค์ ์ ๊ด๋ฆฌํ ์๋ ์์ต๋๋ค.
์ด ์ธ์๋ ๋ค์ํ ์ปจํ ์ด๋๋ค์ด ์์ง๋ง Dependency Injector์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๊ณ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ปจํ ์ด๋์ด๊ธฐ ๋๋ฌธ์ FastAPI๋ฅผ ์ฌ์ฉํ๋ ๋ฐ ์์ด์๋ ์ ์ธ์ ์ปจํ ์ด๋๊น์ง๋ง ์์๋ฉด ๋ ๊ฒ ๊ฐ๋ค์.
์ ๊ทธ๋ผ ์ด์ ์ฌ๊ธฐ์ ์์กด์ฑ ์ฃผ์ ์ ์ด๋ป๊ฒ ํ ์ ์์๊น์?
Dependency Injector๊ฐ ์ ๊ณตํ๋ ์์กด์ฑ ์์ฑ ๋งค์ปค๋์ฆ
Dependency Injector์์๋ ์์กด์ฑ์ ์ฃผ์ ํ ๋ ์ฌ์ฉํ๋ ๋งค์ปค๋์ฆ์ Provider๋ก ๋ช ์ํฉ๋๋ค. ์ด๋ ์์กด์ฑ์ ์์ฑํ๋ ์ญํ ์ ํ๋ฉฐ ๊ทธ ์ข ๋ฅ๋ 10๊ฐ์ง๊ฐ ๋์ง๋ง ์ฐ๋ฆฌ๋ ์ฌ๊ธฐ์ 3๊ฐ์ง ์ ๋๋ฅผ ๋ค๋ฃฐ ๊ฒ์ ๋๋ค. ๋จผ์ 1๊ฐ์ง๋ ์์์ ๋ค๋ฃฌ Configuration Provider์์ต๋๋ค.
๋ ๋ฒ์งธ๋ Singleton Provider์ ๋๋ค. Singleton Provider๋ ์ฌ๋ฌ๋ถ๋ค์ด ์๊ณ ๊ณ์๋ ์ฑ๊ธํด(Singleton) ํจํด์ ๋งํ๋ฉฐ ๊ฐ์ฒด๋ฅผ ์ฑ๊ธํด์ผ๋ก ์์ฑํ์ฌ ์ด๋ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ ๊ฐ์ ์์กด์ฑ์ ์ฌ์ฉํ๋๋ก ํ๊ฒ ๋ค๋ ๊ฒ์ ๋๋ค.
import os | |
from dependency_injector.containers import DeclarativeContainer | |
from dependency_injector.providers import Configuration, Singleton | |
from pydantic import BaseSettings | |
... | |
class MemoService: | |
... | |
class BaseContainer(DeclarativeContainer): | |
config = Configuration() | |
config.from_pydantic(ApplicationSettings()) | |
memo_service = Singleton(MemoService) |
providers์ Singleton์ ์ด์ฉํ์ฌ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋๋ Singleton ์ธ์์ ๊ฐ์ฒด๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค. ๋ง์ฝ ์์ฑ์ ์ธ์๊ฐ ์๋ ๊ฒฝ์ฐ, kwargs๋ฅผ ์ฌ์ฉํ์ฌ ๋ฃ์ด์ค ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ์์ฑ๋ ๊ฐ์ฒด๋ ์ฑ๊ธํด์ผ๋ก ๋์ํ์ฌ ์ด๋ค ์ปดํฌ๋ํธ, ์ด๋ค ์ฃผ๊ธฐ์ ํธ์ถํด๋ ๊ฐ์ ๊ฐ์ฒด๋ฅผ ํธ์ถํ๊ฒ ๋ฉ๋๋ค.๋ง์ฝ, ํด๋น ๊ฐ์ฒด๋ฅผ ๋ค์ ์์ฑํ๊ธฐ ์ํ๋ ๊ฒฝ์ฐ, reset ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํ ํ ์ ์์ต๋๋ค.
์ธ ๋ฒ์งธ๋ Factory Provider์ ๋๋ค. Factory Provider๋ Singleton๊ณผ๋ ๋ฐ๋๋ก ๋งค๋ฒ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋งค์ปค๋์ฆ์ ๋๋ค. ํฉํ ๋ฆฌ๋ ์ฑ๊ธํด์ฒ๋ผ ์์ฑ์์ ์ธ์๋ฅผ kwargs๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ ๋๋ถ์ด Factory Provider Chaining ์ฆ, ์ฐ๊ฒฐ ๊ณ ๋ฆฌ๋ฅผ ์ด์ฉํด์๋ ์์กด์ฑ์ ์ฃผ์ ํ ์ ์์ต๋๋ค.
๊ฐ๋จํ๊ฒ ์์๋ฅผ ๋ค์ด๋ณด๋๋ก ํ์ฃ .
import os | |
from dependency_injector.containers import DeclarativeContainer | |
from dependency_injector.providers import Configuration, Factory | |
from pydantic import BaseSettings | |
... | |
class Memo: | |
... | |
class MemoService: | |
def __init__(self, memo: Memo) -> None: | |
self.memo = memo | |
class BaseContainer(DeclarativeContainer): | |
config = Configuration() | |
config.from_pydantic(ApplicationSettings()) | |
memo_service = Factory(MemoService, memo=Memo()) |
MemoService ์์ฑ์์๋ Memo ๋ฐ์ดํฐ ๋ชจ๋ธ์ด ํ์ํ๋ฉฐ ์ด๋ฅผ Factory๋ก ์์ฑํ๋ ๊ฒฝ์ฐ, argument ์ด๋ฆ์ ์ ์ํ๊ณ ๊ทธ ๋ชจ๋ธ ์ธ์คํด์ค๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
import os | |
from dependency_injector.containers import DeclarativeContainer | |
from dependency_injector.providers import Configuration, Factory | |
from pydantic import BaseSettings | |
... | |
class Memo: | |
... | |
class MemoService: | |
def __init__(self, memo: Memo) -> None: | |
self.memo = memo | |
class BaseContainer(DeclarativeContainer): | |
config = Configuration() | |
config.from_pydantic(ApplicationSettings()) | |
memo_service = Factory(MemoService, memo=Factory(Memo)) |
๋ง์ฝ ์์ฑ์์ ๋ค์ด๊ฐ๋ ์์กด์ฑ ๋ํ Factory๋ก ์์ฑํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ, ์ด๋ค์ Factory๋ก ๋ถ๋ฌ์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. ์ด๋ฐ์์ผ๋ก ๊ณ์ ์ด์ด์ น์ ์ฌ์ฉํ๋ฉด Chaining ์ฝ๋๊ฐ ๋ฉ๋๋ค.
์ ์์กด์ฑ ์์ฑ์ด ๋๋ฌ์ต๋๋ค. ์ด์ FastAPI์์ ์ด๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ ์ ์์๊น์?
Wiring
Dependency Injector์ Provider๋ฅผ ์ด์ฉํ์ฌ ์์กด์ฑ ์ฃผ์ ์ ๋ง์น ๊ฒฝ์ฐ, ์ฐ๋ฆฌ๋ ์ด๋ฅผ ์ฌ์ฉํ ์ปดํฌ๋ํธ ๋์์ wireํด์ค์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์ wire ๋, ์ฐ๊ฒฐ์ ๋ปํ๋ฉฐ ์ปจํ ์ด๋์ ์๋ ์์กด์ฑ์ ํจ์ ํน์ ๋ฉ์๋์ ์ฃผ์ ํ๋ ์ญํ ์ ํฉ๋๋ค.
์ฃผ์ ํ๋ ค๋ ๋์์ ํจ์ ํน์ ๋ฉ์๋๊ฐ ๊ฐ์ ๋ชจ๋์์ Wiring ํ๋ ๊ฒฝ์ฐ์๋ Dependency Injector์์ ์ ๊ณตํ๋ inject ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์์กด์ฑ์ ์ฃผ์ ํ ์ ์์ต๋๋ค.
import uvicorn | |
from dependency_injector.wiring import inject, Provide | |
from fastapi import FastAPI, Depends | |
from src.containers import BaseContainer as Container | |
app = FastAPI() | |
@app.get('/memos') | |
@inject | |
async def get_memos(memo_service: MemoService = Depends(Provide[Container.memo_service])): | |
... | |
if __name__ == '__main__': | |
container = Container() | |
container.wire([sys.modules[__name__]]) | |
uvicorn.run(app=app) |
์ํ๋ API๋ฅผ ๋ง๋ค๊ณ , ํด๋น ํจ์ ์์ Dependency Injector์ inject ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๋ฃ์ด์ค๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฃผ์ ํ ์์กด์ฑ ์ฌ์ฉ์ ์ํด Provide๋ฅผ ํตํด ์ฃผ์ ํ ์์กด์ฑ์ ์ด๋ค ๋ณ์์ ๋ฐฐ์นํ ์ง๋ฅผ ์ ํด์ค๋๋ค. ์ด ๋ FastAPI์ Depends๋ฅผ ๋ฃ์ด์ค๋๋ค.
๊ทธ๋ฆฌ๊ณ , ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ ์ ๋ฐ๋์ Container๋ฅผ ์ฐ๊ฒฐํ๋ ค๋ ๋ชจ๋์ ์ ํด์ค์ผํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ํด๋น ๋ชจ๋์๋ IoC๊ฐ ๋ฐฐ์ ๋์ง ์์ import๋ก ์ปจํ ์ด๋ ๋ด ์์กด์ฑ์ ๊ฐ์ ธ์ค๋ ์ฝ๋๋ฅผ ์์ฑํ๋๋ผ๋ ๋ถ๋ฌ์ค์ง๋ฅผ ๋ชปํฉ๋๋ค.

๋ชจ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ณ ๋๋ฒ๊น ํด๋ณด๋ฉด, MemoService ๊ฐ์ฒด๊ฐ ์ฃผ์ ๋์์์ ๋ณด์ฌ์ค๋๋ค.
Test with FastAPI
์ด ํํธ์์๋ pytest, unittests์ ๊ฐ์ Python์ ํ ์คํธ ํ๊ฒฝ์ ๋ํด์ ์์ธํ ์ค๋ช ํ์ง๋ ์์ ๊ฒ์ ๋๋ค. ์ด ํํธ๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ Python์ ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ง์์ด ํ์ํฉ๋๋ค.
FastAPI์์ ์ ๋์ํ๋์ง๋ฅผ ํ ์คํธ ํด๋ณผ ์ ์์๊น? Dependency Injector ๋ฌธ์๋ FastAPI ํ๋ ์์ํฌ์ ๋ํ ์์ธํ ์ฌ์ฉ๋ฒ์ ์ ๊ณตํด์ค๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก FastAPI ํ๋ ์์ํฌ์์ ํ ์คํธํ ๋๋ ํ ์คํธ ๋ก๋๋งต์ ์ ์ง๋ ๊ฒ์ด ์ค์ํ๋ฐ, ๊ฐ์ธ์ ์ผ๋ก FastAPI์์ Dependency๋ฅผ ํ ์คํธํ ๋ unittest์ AsyncMock์ ์ฌ์ฉํฉ๋๋ค.
import pytest | |
from unittest.mock import AsyncMock | |
from src.asgi import api | |
from src.services.memo import MemoService, Memo | |
from tests.units.api import test_client as client, AsyncClient | |
_service_mock = AsyncMock(spec=MemoService) | |
@pytest.mark.asyncio | |
async def test_get_memos(client: AsyncClient): | |
_service_mock.memo = Memo() | |
with api.container.memo_service.override(_service_mock): | |
response = await client.get('/memos') | |
assert response.status_code == 200 |
Dependency Injector๊ฐ ๊ฐ์ง๋ ๊ฐ์ ์ ๋ฐ๋ก ํ ์คํธ ๊ฐ๋ฅ์ฑ์ ๋๋ค. ์ฐ๋ฆฌ๊ฐ ์ค์ ์์กด์ฑ์ ๊ตฌํํ์ง ์๊ณ ๋ ์ด๋ฌํ ์์กด์ฑ์ ์ฃผ์ ํ ์ํคํ ์ฒ๊ฐ ๋ฏธ๋ฆฌ ๊ตฌ์๋์ด ์๋ค๋ฉด ์ด๋ฅผ Mockingํ์ฌ ํ ์คํธ ํด ๋ณธ ๋ค์ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด์ ๊ฐ์ด ๋์ํ๋์ง๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
์ ์์ ๋ FastAPI๋ฅผ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ์ฌ์ฉํ์ ๋ ํ ์คํธ ์ฝ๋์ ์์์ด๋ฉฐ ์ค์ ๋ก ๋์ํ๊ธฐ ์ํด์๋ ์ถ๊ฐ์ ์ธ ์ฝ๋๋ฅผ ํ์๋ก ํฉ๋๋ค. test_client๋ Python์ ๋น๋๊ธฐ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ httpx๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋ฉฐ asgi์๋ ์ค์ ๋ก ๊ตฌํํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ์ฒด๊ฐ ๋ด๊ฒจ์ ธ ์์ต๋๋ค.
๋ํ ์ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ปจํ ์ด๋์์ Mocking์ ํด์ผํ๊ธฐ ๋๋ฌธ์ FastAPI ๊ฐ์ฒด์ container attribute๋ฅผ ์ถ๊ฐํ์ฌ IoC๋ฅผ ์ถ๊ฐํด๋๊ณ , ํ ์คํธ ํ๊ฒฝ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋์ํ ๊ฒฝ์ฐ์ IoC๋ฅผ ๋ง๋ค์ด, ์์กด์ฑ์ Mocking ํด์ค์ผ๋ง ์ํํ ํ ์คํธ ํ๊ฒฝ์ ๋ง๋ค์ด๋ผ ์ ์์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก Python์์ ๊ฐ์ฒด๋ฅผ Mockingํ ๋๋ ๋น๋๊ธฐ ๊ฐ์ฒด๋ฅผ ๋ชจํนํ๋ AsyncMock๊ณผ ๋๊ธฐ ๊ฐ์ฒด๋ฅผ ๋ชจํนํ๋ Mock์ด ์์ต๋๋ค. Infrastructure ๋ ์ด์ด์์ ๋น๋๊ธฐ ์ฐ๊ฒฐ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ AsyncMock์ ์ฌ์ฉํด์ผํ์ง๋ง ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๋ Mock์ ์ฌ์ฉํด์ผ ํ๋ฉฐ ์ฌ๋ฌ๋ถ๋ค์ด ์ง์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌํ์ ๋ฐ๋ผ ์ด๋ฅผ ์ ๋์ ์ผ๋ก ์ฌ์ฉํด์ผ ํฉ๋๋ค.
๋ง์น๋ฉฐ..
์ฌ๊ธฐ๊น์ง Dependency Injector๋ฅผ ์ด์ฉํ์ฌ FastAPI๋ฅผ ์์๋ก ์์กด์ฑ์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ดค์ต๋๋ค. Django๋ DRF(Django Rest Framework)์ ๊ฒฝ์ฐ์๋ ์์ฒด์ ์ผ๋ก ์์กด์ฑ ๊ด๋ฆฌ ํ๊ฒฝ์ ์ ๊ณตํ์ง๋ง Flask๋ Falcon, FastAPI์ ๊ฐ์ ๋ง์ดํฌ๋ก ํ๋ ์์ํฌ์์๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
์ฐ๋ฆฌ๋ ์ฌ๊ธฐ์ ์ฅ๋จ์ ์ ๋๋ ๋ณผ ์ ์๋๋ฐ, Django์์ ์ฌ์ฉํ๋ ์์กด์ฑ ๊ด๋ฆฌ ๋งค์ปค๋์ฆ์ ๋ค๋ฅธ ํ๋ ์์ํฌ์์ ์ฌ์ฉํ๊ธฐ๋ ์ด๋ ต์ง๋ง Dependency Injector์ ๊ฐ์ ๋ฒ์ฉ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์์กด์ฑ ๋งค์ปค๋์ฆ์ ์ฌ์ฉํ๋ค๋ฉด ๋ค๋ฅธ ํ๋ ์์ํฌ์ ์ด์ ์ด ์ฝ๋ค๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ ๋์จํ๊ณ , ๋ ๋ง์ ๊ฐ๋ฅ์ฑ์ ์ผ๋ํ๊ณ ์ ํ๋ค๋ฉด ์ด ์ ํ์ ๋์์ง ์๋ค๊ณ ๋ด ๋๋ค.
๋ง์ฝ FastAPI๋ฅผ ํ์ ์์ ์ฌ์ฉํ๊ณ ์๊ณ , Dependency Injector์ ๊ฐ์ ์์กด์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณ ๋ คํ๊ณ ์๋ค๋ฉด ์๋์ ๋งํฌ๋ฅผ ํตํด ํ๋ถํ ์์ ๋ฅผ ๊ฒฝํํด๋ณด๊ณ ์ฌ์ฉํด๋ณด์๋ ๊ฑธ ์ถ์ฒํฉ๋๋ค.
https://python-dependency-injector.ets-labs.org/examples/fastapi.html
FastAPI example โ Dependency Injector 4.36.0 documentation
FastAPI example This example shows how to use Dependency Injector with FastAPI. The example application is a REST API that searches for funny GIFs on the Giphy. The source code is available on the Github. Application structure Application has next structur
python-dependency-injector.ets-labs.org