Jakarta RESTful Web Service (JAX-RS) w Javie

Usługi sieciowe (ang. Web services) są podstawą rozwiązań aplikacyjnych w architekturze klient-serwer (client-server). Komunikacja odbywa się z użyciem protokołu HTTP, a dane zazwyczaj przesyłane są w formacie XML, JSON. W tej części skupimy się na REST (ang. Representational State Transfer). Web services wchodzą w skład Jakarta Enterprise Edition (Jakarta EE).

Czym jest REST API?

REST API opracował Roy Thomas Fielding i zaprezentował go w 2000 roku.

REST API służy do łączenia wymiany danych, czyli integracji różnych komponentów mikrousług (ang. Microservices). Możemy powiedzieć, że jest to swoisty interfejs łączący w architekturze klient-serwer. Dzięki zastosowaniu tego modelu klient jest w stanie przesłać żądanie do serwera, który potrafi je obsłużyć.

Interfejs REST posiada zdefiniowane w specyfikacji reguły w jaki sposób mogą się ze sobą łączyć. Specyfikacja REST określa również sposoby wymiany komunikacji. Jego ogromną zaletą jest uniwersalność, dzięki czemu klienta można zaimplementować w innej technologii niż serwer. W związku z tym użyta technologia nie stanowi żadnej bariery przy wymianie danych pomiędzy różnymi usługami.

Reguły REST API

REST API została zaprojektowana tak, aby umożliwić aplikacjom działanie w architekturze klient-serwer. Jako klienta rozumiemy aplikację, która wysyła żądanie oraz uzyskuje dostęp do zasobu. Serwer w tej architekturze jest osobną aplikacją, która udostępnia swoje zasoby. Nie istnieje żadna oficjalna dokumentacja, a jedynie zbiór reguł, których należy przestrzegać przy projektowaniu aplikacji. Poniżej znajduje się ich lista.

Rozdzielenie klienta od serwera

Aplikacje klienta i serwera powinny stanowić osobny byt. Mówiąc inaczej warstwa, na której widoczny jest interfejs użytkownika powinna mieć całą logikę odseparowaną od warstwy serwerowej, która przechowuje dane (np. w bazie danych) oraz je udostępnia. Zasada ta jest obustronna.

Takie podejście niesie ze sobą cały szereg zalet. Najważniejszą z nich jest znacznie uproszczona integracja pomiędzy serwerem a klientem, a co za tym idzie utrzymanie systemu. Najprościej jest wyobrazić sobie, że dany serwer świadczy jakąś usługę. Nic nie stoi na przeszkodzie, aby napisać własną aplikację sieciową lub zwykłą, która korzysta z zasobów wspomnianego serwera. Jest to spowodowane tym, że nie jest potrzebna znajomość implementacji oprogramowania serwera.

Przykładem takie API może być rozkład komunikacji miejskiej. Przy odrobinie chęci można napisać własną aplikację, która będzie pobierała rozkłady jazdy z serwera. Mimo, że nie mamy wiedzy na temat implementacji serwera (technologii w jakiej został wykonany, języka programowania), to wiemy jaki jest zwracany format danych.

REST API rozdzielenie aplikacji klienta od serwera

REST API rozdzielenie aplikacji klienta od serwera

Jednolity interfejs

Dzięki zrealizowaniu poprzedniego punktu (rozdzielenie klienta od serwera) naturalna jest realizacja tej zasady. Dostęp do danego zasobu serwera jest identyczny niezależnie od aplikacji klienta. Może być to aplikacja mobilna na smartfona, gruby klient na komputer PC, a także aplikacja www.

Jedyne co należy zapewnić to zrozumiały komunikat do serwera, a on odpowie zawsze w ten sam sposób. Zwracany format danych będzie identyczny np. XML, JSON.

REST API jednolity interfejs

REST API jednolity interfejs

Brak mechanizmów przechowujących informacje o stanie

Serwer nie przechowuje informacji na temat sesji klienta. W związku z tym każde żądanie (ang. Request) ze strony klienta traktowane jest jako zupełnie niezależne od poprzedniego. Oznacza to, że kolejne żądanie nie zawiera żadnych informacji, które zostało wysłane w jednym z poprzedzających. Zatem, każde żądanie powinno zawierać komplet informacji niezbędnych do uzyskania dostępu do zasobu.

Jedną z zalet takiego podejścia jest brak potrzeby utrzymywania na serwerze sesji użytkownika, co pozytywnie wpływa na wydajność.

Buforowanie otrzymanych danych

Buforowanie danych otrzymywanych w odpowiedzi pozwala zwiększyć wydajność przez zmniejszenie liczby wysyłanych żądań ze strony klienta. Dzieje się tak, ponieważ, gdy serwer wysyłając odpowiedź zezwoli na jej buforowanie danych lokalnie, klient może skorzystać z nich wielokrotnie.

Serwer ma możliwość oznaczyć odpowiedź jako taką, której nie można cacheować. Pozwala to zapobiec przesyłaniu nieaktualnych informacji w kolejnym żądaniu.

REST API buforowanie danych

REST API buforowanie danych

Architektura warstwowa systemu

Komunikacja obu stron nie musi być bezpośrednia. Po drodze mogą występować różne mechanizmy, które występują, jednakże nie wpływają na samą treść komunikacji. Ta reguła zakłada, że logicznie obie aplikacje komunikują się bezpośrednio ze sobą, ale w praktyce po drodze mogą znaleźć się elementy pośredniczące.

Uruchamianie kodu na żądanie

W niektórych przypadkach w odpowiedzi zamiast statycznych danych w postaci XML, czy też JSON można uzyskać dynamiczną odpowiedź w postaci wykonywalnego kodu JavaScript, lub apletu Java. Z tego powodu taki kod powinien być wykonywany wyłącznie na jawne żądanie klienta.

W jaki sposób działa REST API?

Komunikacja w REST API odbywa się za pomocą protokołu HTTP. Oznacza to, że mamy do dyspozycji żądania, które on oferuje.

  • GET – przy jego pomocy dostaniemy się do zasobu. Jego użycie nie modyfikuje w żaden sposób zasobu. Pozwala go jedynie odczytać.
  • POST – pomoże utworzyć zasób na serwerze.
  • PUT – po wskazaniu klucza pomoże zaktualizować istniejący zasób.
  • DELETE – po wskazaniu zasobu pozwoli go usunąć.

Aby wysłać żądanie potrzebny będzie adres URI (ang. Uniform Resource Identifier), który umożliwia identyfikację zasobów na serwerze.

http://localhost:8080/NazwaAplikacji

JAX-RS – API Javy

Jest to API Javy, które umożliwia tworzenie aplikacji wykorzystując architekturę REST do tworzenia web service’ów. JAX-RS API używa adnotacji (ang. Annotations) Javy umożliwiając w ten sposób oznaczenie zasobu oraz metody, które można na nim użyć.

Adnotacje Javy używane przez JAX-RS

Poniżej znajduje się lista adnotacji JAX-RS, które mogą być użyte to tworzenia RESTful web service.

@Path – wartość tej adnotacji pokazuje relatywny adres URI. On z kolei wskazuje lokalizację klasy Javy na serwerze. Przykład użycia @Path("/zasoby"). Wtedy adres URI może wyglądać następująco.

http://localhost:8080/NazwaAplikacji/zasoby

@POST– metoda odpowiedzi od serwera. Odpowiada metodzie HTTP POST
@PUT – metoda odpowiedzi od serwera. Odpowiada metodzie HTTP PUT.
@DELETE – metoda odpowiedzi od serwera. Odpowiada metodzie HTTP DELETE.
@GET – metoda odpowiedzi od serwera. Odpowiada metodzie HTTP GET.
@HEAD – metoda odpowiedzi od serwera. Odpowiada metodzie HTTP HEAD. Użycie tej metody czasem może być wydajniejsze niż GET (np. w przypadku, gdy chcemy sprawdzić czy zasób istnieje).
@QueryParam – Parametr pochodzący z adresu URI, który można użyć w klasie źródłowej.
@Consumes – można oznaczyć typ MIME zasobu, wysłanego od klienta.
@Produces – można oznaczyć typ MIME zasobu, zwrócenego przez serwer.

Implementacja własnego web service REST w Javie

Po omówieniu części teoretycznej czas zaimplementować własny Web service REST. Pierwszym Web Service’m będzie prosta aplikacja wykorzystująca metodę GET i zwracająca po prostu tekst. Całość należy zacząć od założenia projektu w Eclipse. Pokazaliśmy to już w artykule na temat Servletów.

Kolejnym krokiem jest dodanie zwykłej klasy do projektu. Poniżej zamieszczamy jej treść.

Taką aplikację można już umieścić na serwerze. W tym celu klikamy Run on server oraz a następnie przerzucamy aplikację Usluga na prawo.

Efekty możemy zobaczyć na dwa sposoby. Pierwszym z nich jest użycie przeglądarki internetowej oraz wpisanie adresu
http://localhost:8080/Usluga/gory/lista
Innym sposobem jest użycie narzędzia curl. W wierszu poleceń należy wpisać
curl -X GET "http://localhost:8080/Usluga/gory/lista"
Narzędzie to przyda się przy testowaniu innych metod HTTP, gdyż dzięki niemu nie trzeba będzie specjalnie tworzyć aplikacji klienta.

Uwaga, jeśli podczas próby uruchomienia aplikacji na serwerze wystąpi błąd Unsupported class file major version 61 lub podobny, to prawdopodobnie wersja zainstalowanej Javy jest niekompatybilna z tą używaną przez TomEE. Może się zdarzyć, że najnowsza wersja Javy będzie znacznie nowsza niż aktualne wydanie TomEE. W związku z tym zalecamy spojrzeć na datę wydania Javy oraz serwera TomEE.

W momencie pisania artykułu kompatybilna wersja Javy to 17.0.3.1, a TomEE 8.0.11 plume. Przy wyborze serwera TomEE należy również sprawdzić czy obsługuje REST. Należy pamiętać aby wybrać TomEE plume lub TomEE plus.

W przypadku błędu przy próbie zaimportowania pakietu javax.ws należy ściągnąć plik javax.ws.rs-api-2.1.jar, a następnie dodać go do ścieżki tak jak pokazaliśmy w artykule na temat baz danych.

Operacje CRUD w REST

Skrót CRUD oznacza tworzenie, odczyt, aktualizację oraz usuwanie z pamięci. Poniżej pokażemy prostą aplikację, która pokaże w jaki sposób można obsługiwać te operacje.

Poniżej znajdują się przykładowe operacje do wykonania przy pomocy narzędzia curl.

curl -X POST "http://localhost:8080/Usluga/solarsystem/add/1/"Merkury""
curl -X POST "http://localhost:8080/Usluga/solarsystem/add/2/"Wenus""
curl -X POST "http://localhost:8080/Usluga/solarsystem/add/3/"Ziemia""

curl -X GET "http://localhost:8080/Usluga/solarsystem/planets"
curl -X GET "http://localhost:8080/Usluga/solarsystem/planets/1/"

curl -X PUT "http://localhost:8080/Usluga/solarsystem/edit/3/"Planeta Ziemia""
curl -X DELETE "http://localhost:8080/Usluga/solarsystem/delete/1"