Portál AbcLinuxu, 30. dubna 2025 09:09
Případně je tu Liquibase – dá se použít buď přímo jako hotový produkt, nebo pro inspiraci při psaní vlastního řešení.
Liquibase jsme používali mj. na projektu, kde se nasazovalo na více různých databázových systémů – přidává to tam určitou portabilitu a abstrakci. Když ty změny definuješ v XML a ne ve formě SQL skriptu pro specifický systém, tak pro tebe Liquibase vygeneruje SQL skripty pro jednotlivé systémy. Pokud by automatika nestačila, tak si tam vždycky můžeš udělat podmíněný blok, který se pustí jen na určitém DBMS.
To je jen jedna z mnoha výhod.
Na druhou stranu souhlasím, že je to poměrně komplexní řešení. Pokud by šlo o můj vlastní projekt, kde mám na všechno dost času, tak bych pravděpodobně šel taky cestou vlastních skriptů – je to tedy trochu NIH, ale zase minimální komplexita a řešení přesně dle mých potřeb – za cenu toho, že si s tím musím pohrát a nestačí stáhnout z internetu hotový nástroj a začít ho používat.
Jinak, co jsem viděl u zákazníků různé jejich upgradovací skripty, tak to bylo většinou slabší – napsané kdysi někým, kdo už tam není, a ostatní se na to bojí sáhnout… někdy se pak právě přecházelo na ten Liquibase.
Oboje má svoje. Jak jsem psal, pro vlastní projekt, o který se budu starat pořád já, spíš vlastní řešení. Ale někam do větší firmy, kde je pravděpodobná vyšší fluktuace lidí, bych doporučil spíš ten Liquibase. Ono už jen zdokumentovat to vlastní řešení, aby ho mohli používat ostatní a ne jen jeho autor, zabere dost času – a ten si přepočti na peníze.
up()
a down()
, kam si napíšeš ty SQL dotazy (ať už přímo, nebo tím udělátkem, co tam je na manipulaci schématem, nebo obojím) a do tabulky si píše, co už bylo provedeno. Laravel má cca totéž. Phinx je samostatný nástroj, který dělá to samé. Liquibase vypadá jako poněkud overengineered věc, ale tak nějak v hromadě zbytečností tam vykukuje ten samý koncept …
Tak to mluvíš zase spíš o ORM – ano, můžeš si napsat třídy a pak si pod nimi nechat automagicky vygenerovat tabulky… ale to mi nepřijde jako nejšťastnější cesta. Je to takové rychlé a špinavé řešení, někdy použitelné. Ale já přeci jen radši začínám od toho modelu – databázi beru jako něco trvalého, zatímco aplikace, knihovny a frameworky jsou pomíjivé – v čase se mění, nahrazují nebo jich může být současně nad jednou databází několik. Proto se nechci tolik vázat na konkrétní programovací jazyk nebo framework.
Pak je tu otázka rolí/lidí – kdo by ten datový model měl navrhovat a udržovat. Když se to děje ad-hoc v rámci běžného vývoje a implementace jednotlivých úkolů nebo dokonce oprav chyb, tak to nedopadá moc dobře. Někdy je na to vyhrazena separátní role. Nebo to dělá aspoň seniorní vývojář/architekt, který má blízko k relačním databázím. Případně když je část logiky v procedurálním SQL, tak se o ten model nejspíš budou starat lidé, kteří píší to procedurální SQL, protože k relačním databázím mají bližší vztah než běžný javista, phpčkář atd.
vždy je vyzkouším oproti různým předchozím verzím relačního schématuTo je něco, co by nikdy nemělo nastat. Máš mít lineární posloupnost změn (SQL skriptů), které jdou vždy z jedné definované verze do druhé. Takže ať se spustí migrace nad jakoukoliv verzí schematu databáze, tak se jen vyberou ty správné SQL skripty a ty krok po kroku projdou všemi verzemi až do té aktuální. Tedy každá změna pracuje jen s jedním předchozím stavem a vyprodukuje jeden následující stav. Pokud máš různé DB, tak budeš mít různé sady SQL skriptů. Pokud chceš přepínat, tak spustíš v cílové databázi migrace z nuly, čímž vytvoříš prázdnou databázi s aktuálním schematem, a pak budeš mít skript na export a import, či zálohu a obnovu. Pokud někde máš jiné kódovaní, jiný typ sloupce, nebo něco takového, tak tam prostě máš bordel a první co máš udělat je, že bordel uklidíš a databázi dostaneš do jednoho z definovaných stavů (verzí). Pokud je nějaká část schematu proměnlivá, tak má být zdokumentováno jak se může měnit a migrace s tím musí počítat. Lepší ale je mít pár zbytečných sloupečků pro nepoužívané funkce (v dané instalaci), než proměnlivé schema.
Takové ty plugin-specific nuance jsem v zásadě jen v tom, že synchronizované pluginy k zprávičkám přiřazují service-specific ID, ale každá zpráva má ID, jen jeho generátorem je vždy někdo jiný - tudíž sloupec je pro všechny společný.To je jeden textový sloupec dostatečné délky, např. varchar(255). To není v ničem problém.
Nejvíc těch plugin-specific věcí je právě v tabulce "Accounts", kde některé účtu mají jméno/heslo, některé ne, některé umí [...]Na to je nejlepší textový sloupec, kam se parametry serializují v JSON. Z akademického pohledu je to děsná prasárna, ale z praktického je to to nejlepší, co jde udělat. Na typické časté věci (např. uživatelské jméno a heslo) si tam necháš nullable sloupeček a na ten zbytek blob s JSON. Z pohledu databáze to bude sloupec trochu exotického typu a tobě to ušetří spousty potíží.
Obecně by modul (plug-in) měl záviset na jádře, ale nikoli jádro na modulu. Moduly by pak mělo jít psát a přidávat, aniž by se v jádře cokoli měnilo. Na to je potřeba tam mít připravená rozhraní. Co se týče dat, tak modul může mít vlastní schéma (pokud ho DB, např. PostgreSQL, podporuje) nebo v horším případě ten prefix tabulek. Případně může ukládat data do nějakých generických sloupečků třeba v XML (dá se to i indexovat, ale o ty indexy se pak musí někdo ručně starat). Nebo i do EAV – přestože to mnozí považují za „anti-pattern“, jsou případy, kdy je to funkční a smysluplné řešení.
Závislost směrem z jádra k modulu by neměla být ani na úrovni datového modelu. Cizí klíče by tam buď neměly být nebo by se mělo mazat kaskádovitě (modul přijde o svoje data, když se smaže nadřazená entita v jádře, ale to by nemělo vadit). Určitě by modul neměl narušovat funkci jádra tím, že si tam nasmolí nějaké svoje klíče nebo jiná integritní omezení a pak bude blokovat dříve běžně prováděné akce.
To je něco, co by nikdy nemělo nastat. Máš mít lineární posloupnost změn (SQL skriptů), které jdou vždy z jedné definované verze do druhé. Takže ať se spustí migrace nad jakoukoliv verzí schematu databáze, tak se jen vyberou ty správné SQL skripty a ty krok po kroku projdou všemi verzemi až do té aktuální.
Tohle funguje dobře u malých projektů, kde máš těch změn jen pár. Pokud jde o něco většího (spousty tabulek, pohledů, procedur, funkcí…) a s delší historií (každý měsíc se nasazuje řada změn), tak to přestává být použitelné. Kdyby sis tímto způsobem chtěl vytvořit třeba vývojové/testovací prostředí, tak bys musel pokaždé sjíždět inkrementy za posledních deset nebo víc let… To by ses jen tak nedočkal a bylo by to obrovsky neefektivní. Ideál je, když nové prostředí můžeš vytvořit během pár vteřin, pustit na něm nějaké testy, a pak to klidně celé zahodit.
Proto se pak dělají záchytné body (snapshoty), které obsahují kompletní stav k nějaké verzi – a inkrementálně se donasadí jen těch pár posledních změn.
Pak je dobré mít ověřené, že se na aktuální verzi dostaneš z libovolného záchytného bodu a i úplně od nuly tím, že projdeš celou historií – a výsledek bude vždy stejný.
Pak je dobré mít ověřené, že se na aktuální verzi dostaneš z libovolného záchytného bodu a i úplně od nuly tím, že projdeš celou historií – a výsledek bude vždy stejný.Toto je velmi podstatné. Testy, které ověří konzistenci snapshotů jsou v podstatě nutnost. Je pak záležitost politiky podpory starých verzí, kolik se toho bude testovat – např. jen od snapshotu předminulé major verze, nebo klidně celou historii u malé/mladé aplikace. Stále však platí, že snapshoty jsou jen optimalizace. Primární zdroj je posloupnost změn.
Muselo by to ale umět i generování "aktualizačních" SQLTo je právě ta komplikovaná věc. Nejlepší řešení je použít to, co v aplikaci tak jako tak používáš. Každá migrace pak bude třída, která má název a metodu na provedení změn (je rozumné jednotlivé migrace automaticky zabalit do transakcí). Při kompilaci si vygeneruješ jejich seznam. Migrační nástroj pak vypadá tak, že koukne, které migrace byly provedeny a provede ty, které nebyly (v definovaném pořadí). Potřebuješ na to vylistovat seznam migrací, udělat select do tabuky s provedenými migracemi a odečíst provedené od dostupných. To je celé. Nějakých 200 řádek kódu. Výhodou je, že migrace jsou plně integrované v aplikaci a nemusíš řešit externí nástroje. Při spuštění spustíš migrace a hotovo. Představa jednotného schématu a generování změn je sice lákavá, ale praxe ukazuje, že to za to nestojí. Když se podíváš na různá ORM, tak ty to dělají, ale také schovávají většinu z toho, co databáze umí, aby to tak nějak šlo smysluplně udělat. Navíc to pořád neřeší změny v datech. Osvědčilo se mi používat query builder. Když se nějaké dotazy vyskytují často, tak si uděláš metodu, která takový dotaz sestaví. Vedle častých selectů a insertů můžeš mít třeba i přidávání sloupečků. Když pak migrace je běžná (C++) třída, tak máš k dispozici ten samý query builder jako ve zbytku aplikace. Můžeš i zkoumat, zda něco bylo či nebylo provedeno (např. přidej sloupec, pokud chybí a tím uklízet bordel).
custom_data_1 ... custom_data_20Jo, dej tam custom_data_json (jak píši o nějaké to vlákno výše) a při selectu to deserializuj do
QHash<QString, QString>
, tedy hloupé key-value úložiště.
Pokud bys měl nutkání tam dávat další tabulku s EAV, tak se na to vybodni.
Navíc, to není asi OSS jestli se dobře dívám
To se bohužel trochu změnilo od posledně, co jsem to používal. Zdrojáky jsou pořád k dispozici (pod svobodnou licencí Apache 2.0), ale teď tě mnohem víc tlačí k tomu, aby sis koupil placenou verzi. Pro jistotu ty zdrojáky zálohuji.
přijde mi to i takové trošku jak tu někdo psal overengineered.
Tady to chce znát míru a proporce. Když budu mít velký projekt, kde už tak je hodně komplexních knihoven a frameworků, tak ten Liquibase už nepředstavuje významný nárůst komplexity. Ale zatáhnout Liquibase jako závislost do nějakého minimalistického softwaru – z toho bych neměl dobrý pocit.
Muselo by to ale umět i generování "aktualizačních" SQL
Příkazy končící na SQL, jako updateSQL, neprovádí změny v databázi, ale generují SQL skript. Někdy se to hodí, protože jsou prostředí, kde tě nenechají spouštět nějaký tvůj nástroj (Liquibase) nebo ti tam ani nedají přístup a jen řeknou, ať jim dáš SQL skript a že si ho pustí sami.
Nicméně pokud by nastaly nějaké komplikace nebo bylo potřeba změny vrátit zpět, tak si to pak člověk musí řešit sám. (jinak Liquibase má i příkaz rollback
, kterým jde změny vracet zpátky).
pro případ, kdy už nějaké struktury mám, ale potřebuji přidat sloupec, upravit typ sloupce atp. a to s důrazem na zachování dat - tohle by se mi zejména hodilo pro SQLite, kde není možno nedestruktivně přidávat sloupce, musí se vždy zkopírovat stávající tabulka.
Pokud už databáze existuje a dosud nebyla spravovaná přes Liquibase, tak k tomu je tu příkaz generateChangeLog, který ze stávající DB vygeneruje to XML – a na něj se pak dá navázat dalšími inkrementy.
Tiskni
Sdílej:
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.