Как заставить взаимодействие Django Manager и Model следовать принципу Open/Closed?

Я разрабатываю модели для своего приложения Django App и озабочен тем, как отделить логику вызывающего пользователя от реализации модели, чтобы будущие изменения самой модели не требовали изменений в кодовой базе ниже по течению. Короче говоря, придерживаюсь принципа открытости/закрытости (OCP).

Мне интересно, как лучше всего это реализовать, используя при этом лучшие возможности фреймворка Django.

Концептуально, на мой взгляд, имеет смысл сделать следующее:

from django.db import models

class FooManager(models.Manager):
    def is_active(self):
        return self.filter(is_active=True)

class Foo(models.Model):
    _bar = models.CharField(max_length=100, db_column="bar")
    is_active = models.BooleanField(default=True)

    objects = FooManager()

    @property
    def bar(self):
        return self._bar
    
    @bar.setter
    def bar(self, value):
        if not self._bar:
            self._bar = value
        else:
            #some setter logic
            pass
  1. Пользовательский менеджер для логики запросов Для каждой модели определяется пользовательский менеджер, который отвечает за обработку всевозможной логики фильтрации модели. Например, вместо вызова Foo.objects.filter(is_active=True), вызывающая сторона будет вызывать Foo.objects.is_active(). В будущем, если логика реализации is_active изменится, вызывающей стороне не нужно будет менять свою логику, сохраняя OCP.

  2. Инкапсуляция полей модели Для каждого поля модели определены @property геттеры и сеттеры, позволяющие изменять поля модели.

  3. Минимальная связь. Связь сохраняется только между Custom Manager и моделью.

Мои основные опасения по поводу такого подхода

Для пункта 1: Требование, чтобы вся логика фильтрации определялась как методы в пользовательском менеджере, потенциально может привести к десяткам методов в каждом менеджере (из-за комбинации полей), что затруднит управление.

По пункту 2: Хотя это удобно при вызове атрибута модели, например Foo.bar, базовое поле должно вызываться при использовании менеджера. Например, Foo.objects.filter(bar=x) не работает, нужно использовать Foo.objects.filter(_bar=x). Это требование подрывает смысл пункта 2, если не реализован пункт 1.

Вопросы

  1. Является ли этот подход хорошей практикой для достижения развязки в Django при соблюдении принципа открытости/закрытости?

  2. Существуют ли в экосистеме Django более эффективные или более стандартные подходы для развязки логики модели и логики вызывающей стороны без излишней сложности?

  3. Как опытные разработчики Django справляются с инкапсуляцией полей, гарантируя при этом, что запросы остаются интуитивно понятными и поддерживаемыми?

Спасибо за помощь!

The models are probably not the right layer. Indeed, you can make custom lookups [Django-doc] and custom model fields [Django-doc] to make the ORM language more «intuitive». To some extent, this is what Django does with for example a URLField model field [Django-doc]: behind the curtains, it is just a VARCHAR with extra logic for it.

Мы можем сделать пользовательский поиск для поля URL, чтобы извлечь схему, например:

from django.db.models import CharField, Transform, URLField, Value
from django.db.models.functions import StrIndex, Substr


@URLField.register_lookup
class SchemaTransform(Transform):
    lookup_name = 'schema'
    output_field = models.CharField()

    def as_sql(self, compiler, connection):
        return Substr(
            self.lhs, Value(1), StrIndex(self.lhs, Value(':')) - Value(1)
        ).as_sql(compiler, connection)

и, например, отфильтровать с помощью:

unsafe_urls = MyModel.objects.filter(url_field__schema='http')

При этом я бы поставил прагматизм выше разработки приложения, которое стремится полностью следовать принципам SOLID и GRASP: они должны быть скорее индикаторами того, что что-то не так: если для преодоления модификации чего-то небольшого в модели требуется много работы, то явно что-то не так с программным обеспечением. Но если заставить программное обеспечение на 100% следовать принципу Open/Close, то это, как правило, приводит лишь к огромному количеству дополнительной работы, которая в итоге почти никогда не окупается.

Вернуться на верх