Разделение бизнес-логики и доступа к данным в Django
Я пишу проект на Django, и заметил, что 80% кода находится в файле models.py
. Этот код вызывает у меня путаницу, и со временем мне становится трудно понимать, что на самом деле происходит.
Вот что меня беспокоит:
- Мне не нравится, что уровень модели (который должен быть ответственным только за работу с данными из базы данных) также отправляет электронную почту, взаимодействует с API других сервисов и т.д.
- Также я считаю неприемлемым размещать бизнес-логику во views, так как это затрудняет управление. Например, в моем приложении есть как минимум три способа создавать новые экземпляры
User
, но на самом деле их следует создавать унифицированно. - Я не всегда замечаю, когда методы и свойства моих моделей становятся недетерминированными и когда у них появляются побочные эффекты.
Вот простой пример. Сначала модель 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])
Что я хочу сделать — это разделить сущности в моем коде:
- Сущности уровня базы данных, т.е. логика уровня базы данных: какие данные хранит мое приложение?
- Сущности уровня приложения, т.е. бизнес-логика: что делает мое приложение?
Какие лучшие практики для реализации такого подхода, которые можно применить в Django?
5 ответ(ов)
Я обычно реализую уровень сервиса между представлениями и моделями. Это действует как 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()
Имейте в виду, что я обычно выношу модели, представления и сервисы на уровень модулей и разделяю их еще дальше в зависимости от размера проекта.
В Django используется немного изменённая версия MVC. В Django отсутствует концепция "контроллера". Ближайшей аналогией является "представление" (view), что может вызывать путаницу у тех, кто привык к классическому MVC, поскольку в обычном MVC "представление" больше похоже на "шаблон" (template) в Django.
В Django "модель" (model) — это не просто абстракция для работы с базой данных. В определённом смысле, она выполняет функции, схожие с "представлением" в классическом MVC, выступая в роли контроллера. Модель содержит все поведенческие аспекты, связанные с конкретным экземпляром. Если этому экземпляру требуется взаимодействовать с внешним API в рамках своей функциональности, то это также будет считаться кодом модели. На самом деле, модели не обязаны взаимодействовать с базой данных, поэтому можно создать модель, которая полностью функционирует как интерактивный слой для внешнего API. Это более свободное понятие "модели".
В 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), это поможет создать, например, профили пользователей или хотя бы прочитать его код — это будет полезно для вашего случая.
Старый вопрос, но я хотел бы предложить свое решение. Оно основывается на том, что объектам модели тоже требуется дополнительная функциональность, при этом неудобно размещать ее в 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
.
Я в основном согласен с выбранным ответом (https://stackoverflow.com/a/12857584/871392), но хотел бы добавить один вариант в раздел "Составление запросов".
Можно определить классы QuerySet для моделей, чтобы создавать запросы с фильтрацией и так далее. После этого вы можете проксировать этот класс QuerySet для менеджера модели, как это делают встроенные классы Manager и QuerySet.
Тем не менее, если вам нужно запрашивать несколько моделей данных, чтобы получить одну доменную модель, мне кажется более разумным вынести это в отдельный модуль, как было предложено ранее.
Превысил ли Django 100 тыс. посещений в день? [закрыто]
Как отменить последнюю миграцию?
Как получить значения GET-запроса в Django?
Почему выполнение запланированных задач с использованием Celery предпочтительнее, чем crontab?
Как выполнить поиск в стиле getattr() в шаблоне Django