Когда я только начинал свой путь в программировании, у меня почему-то устоялось мнение, что хранимые процедуры и триггеры в базах данных — это нечто из разряда легаси, пережитки прошлого, и использовать их в современных системах, грубо говоря, моветон. Сегодня я поймал себя на мысли, что моё восприятие этих вещей кардинально изменилось, особенно в части триггеров. И поводом для этого стал очень интересный кейс.
В моей базе данных хранится огромное количество элементов, и у каждого из них — бесчисленное множество свойств. Мне нужно при каждой выборке списка находить и группировать одинаковые сущности. Но чтобы понять, идентичны ли два элемента, необходимо сджойнить все связанные таблицы, проверить все атрибуты, теги, значения. В итоге получался монструозный запрос с кучей джойнов, который работал непозволительно долго и просто не отвечал требованиям системы по скорости.
Решением казалось очевидное: вычислять специальный хэш на основе всех свойств и связанных данных и хранить его рядом. Но тут возникла новая проблема — поддержка этого хэша в актуальном состоянии. Сначала я пошёл по привычному пути: начал искать все места в коде, где происходит изменение объекта или его атрибутов. Я написал целую реактивную логику с кучей обработчиков, условий и проверок. Это была настоящая боль: логика плодилась в разных концах приложения, давала ложные срабатывания, а главное — я постоянно боялся упустить какое-нибудь место, где свойство меняется. Ведь достаточно было забыть обновить хэш в одном методе, и вся система становилась неконсистентной.
Чем дальше, тем больше я осознавал, что это какой-то костыльный и ненадёжный путь. Тогда я начал присматриваться к триггерам. Искусственный интеллект, у которого я искал совета, активно отговаривал: предлагал использовать Hibernate-события, листнеры и прочие ORM-инструменты. Но, изучив их, я понял, что там та же самая проблема — нужно перечислять все точки изменений и держать их в голове.
В итоге я рискнул и попросил нейросеть нагенерировать мне миграции с триггерами под Liquibase. Она написала несколько триггеров, которые перекрывали все возможные изменения в базе, затрагивающие нужные атрибуты. Выглядело это лаконично, а главное — работало безоговорочно. Теперь, когда я добавляю новый функционал, мне не нужно искать в коде места для новых обработчиков. База данных берёт это на себя. Неважно, как именно пришло изменение — через приложение, ручной запрос в консоли или через любую другую утилиту, — триггер срабатывает всегда.
Я настроил всё так, что при любом таком событии в специальную служебную таблицу падает запись о том, какой объект изменился. А основное приложение просто периодически мониторит эту таблицу и пересчитывает хэши для изменённых сущностей. Получилось невероятно красивое, стабильное и элегантное решение, которое наконец-то принесло мне спокойствие.
Говорят, триггеры — это зло. А я вот сижу и думаю: по-моему, это круто.