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 {W tym przypadku korzystamy z dynamicznego wyznaczenia typu, ale istnieje możliwość jawnego podania typu klasy usługi.
def storeService
}
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.gtunesPojawienie się wyjątku w ramach domknięcia powoduje wycofanie transakcji.
class GtunesService {
static transactional = false
void someServiceMethod() {
Album.withTransaction {
// ...sama klasa usługowa jest nietransakcyjna,
// ale tutaj jesteśmy objęci transakcją
}
}
}
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 ->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:
// ...seria operacji bazodanowych, transakcyjnych,
// po których decydujemy się wycofać transakcję
tx.setRollbackOnly()
}
- 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
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 {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.
static transactional = true
static scope = 'request'
}
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 {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.
static transactional = true
static expose = ['jmx']
}
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).
A może singleton = jedynak? Już ktoś w internecie tego używa.
OdpowiedzUsuńInteresujące. Odnotowałem i zaczynam się do tego przyzwyczajać. Myślałem już nawet o singlu ;-)
OdpowiedzUsuńChodzi o Projektowanie zorientowane obiektowo czy Języki i Techniki Programowania II? ;-)