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-injector
from 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์„ ์“ฐ๋ฉด ํƒ€์ž… ํžŒํŠธ์˜ ๋„์›€์„ ๋ฐ›์œผ๋ฉด์„œ๋„ ์œ ์—ฐํ•จ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.