Kurážné vedení projektů

Občas si pořídím knížku o řízení projektů, ale nedočtu ji, protože mě nebaví. Teď jsem narazil na výjimku – Scrappy Project Management od Kimberly Wiefling. Je to relativně krátké, čtivé a hlavně je vidět, že ví o čem mluví.

Samozřejmě, pokud nejste úplní nováčci, nedozvíte se tam nic převratného. Je to člověku připomene to co mu radí selský rozum, ale i přesto to nedělá.

Chtěl jsem tady ty rady zopakovat, ale bez kontextu vypadaly tak evidentně, až to bylo trapné. Takže tu jen ocituji věci, které jsem si u jednotlivých kapitol podtrhl a zbytek textu vám nechám za domácí úkol.

Zákazník? Jaký zákazník?

Každý člen týmu má v představu toho, jak vypadá zákazník. Většinou takovou, že se chová a vypadá jako on sám.

Většina týmů si najde čas na zákazníka, až když je pozdě.

Ptejte se: „Co se zdá být nemožné, ale kdyby to bylo možné, změnilo by to váš obchod k lepšímu?“

Pokud nevíte kam jdete, je jakákoliv cesta dobrá

Když většina týmů uslyší startovní výstřel, začne vymýšlet jak dosáhnout cíle. A to ještě předtím, než pochopí co ten cíl vlastně je.

Tím že jasně nadefinujete úspěch, zvyšuje pravděpodobnost jeho dosažení.

[na začátku projektu potřebujete]

  1. Úplný seznam kritérií úspěchu
  2. Jasný popis každého kritéria
  3. Konkrétní, měřitelný a proveditelný cíl pro každé kritérium
  4. Nejnižší přijatelnou úroveň u každého kritéria
  5. Priority alespoň u tří nejdůležitějších kriterií

Nedostatek cílů je jedna z příčin selhání, které můžeme úplně zabránit.

Komunikace? My musíme dělat opravdickou práci

Největší problém komunikace je iluze, že proběhla. G.B. Shaw

Špatná komunikace je další příčina selhání, které se dá zabránit.

Vedoucí projektu musí vést […] To se nedá dělat zpoza klávesnice.

Na co plánovat? Vrhněme se do toho.

Nedbale plánovaný projekt trvá třikrát tak dlouho, než se čekalo. Pečlivě plánovaný jenom dvakrát. Golubův zákon

Proč je tak těžké vytvořit většinu plánů? Protože má většina projektů zpoždění už na začátku.

  1. Zastav se!
  2. Zamysli se! (alespoň na nanosekundu)
  3. Teprv pak konej

Musíme se ujistit, že všichni sdílejí společnou halucinaci o tom, jak bude úspěch projektu vypadat, znít, chutnat, vonět a cítit.

Rizika? Co by se asi tak mohlo stát?

Největší chyba v řízení projektu je identifikovat rizika, ale nic kolem toho neudělat.

Kde je riziko, tam je možnost úspěchu.

Priority? Všechno je priorita číslo 1!

Prioritizace není něco co děláme, abychom vytvořili seznam věcí na které se vykašleme. Nutí nás zamyslet se, co je důležité a na co máme zaměřit naše omezené zdroje.

Změny? Co myslíte tím, že se věci změnily?

Jenom pitomec si může setrvávat v bludu, že projekt může být počat, nedefinován, naplánován a proveden beze změn.

Předpoklady jsou matkou katastrofy

Žádosti jsou obvykle zamítnuty pokud jsou podány poprvé. Na školení „Jsem vedoucí“ se lidé učí zamítnout jakoukoliv žádost o jakékoliv zdroje. … Vyhrajete, pokud zažádáte víckrát, než kolikrát oni zamítnou.

Cože to očekáváte?

Tajemství na spokojeného zákazníka je slíbit míň a dodat víc.

Pokud jde o zákazníka, neexistuje požadavek, specifikace nebo dohoda, u které nemůže dojít k nedorozumění.

Nepoučení pro příště

Učte se ze zkušeností. Pokaždé dělejte nové a víc vzrušující chyby.

Selhání kvůli neočekávaným překvapením zklame. Ale selhání kvůli předvídatelným, vyhnutelným a opakovaným chybám vážně podkopává morálku a produktivitu.

Zatrolený CAP

S tím jak se šíří cloudové šílenství, čím dál tím víc lidí naráží do CAP teorému. Mě i mé kolegy nevyjímaje. Tak jsem si řekl, že si to tu vyjasním.

CAP teorém zjednodušeně říká, že distribuovaný systém, nemůže splňovat všechny tři následující vlastnosti:

  1. Consistency – konzistence – všichni klienti vidí stejná data
  2. Availability – dostupnost – každý klient může vždy číst i zapisovat
  3. Partition tolerance – odolnost vůči rozdělení – systém funguje i když se rozpadne na víc nezávislých častí

Jak sami vidíte, není to zrovna moc exaktně definováno. Co to znamená, že systém funguje při rozpadu na víc částí? Čert ví. Často se také liší definice jednotlivých vlastností. Ale nevadí, že je to vágní. I tak je důležité je mít tu větu neustále na mysli, když se pokoušíte o distribuovaný systém.

Pro jednoduchost si představme, že máme dva databázové servery, kterým budeme říkat třeba Anička a Bohouš. Takže máme Aničku a Bohouše a samozřejmě chceme, aby klienti mohli zapisovat a číst přes oba dva. Taky chceme, aby Anička poznala, že se Bohouš rozbil, a vzala to na chvíli místo něj. No a pak jsou tu tak samozřejmé požadavky, že je snad ani nemusíme zmiňovat. Jako třeba, že se nám nesmí ztrácet data. Když něco zapíšeme, chceme, aby to tam zůstalo. A samozřejmě potřebujeme, aby to všechno jelo tak rychle, jak je jen možné. To se snad rozumí samo sebou.

Problém je, že to nejde.

V první řadě nedokážeme zajistit konzistenci, pokud se provádí zápis přes víc serverů. Nebo přesněji, nedokážeme to pokud chceme změny provádět rychle. Kvůli konzistenci musíme totiž zajistit, že nepřijmeme konfliktní zápisy. Jako třeba pokus o současné převedení peněz z jednoho účtu. Pokud to provede zároveň pro různé požadavky Anička i Pepík tak nakonec může účet skončit v mínusu, i když je to zakázané.

Tento problém se obvykle řeší tak, že všechny změny provádí jen jeden server. Ten už si dokáže ohlídat, jestli se někdo nesnaží dostat do mínusu. Nebo musíme dělat něco jako distribuované transakce, což je nejen pomalejší, ale stejně to zatíží všechny servery, takže se to ani nevyplatí.

Tím pádem můžeme zjednodušeně říci, že pokud vyžadujeme konzistenci, všechny zápisy musí jít přes jeden server. Třeba přes Aničku. Stačí to aby se zajistila konzistence? Přijde na to. Pokud klienti čtou z Bohouše, tak mohou vidět stará data. Pokud se jedná o milisekundy, tak se vždycky můžeme vymlouvat, že ten zápis proběhl až potom, co začali číst. Potíže nastanou, když je zpoždění větší nebo když něco zapíšu na jednom serveru a pak to na tom druhém ještě nevidím. Oboje jsou to řešitelné problémy. Třeba tak, že Bohouš pozná, že má stará data, a v takovém případě přepošle požadavky na Aničku.

Skvělé, takže jsem vyřešili konzistenci, co ostatní požadavky? Dostupnost? Brnkačka. Bohouš pozná, že Anička odpadla a vezme práci za ní. Tak to obvykle řeší klasické relační databáze. Tak v čem je problém?

Samozřejmě, že v té odolnosti vůči rozdělení. Když se nám rozpadne spojení mezi oběma servery, můžou si oba myslet, že je ten druhý spinká a začnou přijímat zápisy. Což nám rozbije konzistenci. Tento problém se před pár lety nemusel řešit. Oba servery obvykle byly spojeny superrychlým superspolehlivým spojením, takže byl rozpad clusteru nepravděpodobný. Dnes ale žijeme v době superlevného superšitoidního cloudu, takže se sít rozpadne několikrát za týden.

Co s tím? Třeba MongoDB to řeší tak, že nás nutí mít lichý počet serverů. Přijímat zápisy pak může jenom ten, co vidí většinu nodů v clusteru. Takže Anička s Bohoušem musí pozvat Cecilku a zapisovat bude jen ten, co vidí alespoň jednoho dalšího. Pokud se síť rozpadne, servery se rozhlédnou a pokud vidí alespoň jeden jiný server, tak se s ním dohodnou, kdo z nich bude zapisovat.

Skvělé, to vypadá, jako kdybychom měli splněné všechny tři podmínky. Ha, hloupá CAP věta vyvrácena. Nebo ne? Bohužel ne. Tím, že jsme si zajistili odolnost vůči rozpadnutí jsme se připravili o dostupnost. Pokud totiž žádný server nevidí většinu clusteru nebude zapisovat nikdo z nich. Sakra.

Pokud máme nespolehlivou síť, tak si prostě musíme vybrat mezi případnou nekonzistencí, která hrozí třeba u MySQL nebo nedostupností, která hrozí u MongoDB. Prostě a zkrátka, pokud se spolu servery nemůžou domluvit, tak jich buď může zapisovat víc nebo žádný.

Zajímavá varianta je, rovnou se vzdát konzistence. To dělá třeba CouchDB. Pokud dojde v konfliktu v datech, tak to jednoduše uloží jako různé verze jednoho dokumentu a řešení hodí na někoho jiného. Buď na aplikaci nebo rovnou na uživatele. Tím pádem nemají problém v tom zajistit dostupnost a odolnost vůči síťovým problémům. Takže třeba můžou mít offline kopii, která se po připojení do online režimu automaticky sesynchronizuje.

Mám dojem, že jsem vyčerpal nejen vás, ale i všechny možnosti. Nebo jsem na něco zapomněl? Nezmínil jsem sharding. Ale sharding nám v podstatě jenom zajistí rozpad jednoho distribuvaného systému na víc nezávislých. Tím si sice rozložíme zátěž, ale CAP teorém nám to neobejde. Na ten budeme nadále narážet v rámci jednotlivých shardů. Hmm, už mě nic nenapadá, tak vás jen poprosím. Nepokoušejte se psát systémy, které jsou konzistentní, stále dostupné a vydrží rozpad sítě. Opravdu to nejde.

Odkazy:

Jak si MongDB replica set volí mastera

Pěkné přirovnání na CAP teorém

Obrazový průvodce NoSQL systémy

Vyvrácení CAP teorému

REST API versioning and hypermedia

Recently, I have been thinking about REST API versioning. I have read several books and articles but the issue is far from straightforward.

Let’s start with a simple example – a petclinic. We will have two resources, pets and owners.

/pets/123  % supports GET, PUT, DELETE
{
   "pet":{
      "name":"Pluto",
      "owner":"/owners/543"
   }
}
/owners/543 % supports GET, PUT, DELETE
{
   "owner":{
      "name":"Norm Ferguson",
      "pets":[
         "/pets/123"
      ]
   }
}

We have a nice and clean REST API. It’s at least level 3 in the Richardson maturity model. Maybe higher.

Our customer is happy, the API is quite popular, but here it comes. A change request. We need to support more owners for a pet. No problem, we can change the resource representation like this.

{
   "pet":{
      "name":"Pluto",
      "owners":[
         "/owners/543"
      ]
   }
}

But it’s a backward incompatible change and we do not want break applications written for the old API. The only option is to create a new version. But how? We have several possibilities.

Version as part of the URI

The Apigee book recommends to add the version to the URI. Something like

/v2/pets/123  % supports GET, PUT, DELETE
{
   "pet":{
      "name":"Pluto",
      "owners":[
         "/owners/543"
      ]
   }
}

You see, I have added version number to the URI. So the clients that support the new API can use v2. But wait, what happens, if a client navigates from v2/pets to owners and back to the pets resource? He will end-up in the first version of the API! We do not want that. It’s inconsistent. To fix this problem, we have to update versions of all resources in our application! The trouble is that we also have to change all the URIs in the resource representations. So the new API would look like this

/v2/pets/123  % supports GET, PUT, DELETE
{
   "pet":{
      "name":"Pluto",
      "owner":"/v2/owners/543"
   }
}
/v2/owners/543 % supports GET, PUT, DELETE
{
   "pet":{
      "name":"Pluto",
      "owners":[
         "/v2/owners/543"
      ]
   }
}

All the resource URI have changed and also all the URIs in the JSON. This change might be easy to implement in a small homogeneous system but will be quite complicated in a system which is composed from several complex modules written in different programming languages. So if you have a REST API which is interconnected by hyperlinks, URI-based versioning will be quite costly. What are the alternatives?

Version as request parameter

/pets/123?v=2
I do not like this one. It’s basically a variant of URI-based versioning. Yes, a client can add the version parameter to every request. But it goes against the notion of hypermedia. The client should not be forced to change the request URI. He should be able to navigate using the links contained in the resource representation.

Version in the header

The other option is to use an HTTP header. The client can pass x-pet-version=2 header with every request and the server will know which version to return. The client usually already uses an HTTP Accept header for content negotiation so one more header should not be a problem. There are several issues with this approach.

The first one is the default value. It’s not clear, what to do, if the client does not send the header. If we serve him the last version, we will break such clients by every API update.

It is also less comfortable to browse older version of such API using the browser.

Version in the domain name

My favorite approach is to use different domain name for each version. Something like http://v2.petclinic.example.org/pets/123. It’s easy to use, easy to implement and easy to change the version. If you use relative URIs, you just need to change the resource that needs to be changed and the rest works automatically. Theoretically, you can even use old code-base on the old domain name and new code-base for the new version.

I am just surprised that this approach is not used more often. If you check popular APIs they usually pick the URI-based approach or HTTP headers. Is there some problem with this approach I do not see?

Sources:

Web API Design: Crafting Interfaces that Developers Love

The REST API Design Handbook