Có vẻ như bạn đang hỏi về sự khác biệt giữa mô hình dữ liệu và mô hình miền - sau này là nơi bạn có thể tìm thấy logic nghiệp vụ và các thực thể theo cảm nhận của người dùng cuối, trước đây là nơi bạn thực sự lưu trữ dữ liệu của mình.
Hơn nữa, tôi đã giải thích phần thứ 3 của câu hỏi của bạn là: làm thế nào để nhận thấy sự thất bại trong việc tách biệt các mô hình này.
Đây là hai khái niệm rất khác nhau và thật khó để tách chúng ra. Tuy nhiên, có một số mẫu và công cụ phổ biến có thể được sử dụng cho mục đích này.
Giới thiệu về mô hình miền
Điều đầu tiên bạn cần nhận ra là mô hình miền của bạn không thực sự về dữ liệu; đó là về các hành động và câu hỏi như "kích hoạt người dùng này", "hủy kích hoạt người dùng này", "người dùng nào hiện đang được kích hoạt?" và "tên người dùng này là gì?". Theo thuật ngữ cổ điển: đó là về các truy vấn và lệnh .
Suy nghĩ theo lệnh
Hãy bắt đầu bằng cách xem các lệnh trong ví dụ của bạn: "kích hoạt người dùng này" và "hủy kích hoạt người dùng này". Điều thú vị về các lệnh là chúng có thể dễ dàng được thể hiện bằng kịch bản nhỏ được đưa ra khi:
đưa ra một người dùng không hoạt động
khi quản trị viên kích hoạt người dùng này
sau đó người dùng sẽ kích hoạt
và một email xác nhận được gửi đến người dùng
và một mục nhập được thêm vào nhật ký hệ thống
(v.v.)
Kịch bản như vậy rất hữu ích để xem các phần khác nhau trong cơ sở hạ tầng của bạn có thể bị ảnh hưởng như thế nào bởi một lệnh - trong trường hợp này là cơ sở dữ liệu của bạn (một loại cờ 'hoạt động'), máy chủ thư, nhật ký hệ thống của bạn, v.v.
Kịch bản như vậy cũng thực sự giúp bạn trong việc thiết lập môi trường Phát triển hướng thử nghiệm.
Và cuối cùng, suy nghĩ trong các lệnh thực sự giúp bạn tạo ra một ứng dụng định hướng nhiệm vụ. Người dùng của bạn sẽ đánh giá cao điều này :-)
Lệnh thể hiện
Django cung cấp hai cách dễ dàng để thể hiện các lệnh; cả hai đều là các tùy chọn hợp lệ và không có gì lạ khi trộn lẫn hai cách tiếp cận.
Lớp dịch vụ
Các mô-đun dịch vụ đã được mô tả bởi @Hedde . Ở đây bạn định nghĩa một mô-đun riêng biệt và mỗi lệnh được biểu diễn dưới dạng hàm.
dịch vụ
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
Sử dụng các hình thức
Một cách khác là sử dụng Mẫu Django cho mỗi lệnh. Tôi thích cách tiếp cận này, bởi vì nó kết hợp nhiều khía cạnh liên quan chặt chẽ:
- thực thi lệnh (nó làm gì?)
- xác nhận các tham số lệnh (nó có thể làm điều này?)
- trình bày lệnh (làm thế nào tôi có thể làm điều này?)
biểu mẫu
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
Suy nghĩ trong truy vấn
Ví dụ của bạn không chứa bất kỳ truy vấn nào, vì vậy tôi đã tự do tạo ra một vài truy vấn hữu ích. Tôi thích sử dụng thuật ngữ "câu hỏi", nhưng truy vấn là thuật ngữ cổ điển. Các truy vấn thú vị là: "Tên của người dùng này là gì?", "Người dùng này có thể đăng nhập không?", "Hiển thị cho tôi danh sách người dùng bị vô hiệu hóa" và "Phân phối địa lý của người dùng bị vô hiệu hóa là gì?"
Trước khi bắt đầu trả lời các truy vấn này, bạn phải luôn tự hỏi mình hai câu hỏi: đây có phải là truy vấn mang tính trình bày chỉ dành cho các mẫu của tôi và / hoặc truy vấn logic nghiệp vụ gắn liền với việc thực hiện các lệnh của tôi và / hoặc truy vấn báo cáo .
Các truy vấn trình bày chỉ được thực hiện để cải thiện giao diện người dùng. Các câu trả lời cho các truy vấn logic nghiệp vụ ảnh hưởng trực tiếp đến việc thực hiện các lệnh của bạn. Truy vấn báo cáo chỉ đơn thuần cho mục đích phân tích và có những hạn chế về thời gian lỏng lẻo hơn. Các loại này không loại trừ lẫn nhau.
Câu hỏi khác là: "tôi có toàn quyền kiểm soát các câu trả lời không?" Ví dụ: khi truy vấn tên người dùng (trong ngữ cảnh này), chúng tôi không có bất kỳ quyền kiểm soát nào đối với kết quả, vì chúng tôi dựa vào API bên ngoài.
Làm câu hỏi
Truy vấn cơ bản nhất trong Django là việc sử dụng đối tượng Manager:
User.objects.filter(active=True)
Tất nhiên, điều này chỉ hoạt động nếu dữ liệu thực sự được biểu diễn trong mô hình dữ liệu của bạn. Đây không phải là luôn luôn như vậy. Trong những trường hợp đó, bạn có thể xem xét các tùy chọn bên dưới.
Thẻ và bộ lọc tùy chỉnh
Sự thay thế đầu tiên rất hữu ích cho các truy vấn chỉ mang tính trình bày: thẻ tùy chỉnh và bộ lọc mẫu.
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)
Phương thức truy vấn
Nếu truy vấn của bạn không chỉ đơn thuần là trình bày, bạn có thể thêm truy vấn vào dịch vụ của bạn (nếu bạn đang sử dụng điều đó) hoặc giới thiệu mô-đun truy vấn :
truy vấn
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
Mô hình proxy
Các mô hình proxy rất hữu ích trong bối cảnh logic kinh doanh và báo cáo. Về cơ bản, bạn xác định một tập hợp con nâng cao của mô hình của bạn. Bạn có thể ghi đè Truy vấn cơ sở của Trình quản lý bằng cách ghi đè Manager.get_queryset()
phương thức.
mô hình
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
Các mô hình truy vấn
Đối với các truy vấn vốn đã phức tạp, nhưng được thực hiện khá thường xuyên, có khả năng là các mô hình truy vấn. Mô hình truy vấn là một dạng không chuẩn hóa trong đó dữ liệu có liên quan cho một truy vấn duy nhất được lưu trữ trong một mô hình riêng biệt. Thủ thuật tất nhiên là giữ cho mô hình không chuẩn hóa đồng bộ với mô hình chính. Các mô hình truy vấn chỉ có thể được sử dụng nếu các thay đổi hoàn toàn nằm dưới sự kiểm soát của bạn.
mô hình
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
Tùy chọn đầu tiên là cập nhật các mô hình này trong các lệnh của bạn. Điều này rất hữu ích nếu các mô hình này chỉ được thay đổi bởi một hoặc hai lệnh.
biểu mẫu
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()
Một lựa chọn tốt hơn sẽ là sử dụng tín hiệu tùy chỉnh. Những tín hiệu này tất nhiên được phát ra bởi các lệnh của bạn. Tín hiệu có lợi thế là bạn có thể giữ nhiều mô hình truy vấn đồng bộ với mô hình ban đầu của bạn. Hơn nữa, xử lý tín hiệu có thể được giảm tải cho các tác vụ nền, sử dụng Celery hoặc các khung tương tự.
tín hiệu
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
biểu mẫu
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
mô hình
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()
Giữ nó sạch sẽ
Khi sử dụng phương pháp này, việc xác định xem mã của bạn có sạch sẽ hay không. Chỉ cần làm theo các hướng dẫn sau:
- Mô hình của tôi có chứa các phương thức làm nhiều hơn việc quản lý trạng thái cơ sở dữ liệu không? Bạn nên trích xuất một lệnh.
- Mô hình của tôi có chứa các thuộc tính không ánh xạ tới các trường cơ sở dữ liệu không? Bạn nên trích xuất một truy vấn.
- Cơ sở hạ tầng tham chiếu mô hình của tôi không phải là cơ sở dữ liệu của tôi (chẳng hạn như thư)? Bạn nên trích xuất một lệnh.
Điều tương tự cũng xảy ra đối với các quan điểm (vì các quan điểm thường gặp phải cùng một vấn đề).
- Quan điểm của tôi có chủ động quản lý các mô hình cơ sở dữ liệu không? Bạn nên trích xuất một lệnh.
Một số tài liệu tham khảo
Tài liệu Django: mô hình proxy
Tài liệu Django: tín hiệu
Kiến trúc: Thiết kế hướng tên miền