Django DI(Dependency Injection, 의존성 주입)

Kyle·2026년 3월 16일

Django에서 DI를 잘 쓰지 않는 이유와, 그래도 도입을 고민해야 하는 순간

백엔드 아키텍처를 공부하다 보면 한 번쯤 DI(Dependency Injection, 의존성 주입)를 접하게 된다.
다른 언어나 프레임워크에서는 DI 컨테이너를 적극적으로 사용하는데, Django에서는 상대적으로 덜 보이는 편이다.
그러다 보니 이런 의문이 생기기 쉽다.

“DI는 좋은 개념이라는데, 왜 Django에서는 잘 안 쓰는가?”
“정말 필요 없는 것인가, 아니면 상황에 따라 필요한 것인가?”

결론부터 말하면, Django에서 DI 라이브러리가 널리 쓰이지 않는 이유는 간단하다.
대부분의 프로젝트에서는 굳이 없어도 개발이 가능하기 때문이다.
하지만 그렇다고 해서 DI의 장점이 사라지는 것은 아니다.
오히려 프로젝트 규모가 커지고 도메인 로직이 복잡해질수록 DI가 주는 구조적 이점은 분명해진다.

———

DI 없이 개발할 때의 모습

DI를 쓰지 않으면 보통 의존성을 클래스 내부에서 직접 생성하게 된다.

class PaymentService:
    def __init__(self):
        self.gateway = StripePaymentGateway()

    def pay(self, amount):
        return self.gateway.charge(amount)

이 방식은 처음에는 단순하고 직관적이다.
코드를 읽는 사람도 바로 이해할 수 있다.
작은 프로젝트에서는 이런 단순함이 오히려 장점이 되기도 한다.

하지만 구조적으로 보면 PaymentService가 StripePaymentGateway라는 구체 클래스에 강하게 결합되어 있는 상태다.
즉, 결제 수단을 Stripe에서 PayPal로 바꾸고 싶다면 PaymentService 코드를 직접 수정해야 한다.
비즈니스 로직과 인프라 구현이 한곳에 묶여 있는 셈이다.

이런 구조는 코드가 작을 때는 큰 문제가 없다.
하지만 기능이 늘어나고 외부 시스템 연동이 많아질수록 변경 비용이 커지기 시작한다.

———

DI를 도입했을 때 달라지는 점

DI를 도입하면 의존성을 클래스 내부에서 만들지 않고 외부에서 주입받도록 설계하게 된다.

class PaymentService:
    def __init__(self, gateway):
        self.gateway = gateway

    def pay(self, amount):
        return self.gateway.charge(amount)

그리고 어떤 구현체를 주입할지는 외부에서 결정한다.

container.register(PaymentGateway, StripePaymentGateway)
payment_service = container.resolve(PaymentService)

이 구조에서는 PaymentService가 더 이상 Stripe 구현체를 직접 알 필요가 없다.
오직 PaymentGateway라는 추상화만 의존하면 된다.
즉, 실제 구현이 Stripe이든 PayPal이든 PaymentService 입장에서는 중요하지 않다.

이때 핵심은 단순히 “객체를 밖에서 넣어준다”가 아니다.
더 중요한 것은 의존성의 방향이 추상화 중심으로 정리된다는 점이다.
비즈니스 로직은 구체 구현이 아니라 인터페이스에 의존하게 되고, 실제 구현체 선택은 외부 조립 영역에서 담당하게 된다.

———

DI를 쓰는 진짜 이유

DI 라이브러리를 쓰는 가장 큰 이유는 단순 편의성이 아니라 구조적인 규칙을 강제할 수 있기 때문이다.

1. 의존성의 흐름이 명확해진다

DI 없이 개발하면 객체가 어디서 생성되고 누구를 의존하는지 클래스 내부에 흩어지기 쉽다.
반면 DI 컨테이너를 사용하면 어떤 인터페이스에 어떤 구현체가 연결되는지가 설정 레벨에서 드러난다.
즉, 의존성 흐름이 코드 곳곳에 숨지 않고 한곳에서 관리된다.

2. 테스트가 쉬워진다

테스트에서는 실제 외부 시스템 대신 Mock이나 Stub을 넣고 싶을 때가 많다.
DI 구조에서는 이 작업이 자연스럽다.

mock_gateway = MockPaymentGateway()
service = PaymentService(gateway=mock_gateway)

클래스 내부에서 직접 객체를 생성하지 않기 때문에 테스트 대체가 훨씬 쉽다.
이 점은 프로젝트가 커질수록 큰 차이를 만든다.

3. 확장성이 좋아진다

새로운 결제 수단이 추가되더라도 기존 비즈니스 로직을 수정하지 않고 구현체만 추가하면 된다.
변경이 코드 수정이 아니라 조립 설정의 변경으로 이동하는 것이다.
이 차이는 유지보수성과 안정성에 직접적인 영향을 준다.

4. 팀 차원의 규칙을 만들 수 있다

DI 컨테이너를 도입하면 팀 내에서 일관된 아키텍처 규칙을 세우기 쉬워진다.
예를 들어 “비즈니스 서비스는 구체 구현을 직접 생성하지 않는다”, “의존성은 인터페이스를 통해 주입받는다” 같은 기준을 명확히 할 수 있다.
이런 규칙은 코드 리뷰 기준을 통일하고, 프로젝트가 커졌을 때 구조가 무너지는 것을 막는 데 도움이 된다.

———

그런데 왜 Django에서는 잘 안 쓰는가

여기서 중요한 질문이 남는다.
이렇게 장점이 많은데 왜 Django에서는 DI 라이브러리가 흔하지 않은가.

이유는 Django 자체가 기본적으로 다른 방식으로 문제를 해결해왔기 때문이다.

Django는 오래전부터 settings.py, 앱 단위 구조, 모델/매니저, 미들웨어, 시그널 같은 방식으로 애플리케이션을 구성해왔다.
그래서 많은 경우 굳이 DI 컨테이너를 두지 않아도 프로젝트가 굴러간다.
필요한 객체는 직접 import해서 쓰거나, 설정값과 팩토리 함수로 조합해도 충분한 경우가 많다.

또한 Django 생태계 자체가 전통적인 DI 프레임워크 중심으로 발전해온 문화가 아니다.
Spring처럼 컨테이너 기반 구성이 프레임워크의 중심 철학인 것도 아니다.
그래서 Django 개발자에게 DI 라이브러리는 종종 “좋은 개념이지만 꼭 필요하지는 않은 것”으로 받아들여진다.

결국 현실적인 이유가 크다.
작고 중간 규모의 Django 프로젝트에서는 DI 없이도 충분히 빠르고 단순하게 개발할 수 있다.
이때 DI 라이브러리까지 도입하면 오히려 복잡도만 늘어나고, 팀원 입장에서는 진입 장벽이 생길 수 있다.
즉, 과잉 설계가 되기 쉽다.

———

그럼 언제 DI 도입을 고민해야 하는가

Django에서 DI가 필요 없다는 말은 아니다.
다만 모든 프로젝트에 일괄적으로 적용할 필요가 없다는 뜻에 가깝다.

다음과 같은 상황이라면 DI 도입을 진지하게 고민할 만하다.

  • 비즈니스 로직이 점점 복잡해지고 있다.

  • 외부 결제, 메시징, 캐시, 파일 저장소 등 인프라 의존성이 많아지고 있다.

  • 테스트 작성 비용이 높고, Mock 주입이 번거롭다.

  • 서비스 레이어와 인프라 레이어를 명확히 분리하고 싶다.

  • 팀 차원에서 아키텍처 규칙을 강제하고 싶다.

    이런 프로젝트에서는 DI가 단순한 취향 문제가 아니라 유지보수 전략이 된다.
    특히 도메인 중심 설계를 지향하거나, 여러 구현체를 유연하게 교체해야 하는 시스템에서는 효과가 크다.

    ———

    정리

    DI 라이브러리를 쓰면 의존성을 더 “예쁘게” 관리할 수 있어서 좋은 것이 아니다.
    핵심은 의존성 관리 규칙을 구조적으로 강제하고, 비즈니스 로직이 구체 구현에 끌려가지 않도록 만드는 데 있다.

    Django에서 DI가 흔하지 않은 이유는 Django가 원래 그런 방식 없이도 충분히 개발 가능한 프레임워크이기 때문이다.
    작은 규모에서는 단순함이 더 중요할 수 있고, 그 경우 DI는 오히려 과한 선택이 될 수 있다.

    하지만 프로젝트가 커지고 도메인 로직이 복잡해질수록 이야기가 달라진다.
    그 시점에는 DI가 테스트, 확장성, 유지보수성 측면에서 분명한 장점을 제공한다.
    즉, Django에서 DI는 “무조건 필요한 기본값”은 아니지만, 특정 규모 이상에서는 충분히 가치 있는 선택지가 된다.

    한 줄로 정리하면 다음과 같다.

    Django에서 DI 라이브러리가 잘 안 쓰이는 이유는 없어도 개발이 되기 때문이고, 그래도 도입을 고민해야 하는 이유는 구조적인 규칙과 확장성을 확보할 수 있기 때문이다.

profile
깔끔하게 코딩하고싶어요

0개의 댓글