neděle 4. prosince 2011

Další krok k Continuous Delivery

V několika předešlých článcích jsem se věnoval continous delivery a ještě ve starším článku jsem se zamýšlel nad buildováním z jedné hromady oproti releasovatelným celkům. Když nad tím teď přemýšlím, přijdou mi releasovatelné celky jako výborný krok k continous delivery, alespoň v našem prostředí v GoodData. V tomhle článku vám nechám trochu nahlédnout do kuchyně toho jakým způsobem vyvíjíme Java komponenty a připojím pár postřehů, které si myslím, že by nám mohli pomoci k plnému continous delivery, protože zatím jsme schopni doručovat nové verze produktu přibližně každých 14 dní.

Na začátku všeho je commit

Chtělo by se začít tímto titulkem, ale já to neudělám, a začnu organizací kódu která se nám osvědčila. Úplně původně jsme měli všechen zdrojový kód na jedné hromadě. V heterogenní aplikaci, kterou v GoodData vytváříme, jsme měli sice strukturované, ale stále na jedné hromadě zdrojové kódy všech služeb UI klientem počínaje a ROLAP enginem konče. Tahle směska má speciální buildovací skript, který volá jednotlivé buildovací nástroje poplatné pro daný jazyk/platformu, v případě Javy to byl Maven. Výsledkem buildovacího procesu jsou RPM balíčky, které umožňují nainstalovat celý produkt.

Ve verzovacím systému pro správu zdrojových souboru (používáme Git) používáme při vývoji nové funkcionality koncept feature branch, to znaméná, že máme Master větev, do které je možné zamergovat z Feature větve až potom co člověk projde prověrkou kvality, která se skládá z důkladné sborky a rozborky, code review a kurately záludných otázek. Merge do Masteru je úzkým hrdlem celého procesu, a code review je docela komplikované ve chvíli kdy měníte různé komonenty. V Jave máme navíc velké množství komponent, které používáme skrze různé služby a proto je potřeba být velice obezřetnými při jednotlivých změnách. Při buildu na jedné hromadě je prakticky nemožné používat stabilní verze komponent a zároveň mít vývojové verze, protože komponenty mají různý vývojový cyklus. Navíc máme odladěné verze komponent, do kterých se prakticky nesahá a nechceme jimi neustále zdržovat kontinuální buildy ať již z důvodů rychlosti či zdrojů.

Releasovatelné komponenty v praxi

Z výše uvedených důvodů jsme se rozhodli postupně migrovat Java komponenty z jedné velké hromady do separátních repositářů na GitHub a releasovat je jako binární artefakty Maven infrastrukturou. Co se týká Javy máme něco kolem třiceti GitHub repositářů. Každý repositář má svého strážce (gatekeeper), tedy člověka který je zodpovědný za komponenty v daném repositáři. Pokud chci něco v dané komponentě změnit udělám to ve vlastní větvi, ze které vytvořím Pull request. GitHub na to má skvělou podporu bez toho aniž bychom si museli cokoliv programovat vlastními silami. Strážce dostane emailovou notifikaci, s odkazem na Pull request. Přes webové rozhraní si prohlédne změny a má dvě možnosti. Pokud je vše v pořádku akceptuje Pull request a GitHub se postará automaticky o zamergování změn. Pokud se strážci něco nezdá, může do zdrojového kódu, případně k Pull request, dopsat svoje komentáře přes webové rozhraní. Ty jsou žadateli o Pull request zaslány formou emailu. Výhrady se zapracují a pokud není jiných námitek Pull request se akceptuje. Tím je pokryté code review.

Nad zdrojovými repositáři na GitHubu máme post commit hooky, které se postarají o klasické kolečko build, test a deploy výsledného artefaktu do binárního Maven repositáře. Jediné bílé místo představuje release management komponenty, který dělá strážce manuálně. Pokud je potřeba vyrealsovat stabilní verzi komponenty musí strážce spustit manuálně Maven release plugin, který se postará o všechny záležitosti (build artefaktu, změna verze POMu, přiřazení tagu atd.). I release komponent bychom chtěli dělat automaticky (pravděpodobně speciánlí commit message a nebo manuálně z CI serveru).

Pravidla

Release notes

Každá komponenta má u sebe soubor RELEASENOTES.md, ve které zaznamenáváme změny v lidsky snadno pochopitelné podobě, aby to člověk nemusel dohledávat z commit logu. Používáme markdown syntaxi, kterou dokáže GitHub renderovat ve webovém rozhraní když člověk prochází repositář.

## 1.1.2
* Added `AbstractMediaTypeAwareFilter` filter provides support for detection  media types from  HTTP headers
* Added `RetainingHttpServletResponse` special `HttpServletResponse` implementation allowing manipulation with the response body and HTTP headers until the flush method is called
* Added `RetainingResponseFilter` this filter wraps `HttpServletResponse` to `RetainingHttpServletResponse`

## 1.1.1
* Added support for altering the Content-Type header (ContentTypeAlteringRequestWrapper)
* Added support for changing the entire request body (BodyAlteringRequestWrapper)   
Verzovací schéma

Používáme klasické trojčíselné označení verze X.Y.Z. Tímto způsobem verzujeme, aby bylo na první pohled patrné, k jakému typu změny v dané verzi komponenty došlo. Číslo X označuje generaci komponenty, je vždy 1 a nepředpokládám, že bude jiné, snad jenom kdybychom se rozhodli danou komponentu totálně překopat. Číslo Y označuje zásadní změny, které mohou být nekompatibilní s předchozí verzí. K nekompatibilním změnám se ještě vrátím. Poslední verze označuje inkrementální změny, bugfixy atd.

Pokud se rozhodnete releasovat komponenty stane se pro vás zpětná kompatibilita velkým zaklínadlem. Nevíte totiž kde kdo v jaké verzi vaší knihovnu používá a kdy se rozhodne nebo případně bude donucen (tranzitivní závislosti) používat novější verzi.

Postupem jsem dospěli k tomu, že nejlepší je dělat zpětně kompatibilní změny především u nízkoúrovňových komponent např. HTTP klient, protože jinak se vám výsledný celek může sesypat jak domeček z karet. Mimochodem to je jedna z nevýhod releasovatelných komponent oproti buildu z jedné hromady. U aplikačních komponent už na to není třeba brát zas takový ohled.

Readme

Většina komponent by u sebe měla mít popis toho k čemu slouží a jak se používají. To usnadňuje orientaci při jejich použití.

JSON
===========
This repository consists of two parts

* [JSON validation](https://confluence.gooddata.com/confluence/display/analysis/JSON+validation+documentation)
* JSON serialization support - also contains [AbstractJsonObject (de)serialization](https://confluence.gooddata.com/confluence/display/Development/Abstract+JSON+object+serialization)

Continous delivery

V Continous delivery alespoň u Java služeb nám brání několik překážek. Zatím nemáme releasovatelné úplně všechny high level služby. Nicméně se držíme pravidla, že v stabilizační větvi (forknutá z Master větve) nepoužíváme SNAPSHOT závislosti. Tím je build reprodukovatelný a nehrozí, že jej rozbijeme. Ve chvíli kdy budeme mít releasovatelné všechny služby už bude jenom několik kroků ke kýženému cíli. Předně psychologická zábrana, které říkám suma všech strachů. Mít tolik testů, schválně neuvádím procento code coverage, které mi umožní udělat commit s vědomím, že případný problém bude zachycen.

Další věcí je staging repository pro binární artefakty služeb. Po tom co je artifakt připravený v Maven repository a tedy úspěšně proběhly jednotkové a integrační testy je potřeba ho otestovat se zbytkem systému. Pokud tyto testy projdou, je možné artefakt posunout do staging repository, ze které je možné brát artefakty pro finální distribuci, která jde k manuálnímu verifikaci.

No a poslední překážkou je neschopnost částečného releasu/rollback jenom některých částí. Zatím dokážeme releasovat jeden velký celek, což samozřejmě zvyšuje náklady spojené s celým releasem.

Závěr

Cesta ke Continous delivery je dlouhá a trnitá, navíc nikdo vám nedá plánek. Funguje jenom postupné zlepšování a učení z vlastních chyb. Doufám, že za půl roku se budu moci pochlubit jak jsme pokročili.