#22 — Web Services (REST)

By 1 April 2015Kurs Javy

Dzisiejsza lekc­ja poświę­cona jest web ser­vices — czyli sposo­bie na korzys­tanie z różnych usług ‘zdal­nie’. Poz­namy m.in. usłu­gi typu REST oraz SOAP a także różnice pomiędzy nimi.

Ponieważ w przy­pad­ku usług webowych kod, którego będziemy uży­wać, będzie się różnił w zależnoś­ci od wyko­rzysty­wanego frame­wor­ka, na potrze­by tej lekcji będziemy korzys­tać z najbardziej pod­sta­wowych imple­men­tacji (czyli głównie bezpośred­nio ze Spring’a).

Należy mieć jed­nak na uwadze, że częs­to może­my potrze­bować użyć innej bib­liote­ki lub sko­rzys­tać z jakiejś funkcjon­al­noś­ci niedostęp­nej w wybranym przez nas narzędz­iu, z tego powodu tą lekcję należy trak­tować bardziej jako wprowadze­nie do zagad­nienia web ser­vices niż kom­pendi­um wiedzy o imple­men­tacji.

Lekcja

Na początku słowo wstępu o samych web ser­vices — zwane także usługa­mi (usługa­mi web, usługa­mi zdal­ny­mi). Idea jest podob­na do naszych ser­wisów, które tworzyliśmy w Springu — czyli mamy pewną klasę, która udostęp­nia nam pewne oper­ac­je. Dokład­nie to samo ma miejsce w przy­pad­ku web ser­vices, z tą różnicą, że kod nie jest wykony­wany lokalnie w aplikacji, a zdal­nie, na innym ser­w­erze, w innej aplikacji. Komu­nikac­ja odby­wa się najczęś­ciej za pomocą pro­tokołu HTTP (ogól­niej — za pośred­nictwem sieci web), stąd nazwa — web (od sposobu komu­nikacji) ser­vices (od tego, czym są log­icznie). W założe­niu web ser­vices pozwala­ją na komu­nikację nieza­leżną od tech­nologii, w których sys­te­my zostały stwor­zone (np. jed­na aplikac­ja została napisana w Javie, dru­ga w C++), w prak­tyce nie jest to tak proste, ale w zde­cy­dowanej więk­szoś­ci przy­pad­ków nie musimy się prze­j­mować tech­nologią, która wyko­rzysty­wana jest ‘po drugiej stron­ie’.

Wyróż­ni­amy dwa główne rodza­je ser­wisów, SOAP i REST (choć jest wiele innych pode­jść i stan­dard­ów, te stanow­ią zde­cy­dowaną więk­szość ist­nieją­cych), przyjrzyjmy się im bliżej.

SOAP vs REST

Wyjaśni­jmy sobie, czym się różnią te dwa pode­jś­cia oraz jakie są ich wady i zale­ty.

SOAP to akro­n­im od Sim­ple Object Access Pro­to­col. Jest on najbardziej zbliżony do tego, co nazy­wamy ser­wisa­mi w zwykłej aplikacji — zakła­da, że mamy pewien zbiór oper­acji, które przyj­mu­ją określone argu­men­ty. SOAP z definicji jest bard­zo for­mal­ny — tzn. każdy ser­wis powinien udostęp­ni­ać plik WSDL, który opisu­je jak się nazy­wa każ­da oper­ac­ja, jakie dane przyj­mu­je, jakiego typu są to dane itp. Dzię­ki temu mamy dostęp do narzędzi takich jak gen­erowanie kodu na pod­staw­ie pliku WSDL (bard­zo ułatwia korzys­tanie z zewnętrznego API). Z drugiej strony nawet proste zapy­ta­nia wyma­ga­ją dużo ‘kodu’ dookoła w przesyłanej wiado­moś­ci (tzw. Enve­lope), przez co ciężko testować takie ser­wisy ręcznie, anal­i­zować przesyłane zapy­ta­nia czy korzys­tać z nich za pomocą bard­zo prostych klien­tów (np. mikrokon­trol­ery, języ­ki pro­gramowa­nia bez bib­liotek itp)

REST to akro­n­im od rep­re­sen­ta­tion­al state trans­fer i jest zbu­dowany wokół całkowicie innych założeń. W przy­pad­ku REST mamy adresy URL które są pewnego rodza­ju iden­ty­fika­tora­mi. Wysyłamy na te adresy zapy­tanie, które może być zarówno JSONem, XMLem, ale też zwykłym tek­stem czy dany­mi bina­rny­mi. To, co powin­no się wydarzyć jest określone przez użytą metodę pro­tokołu HTTP (mamy ich 7) — np. GET pobiera ele­ment, DELETE usuwa go itd. Pola klas może­my wysyłać w kom­ple­cie, a część może­my pom­inąć (cza­sa­mi). Odpowiedź może być w for­ma­cie JSON, XML, lub też zależnie od tego, jakiego typu odpowiedzi zaży­czy sobie klient. Słowem: mamy dość dużą dowol­ność w zakre­sie imple­men­tacji, ale jest ona obar­c­zona dość dużą niepewnoś­cią. Tego typu API sprawdza się wyśmieni­cie w przy­pad­ku sys­temów, które powin­ny być dostęp­ne dla możli­wie dużej iloś­ci klien­tów (także np. bezpośred­nio przeglą­darek inter­ne­towych i stron WWW), lub w których for­mat zapy­tań nie jest tak ważny.

Czy­tałem ostat­nio porów­nanie dość trafnie odd­a­jące ideę obu(niestety, nie jestem w stanie odnaleźć autora/źródła), w którym SOAP porów­nano z lis­tem w kop­er­cie z bąbelka­mi, nato­mi­ast REST do kart­ki pocz­towej.

REST na pewno jest bard­zo log­iczny jes­li chodzi o oper­ac­je na danych — dodawanie, odczyt, aktu­al­iza­c­ja itp. W przy­pad­ku oper­acji czys­to pro­ce­du­ral­nych (np. ‘oblicz jakąś wartość na pod­staw­ie danych wejś­ciowych’) jest on (przy­na­jm­niej dla mnie) dużo mniej jas­ny, a brak okres­lonych stan­dard­ów i norm powodu­je, że imple­men­tac­je częs­to nie są zgodne z intencją RES­Ta i korzys­tanie z nich może przypraw­ić o bół głowy. Jest jed­nak dużo mniej wrażli­wy na zmi­any w API (np. dodanie lub usunię­cie pól, nowe oper­ac­je itp).

SOAP jest ustandary­zowany. To oznacza, że będziemy musieli się dopa­sować do tych stan­dard­ów, ale w zami­an dosta­je­my masę narzędzi, które automaty­cznie zro­bią część pra­cy za nas. Jest jed­nak bardziej prob­lematy­czny w man­u­al­nym testowa­niu i anal­izie zapytań/usług.

Przykładowy serwis REST

W przy­pad­ku Springa (jak i wielu innych frame­worków webowych) ser­wisy REST nie wyma­ga­ją spec­jal­nych bib­liotek czy jakichś innych pode­jść. Nasz pier­wszy ser­wis REST napisze­my jako zwykły kon­trol­er :)

Wcześniej jed­nak parę słów o kon­wencji w REST. Przede wszys­tkim ważny jest adres URL — wszys­tkie dzi­ała­nia na konkret­nym obiek­cie (np. kocie) wykonu­je­my na jego unikalnym adresie URL, np /api/koty/1, gdzie 1 to unikalny iden­ty­fika­tor konkret­nego kota (np. z bazy danych).

Wyróż­ni­amy dwa rodza­je adresów: konkret­nego ele­men­tu (np. /koty/1) oraz całej kolekcji (wszys­t­kich ele­men­tów, np. /koty).

Konkretne metody pro­tokołu HTTP odpowiada­ją za odpowied­nie oper­ac­je:

  • GET — pobieranie (zarówno kolekcji, jak i poje­dynczego ele­men­tu)
  • POST — tworze­nie (tylko kolekcji)
  • PUT — aktu­al­iza­c­ja (tylko poje­dynczego ele­men­tu)
  • DELETE — usuwanie (tylko poje­dynczego ele­men­tu)

Poza zwracaną wartoś­cią (np. zmody­fikowanym ele­mentem), komu­nikac­ja w przy­pad­ku REST odby­wa się także poprzez sta­tusy HTTP. Np. zapy­tanie GET /koty/7, jeśli kot o id 7 nie ist­nieje, zwró­ci sta­tus 404 — nie znaleziono. Dzię­ki temu unikamy tworzenia dodatkowych pól typu ‘sta­tus zapy­ta­nia’ (co ma częs­to miejsce w przy­pad­ku ser­wisów typu SOAP).

Prześledźmy zatem przykład­ową metodę kon­trol­era 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 korzys­tamy tutaj z widoków czy z mod­elu (z MVC). Zami­ast tego zwracamy obiekt typu Respon­seEn­ti­ty — jest on tylko ‘opakowaniem’, które zaw­iera nasz obiekt (ten, który chce­my zwró­cić), nagłów­ki HTTP (jeśli chce­my dodać jakieś nie­s­tandar­d­owe, np. datę ważnoś­ci obiek­tu) oraz sta­tus HTTP (korzys­tamy tutaj z Enum’a Http­Sta­tus).

Jak sama widzisz, nie jest to trudne :) Prześledźmy jeszcze np. edy­cję:

@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 wypad­ku wyglą­da to iden­ty­cznie jak obsłu­ga for­mu­la­rza w nor­mal­nym kon­trol­erze. Jedyną różnicą jest to, co zwracamy. W tym wypad­ku użyliśmy klasy Kot także jako for­mu­la­rza — oczy­wiś­cie nie musimy tego robić (jest to wręcz niewskazane), a zami­ast tego utworzyć nowy obiekt DTO. Jak widzisz, nasza wiedza o kon­trol­er­ach pozwala nam bez prob­le­mu tworzyć ser­wisy RESTowe przy uży­ciu Springa :)

Przykładowy klient REST

Do stworzenia klien­ta wyko­rzys­tamy Springową klasę Rest­Tem­plate - jest to bard­zo proste narzędzie, które pozwala nam właś­ci­wie za pomocą jed­nej lin­ij­ki kodu wywołać ser­wis 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ży­cie jest banalne, wystar­czy podać oczeki­waną klasę (tą, na którą ma być zmapowana odpowiedź), adres URL i metodę HTTP (tą poda­je się w nazwie metody: get­ForEn­ti­ty odpowia­da metodzie GET, post­ForEn­ti­ty metodzie POST itp — patrz tabel­ka niżej).

Dzię­ki temu może­my tworzyć nie tylko klien­tów ale także testy! Wystar­czy uru­chomić nasz kon­tekst (jeśli nie pamię­tasz jak, wróć do lekcji o testowa­niu) po czym wysłać odpowied­nie zapy­tanie.

Poniżej pod­sumowanie metod klasy Rest­Tem­plate — przykład wywoła­nia dla każdego typu metody HTTP. Po kliknię­ciu na przykład, otworzy się peł­na doku­men­tac­ja JavaDoc tej klasy.

Meto­da HTTP Przykład wywoła­nia
GET restTemplate.getForEntity(“http://localhost:8080/koty/1”, Kot.class, Collections.EMPTY_MAP)
POST restTemplate.postForEntity(“http://localhost:8080/koty”, obiek­tZ­Danymi­DoZa­pisa­nia, Kot.class, Collections.EMPTY_MAP)
PUT restTemplate.put(“http://localhost:8080/koty/1”, obiek­tZ­Danymi­DoZmi­any, Collections.EMPTY_MAP)
DELETE restTemplate.delete(“http://localhost:8080/koty/1”, Collections.EMPTY_MAP)

Jak pewnie zauważyłaś, ostat­ni argu­ment to zawsze pus­ta mapa. Robimy to dla uproszczenia przykładu — nor­mal­nie adres URL (ten pier­wszy) może mieć para­me­try, które następ­nie przekazu­je­my poprzez mapę lub tablicę jako ostat­ni argu­ment. W tym przy­pad­ku poda­je­my od razu adres URL ze wszys­tki­mi dany­mi (np. id) więc nie potrze­bu­je­my uży­wać tych para­metrów.

Podsumowanie

Dzisiejsza lekc­ja opisała sposób tworzenia i korzys­ta­nia z ser­wisów RESTowych. W kole­jnej częś­ci nauczymy się robić to samo z uży­ciem ser­wisów SOAPowych, oraz omówimy zagad­nienia związane z ich testowaniem. Dzię­ki temu będziesz w stanie wybrać najbardziej pasu­jącą tech­nologię do zada­nia jak i samodziel­nie tworzyć ser­wisy oraz w razie potrze­by wspier­ać zespół w ich testowa­niu :)

Materiały dodatkowe / dokumentacja

  1. Bard­zo przy­jazny kurs REST (EN)
  2. Kon­supc­ja ser­wisów REST w Springu (EN)

Zadanie

Napisz prostą aplikację webową w Spring MVC, która będzie pobier­ała pogodę z pub­licznego API weather.yahoo.com: https://developer.yahoo.com/weather/. Pogo­da powin­na być wyświ­et­lana dla lokaliza­cji wpisanej przez użytkown­i­ka (za pomocą for­mu­la­rza).

Dru­ga część zada­nia to mody­fikac­ja ist­niejącej aplikacji w taki sposób, żeby wszys­tkie oper­ac­je związane z obiek­tem kot (dodawanie, usuwanie, pobieranie, edy­c­ja, pobieranie wszys­t­kich) były dostęp­ne także za pomocą API restowego (pod adresem /rest/koty )

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzię­ki!

  • 9
  •  
  •  
  •  
  •