오히려 좋아..

상황이 나쁘게만 흘러가는 것 같을 때 외쳐보자.. .

궁금한 마음으로 포트폴리오 보기

Web Programming/Django

[Django] 장고 비즈니스 로직 어디둬야하는 고민하는 사람들 드루와..

junha6316 2022. 8. 23. 10:11

장고를 써온지도 어느 1년이 되었다. 장고는 굉장히 장단점이 명확한 프레임워크 였다.

스타트업들이 가장 빠르게 MVP를 만들어 볼 수 있지만 프로덕트 크기가 커짐에 따라 코드관리가 어려워지는게 문제다.

오늘은 코드관리가 어려운 장고에서 비즈니스 로직관리를 어떤식으로 할 수 있는지에 대해 적어보도록 하겠다. 

 

1. Model 너 누구야?

 먼저 본격적으로 비즈니스 로직을 분리하기 전에 장고에서 비즈니스 로직의 핵심이 되는 Model에 대해서 몇가지 이야기해야 한다. Model의 가장 대표적인 역할은 데이터베이스와 서버어플리케이션 사이의 인터페이스 역할이다. 가령 데이터베이스를 변경해야할 때 장고에서는 setttings에서 사용할 데이터베이스만 변경하면 된다. 즉 데이터베이스 변경으로 인한 변화가 어플리케이션 코드에 전파되지 않는다는 의미이다.(물론 특정 데이터베이스에서만 사용되는 기능을 구현해놓은 경우 이야기가 달라지겠지만..) 두번째 역할은 도메인 모델의 역할이다. 데이터베이스에서 Fetch해온 데이터는 model의 인스턴스 형태로 반환하는데 도메인에서 사용하는 비즈니스 로직을 인스턴스 메서드로 구현해 로직을 처리할 있다. 아래의 예와 같다.

class User:

    username = models.CharField(max_length=20)
    
    def pay(self, bill: Bill):
        # logic for paying bill
        return True
        
users = User.objects.all() 
bill = Bill.objects.first() # bill을 가져옴

for user in users:
    user.pay(bill)

이런 식으로 작성했을 때 장점은 가독성이 올라가는 것과 bill을 지불하는 로직을 한 곳에서 관리할 수 있다는 점이다.

정리해보면 Model은 아래와 같은 두가지 역할을 갖는다.

  1. 데이터베이스와 서버어플리케이션 사이의 인터페이스
  2. 비즈니스 로직을 처리하는 도메인 모델 역할

하지만 stratified structure를 가진 다른 프레임워크와 다르게 하나의 클래스가 너무 많은 역할을 지니고 있다. 이러한 문제를 장고는 파사드 패턴으로 해결했다. 바로 objects라는 키워드다. objects는 장고에서 쿼리를 추상화 하는 역할을 하는데 데이터 베이스와 관련된 로직들은 objects로 분리한다. 아래와 같은 예가 있다. 

User.objects.get()
User.objects.first()
User.objects.all()

추가로 Model Manager를 사용한다면 custom된 쿼리를 추가로 사용할 수 있다. 이러한 맥락에서 Model class에서 classmethod 데코레이터를 이용해 커스텀 쿼리를 구현하기 보다는 Manager를 이용하는 방식이 코드 관리라는 측면에서는 더 좋다고 생각한다.

class UserModelManager(models.Manager):


    def custom_query(self):
        # custom_query
        return
        
        
class User(models.Model):
    
   ...
   
   objects = UserModelManager()
   

User.object.custom_query()

 

2. 새로운 문제

하지만 현실은 항상 기존 시스템이 다루지 못하는 문제를 가져온다. 도메인 모델이 수행하는 로직, 쿼리가 아닌 로직은 어디에 두어야 하는가? 물론 이것도 회바회, 팀바팀이겠지만 보통은 app 단위에 helper.py 를 두어 로직을 두고 필요한 곳에서 import해서 써야한다. 물론 현재 내가 다니는 회사도 역시 동일한 컨벤션을 사용하고 있지만 사용하면서 문제가 많다는 생각이 들었다. 예를 들면 특정 모델에만 사용되는 로직이 app이라는 너무 넓은 범위의 helper에 존재한다는 점이였다. 가령 account에 User라는 모델이 있다고 하고 User에 만 사용되는 로직이 있다고 가정해보자. 그럼 User를 위한 로직을 찾기 위해 개발자는 존재할지도 모르는 해당 로직 helper함수를 찾아 이리저리 떠돌아야할 것이다. 그리고 이런식으로 작성했을 때 하나의 app에 많은 모델이 존재한다면 helper는 금방 쓰레기 통이 되고 말 것이라고 생각했다.

 

3. Services

이러한 문제를 해결하기 위해 아래와 같은 방법을 제안하고 싶다. objects가 Model의 데이터베이스와의 상호 작호작용을 담당한 것처럼 services라는 Model에 두는 것이다. 가령 쿠폰을 발급 받으려고 하는데 유저가 특정 물건을 외부 API를 통해서 확인해야하는 경우라고 가정해보자. 그렇다면 purchase의 helper보다는 아래처럼 작성하는게 코드관리 측면에서 좋다고 생각한다.

class CouponService:

    def check_buy_something(user:User):
        # api call
        return 

class Coupon(models.Model):

    objcets = CouponModelManager()
    services = CouponService()
    
    
 Coupon.services.check_buy_something(user)

 

4. 결론

장고에서 비즈니스 로직 관리는 중요한 문제중 하나이다. 추가로 도메인 로직을 관리하는 더 좋은 점이 있다면 알려주길 바란다. 

끝까지 읽어주셔서 감사합니다!