Search

Django는 죄가 없다

소개

크고 작은 스타트업에서 다양한 Python 프로젝트를 운영했다.
그 중에서도 Django + DRF(Django REST Framework) 조합을 가장 많이 사용했다.
사실상 두 개의 프레임워크를 함께 사용하는 것이 표준처럼 인식되고 있다.
그런데 개인적으로 Django + DRF를 쓸 때마다 개발자 경험이 좋지 않다.
프로젝트가 커질수록 유지보수가 어렵고 확장성이 떨어진다는 느낌을 받는다.
물론 프레임워크의 장단점을 잘 이해하고 컨벤션을 정해서 잘 쓰고 있는 팀들도 있을 것이다.
하지만 DRF에 구조적인 문제로 인해 좋은 코드 설계를 가져가기 어렵다고 생각한다.
이번 글에서는 DRF가 어떤 문제를 갖고 있는지 알아보고 대안을 제시한다.

DRF를 사용할 때 겪는 문제

DRF를 사용하다보면 누가 코드를 작성했는지에 따라 비지니스 로직의 위치가 달라진다.
심지어 같은 사람이 작성했더라도 비지니스 로직의 위치가 일정하지 않은 경우가 빈번하다.
어떤 코드는 model에서, 어떤 코드는 view에서, 어떤 코드는 serializer에서 비지니스 로직을 다룬다.
그래서 내가 짠 코드일지라도 조금만 복잡도가 올라가면 비지니스 로직 파악이 어려워진다.
간단한 기능 수정인데 연관된 모든 코드를 봐야하는 경우가 발생한다.
기본적으로 단일 책임의 원칙(Single Responsibility Principle)이 지켜지지 않는다.
더 큰 문제는 Django는 ORM을 내장하고 있는데 DRF는 프레임워크 자체가 Django ORM을 깊게 다룬다.
깊게 다룬다는 말은 다시 말하면 강하게 종속되어 있다는 이야기다.
DRF의 모든 컴포넌트는 아무데서나 ORM을 통해 데이터베이스에 접근할 수 있다.
너무 많은 자유가 있기 때문에 조금만 부주의하게 사용하면 금방 모든 의존성이 엉켜버린다.
이는 데이터의 상태 추적을 어렵게 만들고 무수히 많은 사이드이펙트를 발생시킨다.
그리고 결국 개발자는 시스템의 통제권을 잃게 된다.

DRF의 문제점

조금 더 구체적으로 DRF 문제점을 살펴보자.

Serializer

먼저 DRF의 Serializer는 너무 많은 역할을 가지고 있다.
원칙적으로 serializer는 데이터 직렬화와 역직렬화, validation 등 데이터 입출력 관리만 해야한다.
하지만 실제로 DRF Serializer는 그 이상의 훨씬 많은 것을 한다.
Serializer에서 복잡한 비지니스 로직을 처리하기도 하고 데이터 변경도 자유롭게 일어난다.
심지어 공식 문서에 이런 사용을 권장하고 있다.
Django request 객체에 대한 접근도 가능해서 사실상 Controller의 모든 역할을 대체할 수 있다.

View(Controller)

DRF를 사용하면 필연적으로 DRF의 View와 ViewSet을 사용하게 된다.
View 역시 문제가 많다.
DRF Serializer가 문제라고 이야기했는데 View는 Serializer에 의존적이다.
대부분의 메소드들이 serializer를 중심으로 동작한다.
DRF View는 Django ORM에 의존적이다.
그렇기 때문에 프로그램을 항상 데이터베이스 중심적으로만 바라보게 된다.
DRF View를 사용하려면 어떤 테이블을 사용할 것인지 Django QuerySet부터 정의해야한다.
그런데 하나의 Controller에서 반드시 하나의 테이블에만 접근하는 경우는 매우 드물다.
프레임워크가 제안하는 방식과 실제 구현의 괴리가 발생한다.
DRF View는 템플릿 메소드 패턴을 활용하여 확장성을 제공하고 controller의 동작을 제어한다.
하지만 고정된 메소드 순서 안에서 복잡한 구현을 위해 메소드를 오버라이드 하다보면 view마다 동작이 달라진다.
개방-폐쇄 원칙(Open-closed Principle)에 어긋난다.
기본적으로 DRF 전체에 클래스 상속이 너무 강하게 사용되기 때문에 발생하는 문제다.
DRF는 매우 간단한 CRUD 기능을 코드 몇 줄 만으로 만들어준다는 장점이 있다.
하지만 조금만 요구조건이 복잡해지면 결국 모든 것을 새로 정의해서 사용해야 되는 상황이 발생한다.
이 때 수많은 안티패턴이 만들어진다.

Django는 죄가 없다

한가지 재밌는 사실은 Django는 느슨한 결합과 강한 응집력을 추구한다는 점이다.
Django 공식 문서의 디자인 철학 가장 첫 번째 줄에 나와있다.
실제로 Django Template 엔진은 웹 요청이나 데이터베이스와 의존성이 하나도 없다.
다만 Django가 풀스택 프레임워크를 지향하기 때문에 모든 부분이 완벽히 독립적이지 않다.
예를 들어, Admin과 같은 기능은 ORM에 완전히 종속적이다.
그럼에도 Django 자체의 디자인적인 결함이 있다고 생각되지는 않는다.
Django의 ORM/Migrations, Security, Admin 같은 기능은 매우 유용하다.
Django와 DRF를 함께 사용했을 때 발생하는 대부분의 문제는 DRF의 문제다.
그렇다면 Django의 장점만 취할 수 있는 방법은 없을까?

대안 제시

개인적으로 DRF 대신 Django Ninja를 사용하고 있다.
Django Ninja는 Pydantic을 이용하여 serializer를 대체한다.
Pydantic을 이용하면 몇 가지 장점이 있다.
먼저 OpenAPI Spec의 문서를 자동으로 생성해주고 IDE Support를 활용할 수 있다.
또 serializer를 사용할 때 보다 작성해야 되는 코드의 양이 줄어들고 성능도 훨씬 빠르다.
대신 DRF에서 쉽게 적용할 수 있던 권한 관리나 페이지네이션 등은 직접 구현해야 한다.
그리고 간단한 CRUD 작업도 마찬가지다.
Django Ninja를 사용하면 DRF 보다 유연하고 확장성 있는 설계를 취할 수 있기 때문에 매우 만족하고 있다.
어떻게 Django와 Django Ninja를 함께 사용하는지 궁금하다면 아래의 레포를 참고하면 된다.
개인적으로 Domain-Driven Design을 선호하기 때문에 DDD까지 적용한 코드 예시다.
django-ddd
qu3vipon

끝 마치며

DRF를 사용하는 것이 무조건 잘못됐다는 말은 아니다.
다만 DRF를 관행처럼 사용하지 않아도 된다고 생각한다.
Django Ninja가 좋은 대안이 될 수 있을 것이다.