sobota 16. srpna 2008

Trochu o designu API

Včera večer jsem ještě se sirkami v očích dopisoval nějaké postřehy ke code review prezentaci, kterou budu mít v pondělí a zároveň jsem konečně dorazil rozhovor s Jardou Tulachem k vydání jeho knihy Practical API Design s podtitulem Confessions of a Java Framework Architect. Musím předeslat, že ten rozhovor na mě udělal dojem, že tato kniha nesmí chybět v mojí knihovničce. Taky je na čase přehodnotit můj trochu skeptičtější pohled na code review, ale pěkně po pořádku, protože to všechno začalo trochu jinde.

Udělali jsme jehož účel není pro popis této příhody vůbec důležitý. Proto abyste mohli s tímto API začít pracovat, tak potřebujete Spring. Náš původní předpoklad byl, že kdokoliv (klient), kdo s tím API bude pracovat, bude nejlépe sám managovaný Springem. Díky IoC pak všechno do sebe zapadne jako dílky puzzle skládačka Jednou ze základních tříd toho API byla továrna na vyrábění domain objektů typu jablka, hrušky, švestky a broskve. Zároveň tato továrna měla metodu k vyrábění unikátních identifikátory pro tyto domain objekty. Všechny naše objekty byly navenek reprezentované rozhraním a to včetně onoho zmíněného identifikátoru.

    
public interface OvocnaTovarna {
    public <T extends Ovoce> T newOvoce(Class typOvoce);    
}    

public interface IdTovarna {
    public Id newId();
    public Id fromString(String idAsString);
}

    
  

Rozdíl mezi implementací Ovoce a Id byl v tom, že ovoce skutečně představovalo netriviální strukturu, jejíchž instance šlo vytvářet pouze se znalostí určitého kontextu. Identifikátor byl oproti tomu v uvozovkách jednoduchý objekt, pro jehož zkonstruování nebylo potřeba kontext znát. Nicméně v rámci jednotnosti API jsme se rozhodli, že i Id bude vytvářeno továrnou a že bude taktéž reprezentované rozhraním jako zbytek domain objektů. Dalším faktorem pro volbu Id jako rozhraní byla možnost změnit jeho implementaci, kdyby například vespodu ležící java.util.UUID nevyhovoval.

Když jsem si včera procházel kód komponent, které naše API používají, tak se najednou ukázalo, že tento způsob návrhu API u klientů narazil a vedlo to k docela zajímavým často se opakujícím paradoxním praktikám. Ačkoliv klient, sám managovaný Springem, použil továrnu na ovoce, ale místo továrny na identifikátory prostě sveřepě konstruktorem vytvářel jedinou dostupnou implementaci rozhraní Id.

Kód vypdadal následovně

   
public class FooComponent { 
   @Autowired //zajisti dependency injection implementace 
   private OvocnaTovarna ovocnaTovarna;
   
   public void doSomethingFishy(String idAsString){
      IdImplementace id = new IdImplementace(idAsString);
      Hruska hruska  = ovocnaTovarna.newOvoce(Hruska.class);
      ...    
   }
}   
   
   

...přitom námi zamýšlené použití se přímo nabízelo.

   
public class FooComponent { 
   @Autowired 
   private OvocnaTovarna ovocnaTovarna;
   @Autowired
   private IdTovarna idTovarna;
      
   public void doSomethingFishy(String idAsString){
      Id id =  idTovarna.fromString(idAsString);
      Hruska hruska  = ovocnaTovarna.newOvoce(Hruska.class);
      ...    
   }
}   
   
   

V zmiňovaném rozhovoru Jarda Tulach uvádí dva základní axiomy.

  • "The first version of an API is never perfect."
  • "You cannot know all the users of your library."

První axiom se potvrdil celkem beze zbytku. Díky code review víme, že uživatelé některé části API používají jinak než jsme zamýšleli. Otázkou je proč. Nejpravděpodobnější variantou je, že jsme dostatečně nezdokumentovali, že se má používat továrna na vytváření identifikátorů. Méně pravděpodobné, ačkoliv reálné, je nechuť používat továrny pro "jednoduché" objekty ačkoliv to bych spíše chápal na místech, kde se Sring používá programově tj. programový bean lookup a po pravdě nikdo nemá rád vzor továrna na továrny.

Druhý axiom je pro nás daleko nebezpečnější v tom, že API může používat člověk u něhož těžko uděláme code review, protože ani nevíme kdo všechno a kde API použil. Poučení pro nás je v tom, že jednak musíme udělat příště tři věci:

  • dobře zdokumentovat API včetně příkladu použití
  • být defensivní - implementační třída nemusí být vůbec veřejná
  • být pragmatičtější - místo rozhraní jsme mohli pro identifikátor zvolit třídu

čtvrtek 14. srpna 2008

Synchronizace kolem objektů v HTTP session

Rozečetl jsem knihu Java Concurrency in practice a jen tak letmo mezi stránkami mě napadlo, že většina webových aplikací, na kterých jsem dělal, vlastně byla thread safe jen tak na oko. Slabé místo představovaly objekty, které ležely v session a mohlo dojít k jejich současnému použití různými vlákny.

Servlet specifikace přímo říká:

Multiple servlets executing request threads may have active access to the same session object at the same time. The container must ensure that manipulation of internal data structures representing the session attributes is performed in a thread safe manner. The Developer has the responsibility for thread safe access to the attribute objects themselves.

Toto riziko se úměrně zvýšilo tím, jak se začaly používat aplikace postavené kolem AJAX konceptů. Tím totiž došlo k několika paralelním asynchronním voláním, které se typický zbíhaly v datech uložených v session. Je potřeba si uvědomit, že ačkoliv se to na první pohled nezdá, tak nesprávně řízený konkurenční přístup k datům v session může vést u aplikace k docela neočekávaným stavům, které v lepším případě končí například na zdánlivě nesmyslné NullPointerException výjimce. Na druhou stranu neřízené synchronizování všeho a na všem může vést k špatné škálovatelnosti aplikace.

Pro případ, kdy chceme opravdu vzájemné vyloučení, tedy serializovaný přístup k datům uvnitř session, tak bychom měli zvážit velikost zámku. Pokud se nepletu tak webový framework JBoss Seam umožňuje synchronizaci každého požadavku právě přes asociovanou session. To je v mnoha případech zbytečně velký zámek, proto se můžeme sami zasynchronizovat přímo na objektu, který se bude potenciálně mezi vlákny sdílet.

Pokud chceme zaručit pouze správnou viditelnost dat mezi vlákny a konkurenční přístup k nim nám nevadí pak můžeme použít volatile a nebo vhodné abstrakce na programové úrovni např. pro mapu zvolit jako implementaci ConcurrentHashMap.

Myslím si, že na správnou synchronizaci objektů sdílených přes session se v mnoha případech nemyslí, protože si to člověk neuvědomí a nebo se tiše ignoruje protože všechny důsledky nejsou patrné. Docela by mě zajímalo jestli to ve svých aplikacích řešíte a nebo to necháváte takzvaně koňovi s poukazem na "ono to už nějak dopadne".