Flask SQLAlchemy Data Mapper vs Active Record Pattern

Я недавно начал работать над Flask и Flask-SQLAlchemy. Исходя из фона Django, я нашел Flask-SQLAlchmey довольно сложным. Я читал, что SQLAlchemy реализует шаблон Data Mapper, в то время как Django ORM основан на шаблоне Active Record.

Вот пример написанного кода, который реализует шаблон хранилища для доступа к базе данных.

Вот еще одна ссылка на комментарий С.Лотта (271 тыс. Репутации), который говорит, что ORM - это уровень доступа к данным, и он отделен от модели.

У меня такие вопросы:

  1. Можете ли вы привести пример практического использования в приведенном выше примере или собственный пример, в котором полезен шаблон Data mapper? Везде, где я читал, шаблон отображения данных полезен в сложной ситуации, но не видел примеров.
  2. Является ли использование шаблона репозиториев, как в приведенном выше случае, таким же, как использование шаблона отображения данных?
  3. Сторонники картографа данных пишут запросы на выборку в классе, отличном от модели, как в примере?
  4. Почему Question.query.filter_by(text = text).all() не лучше использовать, чем db.session.query(Question).filter(Question.text == text).all()?

Это не дубликат шаблона DataMapper vs ActiveRecord, потому что это просто говорит об определении, меня больше интересуют практические примеры.

Ответ 1

По пунктам.

1.

У меня есть устаревшая база данных, для которой я должен написать несколько утилит обработки данных. Использование шаблона Mapper без стиля ORM/ActiveRecord упростило для меня такие вещи, как запись запросов в ActiveRecord. Он работает с хорошими составными объектами, которые напоминают предложения SQL, защищенные от SQL-инъекций.

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

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

SQLAlchemy имеет многоуровневый дизайн, который позволяет вам получить доступ к нижнему уровню "mapper", даже если вы объявляете вещи на верхнем уровне ORM и обычно работаете на нем. Например, в Django это не так просто, если/когда все еще возможно.

2.

В этом примере "репозиторий" выглядит как уровень, построенный над "mapper". Репозиторий мог быть построен поверх простого DBAPI, но картограф делает несколько простых вещей, таких как более удобное связывание параметров, именованные кортежи для наборов результатов, и обертка над простым SQL с составными, повторно используемыми частями.

Преобразователь также обеспечивает определенную степень независимости базы данных. Например. SQL Server и Postgres имеют разные способы конкатенации строк; mapper предоставляет унифицированный интерфейс.

3.

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

Хорошей особенностью дизайна SQLAlchemy является то, что вы можете легко сохранять условия и целые предложения where и повторно использовать их в операторах select/update/delete.

4.

Question.query.filter_by(text = text).all() использует неявную транзакцию. db.session.query(Question).filter(Question.text == text).all() использует явную транзакцию.

Явные транзакции дают вам спокойствие с DML. Они важны с помощью select s, когда вы запрашиваете быстро меняющуюся базу данных и хотите, чтобы ваши несколько связанных select отображали одно и то же согласованное состояние.

Обычно я пишу тривиальную оболочку вокруг sessionmaker и пишу вещи так:

with my_database.transaction() as trans:
   records = trans.query(...)
   ...
   updated = trans.execute(...).rowcount
# Here the transaction commits if all went well.

Когда я определенно знаю, что DML не должен запускаться в этом блоке, я использую .readonly_transaction(), который всегда откат.

Во многих случаях неявная транзакция прекрасна. Django позволяет вам украсить метод с помощью @transaction.atomic и иметь полу-явное управление транзакциями, достаточное в 99% случаев. Но иногда вам нужна еще более тонкая гранулярность.

Ответ 2

Полностью согласен с приведенным выше ответом: да, SQLAlchemy Data Mapper шаблон действительно более гибкий и для сложных запросов он действительно более мощный, менее волшебный и более контролируемый.

Но в простых задачах, таких как CRUD, код SQLAlchemy становится слишком избыточным/избыточным/избыточным.

Например, чтобы просто создать какой-либо объект в простейшем контроллере "create", вам нужно что-то вроде этого:

user = User(name='Nick', surname='Nickson')
session.add(user)
session.flush()

В режиме активной записи ORM вам понадобится только одна строка.

Ну, для простых задач, некоторые из нас могут захотеть чего-то более простого. Я имею в виду, что было бы здорово иметь Active Record для SQLAlchemy.

Хорошие новости: я недавно создал пакет для этого (он также содержит другие полезные материалы).

Проверьте это: https://github.com/absent1706/sqlalchemy-mixins

Ответ 3

  1. Единственная причина, по которой я бы использовал Data Mapper вместо Active Record, это если у вас есть серьезные проблемы с масштабируемостью. Data Mapper поддерживает разделение объектов домена и логику доступа к базе данных, тогда как Active Records помещает логику доступа к базе данных в объект домена. Например, когда вы поднимаете экземпляр Flask, он будет подключаться к базе данных только по требованию, тогда как в Django он всегда будет подключаться к базе данных.

  2. Data Mapper изолирует объекты домена от логики доступа к базе данных, тогда как шаблон Repository представляет собой слой между объектами домена и Data Mapper. Это уровень выше, чем Data Mapper. Например, в шаблоне Data Mapper у вас будут прямые геттеры и сеттеры, а в шаблоне репозитория - геттеры и сеттеры, которые также могут содержать некоторую сложную бизнес-логику.

  3. Data Mapper отделен от класса модели. Только шаблон Active Record объединяет методы получения и установки в одном классе.

  4. Я какое-то время работал с SQLAlchemy и Django, и я определенно предпочитаю Django-подобные запросы. Для моих собственных проектов вероятность использования Flask + SQLAlchemy вместо Django практически равна нулю. Производительность и общность являются двумя наиболее решающими факторами при рассмотрении этих двух рамок.