Совет дня №29
Когда речь заходит о бизнес-логике в базе, большинство людей теряют лицо. Начинаются крики, словно им намерены причинить увечье. А между тем логика в базе – хорошая вещь, нужно лишь правильно к ней подойти.
Возьмем за аксиому следующее: никто не учит базы данных. Все учат Питоны-Джавы, почему-то забывая: если работаете с базой, нужно знать ее тоже. Однако все делается на ORM, а база – черный ящик. Редкий разработчик интересуется, какие запросы в нее идут. Еще реже – попадают ли запросы в индекс, каков план и так далее.
Прикладной программист мыслит объектами. Когда он пишет на Питоне, в его распоряжении строки, числа и их комбинации – объекты. На ORM он выдает примерно такой код:
user = User.get_by_id(user_id)
if user is None:
return None
profiles = Profile.get_by_ref('user_id', user.id)
profile = profile.first()
if not profile:
return None
Если вынудить программиста перенести этот код в базу, он напишет что-то такое:
create function get_user_by_id(id integer)
returns user
return select * from users where ? = id
create function get_profiles_by_user_id(user_id integer)
returns profile
return select * from profiles where ? = user_id limit 1
Далее та же петрушка: получить кортеж пользователя, проверить на NULL; получить профиль, проверить на NULL; далее что-то сделать. Это калька с императивного языка, и в SQL она выглядит уродливо. SQL предназначен для другого; он недостаточно выразителен для императивных нужд, поэтому писать на нем в таком стиле – сущее издевательство.
Решение простое: пишите на SQL реляционно! Это язык для операций над отношениями (таблицами): проекция, объединение, соединение, полу- и анти-соединения и другие их виды. В SQL единица операции – не строка или число, а таблица. Бизнес-логика на SQL сводится к тому, что есть наборы данных и нужно выполнить операции над ними. Например, соединить два набора, что-то отсечь, пересечь с другим набором, дополнить третьим, записать результат в таблицу. Дубликаты записей либо обновить, либо игнорировать.
Все это прекрасно ложится на SQL, и в прошлых советах были примеры. Напишите подобное на ORM – и кода станет больше, не говоря уж о том, что не будет малейшего понимания, что происходит с базой.
Говорят: логика в базе уродлива. Увы, когда я вижу, как соединяют данные в приложении (бесконечные экраны кода) вместо запроса на три строчки, то понимаю — вон он, уродливый код.
Разумеется, иногда один и тот же код повторяется, и его выносят в функции. Например, в фирме особые требования к форматированию денег. Чтобы не копировать одно и тоже, пишут чистую функцию:
create function format_mln_eur(cents int)
returns text
immutable strict parallel safe
return ...
Далее вызывают ее в запросах. Такой подход в порядке вещей, потому что он предотвращает копирование кода.
Другой пример – в финансах временной интервал хранится как четверка чисел: число
лет, месяцев, недель и дней. Пишется функция tenor_to_interval, которая
приводит четверку к типу interval и наоборот:
create function tenor_to_interval(y int, m int, w int, d int)
returns interval
immutable strict parallel safe
return ...
Но не нужно заворачивать в функции CRUD-операции! Именно тут кроется ошибка питонистов и джавистов. SQL – декларативный язык, и все операции носят описательный характер. Если писать процедуру на каждый чих, вы загоните себя в яму. В базе мыслят и программируют реляционно – то есть оперируют отношениями, а не строками и числами. Императивный подход оставьте за дверью, будьте так добры.
Как развить реляционное мышление? Точно также, как и во всем другом: заниматься им.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter