
Привет, Хабр! Хочу поделиться самодельной питонской библиотекой (ссылка на GitHub в конце статьи), существенно упрощающей взаимодествие с базами данных.
На настоящий момент популярны два основных способа организовать работу с базой данных:
-
Воспользоваться «низкоуровневым» интерфейсом СУБД. Например, если у нас база данных в здесь я уже поворчал о том, что задача «Object-Relational Mapping» решения не имеет, и что-то с этим нужно делать. Предложенный тогда вариант просто взять и посадить себя на без-ORM-ную диету был явно непрактичным, поэтому в серьёзных проектах ничего другого не оставалось, как продолжать есть этот кактус, но при этом не прекращать попыток придумать альтернативу.
Постепенно выкристаллизовалась идея NoORM («Not only ORM», по аналогии с «Not only SQL»):
-
Мы не объявляем священную войну ORM-ам, а гармонично с ними сосуществуем, давая альтернатианые решения там, где ORM-ы традиционно приносят головную боль.
-
Мы не пытаемся сделать плюс ещё один тысяче первый, но на этот раз самый окончательно правильный ORM. Чётко осознаём, что задача «ORM» не имеет решения.
-
Технология должна быть применима не только на маленьких поделках и DIY-проектах, но и на больших и сложных бэкендах.
-
Фокус на улучшение developer experience. Все эти удобняшки современных IDE – атодополнение, подсветка синтаксиса, переход к объявлению – всё это очень сладко, вносит комфорт и повышает продуктивность.
-
Технология должна быть подобна истинному дао – делать правильные вещи простыми и естественными, сопротивляясь использованию корявых и неэффективных решений.
Откуда берутся сложности с SQL
Назовём клиентом программу, общающуюся с сервером СУБД. Проигнорируем тот факт, что этот наш клиент сам, возможно, для кого-то является сервером. Взаимодействие с СУБД построено таким образом, что сервер в качестве запроса принимает, по сути, текст программы (SQL это тоже язык программирования, даже не пытайтесь спорить), исполняет её, и отдаёт назад результат. Структура результата зависит от текста запроса, а также от схемы базы данных.

Весьма странный API, не правда ли? Нетипичный. Какой-то текст на вход, какая-то табличка (или число, или просто ничего) на выход – вот и вся схема, вот и весь контракт. Этим, конечно, достигается потрясающая функциональная гибкость, но с точки зрения кода клиента, написанного на «обычном» языке программирования, это сущий кошмар.

Какое-то невнятное «
Взаимодействие с БД через ORM можно схематично изобразить так:

Примечательно здесь то, что работа с базой данных идёт через персистентные объекты, являющиеся экземплярами «модельных» классов, описывающих структуру БД. Эти персистентные объекты умеют себя прочитать из базы и в неё себя записать. Они живут внутри открытой сессии. И ещё эти объекты умеют «лениво» дотягивать из базы связанные с ними другие персистентные объекты. Эти самые персистентные объекты – корень всех проблем:
-
По сути, это передача мутабельного объекта в другой процесс. Безобразно тупая затея. Мы запросили сущность «пользователь Вася» из базы данных в процесс своего бэкенда, и теперь где у нас теперь мастер-копия? Как мы их собираемся синхронизировать, в какой момент, и что собираемся делать с возможными коллизиями?
-
Что случается с живущими в сессии объектами когда сессия закрывается? Что если они продолжают быть нужны в логике приложения? Что если эта логика продолжает считать, что это по-прежнему нормальные объекты, принадлежащие живой сессии?
-
Невозможно найти единственно правильный баланс между
Для любого императивного (и тем более функционального) языка программирования самая естественная в мире вещь это функция, которую можно вызвать с известно какими параметрами, и которая вернёт результат известно какого типа. С точки зрения кода-потребителя программный интерфейс БД выглядит как-то так:

Что мы здесь видим:
-
У нас есть модуль
db_api, в котором есть модульusers, в котором есть функцияget_users. -
Эта функция принимает на вход соединение с базой данных и отдаёт список объектов
DbUser, у которых есть атрибутыemail,idиusername. -
Всё это замечательно дружит с удобняшками IDE,
У вас может возникнуть резонный вопрос: а не закончится ли это тем, что у нас в «db_api» будут сотни и тысячи каких-то маловразумительных датаклассов и функций, и ориентироваться в этом станет совсем невозможно? Честно скажу, у меня самого были такие опасения, когда в порядке эксперимента я взялся переводить с ORM на NoORM один приличного объёма сервис, который много и разнообразно общается с базой данных. Однако обошлось. Более того, стало значительно легче находить ответы на вопросы о том, как, где и для чего используются конкретные таблицы и поля базы данных. Стал проще рефакторинг. Избавившись от персистентных объектов, избавились от необходимости держать открытой сессию на протяжении всей обработки клиентского запроса. Плюс абсолютно предсказуемое поведение коммита – в базу пишется только то, что мы хотим в неё записать здесь и сейчас, и нет никаких персистентных объектов, которые по какой-то неясной причине тоже решили пристроиться к этому коммиту. Ну и, самое сладкое, все «N+1» стали видны как на ладони – если мы в потенциально длинном цикле вызываем какую-то функцию, в которую параметром передаём соединение с БД, мы же не просто так его туда передаём, а, очевидно, для того, чтобы сходить в БД столько раз, сколько раз прокрутится цикл.
NoORM + ORM = ♥
ORM это не только плохие капризные портящие нам кровь персистентные объекты, но и множество восхитительных удобств, от которых нет смысла отказываться:
-
Модель структуры базы данных. Можно держать структуру базы данных в голове, можно нарисовать её тушью на ватмане, можно в разбросанных по столу черновиках, можно в заметочках в ноушене или на страничках в конфлюенсе, а можно в питонском коде под гитом. Последний вариант мне кажется самым симпатичным.
-
Миграции. Они в любом случае боль, но ORM-мы умеют облегчать страдания. Глупо этим пренебрегать.
-
Я тут сказал много злых слов про персистентные объекты, но если их не пускать в «боевой» код, а использовать только для генерации тестовых данных, то там они чудо как хороши. По сути, едва объявив структуру данных, мы сразу забесплатно «из коробки» получаем для неё реализованный
Знаю, некоторым в тягость такой стиль написания SQL, но нельзя не признать, что у него есть свои преимущества, особенно на простых запросах.
Некосколько рекомендаций
По ходу «опытной эксплуатации» этой технологии выработались несколько рекомендаций, которых полезно придерживаться:
-
Не надо нагружать бизнес-логикой объекты, возвращаемые DB API. Пусть это будут просто датаклассы или даже namedtuple-s. Впрочем, никто не мешает реализовать несколько дополнительных свойств, если их вычисление в SQL-запросе по каким-то причинам затруднительно.
-
Объявление этих датаклассов – непосредственно перед функциями, которые их будут возвращать. Точно не в отдельном модуле. Между SQL-запросом и тем местом, где определяется структура его результата должно быть не далеко ходить. Идеально, если объявление датакласса вместе с SQL-запросом помещаются одновременно на один экран.
-
С именами этих датаклассов сильно упариваться не нужно, но удобно, когда они выделяются визуально – когда мы видим, что имя типа переменной начинается с «Db», мы сразу понимаем, что значение взялось из базы данных.
-
Удобно, когда выработано некоторое соглашение об именовании DB-API-функций. Мы используем префиксы «get_», «ins_», «upd_», «del_», «upsert_». Для функций, в которых принудительно отключается автокоммит, используем суффикс «_no_commit».
-
Когда DB-API-функций мало, их можно держать в одном модуле «db_api.py». Если их становится больше, распиливаем этот модуль по функциональным областям, например, «db_api/users.py», «db_api/orders.py», «db_api/warehause.py».
-
Если в монорепозитории живёт несколько подсистем, есть смысл иметь один общий модуль «db_api», но специфические вещи вынести в «db_api»-модули подсистем.
-
Если какая-то часть системы по своей сути является скопищем SQL-запросов (например, коллекция здесь.
P.S. «НоуОуАрЭм» – язык сломаешь, поэтому прижился вариант произношения /nuːrm/ – «нурм», «нурмализация», «сделаем сразу нурмально».
-
-
-
-
ссылка на оригинал статьи https://habr.com/ru/articles/818761/




Добавить комментарий