31 stycznia 2009

Raporty w Grails - rozdział 10 z "Beginning Groovy and Grails"

0 komentarzy
Pewnie już widać koniec książki, bo końcowe zagadnienia sprawiają wrażenie bardzo wyrafinowanych. Właśnie! Czy ja napisałem "wyrafinowanych"?! I tu jest cały pies pogrzebany! Właśnie takie przeświadczenie towarzyszy nam, kiedy rozważamy funkcjonalność raportowania czy przetwarzania wsadowego. Są wartościowe, ale tak rzadko rozważane, że zazwyczaj podchodzimy do nich na samym końcu uważając, że są nieciekawe technologicznie. Sam tego doświadczałem, więc i Wam przypiąłem taką łatkę ;-) Po lekturze rozdziału 10. "Reporting" z książki Beginning Groovy and Grails: From Novice to Professional widzę jak bardzo się myliłem. Czy ja nie pisałem już, że "czytanie książek poszerza horyzonty" (mimo uwag, w felietonie w ostatnim wydaniu ComputerWorld Polska "Lektura nieobowiązkowa" - "Czytasz książeczki, masz los kiepski" albo "Wynika z tego, że prawdziwa wiedza przychodzi z obserwacji życia, a nie z książek." - są to jedynie wycinki i proszę potraktować je z przymróżeniem oka)?!

Wracając do tematu...

W rozdziale 10. "Reporting" możemy przeczytać jak przyjemnym tematem jest wdrożenie funkcjonalności raportowania w Grails z użyciem otwartego JasperReports. Integracja między Grails a JasperReports będzie niezwykle dynamiczna - korzystamy z już i tak dynamicznego Grails (dzięki Groovy) z wykorzystaniem mechanizmu dynamicznego wywoływania metod (ala mechanizm prześwietlania w Javie - ang. Java Reflection).

Wymagania stawiane rozwiązaniu raportującemu to możliwość tworzenia wielu raportów, w różnych formatach - PDF, HTML, TXT, RTF, XLS, CSV oraz XML - w podejściu DRY (ang. Don't Repeat Yourself - oddzielenie odpowiedzialności i enkapsulacja) na bazie modelu i dynamicznego wywoływania metod w Grails. Pojawia się bardzo skomplikowany diagram powiązań (związków) między poszczególnymi elementami aplikacji z kontrolerami, usługą i widokami. Stworzony zostaje znacznik GSP g:report, który wskazuje na kontroler i jego akcję jako właśnie kontrolera całej interakcji w systemie, co ostatecznie kończy się przesłaniem raportu do użytkownika. Autorzy podkreślają rolę poszczególnych elementów systemu, aby kontroler ReportController jedynie kontrolował przepływ danych między składowymi systemu, w którym pobranie danych o zadaniach użytkownika leży w gestii kolejnego kontrolera TodoController z dynamicznie dołączanymi do klas domenowych metodami bazodanowymi. Usługa ReportService odpowiada za interakcję z JasperReports. Każdy ma swoją rolę w systemie. Jeśli ktokolwiek (przy tak skromnym opisie systemu) zadaje sobie pytanie, dlaczego ReportController przekazuje dane raportowe do raportu zamiast oczekiwać, że to raport będzie odszukiwał konieczne dane za pomocą SQL, spieszę z wyjaśnieniem, że jest to poruszone w książce i sprowadza się do łatwości (a właściwie jej braku) zarządzania SQLem. SQL jest ukryty pod klasami domenowymi w Grails, więc warto skorzystać z gotowego rozwiązania.

JasperReports jest rozwiązaniem raportującym, które składa się ze środowiska uruchmomieniowego JasperReports oraz edytora raportów iReport. Rozpoczynamy od definicji raportu w iReport, w którym możemy umieścić elementy graficzne oraz wykresy, z użyciem XML. Do szablonu raportu dołączane są dane i powstaje raport w formacie PDF, XML, HTML, CSV, XLS, RTF i TXT. Do wyrysowania raportów JasperReports korzysta z dodatkowych bibliotek. Sam motor JasperReports jedynie wiąże szablon ze źródłem danych, parametrami i konfiguracją do eksportera raportowego. Eksporter zwraca ByteArrayOutputSource do aplikacji. W przypadku Grails, wystarczy ustawić odpowiedni rodzaj treści (ang. content type) w odpowiedzi HTML i wysłać odpowiedź do przeglądarki.

JasperReports korzysta z Abstract Window Toolkit (AWT), więc uruchomienie go w niegraficznych środowiskach uniksowych wymaga parametru -Djava.awt.headless=true, np. w zmiennej JAVA_OPTS w Grails.

Integracja JasperReports z aplikacją grailsową rozpoczyna się od przekopiowania koniecznych bibliotek z iReport/lib do katalogu lib aplikacji grailsowej. One z kolei trafią do WEB-INF/lib podczas budowania aplikacji webowej.

Przykładowa aplikacja korzysta ze źródła danych JasperReports opartego na JavaBeans (ang. JavaBeans data source), tj. dane pobierane są z listy klas dziedzinowych przekazywanych do usługi ReportService. Utworzenie definicji raportu w iReport wymaga wskazania na klasy dziedzinowe przez zmienną CLASSPATH, które domyślnie (w Grails 1.0.x) znajdują się w USER_HOME/.grails/1.0.x/projects/<projekt_grailsowy>/classes. Na zakończenie pracy z iReport otrzymujemy szablon raportu jako plik jrxml. Kompilujemy szablon do pliku jasper, który kopiujemy do web-app/reports aplikacji.

Zgodnie z konwencją Grails utworzenie własnego znacznika GSP polega na utworzeniu klasy, której nazwa kończy się TagLib w grails-app/taglib. Znacznik tworzymy poleceniem grails create-tag-lib Report, które tworzy samą klasę znacznika oraz test integracyjny w test/integration. Sam znacznik jest domknięciem (metodą w klasie) z dwoma parametrami - mapą atrybutów znacznika attrs oraz jego zawartością (treścią) body. Zdumiewające jest, że wiele z tych informacji zabrakło w rozdziale poświęconym GSP - rozdział 5. "Building the User Interface" (relacja z lektury w Tworzenie interfejsu użytkownika w Grails - rozdział 5 z "Beginning Groovy and Grails"). W klasie znacznika skorzystano z dostępnego domyślnie (dzięki integracji ze Spring Framework) obiektu grailsAttributes - za jego pomocą możemy odczytać kontekst aplikacji - def appPath = grailsAttributes.getApplicationUri(request) czy out, który jest strumieniem wyjściowym.

Następnie tworzy się kontroler ReportController poleceniem grails create-controller Report. W kontrolerze możemy wykorzystać DI ze Spring Framework i wystarczy zadeklarować klasę usługi ReportService reportService jako pole instancji klasy, aby Grails (poprzez Springa) przekazał (wstrzelił) zależność automatycznie. W klasie kontrolera korzysta się z (Listing 10-4)
 ApplicationContext ctx = (ApplicationContext) session.getServletContext()
.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
def controller = ctx.getBean("${params._controller}")
do pobrania (z kontekstu springowego) instancji kontrolera i dynamicznego wykonania metody na niej (Listing 10-4):
 def inputCollection = controller."${params._action}"(params)
Przywyknięcie do tego typu składania i wykonywania metod dynamicznie będzie mnie jeszcze kosztowało nielada wysiłku umysłowego.

Autorzy zwracają uwagę na (potencjalny) pomysł połączenia kontrolera ReportController z usługą ReportService i odradzają go, gdyż "the controller's purpose is to control, not do the actual work". Pamiętajmy o tym - kłania się wzorzec MVC. Tym samym cała wiedza o komunikacji system-JasperReports jest w jednym miejscu - usłudze. Nie jest ona trywialna, gdyż poszczególne formaty raportów wymagają specjalnej obsługi. Stworzenie usługi to wydanie polecenia grails create-service Report.

Na koniec ukłon w stronę alternatywnego rozwiązania - wdrożenia wtyczki grailsowej Jasper, która stała się inspiracją dla całego rozwiązania raportowego w tym rozdziale. Wtyczka opiera się na założeniu, że to raport pobiera dane za pomocą SQL. Instalacja wtyczki to grails install-plugin Jasper.

30 stycznia 2009

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

0 komentarzy
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ć.

29 stycznia 2009

Web 2.0 i Grails - rozdział 8 z "Beginning Groovy and Grails"

0 komentarzy
Kolejny rozdział 8. "Web 2.0 - Ajax and Friends" z Beginning Groovy and Grails: From Novice to Professional za mną. I kolejny raz sprowadziło się do przedstawienia kilku wtyczek grailsowych, które umożliwiają skorzystanie z funkcjonalności ogólnie dostępnych projektów.

Dotychczasowy rozwój aplikacji webowej w Grails wykorzystywał jedynie bardzo podstawowe elementy HTML. Według autorów książki aplikacja webowa powinna mieć to coś, co spowoduje zainteresowanie nią. Nie musi być specjalnie funkcjonalna. Musi mieć to coś. Oczywiście, jeśli będzie funkcjonalna to tym dla niej lepiej, ale nie jest to kryterium przesądzające, który serwis webowy jest dobry. Dobre aplikacje są jedynie dobre (obym mógł napisać chociaż to o swoich!) i "will not generate revenue". W kolejnych rozdziałach przedstawione zostaną techniki, które znacząco uatrakcyjnią aplikację. Ważne, aby aplikacja dostarczała funkcjonalność w interesujący sposób, który sprawi zadowolenie u użytkowników i sprawi, że wrócą do niej (bez konieczności odgórnych poleceń).

Rozdział rozpoczyna się wdrożeniem funkcjonalności pełnotekstowych. Chodzi o to, aby opis zadania składał się z wytłuszczeń, italik, itp. Interpretowanie HTMLa wprowadzonego przez użytkowników jest niezalecane - zbyt wiele zabawy z tym, a i ryzyko zniszczenia strony duże. Instalacja edytora tekstowego, jakkolwiek sprowadza edycję tekstu do pracy podobnej z MS Word czy OpenOffice (kolejność przypadkowa) jest wartościowe, ale często zbyt zaawansowane. Taką funkcjonalność mamy dzięki wtyczce FCK editor. Ostatecznie wybrano udostępnienie (meta)języka ala Wiki z pomocą wtyczki radeox, która opakowuje projekt Radeox z SnipSnap jako Wiki. Instalacja z grails install-plugin radeox i od tej pory możemy korzystać ze znacznika g:radeoxRender do opakowania tekstu, który ma podlegać obróbce przez Radeox, np. (Listing 8-2):
 <g:radeoxRender>${todo?.note}</g:radeoxRender>
Kolejna wartościowa funkcjonalność w aplikacjach webowych to możliwość wyszukiwania treści. W Grails realizujemy to przez wtyczkę Searchable, która oparta jest na OpenSymphony Search Engine, który z kolei korzysta z Apache Lucene. Instalacja to typowe grails install-plugin searchable. Deklarujemy, które klasy domenowe mają być przeszukiwane przy pomocy statycznego bloku static searchable = true. Domyślna strona wyszukiwania to http://<server>:<port>/<kontekst>/searchable.html. Jakby inaczej mogła nazywać się domyślna strona wtyczki searchable?! Niesamowite użycie "konwencji nad konfiguracją". Jeśli nawet Grails nie stanie się Twoim szkieletem webowym, to warto wykorzystać jego cechy w swoich projektach. Połączenie wtyczki radeox z searchable to edycja pliku grails-app/searchable/index.gsp i dodanie g:radeoxRender w odpowiednich miejscach.

Następną funkcjonalnością jest zapis plików na serwerze, która jest realizowana przez nikogo innego, jak sam Spring Framework. Wystarczy zadeklarować w klasie dziedzinowej dodatkowe pola typu byte[] na treść pliku oraz dwa pola typu String dla nazwy pliku i typ, wykorzystać <input type="file" name="<nazwa_pola_typu_byte[]_w_klasie_dziedziny>" /> oraz w formularzu ustawić enctype="multipart/form-data". Następnie w springowym pliku konfiguracyjnym w Grails - conf/spring/resources.xml - zadeklarować właściwe ziarno springowe na bazie CommonsMultipartResolver (zainteresowanych szczegółami zachęcam do lektury książki). Ostatnim krokiem jest obsługa zapisu, tj. obsługa akcji w kontrolerze - zabawa z MultipartHttpServletRequest oraz CommonsMultipartFile. Nic szczególnie interesującego dla zainteresowanych Grails - zabawa konfiguracją springową.

I ostatnia funkcjonalność - wysyłanie emaili. Ponownie na scenie pojawia się Spring Framework ze swoim Spring Mail - org.springframework.mail.MailSender. Dużo konfiguracji springowej w resources.xml, aby ostatecznie zapoznać się z akcją sendAcknowledgment, gdzie korzysta się z grailsowego SimpleTemplateEngine. Fajne tricki, które sprowadzają temat szablonów do kilku wywołań - zawsze miałem przekonanie, że koniecznych jest więcej.

Biblioteki znaczników (ang. tag libraries) dostarczają zbiór własnych akcji, które wykorzystujemy na stronach GSP. Tworzymy biblioteki, aby zawrzeć powtarzające się kawałki kodu w mniej obszernej strukturze, łatwiejszej do użycia w wielu miejscach (co pozwala nam na przestrzeganie zasady DRY - ang. Don't Repeat Yourself). Jak zapewniają autorzy "creating a tag library with Grails is simpler than that" (gdzie "that" to "tworzenie znaczników w szkieletach javowych" - pewnie "piją" do JSP czy JSF, w których tworzenie znaczników to faktycznie wyzwanie).
Biblioteki znaczników w Grails umieszczamy w grails-app/taglib. Są to zwykłe klasy grailsowe POGO (ang. Plain Old Groovy Object), w którym nazwy metod to nazwy znaczników. Do metod przekazywane są dwa parametry attrs oraz body, które reprezentują, odpowiednio, atrybuty oraz ciało znacznika, np. dla poniższej metody
 def wyswietl = { attrs, body ->
def parametrTekst = attrs['tekst']
out << """Skoro prosisz, to wyświetlam tekst "${parametrTekst}"."""
}
odpowiadający znacznik jest <g:wyswietl tekst="do wyswietlenia" />, gdyż wszystkie znaczniki należą do domyślnej przestrzeni znaczników w Grails g. Jeśli chcielibyśmy zmienić przestrzeń dla naszych znaczników dodajemy static namespace = 'naszIdentyfikator' do klasy znaczników. Warto również zwrócić uwagę na użycie potrójnego cudzysłowu, w którym możemy używać dowolnego znaku, nawet cudzysłowu, bez groźby wpadki. Poza tym parametr out wskazuje na standardowe wyjście - na stronę.

AJAX (ang. Asynchronous JavaScript and XML) to sztandarowa technika wspierająca Web 2.0. Nie jest to żadna technologia, a zbiór technologii, jak, wspomniane w nazwie, JavaScript i XML, ale nie tylko. W rozwiązaniach AJAXowych stosuje się również technologie typu JSON. AJAX pozwala na wysłanie danych do serwera asynchronicznie (synchronicznie również, ale w tym nie ma nic szczególnego) oraz wczytanie odpowiedzi bez konieczności przeładowania całej strony. Największym wyzwaniem przy wdrożeniu technologii ajaxowej bezpośrednio jest skorzystanie z przesłanych z serwera danych. To jest miejsce dla szkieletów ajaxowych, które ukrywają przed nami zawiłości AJAX oraz obsługi różnych typów przeglądarek. Popularne szkielety ajaksowe to Prototype, Dojo Toolkit, script.aculo.us oraz Yahoo! User Interface (YUI), a nawet zaklasyfikowano Google Web Toolkit (GWT). Ruby on Rails korzysta z script.aculo.us, Tapestry Tacos z Dojo, a Grails...wszystkie z wymienionych (!) I jak tu nie polubić Grails?! Wybór tego jednego szkieletu ajaxowego to połączenie znacznika <g:javascript library="<nazwa_szkieletu_ajaxowego>">, gdzie nazwa_szkieletu_ajaxowego to prototype, yahoo, scriptaculous oraz dojo. Ten ostatni, ze względu na swój rozmiar, nie jest domyślnie dostarczany z Grails i wymaga instalacji poleceniem grails install-dojo.

Domyślnie dostarczany zestaw znaczników ajaxowych GSP to remoteField, remoteFunction, remoteLink, formRemote, javascript oraz submitToRemote. Dostarczenie własnych ajaxowych komponentów graficznych to zabawa już na poziomie wybranego szkieletu ajaxowego. W książce postawiono na script.aculo.us, jako że krewniak Ruby on Rails właśnie jego używa i w ten sposób można porównać pracę obu.

Wdrożenie AJAXa sprowadza się do zmiany zachowania sposobu odświeżania części stron, tzw. częściowe odświeżenie, czyli w Grails jest to przede wszystkim stworzenie nowego szablonu zwanego częściową stroną (wycinkiem?). Wymaganiem Grails (i Ruby on Rails) jest, aby wywołania ajaxowe były kierowane do innej strony, która z kolei jest dołączona do rozkładu (ang. layout). Wyświetlenie strony z innej to użycie znacznika GSP - g:render. W ruch idzie g:formRemote.

Następny przykład w rozdziale to edycja pola "w miejscu" - po kliknięciu pojawia się ekran edycyjny w miejscu wybranego pola. Tworzony jest nowy znacznik g:editInPlace oparty na script.aculo.us. Jeśli własny znacznik, to nowa klasa AjaxTagLib w katalogu grails-app/taglib z metodą editInPlace (dokładnie taka, jak nazwa znacznika).

Przedstawienie obsługi AJAX w Grails kończy stworzenie funkcjonalności autouzupełniania, które polega na dynamicznym wyświetlaniu listy rozwijalnej z pasującymi do wprowadzonych już znaków ciągami. W zasadzie nic nowego w kontekście nauki Grails poza kolejnymi kontrolerami i akcjami, które wykorzystują zapytania kryteriowe z Hibernate, które ubrane w grailsowy DSL (a w zasadzie GORMowy DSL) przedstawiają się następująco (Listing 8-30):
 def findUsers = {
def users = User.createCriteria().list {
or {
like("userName", "%${params.userId}%")
like("firstName", "%${params.userId}%")
like("lastName", "%${params.userId}%")
}
order("lastName")
order("firstName")
}
}
Niesamowite, jak łatwo czyta się taki kod. Do utworzenia nieuporządkowanej listy użytkowników do wyświetlenia na stronie autorzy skorzystali z grailsowego groovy.xml.MarkupBuilder. Utworzenie listy nieuporządkowanej wygląda podobnie do powyższego zapytania kryteriowego, tj. (Listing 8-30):
 def writer = new StringBuffer()

new groovy.xml.MarkupBuilder(writer).ul {
for (u in users) {
li(id: u.id, "$(u.lastName), $u.firstName")
}
}
render writer.toString()
Na zakończenie ostatnia ciekawa funkcjonalność do rozważenia w naszych aplikacjach webowych celem ich uatrakcyjnienia - strumień RSS (ang. RSS feed). Funkcjonalność dostarczana w Grails przez wtyczkę Feeds, którą instalujemy przez grails install-plugin feeds. Później dedykowany kontroler z akcją, która zwraca strumień w odpowiednim formacie, np. Atom. Ponownie ilość kodu zdumiewa - kilka linijek i temat obsłużony. Cudo! Jeśli tylko będzie okazja na wdrożenie Grails, zrobię to bez wahania. Na pewno niezapomniane wrażenia (oby nie tylko początkowe).

28 stycznia 2009

Bezpieczeństwo w Grails - rozdział 7 z "Beginning Groovy and Grails"

2 komentarzy
Kolejny rozdział 7. "Security in Grails" w książce Beginning Groovy and Grails: From Novice to Professional omawia dostępne wtyczki grailsowe umożliwiające wprowadzenie mechanizmów bezpieczeństwa w aplikacjach grailsowych. Możemy przeczytać o sposobie zabezpieczania danych użytkownika przed ich odczytaniem przez innych użytkowników (bezpieczeństwo na poziomie modelu - ang. domain/model-level security for a user), możliwości przypisania zbioru stron dla niektórych użytkowników, np. administratora (ang. administrator vs regular user access) oraz proste uwierzytelnianie z użyciem Basic Authentication. Nigdy nie spotkałem się z takim podziałem, ale też nigdy aspekt bezpieczeństwa nie był przeze mnie jakoś specjalnie badany. Gdzieś tam się po prostu przewijał. Poza ostatnim typem z uwierzytelnianiem z Basic Auth, który będzie poruszany w rozdziale 9, pozostałe dwa są tematem tego rozdziału. Omówiono wdrożenie bezpieczeństwa przy użyciu kilku różnych sposobów uwierzytelniania i autoryzacji - od najbardziej pracochłonnego i trywialnego - własnego rozwiązania, przez JSecurity, CAS (jedynie uwierzytelnianie), aby docelowo skorzystać ze Spring Security (dawniej Acegi Security). Żaden z nich nie jest rozwiązaniem dedykowanym dla Grails - za pomocą mechanizmu wtyczek udostępnione są funkcjonalności istniejących, zewnętrznych projektów.

Uwierzytelnianie (ang. authentication) to po prostu "przedstawienie się", np. za pomocą pary login/hasło lub certyfikatu. Autoryzacja (ang. authorization) to wydanie zgody na skorzystanie z zasobu.

Przechowywanie haseł zazwyczaj związane jest z zakodowaniem ich z pomocą klasy użytkowej org.apache.commons.codes.digest.DigestUtils, która udostępnia metody dla różnych rodzajów funkcji haszujących - MD5, SHA.

Jednym z ważniejszych katalogów Grails w kontekście konfiguracji bezpieczeństwa jest katalog konfiguracyjny grails-app/conf z m.in. Bootstrap.groovy. Dzięki Bootstrap.groovy Grails umożliwia wypełnienie danymi bazę danych lub...kontekst aplikacji webowej (!) Dodatkowo, podział konfiguracji według środowiska uruchomieniowego - rozwojowe, produkcyjne, testowe - pozwala na uruchomienie pewnych funkcjonalności wyłącznie w danym środowisku. Wykrycie środowiska następuje przez sprawdzenie wartości grails.util.GrailsUtil.environment, np. "development", "production". Wystarczy połączyć to z switch/case.

W ramach uwierzytelniania możemy dodatkowo skorzystać z mechanizmu CAPTCHA (ang. Completely Automated Public Turing Test to Tell Computers and Humans Apart) - na serwerze tworzony jest obrazek, na którym znajduje się ciąg przypadkowych znaków, które użytkownik będzie proszony o wpisanie. W ruch idzie wtyczka Simple Captcha Plugin. Istnieje jednak bardziej zaawansowana wtyczka jcaptcha, która poza utworzeniem obrazka pozwala na dołączenie pliku wav. Wymaga więcej wysiłku podczas konfiguracji i autorzy jedynie wspominają o niej. Wywołanie wtyczki captcha to
 <img src="${createLink(controller: 'captcha', action: 'index')}" />
który czytamy - dołącz obrazek utworzony przez kontroler CaptchaController metodą index. Wystarczy więc zajrzeć do odpowiedniego pliku CaptchaController.groovy i przekonać się, w jaki sposób możliwe jest przesyłanie nie tylko strumienia HTML, ale również binarnego, np. zdjęcia. CaptchaController zapisuje użyty ciąg znaków w sesji, jako session.captcha. Niestety, porównanie wprowadzonego przez użytkownika ciągu z tym utworzonym przez CAPTCHA jest na naszych barkach. Coś w stylu (Listing 7-2):
 def captchaText = session.captcha
if (params.captcha.toUpperCase() == captchaText) {
// poprawnie wprowadzony ciąg znaków
...
}
Kolejnym krokiem jest wykorzystanie filtrów, które w Grails nie wymagają żadnych wpisów do WEB-INF/web.xml (nie zapomnieliśmy, że Grails to w końcu aplikacja webowa, więc taki plik *musi* istnieć). Wystarczy stworzyć klasę zakończoną Filters w grails-app/conf z metodą filters. O filtrach pisałem już w relacji Tworzenie interfejsu użytkownika w Grails - rozdział 5 z "Beginning Groovy and Grails". W filtrach dostępne są obiekty controllerName, request, response, session, servletContext, applicationContext oraz params.

JSecurity jest najprostszym z rozwiązań wprowadzającym pojęcia rola (ang. role) oraz uprawnienie (ang. permission). Jest prawdopodobnie bardzo podobny do tego, który sami stworzylibyśmy, jednakże jego zaletą jest to, że już istnieje i nie wymaga skomplikowanej konfiguracji. JSecurity jest zewnętrznym projektem, który ma swoją wtyczkę grailsową jsecurity. Może korzystać z różnych źródeł danych do obsługi uwierzytelniania (w książce korzysta się wyłącznie z bazy danych). Instalujemy wtyczkę poleceniem grails install-plugin jsecurity, a następnie tworzymy przestrzeń bezpieczeństwa (ang. realm) oraz klasy dziedzinowe wraz z kontrolerami i stronami GSP poleceniem grails create-db-realm. Wykonanie polecenie to również założenie katalogu grails-app/realms, w którym klasy zakończone na Realm są definicją źródeł danych do uwierzytelniania i autoryzacji użytkowników, np. względem bazy danych czy LDAP.

Wtyczka grailsowa cas-client umożliwa wykorzystanie JA-SIG Central Authentication Service (CAS). Jest to nic innego jak klient grailsowy oparty na kliencie javowym dostarczanym w CAS. Wdrożenie CASa pozwala na centralne zarządzanie użytkownikami i ich uwierzytelnianie nie tylko w aplikacjach javowych. Instalacja wtyczki to grails install-plugin cas-client. Konfigurację danych serwera CAS znajdziemy w grails-app/conf/Config.groovy. Podczas budowania aplikacji grailsowej konfiguracja CAS trafia do WEB-INF/web.xml docelowej aplikacji webowej. Parametry grailsowe rozpoczynające się na cas.* zamieniane są na odpowiednie parametry edu.yale.its.tp.cas.client.filter.*, poza cas.urlPattern oraz cas.disabled, które są specyficzne dla wtyczki grailsowej. Kontrolery zabezpieczamy filtrami lub rozszerzając własną klasę z odpowiednimi wywołaniami, np. sprawdzając, czy użytkownik ma prawo do wykonania danej operacji. Nazwa użytkownika dostępna jest w atrybucie sesyjnym CASFilter.CAS_FILTER_USER.

Jako ostatnie rozwiązanie prezentowany jest Spring Security (dawniej Acegi Security) w postaci wtyczki grailsowej acegi. Mamy do dyspozycji uwierzytelnianie z LDAP, CAS, JAAS oraz CAPTCHA. Jako to ujęli autorzy: "Grails is in many ways a glorified Spring application", więc skoro Grails to Spring Framework, więc w kontekście bezpieczeństwa nie możemy postawić na nic innego niż właśnie na Spring Security. Instalacja typowa - grails install-plugin acegi. Następnie wykonujemy grails create-auth-domains, która tworzy klasy dziedzinowe Person, Authority oraz Requestmap. Możemy utworzyć dodatkowe kontrolery i widoki poleceniem grails generate-manager, ale nie jest to konieczne, jeśli samodzielnie zamierzamy zarządzać modelem. Requestmap to zbiór informacji o zabezpieczonych stronach i jakie uprawnienia są wymagane. Dane testowe możemy utworzyć przez odpowiednie konstrukcje GORMowe na klasach dziedzinowych w Bootstrap.groovy. Konfiguracja wtyczki jest w grails-app/config/AcegiConfig.groovy. Zabezpieczanie stron możliwe jest z użyciem dedykowanych znaczników GSP, m.in. g:isLoggedIn oraz g:isNotLoggedIn. Po uwierzytelnieniu wtyczka acegi umieszcza obiekt użytkownika w sesji, więc możemy odczytać jego dane na stronie GSP, np. (Listing 7-12):
 <g:isLoggedIn>
<b>${session.user?.firstName} ${session.user?.lastName}</b>
</g:isLoggedIn>
Kolejny rozdział to technologie Web 2.0 w Grails. Przed nami ponownie cała masa wtyczek, więc przy okazji poznawania Grails można poznać wiele projektów wokół (pewnie nie będę oryginalny, jeśli powiem, że o wielu nie słyszałem wcześniej).

26 stycznia 2009

Model i usługi w Grails - rozdział 6. z "Beginning Groovy and Grails"

4 komentarzy
Po bardzo intensywnym rozdziale 5 o tworzeniu interfejsu użytkownika w Grails (patrz: Tworzenie interfejsu użytkownika w Grails - rozdział 5 z "Beginning Groovy and Grails") przyszła pora na niemniej intensywny rozdział 6. "Building Domains and Services" w książce Beginning Groovy and Grails: From Novice to Professional. Tym razem (jedynie) 50 stron, z niewielką dawką zrzutów ekranów, co daje bardzo merytoryczny rozdział (aż do bólu). Kiedy kilkakrotnie czytałem wczorajszą moją relację miałem dosyć. Zasada, że relacja podnosi ilość zapamiętanej informacji działa znakomicie, ale mam wrażenie, że jak na relację była zbyt obszerna. Może gdybym opuścił moje wtrącenia?! Hmmm, może. Ta nie będzie jednak inna. Ostrzegałem.

Rozdział 6. rozpoczyna się nawiązaniem do filmu..."Monty Python and the Holy Grail", w którym my występujemy jako rycerze króla Artura i czytając tę książkę o Grails mamy nadzieję znaleźć go również. W tym sensie nasza wyprawa zmierza ku krainie szczęśliwości grailsowej - jego fundamentom do budowania modelu aplikacyjnego (nic nie przychodzi mi do głowy poszukując tłumaczenia dla angielskiego domain poza "dziedziną", która nie wydaje mi się bardziej twórcza od "modelu"). Ponownie pojawia się odwołanie do Ruby on Rails, zaś inne szkielety webowe nazywając "other web frameworks" (jeszcze trochę, a rzucę Grails i rozpocznę rozpoznanie tak promowanego, nawet w książce o Groovy & Grails, Ruby on Rails ;-)). Później pojawia się nawiązanie do kolejnego tematu rozdziału - usługach, które w przeciwieństwie do kontrolerów nie mają świadomości webowej (zero gwarantowanego dostępu do sesji czy parametrów żądania i nie mogą być bezpośrednio wywoływane z GSP).

GORM to dla Grailsów mechanizm utrwalania danych jak ActiveRecord dla Ruby on Rails (znowu o nim!), czy JPA dla Java EE. Na długo mnie zmroziło, kiedy kontynuując te porównania przeczytałem "WebSphere uses iBATIS". Że co?! Mariusz i inni ewangeliści iBATIS byliby zachwyceni, ale w którym produkcie z rodziny IBM WebSphere znajduje się iBATIS? Na razie wciąż nie mogę wyjść z podziwu dla potrzeby umieszczenia tego zdania (które w/g mnie jest nieprawdziwe).

GORM opiera swoje działanie na Hibernate i tak na prawdę opakowuje go grailsowym DSLem. W/g autorów GORM to połączenie najlepszych cech ActiveRecord z Hibernate, więc znasz jedno z nich i masz 95% wiedzy o GORM.

Pojęcie relacji między klasami domenowymi wyraża się w GORM za pomocą DSLa - klasa A należy do (belongsTo) B lub klasa A ma wiele (hasMany) B. Wszystko.

Wcześniej już relacjonowałem, że GORM automatycznie dodaje pola id oraz version. Pole id to automatycznie generowany identyfikator, za pomocą którego GORM potrafi związać odłączone (ang. detached) encje^H^H^Hklasy dziedzinowe z tymi utrwalonymi. Za pomocą pola version udostępniony jest mechanizm optymistycznego blokowania.

Dowiedziałem się przy okazji o nowym nazewnictwie dla stylu nazwa_pola_w_bazie, który nazywa się snake_case (podobnie jak CamelCase dla sposobu nazywania klas w Javie - hmmm, a jak mogłoby to być po polsku?!).

Mimo, że GORM to w dużej mierze JPA nie ma co liczyć na wiele podobieństw. Można powiedzieć, że jest tyle różnic, a w zasadzie uproszczeń, że wiedza, że GORM opiera się na Hibernate i JPA na niewiele się zdaje. Najlepiej o tym zapomnieć i nie sugerować się niedoskonałościami JPA/Hibernate. Pewnie niejednego zdumiało moje ostre nastawienie do JPA, ale po zapoznaniu się z uproszczeniami w GORM zastanawiam się, dlaczego trzeba było tak skomplikować JPA. W GORM mamy wyłącznie hasMany oraz belongsTo.

Relacja jeden-do-jednego (1-1) to po prostu użycie jednej klasy dziedzinowej w drugiej (za Listing 6-4 w książce):
 class User {
Address address
}

class Address {
User user
}
Relacja jeden-do-wielu (1-*) modelowana jest przez static hasMany, z listą, w której podaje się nazwę pola i typ oraz belongsTo po stronie wiele (w celu nałożenia ograniczenia w bazie danych przy generowaniu schematu), np.
 class Uzytkownik {
static hasMany = [konta: Konto]
}

class Konto {
static belongsTo = Uzytkownik
}
Relacja wiele-do-jednego (*-1) to nic innego jak odwrócenie relacji jeden-do-wielu i GORM nie wprowadza żadnych specjalnych mechanizmów dla niej (w przeciwieństwie do adnotacji @ManyToOne w JPA!).

Relacja wiele-do-wielu (*-*) to umieszczenie słowa kluczowego hasMany w klasach dziedzinowych po obu stronach relacji, z belongsTo w jednej z nich, np.
 class Blog {
static hasMany = [tagi: Tag]
}

class Tag {
static hasMany = [blogi: Blog]
static belongsTo = Blog
}
Podczas tworzenia schematu bazodanowego lub mapowaniu klas dziedzinowych do istniejącego będziemy potrzebować mechanizmu jego definicji. W GORM mamy do dyspozycji niskopoziomową konfigurację Hibernate (niezalecane) lub preferowany GORM DSL z static mapping:
 class Konto {
static mapping = {
// sekcja odwzorowania modelu obiektowego na relacyjny (mapowanie)
}
}
Zmiana nazwy tabeli - table '<nowa-nazwa-tabeli>'

Zmiana nazw kolumn w tabeli:
 columns {
nazwa column: 'pelna_nazwa'
imie column: 'pierwsza_nazwa'
}
Zmiana sposobu generowania identyfikatorów klucza głównego - id generator: 'hilo', params: [...]. Zmiana na klucz złożony: id composite: ['nazwa', 'imie']

Nie zaleca się zmiany prostego klucza głównego na złożony, gdzie głównym powodem jest...Hibernate, który działa najwydajniej z kluczem prostym.

Wyłączenie wersjonowania - version false.

Pobieranie danych w GORM odbywa się z opóźnieniem. Zmiana na gorliwe ładowanie (wczytywanie):
 columns {
tagi lazy:false
}
Ustanowienie indeksu dla danej kolumny - nazwa index:'Nazwa_Idx'.

Domyślnie GORM hierarchi dziedziczenia przypisuje pojedyncza tabelę (co uniemożliwia nałożenie warunków niepuste czy klucz główny) z kolumną class jako wyróżnikiem do jakiego typu dana krotka należy. Istnieje możliwość zmiany na tabela per klasa (co skutkuje kopiowaniem danych z klasy bazowej oraz łączeniami - joins - do wczytania wszystkich użytkowników) przez tablePerHierarchy true.

Włączenie pamięci podręcznej 2. poziomu (ang. second-level cache) to zmiana w DataSource.groovy (ustawienie parametrów cache.* w sekcji hibernate na true) oraz dodaniu cache true do sekcji static mapping w klasie dziedzinowej (domyślnie read-only oraz non-lazy). Można również określić inne parametry pamięci podręcznej - cache usage:'read-write', include:'lazy'.

Wyłączenie atrybutów klasy dziedzinowej z mechanizmu utrwalania (ustanowienie ich ulotnymi/nietrwałymi - ang. transient properties) - static transients = ["nazwaPolaUlotnego", "kolejne"].

W GORM istnieje mechanizm zdarzeń - zdarzenie przed zapisem, aktualizacją i skasowaniem klasy dziedzinowej w bazie oraz po jej wczytaniu, odpowiednio beforeInsert, beforeUpdate, beforeDelete oraz onLoad. Jeśli stworzymy pola lastUpdated oraz dateCreated będą one automatycznie aktualizowane przez GORM. Wyłączenie ich to autoTimestamp false w static mapping.

Ograniczenia (warunki) jakie musi spełniać klasa dziedzinowa wyrażane są w sekcji static constraints. Poza warunkami określa porządek wyświetlania pól przy wykorzystaniu mechanizmu rusztowania (scaffolding), np.
 class Wpis {
static constraints = {
tytul(nullable:false)
autor(blank:false)
data()
}
}
blank:false = nullable:false + pole nie może być ciągiem o długości 0

Dostępne ograniczenia to blank, creditCard, email, inList, matches, max, min, minSize, maxSize, notEqual, nullable, range, scale, size i url.

Istnieje możliwość stworzenia własnych za pomocą atrybutu validator, po którym następuje treść ograniczenia (jako domknięcie). Wynik true/false określa poprawne/niepoprawne spełnienie warunku. Atrybut it wskazuje na kontrolowane pole (jak to w domknięciu).
 static constraints = {
tytul(nullable:false,
validator: {
println it
return true
})
}
Istnieje możliwość zdefiniowania własnego warunku z parametrami val i obj, które są, odpowiednio, nową wartością pola i samym obiektem klasy dziedzinowej.

Bezpośrednie wywołanie walidacji to wywołanie metody validate() na obiekcie klasy dziedzinowej, np.
 assert true == wpisOGrails.validate()
W Grails istnieje hierarchia komunikatów walidujących, z bardziej ogólnych, niezwiązanych z danym ekranem, do bardzo szczegółowych z nazwą klasy dziedzinowej i pola, np. (za Table 6-3) todo.name.blank.error.Todo.name czy po prostu blank. Komunikaty walidujące zapisane są w zewnętrznym pliku komunikatów (podobnie jak komunikaty dla znacznika GSP - g:message) - domyślnie w messages.properties. Do wyróżnienia własnych walidatorów dodajemy validator, np. wpis.tytul.validator.error.Wpis.tytul byłby najbardziej szczegółowym.

W GORM nie ma potrzeby tworzenia klas DAO z void delete(KlasaDziedzinowa k), void save(KlasaDziedzinowa k) czy podobnie. Każda klasa dziedzinowa ma zapewnione metody save(), get() oraz delete() automatycznie. Po prostu są i tyle. Dzięki temu możemy klasę dziedzinową wciąż nazywać lekką mimo połączonej funkcjonalności klasy dziedzinowej i DAO.

W Grails wyróżnia się trzy typy zapytań - dynamiczne zapytania GORM, zapytania HQL oraz zapytania kryteriowe (ang. Hibernate's criteria queries). GORMowe zapytania, podobnie jak kryteriowe, są ograniczone do zapytań dla pojedycznej klasy.

Możemy zliczać - <klasa-dziedzinowa>.count() oraz zliczać po wybranym polu - <klasa-dziedzinowa>.countBy<NazwaAtrybutu>(<wartość>). W podobnym schemacie konstruowane są zapytania pojedynczego rezultatu - findBy i findWhere - oraz zapytania wielokrotnego rezultatu - findAllBy, findAllWhere, getAll, listOrderBy i list, tj.
 <klasa-dziedzinowa>.<typ-zapytania><Pole1><typ-wiązania><Pole2>...(<lista-wartości>)
, np.
 Wpis.findByTag('grails')
Wpis.findByTagAndTitle('grails','%Groovy and Grails%')
Wpis.findByTagAndTitleOrDate('grails','%Groovy and Grails%','15.01.2008')
i tak dalej.

Można również tak (Listing 6-29):
 Todo.findWhere([priority:"1", status:"2"])
albo (Listing 6-32):
 Todo.getAll(1,3,5)
gdzie pobieramy klasy dziedzinowe odpowiadające identyfikatorom 1, 3 i 5.

Możemy również zawężać interesujące nas rekordy za pomocą filtrów, np. (Listing 6-35)
 Todo.list(max: 10, offset: 20, sort: "priority", order "desc")
Po tej prezentacji zapytań GORMowych pojawia się ciekawe podsumowanie: "using these queries is like eating sushi - an hour later, you're hungry for more". I pojawiają się zapytania HQL z find, findAll oraz executeQuery. Wiedza HSQ przydaje się. Później pojawiają się zapytania kryteriowe ze wstawkami DSLowymi, np. (Listing 6-40)
 List executeCriteriaQuery(def params) {
def todos = Todo.createCriteria().list {
and {
params.each { key, value ->
like (key, value)
}
}
}
}
Wyjaśnienie, co robi powyższe zapytanie pozostawiam dociekliwym. Przykład ma jedynie dać pojęcie możliwości GORM - połączenia cech Hibernate z dynamicznością Groovy.

Na zakończenie sekcji dotyczącej modelu autorzy przedstawiają dwie wtyczki grailsowe upraszczające wprowadzanie zmian między klasami dziedzinowymi a schematem bazodanowym - dbmigrate oraz LiquiBase. Zainteresowani są kierowani do dokumentacji dostępnej chociażby przez grails plugin-info. Podobno w Rails utrzymywanie synchronizacji między klasami a schematem jest bardziej zaawansowane niż w Hibernate, więc nie ma innej możliwości dynamicznej aktualizacji poza stworzeniem bazy danych i aktualizacji (nie wiem, czego jeszcze mógłbym oczekiwać, ale pewnie w dokumentacji RoR jest to wyjaśnione - udam, że mnie to nie interesuje ;-)).

W końcu przechodzimy do sekcji usługowej - omówienia usług w Grails. Klasy usług w Grails nie mają bezpośredniego dostępu do otaczającego ich środowiska webowego ze wszystkimi bajerami typu sesja, żądanie, itp., jak to ma miejsce przy kontrolerach, oraz jedynie kontroler może być wywołany z GSP. Klasy usług umieszczamy w katalogu grails-app/services. Zaletą "wynoszenia" funkcjonalności do klas usługowych jest możliwość kontrolowania obszaru transakcyjnego oraz kontekstu.

Tworzymy serwis poleceniem grails create-service <nazwa-usługi> i otrzymujemy klasę usługi z testem. Domyślnie transakcje są włączone w trybie PROPAGATION_REQUIRED przez
 boolean transactional = true
w klasie usługi <nazwa-usługi>Service.

Dostęp do usługi możliwy jest za pomocą mechanizmu wstrzeliwania zależności (ang. DI - Dependency Injection), który sprowadza się do zadeklarowania zmiennej z nazwą odpowiadającą nazwie klasy usługi, np.
 def todoService
Jeszcze do niedawna Seam nie był dla mnie szkieletem DI, ale po ostatnich rozmowach podczas WarsJava 2008 i lekturze kilku wpisach na blogach zmieniłem zdanie. Dodatkowo, i w tej książce Seam występuje w jednym zdaniu z Spring, HiveMind oraz "any other injection framework out there".

Istnieje możliwość wstrzelenia usługi do innej usługi oraz dowolnego ziarna springowego zdefiniowanego w spring/resources.xml przez jego identyfikator (atrybut id w bean).

Jeśli chcemy wykonać pewne czynności podczas uruchamiania usługi korzystamy ze springowego org.springframework.beans.factory.InitializingBean i jego metody afterPropertiesSet().

Na koniec pojawia się temat dostępnych kontekstów (wykonania) dla usług - prototype, request, flash, flow, conversion, session oraz singleton (domyślny). Zmiana kontekstu (zasięgu) możliwa jest przez
 static scope = "flow"
w klasie usługi.

W ostatnim komentarzu, już w podsumowaniu, zaleca się "odwiedzić" kod źródłowy GORM i w ogóle Grails, aby poznać faktyczną ich moc. Może kiedyś.

25 stycznia 2009

Tworzenie interfejsu użytkownika w Grails - rozdział 5 z "Beginning Groovy and Grails"

0 komentarzy
Teraz dopiero zaczęło się niezwykle interesująco. Dopiero poczułem klimaty Grails. Przyszła pora na dwa mocne rozdziały, z których pierwszy to rozdział 5. "Building the User Interface" z książki Beginning Groovy and Grails: From Novice to Professional. W rozdziale dowiadujemy się o GSP (Groovy Server Pages), znacznikach grailsowych, szablonach i "uzbrajaniu" ich w CSS. Jako przykład praktyczny przedstawianych cech Grails tworzy się interfejs użytkownika z nagłówkiem, stopką, bocznymi panelami, dzieli się centralną przestrzeń na faktyczną zawartość strony i prawy "dodatek" w postaci listy znajomych. Do tego dodaje się podstawową funkcjonalność obsługi procesu uwierzytelnienia. Wszystko na 60 stronach (tak, jest trochę zrzutów ekranu, ale ilość informacji jest i tak przytłaczająca). Interesujące są wycieczki w stronę innych technologii oraz pojawia się coraz więcej porównań z Ruby on Rails.

Grails korzysta z szablonów i układów do budowy stron.

Szablony (ang. template) są w katalogu grails-app/views i rozpoczynają się od podkreślnika (_), co jest konwencją nazewniczą Grails dla wyróżnienia szablonów. Zaleca się, aby umieścić zestaw szablonów dla danej klasy dziedzinowej w jej własnym katalogu "widokowym", np. grails-app/views/user dla klasy dziedzinowej User. Wspólne, bardziej ogólne szablony, powinny znajdować się w grails-app/views/common.

Układ (ang. laytout) to zestaw szablonów w odpowiednim ułożeniu/rozkładzie. Tworzymy układ, np. main.gsp i zapisujemy w domyślnym katalogu grails-app/views/layouts wraz z CSS - main.css w web-app/css.

Dodajemy szablon do układu z pomocą <g:render>, np. <g:render template="/common/footer" />. Kolejną konwencją Grails jest określenie szablonu bez podkreślnika oraz rozszerzenia .gsp. Upiększamy dołączony szablon przez "opakowanie" <div>, z którym związujemy odpowiedni styl.

W pierwszym przykładzie w książce spotykamy się z kilkoma nowymi konstrukcjami GSP - g:layoutTitle, ${createLinkTo}, g:layoutHead, g:javascript, g:layoutBody oraz wspomnianym g:render. Są jeszcze kolejne, a w jednym z nich konstrukcja g:if/g:else, aby "oprogramować" if-then-else do wprowadzenia większej dynamiki w stronach GSP. To jest właśnie ten element Grails, którego nie mogę zaakceptować, podobnie jak znaczniki JSP czy skrypty JSP w JavaServer Pages (JSP). Znaczniki są elementami programistycznymi, a jedynie przypominają swoją budową elementy HTML. Podstawowy mój zarzut względem jakichkolwiek znaczników, w tym i Grails, to brak możliwości podejrzenia wyglądu strony korzystając z edytorów HTML. Żaden ze znanych mi narzędzi HTML nie rozumie znaczników Grails, więc, podobnie jak w JSP, tworzenie stron HTML będzie niezwykle uciążliwie. Gdybym nie znał rozwiązania tego problemu, pewnie wstrzymałbym się z tymi uwagami, ale znajomość Apache Wicket już nie pozwala mi na to (chociaż i tam pojawiają się dedykowane znaczniki). W Wicket (oraz w nieznanych mi Tapestry i Echo) łączenie stron HTML z odpowiadającym im klasom javowym odbywa się przy pomocy dodatkowego i akceptowanego przez edytory (X)HTML atrybutu wicket:id. Oby dla GSP i jego specyficznych znaczników była alternatywna, tak aby nie trzeba było poszukiwać nowych, dedykowanych narzędzi edycyjnych dla osób od HTML w zespole projektowym (nie wspominając o tych od "bebechów", tj. programistów javowo-grailsowych).

Główna strona startowa w aplikacji grailsowej to web-app/index.gsp.

W międzyczasie pojawia się kolejny znacznik GSP - g:each, który jest znacznikiem przejścia dostępnych wartości (atrybut var) w liście (atrybut in) oraz konwencja związana z układami - określenie domyślnego układu przez <meta name="layout" content="main" />, która wskazuje na grails-app/views/layouts/main.gsp.

Biblioteka znaczników Grails przypomina JavaServer Pages Standard Tag Library (JSTL) oraz znaczniki Struts. Po chwili znajdujemy w książce przedstawienie dostępnych znaczników grailsowych, które otrzymują miano "Grails' strength". Ja nie podzielam tego zdania, po tym kiedy zobaczyłem ładniejsze architektonicznie rozwiązanie z Apache Wicket - programista javy zajmuje się modelem, a ekspert HTML widokiem bez konieczności łączenia tych światów jakimikolwiek wymyślnymi konstrukcjami. Prostota ponad wszystko. Czyżbym czegoś niedostrzegał, skoro Grails jako nowy szkielet webowy promuje znaczniki zamiast automagicznego łączenia obu światów ala Wicket.

Znaczniki logiczne: g:if, g:else oraz g:elseif - badamy wyrażenie warunkowe w atrybucie test i na tej podstawie wykonywany jest właściwy blok. Nie ma czego wyjaśniać.

Znaczniki iteracyjne - g:while, g:each, g:collect, g:findAll, g:grep.

Znacznik przypisania - set (nie wspomina się o identyfikatorze g?)

Znaczniki linkowania - g:link, g:createLink oraz g:createLinkTo (który był wcześniej prezentowany w postaci wyrażenia ${createLinkTo}).

Znaczniki ajaksowe - g:remoteField, g:remoteFunction, g:remoteLink, g:formRemote, g:javascript, g:submitToRemote.

Znaczniki formularzy - g:actionSubmit, g:actionSubmitImage, g:checkBox, g:currencySelect i wiele innych, których zadaniem jest umożliwienie dynamicznego tworzenia struktur "formularzowych" HTML, tj. wypełnianie ich dynamicznymi wartościami.

Bardzo skroma kategoria znaczników budowania interfejsu użytkownika jest reprezentowana przez g:richTextEditor. Więcej znaczników dostarczanych jest przez dedykowane wtyczki grailsowe.

Następnie bardzo liczna grupa znaczników tworzenia szablonów i układów stron, z której poznaliśmy już g:render oraz rodzinę g:layout*. Jest ich znacznie więcej.

Znaczniki kontroli poprawności (walidacji) zamyka prezentację dostępnych znaczników GSP. Tutaj znajdziemy g:eachError, g:hasErrors, g:message oraz g:fieldValue.

Na podstawie <g:link controller="user" action="login"> przedstawia się kolejną konwencję grailsową - wskazanie jakie elementy aplikacji grailsowej zostaną wywołane. W tym przypadku będzie to wykonanie akcji login na kontrolerze UserController, a ostateczna strona, na którą zostanie przekierowanie żądania - login.gsp - znajduje się w grails-app/views/user. W tym momencie spodziewałbym się przedstawienia elementów adresu URL, które wskazują na kontroler, akcję (metodę kontrolera) i przekazywane parametry. To jednak pojawia się dużo, dużo później (niestety).

Deklarujemy akcję (metodę) kontrolera konstrukcją
 def <nazwa-akcji> = {}
w kontolerze, np. dla akcji login byłoby to
 def login = {}
W ciele metody można wykonywać wszystkie możliwe czynności (wywołania metod) jakie dostępne są w Grails/Groovy/Javie, czy co tam sobie jeszcze wymyślimy. W książce pojawiają się dwa przykłady na obsługę akcji handleLogin oraz logout.

Zgodnie z dobrą praktyką w aplikacjach webowych wywołanie akcji kończy się przekierowaniem (ang. redirect) do właściwej strony GSP, która odpowiada nazwie akcji (co w naszym przypadku byłoby login.gsp w grails-app/views).

Sprawdzenie poprawności działania kodu, a w tym konkretnym przypadku aplikacji grailsowej ze stronami GSP, jest możliwe dzięki dwóm powszechnie dostępnym szkieletom testowania JUnit oraz Canoo. Obsługują one testowanie jednostkowe (JUnit), integracyjne (JUnit) i funkcjonalne (Canoo). Wspomina się o Six Sigma i Poka-yoke, które jest japońskim słowem i nazwą dla metody popełniania pomyłek tak oczywistych, że ostatecznie zapobiega się im, właśnie przez tworzenie testów. Nigdy nie słyszałem o tym Poka-yoke, więc potwierdza się reguła, że warto czytać książki ;-)

Za pomocą polecenia grails create-controller tworzymy kontroler oraz pusty test integracyjny w test/integration. Test integracyjny tworzymy poleceniem grails create-integration-test, natomiast test jednostkowy grails create-unit-test (w katalogu tests/unit). Ważną różnicą między testami integracyjnym i jednostkowym w Grails jest fakt, że w klasie testu jednostkowego niedostępne są dynamiczne metody save, delete oraz findBy*. W końcu testy jednostkowe testują działanie logiki w pojedyńczej klasie (jednostce kompilacji), a integracyjne ze wszystkim wokół (m.in. z bazą danych). Wspomina się w jednym zdaniu o metodach MockFor* oraz StubFor*, ale nic poza wymienieniem ich nazwy.

Na przykładzie testu integracyjnego dla UserController można przekonać się jak prosto pisze się je w Grails. Baza jest, metody dostępu do bazy danych również (bo to test integracyjny, a nie jednostkowy, więc Grails dostarcza je dynamicznie) i wystarczy jedynie przypisywać parametry żądania HTML z formularza - mapa params - wykonać akcję (wywołanie metody) i sprawdzić, czy wynik jej działania jest jak oczekiwano, co w przypadku UserController i akcji handleLogin będzie umieszczeniem obiektu user w sesji (ponownie mapa, ale tym razem o nazwie session).

Uruchamiamy testy poleceniem grails test-app, z opcjonalnym podaniem nazwy klasy testowej, aby zawęzić ich liczbę, np. grails test-app UserController (wykonają się metody rozpoczynające się test z klasy UserControllerTests). Wynik działania testów pojawi się na konsoli oraz w raporcie w test/reports.

W końcu przychodzi nam poznać wtyczkę grailsową Canoo WebTest, za pomocą której tworzymy testy funkcjonalne. Instalujemy wtyczkę poleceniem grails install-plugin webtest. Następnie poleceniem grails create-webtest <klasa-dziedzinowa> tworzymy test jednostkowy dla wybranej klasy dziedzinowej w katalogu webtest/tests/testsuite.groovy, która rozszerza grails.util.WebTest (z konfiguracją webtest/conf/webtest.properties). testsuite.groovy wykonuje metodę suite() dla wszystkich klas z katalogu webtest/tests zakończonych na Test. Wykonujemy testy poleceniem grails run-webtest. Wynik prezentowany jest w przeglądarce.

Wszystkie szkielety webowe udostępniają mechanizm "wyniesienia" ciagów znaków wrażliwych na ustawienia międzynarodowe do zewnętrznych plików komunikatów (ang. message bundles). Działanie opiera się na działaniu plików komunikatów w Javie. Domyślny plik komunikatów messages.properties znajduje się w katalogu grails-app/i18n (dla przypomnienia: i18n = internationalization, tj. i + 18 znaków + n, podobnie jak l10n = localization). Atrybut code w znaczniku g:message wskazuje na klucz w pliku komunikatów. Należy uważać na testy funkcjonalne z użyciem Canoo WebTest i jego metody verifyText - specjalnego wsparcia nie ma w związku ze zmianą ustawień międzynarodowych, więc samemu należy zadbać o zgodność testów i aplikacji.

Kolejna sekcja w tym rozdziale dotyczy kontroli poprawności wprowadzonych danych (walidacja). Wspominałem już w poprzednich relacjach, że w klasie dziedzinowej można określić warunki jakie musi spełniać klasa przed zapisem - static contraints. Ich niespełnienie spowoduje wyświetlenie strony z domyślnymi komunikatami na czerwono. Do ich zmiany musimy wygenerować statyczną wersję aplikacji w trybie scaffolding, tzw. static scaffolding poleceniem grails generate-views <klasa-dziedzinowa>. Grails utworzy strony create.gsp, edit.gsp, list.gsp i show.gsp w katalog grails-app/views/<klasa-dziedzinowa>. Następnie konieczne jest stworzenie "statycznego rusztowania" (ang. static scaffolding) dla kontrolera poleceniem grails generate-controller <klasa-dziedzinowa>. Uwaga na nadpisanie kontrolera dla danej klasy dziedzinowej. W stworzonych stronach GSP w ruch idą znaczniki g:hasErrors oraz g:renderErrors.

Autorzy przechodzą dalej do omówienia dodatkowego zasięgu (przestrzeni) widoczności obiektów - flash, który wypada pomiędzy żądaniem (request), a sesją (session). Jest on szczególnie przydatny w przypadku domyślnego przekierowywania żądania do strony GSP, przy którym parametry żądania giną, a sesyjne istnieją zbyt długo i należałoby pamiętać o ich "ręcznym" usunięciu. Poza flash mamy do dyspozycji zasięgi: application, session, request oraz page. Wszystkie są mapami. Istnieje możliwość zdefiniowania własnych przestrzeni. Tutaj pojawia się wzmianka o podobieństwie z Ruby on Rails, gdzie pojęcie takiej przestrzeni również istnieje. Łącząc g:message (wyświetlanie komunikatów z zewnętrznego pliku komunikatów) oraz przestrzeń flash, w którym umieszczamy klucze komunikatów możemy obsłużyć temat umiędzynarodowienia komunikatów błędów.

W końcu pojawia się omówienie tematu kontrolowania przebiegu w aplikacji z poziomu kontrolera (jako bardziej właściwego niż kopiowanie podobnej funkcjonalności w widokach), na przykładzie dostępu do danych użytkownika A innym użytkownikom. Jako przykład przytoczę Listing 5-30 z książki:
 def edit = {
if (session.user.id != params.id) {
flash.message = "You can only edit yourself"
redirect(action:list)
return
}
}
Wyjaśnienie działania akcji edit powinno być samowyjaśniające. Na uwagę zasługuje zwłaszcza wywołanie metody redirect, która przekierowuje żądanie do akcji list na zadanym kontrolerze (w tym przypadku będzie to ten sam kontroler). Jako zagadkę pozostawiam wyjaśnienie umieszczenia return po redirect.

Obiekt params jest zmienną mapą parametrów żądania.

I dopiero teraz pojawia się wyjaśnienie budowy adresu URL w Grails
 http://<serwer>:<port>/<kontekst>/<kontroler>/<akcja>/<parametr-klucz>/<parametr-wartosc>/...
Zabezpiczenie samej strony jest zdecydowanie niewystarczające, gdyż włamywacz mógłby spreparować pożądany adres URL samodzielnie i go wywołać bez wywołania strony GSP. Stąd koniecznym jest umieszczenie właściwej kontroli w kontrolerze. Dotyczy to dowolnej technologii webowej.

Wyrażenie return w Grails może zwrócić model, który będzie stosowany do wyświetlenia widoku - strony GSP, w której będą dynamicznie pobierane wartości z modelu, np.
 return [user: mojObiektUser]
Zatem wszystkie odwołania do obiektu user będą odwoływały się do modelu z user opartym na mojObiektUser. Można również zapisać to w równoważnej postaci
 [user: mojObiektUser]
gdyż w Groovy, a więc i w Grails, wynik ostatniego wyrażenia jest wynikiem wywoływanej metody.

Za pomocą konstrukcji
 <klasa-dziedzinowa>.properties = params
w kontrolerze przypisujemy pasujące parametry żądania do odpowiadających im atrybutom klasy dziedzinowej, co znacząco skraca ilość potrzebnego kodu do przypisania danych wprowadzonych przez użytkownika w formularzu do klasy dziedzinowej. Ten mechanizm nazywamy przypisywaniem danych (ang. data binding) i będzie wyjaśnione szerzej w kolejnym rozdziale. Korzystając z niego możemy przypisać pola klasy dziedzinowej korzystając z dynamicznie tworzonego konstruktora, np.
 def user = new User(params)
Grails rozpozna przekazane w mapie params (która jest mapą parametrów żądania, np. z formularza) dostępne dane i przypisze je do odpowiednich pól.

Jako, żę kontrola poprawności to sztandarowy przykład na użycie mechanizmów programowania aspektowego (ang. AOP - Aspect-Oriented Programming), więc pojawia się temat interceptorów (które znane są również z EJB3). Autorzy porównują je również z filtrami w Java Servlets oraz (ponownie) Ruby on Rails. W Grails dostępne są interceptory before oraz after, czyli, odpowiednio, zaraz przed wykonaniem i zaraz przed zakończeniem wykonania metody. Wystarczy zdefiniować domknięcie beforeInterceptor lub afterInterceptor w klasie kontrolera, aby uzyskać pożądaną funkcjonalność. Oba interceptory (metody przechwytujące) to domknięcia, więc mają pełny dostęp do wszystkich widocznych zmiennych kontrolera. Dzięki operatorowi bezpiecznego odczytu ?. mamy możliwość zapobieżenia NPE (= NullPointerException). Operator ?. sprawdza, czy aktualna część wyrażenia nie jest null, zanim rozpocznie wykonanie kolejnej części. Oba interceptory wywoływane są domyślnie dla każdej metody kontrolera, ale za pomocą warunków (wykonania) interceptora (ang. interceptor conditions) - atrybut except w definicji interceptora - ich wykonanie może być zawężone, np.
 def beforeInterceptor = [action:this.&beforeAudit, except:['list']]
gdzie action to domknięcie do wykonania przed wykonaniem wszystkich akcji kontrolera, poza akcją list.

Na zakończenie omawiany jest temat filtrów, które zbliżone są w działaniu do interceptorów, ale są definiowane w dedykowanej dla siebie klasie, której nazwa kończy się na Filters i mogą być związane z wieloma kontrolerami. Ich zastosowanie to umieszczenie danej funkcjonalności kontrolnej w jednym miejscu - filtrze i (warunkowe) związywanie z kontrolerami. Filtry aplikacyjne znajdują się w katalogu grails-app/conf, np.
 class UserFilters {
def filters = {
userModificationCheck(controller: 'user', action: '*') {
// tutaj treść filtru
}
someOtherFilter(uri: '/user/*') {
// tutaj treść filtru
}
}
}
W powyższym przykładzie (zapożyczonym z książki - Listing 5-41) związujemy filtr userModificationCheck ze wszystkimi akcjami (parametr action: '*') kontrolera UserController (parametr controller: 'user'), a someOtherFilter z adresem pasującym do wzorca /user/* (parametr uri: '/user/*'). Określenie wykonania filtra przez, po czy w obu przypadkach definiujemy przez wyrażenie before w ramach definicji filtra, np.
 userModificationCheck(controller: 'user', action: '*') {
before = {
// tutaj treść filtra
}
}
Wykonanie różnych czynności dla różnych akcji (kiedy zdefiniowano, że interesują nas wszystkie akcje - parametr action: '*') jest możliwe z
 if (actionName == 'edit') {
...
}
w ramach treści filtra.

W ramach komentarza autorzy opisują mechanizm odnotowywania komunikatów (ang. logging). Wszystkie kontrolery w Grails mają dostęp do atrybutu log typu org.apache.commons.logging.log. Domyślnie Apache Commons Logging pracuje na bazie Apache log4j. Konfiguracja odnotowywania komunikatów znajduje się w grails-app/conf/Config.groovy i domyślnie jest INFO.

Kolejny rozdział 6 "Building Domains and Services" jest równie bogaty, ale po nim i dzisiejszym pójdzie już z górki. Można będzie w końcu przymierzyć się do pierwszej grailsowej aplikacji.

p.s.I Przeczytałem na blogu Tomka Nurkiewicza - Elegancki CRUD w jednej akcji Struts2 część 1/2 o rozwiązaniu dla przekierowania żądania i utrzymywania komunikatów między żądaniami, których byt powinien być większy od żądania (request), a krótszy od sesji (session). Struts2 rozwiązuje to przez interceptor, a Grailsy przez zakres flash. Mimo wszystko zdumiało mnie przypadkowe(?) przedstawienie problemu, o którego rozwiązaniu właśnie przeczytałem w Grails. Jestem gotów nazwać styczeń 2009 miesiącem zbiegów okoliczności.

p.s.II W międzyczasie trafiłem na ciekawy projekt ze stajni Mozilla Labs - Mozilla Ubiquity. Warto zapoznać się z prezentacją na głównej stronie projektu (bodajże 5 minut), aby poznać ułatwienia, jakie oferuje nam projekt (daje również pojęcie, czego moglibyśmy oczekiwać w naszych aplikacjach webowych, aby były jeszcze bardziej powalające na kolana). Wystarczy zainstalować wtyczkę Ubiquity 0.1.5 i Ctrl+Spacja, aby w praktyce przekonać się o jego sile. Ja już mam. Na razie FF wstał bez problemów i działa z kilkoma poleceniami, więc całkiem nieźle ;-) Już skorzystałem chociażby z Ctrl+Spacja i fullscreen oraz Ctrl+Spacja i deli

p.s.III Pojawił się nowy egzamin na javaBLACKbelt - Groovy - Basic. Można, więc przejrzeć już dostępne pytania i poznać Groovy oczyma innych (w końcu pytania tworzone przez społeczność są właśnie tymi najbardziej wymagającymi, w których cechy produktu są naciągane do granic ich możliwości).

p.s.IV Jeden konkurs minął, a zaczął się kolejny - Bloger Roku 2008. Napiszę o nim szerzej w kolejnym wpisie - teraz tylko taka zajawka.

22 stycznia 2009

Wieści z rozdziału 4. "Introduction to Grails" z "Beginning Groovy and Grails"

5 komentarzy
Po materiale o Groovy przyszła w końcu pora na coś grailsowego - rozdział 4. "Introduction to Grails" w książce Beginning Groovy and Grails: From Novice to Professional. Niech nie przeraża nikogo 40 stron w tym rozdziale, bo wiele w nim zrzutów ekranu. W zasadzie można przyjąć, że przeczytanie tego rozdziału i 2 kolejnych daje pogląd na możliwości Grails. Reszta książki dotyka już specjalizowanych tematów, które każdy szkielet webowy w jakimś tam stopniu udostępnia, więc i w Grails nie mogło tego zabraknąć. O tym jednak w kolejnych relacjach.

Rozdział zaczyna się od przedstawienia jak to programista aplikacji webowych ma źle. Pierwsza sekcja zaczyna się od "Let's face it: developing web application is hard". Po tym i kilku następnych zdaniach miałem wrażenie, że Ci, którzy zajmują się tworzeniem serwisów internetowych korzystając z technologii javowych to jacyś straceńcy - mnogość technologii przytłacza, a będzie ich jeszcze więcej. Normalnie mamy przesr...chlapane! Jak można się domyśleć, autorzy, czarną wizję świata tworzenia aplikacji webowych rozpościerają przed nami, aby przedstawić panaceum - i to ma być właśnie Grails. I w dodatku aplikacje na platformie Java EE nie są wystarczająco zwinne (ang. agile), a na pewno za dużo w nich XMLa. W Grails za to jest inaczej. Jest to szkielet webowy, który naszpikowany jest najlepszymi praktykami, jak konwencje-zamiast-konfiguracji, wbudowane testy jednostkowe oraz wielką trójcę świata projektów otwartych - Spring Framework, Hibernate oraz SiteMesh (może zbyt dużego określenia użyłem dla SiteMesh, ale niech mu tam będzie). Dodając do tego podwaliny w postaci języka Groovy i platformy Korporacyjnej Javy (Java EE), a nawet wybredni powinni znaleźć coś w Grails dla siebie. Nic tylko programistyczna nirvana (przynajmniej z perspektywy autorów)!

Grails jest szkieletem opartym o wzorzec Model-View-Controller (MVC). Model w Grails to klasy dziedzinowe (ang. domain classes - w Java EE nazywa się je po prostu encjami), które są automatycznie bytami trwałymi. Dzięki Hibernate mogą również tworzyć cały schemat bazodanowy podczas uruchomienia. Opierając się na konwencji nazewniczej korzystamy z IoC (automatycznego przekazywania zależności). Kontroler (zarządca) powoływany jest na czas trwania pojedynczego żądania. Domyślny widok oparty jest na Groovy Server Pages (GSP). Dodajemy do tego system szablonów oraz biblioteki znaczników i powinniśmy mieć komplet narzędzi do tworzenia aplikacji webowych.

Z Grails dystrybuowane są: kontener servletów Jetty, baza dnaych HSQLDB (domyślnie w trybie "w pamięci" z każdorazowym tworzeniu schemtu bazodanowego podczas uruchomienia aplikacji) oraz system budowania Gant i wykonywania testów (bazujący na JUnit). Na pewno znacznie skraca czas zestawiania środowiska programistycznego, co kwalifikuję jako plus.

Grails dystrybuowany jest na licencji Apache Software License (ASL) 2.0 pod adresem http://grails.org.

Dla mnie najbardziej interesującą sekcją wprowadzenia do Grails było wyjaśnienie pojęcia scaffolding. Spotykałem się z tym terminem wielokrotnie i wciąż myślałem, że dotyczy to pewnej czynności (zmyliła mnie owa końcówka -ing, podobnie jak slippery, które bardziej przypomina mi rzeczownik niż przymiotnik). Scaffolding to po prostu rusztowanie, które budowlańcom pozwala na konstrukcję domu, a nam, programistom, a w zasadzie to samemu Grailsowi, na dynamiczne budowanie zrębów aplikacji typu CRUD (ang. Create-Read-Update-Delete) na bazie wybranego przez nas modelu bez specjalnego wysiłku programistycznego.

Grails opiera utrwalanie danych relacyjnych na bazodanowe (ang. ORM - Object-Relational Mapping) na Grails Object Relational Mapping (GORM). To GORM modyfikuje zachowanie naszego modelu, aby automatycznie udostępnić funkcjonalność CRUD, więc jedyne co my musimy zrobić, to po prostu utworzyć klasę bez konieczności dbania o dodatki ORM. Zamiast POJO (ang. Plain Old Java Object - stary dobry obiekt javowy) w świecie Groovy mówi się o POGO (ang. Plain Old Groovy Object - stary dobry obiekt groovy).

Grails sam w sobie jest jedynie szkieletem, który udostępnia bardzo podstawowe funkcjonalności do tworzenia aplikacji webowych. Resztę "załatwiają" wtyczki, o które można rozszerzyć nasz projekt grailsowy.

Do dyspozycji mamy trzy popularne biblioteki ajaxowe: script.aculo.us, Rico oraz Prototype. Fundamety Web 2.0 są dostępne od ręki, co również zaliczam na plus.

Praca z Grails, poza programowaniem, to wydawanie poleceń grails, które oparte są na Gant - system budowania napisany w Groovy do tworzenia bezxmlowych skryptów Apache Ant.

Dalsza zawartość rozdziału to przedstawienie instalacji Grails (pobierasz, rozpakowujesz, zmieniasz PATH i voila), aplikacji Collab-Todo, która będzie tworzona w książce jako aplikacja wzorcowa do prezentacji cech Grails i przegląd podstawowych poleceń grails. Polecenia rozpoczyna mechanizm scaffolding. Od razu jednak autorzy nie pozostawiają złudzeń - aplikacja wykorzystująca scaffolding jest przydatna wyłącznie na etapie prototypowania. Mechanizm ten opiera się na prześwietlaniu obiektów (ang. reflection) podczas działania aplikacji, a to destrukcyjnie wpływa na jej szybkość.

Budowanie aplikacji rozpoczynamy od stworzenia struktury katalogowej wraz z podstawowymi plikami poleceniem grails create-app <nazwa-aplikacji>. Następnie wczytujemy projekt do naszego wybranego IDE. I tutaj niestety muszę nadmienić, że o NetBeans IDE ani słowa w książce. Wspomina się jedynie o Eclipse IDE, TextMate oraz IntelliJ IDEA. Polecenie create-app tworzy pliki projektowe dla Eclipse i TextMate.

Warto zapoznać się z czterema plikami konfiguracyjnymi w katalogu grails-app/config. Poza UrlMappings.groovy, wszystkie są świadome środowiska, w którym działają - development, test oraz production. Można definiować własne, ale o tym dopiero w rozdziale 12. Mamy więc BootStrap.groovy, w którym definiujemy obsługę startu i zatrzymania aplikacji, Config.groovy, w którym umieszczamy całą konfigurację, DataSource.groovy dla konfiguracji JDBC i JNDI oraz wspomniany UrlMappings.groovy z przez nas definiowanymi URLami.

Uruchomienie aplikacji Grails to grails run-app w katalogu projektu grailsowego. Wystarczy następnie uruchomić przeglądarkę i wskazać na http://localhost:8080/<aplikacja>, aby cieszyć oko swoim dziełem grailsowym. Nie spodziewajmy się wiele, skoro nic poza strukturą katalogową nie stworzyliśmy - potrzeba klas dziedzinowych (tych, których stan będzie utrwalany w bazie danych i dla których będzie stworzony dedykowany kontroler i widoki - mówimy w końcu o aplikacji CRUD). Potrzebujemy uruchomić polecenie grails create-domain-class <nazwa-klasy>. Dzięki GORM do niej dołączone zostaną metody do obsługi bazy danych, m.in. delete(), save() czy count(). Kasowanie wszystkich rekordów w bazie danych sprowadza się do wykonania <nazwa-klasy-dziedzinowej>.list()*.delete() (pamiętamy o istnieniu operatora spread w Groovy - <lista>*.metoda(), czyli uruchom metoda() na każdym obiekcie w lista?). Autorzy zwracają jednak uwagę na działanie metody list(), która wczytuje *wszystkie* rekordy z bazy - bardzo pamięciożerna operacja, która może zakończyć się OOME (=OutOfMemoryError).

Sprawdzenie poprawnego działania aplikacji można wykonać poleceniem grails test-app, która poza wyświetleniem wyniku testów na konsoli zapisze je w HTML w katalogu test/reports/html.

GORM dostarcza dynamicznie do naszych klas dziedzinowych dwóch pól id oraz version, za pomocą których Hibernate obsługuje zarządzanie trwałymi danymi w bazie danych.

W naszej klasie dziedzinowej można umieścić również pewne ograniczenia, jakie musi spełniać obiekt konstrukcją static constraints. W niej definiujemy, np. czy pole jest wymagane (wystarczy napisać nazwę metody dla danego pola, np. <pole>()), długość pola przez maxSize, lub określić, że pole może być puste (jako argument do metody pola <pole>(nullable: true)).

Kolejnym etapem podczas tworzenia aplikacji jest grails create-controller <nazwa-klasy-dziedzinowej>, który utworzy kontroler w katalogu controllers oraz test w test/integration. Powstanie również katalog grails-app/views/<nazwa-kontrolera>. Domyślnie, utworzony kontroler zawiera coś podobnego do:
 class TodoController {
def index = {}
}
Zamieniając def index = {} na def scaffold = <klasa-dziedzinowa> "uzbrajamy" naszą aplikację w mechanizm scaffolding. Utworzenie kontrolera rozbudowuje stronę startową aplikacji z jego wskazaniem. I tak dla każdego kontrolera. Ich liczba jest nieograniczona.

Autorzy nadmieniają o niedoskonałym momentami przeładowaniu aplikacji przez kontener Jetty. Czasami potrzebny jest jego restart, aby zmiany w modelu i kontrolerze aplikacji zostały rozpoznane. Wystarczy zatrzymać serwer kombinacją Ctrl-C, albo poleceniem kill i uruchomić go ponownie przez grails run-app.

Końcówka rozdziału dotyczy mapowania klas dziedzinowych w relacji jeden-do-wielu za pomocą atrybutów belongsTo oraz hasMany. Wystarczy do klasy dziedzinowej dodać
 User user

static belongsTo = [User]
aby klasa, która to zawiera była w relacji z klasą User przez pole user. Skasowanie obiektu user powoduje skasowanie zawierającej go klasy dziedzinowej związanej przez belongsTo.

Na koniec wyjaśnienie hasMany, tj. dodanie
 static hasMany = [todos: Todo, categories: Category]
powoduje utworzenie relacji jeden-do-wielu z klasą Todo. Kolekcje dostępne są przez atrybuty todos oraz categories. I tym samym kończy się rozdział 4 "Introduction to Grails". Następne 2 zapowiadają się jako najcięższe i jednocześnie najbardziej wartościowe.

Jest wiele materiału na Sieci o Grails, ale ja proponuję przyjrzeć się jednemu, który wpadł mi w ręce dzisiaj w pRaSSówce - Mastering Grails: Give your Grails applications a facelift. Wydaje się być wspaniałym podsumowaniem mojej dzisiejszej relacji. Jeśli zajrzy się do całej serii Mastering Grails na developerWorks będziemy mieli nie tylko okazję przeczytać ten artykuł, ale w dodatku 12 kolejnych (!) Jeśli tylko ma się czas, to można stać się ekspertem Grails w jeden dzień! ;-)

p.s. Konkurs Blog Roku 2008 jeszcze trwa i wszedł w 3. etap, ale już bez Notatnika - za mało głosów i wypadł z gry. Wszystkim głosującym bardzo dziękuję za udział w zabawie. Wierzę, że nie była zbyt kosztowna ;-)

21 stycznia 2009

Reklama nie zawadzi, jeśli odpowiednio umotywowana (przekonajmy się)

5 komentarzy
Ja tu dopiero, co wspomniałem o potrzebie rozpoznania uruchamiania testów Groovy z pomocą Apache Maven (Relacja z rozdziału 3. "More Advanced Groovy" z "Beginning Groovy and Grails"), a do mojej skrzynki trafia wiadomość o zmianach na stronie Grails > Maven Integration. Zbieg okoliczności, czy ktoś dba, abym nie stracił zainteresowania Groovy & Grails?! Na razie temat czeka cierpliwie na swoją kolej (która z pewnością nadejdzie - nie dziś, nie jutro, ale na pewno wkrótce).

Dla zainteresowanych przyspieszeniem rozpoznawania Grails natrafiłem na ciekawą prezentację Guillaume Laforge z Devoxx 2008, która trwała...3 godziny (!) Gość ma klasę, jeśli nie tylko potrafi programować, ale również przedstawiać temat w zrozumiały sposób. Dla niezorientowanych wspomnę, że jest on głównodowodzącym projektem Grails. Nie wiem, jak mu poszło, bo nagrania z jego wystąpienia nie ma, ale za to prezentacja jest do publicznej konsumpcji - Groovy and Grails in Action - Devoxx 2008 - University - Guillaume Laforge. Warto, bo nie zabiera wiele czasu (mimo liczby slajdów), a wiedza z samego źródła.

Dzisiaj miałem dwie ciekawostki, które sprawiły, że moje prywatne zainteresowania mogą mieć znaczący użytek służbowy. Podczas dzisiejszej rozmowy na temat IBM WebSphere Business Services Fabric (WBSF) V6.2 z moim podopiecznym, w ramach programu praktyk studenckich w IBM, moją uwagę przykuł jeden URL w aplikacji zarządzającej WBSF - ten, który zawierał w adresie...wicket:bookmarkablePage (!) Tylko mignęło, ale to wystarczyło, abym innym okiem spojrzał na WBSF, chyba nawet trochę przychylniejszym (chociaż i tak było wystarczająco przychylne). Wciąż jeszcze nieznane jest dla mnie biznesowe zastosowanie WBSF, ale sam fakt, że wykorzystuje pod spodem Apache Wicket od razu sprawił, że nie mogę doczekać się wyników mojego studenta, który ma za zadanie przedstawić produkt ze szczegółami. Możliwość połączenia zainteresowań prywatnych z obowiązkami służbowymi sprawia, że to, co człowiek robi w wolnej chwili staje się natychmiast zajęciem służbowym, a to cieszy każdego zawalonego robotą, czyż nie? Oczywiście ma to swoje minusy. Kiedy zainteresowanie staje się obowiązkiem, niewiele dzieli go od stania się ciężarem i znużenia nim. Sądzę, że do znużenia Wicketem nie dojdzie u mnie prędko (aczkolwiek dawno o nim nie wspominałem), a wraz z Groovy czuję, że wiele dobrego mnie czeka. Jeśli mógłbym "pożenić" te technologie w rozwiązaniach służbowych, tym lepiej dla mnie i klientów rozwiązań. Pozostaje tylko wierzyć, że będzie to dla nich równie wartościowym połączeniem, jak dla mnie.

Drugą ciekawostką, o której już wiedziałem od jakiegoś czasu, ale teraz nabrała innego wydźwięku jest IBM WebSphere sMash. Komercyjny aczkolwiek bezpłatny produkt firmy IBM, który oparty jest o otwarty projekt Project Zero. A dlaczego o nim, kiedy powinienem właśnie przedstawić kolejny rozdział książki Beginning Groovy and Grails: From Novice to Professional? sMash jest szkieletem webowym, w którym aplikacje pisze się w PHP oraz...Groovy. Tu wszyscy wstają, klaszczą i rozpoczynamy imprezę! ;-) To jest właśnie ten moment, w którym jednym zamachem, rozpoznając Groovy, można jednocześnie rozpracować kolejny - IBM WebSphere sMash. Cudownie!

Można sobie teraz tylko wyobrazić miny wszystkich tych, którzy byli przeciwni stosowaniu obu technologii jako niedojrzałych lub (co bardziej kuriozalne) otwartych. Zdaje się, że już przynajmniej jeden gigant IT - IBM - postawił na Apache Wicket i Groovy. Czy trzeba więcej argumentów, aby przekonać najbardziej nieprzekonanych kierowników IT, architektów, szefów grup rozwojowych, że komercyjne produkty z płatnym wsparciem oparte są o produkty darmowe, co sprawia, że dojrzałość tych drugich jest wystarczająca do zastosowań komercyjnych? Sądzę, że tym samym odparty został ostatni zarzut o ryzyku wdrożenia obu i jedynym problemem może być wyłącznie nasz brak zainteresowania ich wykorzystaniem. Jeśli nawet miałem wątpliwości wczoraj w kontekście wartości płynących w promowaniu Groovy czy Wicket, bądź jeszcze dzisiaj rano, teraz już całkowicie się ich pozbyłem. Ktoś jeszcze tego doświadczył?

p.s. Nie byłbym sobą, gdybym nie wspomniał, że ten blog został zgłoszony do konkursu Blog Roku 2008. Coś nie może się ruszyć w rankingu przesłanych SMSów i bidulek siedzi na drugiej stronie w kolejności oddanych głosów. Proponuję natychmiast wysłać SMSa o treści B00204 (czytaj: be-zero-zero-dwa-zero-cztery) pod numer 7144 za 1,22PLN brutto i mieć to z głowy. Po co czekać na ostatni dzień?! ;-)