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