programing

비즈니스 논리 및 데이터 액세스 분리(django)

prostudy 2022. 4. 7. 21:17
반응형

비즈니스 논리 및 데이터 액세스 분리(django)

나는 장고에서 프로젝트를 쓰고 있는데 코드의 80%가 파일에 있다.models.py이 코드는 혼란스럽고, 일정 시간이 지나면, 나는 정말로 무슨 일이 일어나고 있는지 이해할 수 없다.

나를 괴롭히는 것은 다음과 같다.

  1. 내 모델 레벨(데이터베이스의 데이터로 작업만 담당하기로 되어 있던)도 이메일을 보내거나 API를 통해 다른 서비스로 걸어가는 것 등이 보기 흉하다.
  2. 또한, 나는 사업 논리를 관점에 두는 것이 용납될 수 없다고 생각한다. 왜냐하면 이렇게 하면 통제하기가 어려워지기 때문이다.예를 들어, 내 애플리케이션에는 적어도 세 가지 방법으로 새로운 인스턴스(instance)를 생성할 수 있다.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. 내 응용 프로그램 엔티티, 비즈니스 논리 수준:내 애플리케이션은 무엇을 하는가?

장고에 적용할 수 있는 그런 접근법을 시행하기 위한 좋은 관행은 무엇인가?

데이터 모델도메인 모델의 차이점을 묻는 것 같다. 후자는 최종 사용자가 인식하는 비즈니스 논리와 실체를 찾을 수 있는 곳이고, 전자는 실제로 데이터를 저장하는 곳이다.

게다가, 나는 당신의 질문의 세 번째 부분을 "어떻게 하면 이 모델들을 분리하는 데 실패하는 것을 알아차릴 수 있는가"라고 해석했다.

이 두 개념은 매우 다르며, 그것들을 분리하는 것은 항상 어렵다.그러나 이러한 목적을 위해 사용할 수 있는 몇 가지 일반적인 패턴과 도구가 있다.

도메인 모델 정보

가장 먼저 인식해야 할 것은 당신의 도메인 모델이 실제로 데이터에 관한 것이 아니라는 것이다; 그것은 "이 사용자 활성화", "이 사용자 비활성화", "현재 활성화된 사용자는 누구인가?" 그리고 "이 사용자 이름은 무엇인가?"와 같은 행동과 질문에 관한 것이다.고전적인 용어로 말하자면, 그것은 질의와 명령에 관한 것이다.

명령어로 생각하기

먼저 예제에서 "이 사용자 활성화" 및 "이 사용자 비활성화" 명령을 살펴보십시오.명령의 좋은 점은 명령어를 작은 경우에 쉽게 표현할 수 있다는 것이다.

비활성 사용자로 지정됨
관리자가 이 사용자를 활성화할 때
사용자가 활성화됨
그리고 사용자에게 확인 이메일이 전송된다.
시스템 로그에 항목이 추가됨
(등)

이러한 시나리오는 단일 명령어(이 경우 데이터베이스(일종의 '활성' 플래그), 메일 서버, 시스템 로그 등)에 의해 인프라의 다양한 부분이 어떻게 영향을 받을 수 있는지 확인하는 데 유용하다.

이러한 시나리오는 또한 테스트 기반 개발 환경을 설정하는 데 정말로 도움이 된다.

그리고 마지막으로 명령어를 생각해 보면 업무 중심 응용프로그램을 만드는 데 정말 도움이 된다.사용자들은 이를 높이 평가할 것이다:-)

명령 표현

장고는 명령어를 표현하는 두 가지 쉬운 방법을 제공한다; 그것들은 둘 다 유효한 선택사항이고 두 접근법을 혼합하는 것은 드문 일이 아니다.

서비스 계층

서비스 모듈은 이미 @Hedde에 의해 설명되었다.여기서 별도의 모듈을 정의하고 각 명령은 함수로 표현된다.

서비스파이를 치다

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.properties

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

template_properties.파이를 치다

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

쿼리 메서

쿼리가 단순히 현재적인 것이 아닌 경우 services.py에 쿼리를 추가하거나(사용 중인 경우), queries.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 

프록시 모델

프록시 모델은 사업논리와 보고의 맥락에서 매우 유용하다.기본적으로 모델의 향상된 부분 집합을 정의하십시오.메서드를 재정의하여 관리자의 기본 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()

더 나은 옵션은 사용자 지정 신호를 사용하는 것이다.이 신호들은 물론 당신의 명령에 의해 방출된다.신호는 여러 쿼리 모델을 원래 모델과 동기화할 수 있다는 장점이 있다.게다가, 신호 처리 작업은 셀러리나 유사한 프레임워크를 사용하여 백그라운드 작업으로 오프로드될 수 있다.

신호.파이를 치다

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()
    

청결하게 유지

이 접근법을 사용할 때, 당신의 코드가 청결하게 유지되는지 여부를 결정하는 것은 터무니없이 쉬워진다.다음 지침을 따르십시오.

  • 내 모델에 데이터베이스 상태 관리 이상의 작업을 수행하는 메서드가 포함되어 있는가?명령어를 추출해야 한다.
  • 내 모델에 데이터베이스 필드에 매핑되지 않는 속성이 포함되어 있는가?넌 질문을 뽑아내야 해.
  • 내 모델이 내 데이터베이스가 아닌 인프라(예: 메일)를 참조하는가?명령어를 추출해야 한다.

(시선은 종종 같은 문제를 겪기 때문에) 견해도 마찬가지다.

  • 내 보기가 데이터베이스 모델을 능동적으로 관리하고 있는가?명령어를 추출해야 한다.

일부 참조

장고 문서: 프록시 모델

장고 문서: 신호

아키텍처:도메인 기반 설계

나는 보통 뷰와 모델 사이에 서비스 계층을 구현한다.이것은 당신의 프로젝트의 API와 같은 역할을 하며 당신에게 무슨 일이 일어나고 있는지 좋은 헬리콥터 뷰를 제공한다.나는 Java 프로젝트(JSF)와 함께 이 계층화 기법을 많이 사용하는 동료로부터 이 관행을 물려받았다.

models.py

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

   class Meta:
       app_label = "library"

서비스파이를 치다

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()

잘 들어, 나는 보통 모델, 뷰, 서비스를 모듈 레벨로 가져가고 프로젝트의 규모에 따라 더 멀리 떨어져 있어.

우선, 반복하지 마라.

그렇다면 과대평가하지 않도록 조심해, 때로는 시간낭비일 뿐이고, 중요한 것에 대한 집중력을 잃게 만들기도 한다.때때로 파이썬을 복습하라.

활성 프로젝트 살펴보기

  • 더 많은 사람들.
  • 그들이 직설적인 구조를 가지고 있는 장고 저장소
  • pip 저장소는 직선으로 포워드 된 디렉토리 구조를 가지고 있다.
  • 원단 리포지토리도 보기 좋다.

    • 당신은 당신의 모든 모델을 아래에 배치할 수 있다.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...등
    • 페이징이나 필터링도 생각해봐.
  • 자료가 전망을 갱신하다.
    • 템플릿은 데이터를 가져가고 그에 따라 형식을 지정한다.
    • 템플릿이 없는 API도 뷰의 일부임(예:tastypie또는piston
    • 이것은 또한 미들웨어를 설명해야 한다.

미들웨어/템플릿태그 활용

  • 각 요청에 대해 수행해야 할 작업이 필요하다면 미들웨어도 한 가지 방법이다.
    • 예: 타임스탬프 추가
    • 예: 페이지 적중률에 대한 메트릭 업데이트
    • 예: 캐시 채우기
  • 객체를 포맷할 때 항상 반복되는 코드 조각이 있다면 템플리트 태그가 좋다.
    • 예: 활성 탭/Url 빵가루

모델 관리자 활용

  • 만들기User에 들어갈 수 있다.UserManager(models.Manager).
  • 사례에 대한 상세한 내용은 다음에서 다루어야 한다.models.Model.
  • 에 대한 세세한 사항.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()

가능한 경우 양식 사용

모델에 매핑된 양식을 가지고 있으면 많은 보일러 플레이트 코드가 제거될 수 있다.꽤 괜찮다.양식 코드를 모델 코드에서 분리하는 것은 사용자 정의가 많은 경우(또는 더 고급화된 사용을 위해 주기적인 가져오기 오류를 피하는 경우도 있음) 좋을 수 있다.

가능한 경우 관리 명령 사용

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

사업논리가 있으면 따로따로 구분할 수 있다.

  • django.contrib.auth DB가 백엔드를 사용하는 것처럼 백엔드를 사용한다.
  • 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
  • 코드가 일반 도구/libs의 일부인가?->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

또는 당신에게 도움이 되는 다른 모든 것; 당신이 필요로 하는 인터페이스경계를 찾는 것이 당신에게 도움이 될 것이다.

장고는 약간 변형된 종류의 MVC를 사용한다.장고에는 '컨트롤러'라는 개념이 없다.가장 가까운 프록시는 "보기"로, MVC에서는 보기가 장고의 "템플릿"에 가깝기 때문에 MVC 변환기와 혼동을 일으키는 경향이 있다.

장고에서 '모델'은 단순한 데이터베이스 추상화가 아니다.어떤 점에서는 MVC의 통제자로서의 장고의 「관점」과 의무를 공유한다.그것은 한 예와 관련된 모든 행동을 담고 있다.해당 인스턴스가 동작의 일부로 외부 API와 상호 작용해야 하는 경우, 여전히 모델 코드인 것이다.사실, 모델은 데이터베이스와 상호작용할 필요가 전혀 없기 때문에 당신은 외부 API에 대한 상호작용 계층으로 존재하는 모델을 가질 수 있다.그것은 "모델"의 훨씬 더 자유로운 개념이다.

장고에서 MVC 구조는 크리스 프랫의 말대로 다른 프레임워크에서 사용되는 고전적인 MVC 모델과는 달리, 이것을 하는 주된 이유는 케이크PHP와 같은 다른 MVC 프레임워크에서 일어나는 것과 같이 지나치게 엄격한 적용 구조를 회피하기 때문이라고 생각한다.

장고에서는 다음과 같은 방법으로 MVC를 구현하였다.

뷰 레이어는 두 개로 분할된다.보기는 HTTP 요청을 관리하는 데만 사용해야 하며, 이러한 요청을 호출하여 응답한다.보기는 응용프로그램의 나머지 부분(양식, 모델 양식, 사용자 정의 클래스, 간단한 경우 모델과 직접 통신)과 통신한다.인터페이스를 생성하려면 템플릿을 사용하십시오.템플릿은 장고에 문자열과 유사하며, 그 안에 컨텍스트를 매핑하고, 이 컨텍스트는 애플리케이션에 의해 뷰에 전달되었다(보기가 질문할 때).

모델 계층은 캡슐화, 추상화, 검증, 인텔리전스를 제공하며, 데이터 객체 지향(언젠가는 DBMS도 그럴 것이라고 한다)을 제공한다.이것은 당신이 거대한 models.py 파일을 만들어야 한다는 것을 의미하지는 않는다. (사실 매우 좋은 조언은 당신의 모델을 다른 파일로 나누어 ' them'라는 폴더에 넣고, '_init___py' 파일을 당신의 모든 모델을 수입하는 이 폴더에 만들고 마지막으로 모델의 'app__' 속성을 사용하는 것이다.모델 클래스).모델은 당신을 데이터로 조작하는 것을 추상화해야 한다. 그것은 당신의 애플리케이션을 더 단순하게 만들 것이다.필요한 경우 모델용 "도구"와 같은 외부 클래스도 생성하십시오.모델에서도 헤리티지를 사용할 수 있으며, 모델 메타 클래스의 '추상' 속성을 'True'로 설정할 수 있다.

나머지는 어디 있지?작은 웹 애플리케이션은 일반적으로 데이터에 대한 인터페이스의 일종이며, 일부 소규모 프로그램의 경우 뷰를 사용하여 데이터를 조회하거나 삽입하는 것으로 충분하다.더 일반적인 사례들은 실제로 "컨트롤러"인 Forms나 ModelForms 또는 ModelForms는 실제로 "컨트롤러"이다.이것은 다름아닌 일반적인 문제에 대한 실질적인 해결책이며, 매우 빠른 해결책이다.그것은 웹사이트가 할 수 있는 일이다.

만약 양식이 당신에게 도움이 되지 않는다면, 당신은 마술을 하기 위해 당신만의 클래스를 만들어야 한다. 이것의 아주 좋은 예는 관리 애플리케이션이다: 당신은 ModelAmin 코드를 읽을 수 있다. 이것은 실제로 컨트롤러로서 작동한다.표준구조는 없고, 기존 짱오 앱을 검토해 볼 것을 제안한다, 각각의 경우에 따라 다르다.이것이 바로 장고 개발자들이 의도한 것이다. xml 파서 클래스, API 커넥터 클래스, 작업 수행에 셀러리 추가, 원자로 기반 애플리케이션용으로 꼬임, ORM만 사용, 웹 서비스 만들기, 관리 애플리케이션 수정 등...좋은 품질의 코드를 만들고, MVC 철학을 존중하고, 모듈 기반으로 만들고, 자신만의 추상화 레이어를 만드는 것이 당신의 대응력이다.신축성이 아주 좋다.

제 조언은 가능한 한 많은 코드를 읽으라는 겁니다. 주변에 짱고 어플리케이션들이 많이 있지만, 너무 심각하게 받아들이지 마십시오.각각의 경우가 다르고, 패턴과 이론이 도움이 되지만, 항상은 아니지만, 이것은 부정확한 과학이다. django는 단지 당신에게 약간의 고통을 덜어주는 데 사용할 수 있는 좋은 도구들을 제공한다(관리 인터페이스, 웹 양식 검증, i18n, 관찰자 패턴 구현, 이전에 언급된 모든 것 등). 그러나 좋은 디자인은 숙련된 설계자들로부터 나온다.

PS.: 인증 애플리케이션(표준 django)에서 '사용자' 클래스를 사용하며, 예를 들어 사용자 프로필을 만들거나 코드를 읽으면 사례에 유용할 것이다.

오래된 질문이지만, 어쨌든 내 해결책을 제안하고 싶다.모델 오브젝트 역시 models.py 내에 배치하는 것이 어색한 반면, 일부 추가 기능이 필요하다는 것에 동의한 것이다.헤비즈니스 논리는 개인의 취향에 따라 따로 쓰일 수도 있지만, 적어도 그 모델만은 자신과 관련된 모든 것을 하는 것이 좋다.이 솔루션은 또한 모델 자체에 모든 논리를 배치하기를 원하는 사람들을 지원한다.

그렇게 나는 논리학과 모델 정의의 차이를 구분하고 여전히 IDE의 암시를 모두 얻을 수 있는 해킹을 고안해 냈다.

장점은 분명해야 하지만, 내가 관찰한 몇 가지 사항을 나열한다.

  • DB 정의는 그대로 유지 - 논리 "쓰레기"가 첨부되지 않음
  • 모델 관련 논리가 모두 한 곳에 깔끔하게 배치되어 있음
  • 모든 서비스(양식, REST, 보기)에 로직에 대한 단일 액세스 지점이 있음
  • 무엇보다도: 나는 내 models.py이 너무 복잡해지고 논리를 분리해야 한다는 것을 깨닫고 나서 어떤 코드도 다시 쓸 필요가 없었다.분리가 부드럽고 반복적이다.나는 한 시간 또는 전체 수업 또는 models.py 전체에서 기능을 할 수 있다.

파이톤 3.4 이상, 짱오 1.8 이상과 함께 사용해 왔다.

앱/랩톱파이를 치다

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

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

앱/소프트웨어/사용자파이를 치다

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매개 변수

네 말에 동의해야 할 것 같아.장고에는 여러 가지 가능성이 있지만, 가장 좋은 출발점은 장고의 디자인 철학을 검토하는 것이다.

  1. 모델 속성에서 API를 호출하는 것은 이상적이지 않을 것이며, 이러한 관점에서 이러한 작업을 수행하고, 상황을 건조하게 유지하기 위한 서비스 계층을 만드는 것이 더 타당할 것으로 보인다.API 호출이 차단되지 않고 통화 비용이 많이 드는 경우 서비스 작업자(대기열에서 소비하는 작업자)에게 요청을 보내는 것이 타당할 수 있다.

  2. 장고의 디자인 철학 모델에 따르면, "객체"의 모든 측면을 캡슐화한다.따라서 해당 개체와 관련된 모든 비즈니스 논리가 이 곳에 존재해야 한다.

모든 관련 도메인 논리 포함

모델은 마틴 파울러의 활성 레코드 설계 패턴에 따라 "물체"의 모든 측면을 캡슐화해야 한다.

  1. 당신이 묘사하는 부작용은 명백하다. 이곳의 논리는 Querysets와 매니저로 분해되는 것이 더 나을 수 있다.예를 들면 다음과 같다.

    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)
    

나는 대부분 선택된 답변에 동의하지만 (https://stackoverflow.com/a/12857584/871392),) 질의 작성 섹션에 옵션을 추가하기를 원한다.

필터 쿼리 등을 만들기 위한 모델의 QuerySet 클래스를 정의할 수 있다.그런 다음 빌드인 관리자 및 QuerySet 클래스와 같이 모델의 관리자를 위해 이 Queryset 클래스를 프록시할 수 있다.

비록 하나의 도메인 모델을 얻기 위해 여러 데이터 모델을 조회해야 한다면, 나는 이것을 앞에서 제안했던 것과 같은 별도의 모듈에 두는 것이 더 합리적인 것 같다.

장단점이 있는 다양한 옵션에 대한 가장 포괄적인 기사:

  1. 아이디어 #1: 뚱뚱한 모델
  2. 아이디어 #2: 비즈니스 로직을 보기/양식에 배치
  3. 아이디어 #3: 서비스
  4. 아이디어 #4: QuerySets/Manager
  5. 결론

출처: https://sunscrapers.com/blog/where-to-put-business-logic-django/

장고는 웹페이지를 전달하는 데 쉽게 쓰일 수 있도록 고안되었다.만약 당신이 이것과 혼동할 수 없다면 다른 해결책을 사용해야 할 것이다.

모델의 루트 또는 공통 연산을 (같은 인터페이스를 갖기 위해)와 다른 연산을 모델의 컨트롤러에 쓰고 있다.다른 모델에서 작업이 필요하면 컨트롤러를 가져온다.

이 접근방식은 나와 내 애플리케이션의 복잡성에 충분하다.

헤드의 반응은 짱고와 비단뱀 자체의 유연성을 보여주는 사례다.

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

참조URL: https://stackoverflow.com/questions/12578908/separation-of-business-logic-and-data-access-in-django

반응형