TL;DR
REST je takový soubor pravidel, jak by měla fungovat komunikace mezi jednotlivými aplikacemi a službami. Každý objekt, který je součástí této komunikace se nazývá zdroj. Jednotlivé zdroje mohou mít různé reprezentace a k manipulaci s nimi používáme HTTP metody.
Díky dodržování REST principů jsme schopni jednoduše škálovat celou komunikaci, rozdělovat aplikaci do oddělených komponent, které spolu komunikují přes definované rozhraní (API) a šetřit bandwidth tím, že efektivně využíváme cache.
HTTP metody: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
Základní koncepty
REST je architektura (nikoliv návrhový vzor) pro implementaci API, kterou vymyslel Roy Fielding. HTTP protokol je vlastně implementací REST principů.
- zkratka pro REpresentational State Transfer
Klíčové vlastnosti:
- Využívá HTTP protokol, ideální pro CRUD operace
- Umožňuje škálování (síťová infrastruktura, servery, software)
- Umožňuje snadné nasazení nezávislých komponent (např. Microservices)
- Dneska už by měl být standard implementovat API pro aplikaci, aby nebylo potřeba využívat grafické rozhraní
Architektonické principy
Architektura klient-server
- Každá strana má jiné zodpovědnosti
- Middleware - funkcionalita “mezi”, která probíhá mezi komunikací
Vrstvená architektura
- Podporuje vrstvenou architekturu (mezi vrstvami bude middleware)
- Jako uživatel nevnímám cache proxy, loadbalancer, autorizační server
- Je to velmi dobře škálovatelné - autorizační “krabičku” vyměním za jinou
Bezstavovost (Statelessness)
- Požadavek obsahuje všechny informace (autorizaci, token, ID zdroje apod.)
- Umožňuje jednoduše škálovat mezi servery
- U Session (která řeší stavy webových aplikací) se to řeší většinou s:
- Centrální databází uložených sessions
- Bezstavové tokeny (JWT apod.), které všechny informace obsahují v sobě
Uniformní rozhraní
Konečný počet operací - požadavek na REST.
Založené na zdrojích:
- Zdroj může být cokoliv (fyzická osoba, entita)
- Zdroje mají svoje identifikátory (URL, URI, URN, IRI)
- Zdroje mají reprezentaci (HTML, JSON…) a svoje metadata (media type (IANA), čas poslední úpravy, zdrojový odkaz)
- Jeden zdroj může mít více různých reprezentací (jak textové, tak binární)
- Klienti si o ně říkají např. pomocí
Accepthlaviček - Měly by být v rámci IANA
- Klienti si o ně říkají např. pomocí
- O reprezentaci se informuje hlavičkou
Accept(klient, “co chci”) aContent-Type(server, “co posílám”)
- Jeden zdroj může mít více různých reprezentací (jak textové, tak binární)
- Co se s tím zdrojem bude dít, je dáno REST metodou
- Názvy zdrojů by neměly implikovat způsob použití - ne
/getOrders, ale spíšGET /orders
Stavy zdrojů:
- Zdroje mohou mít různé stavy, a každý stav může mít více reprezentací
- Např. zdroj
/ordersmá stav s 2 objednávkami, pak se stav změní na 3 objednávky apod. - Unsafe REST metody vlastně mění stav zdroje
- Změna stavu zdroje vlastně změní i stav aplikace
Metadata zdrojů:
- Mohou být definovány HTTP hlavičkou nebo být součástí datového formátu (např. meta tagy v HTML)
HATEOAS (Hypertext As The Engine Of Application State)
- HATEOAS na Wikipedii, jedná se o klíčový princip RESTu
- Všechny následující a možné akce vztahující se k vrácenému zdroji jsou obsažené v odpovědi vrácené serverem
- Tedy se vždy posílá “stav zdroje” (vyjádřený pomocí HTTP metadat a možných odkazů na přidružené zdroje a navazující REST operace) a není potřeba Session pro informace o stavu zdroje (ale samozřejmě to má svoje limity)
- Ale HATEOAS se velmi dobře škáluje (narozdíl od Session) - pořád je 100 % stateless
- Pomocí odkazů se dají také získávat relevantní data, metadata či akce k odpovědi
- Moje myšlenka:
- Stejně jako odkazy velmi usnadňují navigaci na webových stránkách (vidím odkazy na relevantní zdroje, na košík, na platbu apod.)
- Tak stejně funguje HATEOAS
Atom Syndication format
- Jeden ze způsobů, jak reprezentovat odkazy, které v rámci HATEOAS chodí s response
- Jedná se o standardizovaný formát (původně využívaný na RSS), založený na XML
- Je standardizovaný, ale už se tolik nepoužívá (spíš HAL a JSON-LD se teď používají)
rel- název odkazu, který většinou sémanticky ukazuje smysl/operaci daného linku- např. pro navigaci může mít
relhodnoty: next, previous, self - nebo může mít název: addItem, deleteOrder atd.
- např. pro navigaci může mít
href- URI na zdroj, na který link ukazujetype- jaký je media type zdroje, na který link ukazuje
- Alternativa je uvádět odkazy přímo v hlavičce response (mají stejnou sémantiku jako Atom, ale není kvůli nim potřeba načítat celý zdroj, ale jenom hlavičku (pomocí HEAD metody))
HAL (Hypertext Application Language)
- Společně s response posílá
_links- odkazy na další zdroje- Může sám na sebe (“self”), nebo na edit, remove apod.
- Vždy by to měly být validní zdroje v rámci práv (pro konkrétního uživatele)
- Nebo posílá
_embedded- navazující/vnořené entity (např. další položka u stránkování) - V praxi: Tyto principy jsou fajn na to být fakt RESTful, ale v praxi se používá spíše jednoduchý JSON a dokumentace ve stylu Swagger/OpenAPI
Cacheovatelnost
V rámci RESTu se pracuje primárně s daty, to se dá snadno cachovat. Správné je vždy cachovat statické zdroje. Dynamické zdroje se dají cachovat také (a implementovat mechanismy jako timestamps, ETags apod.)
Cache-Control direktivy:
- uvádí informace pro Proxy, přes které požadavek prochází
private- žádná proxy nesmí cachovat, pouze klientpublic- každý si ji může zacachovat i routery po cestěno-cache- nesmí se cachovat, pokud se zacachuje, musí se vždy revalidovat
max-age=<seconds>- životnost cache- Lze ji “vynutit” tím, že na serveru nastavím reject limit, aby mi klienti nevytěžovali zbytečně prostředky
- Je důležité mít rozumně nastavené age-limit
- A tím pádem si budou klienti více cachovat, aby se nemuseli tolik dotazovat serveru (samozřejmě po čase je to nutné a k tomu právě slouží ten age-limit)
Revalidace:
- Má smysl cachovat i na 0 sekund (tím říkám, že pokaždé je potřeba revalidovat zdroj u serveru)
- Když se znovu dotážu (
If-Modified-Since) a pokud se obsah nezměnil, pošle se 304 (nic se nezměnilo = využij to, co máš zacachované)- pro ETagy se používá
If-None-Match - tomuto se říká Conditional GET
- chci zdroj jenom pokud se změnil - pokud se nezměnil od současné verze, kterou mám jako klient u sebe, tak použiju svoji zacachovanou verzi (je to rychlejší + šetřím zdroje serveru)
- pro ETagy se používá
max-age=0amust-revalidateje to stejné
ETag (novější než Last-Modified):
- Může to být ID nebo hash
- Užitečné, když se mění data opravdu rychle - LastModifiedTime nerozliší svojí přesností, jestli se něco změnilo
- Strong Etag - jakmile se změní 1 bit obsahu, ihned je jiný (je to hash)
- Weak ETag - dojde ke změně, ale ta není tak důležitá pro znovu-generování stránky
- definuje ho samotná aplikační logika, která si to řeší sama
- např. se hodí pro složené objekty (např. změna pořadí není potřeba stahovat nový zdroj), ale pokud se přidá např. nový článek, tak už se Weak ETag změní a bude se revalidovat
- pokud server pošle oboje (Last-Modified-Since a ETag), což by ideálně měl, tak Etag má preferenci
Proxy cache:
- První uživatel dostane celou stránku a další dostanou už tu zacachovanou (používá se například ve firmách kdy více uživatelů opakovaně přistupuje na ty stejné stránky)
Souběžnost (Concurrency)
- Nastává v momentě, kdy 2 klienti aktualizují jeden zdroj ve stejnou chvíli (či jeden aktualizuje a druhý čte apod.)
- řeší se pomocí Conditional PUT, kdy se aktualizuje pouze pokud se zdroj nezměnil od určitého data či jestli sedí ETagy
If-Unmodified-SinceaIf-Matchhlavičky
- pokud ne, tak je to 412 Precondition Failed
- protože je zdroj novější než ho mám u sebe a je riziko toho, že budu přepisovat (a ani nevím co)
- řeší se pomocí Conditional PUT, kdy se aktualizuje pouze pokud se zdroj nezměnil od určitého data či jestli sedí ETagy
HTTP metody
Vlastnosti metod
- Safe (bezpečné) - můžeme zacachovat, nemění stav zdroje (pouze na čtení)
- Idempotentní - opakované volání metody má pořád stejný efekt (zdroj zůstává ve stejném stavu)
- Výsledek může být jiný, jde o ten efekt, nemá měnit stav aplikace
GET
- Požadavek na reprezentaci aktuálního stavu zdroje (reprezentován URI)
- Idempotentní i safe
- Možnost partial GET (když se stahování přeruší nebo stahuji z více zdrojů najednou)
- Dá se dobře cachovat - ptám se serveru, aby mi poslal soubor jenom pokud není mladší než X
POST
- Odeslání informací v těle požadavku na server ke zpracování
- Používá se k vytvoření nového prvku v kolekci
- Server novému zdroji vygeneruje nové ID (klient pouze dodá obsah a URL)
- Není safe a není idempotentní
PUT
- Používá se pro plnou náhradu či vytvoření celého zdroje
- Pokud ID neexistuje, tak se automaticky vytvoří
- Idempotentní
DELETE
- Smazání zdroje
- Idempotentní - 2x smažu a výsledek je pořád stejný (je to smazané)
PATCH
- Používá se pro částečnou změnu zdroje
- Není idempotentní ani safe
- Příklad: změna množství o 50ml - opakování akce vždy zvýší množství o 50ml (PUT by to nastavil vždy na stejnou hodnotu)
- Formát requestu může být v podstatě jakýkoliv - záleží na konkrétní implementaci (bude v dokumentaci)
HEAD
- Stejné jako GET, ale pouze pro hlavičky
- Safe
- Např. pro zjištění ETagu u velkého zdroje - stahovat chci pouze pokud existuje nová verze (šetřím bandwidth)
OPTIONS
- Požadavek na informace o možnostech zdroje (URI)
- Zjištění, jaké HTTP metody mohu používat
- Safe
- Dá se použít jako
ping
REST antipatterns
Spoustu firem a programátorů v praxi porušují pravidla REST → vytvářejí se antipatterny.
Příklady:
GET /orders/?add=new_order- používání metod k jinému účelu než mají sloužit- Rozbíjí to idempotenci a safe/unsafe
- GET požadavky se běžně cachují (např. pomocí proxies) a pak se zacachuje přidání nového zdroje (to není žádoucí chování)
Poznámka: Existují už hodně zavedené antipatterny, které se rozšířeně používají. Je důležité vědět, proč a jak by se to mělo dělat správně.