장고에서 비즈니스 로직 및 데이터 액세스 분리


484

Django에서 프로젝트를 작성 중이며 코드의 80 %가 파일에 있음을 알 수 있습니다 models.py. 이 코드는 혼란스럽고 일정 시간이 지나면 실제로 무슨 일이 일어나고 있는지 이해하지 못합니다.

여기에 나를 귀찮게하는 것이 있습니다 :

  1. 내 모델 수준 (데이터베이스의 데이터 작업에만 책임을 져야 함)이 전자 메일을 보내고 API를 다른 서비스로 보냄 등도 추악합니다.
  2. 또한 비즈니스 논리를보기에 배치하는 것은 용납 할 수 없다는 것을 알았습니다. 이렇게하면 제어하기가 어렵 기 때문입니다. 예를 들어, 내 응용 프로그램에는 새 인스턴스를 만드는 세 가지 방법이 User있지만 기술적으로는 인스턴스를 균일하게 만들어야합니다.
  3. 모델의 방법과 속성이 결정적이지 않고 부작용이 생길 때 항상주의하지는 않습니다.

다음은 간단한 예입니다. 처음에 User모델은 다음과 같습니다.

class User(db.Models):

    def get_present_name(self):
        return self.name or 'Anonymous'

    def activate(self):
        self.status = 'activated'
        self.save()

시간이 지남에 따라 다음과 같이 바뀌 었습니다.

class User(db.Models):

    def get_present_name(self): 
        # property became non-deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

    def activate(self):
        # method now has a side effect (send message to user)
        self.status = 'activated'
        self.save()
        send_mail('Your account is activated!', '…', [self.email])

내가 원하는 것은 코드에서 엔티티를 분리하는 것입니다.

  1. 내 데이터베이스의 엔티티, 데이터베이스 레벨 : 내 애플리케이션에는 무엇이 포함됩니까?
  2. 애플리케이션, 비즈니스 로직 레벨의 엔티티 : 애플리케이션을 만들 수있는 요소는 무엇입니까?

Django에 적용 할 수있는 이러한 접근 방식을 구현하는 좋은 방법은 무엇입니까?


14
신호에 대한 정보
Konstant

1
태그를 제거했지만 DCI를 사용하여 시스템의 기능 (기능)과 시스템의 데이터 (데이터 / 도메인 모델)를 분리 할 수 ​​있습니다.
Rune FS

2
신호 콜백에서 모든 비즈니스 로직을 구현할 것을 제안하십니까? 불행히도, 모든 내 응용 프로그램이 데이터베이스의 이벤트에 연결될 수있는 것은 아닙니다.
defuz

룬 FS, 나는 DCI를 사용하려고 시도했지만 내 프로젝트에는별로 필요하지 않은 것처럼 보였습니다. 컨텍스트, 객체에 대한 믹스 인으로서의 역할 정의 등. "? 최소한의 예를 들어 주시겠습니까?
defuz

답변:


634

데이터 모델도메인 모델 의 차이점에 대해 문의하는 것 같습니다 . 후자는 최종 사용자가 인식 한 비즈니스 로직과 엔터티를 찾을 수있는 곳이고, 전자는 실제로 데이터를 저장하는 곳입니다.

또한 귀하의 질문의 세 번째 부분을 다음과 같이 해석했습니다.

이것들은 매우 다른 두 가지 개념이며 항상 분리하기가 어렵습니다. 그러나이 목적으로 사용할 수있는 몇 가지 일반적인 패턴과 도구가 있습니다.

도메인 모델 정보

가장 먼저 인식해야 할 것은 도메인 모델이 실제로 데이터에 관한 것이 아니라는 것입니다. "이 사용자 활성화", "이 사용자 비활성화", "현재 활성화 된 사용자"및 "이 사용자의 이름은 무엇입니까?"와 같은 작업질문 에 관한 것입니다. 고전적인 용어 : 쿼리명령 에 관한 것 입니다.

명령에 대한 생각

예를 들어 "이 사용자 활성화"및 "이 사용자 비활성화"명령을 살펴 보겠습니다. 명령에 대한 좋은 점은 작은 시나리오로 쉽게 표현할 수 있다는 것입니다.

지정된 비활성 사용자 관리자는이 사용자가 활성화 한 후 , 사용자가 활성화되고 그리고 확인 이메일이 사용자에게 전송되고 엔트리 시스템 로그에 추가되고 (등등)




이러한 시나리오는 데이터베이스 (일부 '활성'플래그), 메일 서버, 시스템 로그 등 단일 명령으로 인프라의 여러 부분이 어떤 영향을 받는지 확인하는 데 유용합니다.

이러한 시나리오는 또한 테스트 주도 개발 환경을 설정하는 데 도움이됩니다.

마지막으로, 명령을 생각하면 실제로 작업 지향 응용 프로그램을 만드는 데 도움이됩니다. 귀하의 사용자는 이것을 높이 평가할 것입니다 :-)

표현 명령

Django는 명령을 표현하는 두 가지 쉬운 방법을 제공합니다. 둘 다 유효한 옵션이며 두 가지 접근 방식을 혼합하는 것은 드문 일이 아닙니다.

서비스 계층

서비스 모듈은 이미되었습니다 @Hedde 설명 . 여기서 별도의 모듈을 정의하고 각 명령은 함수로 표시됩니다.

services.py

def activate_user(user_id):
    user = User.objects.get(pk=user_id)

    # set active flag
    user.active = True
    user.save()

    # mail user
    send_mail(...)

    # etc etc

양식 사용

다른 방법은 각 명령에 장고 양식을 사용하는 것입니다. 이 접근법은 밀접하게 관련된 여러 측면을 결합하기 때문에 선호합니다.

  • 명령 실행 (어떻게 수행합니까?)
  • 명령 매개 변수의 유효성 검사 (이 작업을 수행 할 수 있습니까?)
  • 명령 발표 (어떻게 할 수 있습니까?)

forms.py

class ActivateUserForm(forms.Form):

    user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
    # the username select widget is not a standard Django widget, I just made it up

    def clean_user_id(self):
        user_id = self.cleaned_data['user_id']
        if User.objects.get(pk=user_id).active:
            raise ValidationError("This user cannot be activated")
        # you can also check authorizations etc. 
        return user_id

    def execute(self):
        """
        This is not a standard method in the forms API; it is intended to replace the 
        'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern. 
        """
        user_id = self.cleaned_data['user_id']

        user = User.objects.get(pk=user_id)

        # set active flag
        user.active = True
        user.save()

        # mail user
        send_mail(...)

        # etc etc

쿼리로 생각하기

귀하의 예에는 쿼리가 포함되어 있지 않으므로 유용한 쿼리를 자유롭게 만들었습니다. "질문"이라는 용어를 선호하지만 쿼리는 고전적인 용어입니다. 흥미로운 질문은 "이 사용자의 이름은 무엇입니까?", "이 사용자는 로그인 할 수 있습니까?", "비활성화 된 사용자 목록 표시"및 "비활성화 된 사용자의 지리적 분포는 무엇입니까?"입니다.

이러한 쿼리에 응답하기 전에 항상 두 가지 질문을해야합니다. 템플릿에 대한 프레젠테이션 쿼리 및 / 또는 명령 실행 과 관련된 비즈니스 논리 쿼리 및 / 또는 보고 쿼리입니다.

프리젠 테이션 쿼리는 사용자 인터페이스를 개선하기 위해 만들어집니다. 비즈니스 로직 쿼리에 대한 답변은 명령 실행에 직접적인 영향을줍니다. 보고 쿼리는 단지 분석 목적을위한 것이며 시간 제약이 느립니다. 이 범주는 상호 배타적이지 않습니다.

다른 질문은 "답을 완전히 통제 할 수 있는가?"입니다. 예를 들어, 사용자 이름 (이 문맥에서)을 쿼리 할 때 외부 API에 의존하기 때문에 결과를 제어 할 수 없습니다.

쿼리하기

Django에서 가장 기본적인 쿼리는 Manager 객체를 사용하는 것입니다.

User.objects.filter(active=True)

물론 데이터가 실제로 데이터 모델에 표시되는 경우에만 작동합니다. 항상 그런 것은 아닙니다. 이 경우 아래 옵션을 고려할 수 있습니다.

맞춤 태그 및 필터

첫 번째 대안은 사용자 지정 태그 및 템플릿 필터와 같이 단순한 프레젠테이션 쿼리에 유용합니다.

template.html

<h1>Welcome, {{ user|friendly_name }}</h1>

template_tags.py

@register.filter
def friendly_name(user):
    return remote_api.get_cached_name(user.id)

쿼리 방법

쿼리가 단순히 프레젠테이션 용이 아닌 경우 services.py 에 쿼리를 추가하거나 (사용중인 경우) query.py 모듈을 도입 할 수 있습니다 .

query.py

def inactive_users():
    return User.objects.filter(active=False)


def users_called_publysher():
    for user in User.objects.all():
        if remote_api.get_cached_name(user.id) == "publysher":
            yield user 

프록시 모델

프록시 모델은 비즈니스 로직 및보고와 관련하여 매우 유용합니다. 기본적으로 모델의 향상된 서브 세트를 정의합니다. Manager.get_queryset()메소드를 대체하여 Manager의 기본 QuerySet을 대체 할 수 있습니다 .

models.py

class InactiveUserManager(models.Manager):
    def get_queryset(self):
        query_set = super(InactiveUserManager, self).get_queryset()
        return query_set.filter(active=False)

class InactiveUser(User):
    """
    >>> for user in InactiveUser.objects.all():
    …        assert user.active is False 
    """

    objects = InactiveUserManager()
    class Meta:
        proxy = True

쿼리 모델

본질적으로 복잡하지만 자주 실행되는 쿼리의 경우 쿼리 모델이있을 수 있습니다. 쿼리 모델은 단일 쿼리에 대한 관련 데이터가 별도의 모델에 저장되는 비정규 화 형식입니다. 물론 비정규 화 된 모델을 기본 모델과 동기화하는 것이 중요합니다. 쿼리 모델은 변경 사항이 전적으로 귀하의 통제하에있는 경우에만 사용할 수 있습니다.

models.py

class InactiveUserDistribution(models.Model):
    country = CharField(max_length=200)
    inactive_user_count = IntegerField(default=0)

첫 번째 옵션은 명령에서 이러한 모델을 업데이트하는 것입니다. 이 모델이 하나 또는 두 개의 명령으로 만 변경되는 경우 매우 유용합니다.

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

더 나은 옵션은 맞춤형 신호를 사용하는 것입니다. 이 신호는 물론 명령에 의해 방출됩니다. 신호는 여러 쿼리 모델을 원래 모델과 동기화 할 수 있다는 이점이 있습니다. 또한 Celery 또는 유사한 프레임 워크를 사용하여 백그라운드 처리로 신호 처리를 오프로드 할 수 있습니다.

signal.py

user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])

forms.py

class ActivateUserForm(forms.Form):
    # see above

    def execute(self):
        # see above
        user_activated.send_robust(sender=self, user=user)

models.py

class InactiveUserDistribution(models.Model):
    # see above

@receiver(user_activated)
def on_user_activated(sender, **kwargs):
        user = kwargs['user']
        query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
        query_model.inactive_user_count -= 1
        query_model.save()

깨끗하게 유지

이 접근 방식을 사용하면 코드가 깨끗하게 유지되는지 쉽게 판단 할 수 없게됩니다. 다음 지침을 따르십시오.

  • 내 모델에 데이터베이스 상태 관리보다 더 많은 방법이 있습니까? 명령을 추출해야합니다.
  • 내 모델에 데이터베이스 필드에 매핑되지 않는 속성이 포함되어 있습니까? 쿼리를 추출해야합니다.
  • 내 모델이 데이터베이스가 아닌 인프라 (예 : 메일)를 참조합니까? 명령을 추출해야합니다.

뷰는 종종 같은 문제를 겪기 때문에 뷰도 마찬가지입니다.

  • 뷰가 데이터베이스 모델을 능동적으로 관리합니까? 명령을 추출해야합니다.

일부 참고 문헌

Django 설명서 : 프록시 모델

장고 문서 : 신호

아키텍처 : 도메인 기반 디자인


11
DDD를 장고 관련 질문에 통합 한 답변을 보는 것이 좋습니다. Django가 지속성을 위해 ActiveRecord를 사용한다고해서 우려가 분리되는 것은 아닙니다. 좋은 대답입니다.
Scott Coates

6
약탈 된 사용자가 해당 객체를 삭제하기 전에 객체의 소유자인지 확인하려면 뷰 또는 양식 / 서비스 모듈에서 확인해야합니까?
Ivan

6
@Ivan : 둘 다. 그것은 해야한다 그것은 당신의 비즈니스 제약의 일부이기 때문에 양식 / 서비스 모듈합니다. 그것은 해야 만 현재의 행동이 사용자가 실제로 실행할 수있는 것이 있기 때문에보기에합니다.
출판사

4
사용자 정의 관리자 메소드 는 다음과 같은 쿼리를 구현하는 좋은 방법 User.objects.inactive_users()입니다. :하지만 프록시 모델 예는 여기 IMO 잘못된 의미로 연결 u = InactiveUser.objects.all()[0]; u.active = True; u.save()아직하고 isinstance(u, InactiveUser) == True. 또한 많은 경우 쿼리 모델을 유지 관리하는 효과적인 방법은 db보기를 사용하는 것입니다.
Aryeh Leib Taurog

1
@adnanmuttaleb 맞습니다. 답변 자체는 "도메인 모델"이라는 용어 만 사용합니다. 제 답변은 DDD가 아니기 때문에 DDD에 대한 링크를 포함 시켰습니다. 그러나이 책은 도메인 모델에 대해 생각하는 데 큰 도움이됩니다.
출판사

148

일반적으로 뷰와 모델 사이에 서비스 계층을 구현합니다. 이것은 프로젝트의 API처럼 작동하며 진행 상황에 대한 헬리콥터를 잘 보여줍니다. 이 계층화 기법을 Java 프로젝트 (JSF)와 함께 많이 사용하는 동료로부터이 관행을 물려 받았습니다. 예 :

models.py

class Book:
   author = models.ForeignKey(User)
   title = models.CharField(max_length=125)

   class Meta:
       app_label = "library"

services.py

from library.models import Book

def get_books(limit=None, **filters):
    """ simple service function for retrieving books can be widely extended """
    return Book.objects.filter(**filters)[:limit]  # list[:None] will return the entire list

views.py

from library.services import get_books

class BookListView(ListView):
    """ simple view, e.g. implement a _build and _apply filters function """
    queryset = get_books()

나는 보통 모델, 뷰 및 서비스를 모듈 수준으로 가져오고 프로젝트의 크기에 따라 더 멀리 분리합니다.


8
나는 일반적인 접근 방식을 좋아하지만 특정 예제는 일반적으로 관리자 로 구현됩니다 .
arie

9
웹숍 서비스의 경우 @arie가 반드시 더 좋은 예일 필요는 없습니다. 장바구니 세션 생성, 제품 평가 계산과 같은 비동기 작업, 이메일 생성 및 전송 등
Hedde van der Heide

4
나는이 접근법도 좋아하고 자바에서 나온다. 저는 파이썬을 처음 사용합니다. views.py를 어떻게 테스트 하시겠습니까? 예를 들어, 서비스에서 원격 API 호출을하는 경우 서비스 계층을 어떻게 조롱 하시겠습니까?
Teimuraz

71

우선, 자신을 반복하지 마십시오 .

그렇다면 과도하게 엔지니어링하지 않도록주의하십시오. 때로는 시간 낭비 일 뿐이므로 중요한 것에 집중하지 못하게됩니다. 때때로 zen의 파이썬 을 검토하십시오 .

활발한 프로젝트 살펴보기

  • 더 많은 사람들 = 더 잘 조직 할 필요
  • 장고는 리포지토리 가 간단한 구조를 가지고있다.
  • 핍 저장소 그들은 straigtforward 디렉토리 구조를 가지고있다.
  • 패브릭 저장소 도 볼 수있는 좋은 하나입니다.

    • 당신은 모든 모델을 아래에 배치 할 수 있습니다 yourapp/models/logicalgroup.py
  • 예를 들어 User, Group관련 모델에 따라 갈 수 있습니다yourapp/models/users.py
  • 예를 들어 Poll, Question, Answer...에서 갈 수있다yourapp/models/polls.py
  • __all__내부에 필요한 것을로드yourapp/models/__init__.py

MVC에 대한 추가 정보

  • 모델은 당신의 데이터입니다
    • 여기에는 실제 데이터가 포함됩니다
    • 여기에는 세션 / 쿠키 / 캐시 / fs / 색인 데이터도 포함됩니다
  • 사용자가 컨트롤러와 상호 작용하여 모델 조작
    • API 또는 데이터를 저장 / 업데이트하는 뷰일 수 있습니다.
    • 이것은 request.GET/ request.POST... etc 로 조정할 수 있습니다
    • 페이징 또는 필터링 도 생각하십시오 .
  • 데이터가 뷰를 업데이트합니다
    • 템플릿은 데이터를 가져 와서 적절히 포맷합니다
    • 템플릿이없는 API도 뷰의 일부입니다. 예 tastypie또는piston
    • 미들웨어도 고려해야합니다.

미들웨어 / 템플리트 태그 활용

  • 각 요청에 대해 수행해야 할 작업이 필요한 경우 미들웨어가 한 가지 방법입니다.
    • 예. 타임 스탬프 추가
    • 예 : 페이지 조회수에 대한 측정 항목 업데이트
    • 예 : 캐시 채우기
  • 개체의 서식을 지정하기 위해 항상 반복되는 코드 스 니펫이 있으면 템플릿 태그가 좋습니다.
    • 예 : 활성 탭 / URL 빵 부스러기

모델 관리자 활용

  • 창조 User는 안으로 갈 수 있습니다 UserManager(models.Manager).
  • 인스턴스에 대한 gory 세부 사항은에서 이동해야합니다 models.Model.
  • 에 대한 gory 세부 사항 queryset은에 갈 수 models.Manager있습니다.
  • User번에 하나씩 생성 할 수 있으므로 모델 자체에 있어야한다고 생각할 수도 있지만 객체를 생성 할 때 세부 정보가 모두없는 것 같습니다.

예:

class UserManager(models.Manager):
   def create_user(self, username, ...):
      # plain create
   def create_superuser(self, username, ...):
      # may set is_superuser field.
   def activate(self, username):
      # may use save() and send_mail()
   def activate_in_bulk(self, queryset):
      # may use queryset.update() instead of save()
      # may use send_mass_mail() instead of send_mail()

가능하면 양식을 활용하십시오.

모델에 매핑되는 양식이 있으면 많은 상용구 코드를 제거 할 수 있습니다. 은 ModelForm documentation꽤 좋다. 사용자 정의가 많은 경우 모델 코드에서 양식 코드를 분리하는 것이 좋습니다 (또는 고급 사용을 위해 주기적 가져 오기 오류를 피할 수 있음).

사용 관리 명령 가능한 경우

  • 예 : yourapp/management/commands/createsuperuser.py
  • 예 : yourapp/management/commands/activateinbulk.py

비즈니스 로직이 있다면 분리 할 수 ​​있습니다

  • django.contrib.auth db에 백엔드가있는 것처럼 backends를 사용합니다 ...
  • a를 setting(예를 들어, 비즈니스 로직 AUTHENTICATION_BACKENDS)
  • 당신은 사용할 수 있습니다 django.contrib.auth.backends.RemoteUserBackend
  • 당신은 사용할 수 있습니다 yourapp.backends.remote_api.RemoteUserBackend
  • 당신은 사용할 수 있습니다 yourapp.backends.memcached.RemoteUserBackend
  • 어려운 비즈니스 로직을 백엔드에 위임
  • 입 / 출력에 대한 기대치를 올바르게 설정하십시오.
  • 비즈니스 로직 변경은 설정을 변경하는 것만 큼 간단합니다. :)

백엔드 예 :

class User(db.Models):
    def get_present_name(self): 
        # property became not deterministic in terms of database
        # data is taken from another service by api
        return remote_api.request_user_name(self.uid) or 'Anonymous' 

될 수 있습니다 :

class User(db.Models):
   def get_present_name(self):
      for backend in get_backends():
         try:
            return backend.get_present_name(self)
         except: # make pylint happy.
            pass
      return None

디자인 패턴에 대한 추가 정보

인터페이스 경계에 대한 추가 정보

  • 사용하려는 코드가 실제로 모델의 일부입니까? ->yourapp.models
  • 코드가 비즈니스 로직의 일부입니까? ->yourapp.vendor
  • 코드는 일반 도구 / 라이브러리의 일부입니까? ->yourapp.libs
  • 코드가 비즈니스 로직 라이브러리의 일부입니까? -> yourapp.libs.vendor또는yourapp.vendor.libs
  • 좋은 방법은 다음과 같습니다. 코드를 독립적으로 테스트 할 수 있습니까?
  • 분리가 논리적입니까?
    • 예, 좋습니다 :)
    • 아닙니다. 논리적 개념을 개별적으로 테스트하는 데 문제가있을 수 있습니다.
  • 10 배 더 많은 코드를 얻을 때 리팩터링해야한다고 생각하십니까?
    • 예, 좋지 않습니다, 부에노, 리 팩터가 많은 작업이 될 수 있습니다.
    • 아뇨, 정말 대단합니다!

요컨대, 당신은 할 수 있었다

  • yourapp/core/backends.py
  • yourapp/core/models/__init__.py
  • yourapp/core/models/users.py
  • yourapp/core/models/questions.py
  • yourapp/core/backends.py
  • yourapp/core/forms.py
  • yourapp/core/handlers.py
  • yourapp/core/management/commands/__init__.py
  • yourapp/core/management/commands/closepolls.py
  • yourapp/core/management/commands/removeduplicates.py
  • yourapp/core/middleware.py
  • yourapp/core/signals.py
  • yourapp/core/templatetags/__init__.py
  • yourapp/core/templatetags/polls_extras.py
  • yourapp/core/views/__init__.py
  • yourapp/core/views/users.py
  • yourapp/core/views/questions.py
  • yourapp/core/signals.py
  • yourapp/lib/utils.py
  • yourapp/lib/textanalysis.py
  • yourapp/lib/ratings.py
  • yourapp/vendor/backends.py
  • yourapp/vendor/morebusinesslogic.py
  • yourapp/vendor/handlers.py
  • yourapp/vendor/middleware.py
  • yourapp/vendor/signals.py
  • yourapp/tests/test_polls.py
  • yourapp/tests/test_questions.py
  • yourapp/tests/test_duplicates.py
  • yourapp/tests/test_ratings.py

또는 당신을 돕는 다른 것; 필요한 인터페이스경계 찾는 것이 도움이 될 것입니다.


27

Django는 약간 수정 된 MVC를 사용합니다. 장고에는 "컨트롤러"라는 개념이 없습니다. 가장 가까운 프록시는 "뷰"이며, MVC에서 뷰는 장고의 "템플릿"과 비슷하기 때문에 MVC 변환과 혼동되는 경향이 있습니다.

장고에서 "모델"은 단순한 데이터베이스 추상화가 아닙니다. 어떤면에서는 MVC의 컨트롤러로서 Django의 "보기"와 의무를 공유합니다. 인스턴스와 관련된 전체 동작을 보유합니다. 해당 인스턴스가 동작의 일부로 외부 API와 상호 작용해야하는 경우 여전히 모델 코드입니다. 실제로 모델은 데이터베이스와 전혀 상호 작용할 필요가 없으므로 외부 API에 대한 대화 형 계층으로 존재하는 모델을 가질 수 있습니다. "모델"의 훨씬 더 자유로운 개념입니다.


7

Django에서 MVC 구조는 Chris Pratt가 말한 것처럼 다른 프레임 워크에서 사용되는 고전적인 MVC 모델과는 다른데,이를 수행하는 주된 이유는 CakePHP와 같은 다른 MVC 프레임 워크에서와 같이 너무 엄격한 응용 프로그램 구조를 피하는 것입니다.

Django에서 MVC는 다음과 같은 방식으로 구현되었습니다.

뷰 레이어는 두 가지로 나뉩니다. 뷰는 HTTP 요청을 관리하는 데만 사용해야하며 호출되어 응답합니다. 뷰는 나머지 응용 프로그램 (폼, 모델 폼, 사용자 정의 클래스, 간단한 경우 모델과 직접 통신)과 통신합니다. 인터페이스를 만들려면 템플릿을 사용합니다. 템플릿은 Django와 문자열 모양이며 컨텍스트를 템플릿에 매핑하며이 ​​컨텍스트는 응용 프로그램에 의해 뷰에 전달되었습니다 (뷰가 요청할 때).

모델 계층은 캡슐화, 추상화, 유효성 검사, 인텔리전스를 제공하고 데이터를 객체 지향으로 만듭니다 (언젠가 DBMS도 마찬가지라고 함). 이것은 거대한 models.py 파일을 만들어야한다는 것을 의미하지는 않습니다 (실제로 매우 좋은 조언은 모델을 다른 파일로 분할하고 'models'라는 폴더에 넣고 '__init__.py'파일을 여기에 넣는 것입니다) 모든 모델을 가져와 최종적으로 models.Model 클래스의 'app_label'속성을 사용하는 폴더). 모델을 사용하면 데이터를 사용하지 않아도되므로 응용 프로그램이 더 간단 해집니다. 필요한 경우 모델의 "도구"와 같은 외부 클래스를 작성해야합니다. 또한 모델의 Heritage를 사용하여 모델의 메타 클래스의 'abstract'속성을 'True'로 설정할 수도 있습니다.

나머지는 어디에 있습니까? 작은 웹 응용 프로그램은 일반적으로 데이터에 대한 일종의 인터페이스이며, 일부 작은 프로그램의 경우 데이터를 쿼리하거나 삽입하기 위해 뷰를 사용하는 것으로 충분합니다. 보다 일반적인 경우에는 실제로 "제어기"인 Forms 또는 ModelForms를 사용합니다. 이것은 일반적인 문제에 대한 실용적인 해결책이 아니라 매우 빠른 문제입니다. 웹 사이트가하는 일입니다.

Forms가 당신에게 적합하지 않다면, 마법을 수행하기 위해 자신의 클래스를 만들어야합니다. 이것의 좋은 예는 관리자 응용 프로그램입니다. ModelAmin 코드를 읽을 수 있습니다. 실제로 컨트롤러로 작동합니다. 표준 구조가 없으므로 기존 Django 앱을 검사하는 것이 좋습니다. 각 사례에 따라 다릅니다. 이것은 Django 개발자가 의도 한 것입니다 .xml 파서 클래스, API 커넥터 클래스, 작업 수행을위한 셀러리 추가, 리액터 기반 응용 프로그램에 대한 트위스트, ORM 만 사용, 웹 서비스 만들기, 관리 응용 프로그램 수정 등을 수행 할 수 있습니다. .. 좋은 품질의 코드를 만들고 MVC 철학을 존중하는지 여부를 모듈 기반으로 만들고 자신 만의 추상화 계층을 만드는 것은 귀하의 책임입니다. 매우 유연합니다.

내 충고 : 가능한 한 많은 코드를 읽고, 많은 장고 응용 프로그램이 있지만 그렇게 심각하게 받아들이지 마십시오. 각각의 경우는 패턴과 이론이 다르지만 항상 그런 것은 아니지만, 이것은 부정확 한 경험입니다 .django는 관리자 인터페이스, 웹 양식 유효성 검사, i18n, 관찰자 ​​패턴 구현과 같은 일부 고통을 완화하는 데 사용할 수있는 좋은 도구를 제공합니다. 앞서 언급 한 것들과 다른 것들), 그러나 좋은 디자인은 숙련 된 디자이너들로부터 나온다.

추신 : 인증 응용 프로그램 (표준 장고의)에서 '사용자'클래스를 사용하십시오. 예를 들어 사용자 프로파일을 만들거나 최소한 코드를 읽을 수 있습니다.이 경우 유용합니다.


1

오래된 질문이지만 어쨌든 내 솔루션을 제공하고 싶습니다. 모델 객체도 모델을 추가하는 것이 어려우 면서 일부 추가 기능이 필요하다는 수용에 기반합니다 .py . 무거운 비즈니스 논리는 개인적인 취향에 따라 개별적으로 작성 될 수 있지만, 나는 적어도 그 자체와 관련된 모든 것을하는 모델을 좋아합니다. 이 솔루션은 또한 모든 로직을 모델 내에 배치하려는 사람들을 지원합니다.

따라서 저는 모델 정의에서 논리를 분리하고 여전히 IDE에서 모든 힌트를 얻을 수 있는 해킹 을 고안 했습니다 .

장점은 분명해야하지만 이것은 내가 관찰 한 몇 가지를 나열합니다.

  • DB 정의는 그대로 유지됩니다. 로직 "쓰레기"가 첨부되지 않았습니다.
  • 모델 관련 논리는 모두 한 곳에 깔끔하게 배치됩니다
  • 모든 서비스 (양식, REST,보기)에는 논리에 대한 단일 액세스 포인트가 있습니다
  • 무엇보다도 : 나는 models.py 가 너무 복잡해져 로직을 분리해야 한다는 것을 깨달았을 때 코드를 다시 작성할 필요 가 없었습니다. 분리는 부드럽고 반복적입니다. 한 번에 또는 전체 수업 또는 전체 모델에서 기능을 수행 할 수 있습니다 .py.

파이썬 3.4 이상 및 장고 1.8 이상에서 이것을 사용했습니다.

app / models.py

....
from app.logic.user import UserLogic

class User(models.Model, UserLogic):
    field1 = models.AnyField(....)
    ... field definitions ...

app / logic / user.py

if False:
    # This allows the IDE to know about the User model and its member fields
    from main.models import User

class UserLogic(object):
    def logic_function(self: 'User'):
        ... code with hinting working normally ...

내가 알 수없는 유일한 것은 내 IDE (이 경우 PyCharm)가 UserLogic이 실제로 사용자 모델임을 인식하게하는 방법입니다. 그러나 이것은 분명히 해킹이므로 항상 self매개 변수 유형을 지정하는 약간의 성가신 것을 기뻐합니다 .


실제로 나는 그것을 사용하기 쉬운 접근 방식으로 본다. 그러나 최종 모델을 다른 파일로 옮기고 models.py에서 상속하지 않습니다. service.py는 userlogic + model과 충돌
Maks

1

당신의 의견에 동의해야합니다. 장고에는 많은 가능성이 있지만 장고의 디자인 철학을 검토하는 것이 가장 좋습니다 .

  1. 모델 속성에서 API를 호출하는 것은 이상적이지 않습니다.보기에서 이와 같은 작업을 수행하고 서비스 계층을 만들어 건조를 유지하는 것이 더 합리적 인 것처럼 보입니다. API 호출이 비 블로킹이고 호출이 비싸면 요청을 서비스 워커 (큐에서 소비하는 워커)에게 보내는 것이 합리적 일 수 있습니다.

  2. Django의 디자인 철학 모델에 따르면 "객체"의 모든 측면을 캡슐화합니다. 따라서 해당 객체와 관련된 모든 비즈니스 로직이 있어야합니다.

모든 관련 도메인 로직 포함

모델은 Martin Fowler의 Active Record 디자인 패턴에 따라“객체”의 모든 측면을 캡슐화해야합니다.

  1. 당신이 묘사하는 부작용은 명백합니다. 여기서의 논리는 쿼리 셋과 매니저로 나눌 수 있습니다. 예를 들면 다음과 같습니다.

    models.py

    import datetime
    
    from djongo import models
    from django.db.models.query import QuerySet
    from django.contrib import admin
    from django.db import transaction
    
    
    class MyUser(models.Model):
    
        present_name = models.TextField(null=False, blank=True)
        status = models.TextField(null=False, blank=True)
        last_active = models.DateTimeField(auto_now=True, editable=False)
    
        # As mentioned you could put this in a template tag to pull it
        # from cache there. Depending on how it is used, it could be
        # retrieved from within the admin view or from a custom view
        # if that is the only place you will use it.
        #def get_present_name(self):
        #    # property became non-deterministic in terms of database
        #    # data is taken from another service by api
        #    return remote_api.request_user_name(self.uid) or 'Anonymous'
    
        # Moved to admin as an action
        # def activate(self):
        #     # method now has a side effect (send message to user)
        #     self.status = 'activated'
        #     self.save()
        #     # send email via email service
        #     #send_mail('Your account is activated!', '…', [self.email])
    
        class Meta:
            ordering = ['-id']  # Needed for DRF pagination
    
        def __unicode__(self):
            return '{}'.format(self.pk)
    
    
    class MyUserRegistrationQuerySet(QuerySet):
    
        def for_inactive_users(self):
            new_date = datetime.datetime.now() - datetime.timedelta(days=3*365)  # 3 Years ago
            return self.filter(last_active__lte=new_date.year)
    
        def by_user_id(self, user_ids):
            return self.filter(id__in=user_ids)
    
    
    class MyUserRegistrationManager(models.Manager):
    
        def get_query_set(self):
            return MyUserRegistrationQuerySet(self.model, using=self._db)
    
        def with_no_activity(self):
            return self.get_query_set().for_inactive_users()

    admin.py

    # Then in model admin
    
    class MyUserRegistrationAdmin(admin.ModelAdmin):
        actions = (
            'send_welcome_emails',
        )
    
        def send_activate_emails(self, request, queryset):
            rows_affected = 0
            for obj in queryset:
                with transaction.commit_on_success():
                    # send_email('welcome_email', request, obj) # send email via email service
                    obj.status = 'activated'
                    obj.save()
                    rows_affected += 1
    
            self.message_user(request, 'sent %d' % rows_affected)
    
    admin.site.register(MyUser, MyUserRegistrationAdmin)

0

나는 대부분 선택한 답변 ( https://stackoverflow.com/a/12857584/871392 )에 동의 하지만 쿼리 만들기 섹션에서 옵션을 추가하고 싶습니다.

필터 필터 만들기 등의 모델에 대한 QuerySet 클래스를 정의 할 수 있습니다. 그런 다음 내장 관리자 및 QuerySet 클래스와 같이 모델 관리자에 대해이 쿼리 세트 클래스를 프록시 할 수 있습니다.

하나의 도메인 모델을 얻기 위해 여러 데이터 모델을 쿼리해야한다면 이전에 제안한대로 별도의 모듈에 넣는 것이 더 합리적입니다.



-6

Django는 웹 페이지를 쉽게 전달할 수 있도록 설계되었습니다. 이것에 익숙하지 않으면 다른 솔루션을 사용해야합니다.

모델의 루트 또는 공통 작업 (동일한 인터페이스를 갖기 위해)과 모델의 컨트롤러에서 다른 작업을 작성하고 있습니다. 다른 모델에서 작업이 필요한 경우 해당 컨트롤러를 가져옵니다.

이 접근법은 저에게 충분하며 응용 프로그램의 복잡성에 충분합니다.

Hedde의 답변은 django와 python 자체의 유연성을 보여주는 예입니다.

어쨌든 매우 흥미로운 질문!


9
질문에 대한 나의 이해에 도움이된다고 말하는 것이 어떻습니까?
Chris Wesseling

1
Django는 django.db.models 외에도 많은 것을 제공하지만, 대부분의 생태계는 django 모델을 사용하는 모델에 크게 의존합니다.
andho

1
소프트웨어 개발에 사용되는 디자인 패턴입니다. 또한 django는 웹 페이지뿐만 아니라 중간 또는 대규모로 소프트웨어를 제공하는 데 쉽게 사용할 수 있도록 설계되었습니다!
Mohammad Torkashvand
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.