Под впечатлением от недавнего обсуждения Liquibase с коллегами, и на основе ранее сложившегося впечатления о распространенном отношении к этому инструменту со стороны разработчиков, я решил написать о своем видении предназначения Liquibase, и ему подобных инструментов, в проектах разработки enterprise‑приложений с использованием баз данных.
Определение Liquibase: «Liquibase is an open‑source database‑independent library for tracking, managing and applying database schema changes», представление продукта на официальном сайте: «Accelerate Your Database Deployments», «CI/CD for the database», «Release software faster by bringing DevOps to the database with Liquibase».
Определение Flyway: «Flyway is an open‑source database‑migration tool», представление продукта на официальном сайте: «Increase reliability of deployments by versioning your database», «Database migrations made easy», «Made for CI/CD».
По всей видимости, первичное восприятие у большинства формируется такое: это инструменты для DevOps. Дополняет такое восприятие декларируемое предназначение: "Миграции баз данных", которое, как я считаю, неудачным образом распространяется дальше в тематических статьях как основное предназначение этих инструментов. Т.е. понимание сводится к тому, что если речь заходит о применении указанных инструментов, то предполагается наличие уже существующей, разработанной/разрабатываемой, базы данных, изменения из которой теперь будут замечательным образом переноситься с одного стенда на другой, разово или на регулярной основе.
Я же считаю, что эти инструменты - это, в первую очередь, инструменты разработчика, которые стоит применять разработчикам непосредственно в самом процессе разработки, причем с самого начала; а все остальные сценарии их применения - второстепенны и носят частный характер.
Попробуем разобраться, собственно, в чем исходная проблема, почему вообще появились такие инструменты, для чего вообще нужно в них разбираться, и почему "It is what it is".
Как известно, нельзя просто так взять и пересоздать базу данных, находящуюся в эксплуатации (в т.ч. ранее развернутую в общей тестовой среде с множеством разработчиков, работающих над одним проектом). Выдвигая это утверждение, для осторожности, ограничусь только реляционными базами данных, и уточню, что речь идет о структуре БД - DDL-инструкциях для таблиц, в общем случае.
![](https://habrastorage.org/getpro/habr/upload_files/94a/c15/e1d/94ac15e1d94c2fceb3226395d1935a17.jpg)
В «обычном» приложении структурные элементы проекта можно дополнить/изменить, после чего проект пересобирается и разворачивается на стенде, с замещением ранее развернутой копии приложения новой его версией.
// Версия 0.1
// /incredible-app/src/main/java/net/cuttingedge/SilverBulletSolution.java
package net.cuttingedge;
public class SilverBulletSolution {
int magicAttribute;
public static void main(String[] args) {
System.out.println("This is the initial version!");
}
}
// Версия 0.2
// /incredible-app/src/main/java/net/cuttingedge/SilverBulletSolution.java
package net.cuttingedge;
public class SilverBulletSolution {
int magicAttribute;
String yetAnotherMagicAttribute;
public static void main(String[] args) {
System.out.println("This is a breakthrough improvement!");
}
}
В случае с базой данных сделать также - не получится: дополненное/измененное определение главного структурного элемента проекта - таблицы, - просто так не замещается, приходится добавлять отдельные инструкции по изменению.
-- Версия 0.1
-- /incredible-app/src/main/database/cuttingedge/silver_bullet_solution.sql
create schema cuttingedge;
create table cuttingedge.silver_bullet_solution(
magic_attribute integer
);
-- Версия 0.2
-- /incredible-app/src/main/database/cuttingedge/01-silver_bullet_solution.sql
create schema if not exists cuttingedge;
create table if not exists cuttingedge.silver_bullet_solution(
magic_attribute integer
);
-- /incredible-app/src/main/database/cuttingedge/02-silver_bullet_solution-new_attribute.sql
alter table cuttingedge.silver_bullet_solution
add column yet_another_magic_attribute text
;
Хорошо, что есть, то есть.
Проект базы данных ведь может быть представлен в виде текстовых файлов - скриптов, разложенных по папкам и выстроенных в нужной последовательности, а значит, укладывается в репозиторий системы управления версиями (VCS) и может быть, в итоге, развернут из нужной ветки на нужный стенд в нужном состоянии. По этому пути и идут. Например, выстраивают систему сборки целевой БД с использованием Shell-скриптов и тех или иных дополнительных инструментов. Разработчики при этом разрабатывают скрипты отдельно, задействуя какой-нибудь удобный менеджер БД (database tool) и используя подготовленный стенд с развернутой СУБД, локальный и/или разделяемый, после чего составляют Pull/Merge Request и направляют его на Code Review. Итоговая консолидация проекта, после слияния изменений, автоматизированная в той или иной степени, дает конечный результат. Схема работы понятна и особых вопросов не вызывает. Все нормально, если все делается так, как задумано. Обнаруживаемые при этом, ранее неиспользованные, инструменты, такие как Liquibase и Flyway, воспринимаются часто как "yet another tool", которые, вероятно, более удобные и на которые стоит переходить, т.к. они получают статус стандарта де-факто для решения подобных задач.
И к своему удивлению, я неоднократно слышал от разработчиков, намеревающихся использовать Liquibase, примерно следующее: "Я буду продолжать разрабатывать свои скрипты так, как и раньше, в рамках понятной мне проектной модели. А для Liquibase нужно, чтобы скрипт развертывания как-то был сгенерирован в нужном формате, и, далее, выполнен стороной DevOps". В моем понимании, это равносильно такому высказыванию: "У меня есть папка программного проекта с текстовыми файлами. Нужно чтобы кто-то (не я, например, DevOps) произвел сборку проекта". Наверное, можно возразить, что разработчик же всегда будет направлять на развертывание корректные изменения. Но, в моем понимании, проблема сложности реальных enterprise-проектов находится на таком уровне, что не стоит полагаться на то, что ты помнишь или не помнишь, что и где ты сделал или не сделал. Уверенность в корректности производимых изменений строится на успешности их применения на текущем стенде? А стенд в каком состоянии? А какой это стенд? А когда и кто на этом или другом стенде производил развертывание, и какое? Наверное, гораздо лучше полагаться на автомат, который обеспечивает контроль корректности, часто неочевидный. И правильный путь - это полная сборка проекта целевой БД при каждом внесении в проект изменений, включающая развертывание на доступном стенде, т.е. когда этап развертывания неотделим от процесса разработки (развертывание репетируется) - само вырабатываемое разработчиком проектное решение включает развертывание.
Liquibase может и, на мой взгляд, должен использоваться как инструмент организации и сборки проекта разработки базы данных - это его главное предназначение. Структура проекта Liquibase изначально должна закладываться, как древовидная иерархия файлов формата Changelog. Генерация Changelog-файла из существующей БД применяется только на начальном этапе для старых проектов, которые переводятся в формат Liquibase, или если необходима какая-то разовая миграция. Проектный каталог, также, как и обычно, сохраняется в репозитории системы управления версиями (VCS). А под версионированием со стороны Liquibase понимается автоматически обеспечиваемый процесс применения тех изменений в рамках текущего обновления, которые необходимы к применению на конкретном стенде (в целевой БД Liquibase регистрирует примененные изменения в специальных служебных таблицах (Tracking Tables), что позволяет ему при следующем обновлении определить, какие изменения уже были применены, а какие - еще нет). При этом, нет строгой необходимости в применении отдельных веток в рамках VCS для разных стендов - одно и то же состояние проекта можно выносить на разные стенды, с различным текущим состоянием, причем неоднократно - вопрос разделения состояний проекта будет определяться только необходимостью разделения стадий для доработок. Прийти к желаемой модели замещения доработанной таблицы на основе ее локализованного в одном блоке кода определении, Liquibase, конечно же, не позволит - по прошествии времени, определение целевой таблицы все равно будет представлено в проекте как начальная инструкция create и серия последующих инструкций alter (есть возможность, по истечении некоторого времени, после обновления всех задействованных в процессе стендов, "причесать" определение таблицы, объединив все дополнения к таблице в единой инструкции create, но это не совсем тривиальная процедура). Тем не менее, такой инструментарий и так позволяет заметно "приподнять" проектную модель для БД на пути к высокоуровневой разработке, основанной на принципах Agile с CI/CD.
Еще больше приблизиться к желаемой высокоуровневой проектной модели для БД позволяет подход, основанный на применении метаданных. Он позволяет перейти на декларативное описание структуры БД, с привнесением в проект объектно-ориентированной парадигмы (частично). В результате, инструкции Liquibase будут сведены к простым инструкциям по внесению изменений в таблицы метаданных; проект по разработке целевой БД, в части структуры, превратится в набор деклараций (это не касается кода целевых представлений, функций и процедур, для которых обобщенные элементы могут быть применены лишь частично). Конечно, все это обеспечивается специальной платформой, которая должна быть разработана, если ее нет. Но главная мотивирующая идея такого подхода - это обеспечение возможности концентрации над логическим проектным решением для прикладного разработчика, и переключение от работы над экземпляром целевой системы к работе над генератором экземпляров - для системного разработчика. И это материал для отдельной статьи.