What are ListenableFutures good for?

Standard Futures are in Java since version 5. But recently they started to lose breath. Nowadays we do have asynchronous servlets, asynchronous HTTP clients and lot of other asynchronous libraries. Good old Futures are not good enough for connecting those pieces together.

Let’s demonstrate it on a simple example. It’s just one of many possible examples, but I think it’s the most representative one. Imagine we have a simple servlet controller that just calls some backend API, processes the result and returns it. If you think about it you will notice that we do not need any thread for most of the processing time, we are just waiting for the response. On one end we do have asynchronous servlet and on the other asynchronous HTTP client. We just need to connect those two. In the following example I will be using Spring 4 features, but it’s easy to transform it to your favorite library.

@Controller
@EnableAutoConfiguration
public class ListenableFutureAsyncController {
    // Let's use Apache Async HTTP client
    private final AsyncRestTemplate restTemplate = new AsyncRestTemplate(
        new HttpComponentsAsyncClientHttpRequestFactory()
    );

    @RequestMapping("/")
    @ResponseBody
    DeferredResult<String> home() {
        // Create DeferredResult with timeout 5s
        final DeferredResult<String> result = new DeferredResult<>(5000);

        // Let's call the backend
        ListenableFuture<ResponseEntity<String>> future = 
            restTemplate.getForEntity("http://www.google.com", String.class);

        future.addCallback(
          new ListenableFutureCallback<ResponseEntity<String>>() {
            @Override
            public void onSuccess(ResponseEntity<String> response) {
                // Will be called in HttpClient thread
                log("Success");
                result.setResult(response.getBody());
            }

            @Override
            public void onFailure(Throwable t) {
                result.setErrorResult(t.getMessage());
            }
        });
        // Return the thread to servlet container, 
        // the response will be processed by another thread.
        return result;
    }

    public static void log(Object message) {
        System.out.println(format("%s %s ",Thread.currentThread().getName(), message));
    }

    // That's all you need to start the application
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ListenableFutureAsyncController.class, args);
    }
}

The method starts by creating DeferredResult. It’s a handy abstraction around asynchronous servlets. If we return DeferredResult, servlet container thread is returned to the pool and another one is later used for sending the result. To send the result, we have to either call setResult or setErrorResult method from another thread.

In the next step we call the backend. We use Spring 4 AsyncRestTemplate which is able to wrap Apache Async HTTP Client. It returns ListenableFuture and we can use callbacks to say what to do when the backend request succeeds or fails. Then it’s straightforward to return the result. Please note that the callback is called using HttpClient I/O dispatcher thread. It’s fine in our simple case but for more CPU intensive task we would have to use another thread-pool.

The callback is what’s important, that’s what makes ListenableFutures different. In fact, it’s the only difference between Spring ListenableFuture and standard Java 5 future. It’s a small difference, but without it we would not be able to implement the example above.

If we write the code like this, we need a thread only for commencing the request and for sending the response back. The rest is handled asynchronously by NIO.

You can also notice that the code is much more complicated than equivalent synchronous code. It’s more similar to NodeJS code than to what we are used to in Java. We do have those strange callbacks, different threads processing the same request and other conceptually complicated stuff. So, if you do not need to scale to thousands concurrent requests, you might want to think twice about using this approach. But if you need to scale, this is the direction to go. Next time we will re-implement the same example using Java 8 Completable futures to see if it looks better.

The full source code is available here. It uses Spring Boot so the class you have seen is really all you need.

Co je na Scrumu nejdůležitější?

Napadla mě taková pěkná otázka. Co je na Scrumu, nebo jiné vaší oblíbené agilní metodice, to nejdůležitější? Ano, je to nesmyslná otázka, ale mě se přesto líbí, protože krásně odhaluje, jak kdo o agile přemýšlí. Přestaňte číst a schválně se nad tím zkuste zamyslet. Co je pro vás na agile nejdůležitější? Bez čeho by to nedávalo smysl? Teď přestanete číst a přemýšlejte.

Já jsem si třeba kdysi myslel, že nejdůležitější jsou iterace. Tenkrát jsem dělal ve vodopádu a iterace mi přišly jako to nejdůležitější. Dnes to vidím jinak, a nemůžu pochopit jak jsem to mohl nevidět. Nejdůležitější je přeci tým. Ať se podíváte na jakoukoliv agilní praktiku, tak existuje kvůli týmu. Scrum Master pomáhá týmu dosáhnout jeho cílů, Product Owner musí být týmu k dispozici, retrospektiva je tu proto, aby se tým mohl zlepšovat, standup je od toho, aby tým věděl co kdo dělá. V Kanbanu se sleduje WIP aby tým viděl, jestli mu něco nedrhne. V XP je zákazník součástí týmu, tým má společné vlastnictví. Všechno se to točí kolem týmu. Slovo tým na nás kouká odevšud, nevidět ho je jako nevidět pro stromy les. Abych byl spravedlivý, přede mnou se ten les za stromy skrýval hodně dlouho.

Proč je tým tak důležitý? Použiji slova klasiků:

Stmelený tým je skupina lidí tak silně spojena, že celek je víc než součet jeho částí. Výkonnost takového týmu je větší než výkonnost těch samých lidí pracujících mimo stmelený tým. Neméně důležitá je radost z práce ve stmeleném týmu, která je často větší, než by odpovídalo povaze dané práce. … Jakmile se tým začne stmelovat, pravděpodobnost úspěchu roste dramaticky. … Tým nemusí být řízen v tradičním slova smyslu a rozhodně nemusí být motivován. Je rozjetý. … Důvod je jednoduchý, týmy jsou ze své podstaty formovány kolem cílů. … Před tím než se tým stmelí, jednotlivci můžou mít různé cíle. Nicméně jako součást stmelování, všichni přijali společný cíl.

Není toto pěkný popis toho, jak by měl fungovat agilní tým? Mimochodem, je to citát z mé oblíbené knihy Peopleware, z roku 1987. Nedávno se mi poštěstilo vidět počátky tmelení týmu v praxi a je to hodně zajímavé. Vidíte, jak se lidi najednou snaží řešit problémy, jak se začínají sami řídit, jak se víc a víc zajímají o to co a jak dělají. Vidíte jak ten vlak nabírá rychlost.

Tým je tedy srdcem každé agilní metodiky. Je bohužel i její achilovou šlachou. Iterace nebo retrospektivy zavedu snadno, ale jak zařídit, aby tým fungoval dohromady? Zalistujme v Peopleware o kus dál, do kapitoly týmovražda, kde se dočteme

Tým nemůžete stmelit. Můžete jenom doufat, že se to podaří, můžete se modlit, můžete zvyšovat šance na stmelení, ale nemůžete zařídit, aby se to stalo. Je to příliš křehký proces na to, aby se dal kontrolovat.

Autoři dál pokračují a píší jak se jim nedařilo najít způsoby, jak zařídit aby se tým stmelil, hrdě to přiznávají a říkají, že se jim naopak podařilo najít hromadu způsobů jak zařídit, aby se to nepovedlo. Jsou to hlavně:

  • Defenzivní management – kdy manažeři nevěří svým lidem
  • Byrokracie
  • Fyzické oddělení členů týmu
  • Fragmentace času lidí
  • Snižování kvality produktu
  • Nesmyslné termíny
  • Clique control

Takže pokud se u vás zavádění agile nedaří, podívejte se, jestli fungují týmy. Pokud ne, zkuste mu odstranit překážky a doufejte, že to začne fungovat.

Nasazujeme a testujeme (mikro)služby

Pokud dostanete chybu v testu vyšší úrovně, nemáte jenom chybu v testovaném kódu. Také vám někde chybí unit test.
Martin Fowler

Včera jsem se vrátil z GeeConu. Jelikož jsem neuvěřitelně vzdělaný, sečtělý a šikovný, tak mě tam zas tolik věcí nezaujalo. Dostala mě ale prezentace Sama Newmana o testování a nasazování mikroslužeb. Dozvěděl jsem se tam řešení problému, s kterým si nevíme rady.

Máme totiž platformu rozdělenou na služby, které jsou ve správě jednotlivých týmů. To má spoustu výhod. Každý si hraje na svém písečku, může si rozhodnout v jakém jazyce to bude psát, jak to bude testovat, jak často chce nasazovat a spoustu dalších věcí. Tím, že jsme platformu rozdělili na služby, se nám ohromně usnadnila práce. Každý máme svoji hromádku, o kterou se staráme a za kterou neseme zodpovědnost. Od té doby, co jsem se takto rozdělili, jsme se ohromně zlepšili ve vývoji, testování i nasazování jednotlivých služeb. Každá služba má totiž specifické požadavky a najednou nám odpadla většina pokusů najít dokonalé univerzální řešení. Víc experimentujeme a každý tým si najde co mu vyhovuje.

Problém ale je, že jednotlivé služby pořád tvoří jednu platformu a zákazníkovi je jedno, že moje část je skvěle otestovaná a funguje, když platforma nefunguje jako celek. Jednotlivé týmy se starají o svoje služby, ale docela nám drhne nasazování a testování, když se jednotlivé služby dají dohromady. Například je složité rozhodnout jaké kombinace služeb testovat, když má každá služba jiný cyklus nasazování. Dopředu moc nevíte jaké verze jednotlivých služeb se potkají na produkci. Co když neškodná změna jedné služby, rozbije něco ve druhé službě? Existuje několik evidentních řešení, které bohužel nezabírají.

Integrační testy

Ideálním řešením jsou integrační testy, které automaticky otestují celou platformu, všechny případy užití a všechny možné kombinace služeb. Před každou změnou se tyto testy spustí, a když projdou, tak je všechno v pořádku a můžeme nasazovat. Jak už jsem řekl, toto řešení je ideální, což bohužel znamená, že v reálném světě moc nefunguje. Abych mohl spustit integrační testy, tak musím všechno nasadit, pospojovat a pak spustit testy, které klikají skrz UI. To za prvé dost dlouho trvá a za druhé je tam tolik věcí, které se můžou rozbít, že testy někdy víc padají, než procházejí. Co dělat když testy neprojdou? Který tým se na to má podívat? Kdo určí co se rozbilo? Sam Newman sice říkal, že když integrační testy neprocházejí, tak je to dobře. Pravděpodobně to poukazuje na křehkost našeho testování. Ale zestabilnění integračního testování je příliš velké sousto, kterému se sice asi nevyhneme, ale bude to dost dlouho trvat. Do GeeConu jsem věřil, že to jde, že je to jediná správná cesta, že se jen málo snažíme. Ale asi jsem byl příliš velký idealista.

Navíc mi nedošla jedna zásadní věc. Protože jsou integrační testy pracné, pomalé a křehké, je dobré je používat jen na věci, které se jinak otestovat nedají. Třeba na otestování toho, že nám celý systém funguje. Spousta věcí se dá ale otestovat jinak, jednodušeji.

Nasazovat najednou

Druhá možnost je nasazovat najednou. Udělat release okna a dopředu vědět co kdo bude kdy nasazovat. Můžeme se tvářit, že nemáme služby ale jeden celek, který můžeme najednou otestovat a nasadit. V teorii to zní dobře, ale předpokládá to, že nikdo neudělá chybu. Že nikdo na poslední chvíli nezjistí, že mu něco nefunguje nebo že na něco zapomněl. Co pak? Odložit i release ostatních služeb? Následující release pak bude ještě větší, a tím se zvětší i pravděpodobnost, že se něco rozbije. Takže se release zas odloží a tak dál. To je cesta do pekel.

Kontrakt mezi službami

Když se nám nedaří testovat to celé najednou, co takhle dobře otestovat jednotlivé služby a pevně stanovit kontrakt mezi nimi? Něco podobného navrhoval kdysi kolega, že prý nemusíme tak intenzivně testovat celou platformu, stačí když dobře zdokumentujeme API. Tenkrát jsme mu to rozmluvili. Právem. Dokumentace je obvykle neúplná, nepřesná a zastaralá, ta by nám kvalitu nezajistila. Nedošlo nám ale, že jsme byli jen kousíček o řešení. Co takhle přísnější kontrakt? Co když tým spravující jednu službu uzavře kontrakt s týmem spravujícím druhou službu o tom jak ji budou používat. A místo toho, aby si na to plácli, tak na to napíší testy. Říká se tomu Consumer-Driven Contract.

Jednoduše se napíší testy, které říkají, my vás budeme volat takto a vy nám budete odpovídat takto. Když se přihodí toto, tak nám vrátíte tamto. Pokud někdo změní službu, spustí testy, které hlídají její kontrakt. Když projdou, je velká šance, že se nic nerozbilo. Díky testům kontraktu budeme mít otestovanou spolupráci mezi službami, díky ostatním testům služby samotné. Místo velkých megatestů, které to pokryjí najednou od hlavy až k patě, pokryjeme systém menšími, překrývajícími se testy. Navíc budeme mít i zdokumentovaný kontrakt mezi službami, což není nikdy na škodu.

Není to samozřejmě dokonalé, pořád tam budou věci, na které zapomeneme. Pořád budeme potřebovat integrační testy, ale nebude jich potřeba tolik. Stačí jich pár, které ale na oplátku můžeme pouštět třeba i na produkci.


Sam Neward toho radil víc, ale consumer-driven contracts mě zaujaly nejvíce. Abych vás neochudil o zbytek, tady je celá prezentace a toto jsou hlavní rady:

Buďte si vědomi vaší pyramidy testů. Vyvažujte ji.Pyramida testů říká v zásadě to, že byste měli mít hromadu unit testů, spoustu komponentových testů a míň integračních testů. Čím větší daný test je, tím míň jich chcete mít. Je to relativní číslo, neříká, že máte kašlat na integrační testy. Říká jen, že pokud máte víc integračních testů než unit testů, tak je něco špatně.

Rozumějte rovnováze mezi testováním a rychlou nápravou – Máte omezené množství zdrojů, je dobré najít rovnováhu mezi tím, jak umíte testovat a mezi tím jak rychle umíte odhalit a napravit chyby na produkci. Možná je výhodnější mít méně dokonalé testy, ale lepší monitoring a rychlejší releasy. Všimněte si, že je to o rovnováze, rozhodně neříká ať kašlete na testy.

Nasazujte jednu věc po druhé – V daném okamžiku nasazujte jenom jednu službu. Budete mít snazší rollback a diagnostiku chyb.

Zvažte consumer-driven contracts – o tom je celý tento zápisek.

Prozkoumejte nasazovaní založené na imagích, abyste zmenšili rozdíly mezi testovacími prostředími – místo toho, abychom měli jeden image s operačním systémem a všechno ostatní na něj instalovali, je údajně lepší vyrobit image s celou naší službou a ten image pak prohnat celým testovacím potrubím až po produkci. Vyžaduje to mít jednu službu per image, ale díky Dockeru to prý jde udělat lacině.