В других и вовсе приводит к засорению базы мусором с других площадок и к ошибкам после «простейшего мержа».
Знакомых с такими ситуациями, критиков и знающих точно, что я изобрел велосипед — приглашаю под кат.
Подобный принцип хранения я встречал в нескольких компаниях, но по какой-то причине его описание в сети
Общая концепция проста как апельсин и принимает 2 установки:
1. База данных должна знать о том, какие патчи к ней применялись.
2. При создании связей в записях ни в коем случае не используются значения идентификаторов. (кроме полученных в вычислениях).
При соблюдении этих условий продукт должен работать на любой машине с объективно стабильным результатом. Да, может показаться, что второе условие не выполнимо, но если все базы создаются одним и тем же скриптом — расхождение идентификаторов будет уже аномалией.
Итерации в процессе создания схемы.
«Кто создал столбец?»
«Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
«Мы это уже 100 лет как не используем. Откуда оно здесь?»
Знакомо? Многие принимают как аксиому тот факт, что база данных существует «как есть». Это письмо Дяди Федора, которое не имеет своего автора. Но все так лишь отчасти. У каждого изменения, столбца и записи есть свой автор. Все изменения делаются на линии времени. Похоже на git / svn, etc.? Мы все активно пользуемся системами версионного контроля и прекрасно сдружились со всеми бонусами, которые дарит нам такой подход. Давайте попробуем применить его здесь.
Итак, переходим к практике и взглянем на несметное количество папок в проекте.

Для начала создадим таблицу с информацией о патчах. Здесь мы запомним какие патчи у нас отработали (name + type) и с каким результатом (result)
CREATE TABLE IF NOT EXISTS dc (
id INTEGER(11) AUTO_INCREMENT NOT NULL,
code VARCHAR(100) NOT NULL,
type VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
result VARCHAR(1) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
m_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
Чтобы все работало так, как мы хотим, добавим самый главный файл с оригинальным названием.
#!/usr/bin/env bash username="habr" password="habr" database="mydatabase" cd COMMON mysql --user ${username} --password=${password} -D${database} < dc.sql if [ $? -eq "1" ]; then exit $? fi echo '' echo '>>> TABLES' echo '' cd TABLE FILES=* for f in ${FILES} do scriptName=`expr "$f" : '\([a-z_]*\)'` var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.tables as t where t.TABLE_NAME='${scriptName}'" -s) if [ ${var} -ne '1' ]; then echo "Processing $f file..." mysql --user ${username} --password=${password} -D${database} < ${f} mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'CREATE TABLE', "$?")" if [ $? -ne 0 ]; then exit $? fi else echo '--- Skip '${f}' ---' fi done echo '' echo '>>> FOREIGN KEYS' echo '' cd ../F_KEY FILES=* for f in ${FILES} do scriptName=`expr "$f" : '\([a-z_A-Z]*\)'` var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.table_constraints as t where t.constraint_name='${scriptName}'" -s) if [ ${var} -ne '1' ]; then echo "Processing $f file..." mysql --user ${username} --password=${password} -D${database} < ${f} mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'CREATE FK', "$?")" if [ $? -ne 0 ]; then exit $? fi else echo '--- Skip '${f}' ---' fi done echo '' echo '' echo '>>> LOAD DATA SCRIPTS' echo '' cd ../DATA FILES=* for f in ${FILES} do scriptName=`expr "$f" : '\([a-z0-9]*\)'` var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from ${database}.dc as t where t.code='${f}' and result='0'" -s) if [ ${var} -ne '1' ]; then echo "Processing $f file..." mysql --user ${username} --password=${password} -D${database} < ${f} mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD DATA', "$?")" if [ $? -ne 0 ]; then exit $? fi else echo '--- Skip '${f}' ---' fi done echo '' echo '' echo '>>> LOAD TRIGGERS' echo '' cd ../TRIGGER FILES=* for f in ${FILES} do scriptName=`expr "$f" : '\([a-z_0-9]*\)'` var=$(mysql --user ${username} --password=${password} -D${database} <<< "select count(*) from information_schema.triggers as t where t.trigger_name='${f}'" -s) if [ ${var} -ne '1' ]; then echo "Processing $f file..." mysql --user ${username} --password=${password} -D${database} < ${f} mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD TRIGGER', "$?")" if [ $? -ne 0 ]; then exit $? fi else echo '--- Skip '${f}' ---' fi done echo '' exit $?
Принцип был взят как самый очевидный — имя патча оно же имя файла. Для триггеров, таблиц и внешних ключей проверяется наличие объектов с такими именами через information_schema. Если результат отрицательный — выполняется прогон соответствующего скрипта.
Никаких коллизий идентификаторов
Гораздо интереснее наполнение каталогов данными. Оно проводится через исполнения go-скриптов в каталоге /COMMON/DATA.
Факт отработки скрипта фиксируется в нашей таблице dc (data containers). Если все было ок — при следующем прогоне файл пропускаем.
Исполнение идет в алфавитном порядке, так что наиболее универсальным является использование timestamp в имени.
В самом простом исполнении это обычные INSERT-запросы со светлой надеждой на стабильность и предсказуемость автоинкремента.
-- добавление базовых ресурсов
INSERT INTO ds.reso_type (name, description) VALUES ('Пища', 'Основной ресурс, обеспечивающий жизнь');
INSERT INTO ds.reso_type (name, description) VALUES ('Дерево', 'Основной ресурс, обеспечивающий строительство');
INSERT INTO ds.reso_type (name, description) VALUES ('Камень', 'Основной ресурс, обеспечивающий строительство');
# noinspection SqlResolve
INSERT INTO ds.human_type (name, description) VALUES ("Чернь", "Кто все эти люди?");
А как же быть с более сложными данными? Используем вложенные запросы.
INSERT INTO reso_speed (resoId, popId, speed) VALUES (
(SELECT ht.id
FROM human_type ht
WHERE ht.name = 'Чернь'),
(SELECT rt.id
FROM reso_type rt
WHERE rt.name = 'Пища'),
30);
Или же напишем вспомогательные функции.
#создание tasktype
DELIMITER //
CREATE FUNCTION installTaskType(name VARCHAR(50), title VARCHAR(100), groupId INT(11), description VARCHAR(1000))
RETURNS int(11)
BEGIN
SELECT count(id) into @c from tasktype as t where t.name = name COLLATE 'utf8_unicode_ci';
if (@c = 0) THEN
insert into `tasktype`(`name`,`title`,`group_id`,`description`)
VALUES (name, title, groupId, description);
RETURN LAST_INSERT_ID();
END IF;
SELECT id INTO @taskTypeId FROM tasktype as t where t.name = name COLLATE 'utf8_unicode_ci';
RETURN @taskTypeId;
END //
DELIMITER ;
# создание новых taskgroup
DELIMITER //
CREATE FUNCTION installTaskGroup(name VARCHAR(255))
RETURNS INT(11)
BEGIN
SELECT count(id) into @c FROM taskgroup as t where t.name = name COLLATE 'utf8_unicode_ci';
if (@c = 0) THEN
INSERT INTO taskgroup (`name`) VALUES (name);
RETURN LAST_INSERT_ID();
END IF;
SELECT id INTO @groupId FROM taskgroup as t WHERE t.name = name COLLATE 'utf8_unicode_ci';
RETURN @groupId;
END //
DELIMITER ;
В go-скрипт пишем:
SELECT installTaskGroup('TEST_GROUP') into @groupId;
SELECT installTaskType('TEST_TASK', 'Это тестовая задача отправки статьи на HABR', @groupId, '');
Итак, мы получили подход, который позволяет выработать единую систему управления базой данных на примере git + mysql.
В качестве профита получили:
— авторство + в некоторых случаях привязка задач таск-трекера;
— последовательность в накате данных;
— новый проект на jenkins;
— спокойные нервы.
Искренне благодарю всех дочитавших за внимание. Приветствую и жду критику). Согласен, что продукт местами получился сырой, но для домашних нужд подходит отлично.
Тапками сильно не закидывайте — это первая попытка опубликоваться на Хабре (девушкам можно).
Комментарии (51)
sshikov
02.01.2017 15:24+3Не знаю, что вы в сети искали, но принципы построения подобных продуктов точно никто от вас не прячет.
Можно например взглянуть на liquibase, или на flyway (это лишь два первых пришедших в голову, и далеко не единственные), где подобные (и еще кое-какие другие полезные) концепции давно реализованы. Идее хранить список примененных патчей в самой базе — наверное сто лет в обед.
И кстати, вынужден вас огорчить — на ваши же три вопроса:
«Кто создал столбец?»
«Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
«Мы это уже 100 лет как не используем. Откуда оно здесь?»
данный велосипед ответов все равно не дает. Во всяком случае — простых и полных ответов.gunya
02.01.2017 19:35+1> «Кто создал столбец?»
> «Что здесь должно храниться? Откуда эти числа? Пишите хотя бы комментарии!»
> «Мы это уже 100 лет как не используем. Откуда оно здесь?»
Семь бед — один git blame.
Если по commit message непонятно, зачем и откуда появилась миграция БД — это должно было быть выявлено на этапе code review.sshikov
02.01.2017 19:45+3Чтобы понять, «кто создал столбец» — для начала надо понять, какой скрипт это содержал. Причем «это» может содержать как create table, так и alter table, в разных вариантах — и тут важно понимать, какой из них был применен последним.
Blame на этот вопрос не отвечает, точнее отвечает не на этот. Blame это второй этап — когда мы знаем скрипт, мы можем узнать автора. А первый этап тут как раз не раскрыт.
Вопрос же «что тут хранится» вообще к blame никаким боком по-моему не относится.sshikov
02.01.2017 19:55На самом деле полноценный ответ на поставленные вопросы — это история изменений объекта базы (обычно колонки или таблицы). Которая содержит все примененные ранее скрипты миграции, повлиявшие на объект.
И в общем случае эта задача полноценно не решается ни одним из известных мне фреймворков, по крайней мере теми из них, где можно писать миграции самостоятельно (и следовательно, применять в их коде произвольный SQL), что не позволяет фреймворку в принципе проанализировать, что же на самом деле миграция делает.
gunya
02.01.2017 20:03Обычно найти в миграциях нужный столбец / таблицу не так сложно — есть grep / поиск по проекту.
В идеале, конечно, нужно в комментарии к таблице/колонке ставить комментарий с ID миграции — но не уверен, что все СУБД это поддерживают и не уверен, что есть системы управления миграциями, которые это умеют.sshikov
02.01.2017 21:44Вы забываете, что не все миграции могли быть применены к конкретной базе в данный момент времени. Это как минимум одна причина, почему просто grep недостаточно. Вторая — это порядок применения потенциально многих alter table, о котором grep тоже нам мало что скажет.
gunya
02.01.2017 22:18Для этого очень помогает числовой идентификатор миграции и convention для имен файлов:
— 001_create_users_table.sql
— 002_create_posts_table.sql
— 003_add_author_to_posts.sql
Есть засада — при работе в параллельных ветках могут появиться две миграции с одним id — но это элементарно проверяется во время CIsshikov
03.01.2017 11:11И как числовой идентификатор поможет мне узнать, какие миграции реально были применены к базе, и какие из них относятся к интересущей колонке? Ваши id — это то что вы планировали применить, но вовсе не текущее состояние базы.
И кстати, выявить дубли id легко — а вот исправить может быть не так-то просто.
Что до конвенций… ну вот вам типичная миграция в моем проекте:
— найти constraint (некоторые из них были созданы давно, и соглашениям об именовании не соответствуют), как правило это PK или FK, и имя сгенерировано автоматически.
— drop constraint по найденному ранее имени
— drop computed column
— alter table alter column
— re-create constraint
— re-create computed column
Это все одна достаточно типовая сделанная вручную миграция. Всего-лишь изменение типа колонки, на которую завязаны либо constraint, либо другие вычисляемые колонки. Почему так? А потому что MS SQL, например, иначе не умеет, даже в enterprise edition. Если у вас база большая, то все может быть еще намного более грустно.
Наименование вида add_author_to_posts ничего нам в данном случае не даст, потому что оно отражает бизнес смысл миграции, а какие конкретно объекты базы она затронула — тут не говорится.
Т.е. по хорошему, механизм миграций должен бы уметь как минимум группировать операции DDL, которые умеет текущая база, и понимать, что вот эти вот пять или десять изменений — это часть одного большого изменения с точки зрения бизнеса. И еще понимать, какие конкретно объекты примененный конкретный скрипт затронул, и что он с ними сделал. А это совсем не просто, и один очевидный случай уже озвучивали — отличить переименование колонки от drop + create это задачка вовсе не для grep, тут даже анализ SQL не всегда может помочь.gunya
03.01.2017 15:14> И как числовой идентификатор поможет мне узнать, какие миграции реально были применены к базе, и какие из них относятся к интересущей колонке? Ваши id — это то что вы планировали применить, но вовсе не текущее состояние базы.
Любой тул для миграций хранит в БД идентификатор последней примененной миграции. Если же тул умеет применять миграции в разном порядке (alembic) — он же и умеет выводить текущее состояние базы.
> Т.е. по хорошему, механизм миграций должен бы уметь как минимум группировать операции DDL, которые умеет текущая база, и понимать, что вот эти вот пять или десять изменений — это часть одного большого изменения с точки зрения бизнеса. И еще понимать, какие конкретно объекты примененный конкретный скрипт затронул, и что он с ними сделал. А это совсем не просто, и один очевидный случай уже озвучивали — отличить переименование колонки от drop + create это задачка вовсе не для grep, тут даже анализ SQL не всегда может помочь.
Поэтому для этого есть программист, который умеет парсить SQL и комментарии.sshikov
03.01.2017 21:25>Поэтому для этого есть программист, который умеет парсить SQL и комментарии.
Ну так о том и речь. К сожалению, с пониманием даже синтаксиса SQL для более чем одной базы у многих инструментов большие проблемы. С пониманием метаданных из базы впрочем тоже. Т.е. например, вполне себе коммерческий SQLDBX не понимает некоторые специфичные для для MS SQL вещи.
gunya
03.01.2017 15:18С другой стороны, если вот очень хочется комбайн, который умеет в самые спорные хотелки — есть Liquibase. Он умеет по каждому изменению документацию генерировать, например.
sshikov
03.01.2017 21:21Ну, я его уже упоминал, по-моему в первом же комментарии. Но для моих нетривиальных задач его уже не хватает, довольно-таки часто.
Вообще в идеале я бы предпочел наверное иметь инструмент, который хранил бы модель в виде скажем ER, сам бы следил за ее версионированием, умел показывать структурные различия между версиями модели, а скрипты для модификации базы генерировал бы сам, с учетом того, что это за база (т.е. Oracle или скажем MS SQL), и какая версия модели данных там на сегодня установлена.
И чтобы инструмент сам бы понимал, какие из модификаций внесенных в ER-модель, в какие скрипты выливаются, и помнил бы историю для конкретной базы, и общую историю модели.
Но я таких идеальных инструментов не видел. Для тех что видел — как правило типичны проблемы с версионированием. Возможно они есть и стоят больших денег, поэтому никто из работодателей их не покупает.ggrnd0
03.01.2017 23:49+1Примерно похож EntityFramework.
Но до идеального инструменту ему далеко.
А в Enterprise подобного софта должно быть навалом на любой вкус.
Сомневаюсь что есть инструмент умеющий работать с разделением/объединением таблиц.
hardex
02.01.2017 16:15+6То, что вы выбрали путь разработчика — похвально, но просить к своей статье лучшего отношения из-за того, что вы девушка — это уже сексизм.
alexey-m-ukolov
02.01.2017 16:26+5Судя по статье и логину, автор — мужчина и он зачем-то разрешает девушкам кидаться в него тапками. Странно, одним словом.
k12th
02.01.2017 16:41+5Любит человек, чтобы девушки в него тапками кидались, чего ж такого, это же по обоюдному согласию и никому вреда не приносит:D
conf
02.01.2017 16:38+1Как уже отметили, это реализовано во всех крупных фреймворках, например, вот так в Ruby on Rails: http://rusrails.ru/rails-database-migrations. Ищите по ключевым словам «database migrations».
alprk
02.01.2017 16:52Миграции это конечно замечательно, а пробовал кто-нибудь организовать проект таким образом чтобы миграции были артефактом сборки? Т.е. например есть у меня проект, в котором есть директории tables, views и пр. Мы вносим изменения в код создания одной из таблиц, затем запускаем сборку
make migration
и получаем в папке migrations код обновления схемы до новой версии.andreymal
02.01.2017 17:03+2В общем случае полностью автоматическое создание такого кода невозможно (без логики не понять, удалили один столбец и создали другой или просто переименовали столбец), но как минимум у Django есть
django-admin makemigrations
aikixd
02.01.2017 18:08В VS есть тип проекта для баз данных и набор инструментов для миграций. Вместе с контролем версий, основную работу можно сделать мышкой.
greabock
02.01.2017 18:23Ну, для примера, на php в `doctrine-orm` и `propel` (первая это datamapper, а вторая это active record), можно делать diff на основе схемы модели сущностей, которая может быть описана в отдельном xml/yml файле, или в аннотацияx к самим сущностям.
gunya
02.01.2017 19:32У python есть alembic, который делает интроспекцию текущей БД, смотрит на модели ORM, делает сравнение и генерирует миграцию.
Естественно, он может отловить не все, например, переименования таблиц и индексов надо дописывать вручную. Но 80% простых миграций генерируются автоматически, но всегда нужно проверять, какие миграции получаются на выходе.
mayorovp
02.01.2017 19:33Первая проблема тут в том, что создание миграций должно быть stateful, а не stateless. Миграция зависит не от текущих исходников — а от разности текущих исходников и прошлой версии. Это выбивается из идеологии
make
. Создание миграции больше похоже на git commit, чем на make.
Вторая проблема — в том, что автоматически можно делать только простейшие миграции. Любое крупное изменение структуры БД обычно требует ручной донастройки.
alprk
10.01.2017 15:12согласен, действительно с идеологией make расходится, возможно будет более здраво если предполагать не создание цепочки миграций и одной миграции для приведения конкретной базы в состояние, описанное кодом?
alprk
10.01.2017 15:14применительно того как получить миграцию, можно использовать что-то подобное http://www.apgdiff.com/, просто получать разницу между временными БД
ohotNik_alex
10.01.2017 15:44creates output with DDL statements that can be used to update old database schema to new one
даже на уровне только схемы часто возникают коллизии. как минимум большинство подобных инструментов (за данный конкретный не скажу) работают исключительно на добавление.
а есть еще данные, которые нужно как-то мержить между площадками.
ggrnd0
02.01.2017 22:31В ECM7.Migrator есть поддержка nant/msbuild. А так же есть консольная утилита и возможность запустить миграции прямо из кода.
Hibernate/NHibernate умеют мигрировать БД сами.
mayorovp
02.01.2017 22:42Это другое. Там есть механизм для выполнения миграций при билде — а речь шла о генерации миграций.
Danik-ik
05.01.2017 09:35А я все миграции пишу сразу патчами, и выводить патчи из разницы состояний не приходится. Но у меня строго последовательное версионирование. Впрочем, до меня, до введения версионирования, были в патчах сентенции типа "если нет столбца добавить столбец" и т.п. и просто было две обновляемых очереди патчей — ежедневная, относительно свежая, при запуске приложения, и по запросу (практически — повторение всех патчей, каждый при этом проверяют необходимость применения самих себя). И всё работало и не ломалось.
D_T
02.01.2017 16:56+5В самом простом исполнении это обычные INSERT-запросы со светлой надеждой на стабильность и предсказуемость автоинкремента.
Зачем таким таблицам автоинкремент если надо одинаковый ID во всех копиях БД? Если это таблицы с метаданными, которые могут меняться только очередным скриптом обновления, то тут просто надо убрать автоинкремент и явно задавать значение ID в INSERT-запросе.ohotNik_alex
05.01.2017 17:08для работы в разных ветках при участии нескольких разработчиков. при завязке на идентификатор можно при мерже выяснить, что id занят.
andreymal
06.01.2017 02:37Тогда просто изменить id да смержить, не?
ohotNik_alex
09.01.2017 11:06если ветка отводилась на месяц — два (такое при крупных переработках случается часто) — это чревато большим количеством ручных операций. всегда можно что-то где-то недоглядеть. опасно. особенно если плохо налажено тестирование.
andreymal
09.01.2017 11:11Надо налаживать тестирование) А большое количество ручных операций на всего лишь смену айдишников по-моему намекает, что что-то ещё тоже не налажено
gunya
02.01.2017 19:52+1Написать скрипт на bash — это конечно, хорошо, но только вы не проверяете, что миграция вообще завершилась, не говоря об обработке ошибок:
mysql --user ${username} --password=${password} -D${database} < ${f} # Explosion mysql --user ${username} --password=${password} -D${database}<<<"INSERT INTO dc (code, type, result) values ('${f}', 'LOAD TRIGGER', "$?")" # OK if [ $? -ne 0 ]; then exit $? fi
Более того, что произойдет, если миграция применилась частично? Что произойдет, если скрипт по какой бы то ни было причине упадет в середине применения миграции (ребут сервера, потеря сети, что угодно)? Что произойдет, когда необходимо произвести даунгрейд базы?
Пожалуйста, прекратите изобретать велосипед и поищите инструмент для миграций на вашем ЯП, их миллион самых разных. Если по какой-либо причине инструмента для миграций не сделали — есть много разных утилит, которые можно использовать независимо от ЯП проекта.
При этом:
— Все миграции обернуты в транзакции, следовательно миграции атомарны. К сожалению, в mysql нет транзакций для изменения схем данных, но это можно обойти. Миграции данных будут атомарны везде.
— У этих тулов есть community — вы перестаете быть незаменимым (и можете спокойно ходить в отпуск), плюс вы избавитесь от overhead на поддержание своей утилиты для миграций.
— Вы получите не только тот функционал, который есть сейчас, но и как минимум получите возможность rollback. В случае с mattes/migrate можно даже переиспользовать те SQL-скрипты, которые есть у вас.ohotNik_alex
05.01.2017 17:21-1да, вы правы. тут у меня логическая ошибка. отслеживается результат второго выражения. подпилю.
за ссылки огромнейшее спасибо — ознакомлюсь то ли это.
атомарность миграции вещь полезная, но не обязательная. предложенный принцип работы таков, что все отрабатывает как с бусинками. нанизываются команды одна за другой. если встретим проблему — работа прекратится и продолжится со скрипта, который не прошел. хотя в случае обрыва связи вопрос. надо проверить опытом.
даунгрейд в текущей реализации не предусмотрен. хотя для себя я всегда держу rollback скрипты к каждому изменению (где это возможно, разумеется).
Danik-ik
05.01.2017 09:42При создании связей в записях ни в коем случае не используются значения идентификаторов. (кроме полученных в вычислениях)
Что имеется в виду и почему?
baldrs_asgaardson
05.01.2017 17:03Почитайте про миграции и репликацию, первое поможет с отслеживание изменений, второе с расхождением состояния баз на серверах если они у вас одни и те же.
miksir
05.01.2017 17:03Использование подобных миграций никак не исключает следованию принципов неразрушающих изменений (не удаляем столбцы, не переименовываем, не меняем тип данных и т.п.). Даже если приложение односерверное, при деплое такого кода мы получим момент, когда код ожидает поле, а в базе его уже (или еще) нет, что черевато фатальными ошибками. И эта проблема может становится еще более актуальной, когда у нас много нод приложений с одной базой.
Это можно было бы попробовать решить разделяя миграции на две части — созидательную (добавление), исполняемую до деплоя кода, и разрушительную (удаление), исполняемую после деплоя кода. Но мантейнить такое сложновато, да и все-равно придется учитывать неразрушающие правила для разделения таких миграций. Гораздо проще просто помечать ненужные колонки как deprecated и переодически их чистить.
ohotNik_alex
05.01.2017 17:05этот принцип работает для всех многомодульных приложений. так что вы тут абсолютно правы. но в статье нет завязки, что все изменения в базе должны быть немедленно отражены в коде. обратную совместимость никто не отменял.
ggrnd0
05.01.2017 20:18Еще можно откладывать удаление колонок на несколько миграций позже.
Например, удалять ненужные колонки при обновлении мажорной версии программы.
Подобное обновление все равно сопровождается созданием бекапа и остановкой сервиса.
Так что удалять можно невозбранно.miksir
05.01.2017 21:08Именно на несколько миграций позже это и откладывается. Главное, что бы разработчики не забывали помечать такие элементы схемы как deprecated. Или же были подготовлены сответствующие скрипты синхронизации со схемой приложения (например, в случае использования ORM). Такие изменения прекрасно делаются и без остановки сервиса, равно как и бакапы.
Pinsky
05.01.2017 22:34-1Есть золотое слово «апрув». Все изменения в продовой БД проводить только с получения апрува и фиксировать каждое из них и ответственного.
greabock
Это называется «миграции схемы бд». Есть куча различных реализаций на куче разных языков.