Бізнес-логіка у базі даних за допомогою SchemaKeeper
Мета цієї статті – на прикладі бібліотеки schema-keeper показати інструменти, які дозволяють суттєво полегшити процес розробки баз даних у рамках PHP-проектів, які використовують СУБД PostgreSQL.
Інформація з цієї статті, в першу чергу, буде корисна розробникам, які максимально хочуть використовувати можливості PostgreSQL, але стикаються з проблемами супроводу бізнес-логіки, винесеної в БД.
Стаття не описуватиме переваги чи недоліки зберігання бізнес-логіки у базі даних. Передбачається, що вибір уже зроблено читачем.
Будуть розглянуті такі питання:
Як зберігати дамп структури БД у системі контролю версій (далі за текстом — VCS)
Як відстежувати зміни у структурі БД після збереження дампи
Як переносити зміни в структурі БД на інші оточення без конфліктів та гігантських файлів міграцій
Як налагодити процес паралельної роботи над проектом кількох розробників
Як безпечно деплоїти більше змін у структурі БД на production-оточення
SchemaKeeper заточений під роботу з процедурами, написаними мовою PL/pgSQL. Тестування коїться з іншими мовами не проводилося, відповідно використання то, можливо настільки ефективно, чи неможливо.
В якому вигляді зберігати дамп структури БД у VCS
Бібліотека schema-keeper надає функцію saveDumpяка зберігає структуру всіх об'єктів з БД у вигляді окремих текстових файлів. На виході створюється директорія, що містить структуру БД, розбиту на згруповані файли, які легко додати VCS.
Розглянемо перетворення об'єктів з БД на файли на декількох прикладах:
Зміст файлів є текстове уявлення структури конкретного об'єкта БД. Наприклад, для збережених процедур вмістом файлу буде повне визначення процедури, що починається з блоку CREATE OR REPLACE FUNCTION.
Як видно з таблиці вище, шлях до файлу зберігає в собі інформацію про тип, схему та назву об'єкта. Такий підхід полегшує навігацію по дампі та code review змін до БД.
Розширення .sql для файлів з вихідним кодом збережених процедур вибрано для того, щоб IDE автоматично надавали інструменти взаємодії з БД при відкритті файлу.
Як відстежувати зміни у структурі БД після збереження дампи
Зберігши дамп поточної структури БД в VCS, ми отримуємо можливість перевірити чи вносилися зміни до структури бази після створення дампа. У бібліотеці schema-keeper для виявлення змін структури БД передбачено функцію verifyDumpяка без побічних ефектів повертає інформацію про відмінності.
Альтернативний спосіб перевірки – повторно викликати функцію saveDump, вказавши ту ж директорію, і перевірити у VCS наявність змін. Так як всі об'єкти з БД збережені в окремих файлах, то VCS покаже об'єкти, що тільки змінилися.
Головний мінус цього способу - необхідність перезапису файлів, щоб побачити зміни.
Як переносити зміни в структурі БД на інші оточення без конфліктів та гігантських файлів міграцій
Завдяки функції deployDump вихідний код збережених процедур може правитися так само як і звичайний вихідний код програми. Можна додавати/видаляти нові рядки в коді збережених процедур і відразу ж надсилати зміни в систему контролю версій, або створювати/видаляти збережені процедури шляхом створення/видалення відповідних файлів у директорії з дампом.
Наприклад, для створення нової процедури, що зберігається в схемі public достатньо створити новий файл із розширенням .sql у директорії public/functions, помістити в нього вихідний код процедури, що зберігається, включаючи блок CREATE OR REPLACE FUNCTION, потім викликати функцію deployDump. Аналогічно відбувається зміна і видалення процедури, що зберігається. Таким чином, код одночасно потрапляє і до VCS, і до бази даних.
Якщо у вихідному коді будь-якої збереженої процедури з'явиться помилка, або невідповідність між назвами файлу і процедури, що зберігається, то deployDump не виконається, відобразивши текст помилки. Розугодження процедур, що зберігаються між дампом і поточною БД неможливе при використанні deployDump.
При створенні нової процедури, що зберігається, немає необхідності вручну вводити правильну назву файлу. Достатньо, щоб у файлу було розширення .sql. Після виклику deployDump текст помилки міститиме правильну назву, яку можна використовувати для перейменування файлу.
deployDump дозволяє змінювати параметри функції або типу, що повертається без додаткових дій, у той час як при класичному підході довелося було б
спочатку виконати DROP FUNCTION, а тільки потім CREATE OR REPLACE FUNCTION.
На жаль, існують деякі ситуації, коли deployDump не може автоматично застосувати зміни. Наприклад, якщо видаляється функція тригера, яка використовується хоча б одним тригером. Такі ситуації вирішуються за допомогою файлів міграцій вручну.
Якщо за перенесення змін у процедурах, що зберігаються, відповідає сам schema-keeper, то перенесення інших змін у структурі необхідно використовувати файли міграцій. Наприклад, гарною бібліотекою для роботи з міграціями є doctrine/migrations.
Міграції повинні застосовуватись до запуску deployDump. Це дозволяє вносити всі зміни до структури і вирішувати проблемні ситуації, щоб зміни в процедурах, що зберігаються, згодом перенеслися без проблем.
Докладніше роботу з міграціями буде описано в наступних розділах.
Як налагодити процес паралельної роботи над проектом кількох розробників
Необхідно створити скрипт повної ініціалізації БД, який запускатиметься розробником на своїй робочій машині, приводячи структуру локальної БД у відповідність із збереженим у VCS дампом. Найпростіше розділити ініціалізацію локальної БД на 3 кроки:
Імпорт файлу з базовою структурою, який називатиметься, наприклад, base.sql
Застосування міграцій
виклик deployDump
base.sql — це відправна точка, поверх якої застосовуються міграції та виконується deployDump, Тобто base.sql + миграции + deployDump = актуальная структура БД. Сформувати такий файл можна за допомогою утиліти pg_dump. Використовується base.sql виключно при ініціалізації бази даних із нуля.
Назвемо скрипт повної ініціалізації БД refresh.sh. Робочий процес може виглядати так:
Розробник запускає у своєму оточенні refresh.sh та отримує актуальну структуру БД
Розробник починає роботу над поставленим завданням, модифікуючи локальну БД під потреби нового функціоналу (ALTER TABLE ... ADD COLUMN і т.д)
Після виконання завдання розробник викликає функцію saveDump, щоб зафіксувати у VCS зміни, зроблені у БД
Розробник повторно запускає refresh.sh, Потім verifyDump, який тепер показує список змін для включення до міграції
Розробник переносить усі зміни структури у файл міграції, запускає ще раз refresh.sh и verifyDump, і, якщо міграція складена коректно, verifyDump покаже відсутність відмінностей між локальною БД та збереженим дампом
Описаний вище процес сумісний із принципами gitflow. Кожна гілка VCS буде містити свою версію дампа, і при злиття гілок відбуватиметься злиття дампів. Найчастіше після злиття не потрібно робити ніяких додаткових дій, але якщо в різних гілках вносилися зміни, наприклад, в одну й ту саму таблицю, може виникнути конфлікт.
Розглянемо конфліктну ситуацію з прикладу: є гілка розвивати, від якої відгалужені дві гілки: особливість1 и особливість2, які не мають конфліктів з розвиватиале мають конфлікти між собою. Стоїть завдання виконати злиття обох гілок у розвивати. Для такого випадку рекомендується спочатку виконати злиття однієї з гілок у розвивати, а потім злиття розвивати в гілку, що залишилася, дозволивши при цьому конфлікти в гілці, що залишилася, після чого виконати злиття останньої гілки в розвивати. На етапі вирішення конфліктів можливо доведеться виправити файл міграції в останній гілці, щоб він відповідав фінальному дампу, що включив результати злиття.
Як безпечно деплоїти більше змін у структурі БД на production-оточення
Завдяки наявності у VCS дампа актуальної структури БД з'являється можливість перевірити production-базу на точну відповідність необхідної структури. Це гарантує, що на production-базу успішно перенеслися зміни, які задумували розробники.
Так як DDL в PostgreSQL є транзакційним, рекомендується дотримуватися наступного порядку деплою, щоб у разі непередбаченої помилки «безболісно» виконати ROLLBACK:
Розпочати транзакцію
У транзакції виконати усі міграції
У цій же транзакції виконати deployDump
Не завершуючи транзакцію, виконати verifyDump. Якщо помилок немає, виконати COMMIT. Якщо помилки є, виконати ROLLBACK
Ці кроки досить легко вбудовуються в існуючі підходи до деплою додатків, у тому числі zero-downtime.
Висновок
Завдяки вищеописаним методам можна вичавлювати максимум продуктивності з PHP + PostgreSQL проектів, жертвуючи при цьому відносно невеликою кількістю зручності розробки в порівнянні з реалізацією всієї бізнес-логіки в основному коді програми. Більш того, обробка даних у PL/pgSQL часто виглядає прозоріше і вимагає меншої кількості коду, ніж той самий функціонал, написаний на PHP.