Tách logic kinh doanh và truy cập dữ liệu trong django


484

Tôi đang viết một dự án ở Django và tôi thấy rằng 80% mã nằm trong tệp models.py. Mã này khó hiểu và sau một thời gian nhất định, tôi không còn hiểu chuyện gì đang thực sự xảy ra.

Đây là những gì làm phiền tôi:

  1. Tôi thấy thật tệ khi cấp độ mô hình của tôi (được cho là chỉ chịu trách nhiệm cho công việc với dữ liệu từ cơ sở dữ liệu) cũng đang gửi email, đi bộ API đến các dịch vụ khác, v.v.
  2. Ngoài ra, tôi thấy không thể chấp nhận đặt logic kinh doanh trong chế độ xem, bởi vì cách này trở nên khó kiểm soát. Ví dụ, trong ứng dụng của tôi có ít nhất ba cách để tạo các thể hiện mới User, nhưng về mặt kỹ thuật, nó nên tạo ra chúng một cách thống nhất.
  3. Tôi không luôn luôn nhận thấy khi các phương thức và tính chất của các mô hình của tôi trở nên không xác định và khi chúng phát triển các tác dụng phụ.

Đây là một ví dụ đơn giản. Lúc đầu, Usermô hình là như thế này:

class User(db.Models):

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

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

Theo thời gian, nó biến thành thế này:

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

Điều tôi muốn là tách các thực thể trong mã của mình:

  1. Các thực thể của cơ sở dữ liệu của tôi, cấp độ cơ sở dữ liệu: Cái gì chứa ứng dụng của tôi?
  2. Các thực thể của ứng dụng của tôi, mức logic kinh doanh: Điều gì có thể làm cho ứng dụng của tôi?

Các thực hành tốt để thực hiện một cách tiếp cận như vậy có thể được áp dụng trong Django là gì?


14
Đọc về tín hiệu
Konstant

1
bạn cũng đã xóa thẻ nhưng bạn có thể sử dụng DCI để tích hợp sự riêng biệt của hệ thống (chức năng) và hệ thống là gì (mô hình dữ liệu / miền)
Rune FS

2
Bạn đề xuất để thực hiện tất cả các logic kinh doanh trong các cuộc gọi lại tín hiệu? Thật không may, không phải tất cả các ứng dụng của tôi có thể được liên kết với các sự kiện trong cơ sở dữ liệu.
defuz

Rune FS, tôi đã thử sử dụng DCI, nhưng dường như nó không cần nhiều cho dự án của tôi: Bối cảnh, định nghĩa vai trò là mixin cho các đối tượng, v.v. Có một cách dễ dàng hơn để "tách" và " Là"? Bạn có thể đưa ra một ví dụ tối thiểu?
defuz

Câu trả lời:


635

Có vẻ như bạn đang hỏi về sự khác biệt giữa mô hình dữ liệumô 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 độngcâ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ấnlệ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
một email xác nhận được gửi đến người dùng
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


11
Thật tuyệt khi thấy một câu trả lời kết hợp DDD vào một câu hỏi liên quan đến django. Chỉ vì Django sử dụng ActiveRecord cho sự kiên trì không có nghĩa là tách các mối quan tâm ra khỏi cửa sổ. Câu trả lời chính xác.
Scott Coates

6
Nếu tôi muốn xác thực rằng người dùng bị nới lỏng là chủ sở hữu của một đối tượng trước khi xóa đối tượng đó, tôi nên kiểm tra xem trong chế độ xem hoặc trong mô-đun biểu mẫu / dịch vụ?
Ivan

6
@Ivan: cả hai. Nó phải ở trong mô-đun mẫu / dịch vụ vì nó là một phần của các ràng buộc kinh doanh của bạn. Nó nên cũng có trong giao diện vì bạn chỉ nên hành động hiện tại người dùng có thể thực sự thực thi.
publysher

4
Phương pháp quản lý tùy chỉnh là một cách tốt để thực hiện các truy vấn : User.objects.inactive_users(). Nhưng ví dụ về mô hình proxy ở đây IMO dẫn đến ngữ nghĩa không chính xác: u = InactiveUser.objects.all()[0]; u.active = True; u.save()và chưa isinstance(u, InactiveUser) == True. Ngoài ra, tôi sẽ đề cập đến một cách hiệu quả để duy trì mô hình truy vấn trong nhiều trường hợp là với chế độ xem db.
Aryeh Leib Taurog

1
@adnanmuttaleb Điều này đúng. Lưu ý rằng bản thân câu trả lời chỉ sử dụng thuật ngữ "Mô hình miền". Tôi đã bao gồm liên kết đến DDD không phải vì câu trả lời của tôi là DDD, mà bởi vì cuốn sách đó làm rất tốt trong việc giúp bạn suy nghĩ về các mô hình miền.
publysher

148

Tôi thường triển khai một lớp dịch vụ ở giữa các khung nhìn và các mô hình. Điều này hoạt động giống như API của dự án của bạn và cung cấp cho bạn chế độ xem trực thăng tốt về những gì đang diễn ra. Tôi đã thừa hưởng thực tiễn này từ một đồng nghiệp của tôi sử dụng kỹ thuật phân lớp này rất nhiều với các dự án Java (JSF), ví dụ:

mô hình

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

   class Meta:
       app_label = "library"

dịch vụ

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

lượt xem

from library.services import get_books

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

Nhắc bạn, tôi thường đưa các mô hình, chế độ xem và dịch vụ lên cấp mô-đun và tách biệt hơn nữa tùy thuộc vào quy mô của dự án


8
Tôi thích cách tiếp cận chung mặc dù theo hiểu biết của tôi, ví dụ cụ thể của bạn thường sẽ được thực hiện như một người quản lý .
arie

9
@arie không nhất thiết là một ví dụ tốt hơn cho các dịch vụ webshop sẽ bao gồm những thứ như tạo phiên giỏ hàng, các tác vụ không đồng bộ như tính toán xếp hạng sản phẩm, tạo và gửi email vvetera
Hedde van der Heide

4
Tôi cũng thích cách tiếp cận này, cũng đến với java. Tôi chưa quen với python, Bạn sẽ kiểm tra lượt xem như thế nào? Làm thế nào bạn sẽ giả định lớp dịch vụ (ví dụ, nếu dịch vụ thực hiện một số cuộc gọi api từ xa)?
Teimuraz

71

Trước hết, đừng lặp lại chính mình .

Sau đó, xin vui lòng cẩn thận không áp đảo, đôi khi nó chỉ là một sự lãng phí thời gian và làm cho ai đó mất tập trung vào những gì quan trọng. Thỉnh thoảng xem lại zen của trăn .

Hãy xem các dự án đang hoạt động

  • Nhiều người hơn = cần nhiều hơn để tổ chức đúng cách
  • các django kho họ có một cấu trúc đơn giản.
  • các kho pip họ có một cấu trúc thư mục straigtforward.
  • các kho vải cũng là một trong những tốt để xem xét.

    • bạn có thể đặt tất cả các mô hình của bạn dưới yourapp/models/logicalgroup.py
  • ví dụ User, Groupvà các mô hình liên quan có thể đi theoyourapp/models/users.py
  • ví dụ như Poll, Question, Answer... có thể đi dướiyourapp/models/polls.py
  • tải những gì bạn cần ở __all__bên trongyourapp/models/__init__.py

Tìm hiểu thêm về MVC

  • mô hình là dữ liệu của bạn
    • điều này bao gồm dữ liệu thực tế của bạn
    • điều này cũng bao gồm dữ liệu phiên / cookie / cache / fs / index của bạn
  • người dùng tương tác với bộ điều khiển để thao tác mô hình
    • đây có thể là API hoặc chế độ xem lưu / cập nhật dữ liệu của bạn
    • điều này có thể được điều chỉnh với request.GET/ request.POST... vv
    • nghĩ phân trang hoặc lọc quá.
  • dữ liệu cập nhật khung nhìn
    • các mẫu lấy dữ liệu và định dạng cho phù hợp
    • API thậm chí các mẫu w / o là một phần của chế độ xem; ví dụ tastypiehoặcpiston
    • điều này cũng nên chiếm phần mềm trung gian.

Tận dụng phần mềm trung gian / templatetags

  • Nếu bạn cần một số công việc phải hoàn thành cho mỗi yêu cầu, phần mềm trung gian là một cách để đi.
    • ví dụ: thêm dấu thời gian
    • ví dụ: cập nhật số liệu về lượt truy cập trang
    • ví dụ: điền vào bộ đệm
  • Nếu bạn có các đoạn mã luôn lặp lại để định dạng các đối tượng, templatetags là tốt.
    • ví dụ: tab hoạt động / url Breadcrumbs

Tận dụng lợi thế của người quản lý mô hình

  • tạo Usercó thể đi trong a UserManager(models.Manager).
  • chi tiết gory cho các trường hợp nên đi trên models.Model.
  • chi tiết gory cho querysetcó thể đi trong một models.Manager.
  • bạn có thể muốn tạo một Usercái cùng một lúc, vì vậy bạn có thể nghĩ rằng nó nên sống trên chính mô hình, nhưng khi tạo đối tượng, có lẽ bạn không có tất cả các chi tiết:

Thí dụ:

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

Sử dụng các hình thức nếu có thể

Rất nhiều mã soạn sẵn có thể được loại bỏ nếu bạn có các biểu mẫu ánh xạ tới mô hình. Điều ModelForm documentationnày là khá tốt. Việc tách mã cho các biểu mẫu khỏi mã mô hình có thể tốt nếu bạn có nhiều tùy chỉnh (hoặc đôi khi tránh các lỗi nhập theo chu kỳ để sử dụng nâng cao hơn).

Sử dụng các lệnh quản lý khi có thể

  • ví dụ yourapp/management/commands/createsuperuser.py
  • ví dụ yourapp/management/commands/activateinbulk.py

nếu bạn có logic kinh doanh, bạn có thể tách nó ra

  • django.contrib.auth sử dụng phụ trợ , giống như db có phụ trợ ... vv.
  • thêm một settinglogic cho doanh nghiệp của bạn (ví dụ AUTHENTICATION_BACKENDS)
  • bạn đã có thể sử dụng django.contrib.auth.backends.RemoteUserBackend
  • bạn đã có thể sử dụng yourapp.backends.remote_api.RemoteUserBackend
  • bạn đã có thể sử dụng yourapp.backends.memcached.RemoteUserBackend
  • ủy thác logic kinh doanh khó khăn cho phụ trợ
  • đảm bảo đặt kỳ vọng ngay trên đầu vào / đầu ra.
  • thay đổi logic kinh doanh cũng đơn giản như thay đổi cài đặt :)

ví dụ phụ trợ:

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' 

có thể trở thành:

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

thêm về các mẫu thiết kế

thêm về ranh giới giao diện

  • Là mã bạn muốn sử dụng thực sự là một phần của các mô hình? ->yourapp.models
  • Là mã một phần của logic kinh doanh? ->yourapp.vendor
  • Là phần mã của các công cụ / libs chung? ->yourapp.libs
  • Là mã một phần của libs logic kinh doanh? -> yourapp.libs.vendorhoặcyourapp.vendor.libs
  • Đây là một điều tốt: bạn có thể kiểm tra mã của mình một cách độc lập không?
  • Sự phân tách có hợp lý không?
    • Vâng tốt :)
    • không, bạn có thể gặp khó khăn khi kiểm tra các khái niệm logic đó một cách riêng biệt.
  • Bạn có nghĩ rằng bạn sẽ cần phải cấu trúc lại khi bạn nhận được thêm 10 lần mã không?
    • vâng, không tốt, không có bueno, refactor có thể là rất nhiều công việc
    • không, điều đó thật tuyệt vời

Nói tóm lại, bạn có thể có

  • 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

hoặc bất cứ điều gì khác giúp bạn; tìm kiếm các giao diện bạn cần và các ranh giới sẽ giúp bạn.


27

Django sử dụng một loại MVC được sửa đổi một chút. Không có khái niệm về "bộ điều khiển" trong Django. Proxy gần nhất là "view", có xu hướng gây nhầm lẫn với chuyển đổi MVC bởi vì trong MVC, một view giống như "template" của Django.

Trong Django, một "mô hình" không chỉ đơn thuần là sự trừu tượng hóa cơ sở dữ liệu. Trong một số khía cạnh, nó chia sẻ nhiệm vụ với "quan điểm" của Django là bộ điều khiển của MVC. Nó giữ toàn bộ hành vi liên quan đến một thể hiện. Nếu trường hợp đó cần tương tác với API bên ngoài như một phần của hành vi thì đó vẫn là mã mô hình. Trên thực tế, các mô hình hoàn toàn không bắt buộc phải tương tác với cơ sở dữ liệu, vì vậy bạn có thể hình dung được các mô hình hoàn toàn tồn tại dưới dạng một lớp tương tác với API bên ngoài. Đó là một khái niệm miễn phí hơn nhiều về một "mô hình".


7

Trong Django, cấu trúc MVC giống như Chris Pratt đã nói, khác với mô hình MVC cổ điển được sử dụng trong các khung công tác khác, tôi nghĩ lý do chính để làm điều này là tránh cấu trúc ứng dụng quá nghiêm ngặt, giống như xảy ra trong các khung MVC khác như CakePHP.

Trong Django, MVC được triển khai theo cách sau:

Xem lớp được chia làm hai. Các khung nhìn chỉ nên được sử dụng để quản lý các yêu cầu HTTP, chúng được gọi và trả lời chúng. Các khung nhìn giao tiếp với phần còn lại của ứng dụng của bạn (biểu mẫu, dạng mẫu, lớp tùy chỉnh, trong các trường hợp đơn giản trực tiếp với các mô hình). Để tạo giao diện, chúng tôi sử dụng Mẫu. Các mẫu giống như Django, nó ánh xạ một bối cảnh vào chúng và bối cảnh này được ứng dụng truyền đến chế độ xem (khi xem yêu cầu).

Lớp mô hình cung cấp đóng gói, trừu tượng hóa, xác nhận, thông minh và làm cho dữ liệu của bạn hướng đối tượng (họ nói rằng một ngày nào đó DBMS cũng sẽ). Điều này không có nghĩa là bạn nên tạo các tệp mô hình khổng lồ (thực tế, một lời khuyên rất hay là chia các mô hình của bạn thành các tệp khác nhau, đặt chúng vào một thư mục có tên 'mô hình', tạo tệp '__init__.py' vào đây thư mục nơi bạn nhập tất cả các mô hình của mình và cuối cùng sử dụng thuộc tính 'app_label' của model.Model class). Mô hình nên trừu tượng bạn khỏi hoạt động với dữ liệu, nó sẽ làm cho ứng dụng của bạn đơn giản hơn. Nếu bạn cũng cần tạo các lớp bên ngoài, như "công cụ" cho các mô hình của mình. Bạn cũng có thể sử dụng di sản trong các mô hình, đặt thuộc tính 'trừu tượng' của lớp Meta của mô hình thành 'True'.

Phần còn lại ở đâu? Vâng, các ứng dụng web nhỏ thường là một loại giao diện cho dữ liệu, trong một số trường hợp chương trình nhỏ sử dụng chế độ xem để truy vấn hoặc chèn dữ liệu là đủ. Các trường hợp phổ biến hơn sẽ sử dụng Forms hoặc ModelForms, thực sự là "bộ điều khiển". Đây không phải là một giải pháp thực tế cho một vấn đề phổ biến và rất nhanh. Đó là những gì một trang web sử dụng để làm.

Nếu Biểu mẫu không phù hợp với bạn, thì bạn nên tạo các lớp của riêng mình để thực hiện phép thuật, một ví dụ rất hay về ứng dụng này là ứng dụng quản trị viên: bạn có thể đọc mã ModelAmin, điều này thực sự hoạt động như một trình điều khiển. Không có cấu trúc tiêu chuẩn, tôi khuyên bạn nên kiểm tra các ứng dụng Django hiện có, nó phụ thuộc vào từng trường hợp. Đây là những gì các nhà phát triển Django dự định, bạn có thể thêm lớp trình phân tích xml, lớp trình kết nối API, thêm Celery để thực hiện các tác vụ, xoắn cho ứng dụng dựa trên lò phản ứng, chỉ sử dụng ORM, tạo dịch vụ web, sửa đổi ứng dụng quản trị viên và hơn thế nữa. .. Đó là khả năng đáp ứng của bạn để tạo mã chất lượng tốt, có tôn trọng triết lý MVC hay không, làm cho nó dựa trên mô-đun và tạo các lớp trừu tượng của riêng bạn. Nó rất linh hoạt.

Lời khuyên của tôi: đọc càng nhiều mã càng tốt, có rất nhiều ứng dụng django xung quanh, nhưng đừng quá coi trọng chúng. Mỗi trường hợp là khác nhau, mô hình và lý thuyết giúp, nhưng không phải lúc nào cũng vậy, đây là một vấn đề không chính xác, django chỉ cung cấp cho bạn các công cụ tốt mà bạn có thể sử dụng để giảm bớt một số khó khăn (như giao diện quản trị, xác thực mẫu web, i18n, triển khai mẫu quan sát viên, tất cả những đề cập trước đây và những người khác), nhưng thiết kế tốt đến từ các nhà thiết kế có kinh nghiệm.

PS.: Sử dụng lớp 'Người dùng' từ ứng dụng auth (từ django tiêu chuẩn), bạn có thể tạo ví dụ về hồ sơ người dùng hoặc ít nhất là đọc mã của nó, nó sẽ hữu ích cho trường hợp của bạn.


1

Một câu hỏi cũ, nhưng tôi muốn đưa ra giải pháp của mình. Nó dựa trên sự chấp nhận rằng mô hình đối tượng quá yêu cầu một số chức năng bổ sung trong khi nó là khó khăn khi phải đặt nó trong models.py . Logic kinh doanh nặng nề có thể được viết riêng tùy theo sở thích cá nhân, nhưng ít nhất tôi thích mô hình để làm mọi thứ liên quan đến chính nó. Giải pháp này cũng hỗ trợ những người thích có tất cả logic được đặt trong các mô hình.

Như vậy, tôi đã nghĩ ra một bản hack cho phép tôi tách logic khỏi các định nghĩa mô hình và vẫn nhận được tất cả các gợi ý từ IDE của tôi.

Những lợi thế nên rõ ràng, nhưng điều này liệt kê một vài điều mà tôi đã quan sát thấy:

  • Các định nghĩa DB vẫn còn đó - không có "rác" logic kèm theo
  • Tất cả logic liên quan đến mô hình được đặt gọn gàng ở một nơi
  • Tất cả các dịch vụ (biểu mẫu, REST, dạng xem) có một điểm truy cập duy nhất vào logic
  • Hay nhất của tất cả: Tôi không phải viết lại bất kỳ mã một lần tôi nhận ra rằng tôi models.py trở nên quá lộn xộn và phải tách logic đi. Việc phân tách diễn ra suôn sẻ và lặp đi lặp lại: Tôi có thể thực hiện một chức năng tại một thời điểm hoặc toàn bộ lớp hoặc toàn bộ mô hình.

Tôi đã sử dụng cái này với Python 3.4 trở lên và Django 1.8 trở lên.

ứng dụng / mô hình

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

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

ứng dụng / 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 ...

Điều duy nhất tôi không thể tìm ra là làm thế nào để IDE của tôi (PyCharm trong trường hợp này) nhận ra rằng UserLogic thực sự là mô hình Người dùng. Nhưng vì đây rõ ràng là một vụ hack, tôi khá vui khi chấp nhận sự phiền toái nhỏ của việc luôn chỉ định loại cho selftham số.


Trên thực tế tôi thấy nó là một cách tiếp cận dễ sử dụng. Nhưng tôi sẽ chuyển mô hình cuối cùng sang một tệp khác và không kế thừa trong mô hình. Nó sẽ giống như service.txt là mô hình userlogic + clash
Maks

1

Tôi sẽ đồng ý với bạn. Có rất nhiều khả năng trong django nhưng nơi tốt nhất để bắt đầu là xem xét triết lý thiết kế của Django .

  1. Gọi API từ một thuộc tính mô hình sẽ không lý tưởng, có vẻ như sẽ có ý nghĩa hơn khi làm một cái gì đó như thế này trong chế độ xem và có thể tạo một lớp dịch vụ để giữ cho mọi thứ khô ráo. Nếu cuộc gọi đến API không bị chặn và cuộc gọi là một cuộc gọi đắt tiền, việc gửi yêu cầu đến một nhân viên dịch vụ (một nhân viên tiêu thụ từ hàng đợi) có thể có ý nghĩa.

  2. Theo mô hình triết lý thiết kế của Django gói gọn mọi khía cạnh của một "đối tượng". Vì vậy, tất cả logic kinh doanh liên quan đến đối tượng đó nên sống ở đó:

Bao gồm tất cả logic miền có liên quan

Các mô hình nên gói gọn mọi khía cạnh của một đối tượng, theo mô hình thiết kế Bản ghi hoạt động của Martin Fowler.

  1. Các tác dụng phụ mà bạn mô tả là rõ ràng, logic ở đây có thể được chia thành các Truy vấn và người quản lý tốt hơn. Đây là một ví dụ:

    mô hình

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

    quản trị viên

    # 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

Tôi hầu hết đồng ý với câu trả lời được chọn ( https://stackoverflow.com/a/12857584/871392 ), nhưng muốn thêm tùy chọn trong phần Tạo câu hỏi.

Người ta có thể định nghĩa các lớp Queryset cho các mô hình để thực hiện các truy vấn bộ lọc, v.v. Sau đó, bạn có thể ủy quyền lớp truy vấn này cho trình quản lý mô hình, như các lớp Trình quản lý và Truy vấn tích hợp thực hiện.

Mặc dù, nếu bạn phải truy vấn một số mô hình dữ liệu để có được một mô hình miền, tôi thấy hợp lý hơn khi đặt mô hình này vào mô-đun riêng như đề xuất trước đây.


0

Bài viết toàn diện nhất về các tùy chọn khác nhau với ưu và nhược điểm:

  1. Ý tưởng số 1: Người mẫu béo
  2. Ý tưởng 2: Đưa logic kinh doanh vào chế độ xem / hình thức
  3. Ý tưởng 3: Dịch vụ
  4. Ý tưởng 4: Truy vấn / Người quản lý
  5. Phần kết luận

Nguồn: https://sunscrapers.com/blog/where-to-put-business-logic-django/


Bạn nên thêm một số lời giải thích.
m02ph3u5

-6

Django được thiết kế để dễ dàng sử dụng để cung cấp các trang web. Nếu bạn không thể chia sẻ với điều này có lẽ bạn nên sử dụng một giải pháp khác.

Tôi đang viết các hoạt động gốc hoặc phổ biến trên mô hình (để có cùng giao diện) và các hoạt động khác trên bộ điều khiển của mô hình. Nếu tôi cần một hoạt động từ mô hình khác, tôi nhập bộ điều khiển của nó.

Cách tiếp cận này đủ cho tôi và sự phức tạp của các ứng dụng của tôi.

Phản ứng của Hedde là một ví dụ cho thấy tính linh hoạt của django và python.

Câu hỏi rất thú vị nào!


9
Làm thế nào để nói rằng nó đủ tốt để bạn hữu ích cho sự hiểu biết của tôi về câu hỏi của bạn?
Chris Wesseling

1
Django có nhiều thứ hơn để cung cấp ngoài django.db.models, nhưng hầu hết hệ sinh thái phụ thuộc rất nhiều vào mô hình của bạn bằng cách sử dụng các mô hình django.
andho

1
Các mẫu thiết kế được sử dụng để phát triển phần mềm. Và django được thiết kế để dễ dàng được sử dụng để cung cấp phần mềm ở quy mô trung bình hoặc lớn không chỉ các trang web!
Mohammad Torkashvand
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.