Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
TL;DR
- Dependency Injection(DI)์ ํด๋์ค๊ฐ ์์กด์ฑ์ ์ง์ ์์ฑํ์ง ์๊ณ ์ธ๋ถ์์ ์ฃผ์ ๋ฐ๋ ์ค๊ณ ํจํด
- Strategy ํจํด๊ณผ ๊ฒฐํฉํ๋ฉด Open/Closed Principle์ ์งํค๋ฉด์ ๊ธฐ๋ฅ์ ํ์ฅ ๊ฐ๋ฅ
- Python์์๋ Protocol(๊ตฌ์กฐ์ ์๋ธํ์ดํ)๋ก ์ธํฐํ์ด์ค๋ฅผ ์ ์ํ๊ณ , ์ ๋ต ๊ฐ์ฒด ๋๋ functools.partial ํํ๋ก ๊ฒฝ๋ ๊ตฌํ๋ ๊ฐ๋ฅ
- ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด
dependency-injector๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์์กด์ฑ ๊ทธ๋ํ๋ฅผ ์ ์ธ์ ์ผ๋ก ๊ด๋ฆฌ
Source
1. Dependency Injection์ด๋
Dependency Injection(์์กด์ฑ ์ฃผ์ , DI)์ ๊ฐ์ฒด๊ฐ ํ์๋ก ํ๋ ์์กด์ฑ์ ์ค์ค๋ก ๋ง๋ค์ง ์๊ณ ์ธ๋ถ์์ ๋๊ฒจ๋ฐ๋ ์ค๊ณ ํจํด์ด๋ค.
ํต์ฌ ์์ด๋์ด๋ ๊ฐ๋จํ๋ค.
- DI ์์ด: ํด๋์ค ๋ด๋ถ์์
self.db = DatabaseConnection()์ฒ๋ผ ์ง์ ์์ฑ - DI ์ ์ฉ: ์์ฑ์ ํ๋ผ๋ฏธํฐ๋ก
def __init__(self, db):์ฒ๋ผ ๋ฐ์์ ์ฌ์ฉ
์ด ํ ๊ฐ์ง ์ฐจ์ด๊ฐ ์ฝ๋์ ๊ฒฐํฉ๋, ํ ์คํธ ์ฉ์ด์ฑ, ๊ต์ฒด ๊ฐ๋ฅ์ฑ์ ํฌ๊ฒ ๋ฐ๊พผ๋ค.
2. ์ ํ์ํ๊ฐ
DI ์์ด ์์กด์ฑ์ ํ๋์ฝ๋ฉํ๋ฉด ์๊ธฐ๋ ๋ฌธ์ ๋ค์ด ์๋ค.
- ํ ์คํธ๊ฐ ์ด๋ ต๋ค - ํด๋์ค๋ฅผ ํ ์คํธํ ๋๋ง๋ค ์ค์ DB๋ ์ธ๋ถ API์ ์ฐ๊ฒฐํด์ผ ํ๋ค
- ๊ตฌํ ๊ต์ฒด๊ฐ ํ๋ค๋ค - PostgreSQL์์ MySQL๋ก ๋ฐ๊พธ๋ ค๋ฉด ์ฌ์ฉํ๋ ํด๋์ค๋ฅผ ์ ๋ถ ์์ ํด์ผ ํ๋ค
- ๊ฒฐํฉ๋๊ฐ ๋๋ค - ํ๋๋ฅผ ๋ฐ๊พธ๋ฉด ์ฐ์์ ์ผ๋ก ๋ค๋ฅธ ๊ณณ์ด ๊นจ์ง๋ค
ํต์ฌ ์์น
โ๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ด ์๋๋ผ ์ธํฐํ์ด์ค(์ถ์)์ ์์กดํ๋ผโ - ์ด๊ฒ์ด DI์ ๊ทผ๋ณธ ์์น์ด๋ค.
3. ์ค์ ์์ - ์ง๋ ฌํ ํฌ๋งท ์ ํ
DI๊ฐ ์ ํ์ํ์ง ์ง๋ ฌํ(serialize) ์์๋ก ๋ณด์. ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ JSON, TOML, YAML ๋ฑ ์ฌ๋ฌ ํฌ๋งท์ผ๋ก ๋ณํํ๋ ์ํฉ์ด๋ค.
DI ์๋ ์ฝ๋ - ํฌ๋งท์ ๋ฌธ์์ด๋ก ํ๋์ฝ๋ฉ
@dataclass
class UserData:
username: str
email: str
def serialize(self, format: str) -> str:
if format == "json":
return json.dumps(asdict(self))
elif format == "toml":
return toml.dumps(asdict(self))
else:
raise ValueError(f"Unknown format: {format}")
@classmethod
def deserialize(cls, data: str, format: str) -> "UserData":
if format == "json":
return cls(**json.loads(data))
elif format == "toml":
return cls(**toml.loads(data))
# ...๋งค๋ฒ elif ์ถ๊ฐYAML ํฌ๋งท์ ์ถ๊ฐํ๋ ค๋ฉด serialize์ deserialize ๋ ๋ฉ์๋ ๋ชจ๋ ์์ ํด์ผ ํ๋ค. ์ด๊ฒ์ด Open/Closed Principle(OCP) ์๋ฐ์ด๋ค. OCP๋ โํ์ฅ์๋ ์ด๋ ค ์๊ณ , ์์ ์๋ ๋ซํ ์์ด์ผ ํ๋คโ๋ ์ค๊ณ ์์น์ด๋ค.
DI ์ ์ฉ ์ฝ๋ - Strategy ํจํด์ผ๋ก ์ฃผ์
from typing import Any, Protocol
class SerializerStrategy(Protocol):
def serialize(self, data: dict[str, Any]) -> str: ...
def deserialize(self, data: str) -> dict[str, Any]: ...
class JsonSerializer:
def serialize(self, data: dict[str, Any]) -> str:
return json.dumps(data, indent=2)
def deserialize(self, data: str) -> dict[str, Any]:
return json.loads(data)
class TomlSerializer:
def serialize(self, data: dict[str, Any]) -> str:
return toml.dumps(data)
def deserialize(self, data: str) -> dict[str, Any]:
return toml.loads(data)
class YamlSerializer:
def serialize(self, data: dict[str, Any]) -> str:
return yaml.dump(data)
def deserialize(self, data: str) -> dict[str, Any]:
return yaml.safe_load(data)@dataclass
class UserData:
username: str
email: str
def serialize(self, strategy: SerializerStrategy) -> str:
return strategy.serialize(asdict(self))
@classmethod
def deserialize(cls, data: str, strategy: SerializerStrategy) -> "UserData":
return cls(**strategy.deserialize(data))user = UserData("alice", "alice@example.com")
print(user.serialize(JsonSerializer())) # JSON
print(user.serialize(TomlSerializer())) # TOML
print(user.serialize(YamlSerializer())) # YAML์ ํฌ๋งท(์: MessagePack)์ ์ถ๊ฐํด๋ UserData ํด๋์ค๋ ํ ์ค๋ ์์ ํ์ง ์๋๋ค. ์ ๋ต ํด๋์ค๋ง ํ๋ ๋ง๋ค๋ฉด ๋๋ค. ์ด๊ฒ์ด OCP๋ฅผ ์งํค๋ DI + Strategy ํจํด์ด๋ค.
4. ํด๋์ค ์์ด ๊ฒฝ๋ DI - functools.partial + tuple
์์์์๋ ์ ๋ต์ ํด๋์ค๊ฐ ์๋ tuple๋ก ๊ฒฝ๋ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๋ ๋ณด์ฌ์ค๋ค. ๊ฐ๋จํ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ค.
from functools import partial
# (encoder, decoder) ํํ๋ก ์ ๋ต ์ ์
json_strategy = (partial(json.dumps, indent=2), json.loads)
toml_strategy = (toml.dumps, toml.loads)
yaml_strategy = (partial(yaml.dump), yaml.safe_load)@dataclass
class UserData:
username: str
email: str
def serialize(self, strategy: tuple) -> str:
encoder, _ = strategy
return encoder(asdict(self))
@classmethod
def deserialize(cls, data: str, strategy: tuple) -> "UserData":
_, decoder = strategy
return cls(**decoder(data))user = UserData("bob", "bob@example.com")
print(user.serialize(json_strategy))
print(user.serialize(yaml_strategy))๋์์ธ ํจํด์ ์๊ฒฉํ๊ฒ ํด๋์ค๋ก ๊ตฌํํ ํ์๋ ์๋ค. ํต์ฌ ์์ด๋์ด๋ง ์งํค๋ฉด ํจ์์ tuple๋ก๋ ๊ฐ์ ํจ๊ณผ๋ฅผ ๋ผ ์ ์๋ค.
5. ์ผ๋ฐ์ ์ธ DI ํจํด - DB ์ฐ๊ฒฐ ์์
์ง๋ ฌํ ์ธ์๋, DI๊ฐ ๊ฐ์ฅ ํํ๊ฒ ์ฐ์ด๋ ํจํด์ DB ์ฐ๊ฒฐ์ด๋ค.
DI ์๋ ์ฝ๋
class UserRepository:
def __init__(self):
self.database = DatabaseConnection() # ์ง์ ์์ฑ - ๊ฐํ ๊ฒฐํฉ
def get_users(self):
return self.database.execute_query("SELECT * FROM users")UserRepository()๋ฅผ ๋ง๋ค๋ฉด ํญ์ ์ค์ DB์ ์ฐ๊ฒฐ๋๋ค. ํ
์คํธํ ๋ฐฉ๋ฒ์ด ์๋ค.
DI ์ ์ฉ ์ฝ๋
class UserRepository:
def __init__(self, database_connection):
self.database = database_connection # ์ธ๋ถ์์ ์ฃผ์
def get_users(self):
return self.database.execute_query("SELECT * FROM users")
# ํ๋ก๋์
repository = UserRepository(DatabaseConnection())
# ํ
์คํธ - Mock์ผ๋ก ๊ต์ฒด
test_repo = UserRepository(MockDatabase())๊ฐ์ ํด๋์ค๋ฅผ ์ค์ DB๋ก๋, Mock์ผ๋ก๋ ์ฌ์ฉํ ์ ์๋ค.
6. Python์์์ DI ๊ตฌํ ๋ฐฉ๋ฒ
Python์์ DI๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ธ ๊ฐ์ง๋ค.
4-1. Constructor Injection (์์ฑ์ ์ฃผ์ )
๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ๊ถ์ฅ๋๋ ๋ฐฉ์์ด๋ค. ๊ฐ์ฒด ์์ฑ ์์ ์ ์์กด์ฑ์ ๋๊ธด๋ค.
class UserRepository:
def __init__(self, database_connection):
self.database = database_connection
def get_users(self):
return self.database.execute_query("SELECT * FROM users")- ์์กด์ฑ์ด ๋ช ํํ๊ฒ ๋๋ฌ๋๋ค
- ๋ถ๋ณ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ข๋ค
- ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ด ๋ฐฉ์์ด๋ฉด ์ถฉ๋ถํ๋ค
4-2. Setter Injection (์ธํฐ ์ฃผ์ )
๊ฐ์ฒด ์์ฑ ํ์ ์์กด์ฑ์ ์ค์ ํ๋ค.
class UserRepository:
def __init__(self):
self.database = None
def set_database(self, database_connection):
self.database = database_connection
def get_users(self):
return self.database.execute_query("SELECT * FROM users")- ์ ํ์ ์์กด์ฑ์ ์ ํฉํ๋ค
- ์์กด์ฑ์ด ์ค์ ๋๊ธฐ ์ ์ ํธ์ถ๋๋ฉด ์๋ฌ๊ฐ ๋ ์ ์๋ค
4-3. Method Injection (๋ฉ์๋ ์ฃผ์ )
๋ฉ์๋ ํธ์ถ ์๋ง๋ค ์์กด์ฑ์ ๋๊ธด๋ค.
class UserRepository:
def get_users(self, database_connection):
return database_connection.execute_query("SELECT * FROM users")- ํธ์ถ๋ง๋ค ๋ค๋ฅธ ์์กด์ฑ์ ๋๊ธธ ์ ์๋ค
- ๋งค๋ฒ ๋๊ฒจ์ผ ํ๋ฏ๋ก ๋ฐ๋ณต์ ์ด๋ค
7. Python Protocol๊ณผ DI
Python์ typing ๋ชจ๋์์ ์ ๊ณตํ๋ Protocol์ ํ์ฉํ๋ฉด DI๋ฅผ ๋ ์์ ํ๊ฒ ์ธ ์ ์๋ค. Java์ ์ธํฐํ์ด์ค์ ๋น์ทํ ์ญํ ์ด๋ค.
from typing import Protocol
class Database(Protocol):
def execute_query(self, query: str) -> list[str]: ...
class PostgresDB:
def execute_query(self, query: str) -> list[str]:
# ์ค์ Postgres ์ฟผ๋ฆฌ ์คํ
return ["user1", "user2"]
class MockDB:
def execute_query(self, query: str) -> list[str]:
return ["mock_user"]
class UserRepository:
def __init__(self, database: Database):
self.database = database
def get_users(self) -> list[str]:
return self.database.execute_query("SELECT * FROM users")Database Protocol์ ๋ง์กฑํ๋ ๊ฐ์ฒด๋ผ๋ฉด ์ด๋ค ๊ฒ์ด๋ ์ฃผ์
ํ ์ ์๋ค. ๋ณ๋์ ์์ ์์ด ๊ตฌ์กฐ๋ง ๋ง์ผ๋ฉด ๋๋ค(structural subtyping).
8. dependency-injector ๋ผ์ด๋ธ๋ฌ๋ฆฌ
ํ๋ก์ ํธ ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด ์์กด์ฑ ์ฐ๊ฒฐ์ ์๋์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์์ง๋ค. dependency-injector ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ด๋ฅผ ์ปจํ
์ด๋๋ก ์ ์ธ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค.
pip install dependency-injectorfrom dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
# Singleton: ์ฑ ์ ์ฒด์์ ํ๋๋ง ์์ฑ
database = providers.Singleton(PostgresDB)
# Factory: ํธ์ถํ ๋๋ง๋ค ์๋ก ์์ฑ
user_repository = providers.Factory(
UserRepository,
database=database,
)
user_service = providers.Factory(
UserService,
user_repository=user_repository,
)
# ํ๋ก๋์
container = Container()
service = container.user_service()
# ํ
์คํธ - override๋ก Mock ๊ต์ฒด
container.database.override(providers.Singleton(MockDB))
test_service = container.user_service()Provider ์ข ๋ฅ
Singleton- ํ ๋ฒ ์์ฑ ํ ์ฌ์ฌ์ฉ (DB ์ฐ๊ฒฐ ๋ฑ)Factory- ๋งค๋ฒ ์ ์ธ์คํด์ค ์์ฑConfiguration- ์ค์ ๊ฐ ๊ด๋ฆฌ
9. ํ ์คํธ์์์ DI ํ์ฉ
DI์ ๊ฐ์ฅ ํฐ ์ค์ฉ์ ์ด์ ์ ํ ์คํธ๋ค.
import pytest
class FakeUserRepository:
def __init__(self):
self.users = ["alice", "bob"]
def get_users(self):
return self.users
def test_get_all_users():
fake_repo = FakeUserRepository()
service = UserService(fake_repo)
result = service.get_all_users()
assert result == ["alice", "bob"]- ์ค์ DB ์์ด ๋น ๋ฅด๊ฒ ํ ์คํธํ ์ ์๋ค
- ํ ์คํธ ๋์ ํด๋์ค๋ง ๊ฒฉ๋ฆฌํด์ ๊ฒ์ฆํ ์ ์๋ค
- ์ธ๋ถ ์๋น์ค ์ฅ์ ์ ๋ฌด๊ดํ๊ฒ ํ ์คํธ๊ฐ ์์ ์ ์ด๋ค
10. ์ ๋ฆฌ
| ํญ๋ชฉ | DI ์์ | DI ์ ์ฉ |
|---|---|---|
| ๊ฒฐํฉ๋ | ๋์ (๊ตฌํ์ ์ง์ ์์กด) | ๋ฎ์ (์ถ์์ ์์กด) |
| ํ ์คํธ | ์ด๋ ค์ (์ค์ ์์กด์ฑ ํ์) | ์ฌ์ (Mock ์ฃผ์ ๊ฐ๋ฅ) |
| ๊ต์ฒด ๊ฐ๋ฅ์ฑ | ๋ฎ์ (์ฝ๋ ์์ ํ์) | ๋์ (์ฃผ์ ๋ง ๋ณ๊ฒฝ) |
| ๋ณต์ก๋ | ๋ฎ์ | ์ฝ๊ฐ ์ฆ๊ฐ |
์ค๋ฌด ํ
- ๊ฐ๋จํ ์คํฌ๋ฆฝํธ์์๋ DI๊ฐ ๊ณผํ ์ ์๋ค. ํด๋์ค ๊ฐ ์์กด ๊ด๊ณ๊ฐ ์๊ธฐ๊ธฐ ์์ํ ๋ ๋์ ํ์.
- Python์ duck typing ๋๋ถ์ Java์ฒ๋ผ ์ธํฐํ์ด์ค๋ฅผ ๊ฐ์ ํ์ง ์์๋ DI๊ฐ ์์ฐ์ค๋ฝ๋ค.
Protocol์ ์ฐ๋ฉด ํ์ ํํธ์ ๋์์ ๋ฐ์ผ๋ฉด์๋ ์ ์ฐํจ์ ์ ์งํ ์ ์๋ค.