V anketě o používaných způsobech verzovaní datového modelu se sešlo 23 hlasů. Díky za ně. Jasným vítězem jsou "skripty v svn", na druhém místě potom vlastní nástroj pro verzování datového modelu.
Zájemcům o řešení, které může snížit režii spojenou s verzováním a správou modelu, doporučuji svůj přehledový článek o nástroji LiquiBase.
Zobrazují se příspěvky se štítkemdatabáze. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemdatabáze. Zobrazit všechny příspěvky
středa 3. března 2010
sobota 20. února 2010
Unit testy nad in-memory databází
V poslední době jsem několikrát narazil na otázku testování dao tříd a volby databáze, nad kterou testy běží. Překvapilo mě, jak kontroverzní otázka to je a jak vyhraněné názory na ni existují. Testujete dao třídy? Píšete unit testy pro cílovou databázi nebo používáte nějakou in-memory variantu jako HSQL? Mají testy s jinou než cílovou databází smysl? Na tyto a související otázky se pokusí odpovědět tento článek.
Testování dao tříd
Někdy mohou být zásadní náklady na vytvoření a správu testovacího prostředí. U běžně používaných databází (MySql, Oracle, PostgreSql, ...) většinou nepředstavuje další udržovaná instance velký problém. Existují ale výjimky. Setkal jsem se například s prostředím, kdy byl v produkci použitý Informix a kde vytvoření "další" instance nebylo snadné. Požadavek na novou instanci neznamenal konečnou, ale byl dost nákladný na to, aby ospravedlnil hledání jiných možností.
Jsou testy dao tříd nad databází unit testy nebo spíš testy integrační? Odpověď na tuto otázku nemusí ležet v kódu testů, ale v náhledu na ně. Pokud na test nad in-memory databází nahlížíme jako na unit test, můžeme in-memory databázi chápat jako stub, který prostě nahrazuje jednu závislost a dovoluje testu běžet v "izolaci". To je chápání, které je mi blízké. Pokud ale takový přístup nazveme integračním testem, nabízí se otázka jakou má takový test vypovídající hodnotu? O funkčnosti dao třídy toho může test hovořit hodně, ale o integraci s produkční databází velmi málo. Proto testy dao tříd nad in-memory databází za integrační nepovažuji.
Závěr
Testujete na svých projektech dao třídy? Jakou databázi k testům používáte? Cítíte rozpor mezi použitím cílové databáze něbo nějaké in-memory varianty? Podělte se o své názory a zkušenosti v diskuzi pod článkem. Pokud přemýšlíte o tom, jak vytvořit a spravovat datový model pro testy, můžete se podívat na můj článek o nástroji LiquiBase. Pokud řešíte inicializaci dat pro testy, můžete některé odpovědi nalézt v článku o inicializaci databázových dat.
Testování dao tříd
Předně bych chtěl říct, že rozhodně jsem zastáncem psaní testů dao tříd. Ať už na projektu používáte ORM, přímý přístup přes JDBC nebo objektovou databázi, dao třídy obsahují množství kódu, jehož funkčnost je třeba ověřit. Správnost mapování, skládání dotazů, zpracování result setů, řešení krajních stavů nebo třeba odolnost vůči sql injection mohou být faktory, na které se typický test zaměří. Možnost použití jiné než cílové databázi (tou myslím databázi - Oracle, MySql, ... - která bude použitá v produkčním prostředí) zásadně závisí na tom, jestli se v kódu dao tříd vyskytuje konkrétní sql dialekt. Pokud se dotazujeme přímo přes JDBC a používáme pokročilé vlastnosti databáze, těžko můžeme dao třídy nechat běžet nad něčím jiným. Jaké jsou tedy důvody, které mohou někoho vést k psaní unit testů nad in-memory databází?
Proč vůbec in-memory databázi používat?
Někdy mohou být zásadní náklady na vytvoření a správu testovacího prostředí. U běžně používaných databází (MySql, Oracle, PostgreSql, ...) většinou nepředstavuje další udržovaná instance velký problém. Existují ale výjimky. Setkal jsem se například s prostředím, kdy byl v produkci použitý Informix a kde vytvoření "další" instance nebylo snadné. Požadavek na novou instanci neznamenal konečnou, ale byl dost nákladný na to, aby ospravedlnil hledání jiných možností.
Dalším důvodem jsou (ne)závislosti, které unit testy potřebují ke svému běhu. V některých prostředích je výhodnější, když unit testy potřebují ke svému běhu jenom svůj a testovaný kód, nikoliv závislosti na dalších systémech včetně databáze. S tím úzce souvisí i opakovatelnost testů, resp. opakovatelnost očekávaného výsledku. Testy nad cílovou databází mohou být citlivější na změny a chyby v testovacích datech, pokud už data před testem obsahují.
Často diskutovaným důvodem je také rychlost spouštění testů. Zastánci testů nad in-memory databází někdy vyzdvihují jejich rychlost, kdy testy nad nimi běží údajně výrazně rychleji než nad cílovou databází. Já tento argument neuznávám. V případě in-memory databáze musíme vedle běhu samotného testu do výsledku započítat určitě vytvoření datového modelu, případně inicializaci dat a to jsou akce, které mohou něco stát.
Unit nebo integrační?
Jsou testy dao tříd nad databází unit testy nebo spíš testy integrační? Odpověď na tuto otázku nemusí ležet v kódu testů, ale v náhledu na ně. Pokud na test nad in-memory databází nahlížíme jako na unit test, můžeme in-memory databázi chápat jako stub, který prostě nahrazuje jednu závislost a dovoluje testu běžet v "izolaci". To je chápání, které je mi blízké. Pokud ale takový přístup nazveme integračním testem, nabízí se otázka jakou má takový test vypovídající hodnotu? O funkčnosti dao třídy toho může test hovořit hodně, ale o integraci s produkční databází velmi málo. Proto testy dao tříd nad in-memory databází za integrační nepovažuji.
Závěr
Jsem zastáncem testů nad in-memory databází tam, kde to má smysl. Dobrými důvody jsou pro mě náklady na správu testovacího prostředí a nezávislost unit testů na dalších systémech.
Testujete na svých projektech dao třídy? Jakou databázi k testům používáte? Cítíte rozpor mezi použitím cílové databáze něbo nějaké in-memory varianty? Podělte se o své názory a zkušenosti v diskuzi pod článkem. Pokud přemýšlíte o tom, jak vytvořit a spravovat datový model pro testy, můžete se podívat na můj článek o nástroji LiquiBase. Pokud řešíte inicializaci dat pro testy, můžete některé odpovědi nalézt v článku o inicializaci databázových dat.
neděle 31. ledna 2010
Verzování datového modelu a LiquiBase
Přidávání tabulek, sloupců, integritních omezení, přejmenovávání a štěpení tabulek, konfigurace vývojového, testovacího a produkčního prostředí... Je mnoho požadavků, které mají vliv na podobu datového modelu. V tomto blogu zkusím popsat běžné činnosti týkající se správy relačního datového modelu a stručně představit nástroj LiquiBase. Článek je volným pokračováním předchozího příspěvku Inicializace databázových dat prostředky Javy.
Různá prostředí kladou na správu datového modelu různé nároky - přizpůsobitelnost, nezávislost na konkrétní databázi, použitelnost pro testy nebo verzování. Krátce jsem je popsal v předchozím příspěvku. Jaké jsou běžné činnosti, které se správou datového modelu souvisí? Samozřejmě potřebujeme evidovat jakékoliv změny formou nějakého changelogu. V jistou chvíli je nutné změny z changelogu aplikovat na nějaký existující model - provést jeho aktualizaci. Někdy může být nutné naopak změny odstranit - provést jejich rollback. Při kvapném rozvoji datového modelu přijde vhod možnost porovnat dva modely. Při zavádění verzování je užitečná možnost reverzního vytvoření changelogu z existující databáze. A v neposlední řadě je dobré mít změny dobře zdokumentované.
Právě na tyto činnosti se zaměřili tvůrci nástroje LiquiBase. Než se k němu ale dostanu, popíšu napřed alternativní možnosti. Změny datového modelu se tradičně dají organizovat pomocí sql skriptů verzovaných nějakým scm nástrojem (jako je třeba Subversion). Pro každou změnu datového modelu se připraví samostatný sql skript, který se prostě vloží do scm systému. Jeho revize potom dovolí vybrat správnou množinu skriptů, které někdo ručně spustí. Některé firmy nedají dopustit na nástroje, které si vyvinuly svépomocí a které dovolí správu modelu alespoň částečně automatizovat. I správa datového modelu je doménou, na které se dá vydělávat a kterou řeší komerční nástroje.
LiquiBase
Na nových projektech se snažím používat LiquiBase. Je to nástroj, který mě zaujal svým úzkým zaměřením (neřeší nic, co by se správou datového modelu nesouviselo), snadnou přístupností (začlenění do vývojového procesu je jednoduché a přímočaré) a perfektní dokumentací. Nebudu suplovat dokumentaci LiquiBase a tutoriály přístupné z jeho webu, chci nástroj jenom stručně představit.
Changelog
Klíčový konceptem je changelog - xml soubor obsahující změny, zvané changesety. Změnou je třeba založení tabulky, přidání sloupce nebo vytvoření indexu. Každá změna je identifikovaná dvojicí (id,author). Changelog představuje historii změn datového modelu, ale neříká nic o tom, jak vypadají konkrétní instance modelu. Proto LiquiBase potřebuje, aby každý spravovaný mode obsahoval tabulku DatabaseChangeLog(id,author,filename,dateexecuted,...), která eviduje changesety aplikované na model.
Příklad changelogu:
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.6"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.6
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.6.xsd">
<changeSet id="1" author="bob">
<createTable tableName="department">
<column name="id" type="int">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="active" type="boolean" defaultValue="1"/>
</createTable>
</changeSet>
</databaseChangeLog>
Podporované jsou tyto databáze:
- MySQL, PostgreSQL, Oracle, MS-SQL, Sybase Enterprise, Sybase Anywhere, DB2, Apache Derby, HSQL, H2, InterSystems Caché, Firebird, MaxDB / SAPDB, SQLite.
A tyto předdefinované operace (refactorings):
- Structural Refactorings - Add Column, Rename Column, Modify Column, Drop Column, Alter Sequence, Create Table, Rename Table, Drop Table, Create View, Rename View, Drop View, Merge Columns, Create Stored Procedure
- Data Quality Refactorings - Add Lookup Table, Add Not-Null Constraint, Remove Not-Null Constraint, Add Unique Constraint, Drop Unique Constraint, Create Sequence, Drop Sequence, Add Auto-Increment, Add Default Value, Drop Default Value
- Referential Integrity Refactorings - Add Foreign Key Constraint, Drop Foreign Key Constraint, Add Primary Key Constraint, Drop Primary Key Constraint
- Architectural Refactorings - Create Index, Drop Index
- a další
LiquiBase je možné používat i na nepodporované databázi. Pokud pro ni nefungují některé z předdefinovaných operací, může changeset jednoduše obsahovat specifický sql kód.
Changelog můžeme udržovat ručně, vytvořit reverzně z existujícího modelu nebo použít podporu v IDE. Dobře funguje plugin pro IntelliJ IDEA a deklarovaná je i podpora pro Eclipse. Plugin pro Eclipse se mi ale nepodařilo zprovoznit.
Update a rollback
Základní příkazy pro aplikaci, resp. mazání změn, jsou update a rollback. Spouští se z příkazové řádky, z mavenu, z antu nebo třeba z IDE. Příkaz update aplikuje všechny nové changesety na existující model. To, jestli je daný changeset nový nebo již aplikovaný, pozná podle záznamů v tabulce DatabaseChangeLog. Příkaz rollback bere jako vstupní parametr identifikaci changesetu a všechny novější změny z modelu odstraní (pokud je to možné).
Dalšími užitečnými funkcemi jsou například
- Diff - porovnání dvou databází a vytvoření changelogu.
- DBDoc - vytvoří html manuál s popisem všech změn datového modelu.
- nebo SQL výstup, který changlog převede na SQL skript.
Alternativy
migrate4j nebo dbmigrate jsou nástroje s podobným zaměřením jako LiquiBase. Vývojáři Ruby asi budou znát Ruby Migrations.
Shrnutí
Článek chtěl popsat požadavky na evidenci změn datového modelu a ukázat, že s LiquiBase lze s malým úsilím změny organizovat lépe než jako "skripty v svn". Jak změny datového modelu organizujete vy? Máte bližší zkušenosti s nástrojem LiquiBase nebo jiným podobným? Podělte se o své názory v diskuzi pod článkem a zapojte se do hlasování.
středa 4. listopadu 2009
Inicializace databázových dat prostředky Javy
Končí inicializace databáze vytvořením tabulek a integritních omezení? Jak ji naplnit nezbytnými daty? Téměř každá aplikace potřebuje k práci nějaké ty číselníky, role, přístupová práva, uživatele a další data. V tomto blogu zkusím popsat, co může inicializace dat v relační databázi znamenat a jak pro ni výhodně použít prostředky Javy.
Článek je psán s ohledem na běžný projekt, který pracuje nad relačními daty (ne nad objektovou databází, ne nad geografickými daty a pod.), kde objem inicializačních dat není extrémně velký (nebudu popisovat inicializaci dat v obrovském datovém centru), kde v databázi není implementovaná žádná logika a kde si persistenci řeší aplikace (ORM, sql, ...).
Budu předpokládat, že už máme založenou databázi a v ní vytvořený datový model. Vytvoření datového modelu může být sama o sobě zajímavá otázka, hlavně s ohledem na jeho vývoj, aktualizace a udržování změn. Dnes mě ale bude zajímat pouze inicializace dat. Co od ní můžeme požadovat?
- Přizpůsobitelnost - Můžeme si představit aplikaci, která je instalovaná u několika zákazníků, typicky mnoha. O datech pak můžeme mluvit jako o obecných a zákaznicky specifických. Jak inicializovat zákaznicky specifická data? Je dobré mít inicializaci dat rozdělenou na inicializaci těch obecných a s ní konzistentní způsob na inicializaci zákaznických rozšíření.
- Nezávislost na konkrétní databázi - Jsou aplikace, typicky produktová řešení, které je možné provozovat na různých databázích (ve smyslu Oracle, MySql, ...). Nemusí to být vždy možné, ale určitě by bylo výhodné mít společný způsob inicializace dat použitelný pro každou z nich.
- Použitelnost pro testy - Unit testy vyžadující databázi také potřebují mít inicializovaná data. Typicky vyžadují podmnožinu z dat produkčních (číselníky...) a nějaká data specifická pro testy. Navíc, různé testy mohou potřebovat různá testovací data. Různé testy si tak můžeme představit jako různé zákazníky a testovací data rozdělit na obecná a specifická pro konkrétní test. Bylo by hezké mít jeden způsob, jak přistoupit k inicializaci produkčních i testovacích dat.
- Testovatelnost - Také v datech mohou být chyby. Bylo by dobré mít způsob, jak správnost inicializace dat otestovat.
- Verzování a správa změn - Tak, jak se vyvíjí aplikace, mohou se vyvíjet i data, se kterými pracuje. Přirozeným požadavkem se zdá být možnost verzování změn inicializačních dat.
- Přehlednost a udržovatelnost - Stejně jako zdrojové kódy nebo jiné artefakty, i data a jejich inicializace chceme mít dobře organizovaná a inicializační proceduru přiměřeně jednoduchou a dlouhodobě udržovatelnou.
- A další... Samozřejmě existuje spousta dalších kritérií, která můžeme po inicializaci požadovat. Snažil jsem se shrnout ta, která jsou zajímavá pro další text.
Jak se inicializace dat řeší? Shrnu ty způsoby, které jsem si na projektech vyzkoušel a které mi přijdou jako nejčastěji používané.
Dost často se databáze průběžným vývojem přivede do stavu, který odpovídá požadavkům na produkční prostředí, a pak se pořídí její dump. Ten se v cílovém prostředí celý naimportuje. Problémem může snaha o podporu více databází - jak ve smyslu Oracle, MySql, tak podle zákazníků. Různé varianty vyžadují existenci různých dumpů, tedy i databází, ze kterých dump vytvoříme. Naopak výhodou je rychlost importu dat. Otázkou je, nakolik je pro nás rychlost inicializace důležitá. Dumpy se často kombinují s sql skripty, které databázi po importu dumpu aktualizují. Přehlednost, udržovatelnost, použitelnost pro testy, verzování, ... také vidím jako obtížné.
Sql skripty
Data můžeme do databáze přirozeně vložit sql skripty. Typicky ale nestačí "dlouhý seznam insertů", skripty mohou vypadat o dost složitěji. U složitěji provázaných dat (na úrovni doménového modelu bych řekl "grafů objektů") mohou skripty obsahovat hodně práce s proměnnými, cykly, někdy i funkce nebo celé balíčky funkcí. Zajímá mě inicializace dat na projektu, kde si perzistence řeší aplikační vrstva a taková spousta sql kódu jenom pro inicializaci dat je tedy nechtěná. Přehlednost a udržovatelnost sql skriptů také stojí hodně úsilí.
Speciální nástroj na správu a migraci dat
Existují nástroje, které inicializaci a obecně správu a migraci dat komplexně řeší. Příkladem může být třeba Embarcadero Change Manager. Vedle ceny může být u takových nástrojů limitující i způsob, jak práci s nimi zaintegrovat s našimi dalšími nástroji (IDE, maven, ...) a vývojovým procesem. Příklad nebo hodnocení komerčních nástrojů je ale mimo rozsah tohoto článku.
Java + ORM
Na několik posledních projektech jsme inicializaci dat prováděli z prostředí Javy. Máme-li doménový model, který data plně popisuje, můžeme libovolná data instancemi tříd z doménového modelu reprezentovat (od toho doménový model máme, že...). Inicializace dat se tak převedla na inicializaci doménového modelu. A to už je otázka pro Javu, kde to umím řešit přehledně a dlouhodobě udržovatelně a to i pro velmi složité "grafy objektů". Dobrý nápad je zestručnit výřečný Java kód pro inicializace použitím nějakého dynamického jazyka, výhodné může být třeba Groovy.
Pro řešení persistence na projektech používáme nejčastěji Hibernate, máme tedy pro doménový model vytvořené mapování. Zapsat instance tříd z doménového modelu do databáze je potom triviální úkol. Řeším inicializaci a chci tedy řešit hlavně "data", algoritmus zápisu mi kód nebude znepřehledňovat.
Podle některých učebnic by asi inicializace dat neměla záviset na kódu, který data posléze bude využívat, ale v závislosti na doménovém modelu a mapování nic špatného nevidím. Pokud na projektu není použité ORM a persistenci řeší DAO třídy třeba s využitím JDBC nebo jakkoliv jinak, ani závislost na DAO vrstvě nemusí být problém.
Použití ORM je výhodné i v případech, kdy se vytváří nějaké produktové řešení a je nutnost podporovat více databází. Hibernate se svými dialekty mi proto velmi vyjde vstříc.
Pro některé typy dat může být výhodné reprezentovat jejich inicializační hodnoty mimo Java kód. Řešíme tak třeba číselníky. Máme je reprezentované jednoduchým XML formátem a v Javě napsaný jeho parser a konvertor do doménového modelu. XML je výhodné i v tom, že obsah XML souborů může generovat nebo kompilovat z různých zdrojů automaticky.
Shrnutí
Popsané způsoby a jejich vztah k požadavkům na inicializaci dat si můžeme shrnout v následující tabulce.
| Přehlednost a udržovatelnost | Přizp. | Nezávislost na konkrétní db | Použitelnost pro testy | Testovatelnost | Verzování a správa změn | |
| Dump | subjektivní, imho ne | obtížně | různé dumpy pro různé db | obtížně | ano | obtížně |
| Sql | subjektivní, imho ne | obtížně | různé skripty pro různé db | obtížně | ano | ano |
| Java + ORM mapování | subjektivní, imho ano | ano | ano | ano | ano | ano |
Tabulka samozřejmě přináší značně subjektivní zhodnocení, ale ukazuje, proč dávám přednost variantě s Javou a mapováním.
Článek neměl ambici encyklopedicky popsat všechny možné způsoby inicializace. Chtěl jsem shrnout ty přístupy, se kterými jsem se setkal a vysvětlit, proč mi přijde výhodné provádět inicializaci prostředky Javy. Jako každé řešení to má své výhody i nevýhody. Myslíte, že je inicializace dat prostředky Javy dobrý nápad? Používáte podobný postup taky? Podělte se o své názory a nápady v diskuzi pod článkem.
V příštím blogu se podívám na to, jak přistoupit k založení datového modelu a jak zvládnout jeho vývoj a aktualizace včetně aktualizací dat. Představím také zajímavý nástroj LiquiBase.
Přihlásit se k odběru:
Příspěvky (Atom)