Dzisiejsza lekcja poświęcona jest web services — czyli sposobie na korzystanie z różnych usług ‘zdalnie’. Poznamy m.in. usługi typu REST oraz SOAP a także różnice pomiędzy nimi.
Ponieważ w przypadku usług webowych kod, którego będziemy używać, będzie się różnił w zależności od wykorzystywanego frameworka, na potrzeby tej lekcji będziemy korzystać z najbardziej podstawowych implementacji (czyli głównie bezpośrednio ze Spring’a).
Należy mieć jednak na uwadze, że często możemy potrzebować użyć innej biblioteki lub skorzystać z jakiejś funkcjonalności niedostępnej w wybranym przez nas narzędziu, z tego powodu tą lekcję należy traktować bardziej jako wprowadzenie do zagadnienia web services niż kompendium wiedzy o implementacji.
Lekcja
Na początku słowo wstępu o samych web services — zwane także usługami (usługami web, usługami zdalnymi). Idea jest podobna do naszych serwisów, które tworzyliśmy w Springu — czyli mamy pewną klasę, która udostępnia nam pewne operacje. Dokładnie to samo ma miejsce w przypadku web services, z tą różnicą, że kod nie jest wykonywany lokalnie w aplikacji, a zdalnie, na innym serwerze, w innej aplikacji. Komunikacja odbywa się najczęściej za pomocą protokołu HTTP (ogólniej — za pośrednictwem sieci web), stąd nazwa — web (od sposobu komunikacji) services (od tego, czym są logicznie). W założeniu web services pozwalają na komunikację niezależną od technologii, w których systemy zostały stworzone (np. jedna aplikacja została napisana w Javie, druga w C++), w praktyce nie jest to tak proste, ale w zdecydowanej większości przypadków nie musimy się przejmować technologią, która wykorzystywana jest ‘po drugiej stronie’.
Wyróżniamy dwa główne rodzaje serwisów, SOAP i REST (choć jest wiele innych podejść i standardów, te stanowią zdecydowaną większość istniejących), przyjrzyjmy się im bliżej.
SOAP vs REST
Wyjaśnijmy sobie, czym się różnią te dwa podejścia oraz jakie są ich wady i zalety.
SOAP to akronim od Simple Object Access Protocol. Jest on najbardziej zbliżony do tego, co nazywamy serwisami w zwykłej aplikacji — zakłada, że mamy pewien zbiór operacji, które przyjmują określone argumenty. SOAP z definicji jest bardzo formalny — tzn. każdy serwis powinien udostępniać plik WSDL, który opisuje jak się nazywa każda operacja, jakie dane przyjmuje, jakiego typu są to dane itp. Dzięki temu mamy dostęp do narzędzi takich jak generowanie kodu na podstawie pliku WSDL (bardzo ułatwia korzystanie z zewnętrznego API). Z drugiej strony nawet proste zapytania wymagają dużo ‘kodu’ dookoła w przesyłanej wiadomości (tzw. Envelope), przez co ciężko testować takie serwisy ręcznie, analizować przesyłane zapytania czy korzystać z nich za pomocą bardzo prostych klientów (np. mikrokontrolery, języki programowania bez bibliotek itp)
REST to akronim od representational state transfer i jest zbudowany wokół całkowicie innych założeń. W przypadku REST mamy adresy URL które są pewnego rodzaju identyfikatorami. Wysyłamy na te adresy zapytanie, które może być zarówno JSONem, XMLem, ale też zwykłym tekstem czy danymi binarnymi. To, co powinno się wydarzyć jest określone przez użytą metodę protokołu HTTP (mamy ich 7) — np. GET pobiera element, DELETE usuwa go itd. Pola klas możemy wysyłać w komplecie, a część możemy pominąć (czasami). Odpowiedź może być w formacie JSON, XML, lub też zależnie od tego, jakiego typu odpowiedzi zażyczy sobie klient. Słowem: mamy dość dużą dowolność w zakresie implementacji, ale jest ona obarczona dość dużą niepewnością. Tego typu API sprawdza się wyśmienicie w przypadku systemów, które powinny być dostępne dla możliwie dużej ilości klientów (także np. bezpośrednio przeglądarek internetowych i stron WWW), lub w których format zapytań nie jest tak ważny.
Czytałem ostatnio porównanie dość trafnie oddające ideę obu(niestety, nie jestem w stanie odnaleźć autora/źródła), w którym SOAP porównano z listem w kopercie z bąbelkami, natomiast REST do kartki pocztowej.
REST na pewno jest bardzo logiczny jesli chodzi o operacje na danych — dodawanie, odczyt, aktualizacja itp. W przypadku operacji czysto proceduralnych (np. ‘oblicz jakąś wartość na podstawie danych wejściowych’) jest on (przynajmniej dla mnie) dużo mniej jasny, a brak okreslonych standardów i norm powoduje, że implementacje często nie są zgodne z intencją RESTa i korzystanie z nich może przyprawić o bół głowy. Jest jednak dużo mniej wrażliwy na zmiany w API (np. dodanie lub usunięcie pól, nowe operacje itp).
SOAP jest ustandaryzowany. To oznacza, że będziemy musieli się dopasować do tych standardów, ale w zamian dostajemy masę narzędzi, które automatycznie zrobią część pracy za nas. Jest jednak bardziej problematyczny w manualnym testowaniu i analizie zapytań/usług.
Przykładowy serwis REST
W przypadku Springa (jak i wielu innych frameworków webowych) serwisy REST nie wymagają specjalnych bibliotek czy jakichś innych podejść. Nasz pierwszy serwis REST napiszemy jako zwykły kontroler :)
Wcześniej jednak parę słów o konwencji w REST. Przede wszystkim ważny jest adres URL — wszystkie działania na konkretnym obiekcie (np. kocie) wykonujemy na jego unikalnym adresie URL, np /api/koty/1, gdzie 1 to unikalny identyfikator konkretnego kota (np. z bazy danych).
Wyróżniamy dwa rodzaje adresów: konkretnego elementu (np. /koty/1) oraz całej kolekcji (wszystkich elementów, np. /koty).
Konkretne metody protokołu HTTP odpowiadają za odpowiednie operacje:
- GET — pobieranie (zarówno kolekcji, jak i pojedynczego elementu)
- POST — tworzenie (tylko kolekcji)
- PUT — aktualizacja (tylko pojedynczego elementu)
- DELETE — usuwanie (tylko pojedynczego elementu)
Poza zwracaną wartością (np. zmodyfikowanym elementem), komunikacja w przypadku REST odbywa się także poprzez statusy HTTP. Np. zapytanie GET /koty/7, jeśli kot o id 7 nie istnieje, zwróci status 404 — nie znaleziono. Dzięki temu unikamy tworzenia dodatkowych pól typu ‘status zapytania’ (co ma często miejsce w przypadku serwisów typu SOAP).
Prześledźmy zatem przykładową metodę kontrolera do tworzenia kota:
@RequestMapping("/koty")
public class KotyApiController {
@Autowired
KotyService kotyService;
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Kot> get(@PathVariable("id") Long id) {
Kot kot = kotyService.getById(id);
return new ResponseEntity<Kot>(kot, new HttpHeaders(), HttpStatus.OK);
}
}
Jak pewnie zauważyłaś w ogóle nie korzystamy tutaj z widoków czy z modelu (z MVC). Zamiast tego zwracamy obiekt typu ResponseEntity — jest on tylko ‘opakowaniem’, które zawiera nasz obiekt (ten, który chcemy zwrócić), nagłówki HTTP (jeśli chcemy dodać jakieś niestandardowe, np. datę ważności obiektu) oraz status HTTP (korzystamy tutaj z Enum’a HttpStatus).
Jak sama widzisz, nie jest to trudne :) Prześledźmy jeszcze np. edycję:
@RequestMapping(value="/{id}", method=RequestMethod.POST)
public ResponseEntity edit(@PathVariable("id") Long id, @RequestBody @Valid Kot kot, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
Kot kot = kotyService.getById(id);
//tutaj przypisujemy wartość pól z obiektu otrzymanego w zapytaniu
kot = kotyService.update(kot);
return new ResponseEntity(kot, new HttpHeaders(), HttpStatus.OK);
}
W tym wypadku wygląda to identycznie jak obsługa formularza w normalnym kontrolerze. Jedyną różnicą jest to, co zwracamy. W tym wypadku użyliśmy klasy Kot także jako formularza — oczywiście nie musimy tego robić (jest to wręcz niewskazane), a zamiast tego utworzyć nowy obiekt DTO. Jak widzisz, nasza wiedza o kontrolerach pozwala nam bez problemu tworzyć serwisy RESTowe przy użyciu Springa :)
Przykładowy klient REST
Do stworzenia klienta wykorzystamy Springową klasę RestTemplate - jest to bardzo proste narzędzie, które pozwala nam właściwie za pomocą jednej linijki kodu wywołać serwis RESTowy oraz zmapowac odpowiedź na podaną przez nas klasę. Spójrzmy na poniższy przykład:
RestTemplate restTemplate = new RestTemplate();
Kot kot = restTemplate.getForObject("http://localhost:8080/api/koty/1", Kot.class);
Jak widać użycie jest banalne, wystarczy podać oczekiwaną klasę (tą, na którą ma być zmapowana odpowiedź), adres URL i metodę HTTP (tą podaje się w nazwie metody: getForEntity odpowiada metodzie GET, postForEntity metodzie POST itp — patrz tabelka niżej).
Dzięki temu możemy tworzyć nie tylko klientów ale także testy! Wystarczy uruchomić nasz kontekst (jeśli nie pamiętasz jak, wróć do lekcji o testowaniu) po czym wysłać odpowiednie zapytanie.
Poniżej podsumowanie metod klasy RestTemplate — przykład wywołania dla każdego typu metody HTTP. Po kliknięciu na przykład, otworzy się pełna dokumentacja JavaDoc tej klasy.
Jak pewnie zauważyłaś, ostatni argument to zawsze pusta mapa. Robimy to dla uproszczenia przykładu — normalnie adres URL (ten pierwszy) może mieć parametry, które następnie przekazujemy poprzez mapę lub tablicę jako ostatni argument. W tym przypadku podajemy od razu adres URL ze wszystkimi danymi (np. id) więc nie potrzebujemy używać tych parametrów.
Podsumowanie
Dzisiejsza lekcja opisała sposób tworzenia i korzystania z serwisów RESTowych. W kolejnej części nauczymy się robić to samo z użyciem serwisów SOAPowych, oraz omówimy zagadnienia związane z ich testowaniem. Dzięki temu będziesz w stanie wybrać najbardziej pasującą technologię do zadania jak i samodzielnie tworzyć serwisy oraz w razie potrzeby wspierać zespół w ich testowaniu :)
Zadanie
Napisz prostą aplikację webową w Spring MVC, która będzie pobierała pogodę z publicznego API weather.yahoo.com: https://developer.yahoo.com/weather/. Pogoda powinna być wyświetlana dla lokalizacji wpisanej przez użytkownika (za pomocą formularza).
Druga część zadania to modyfikacja istniejącej aplikacji w taki sposób, żeby wszystkie operacje związane z obiektem kot (dodawanie, usuwanie, pobieranie, edycja, pobieranie wszystkich) były dostępne także za pomocą API restowego (pod adresem /rest/koty )
Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!