1. Dump nebo migrační skript

Když je projekt malý, vystačí si obvykle s jednoduchým „dumpem“ databáze. Jeho vytvoření je díky programům jako mysqldump, pg_dump nebo adminer opravdu jednoduché. Stačí jeden příkaz nebo kliknutí a hned je na světě soubor obsahující vše potřebné. Procesu, který přenese celou novou databázi na nové prostředí se říká „deployment“.

Jakmile je však projekt trochu větší, dělá na něm více lidí nebo používá různé moduly, které si vytvářejí vlastní databázové struktury, stane se „dump“ nepřehledný. Navíc se tím zbytečně zvětšuje objem kódu projektu, i když větší část „dumpu“ je totožná s předchozí verzí. Hodilo by se tedy ukládat pouze změny, které byly v databázi provedeny. Měl by tedy vzniknout soubor, který bude obsahovat jeden či více databázových příkazů, které provedou příslušné změny. Jelikož se může verze nejen povyšovat („upgrade“), ale také ponižovat („downgrade“), hodí se mít také někde uložené databázové příkazy potřebné pro vrácení změn do předchozího stavu. Souborům, které obsahují tyto změnové příkazy se říká „migrační skripty”. Díky tomuto přístupu se snadněji analyzuje obsah změn mezi jednotlivými „verzemi“ databáze, což se může hodit mimo jiné i při hledání chyby. Zároveň se zmenší objem kódu projektu. Nasazení potřebných změn na nové prostředí, nazývané v případě vystavování změn „migrace“, proběhne výrazně rychleji. Pro všechny výše popsané výhody se jistě vyplatí zvážit zvýšení časové náročnosti při vytváření souboru popisujícího aktuální stav databáze.

2. Problémy při verzování databáze

Verzování databáze se může zdát na první pohled jednoduché, ale nese s sebou několik dílčích problémů. Jejich řešení je obvykle jednoduché, ale je dobré o nich vědět.

2.1. Stav migrace

Když existoval jen „dump“ databáze, stačilo při stažení nové verze smazat aktuální databázi a nahrát „dump“ a vše bylo jak mělo. Jakmile jsou však změny rozděleny do dílčích souborů, je potřeba vědět které změny již vystaveny byly a které ne. S tím souvisí i potřeba programátora zjistit aktuální stav migrace, jestli je vše vystaveno, nebo se má něco vystavit.

Tento problém se řeší jednoduše tím, že existuje existuje seznam dostupných a seznam nainstalovaných migrací, které se vzájemně porovnají. Seznam dostupných migračních skriptů vznikne tak, že se vypíše seznam všech souborů ze složek obsahujících migrační skripty. Jelikož však složky mohou obsahovat i jiné soubory (například soubor .htaccess blokující přístup do složky z internetového prohlížeče), je potřeba pamatovat na správný formát pro název souboru při vytváření seznamu. Seznam již nainstalovaných migrací není možné jednoduše „vygenerovat“ a proto se musí udržovat samostatně. Jedna z možností je vytvoření samostatné databázové tabulky, která obsahuje příslušný seznam. Druhou variantou je uchování seznamu v souboru. První varianta má výhodu, že „dump“ databáze obsahuje rovněž informaci, jaký stav databáze obsahuje. Při kontrole stavu migrace je však potřeba kontaktovat databázi, což může být za určitých okolností nevhodné. Varianta uchování seznamu v souboru nevyžaduje pro kontrolu stavu mít přímý přístup k databázi. Může však dojít ke změně stavu databáze jiným kolegou a rázem se stane lokální ukazatel stavu neaktuálním, což ale není možné zjistit. Souborový přístup přináší více problémů, ale může se hodit například když je potřeba udržovat verzi databáze bez zásahu do struktury databáze.

2.2. Přenositelnost migračních skriptů

Ve světě SQL databází existuje větší množství různých implementací. Některé jsou vzájemně kompatibilní (MySQL, MariaDb) s jinými (PostgreSQL, MsSQL, PlSQL, …) však není kompatibilita stoprocentní.

Je možné vytvářet migrační skripty pro každý typ databáze zvlášť, což rozhodně není pohodlné. Nebo je možné použít některý framework (Dibi, AdoDb, …) pro vytváření databázových dotazů, který vygeneruje příslušný dotaz pro daný typ databáze. Případně by mohl systém myslet na fakt, že může být použit jako součást menší komponenty nebo knihovny. Takový kód pak může někdo nasadit do projektu, který nedisponuje stejným databázovým verzovacím systémem. Mohl by proto umět vygenerovat příslušné migrační skripty přímo pro dané typy databáze.

2.3. Notifikace o změně

Když si programátor stáhne novou verzi projektu, měl by být informován o potřebě migrace databáze. Jelikož se stav databáze nemění s každou změnou v kódu projektu, je snadné zapomenout. V lepším případě aplikace zhavaruje a oznámí chybu, ze které se programátor při troše štěstí dozví, že má provést migraci. V horším případě se to projeví po delší době, kdy bude delší čas zkoumat, proč se aplikace nechová dle očekávání. Navíc může dojít ke změně databáze do nekonzistentního stavu. Napravení takového stavu je časově náročné a rovněž se o něm programátor nemusí dozvědět.

Stačí však aktuální stav migrace vypisovat ve vývojové konzoli (Nette debug bar, Symfony toolbar, …) případně informovat o potřebě migrace při každém stažení nové verze projektu z verzovacího systému za pomoci takzvaných „hooků“ (například Git hook), s tím však souvisí problém automatizovatelnosti.

2.4. Automatizovatelnost

Určitě je to příjemné, když má verzovací nástroj své grafické rozhraní, ve kterém je vše přehledně zobrazeno. Neméně důležitá je však i možnost provést jednotlivé úkony (kontrola stavu migrace, provedení migrace, vrácení změn, …) z příkazové řádky nebo ze zdrojového kódu. Jenom díky tomu je možné zcela automatizovat proces deploymentu projektu nebo vytvářet další rozšíření třetí stranou.

Aby toho bylo možné dosáhnout, je potřeba mít vhodně navržený zdrojový kód verzovacího systému podle architektury MVC. A pokud možno s tímto záměrem počítat už v průběhu vývoje.

2.5. Modularita projektu

Projekt na který se verzovací systém nasadí obvykle pracuje s mnoha moduly. Obvykle tyto moduly jsou produkty třetích stran a jejich vývoj tak probíhá odděleně. Občas však pracují s databází a bylo by proto vhodné aby na ně verzovací systém nezapomínal a uměl je zahrnout „pod svá křídla“.

Řešení takového problému však není vůbec jednoduché. V lepším případě projekt pracuje se stejným verzovacím systémem. V takovém případě by stačilo nastavit projeketový verzovací systém, aby monitoroval složku v modulu. V horším případě obsahuje pouze přírůstkové SQL skripty. V takovém případě by měl umět verzovací systém sledovat obsah této složky a umožňovat alespoň povýšení („upgrade“) pokud by rovnou neměl umět vygenerování opačného migračního skriptu.

3. Dostupné implementace

Existuje několik různých technologií, které řeší tuto problematiku více či méně dobře. Vzájemně jsou však velice rozdílné. Některé z nich nejsou příliš známé, ačkoliv si svoji pozornost jistě zaslouží.

3.1. Doctrine

Ve světě jazyka PHP je projekt Doctrine asi nejznámější. Jedná se hlavně o ORM vrstvu, která mimo jiné umožňuje právě řešení databázových migrací.

Implementace Doctrine migration je zajímavá v tom, že umí porovnat aktuální stav databáze se stavem, jaký je předepsaný u konfigurace ORM entit a případně vygenerovat rozdílový migrační kód na jeden příkaz. Funkcionalita která rozhodně stojí za pozornost.

Daní za jinak opravdu dobře provedenou správu databázových verzí je, že se jedná o relativně malou část jinak velkého a komplexního projektu a její samostatné použití bez ORM vrstvy je přinejmenším komplikované. Pro komunikaci s databází využívá vlastní abstrakci DBAL. Stav migrace ukládá do tabulky v databázi. Notifikace o změně lze implementovat. Neřeší však problém modularity projektu.

Shrnutí: Rozhodně velice zajímavý počin vhodný pro většinu projektů jako komplexní řešení ORM vrstvy. Samostatné použití jako nástroj pro verzování databáze je komplikované.

3.2. DBV - Database version control

Projekt DBV je relativně mladý a ne příliš známý. Nabízí celkem přehlednou vizualizaci jednotlivých verzí databáze. Umožňuje snadno vygenerovat zálohu jednotlivých tabulek, nebo jejich nasazení do databáze.

Nedisponuje však ovládáním z příkazové řádky. Je tvořen jako jeden veliký projekt včetně vizualizační části, takže jeho použití součástí projektu (například jako ukazatel ve vývojovém panelu) je přinejmenším nehospodárné. Migrační skripty je nutné tvořit ručně. Práce s databází je přes nativní PDO vrstvu. Neřeší však problém přenositelnosti migračních skriptů. Je však možné vytvořit vlastní implementaci rozhraní DBV_Adapter_Interface, které jsou jednotlivé SQL kódy předány. Díky tomu je možné použít vlastní abstrakci s použitím frameworku (Dibi, AdoDb, …) a migrační skripty psát v pseudo SQL, který framework překonvertuje dle potřeby. Aktuální stav databáze uchovává v souboru. Neřeší však problém modularity projektu.

Shrnutí: Vlaštovka mezi verzovacími nástroji v PHP. Nevnucuje vlastní přístup k databázi. Pro použití jako součásti projektu neumožňuje „instalaci“ bez grafické nadstavby. Při troše práce však může být opravdu zajímavou alternativou.

3.3. Ruckusing migrations

Projekt Ruckusing migrations se veřejně hlásí k tomu, že vychází z konceptu databázového verzování v RoR. Disponuje skripty pro vygenerování kostry migračního skriptu (neplést s rozdílovým migračním kódem z projektu Doctrine). Disponuje rozhraním příkazové řádky. Nabízí sympatický framework pro sestavování jednotlivých SQL příkazů. Umožňuje rozdělení migrací na moduly.

Neřeší problém notifikace o změně, ale není problém vytvořit příslušné rozšíření. Stejně jako ostatní systémy neřeší možnost napojení modulu, který má jednotlivé verze přímo v přírůstkových SQL skriptech.

Shrnutí: Zatím asi nejpokročilejší nástroj pro verzování databáze v PHP, pokud projekt nechce pracovat s ORM vrstvou.

4. Generování migračních skriptů

Zatímco projekt Doctrine umožňuje generovat migrační skripty na základě rozdílu mezi aktuálním stavem databáze a předpisem u entit, ostatní verzovací systémy takový luxus nenabízí. Stojí však za pozornost projekt Adminer, který nabízí takzvaný „Alter export“, který porovná dvě databáze mezi sebou a vygeneruje rozdílové SQL skripty.

5. Závěr

Projekt Doctirne nabízí velice zajímavý přístup k práci s databází a pro většinu projektů, které však nepoužívají moduly které také potřebují pracovat s databází. Jakmile je však požadován modulárnější nebo NotORM přístup, nebo se vyvíjí jednoduchá komponenta či knihovna pracující s databází, neexistuje dosud uspokojivé řešení pro všechny výše zmíněné problémy.