Tag Archives: REST

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

RESTované webové služby

Jak je o mě jistě známo, jsem svého druhu dinosaurus. Stále ještě programuji v Javě a dokonce používám takové předpotopní technologie, jakými jsou SOAP webové služby. Ale abych věděl, o čem všichni mluví, občas si přečtu i nějakou tu knihu o novinkách. Onehdá jsem studoval Scalu (vřele doporučuji) a teď jsem zrovna dočetl knihu RESTful Web Services od pánů Richardsona a Rubyho.

Pokud se chcete dozvědět co to vlastně ten REST je, tak je to ta pravá kniha pro vás. Dozvíte se tam hodně o filozofii RESTu, o jeho výhodách, o tom jak takové služby navrhovat a jaké technologie k tomu používat. Většina příkladů je sice napsána v jakémsi obskurním jazyce jménem Ruby, ale pochopí je i Javista.

Kniha je napsána i docela čtivě, musíte jenom překousnout demagogii typu „REST je nejlepší, RPC je k ničemu“. Co se mi hodně líbilo je to, že jsem snad konečně pochopil principy RESTu. Je to totiž jedna z těch technologií, o kterých všichni mluví, ale málokdo ji plně rozumí. Pokusím se tu shrnout to nejdůležitější.

Dělej to jako Web

To je asi hlavní krédo RESTovaných aplikací. Web funguje, je snadno použitelný, dobře škáluje, tak proč bychom nemohli dělat webové služby úplně stejně. Je tu veliký rozdíl v tom, že normální web je určen pro lidi a webové služby pro stroje. Ale proč nepoužít stejných osvědčených principů?

Zdrojově orientované

Zdrojově orientované je můj geniální překlad pro Resource-Oriented. Znamená to, že se místo na služby, orientujeme na zdroje. Místo na slovesa typu udělej, založ, smaž se soustředíme na podstatná jména typu účet, zákazník a podobně. Je to podobné jako objektové programování, v něm bychom se měli soustředit hlavně na objekty samotné, jejich operace by z nich měli vyplývat. Stejné princip by měl platit u RESTu. Tady musím dát příznivcům RESTu za pravdu, normální WS jsou silně funkcionální, o objektech tam není ani památky. Na druhou stranu je spousta služeb, které se jako RESTové jenom tváří, ale v podstatě jsou také jen RPC.

Adresovatelnost

Další důležitý princip je adresovatelnost. Každá věc která je v daném systému důležitá, by měla mít vlastní adresu. To znamená, že každá instance objektu, by měla mít vlastní URI. Hodně to souvisí s orientací na zdroje. Každý zdroj, má svoji adresu, například takovouto http://www.exaple.com/v1/bank/client/12345. Pokud použijeme přirovnání k objektům, tak URI je podobná referenci.

Propojenost (Connectedness)

Propojenost hodně souvisí s předchozími body. Znamená to, že jako výsledek operací dostávám odkazy na další zdroje. Pokud se například zeptám na seznam klientů banky, měl bych dostat seznam jejich URI a ne jen jejich ID. Pokud bereme URI jako referenci na instanci, tak to dává smysl.

Idempotentnost

Krásné české slovo označující, to, že by mělo být možné zavolat jednu službu vícekrát, aniž by hrozilo něco ošklivého. To znamená, že operace by pokud možno neměly mít žádné vedlejší účinky. Pokud mění stav systému, měly by to dělat tak, aby se nic nestalo, pokud ho změní vícekrát. Je překvapivé, že takových operací je valná většina. Nic se nestane, pokud například vícekrát nastavím stejnou hodnotu nebo něco smažu. Jsou tu samozřejmě výjimky, ale i u těch se dá idempotentnost zařídit.

Bezstavovost

Bezstavovost je často nepochopené téma. A není se čemu divit, je to hodně matoucí. Pokusím se to ale vysvětlit. Bezstavovostí se v tomto případě myslí, že webová služba neukládá stav klienta. Rozhodně tam není žádná session. Klient může navíc volat operace v libovolném pořadí, takže by se nemělo objevovat žádné flow jak ho známe z normálního webu. Ale aplikace samotná samozřejmě stav má. Takže si samosebou pamatuje, kolik kdo má na účtu, kam chce letět na dovolenou nebo kam rád chodí do restaurace. Ale nejedná se o stav klientské aplikace, jedná se o stav zdrojů. V tom je veliký rozdíl.

Bezstavovost je docela rozumný princip, díky ní se například dobře škáluje. Bohužel často k jejímu dodržení musíme švindlovat. Občas prostě potřebujeme držet stav klientské aplikace. V RESTu proto dělají krok stranou a prohlásí stav klienta za stav systému. Takže když potřebují uložit obsah nákupního košíku, řeknou, že to není stav klienta, ale stav aplikace. Nákupní košík je vlastně takový zdroj, jehož stav aplikace naprosto přirozeně drží. Takový košík má pak svoji URI, své operace a je to prostě normální zdroj. Problém vyřešen. Tedy kromě té škálovatelnosti, s ní jsem na tom úplně stejně jako kdybych používal session. (Kvízová otázka. Jak zajistit, aby přidání položky do nákupního košíku bylo idempotentní?)

No zbytek z těch asi 400 stránek tu opisovat nebudu. Radši napíšu proč se budu držet dál normálních (tlustých) webových služeb.

V první řadě se mi nelíbí úzká vazba na HTTP. To je jedna z věcí, která mi ještě nedošla, takže budu rád když mi ji někdo vysvětlí. Na jedné straně čtu, že REST je na HTTP úplně nezávislý. Na druhé straně, je ale REST s HTTP neuvěřitelně úzce spjat. Chcete-li změnit stav zdroje, máte použít HTTP PUT, chcete-li ho smazat, použije HTTP DELETE, chcete zjistit jeho stav, použijte GET. Nastane chyba? Dostanete ten a ten HTTP chybový kód. Tak je to závislé nebo ne? Mě spíš připadá že jo.

Dalším důvodem, proč zatím zůstanu u klasického ošklivého SOAPu je to, že mi vyhovuje. Já prostě nepíši aplikace, které jsou podobné webovým stránkám. Píši věci, které jsou svoji podstatou spíše procedurální než objektové. Navíc mi vyhovuje to, že existuje něco jako WSDL, které je sice ošklivé jak noc, ale které i přes své mouchy funguje. Ano pro REST máme WADL, ale že by byl široce rozšířen se říci nedá.

Také mi vyhovuje, že v SOAPu jsou metadata součástí zprávy a ne protokolu, kterým se zpráva posílá. Prostě mi to připadá bezpečnější. Když volám webovou službu tak volání bohužel často přejde přes několik JMS front a pár HTTP připojení. Takže se na to radši dívám tak, že posílám XML dokument, který přes nějaké složitosti snad doputuje k svému adresátu.

Tím rozhodně nechci říci, že je REST špatný. Pokud bych vystavoval webové služby pro něco, co má svůj ekvivalent ve webové aplikaci, asi bych nad RESTem vážně uvažoval. Protože se ale pohybuji v jiném světě, tak je moje volba jiná. To mi ale nebrání se poučit a pár fint od RESTu okoukat.