Лучшие практики для Django — рефакторинг толстых моделей

Перевод статьи «Django Best Practices — Refactoring Django Fat Models».

Photo by Shaun He on Unsplash

Чистый, читаемый и понятный код — вот к чему в идеале стремится любой разработчик. Поэтому инструменты QuerySet и Manager играют большую роль в работе с Django. С их помощью проще не забыть о принципах DRY (Don’t repeat yourself) и KISS (Keep it Simple, Stupid), когда используешь ORM. Поэтому на первый взгляд создавать толстые модели кажется хорошей идеей. Пока не натолкнешься на последствия.

Зачем рефакторить толстые модели Django?

Довольно долго использование толстых моделей считалось хорошей практикой. Мы считаем это фундаментальной ошибкой. Если переложить на модели обработку и бизнес-логики, и ограничений (rules), то в будущем это приведет к излишним сложностям.

Очевидно, что большая часть разработчиков как придерживалась этого мнения долгие годы, так и впредь будет отстаивать преимущества этого подхода. И всё же мы порассуждаем, почему от него стоит отказаться.

Избыточная связанность

В топе недостатков толстых моделей — особенности  их взаимодействия с функциями. Большая часть бизнес-логики находится в активном потоке. Очень вероятно, что из начального потока будут совершаться вызовы методов других моделей и функций. На первый взгляд, в этом нет ничего криминального, однако позже появятся лишние зависимости. В дальнейшем это только усложнит работу с моделями, являющимися основой вашего приложения.

Весьма вероятно, что из-за всех этих зависимостей в будущем вам потребуется модифицировать основной код. Например, вследствие внесения изменений в других моделях.

Чем больше толстых моделей, тем больше дыр приходится латать. Продолжаться это будет до тех пор, пока однажды вы не поймёте — ресурсы на работу с побочками от толстых моделей исчерпаны.

Сложности с тестированием

Разработчики стараются сделать свой код как можно более простым по разным причинам, в том числе — чтобы дебажить код во время фазы тестирования было проще. Чем более избыточен код, тем сложнее тестировать и тем больше макеты-пустышки (Mock).

Никому не хочется возиться в паутине бесконечных ветвлений в зависимостях. Создавайте простые и читаемые методы, тогда тестирование будет лёгким. Спросите у коллег какой код подарил им самые жуткие  и бессонные ночи — они расскажут про сложные методы с зависимостями.

Изоляция бизнес-логики от данных в моделях

Мы назвали причины для отказа от толстых моделей. Пришло время разобраться, как превратить такие модели в простой читаемый код. Для этого понадобятся Model Manager и QuerySets.

Преимущества менеджера моделей

Начнем с небольшой настройки:

  1. Перенесем создание пользователей в UserManager(models.Manager)
  2. Данные для инстансов идут в models.Model
  3. Данные для queryset можно поместить в models.Manager

Было бы неплохо создавать одного пользователя в момент времени, потому вам захочется поселить этот механизм в саму модель. Однако при создании объекта у вас может не хватать данных:

Например:

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

Инкапсуляция запросов в базу данных через QuerySet

Теперь можно отдать QuerySets инкапсуляцию всех запросов в базу данных. Вот его некоторые преимущества:

  • Повторное использование. Метод можно вызывать в других частях кода. Это свойство освобождает от лишних объявлений.
  • Унификация. Если в запросе передается значение, то в его переобъявлении нет необходимости — достаточно прописать его один раз.
  • Читаемость. Отсекаем сложности, связанные с  методами и пишем простой, читаемый и правильный запрос.
  • Нетривиальность. Так как инкапсуляция сохраняет оригинальный интерфейс, вы можете оптимизировать, менять или дорабатывать бизнес-логику прямо в коде вызова. Совершать изменения в таком ключе гораздо легче.

Перейдем к практическим примерам. Поработаем с классами Category и Product.

class Category(models.Model):
  name = models.CharField(max_length=100)

  def __str__(self):
    return self.name


class Product(models.Model):
  name = models.CharField(max_length=120)
  price = models.FloatField(blank=True, null=True)
  stock = models.IntegerField(default=1)
  category = models.ForeignKey(Category, on_delete=models.CASCADE)

  def __str__(self):
    return self.name

Заметим, что каждому товару соответствует одна категория. Допустим у нас 4 товара с категориями:

  1. Sport Car [категория: car]
  2. Truck [категория: car]
  3. Coupe [категория: car]
  4. Bicycle [категория: other]

Чтобы получить список всех товаров категории «car» запрос должен выглядеть  так:

Cars = Product.objects.filter(category__name__name=’car’)

Теперь всё просто и понятно. А что если мы хотим отобразить автомобили на нескольких страницах сразу? Просто копируем этот код несколько раз.

Теперь нам помогут Manager и QuerySets.

Создаём файл managers.py.

from Django.DB import models

class ProductQuerySet(models.query.QuerySet):
  def get_by_category(self, category_name):
    return self.filter(category__name=category_name)

  def get_by_price(self):
    return self.order_by('price')

class ProductManager(models.Manager):

  def get_queryset(self):
    return ProductQuerySet(self.model, using=self._db)

  def get_by_category(self, category_name):
    return self.get_queryset().get_by_category(category_name)

  def get_by_price(self):
    return self.get_queryset().get_by_price()

Не забудем импортировать менеджер в модель и привязать его к переменной товара или менеджера.

from .managers import ProductManager

class Product(models.Model):
...

objects = ProductManager()

Теперь запрос выглядит вот так:

Car = Product.objects.get_by_category("cars").get_by_price()

Итог

Искушение свалить всё в кучу в толстой модели велико, и большинство из нас попадало в эту ошибку много раз. К счастью, у Django есть инструменты, чтобы оставить эту привычку в прошлом, нам остаётся только воспользоваться ими.

Manager и QuerySet отлично подходят на роль инструментов, необходимых для написания чистого и понятно кода.

Благодаря им нет нужды повторять фильтр в каждом view или, того хуже, менять их несколько раз, когда меняется его наименование. Выбирайте между повторениями частей кода и его качественным усложнением.

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх