30 stycznia 2009

RESTowe Web Services w Grails - rozdział 9 z "Beginning Groovy and Grails"

Rozdział 9. "Web Services" w książce Beginning Groovy and Grails: From Novice to Professional to przedstawienie tematu udostępnienia treści z aplikacji światu zewnętrznemu za pomocą usług sieciowych (ang. Web Services) w stylu REST (ang. Representational State Transfer).

W zamyśle usługi sieciowe miały ułatwić integrację rozproszonych systemów informatycznych. W ostatnich czasach wykorzystuje się je również do łączenia treści pochodzących z różnych systemów (dostępnych przez usługi sieciowe) nadając im nową, atrakcyjniejszą postać (za Dostęp do wielu informacji w jednym miejscu, Co to jest mashup czy Co to jest mashup?) jako hybrydowe aplikacje webowe (ang. mashups). Jakkolwiek o mashupach (mikstura aplikacyjna?) mówi się w kontekście aplikacji webowych, to miksturą aplikacyjną nie musi być jedynie aplikacją webową - wystarczy, aby korzystać z wielu usług od wielu dostawców, aby udostępnić pewne novum aplikacyjne.

REST nie jest standardem ani specyfikacją. Jest podejściem architektonicznym, w którym promuje się udostępnienie bezstanowych usług typu CRUD (ang. Create, Read, Update, Delete) korzystając z metod HTTP - GET, POST, PUT oraz DELETE. Zgodnie ze specyfikacją HTTP, każda z metod ma swoje przeznaczenie funkcjonalne, które można związać jednoznacznie z odpowiednimi akcjami CRUD. W REST chodzi o takie skonstruowanie adresów URL, że w połączeniu z odpowiednią metodą HTTP przekażą wystarczającą ilość informacji, aby wskazać pożądaną czynność CRUD. Usługi sieciowe wpisują się w ten schemat idealnie - protokołem komunikacyjnym jest HTTP (podobne założenie istnieje w REST), podczas gdy komunikaty są budowane w XML. Komunikacja między klientem a usługodawcą jest bezstanowa (co ponownie doskonale pasuje do specyfiki HTTP). Odpowiedni URL to:
 http://<serwer>/<kontekst>/<byt>/<identyfikator>
gdzie w przypadku Grails, <byt> będzie klasą domenową, której egzemplarze będą jednoznacznie identyfikowani przez <identyfikator>. Warto zauważyć, że konwencja grailsowa konstrukcji adresów URL idealnie wpasowuje się do konwencji RESTowej. Jedyną różnicą jest brak określenia wykonywanej akcji w Grails.

W tabeli Table 9-1 przedstawiony został związek między akcją CRUDową, wyrażeniem SQL, metodą HTTP, a akcją w Grails. I tak, Create, to odpowiednio, INSERT, PUT oraz create, Read to SELECT, GET i show, itd. Jedną z akcji, która nie znalazła swojego odpowiednika w metodzie HTTP jest pobranie kolekcji bytów (encji), która odpowiada SELECT w SQL i list w Grails (oczywiście można założyć, że brak identyfikatora w URL przy GET to właśnie pobranie kolekcji). Wynika to z faktu, że REST jest wyłącznie skoncentrowany na akcjach CRUDowych i nie dotyka funkcjonalności w stylu wyszukiwanie czy zwracanie list (kolekcji).

Początkowo istniała wtyczka udostępniająca funkcjonalność REST, jednakże w wersjach Grails 1.0 i nowszych została włączona w sam szkielet i nie ma już żadnego znaczenia.

Konfiguracja budowy adresów URL znajduje się w pliku grails-app/conf/UrlMapping.groovy. URLe są istotnym elementem RESTowych usług sieciowych i tak się składa, że idealnie pasują do konwencji w Grails. Ich konfiguracja to "/$controller/$action?/$id?", itd. Warto zwrócić uwagę na operator ?, który czyni poprzedzające elementy opcjonalnymi. Domyślnie RESTowe usługi sieciowe zwracają komunikat XML. Możliwy jest również format JSON (ang. JavaScript Object Notation), który odpowiada mapie w Grails. Rozróżnienie, który format będzie zwracany wymaga zmiany w domyślnym formacie adresu URL, na "/$rest/$controller/$id?". Skoro $rest to dwie z możliwych wartości, korzysta się z sekcji constraints z warunkiem rest(inList:["rest","json"]).
 constraints {
rest(inList:["rest","json"])
}
Wszystkie inne wartości spowodują HTTP 404 (Not Found).

W specjalnie skonstruowanym kontrolerze RestController korzysta się z dostępnych domyślnie w kontrolerach obiekcie grailsApplication i jego metodzie getArtefact() do dynamicznego wczytania, określonej w adresie URL jako tekst, klasy dziedzinowej (pamiętajmy, że ładowarka klas w Grails jest bogatsza od tej bezpośrednio w Javie, która uruchamia Grails, więc Class.forName() odpada). Wykonanie statycznej metody na klasie dziedzinowej dynamicznie sprowadza się do InvokeHelper.invokeStaticMethod(klasaDziedzinowa, metoda, parametry). Formatowaniem odpowiedzi zajmują się metody encodeAsXML() lub encodeAsJSON(). Fajne jest składanie nazwy metody dynamicznie w Groovy (Listing 9-6):
 def restType = (params.rest == "rest")?"XML":"JSON"
render obj."encodeAs$restType"()
Zdumiewające!

W pewnym momencie (strona 302) autorzy mechanizm statycznego importu (ang. static import) w Javie 5+ określają jako "an interesing Groovy import technique". Co w tym specyficznego dla Groovy, nie mam pojęcia.

Później prezentacja klienta RESTowego na bazie XmlSlurper, który jest niezwykle...skromny (ze względu na liczbę linii). "Skromność" Groovy to niezykła jego siła, ale ceną jest strata szybkości działania (co warto zauważyć nie zawsze ma znaczenie). Użycie XmlSlurper umożliwia dostęp do treści XML za pomocą notacji XPath.

Później wiele kodu do obsługi różnych akcji CRUDowych zakodowanych w adresie URL. Miło się czyta, aż kończymy na podsumowaniu, w którym podkreśla się wartość, jak to się wyrażono, eksponowania modelu w postaci RESTowej usługi sieciowej. Warto, bo "you may be amazed at the innovations you never even imagined" (Summary, str. 309). Udostępniamy treść, a tym samym pozwalamy innym puścić wodze fantazji, co z nią możnaby jeszcze nowatorskiego zrobić.