19 kwietnia 2009

Rozdział 11. o usługach z DGG2

Rozdział 11. "Services" w książce "The Definitive Guide to Grails, Second Edition" omawia temat klas usługowych (usług) w Grails. Na bazie klas usługowych tworzy się warstwę usługową systemu z określonym API. Usługą nazwiemy po prostu pewną czynność biznesową, dla której w ogóle tworzymy system. Każdy system ma taką warstwę, mniej lub bardziej związaną z warstwą widoku czy kontrolerów (patrząc przez pryzmat wzorca MVC (Model-View-Controller)).

Grails opiera swoje działanie na pracy Spring Framework i naturalnie Spring stał się idealnym kandydatem dla zbudowania warstwy usługowej.

Klasy usługowe, podobnie jak inne klasy (poza testami jednostkowymi i integracyjnymi), nie rozszerzają jakiejkolwiek klasy bazowej i znajdują się w katalogu grails-app/services.

Polecenie do tworzenia klas usługowych: grails create-service [nazwa-klasy-usługowej].

Usługi są singletonami (przydałby się jakiś chwytliwy polski odpowiednik), co oznacza, że mamy do dyspozycji wyłącznie pojedynczy egzemplarz i stan, jeśli istnieje, współdzielony jest ze wszystkimi wywołaniami klienckimi.

W Spring Framework mamy do dyspozycji automatyczne wiązanie zależności (ang. autowiring) po nazwie lub typie. Podobnie działa tworzenie egzemplarzy usług w Grails. Wystarczy zadeklarować atrybut o nazwie klasy usługowej (rozpoczynając od małej litery), aby skorzystać z automatycznego wiązania po nazwie. Podczas uruchomienia atrybut zostanie zainicjowany przez Grails odpowiednim egzemplarzem usługi, np. (Listing 11-3):
 class StoreController {
def storeService
}
W tym przypadku korzystamy z dynamicznego wyznaczenia typu, ale istnieje możliwość jawnego podania typu klasy usługi.

Mechanizm automatycznego wiązania zależności dostępny jest w kontrolerach, usługach i znacznikach GSP.

Nie zalecane jest samodzielne tworzenie egzemplarzy klas usługowych, gdyż Grails, poza zmniejszeniem ilości pracy, jaką musimy włożyć do oprogramowania systemu, obsługuje również inne aspekty systemu, jak transakcje.

Klasy usługowe są klasami transakcyjnymi, tj. wszystkie publiczne metody wykonywane są pod auspicjami transakcji (podobnie jak publiczne metody biznesowe ziaren EJB). Kontrolujemy opakowanie transakcją metod publicznych usługi statyczną właściwością transactional, która akceptuje wartość logiczną true/false (domyślnie true).

Wyłączenie automatycznego opakowania transakcji dla publicznych metod to static transactional = false. Przy takiej konfiguracji, korzystamy ze statycznej metody withTransaction() dostępnej w każdej klasie dziedzinowej. Parametrem wejściowym jest domknięcie, które wyznacza zasięg transakcji, np. (Listing 11-7):
 package com.g2one.gtunes

class GtunesService {
static transactional = false

void someServiceMethod() {
Album.withTransaction {
// ...sama klasa usługowa jest nietransakcyjna,
// ale tutaj jesteśmy objęci transakcją
}
}
}
Pojawienie się wyjątku w ramach domknięcia powoduje wycofanie transakcji.

Samodzielne kontrolowanie wyniku transakcji w metodzie withTransaction() jest możliwe przez przekazywany parametr tx (typu org.springframework.transaction.TransactionStatus), np. (Listing 11-8):
 Album.withTransaction { tx ->
// ...seria operacji bazodanowych, transakcyjnych,
// po których decydujemy się wycofać transakcję
tx.setRollbackOnly()
}
Dostęp do usług nie jest synchronizowany, co w połączeniu z jej pojedynczym egzemplarzem, wymaga od nas samodzielnego dbania o jej współbieżność. Jeśli potrzebujemy przechowywać stan w usłudze możemy zdefiniować jej zasięg działania inny niż domyślny singleton. Mamy do dyspozycji:
  • prototype - nowy egzemplarz usługi jest tworzony każdorazowo podczas wstrzeliwania (rozwiązywania) zależności
  • request - nowy egzemplarz usługi per żądanie
  • flash - egzemplarz usługi ten sam dla obecnego i następnego żądania
  • flow - zasięg przepływu
  • conversation - zasięg przepływu i jego podprzepływów
  • session - zasięg sesji użytkownika
  • singleton - domyślny zasięg usług, gdzie mamy jedynie pojedynczą instancję w systemie
Użycie zasięgów flash, conversation oraz flow wymaga, aby klasa usługowa implementowała java.io.Serializable.

Deklaracja zasięgu istnienia usługi realizowana jest przez statyczną właściwość scope o wartości jak wyżej, np. (Listing 11-9):
 class LoanCalculationService {
static transactional = true
static scope = 'request'
}
Testowanie klas usługowych w ramach testów jednostkowych wymaga samodzielnego tworzenia egzemplarzy, natomiast testy integracyjne uruchamiają całą infrastrukturę Grails wraz ze Spring IoC. Do testowania jednostkowego możemy podeprzeć się Expando.

Udostępnianie usługi światu poza Grails możliwe jest przez dostępne wtyczki (rozszerzenia), np. jmx. Po instalacji wtyczki jmx poleceniem grails install-plugin jmx wystarczy zadeklarować statyczną zmienną expose z listą składającą się z pożądanego mechanizmu udostępniania, w tym przypadku byłoby to 'jmx', np. (Listing 11-11):
 class GtunesService {
static transactional = true
static expose = ['jmx']
}
Przy takiej konfiguracji, każda publiczna metoda jest dostępna via JMX i wystarczy uruchomić Grails z opcją com.sun.management.jmxremote, np. poprzez zmienną JAVA_OPTS, aby podłączyć się jconsole.

Istnieją wtyczki udostępniające usługi za pomocą protokołów XML-RPC, RMI, Hessian, Burlap oraz Spring HttpInvoker czy SOAP (wtyczki xfire i axis2). Uaktywnienie sposobu udostępnienia usług za pomocą wybranej technologii, to odpowiednie zadeklarowanie statycznej zmiennej expose. W ten sposób moglibyśmy stworzyć aplikację Grails całkowicie opartą na warstwie usługowej i dziedzinowej bez warstwy kontrolerów (kłania się SOA, czyli mocny argument za wdrożeniem Grails).