데이터 모델 과 도메인 모델 의 차이점에 대해 문의하는 것 같습니다 . 후자는 최종 사용자가 인식 한 비즈니스 로직과 엔터티를 찾을 수있는 곳이고, 전자는 실제로 데이터를 저장하는 곳입니다.
또한 귀하의 질문의 세 번째 부분을 다음과 같이 해석했습니다.
이것들은 매우 다른 두 가지 개념이며 항상 분리하기가 어렵습니다. 그러나이 목적으로 사용할 수있는 몇 가지 일반적인 패턴과 도구가 있습니다.
도메인 모델 정보
가장 먼저 인식해야 할 것은 도메인 모델이 실제로 데이터에 관한 것이 아니라는 것입니다. "이 사용자 활성화", "이 사용자 비활성화", "현재 활성화 된 사용자"및 "이 사용자의 이름은 무엇입니까?"와 같은 작업 및 질문 에 관한 것입니다. 고전적인 용어 : 쿼리 및 명령 에 관한 것 입니다.
명령에 대한 생각
예를 들어 "이 사용자 활성화"및 "이 사용자 비활성화"명령을 살펴 보겠습니다. 명령에 대한 좋은 점은 작은 시나리오로 쉽게 표현할 수 있다는 것입니다.
지정된 비활성 사용자 관리자는이 사용자가 활성화 한 후 , 사용자가 활성화되고 그리고 확인 이메일이 사용자에게 전송되고 및 엔트리 시스템 로그에 추가되고
(등등)
이러한 시나리오는 데이터베이스 (일부 '활성'플래그), 메일 서버, 시스템 로그 등 단일 명령으로 인프라의 여러 부분이 어떤 영향을 받는지 확인하는 데 유용합니다.
이러한 시나리오는 또한 테스트 주도 개발 환경을 설정하는 데 도움이됩니다.
마지막으로, 명령을 생각하면 실제로 작업 지향 응용 프로그램을 만드는 데 도움이됩니다. 귀하의 사용자는 이것을 높이 평가할 것입니다 :-)
표현 명령
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 설명서 : 프록시 모델
장고 문서 : 신호
아키텍처 : 도메인 기반 디자인