Dzisiaj, opowiem o tym, czym jest HATEOAS oraz dlaczego standard HAL (Hypertext Application Language) powinien być implementowany w każdym API dla protokołu HTTP. Jeżeli nie wiesz czym HATEOS jest, to koniecznie sprawdź ten artykuł, aby budować prawdziwe RESTful API.
Słowem wstępu
Chyba każdy backend-owiec wie czym jest REST API, a jeżeli jeszcze o tym nie słyszał, to na pewno budował API dla protokołu HTTP. Dzisiaj chciałbym poruszyć temat czegoś, co nazywa się HATEOAS, czyli Hypermedia As The Engine Of Application State i czego stosowanie naprawdę zamienia nasz interfejs do komunikacji po HTTP w RESTful API.
Sprawdź także inne wpisy o tym, jak poprawnie budować warstwę UI w swoich aplikacjach.
Co to jest API?
Jednak zanim przejdziemy do mięsa wpisu, to chciałbym abyśmy wszyscy zaczęli jego lekturę na tym samym poziomie. We wstępie, użyłem określenia API, czyli Application Programming Interface, co po przełożeniu na język polski da nam: interfejs programowania aplikacji.
Jeżeli chcemy skomunikować ze sobą dwie aplikacje, systemy czy moduły, to muszą one udostępnić możliwość pobierania dostępnych zasobów oraz sterowania swoim stanem. Taki mechanizm nazywamy interfejsem do komunikacji, czyli API. Może on zostać zaimplementowany na wysokim poziomie systemu i być dedykowany np. dla protokołu HTTP, brokera event-ów czy jeszcze innych kanałów komunikacyjnych.
API vs. REST API
REST API, czyli Representational State Transfer API, jest to standard budowy interfejsów komunikacyjnych po protokole HTTP. Ten styl architektoniczny zakłada, że nasze API będzie zbudowane zgodnie z sześcioma regułami działania. Jeżeli stworzymy nasze API dla HTTP zgodnie z wytycznymi REST-a, to nasz interfejs będziemy mogli określić mianem RESTFUL. Wspomniane zasady budowy to:
- Klient – Serwer, czyli podział aplikacji na stronę kliencką (frontend) oraz serwerową (backend). Chodzi tutaj o rozdział obowiązków wywoływania żądań i ich niskopoziomej obsługi na dwie instancje. Klient wysyła żądanie, a serwer je odpowiednio obsługuje.
- Bezstanowość, to zasada, która mówi nam, że część serwerowa powinna być w pełni niezależna od strony klienckiej. Każde zapytanie wysłane z frontend-u powinno zawierać komplet informacji niezbędnych do zakończenia żądania przez backend. Nie powinniśmy np. zapisywać danych pomocniczych w sesji, po stronie serwera.
- Pamięć podręczna klienta, czyli bardziej zalecenie, aniżeli reguła, mówiące nam o tym, że powinniśmy cache-ować dane po stronie frontend-u, aby ograniczyć czas ładowania. Odpowiedź backend-u każdorazowo powinna zawierać informacje czy może zostać zapisana w pamięci podręcznej klienta.
- Warstwowość, to reguła, która każe zakładać, że klient nie może być pewien, że rozmawia bezpośrednio z serwerem. Zapewnia to możliwość wprowadzenia różnych mechanizmów, które służyć będą jako pośrednicy np. do zwiększenia wydajności czy poprawy skalowalności (load balancer-y).
- Kod na żądanie, czyli możliwość uruchomienia otrzymanej z backend-u odpowiedzi, która jest kodem, np. JavaScript.
- Spójny interfejs, nasza ostatnia zasada REST, która będzie dziś dla nas tą najważniejszą. Mówi ona o tym, że zasoby muszą być dostępne przy pomocy określonego adresu URI, wywołanego odpowiednią metodą HTTP, a odpowiedź zawiera odnośniki do innych akcji lub powiązanych zasobów (HATEOAS). Budowa spójnego interfejsu odbywa się na podstawie odrębnych zasad, np. stosowanie metody PUT przy aktualizacji zasobu, kod 404, gdy zasób nie został znaleziony i inne.
Model dojrzałości Richardson-a
Istnieje pewna miara, mówiąca nam o tym na ile nasze API jest RESTFUL. Nazywa się ona modelem dojrzałości Richardson-a (RMM – Richardson Maturity Level) i składa się z czterech poziomów, od 0 do 3.

- Poziom 0 – czyli o REST nie ma tutaj nawet mowy. Poziom określony mianem bagna, zaliczamy do niego np. komunikację po protokole SOAP (kochane xml-e). Do komunikacji używane są np. tylko metody GET oraz POST, status odpowiedzi to zawsze 200, a URI manipulacyjne wyglądają np. tak: POST: /moje-api/pobierz-uzytkownika. Nie wspominając już o standaryzacji struktury odpowiedzi.
- Poziom 1 – nadal mamy bagno, ale już nie takie wielkie. Nasze API definiuje bowiem zasób, bądź operację, a adresy URI są do tego dostosowane. Wciąż jednak wykorzystujemy tylko dwie metody: GET i POST i nie posiadamy standaryzacji odpowiedzi. Przykładowy endpoint na tym poziomie może wyglądać tak: POST /moje-api/uzytkownicy/:id.
- Poziom 2 – jest dobrze, ale nie idealnie i tak wygląda większość „REST-ów”. Wprowadzamy semantykę, komunikację za pomocą innych metod HTTP, np. DELETE, PUT czy PATCH, a także dostosowujemy kody odpowiedzi adekwatnie do metody zapytania. Przykładowy call do API na tym poziomie wygląda tak: GET /moje-api/uzytkownicy/:id, a także mamy całkowitą standaryzację response code’ów:
- 1XX – informacje,
- 2XX – powodzenie,
- 3XX – przekierowania,
- 4XX – błędy związane z niepoprawnym zapytaniem,
- 5XX – błędy po stronie serwera.
- Poziom 3 – możemy powiedzieć, że nasze API jest RESTFUL. Na tym poziomie wymagane jest, aby aplikacja implementowała HATEOAS. Wielu programistów pomija ten poziom, ponieważ jego wprowadzenie wiąże się ze znacznym skomplikowaniem development-u i utrzymania interfejsu. Warto pamiętać, że pomijając implementację HATEOAS-a godzimy się z faktem, że nasze API nie jest zgodne z REST.
Więcej o modelu dojrzałości Richardson-a możesz dowiedzieć się tutaj.
Czym w praktyce jest HATEOAS?
Jak wspomniałem, HATEOAS jest to podejście standaryzujące sposób, w jaki klienty mogą poruszać się po naszym API. W każdej odpowiedzi od serwera powinny zostać zawarte odnośniki do powiązanych zasobów, a także operacji. Upraszczając to jeszcze bardziej, w zwrotce umieszczamy linki do owych miejsc po stronie serwerowej. Ułatwia to stronie klienckiej generowanie chociażby przycisków do zarządzania zasobem, np. automatyczne generowanie button-a usuń, jeśli w linkach znajduje się odnośnik do tej operacji. Dodatkowo, zmniejszamy rozmiar dokumentacji naszego API.
Przykładowy response z backend-u, który spełnia HATEOAS, może wyglądać następująco:
{
"id": 230,
"name": "Jan Kowalski",
"links": {
{ "rel": "delete", "href": "/api/v1/users/230", "method": "DELETE" }
{ "rel": "self", "href": "/api/v1/users/230", "method": "GET" }
}
}
Jak się pewnie domyślasz, w celu uniknięciu wymyślania koła na nowo, za każdym razem, kiedy chcemy zaimplementować ten mechanizm, powstała również standaryzacja sposobu budowania HATEOAS-a, a nazywa się ona HAL.
HAL vs. HATEOAS
HAL, czyli język znaczników hipertekstu aplikacji. Został zaproponowany, jako standard budowania hypermediów, HATEOAS-a, w odpowiedziach serwera. Zakłada on posiadanie dwóch głównych pól w każdej zwrotce backend-u: _links oraz _embeded. W sekcji _links znajdują się odnośniki do powiązanych zasobów oraz operacji, zaś w _embeded możemy umieścić obiekty wchodzące w relacje z zasobem nadrzędnym. Przykładowa struktura odpowiedzi, w standardzie HAL, może wyglądać następująco:
// single resource
GET /api/v1/users/230
{
"_links": {
"self": {
"href": "/api/v1/users/230"
}
},
"_embedded": {
"address": {
"_links": {
"self": {
"href": "/api/v1/users/230/address"
}
},
"street": "Krajobrazowa",
"city": "Warszawa"
}
},
"id": 230,
"name": "Jan Kowalski"
}
// collection
GET /api/v1/users
{
"_links": {
"self": {
"href": "/api/v1/users/230"
}
"next": {
"href": "/api/v1/users/231"
},
"prev": {
"href": "/api/v1/users/228"
},
"first": {
"href": "/api/v1/users/3"
},
"last": {
"href": "/api/v1/users/1203"
}
},
"_embedded": {
"address": {
"_links": {
"self": {
"href": "/api/v1/users/230/address"
}
},
"street": "Krajobrazowa",
"city": "Warszawa"
}
},
"id": 230,
"name": "Jan Kowalski"
}
Podsumowanie
- API jest to interfejs komunikacyjny aplikacji, który może zostać zbudowany na dowolnym kanale komunikacyjnym.
- REST API, to standard budowy API po protokole HTTP.
- Aby budować prawdziwe interfejsy RESTFUL, należy wdrażać HATEOAS, czyli 3 poziom RMM.
- HAL, to standaryzacja w sposobie budowania odpowiedzi typu hypermedia.
Dodaj komentarz