장고에서 어떤 방법을 통해 서로의 코딩 규칙, 비즈니스 로직의 위치, 각 책임의 분리를 만들어서 효율적인 개발, 수정 및 유지보수를 가능하게 하는걸까?
장고에서 개발할 때 항상 고민되는 부분을 생각해보자.
비즈니스 로직을 어디다 둘 것인가?
장고를 처음 개발할 때 보통 view에 적곤한다. 간단한 경우 혹은 해당 시나리오의 진입점이 이 API 하나인 경우에 선택해서 사용해도 비즈니스 로직이 이곳저곳에 존재한다는 점 말고는 괜찮은 편인듯하다.
그런데 장고 개발을 하다보면 해당 비즈니스 로직을 처리해서 사용하는 부분이 한군데인 경우가 오히려 적고 재사용 되는 부분이 훨씬 많이 생기기도 한다. 이때는 view에 작성된 코드를 하나씩 찾아서 수정해야하는 불상사가 발생해서 view에 작성하는 것은 지양하는 편이다.
필요한 비즈니스 로직을 signal에 둘 수도 있다. 특정 오브젝트가 생성된 경우 전처리, 후처리에 필요한 로직을 편하게 넣을 수 있다. 기존에 작성된 코드를 건들지 않고 추가 로직을 편하게 붙일 수 있는 장점이 존재한다.
그런데 코드를 보는 입장에선 로직의 흐름을 파악하기 힘들어지는 문제가 발생한다. 특정 API에서 코드의 흐름을 추적하다가 오브젝트가 삭제, 수정, 생성될 때 시그널까지 봐야하거나 미처 시그널을 확인하지 못하는 경우 문제가 발생할 가능성이 크다.
또 테스트 코드를 작성할때 시그널에 중요한 비즈니스 로직이 포함된 경우 서비스 레이어처럼 별도로 비즈니스 로직만 떼어다가 테스트를 하거나 하는 것이 불가능해진다.
데이터에 접근하는 로직을 매니저, 쿼리셋에 정의하고 그것을 사용하는 방법이다. 간단한 경우에는 가능하나 외부 API 호출이 필요하다거나 로직이 복잡해지는경우 사용하기 힘들다.
비즈니스 로직을 모델에 두는 것을 Fat Model이라고도 부르며 심한 경우에는 God Model이라고 한다.
모델이 비즈니스 로직을 갖게되는 즉 도메인을 정의하고 해당 도메인의 행동까지 표현한다. 모델의 메소드, 프로퍼티에 각 필드의 검증에 대한 기능뿐만아니라 핵심 로직, 행동을 다 표현한다. 이를 통해 직관적으로 모델만보면 해당 모델이 어떻게 동작할지 알 수 있고 코드의 작성이 간단하고 편하다.
문제는 프로젝트의 규모가 커질 경우, 해당 모델의 사이즈가 너무 커지고 다른 모델과의 상호작용이 빈번하게 발생하는 경우 유지보수가 점점 힘들어 질수있다.
모델에서 비즈니스 로직을 수행하는 것이 아닌 무결성 검증, 데이터 검증만 수행하고 중요한 비즈니스 로직에 대한 부분은 별도의 함수, 클래스로 빼서 진행하는 방식이다.
장점으로는 여러 모델이 상호작용하는 경우 비교적 깔끔하게 분리할 수 있고 서비스 레이어만 보면 대부분의 비즈니스 로직을 확인 가능하며 코드의 중복을 많이 없앨 수 있다. 테스트도 서비스 레이어를 위주로 진행하면 대부분의 비즈니스 로직에 대한 테스트를 간단하게 진행할 수 있다.
단점으로는 간단한 프로젝트를 진행하는데 서비스 레이어를 사용하는 경우 여러 파일로 분리된 상태이므로 한눈에 확 보이지 않고 해당 모델이 동작하는 기능을 빠르게 찾긴 힘들어진다. 그리고 특정 모델의 동작이 여러 서비스 레이어에 중복적으로 작성될 가능성도 높아지는 편이다. 설계 난이도도 다른 방법에 비해 높아지는 편이며 파일, 레이어를 나누는 명확한 기준이 필요해진다.
개발자들의 선호도, 프로젝트의 규모 등 잘 고려해서 프로젝트에 맞는 방법을 찾아서 사용하는 것이 중요하다.
장고 프로젝트를 진행하다보면 Middleware, Decorator, Mixin을 많이 쓰게된다. 이 때 세가지의 기능이 중복되거나 혼재되어 사용되는 경우가 많아진다. 프로젝트를 진행할 때 각 기능은 어느 범위, 어느 기능을 담당할건지 구분이 필요하다.
전역적으로 요청 발생시 request에 대한 전처리 response에 대한 후처리가 가능하다. 기본적으로 세션, Csrf등 처리에 사용되며 특정 상황에서 조건 미달성시 특정 페이지로 보내는 기능을 손쉽게 구현할 수 있다. 토큰 인증도 처리할 수 있으나 보통 Authentication 클래스를 사용하는 편이다.
특정 뷰, 메소드의 전, 후에 특정 처리가 필요할 경우 주로 사용한다. 특정 API의 특정 메소드에 권한 인증, 캐싱 등이 필요한 경우 정의해서 사용한다.
각 뷰에서 공통적으로 사용되거나 특정 기능을 분리해서 각 뷰에서 믹스인을 상속받아서 구현이 가능하다. API를 만들 때 주로 사용하는 GenericAPIView가 믹스인을 통해서 구현되며 GenericAPIView에서 필요한 기능만 사용하고 싶을 경우 특정 믹스인만 가져와서 사용할 수 있다.
API를 만들다보면 들어오는 입력에 대한 검증, 혹은 출력으로 줘야하는 모델에 대한 직렬화 등 많은 곳에서 serializer를 사용하게 된다. 그런데 이 serializer도 사용하는 방법이 여러가지 있다.
serializer를 하나만 구현하고 입력, 출력, 전체수정, 부분수정에 대해 필요한 필드의 구분은 read_only, write_only, partial 등 옵션에 의존해서 구현하는 방식이다.
입력에 필요한 시리얼라이저, 출력에 필요한 시리얼라이저를 별도로 구현해서 사용한다. 이 경우 GenericAPIView를 사용하는 경우 get_serializer_class를 재정의해서 매핑하여 사용할 수 있다.
각 APIView에서 필요한 serializer를 이너 클래스로 구현해서 사용한다. 시리얼라이저의 재사용을 최대한 줄여 각 시리얼라이저 수정시 다른 APIView에 영향을 끼치는 것을 줄일 때 사용한다.
(작성중)