pondělí 25. září 2017

Sémantický identifikátor entity

Na projektech korporátních i startapových jsem se setkal vždy s tím, že si servisní třídy mezi sebou vyměnují identifikátory entit.

Často je to např. Long, pokud je datovým podkladem SQL databáze ale setkávám se i se Stringem, když je entita ze vzdáleného systému volaného přes MQ, WebServices, Rest apod.

Problém pak nastává v tom, že se tyto identifikátory často pletou.

Například

public void updateTransaction(Long transactionId, 
                              Long userId, 
                              Long operatorId, 
                              Long clientId) {
 // ... do some nasty business logic
}

Není jasné, jestli userId je identifikátor entity User - nejspíš ano. A co operatorId? Je to taky z entity User? Obzvláště, když žádná entita Operator v doménovém modelu není. 


Co kdyby rozhraní vypadalo takto
public void updateTransaction(TransactionId transactionId, 
                              UserId userId, 
                              UserId operatorId, 
                              ClientId clientId) {
 // ... do some nasty business logic
}

Takto už je sématický význam parametrů jasnější. Co ale ve skutečnosti obsahuje třída UserId?
public final class UserId {
  private final Long value;
 
  private UserId(Long value) {
    this.value = value;
  }
 
  public Long getValue() {
    return value;
  }
 
  public static UserId of(Long value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(value); 
  }
 
  public static UserId of(String value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(Long.valueOf(value)); 
  }
}


Instance je immutable a je tedy thread-safe. Zároveň hlídá, že hodnota value je ne-null-ová. Může obsahovat tolik factory metod kolik je potřeba - v tomto případě umí instanciovat z Long a ze String. 

Co když používám Hibernate? Jak to mám namapovat? Můžeme nentitu nechat tak jak je - anotovanou nad attributy - pak Hibernate mapuje hodnoty přímo na atributy bez použití set a get metod.
  
@Entity
@Table(name = "COOL_USER")
public class User {
  @Id
  @GeneratedValue
  @Column(name = "USER_ID")
  private Long id;

  @Column(name = "FIRST_NAME")
  private String firstName;
   
  @Column(name = "LAST_NAME")
  private String lastName;

  public UserId getId() {
    return UserId.of(id);
  }

  public void setId(UserId id) {
    this.id = id.getId();
   }
    
    // other set get methods for firstName and lastName

}

Čitelnost servisního kódy je mnohem lepší a navíd kompilátor za vás dělá kontrolu, jestli jste parametry nepopletli. 

Myslím si, že podobný přístup by se dal použít i na jiné jednoduché datové typy - např. AccountNumber, BirthNumber, DateOfBirth místo String, String, Date. 

čtvrtek 20. dubna 2017

How to join strings with separator

import com.google.common.base.Joiner;
 
public static void main(final String[] args) {
  final String output = Joiner.on(", ").join("white", "blue", "red", "black");
  System.out.println("output = " + output);        
}
output = white, blue, red, black

pondělí 21. listopadu 2016

Peníze v Javě


Tentokrát bych chtěl rozebrat možnosti a moje zkušenosti, jak v kódu nakládat s penězi.

Datový typ - BigDecimal


Pro ukládání částky je rozhodně nutné používat datový typ BigDecimal  Jedině ten zaručuje, že nebude docházet k nečekanému zaokrouhlování za desetinou čárkou. BigDecimal snese v podstatě libovolnou přesnost a velikost čísla. Rozhodně nepoužívejte datové typy typu Float nebo Double.

Setkal jsem se i s aplikací, kde částky byly uchovávány jako celé číslo. Pro zobrazení se pak musela desetinná čárka uměle posouvat, tak aby dávala obsahově smysl. Fungovalo to dobře, ale myslím, že už je to přežité. Použil bych tento přístup jen v programovacím jazyku, který nemá BigDecimal alternativu.

Třída BigDecimal je immutable, takže se s instancemi ve vícevláknovém běhu pracuje bezpečně. Poskytuje všechny základní operace jako je sčítání, násobení a zaokrouhlování.

Identifikace měny


Peníze jsou ale ve skutečnosti dvojicí částky a měny.  Třídu, která by ale skládala dohromady částku a měnu přímo v JDK nemáme. Pokud máte částku a víte že jde o nějaké peníze, ale nevíte v jaké měně, tak je to docela problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price; // What currency is it ?

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }
    // geters …
}

Třídu pro měnu máme v Javě už od verze 1.4 -  Currency. Rozšířit entitu o další atribut by neměl být problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price;
    private final Currency currency;

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price, 
                   final Currency currency) {

        this.productId = productId;
        this.name = name;
        this.price = price;
        this.currency = currency;
    }

    // geters …
}

Pokud v jedné entitě máte více cen - např. prodejní, nákupní, apod. Duplikují se vám dvojice částka a měna a poměrně snadno při použití může dojít k tomu, že částky a měny poplete. Musíte také ohlídat, aby byla vždy zadaná celá dvojice. Pokud dovolíte zadat částku bez měny, opět je to problém.

Joda Money

Problém neexistence třídy pro peníze vyřešil Stephen Colebourne, mimochodem autor populární knihovny JodaTime. Naimplementoval knihovnou pod názvem Joda-Money. Ta obsahuje třídu jak pro měnu - CurrencyUnit tak pro magickou dvojici částka, měna - a to hned ve dvou různých třídách Money a BigMoney. 

Money je třída, která agreguje  dvojici částka (jako BigDecimal) a měna (jako CurrencyUnit). Navíc hlídá počet desetinných míst pro danou měnu. Třída BigMoney poskytuje vše co Money, ale nehlídá počet míst za desetinou čárkou pro danou měnu. To se hodí, pokud vám částky přicházejí ze zdroje, který nemáte úplně pod kontrolou. Konverze mezi oběma typy je samozřejmě přímočará.  Obě třídy jsou immutable, takže opět bezpečné pro práci ve vícevláknovém prostředí. 

Upravený příklad vypadá takto.

public class Product {
    private final Long productId;
    private final String name;
    private final Money price;
    
    public Product(final Long productId, 
                   final String name, 
                   final Money price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }

    // geters ... 
}

Závěr

Pokud pracujete v rámci programu s penězi, doporučuji určitě zahrnout do projektu o jednu závislost navíc a důsledně používat datový typ Money resp. BigMoney, místo dvou nezávislých atributů pro částku a pro měnu. Předcházíte tak riziku, že atributy prohodíte. Navíc je kód mírně přehlednější. Líbí se mi i že místo obecného typu pro číslo použit naprosto konkrétní pro peníze.


čtvrtek 18. srpna 2016

Mockování emailové komunikace v integračních testech

Na projektu JdemeNaTo jsme narazili na problém, jak v selenium integračních testech pracovat s emaily.

Testovaná aplikace posílá emaily přes SMTP protokol. Test je pak musí přečíst a zpracovat. Samozřejmě, že email jako takový se nesmí dostat ven z integračního prostředí.

Poměrně dlouhou dobu jsme používali jednoduché řešení Dumbster. To spočívalo v tom, že si test vytvořil při svém spuštění mock SMTP server na dohodnutém portu, a test si pak přímo přečetl příchozí mail. Po skončení testu se port zase uzavřel.

Toto řešení jsme museli ale opustit v době, kdy máme celé integrační prostředí postavené na dockeru. Testy pouštíme stále pomocí mavenu na TeamCity proti testovanému serveru hostovaném v docker kontejneru. Každý docker kontejner má svoji ip adresu a namá ponětí o tom, kdo ho volá.

Hledali jsme vhodnou náhradu za Dumbster a našli jsme překvapivě robustní implementaci v javě GreenMail, který je určený právě pro testovací účely. Sympatická je i velká variabilita nasazení - poskytují docker image, tak i standalone aplikaci či jako war do Tomcatu. Na straně posílání mailů jsme nemuseli udělat žádnou úpravu. Na straně čtení jsme si napsali jednoduchou rutinu založenou na POP3 protokolu.

Pokud hledáte řešení, jak mockovat email server v interačních testech, mohu GreenMail určitě doporučit.

sobota 17. ledna 2015

Code coverage of Selenium tests in TeamCity

There are couples of tools, which measure code coverage in java - cobertura, emma, jacoco.
They work pretty well for regular unit tests.

But how to measure code coverage of integration tests?

Here is my solution

All integration tests are written in Java - jUnit and Selenium WebDriver API - run by standard maven-failsafe-plugin. Application is deployed on Tomcat by TeamCity. Integration tests are run by TeamCity also.

If you run Tomcat with JaCoCo agent, you can measure code coverage on the Tomcat side.

This is part of Tomcat setenv.sh file

CATALINA_OPTS="-Xms512m -Xmx8g -XX:MaxPermSize=1024m -server -javaagent:/opt/tomcat1/org.jacoco.agent-0.7.2.201409121644-runtime.jar=output=tcpserver,address=localhost,port=6300"

After run all integration tests may TeamCity ask for results by jacoco-maven-plugin. It connects to Tomcat by TCP and saves result localy to jaacoco.exec file.

mvn jacoco:dump

TeamCity supports import javacoco result file by service massages. I added a regular command line build step. Service message should be printed into a standard output stream of the build; hence I used linux echo command. Unfortunately, single quote must by surround by double quote.

echo \##teamcity[jacocoReport dataPath="'"target/jacoco.exec"'" includes="'"com.jpower8.*"'" excludes="'"com.jpower8.*.*Test"'"]

Additional tab with coverage results appears in build detail.

neděle 23. listopadu 2014

DevFest 2014

Po letech v dejvickém kampusu. Padla na mě nostalgie. Národní technická knihovna z venku super. Fakulta stavební byla pro mě ale premiéra.

1. Rachel Simpson - Unboxing UX: Design prototyping for Engineers 

Výborná přednáška o UX designu pro vývojáře. Doporučovala k prostudování Steve Krug - Rocket Surgery Made Easy. Pro rychlé prototypování používá tužku a papír. To pak vyfotí a rozpohybuje v aplikaci POP. Přímo na přednášce jsem zkoušel a vypadá to super. Ohledně UX testování pouštěla vtipné video - The scrollwheel.  Doporučuji projít si i portfolio autorky. 

2. Mirek Černý - How to become a world-class startup

Mluvil o své firmě futurelytics. O té jsem nic nevěděl. Dostali se přes seedcamp do ameriky, kde dostali investici. Doporučoval naučit se prezentovat svůj nápad během 30s - elevator pitch. V česku doporučoval Credo Ventures a v Americe Index Ventures. Podíl investorů prý běžně bývá kolem 10%. U odměňování spolupracovníků a zaměstnanců preferuje opční plán.

3. Filip Hráček - Plymer.dart

Prezentoval použití google technologií na datech z městské knihovny, s cílem doporučit knihu z vypůjčení. Troufl si na živé demo, které se povedlo. Inspiraci bral z amazonu a z knížky Programming Collective Intelligence. Programování v Polymer.dart mi hodně připomínalo Tapestry. 

4. Firma

Dva bloky přednášek, kde Mirek Černý z futurelytics, Marie Chytilová se slevomatu, Ondřej Krátký z Liftago a Daniel Franc z googlu radili se zakládáním a provozem firmy. Účastníci si představili v hlavě svoji firmu a odpovídali na otázky. Některé odpovědi pak panelisti rozebírali.
Celkově bych tento blok hodnotil kladně, i když zůstalo jen u naprostých základů.

Po přednášce panelisti nabízeli krátkou individuální konzultaci, čehož jsem využil. 

5. Vojtěch Bárta - Kvalita jako odpovědnost celého týmu

Vojta pracuje jako QA ve firmě Vendavo. Ukazoval, jaké jsou problémy s kvalitou ve vodopádu i v agilním světě. Pro mě to bylo spíš jen opáčko toho, co už jsem věděl. 

6. Věrka Koukalová - Monetizace aplikace

Přednášející prezentovala, jaké jsou možnosti vydělat na aplikaci umístěnou v google play nebo app store. Přednáška byla celkem vtipná, ale mě posloužila hlavně pro utříbení myšlenek, kudy se nevydávat. 

7. Tomáš Holas - Firemní kultura

Definoval co to je firemní kultura jako
Systém psaných i nepsanách konvencí které ovlivňují jakým způsobem se lidé staví k řešení probéml a jak se k sobě navzájem chovají. 
 Porovnával zkušenosti z korporací, ze startupu a svobodné firmy. Přednáška byla překvapivě vtipná.

středa 12. listopadu 2014

SQL Performance Explained

Na konci října jsem vyrazil na Geecon, který se poprvé konal v Praze. Na jedné z přednášek o SQL prezentátor Lukas Eder doporučoval knížku SQL Performance Explained.

Protože se po večerech a víkendech snažím o zrychlování našeho systému JdemeNaTo, tak jsem zainvestoval €10 a začetl se.

Knížka detailně popisuje, jak funguje index a jaké jsou běžné chyby při používání. Příklady autor ukazuje hlavně na Oracle, ale alternativy i na jiných - např. moje oblíbená PostgreSQL.

Moc se mi líbí stručný a výstižný jazyk. Každá věta nese informaci. Nikde žádná zbytečná výplň.

Nové poznatky jsem ihned začal úspěšně aplikovat na naší databázi, která už není úplně malá (řádově miliony řádků).

Rozhodně doporučuji k prostudování a dokonce zvažuji, připlatit si za vytištěnou verzi.

neděle 8. června 2014

SQL Antipatterns

Při procházení knížek z nakladatelství The Pragmatic Bookshelf jsem narazil titul, který mě zaujal svým názvem.

Autor popisuje 24 vzorů na které naráží při používání klasických relačních databází. Problém popíše, navrhne možné řešení a s vědeckou metodičností rozebírá výhody a nevýhody jednotlivých variant.

Mě zaujaly dva vzory, které jsem mohl ihned aplikovat na projektu jdemenato.cz.








Enumerace

Standardním přístupem je enumeraci omezit pomocí omezení na sloupci.
CREATE TABLE Bugs (
   -- other columns
   status VARCHAR(20),
   status VARCHAR(20) check (status in ('NEW', 'IN PROGRESS', 'FIXED'))
); 
Lepším řešením je ale vytáhnout celou enumeraci do nové tabulky, kde hodnota enumerace je přímo primárním klíčem.
CREATE TABLE BugStatus (
   status VARCHAR(20) PRIMARY KEY
);

INSERT INTO BugStatus (status) VALUES ('NEW' ), ('IN PROGRESS' ), ('FIXED' );

CREATE TABLE Bugs (
   -- other columns
   status VARCHAR(20),
   FOREIGN KEY (status) REFERENCES BugStatus(status) ON UPDATE CASCADE
);
Lze se pak se pak krásně dotazovat na všechny hodnoty
SELECT status FROM BugStatus ORDER by status;
Elegantně jdou přidávat nové hodnoty do enumerace i
INSERT INTO BugStatus (status) VALUES ('DUPLICATE' );
a díky ON UPDATE CASCADE i nahrazovat nahrazovat historicky špatně zvolené.
UPDATE BugStatus SET status = 'INVALID' WHERE status = 'BOGUS' ;

Naivní stromy

Běžně jsem se setkal s tím, že stromovou struktura se řeší pomocí reference na sebe sama.
CREATE TABLE Comments (
   comment_id SERIAL PRIMARY KEY,
   parent_id BIGINT UNSIGNED,
   comment TEXT NOT NULL,
   FOREIGN KEY (parent_id) REFERENCES Comments(comment_id)
);
I pokud má vaše databáze podporu pro hierarchické dotazy, není to žádná hitparáda.
WITH CommentTree
   (comment_id, bug_id, parent_id, author, comment, depth)
AS (
   SELECT *, 0 AS depth FROM Comments
   WHERE parent_id IS NULL
UNION ALL
   SELECT c.*, ct.depth+1 AS depth FROM CommentTree ct
   JOIN Comments c ON (ct.comment_id = c.parent_id)
)
SELECT * FROM CommentTree WHERE bug_id = 1234;
Autor popisuje několik variant, jak vazby ukládat. Mě osobně se nejvíce líbilo řešení pomocí closure table - doporučuji k nastudování.

sobota 12. dubna 2014

Spring configuration files - best practice

I prefer to configure spring with xml files. The question is, how they should be named and where should be located.

The official spring documentation do not provide any recommendation. Here are some with I advocate.

Name consistency

Start all files with the same prefix applicationConfig*.xml

E.g. applicationConfig-security.xml, applicationConfig-hibernate.xml, applicationConfig-quartz.xml etc.

File Location

  • JAR -> src/main/resources/META-INF/spring
  • WAR -> src/main/webapp/WEB-INF/spring

Do not use version number in schema reference

Instead of

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
  ...
</beans> 

Use this

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
  ...
</beans> 

Why?

Spring automatically use highest version available from maven dependencies. Upgrade to newer version of spring is much more easy.

středa 2. dubna 2014

Řízení transakcí přes různé DAO implementace

Na startu projektu jdemenato.cz jsme DAO vrstvu implementovali přes JPA/Hibernate - standardně dle návodů.

V produkčním režimu, kde nám neustále roste počet uživatelů, jsme s tímto naivním řešením vydrželi jen několik měsíců. Kritické dotazy, které nejvíce vytěžovaly databázi,  jsem přepsali přes Criteria API na "lepší" SQL dotazy.

Po dalších měsících produkčního života nás zákaznické požadavky přinutili některé SQL dotazy psát ručně. Nejprimitivnější jsme zrealizovali přes Hibernate native SQL ale u složitějších jsme šáhli na JdbcTemplate a později k MyBatis.

To ovšem nastolilo problém s řízením databázových transakcí, jejichž součástí je více DAO technologií.

Ukázalo se, že na podobné případy chlapci ve springu pamatovali a stačilo vyměnit HibernateTransacionManager za DataSourceTransactionManager.

Tímto způsobem je DataSource "nejmenším společním jmenovatelem" spojující DAOs implementované přes Hibernate, MyBatis i JdbcTemplate.

Aby si AnnotationSessionFactoryBean nevytvořila svůj vlastní transakční manager, je nutné nastavit ji property useTransactionAwareDataSource=true.

        
<bean name="sessionFactory" 
  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>       
  <property name="useTransactionAwareDataSource" value="true"/>
</bean>
<bean id="sqlSessionFactory" 
         class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="configLocation" value="classpath:/META-INF/mybatis/myBatis-configuration.xml"/>
</bean>

středa 25. prosince 2013

Logování

Na projektech, kterých se účastním, se ještě dnes setkávám kódem, který považuje logování pomocí System.out za výborný nápad. Proto bych v tomto článku chtěl rozebrat, co považuji konci roku 2013 za nejlepší logovací řešení.


Přehled

V javě máme několik možností, jak logovat:
  1. System.out respektive System.err,
  2. java.util.logging (JUL),
  3. Jakarta (Apache) commons logging (JCL),
  4. log4j,
  5. slf4j a
  6. logback.

System.out resp. System.err

Tuto variantu používám jen při zkoušení izolovaných věcí a záměrně odřezávám nepotřebné závislosti. Funguje na všech verzí javy.

Hlavní důvody, proč nepoužívat System.out
  • Nelze konfiguračně vypnout na různých prostředích což má za následek výkonostní a bezpečnostní problémy a
  • nelze konfiguračně nastavit úroveň zpráv.

Java Util Logging (JUL)

Tato možnost již máme v JRE myslím od verze 1.5. Kupodivu jsem ji  na komerčních projektech ani v open-source projektech nepotkal. Podle mě se neujala, protože přišla tak trochu s křížkem po funuse. 


log4j

Tuto možnost jsem používal na SE i EE projektech před pěti a více lety. Nenarážel jsem na limity tohoto řešení a byl jsem s ním byl spokojen. Možnosti konfigurovatelnosti ale daleko zaostávají za jeho nástupcem - logback.  


Jakarta Commons Logging (JCL)

Toto řešení mělo za cíl unifikovat rozhraní mezi různými logovacími frameworky. Samo o sobě logování neřeší, jen posílá zprávy do konkrétních implentací - log4i, JUL.  Rozhraní je skutečně jednoduché a můj oblíbený spring framework je tímto api prolezlý skrz naskrz. Toto řešení bohužel sebou nese některé problémy, kvůli kterým bych toto řešení nedoporučoval. 


slf4j

Tato knihovna řeší stejný problém jako JCL - tedy unifikace rozhraní nad různými logovacími API. Nové komerční projekty i řada open-source projektů dnes používá právě toto api. Dnes ho používám na několika komerčních i sranda projektech k plné spokojenosti. 


logback

Autorem je tvůrce log4j i slf4j - Ceki Gülcü.
Důvody, proč přejí z log4j na logback sám sepsal v článku Logback: Reasons to Switch 

Mě osobně nejvíce oslovily dvě vlastnosti:
  1. podmíněná konfigurace, kterou používám pro nastavení úrovně logování v závislosti na prostředí. 
  2. SiftingAppender, který umožňuje logovat dle dynamického parametru, např zalogovaného uživatele.

Závěr

  • Nepoužívejte System.out resp. System.err
  • Používejte slf4j spolu s logback.


pondělí 14. října 2013

Logování pomalých dotazů v PostgreSQL

Na produkčním prostředí jsem se snažil zjistit, které dotazy databázi nejvíce vytěžují.

Mile mě překvapilo, jak mocný je v tomto ohledu PostgreSQL.

Jednoduché logování dotazů, jejichž čas zpracování trvá více jak monitorovaný čas mě umožnil identifikovat dotazy, o kterých jsem ani netušil, že by mohli dělat problém.

Stačí v postgresql.conf zapnout magický přepínač log_min_duration_statement, který je defaultně vypnutý.

Pokud ho nastavím takto
log_min_duration_statement=100
tak mi do logu databáze zapíše dotazy, které trvají více jak 100ms.
Pak už přichází ke slovu klasický explain a refaktoring.

neděle 10. února 2013

pondělí 5. listopadu 2012

Continuous delivery v jPower8


Náš produkt nasazujeme produkci každé dva týdny. Máme tak nastavený sprint cyklus.

 Celý proces je (skoro) plně automatický.


Zdrojáky verzujeme v svn (migraci na git plánujeme).

Stabilitu buildu udržuje TeamCity, které

  • provádí unit testy,
  • spouští seleniové (integrační) testy,
  • nasazuje na testovací prostředí a
  • provádí statickou analýzu kódu.

Doba od commitu do nasazení na test je cca 15 minut.

Pro upečení stabilního verze používáme maven-release-plugin, který se postará o to, že zvedne verzi ve všech pomech, otaguje verzi v svn a uloží upečený build do artifactory. To vše se děje jedním stiskem tlačítka na TeamCity.

Posledním krokem je samotné nasazení waru na produkční prostředí. To dělám kvůli svému pocitu důležitosti ještě stále ručně (a protože nechci, aby TeamCity mělo na produkci přístup).

Samostatnou kapitolou je aplikace migračního databázového skriptu. Ještě donedávna jsme ji prováděli ručně, což sebou neslo riziko lidské chyby a hlavně velkého opruzu.

Pokud je například potřeba přejmenovat sloupec do tabulky, přidat index, změnit constraint, vývojář commitnul migrační sql sript do svn. Dále musel změnu aplikovat u sebe lokálně minimálně na dvou databázích - jedna pro unit testy a druhá pro lokální vývoj. Dále musel změnu provést na testovacím prostředí na dalších třech databázích. Ostatní vývojáři si také museli tuto změnu aplikovat u sebe lokálně. No prostě opruz až na půdu.

Tuto příjemnou činnost jsme hodili na framework flyway. Má přesně takovou složitost, jakou jsme byli ochotni akceptovat. Do databáze přidal jednu tabulku, ze které je naprosto zřejmé, které migrační skripty už v databázi jsou aplikované. Vše je plně automatické a v režii spring beany. Jednoduché jako facka.

A jak jste se zaváděním continuous delivery vy?