DRY를 위해 Django Manager를 적용해봅시다.

안녕하세요. 화해팀 백엔드 개발자 장영석입니다. 

 

화해팀의 백엔드 플랫폼에서는 다양한 언어와 프레임워크를 사용하여 개발을 진행해 나가고 있습니다. 그중 가장 많이 사용되고 있는 웹 프레임워크는 Django입니다. ORM QuerySet을 적극적으로 사용하는 것이 Django 특징 중 하나입니다. 하지만 애플리케이션의 규모가 커질수록 QuerySet 중복 코드가 늘어나면서 유지 관리가 어려워지는 단점이 있습니다.

 

위와 같은 문제를 인지하고 개선하고 품질을 유지해나가기 위한 화해팀 백엔드 플랫폼의 활동들을 소개하려 합니다.

 

 

 

백엔드 플랫폼의 일하는 방식 중에는 주간 Meet Up 행사가 있습니다. 주간 이슈들 (장애 처리에 대한 회고, 해결되어야 할 문제, 기술 부채 해결 등)을 문서에 기록해 두고 이 시간을 활용해 이러한 내용들을 함께 공유하고 해결책을 논의 해나가면서 함께 성장 해나가기 위한 행사입니다.

 

 

 

 

논의 대상은 예상 소요 시간과 우선순위를 고려해 선정됩니다. 쉽게 결정되지 않을 경우 투표를 활용합니다.

 

논의 결과를 통해 액션 아이템을 생성합니다. Django의 ORM QuerySet 중복 코드를 해결하기 위한 방법으로 Django manager를 사용하기로 결정되었습니다. 바로 적용하기보다는 팀원 전체의 상향 평준화를 위해 별도의 학습 기간을 가지고 여러 사례에 적용하기로 합니다.

 

 

 

 

 

 

QuerySet 중복 코드의 발생

 

아래와 같은 사용자 Model이 있다고 가정해 봅시다.

 

 

대부분 활성화 사용자 조회를 필요로 하기에 아래 코드들이 여기저기 중복으로 발생하게 됩니다.

 

 

해당 QuerySet을 리턴하는 함수를 작성하여 공유할 수 있지만 사용해보니 응집도가 떨어져 사용률이 적다는 단점이 있었습니다. 이를 개선하기 위해 도입한 Django의 Manager에 대한 이야기를 적어보겠습니다.

 

 


 

 

Django Manager의 역할

 

Manager는 Django Model에 제공되는 데이터베이스 쿼리 인터페이스입니다. 모든 Model은 하나 이상의 Manager를 가지게 됩니다.

 

 

 

Manager 이름 사용하기

 

Django는 기본으로 모든 Model에 objects라는 이름으로 Manager를 추가합니다. 그렇기에 별도의 Manager 추가 없이 QuerySet을 사용할 수 있습니다.

 

 

만약 Manager 이름을 변경하고 싶다면 해당 Model에 models.Manager() 타입의 클래스 attribute를 정의할 수 있습니다.

 

 

위 예제처럼 Model의 Manager 이름을 재정의하게 되면 기본으로 제공되는 objects를 사용할 수 없습니다.

 

그럼에도 objects를 사용한다면 AttributeError 예외를 만나게 됩니다.

 

 


 

커스텀 Manager 사용하기

 

기본 Manager의 확장이 필요한 경우 커스텀 Manager를 모델에 사용할 수 있습니다.

 

필요성에 따라 커스텀 Manager를 활용하는 방법은 두 가지로 분류할 수 있습니다. extra Manager method를 추가하는 것과 initial QuerySet을 수정하는 것입니다.

 

 

Extra Manager method 추가하기

집계 쿼리 등의 “table-level”의 기능을 작성할 때 extra Manager method를 추가합니다. “row-level” 기능을 작성할 때는 Model 메소드를 사용하는 것이 좋습니다.

 

 

 

 

Initial QuerySet 수정하기

Manager의 기본 QuerySet은 해당 Model의 모든 데이터를 리턴하도록 작성되어 있습니다.

 

Manager의 get_queryset 메소드 override로 기본 QuerySet을 수정할 수 있습니다. 주의할 점은 extra Manager 메소드 와는 다르게 get_queryset 메소드는 반드시 QuerySet을 리턴해야 합니다.

 

 

이렇게 정의하면 User Model에서 두 개의 Manager를 사용할 수 있게 됩니다. objects 사용 시 데이터베이스의 모든 데이터를 리턴하고 active_objects 사용 시 active 필드가 True인 데이터만 리턴합니다.

 

 

active_objects를 사용하더라도 QuerySet을 리턴하므로 filter 나 exclude, count 등 모든 표준 QuerySet의 기능을 사용할 수 있습니다.

 

 

 


 

 

Default Manager의 역할과 선정 방식 이해하기

Manager를 재정의할 경우 해당 Model의 Manager name 순서가 생각보다 중요합니다. 첫 번째 정의되는 Manager가 Model의 default manager가 됩니다.

 

 

Django의 dumpdata 같은 기능에서 또는 Django Third Party 애플리케이션의 경우 Model의 Manager 재정의 여부를 알 수 없습니다. 위와 같이 Manager name을 알 수 없는 경우 Model._default_manager를 사용하게 됩니다.

 

 

또는 Model의 Meta.default_manager_name을 사용하여 manager name을 정의합니다.

 

 

default manager로 get_queryset() 메소드가 수정된 Manager를 사용하면 예기치 못한 상황이 발생합니다. 예를 들어 Django의 dumpdata는 모든 데이터를 백업하는 용도로 사용하지만 아래와 같은 상황에서는 필터링 된 데이터를 백업하는 의도하지 않은 결과로 이어집니다.

 

 

Manager의 get_queryset() 메소드 수정이 필요하다면 반드시 위와 같은 상황을 충분히 고려하여 작성해야 합니다. default manager가 결정되는 우선순위는 아래와 같습니다.

 

  1. Meta.default_manager_name 설정
  2. 부모(다중 상속 시 가장 왼쪽)의 Meta.default_manager_name 설정
  3. 해당 모델에 manager 이름이 정의된 순서 (가장 위)

 

 


 

 

Manager에서 커스텀 QuerySet을 사용하여 중복 코드 줄이기

ORM 중복 코드를 제거하려 Manager를 사용하다 보면 그 안에서 또 QuerySet 중복 코드가 발생하게 됩니다.

 

 

위의 경우 PersonManager와 ActivePersonManager의 authors 와 editors 중복 코드를 제거하기 위해 커스텀 QuerySet을 활용하는 방법이 있습니다.

 

 

QuerySet.as_manager를 통해 바로 Model의 Manager를 생성해서 할당하는 방법이 있습니다. Django의 설계 철학 중 하나인 DRY(Don’t Repeat Yourself)를 얼마나 신경 쓰는지 알 수 있는 부분 같습니다. 사용 시 주의할 점이 몇 가지 있습니다. 자세한 내용은 Django 문서를 참고 바랍니다.

 

 

더 나아가 커스텀 Manager와 커스텀 QuerySet을 모두 사용하고 싶다면 Manager.from_queryset()을 활용할 수 있습니다.

 

 

 

 

 

다수의 Model에서 공통의 Manager를 사용해야 하는 사례를 살펴보겠습니다.

 

아래와 같이 active 필드를 가지고 있는 세 개의 모델이 있다고 가정합니다.

 

 

active 필드를 필터를 위한 QuerySet 중복 코드를 해결하기 위해 위에서 학습한 get_queryset을 재정의 하는 커스텀 Manager를 작성하고 active_objects 이름으로 정의합니다.

 

 

위와 같이 작성 시 ActiveManager가 Default Manager가 되므로 의도치 않은 상황이 발생할 수 있음을 알려드렸습니다. 이런 상황에서는 기본 Manager를 objects 이름으로 정의해 주기로 합니다.

 

 

Model이 늘어날수록 Manager 정의 중복 코드를 피할 수 없게 되었습니다. 중복된 코드들을 상속을 활용하여 제거하기로 합니다.

 

 

이제 중복 코드가 사라지고 깔끔해졌습니다. 하지만 이후 Reply Model에 새로운 Manager가 추가된다면 Default manager 선정 우선순위에 의해 변경되는 이슈가 발생합니다.

 

 

위 상황을 방지하기 위해서는 우선순위가 더 높은 default_manager_name을 사용합니다.

 

 

최종적으로 위와 같이 정의되었지만 결국 Reply Model에서 직접 default_manager_name을 지정하게 되면 default manager가 변경되는 일이 발생합니다.

 

컨벤션을 위한 문서가 필요한 이유입니다.

 

 

 

일련의 과정을 지나 최종적으로 합의가 되면 문서를 작성합니다.

 

문서 정리를 통해 새로운 동료가 합류하게 되었을 때 보다 빠르게 적응할 수 있고 동료 간 커뮤니케이션 비용을 줄이고 본질적인 문제에 집중할 수 있게 됩니다.

 

지원자가 문서를 작성하여 리뷰를 통해 초안을 완성하고 이 문서에 대한 버전 관리를 통해 최신화를 유지해 나갈 수 있도록 합니다.

 

 

 

 

 

 

코드 리뷰를 통해 문서 만으로는 부족한 부분들을 동료들과 논의해나가면서 문제점을 개선합니다. 이 과정을 통해 부족했던 부분들이 문서에 추가되면서 점점 완성도 높은 컨벤션 문서를 사용할 수 있게 됩니다.

 

 

 

 

 

마치며

 

이슈 해결을 위해 최소한의 학습으로 빠르게 적용하는 것이 좋을 때도 있습니다. 하지만 개인적으로 팀의 성장 관점에서 바라볼 때 충분한 학습을 기반으로 공유하고 토론하는 과정을 통해 완성도 높고 균일한 퀄리티의 결과물을 만들어 나가는 것이 바람직하다고 생각합니다.

 

화해 개발팀은 이런 점에서 최고의 문화와 환경을 갖고 있습니다. 업무적 성과와 함께 개인과 팀의 성장을 이뤄갈 수 있는 문화를 기반으로 구성원 모두가 자신의 성장과 함께 동료의 성장에 대해 고민하고 지원합니다. 

 

이런 문화와 일하는 방식을 추구하는 화해 개발팀에서 현재 많은 포지션에 채용을 진행하고 있으니 화해팀과 함께 동반성장 해나가고 싶으신 분들의 많은 지원 바랍니다!

 

 

참고자료

 

https://docs.djangoproject.com/en/3.0/topics/db/managers/#creating-a-manager-with-queryset-methods

 

 

 

 

 

 

 

 

 

by nc nd