본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 의존성 주입(Dependency Injection)을 구현하는 독보적인 7가지 방법과 실무적 해결책

by Papa Martino V 2026. 3. 29.
728x90

의존성 주입(Dependency Injection)
의존성 주입(Dependency Injection)

 

파이썬의 동적 특성을 살리면서 코드의 결합도를 낮추는 아키텍처 설계의 정수


1. 왜 파이썬에서 의존성 주입(DI)이 중요한가?

현대 소프트웨어 개발에서 객체 간의 결합도(Coupling)를 관리하는 것은 유지보수성의 핵심입니다. 의존성 주입(Dependency Injection, DI)은 객체가 스스로 필요한 객체를 생성하는 것이 아니라, 외부에서 주입받는 디자인 패턴입니다. 흔히 Java의 Spring 프레임워크에서만 중요하다고 생각하기 쉽지만, 파이썬처럼 유연한 언어일수록 DI를 통해 테스트 용이성(Testability)확장성을 극대화할 수 있습니다. 본 포스팅에서는 단순한 이론을 넘어, 파이썬 환경에서 가장 '파이썬다운(Pythonic)' 방식으로 의존성을 관리하고 프로젝트의 복잡도를 해결하는 구체적인 전략을 다룹니다.

2. 의존성 주입 방식 및 전략 비교

구현 방식 주요 특징 장점 단점
생성자 주입 (Constructor) __init__을 통해 의존성 전달 명시적이며 불변성 유지에 유리 의존성이 많아지면 인자가 너무 많아짐
속성 주입 (Setter/Property) 인스턴스 생성 후 속성으로 할당 선택적 의존성 주입에 유용 객체가 불완전한 상태로 존재 가능
메서드 주입 (Interface) 메서드 호출 시 필요한 객체 전달 특정 기능 실행 시에만 의존성 필요 메서드 시그니처가 복잡해질 수 있음
DI 컨테이너 라이브러리 Dependency Injector 등 라이브러리 사용 대규모 프로젝트의 복잡한 의존성 관리 추가 학습 곡선 및 외부 라이브러리 의존

3. 실무 적용을 위한 7가지 핵심 Example

Example 1: 가장 기초적인 생성자 주입 (The Foundation)

가장 권장되는 방식입니다. 클래스가 생성될 때 필요한 인터페이스를 명확히 정의합니다.


class MessageService:
    def send(self, message: str):
        raise NotImplementedError

class EmailService(MessageService):
    def send(self, message: str):
        print(f"Sending Email: {message}")

class UserNotification:
    def __init__(self, service: MessageService):
        self.service = service  # 의존성 주입

    def notify(self, user_msg: str):
        self.service.send(user_msg)

# 실무 활용
email_api = EmailService()
notifier = UserNotification(email_api)
notifier.notify("시스템 점검 안내")
        

Example 2: 추상 베이스 클래스(ABC)를 활용한 인터페이스 강제

파이썬의 abc 모듈을 사용하여 주입될 객체의 규격을 엄격히 제한합니다.


from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass

class MySQLDatabase(Database):
    def connect(self):
        return "MySQL Connected"

class PostgreSQLDatabase(Database):
    def connect(self):
        return "Postgres Connected"

def start_application(db: Database):
    print(db.connect())

start_application(PostgreSQLDatabase())
        

Example 3: 디폴트 인자를 활용한 가벼운 DI

작은 규모의 프로젝트에서 별도의 설정 없이 테스트 용이성을 확보하는 기법입니다.


import os

class ConfigReader:
    def get_config(self):
        return os.environ.get("APP_ENV", "dev")

def process_data(config_loader=ConfigReader()):
    # 테스트 시에는 MockConfigReader()를 주입 가능
    current_env = config_loader.get_config()
    print(f"Running in {current_env} mode")
        

Example 4: 데코레이터를 활용한 의존성 주입 (FastAPI 스타일)

함수 실행 시점에 동적으로 의존성을 해결하는 방식입니다.


def inject_db(func):
    def wrapper(*args, **kwargs):
        db = "Session-Object" # 실제로는 DB 커넥션 풀링 로직
        return func(db, *args, **kwargs)
    return wrapper

@inject_db
def save_user_profile(db, user_id: int):
    print(f"Using {db} to save user {user_id}")
        

Example 5: 클로저를 이용한 의존성 캡슐화

클래스를 만들지 않고도 의존성을 고정시킨 함수를 생성할 수 있습니다.


def create_logger(destination: str):
    def log(message: str):
        print(f"[{destination}] LOG: {message}")
    return log

file_logger = create_logger("file.log")
file_logger("의존성 주입 완료")
        

Example 6: 'Dependency Injector' 라이브러리 활용 (고급)

대규모 엔터프라이즈 환경에서 컨테이너 기반으로 관리하는 선언적 방식입니다.


from dependency_injector import containers, providers

class ApiClient:
    def __init__(self, api_key: str):
        self.api_key = api_key

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    api_client = providers.Singleton(ApiClient, api_key=config.api_key)

container = Container()
container.config.api_key.from_value("SECRET_123")
client = container.api_client()
        

Example 7: 테스트 코드에서의 Mocking 주입 해결

DI의 진가는 테스트에서 발휘됩니다. 실제 API 호출 대신 가짜 객체를 주입합니다.


from unittest.mock import MagicMock

def test_user_notification():
    mock_service = MagicMock()
    notifier = UserNotification(mock_service)
    
    notifier.notify("Test")
    
    mock_service.send.assert_called_with("Test")
    print("Test Passed: Mock injected successfully")
        

4. 결론: 파이썬에서 DI를 성공적으로 도입하는 전략

파이썬에서 의존성 주입은 Java처럼 복잡할 필요가 없습니다. Duck Typing의 유연함을 살리되, 중요한 비즈니스 로직에는 명시적인 생성자 주입을 사용하는 것이 가장 세련된 방법입니다. 위의 7가지 예제를 상황에 맞게 조합한다면 코드의 품질을 한 단계 높일 수 있을 것입니다.

출처 및 참고문헌:

  • Python Software Foundation - Design Patterns
  • Dependency Injection in Python (Dependency Injector Documentation)
  • Clean Code in Python (Mariano Anaya)
728x90