#22 – Web Services (REST)

By 1 kwietnia 2015Kurs Javy

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.

Metoda HTTP Przykład wywołania
GET restTemplate.getForEntity(„http://localhost:8080/koty/1”, Kot.class, Collections.EMPTY_MAP)
POST restTemplate.postForEntity(„http://localhost:8080/koty”, obiektZDanymiDoZapisania, Kot.class, Collections.EMPTY_MAP)
PUT restTemplate.put(„http://localhost:8080/koty/1”, obiektZDanymiDoZmiany, Collections.EMPTY_MAP)
DELETE restTemplate.delete(„http://localhost:8080/koty/1”, Collections.EMPTY_MAP)

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 :)

Materiały dodatkowe / dokumentacja

  1. Bardzo przyjazny kurs REST (EN)
  2. Konsupcja serwisów REST w Springu (EN)

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 )

Licencja Creative Commons

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!

  • 9
  •  
  •  
  • 10
  •  
  • wannaBeJunior

    Hej! Jest jakaś szansa, aby pojawiło się rozwiązanie zadania domowego o aplikacji pobierającej api pogody z tej lekcji ? :)

    • Cześć! Powolutku uzupełniamy rozwiązania, więc trochę to jeszcze zajmie. Jeśli masz konkretny problem/pytanie, to proponuję je zadać i w ten sposób na pewno postaramy się pomóc znacznie szybciej. Pozdrawiamy!

      • wannaBeJunior

        A więc – przepisałem kod z spring.io który pobiera jakieś tam dane http://pastebin.com/1kcKqLfX , a teraz chciałbym móc je wyświetlić na stronie metodami restowymi. Napisałem klase ( http://pastebin.com/Vx3wWByh )
        która na razie miałaby wyswietlac zwykly napis w celu sprawdzenia czy to zadziała, i jednak nie działa :D
        To jest pewnie całkowicie zle, ale prosiłbym o jakies wskazówki które mnie naprowadza do rozwiązania :)

        • Cześć, usuń póki co metodę run i sprawdź aplikację w przeglądarce – to łatwiejsze na dłuższą metę ;)
          Aplikacja powinna zadziałać w takiej wersji jak jest, zerknij w logi – tam znajdziesz informacje o tym, czy faktycznie się uruchomiła oraz pod jakim adresem jest dostępna ;)

  • Cześć,
    w kodzie, który wysłałaś w adresie URL masz zakodowane znaki – np. %20 to spacja. RestTemplate sam zamienia spacje na właściwe kodowanie, a w Twoim przypadku – znaki ‚%’ zamienia na ‚%25’ (możesz to zobaczyć w logu: 21:04:46.021 [main] DEBUG o.s.web.client.RestTemplate – GET request for „https://query.yahooapis.com/v1/public/yql?q=select%2520item.condition%2520from%2520weat… – %20 zostało zamienione na %2520)
    Przykładowy kod, który zadziała (podmień tylko tą linijkę):

    String uri = „https://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=”nome, ak”)”;

    • Ola

      Dziękuję za odpowiedź :)

  • RoryMercury69

    Cześć,
    Przypadkowo trafiłem na artykuł i jeśli dobrze rozumiem to tak krótko i na temat w REST to powiedzmy na podstawie jakiegoś adresu np. http://jakastamstrona/temat/ID to możemy dowolnie manipulować co mamy z nim zrobić np usunąć,zaktualizować,wyświetlić czy też dodać (bez ID http://jakastamstrona/temat) w aplikacji która ma połączenie do tej samej bazy danych. Nie zawsze wzoruje się na tym co jest napisane w skrypcie bo w innym języku inaczej się to zapisze ale aby założenia spełniało. Javowcem nie jestem z góry mówię, przez ten język ostatnio koszmary miałem :)

    • zasadniczo tak, w uproszczeniu można tak REST opisać. Rozwijając skrót jest to Representational State Transfer – ta część ‚Representational’ odnosi się właśnie do tego, o czym mówisz – adres także ‚reprezentuje’ czym jest dany zasób.

      Co do koszmarów współczujemy, ale zakładam że bardziej wynikało to z projektu a nie z Javy jako języka – język programowania to tylko narzędzie, które może pomagać albo przeszkadzać (jeśli zastosujemy je niewłaściwie)

  • Justyna

    Hej, bardzo ciekawy blog, czy można szybko powiedzieć jak można wykonać podane zadani używając POSTMAN albo GIT bash? Jestem dopiero poczutkująca w tym zakresie:)

    • Cześć,
      najlepiej poszukać tutoriali do narzędzi, które wydają Ci się najbardziej przystępne (funkcjonalnie są one wszystkie bardzo podobne). Dla Postman’a przykład znajdziesz tutaj: https://www.getpostman.com/docs/, Git bash służy jednak do czego innego (choć od biedy też da się wysłać zapytanie).
      Niemniej oba programy możesz wykorzystać jedynie do weryfikacji stworzonego przez siebie zadania – samą implementację musisz umieścić w swoim projekcie :)

  • Gosc

    Hej, umyka mi kilka spraw związanych z samą konfiguracją Springa. Nie znalazłem niestety odpowiedzi w poprzednich samouczkach. Pytanie- co jest beanem w Springu? Czy KotDao, KotEntity ma być wrzucony do config-a jako bean? Czy przez to że to są obiekty na których pracujemy tworząc je jako nowe javowe obiekty, to nie muszę ich tam wrzucać? Jeśli coś ma być w konfiguracji, to w jakim miejscu i po co? No i pytanie jeszcze- czy korzystamy tutaj z tradycyjnego zestawu DispatcherServlet + InternalResourceViewResolver?