6

Разделение бизнес-логики и доступа к данным в Django

15

Я пишу проект на Django, и заметил, что 80% кода находится в файле models.py. Этот код вызывает у меня путаницу, и со временем мне становится трудно понимать, что на самом деле происходит.

Вот что меня беспокоит:

  1. Мне не нравится, что уровень модели (который должен быть ответственным только за работу с данными из базы данных) также отправляет электронную почту, взаимодействует с API других сервисов и т.д.
  2. Также я считаю неприемлемым размещать бизнес-логику во views, так как это затрудняет управление. Например, в моем приложении есть как минимум три способа создавать новые экземпляры 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): 
        # свойство стало недетерминированным, данные берутся от другого сервиса через API
        return remote_api.request_user_name(self.uid) or 'Anonymous'

    def activate(self):
        # метод теперь имеет побочный эффект (отправляет сообщение пользователю)
        self.status = 'activated'
        self.save()
        send_mail('Ваш аккаунт активирован!', '…', [self.email])

Что я хочу сделать — это разделить сущности в моем коде:

  1. Сущности уровня базы данных, т.е. логика уровня базы данных: какие данные хранит мое приложение?
  2. Сущности уровня приложения, т.е. бизнес-логика: что делает мое приложение?

Какие лучшие практики для реализации такого подхода, которые можно применить в Django?

5 ответ(ов)

1

Я обычно реализую уровень сервиса между представлениями и моделями. Это действует как 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):
    """ простая сервисная функция для получения книг, можно широко расширять """
    return Book.objects.filter(**filters)[:limit]  # list[:None] вернет весь список

views.py

from library.services import get_books

class BookListView(ListView):
    """ простое представление, например, реализуйте функции _build и _apply для фильтров """
    queryset = get_books()

Имейте в виду, что я обычно выношу модели, представления и сервисы на уровень модулей и разделяю их еще дальше в зависимости от размера проекта.

0

В Django используется немного изменённая версия MVC. В Django отсутствует концепция "контроллера". Ближайшей аналогией является "представление" (view), что может вызывать путаницу у тех, кто привык к классическому MVC, поскольку в обычном MVC "представление" больше похоже на "шаблон" (template) в Django.

В Django "модель" (model) — это не просто абстракция для работы с базой данных. В определённом смысле, она выполняет функции, схожие с "представлением" в классическом MVC, выступая в роли контроллера. Модель содержит все поведенческие аспекты, связанные с конкретным экземпляром. Если этому экземпляру требуется взаимодействовать с внешним API в рамках своей функциональности, то это также будет считаться кодом модели. На самом деле, модели не обязаны взаимодействовать с базой данных, поэтому можно создать модель, которая полностью функционирует как интерактивный слой для внешнего API. Это более свободное понятие "модели".

0

В Django структура MVC, как говорил Крис Пратт, отличается от классической модели MVC, используемой в других фреймворках. Основная причина этой разницы, по моему мнению, заключается в том, чтобы избежать слишком строгой структуры приложения, как это бывает в других MVC-фреймворках, таких как CakePHP.

В Django MVC реализована следующим образом:

Слой представления (View) разделен на две части. Views предназначены только для обработки HTTP-запросов: они вызываются и отвечают на них. Views взаимодействуют с остальной частью вашего приложения (формами, modelForms, пользовательскими классами или, в простых случаях, напрямую с моделями). Для создания интерфейса мы используем шаблоны (Templates). Шаблоны аналогичны строкам для Django, они связывают контекст с собой, а этот контекст передается представлению приложением (когда представление запрашивает).

Слой модели (Model) обеспечивает инкапсуляцию, абстракцию, валидацию, интеллектуальность и делает ваши данные объектно-ориентированными (говорят, что когда-нибудь это будет и в СУБД). Это не значит, что вы должны создавать огромные файлы models.py; на самом деле, очень хорошая рекомендация — разбивать ваши модели на отдельные файлы, помещать их в папку под названием 'models', создавать в этой папке файл 'init.py', куда вы импортируете все свои модели, и в итоге использовать атрибут 'app_label' в классе models.Model. Модель должна абстрагировать вас от работы с данными, это упростит ваше приложение. Также, если необходимо, вы можете создавать внешние классы, такие как "инструменты" для ваших моделей. Вы также можете использовать наследование в моделях, установив атрибут 'abstract' в мета-классе вашей модели на 'True'.

А где же остальное? В целом, небольшие веб-приложения обычно представляют собой своего рода интерфейс к данным. В некоторых простых случаях использование представлений для запроса или вставки данных было бы достаточно. Более распространенные случаи будут использовать формы или modelForms, которые на самом деле выступают в роли "контроллеров". Это практическое решение для распространенной проблемы и весьма быстрое. Именно так и работают веб-сайты.

Если форм вам недостаточно, создайте свои собственные классы для выполнения необходимых действий. Хороший пример — это административное приложение: вы можете посмотреть код ModelAdmin, который фактически работает как контроллер. Стандартной структуры не существует; я рекомендую исследовать существующие Django-приложения, все зависит от конкретного случая. Именно этого и хотели разработчики Django: вы можете добавлять класс для парсинга XML, класс для подключения к API, добавить Celery для выполнения задач, использовать Twisted для реактивного приложения, работать только с ORM, создавать веб-сервис, модифицировать административное приложение и многое другое. Ваша ответственность — создавать качественный код, следуя философии MVC или нет, делать его модульным и создавать свои собственные абстракционные слои. Django очень гибок.

Мой совет: изучайте как можно больше кода. Вокруг много Django-приложений, но не воспринимайте их слишком серьезно. Каждый случай уникален, паттерны и теория могут помочь, но не всегда — это не точная наука. Django просто предоставляет хорошие инструменты, которые помогут облегчить некоторые задачи (такие как административный интерфейс, валидация веб-форм, i18n, реализация паттерна наблюдатель и многие другие), но хорошие дизайны создаются опытными разработчиками.

P.S.: Используйте класс 'User' из модуля auth (стандартного Django), это поможет создать, например, профили пользователей или хотя бы прочитать его код — это будет полезно для вашего случая.

0

Старый вопрос, но я хотел бы предложить свое решение. Оно основывается на том, что объектам модели тоже требуется дополнительная функциональность, при этом неудобно размещать ее в models.py. Тяжелую бизнес-логику можно писать отдельно в зависимости от личных предпочтений, но я, по крайней мере, предпочитаю, чтобы модель выполняла все, что связано с самой собой. Это решение также поддерживает тех, кто предпочитает размещать всю логику внутри моделей.

В связи с этим я придумал хак, который позволяет мне отделить логику от определения моделей и при этом получать все подсказки от моего IDE.

Преимущества должны быть очевидны, но вот несколько, которые я наблюдал:

  • Определения базы данных остаются именно теми, чем являются - никакой логики "мусора" не attached.
  • Связанная с моделью логика аккуратно размещена в одном месте.
  • Все сервисы (формы, REST, представления) имеют единую точку доступа к логике.
  • И самое лучшее: мне не пришлось переписывать код, когда я понял, что мой models.py стал слишком загроможденным, и я должен отделить логику. Процесс разделения был плавным и итеративным: я мог сделать функцию за раз или целый класс, или всю models.py.

Я использую это с Python 3.4 и выше и Django 1.8 и выше.

app/models.py

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

class User(models.Model, UserLogic):
    field1 = models.AnyField(....)
    ... определения полей ...

app/logic/user.py

if False:
    # Это позволяет IDE знать о модели User и ее членах полях
    from main.models import User

class UserLogic(object):
    def logic_function(self: 'User'):
        ... код с работающими подсказками ...

Единственное, что я не могу понять, это как заставить мой IDE (в данном случае PyCharm) распознать, что UserLogic на самом деле является моделью User. Но так как это, очевидно, хак, я вполне готов принять небольшое неудобство всегда указывать тип для параметра self.

0

Я в основном согласен с выбранным ответом (https://stackoverflow.com/a/12857584/871392), но хотел бы добавить один вариант в раздел "Составление запросов".

Можно определить классы QuerySet для моделей, чтобы создавать запросы с фильтрацией и так далее. После этого вы можете проксировать этот класс QuerySet для менеджера модели, как это делают встроенные классы Manager и QuerySet.

Тем не менее, если вам нужно запрашивать несколько моделей данных, чтобы получить одну доменную модель, мне кажется более разумным вынести это в отдельный модуль, как было предложено ранее.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь