01 lutego 2009

Przetwarzanie wsadowe w Grails - rozdział 11 z "Beginning Groovy and Grails"

Rozdział 11. "Batch Processing" w książce Beginning Groovy and Grails: From Novice to Professional omawia temat przetwarzania wsadowego w Grails. Zdumiewające jest pierwsze zdanie rozpoczynające rozdział "Grails is more than just a web framework - it is an application framework". Wydaje się, że może być to bardzo zdumiewające, nie tylko dla takich nowicjuszy grailsowych jak ja. Od jakiegoś czasu śledzę grupę dyskusyjną Grails User, przeglądałem dokumentację na oficjalnej stronie Grails i do tej pory nie trafiłem na podobne stwierdzenie.

Prawie wszystkie aplikacje zawierają funkcjonalność, która musi być wykonywana o określonych porach lub co zadany interwał, którą nazywamy przetwarzaniem wsadowym (ang. batch processing).

W Grails obsługa przetwarzania wsadowego opiera się na otwartym projekcie Quartz, który dostarczany jest w ramach podstawy infrastrukturalnej Grails - Spring Framework. Quartz jest podobny do uniksowego crona, który uruchamia zadania w przyszłości, z tą różnicą, że może korzystać z komponentów aplikacyjnych udostępnianych w ramach serwera aplikacyjnego.

Pracę z Quartz w Grails rozpoczynamy jego instalacją poleceniem grails install-plugin quartz. Wtyczka dostarcza nowe polecenie grails create-job.

Zadanie (ang. job) jest aplikacją, którą chcemy wykonać o zadanej porze. Określamy co i kiedy ma być wykonywane.

Tworzymy zadanie poleceniem grails create-job <nazwa-zadania>. Powstanie klasa <NazwaZadania>Job w grails/job. Konwencją Grails jest, że polecenia create-* tworzą klasy o zadanej przez użytkownika nazwie dodając przyrostek odpowiadający poleceniu, np. create-job first tworzy klasę FirstJob. Domyślna klasa zbudowana jest z atrybutu timeout, który określa, co ile wykonywane będzie zadanie zdefiniowane przez domknięcie execute().

W rozdziale znajduje się przykład tworzenia funkcjonalności tworzenia i rozsyłania raportów w trybie wsadowym oparte na klasach i usługach stworzonych w rozdziałach wcześniejszych.

Klasa zadania może zawierać opcjonalne atrybuty name oraz group. Definiują one, odpowiednio, nazwę zadania oraz jego grupę.

Domyślnie zadanie związane jest z sesją Hibernate, więc pobieranie danych z bazy nie wymaga specjalnych czynności. Wyłączenie wiązania zadania z sesją Hibernate jest możliwe, jeśli ustawimy atrybut sessionRequired na false.

Mamy dwie możliwości definiowania pory wykonania zadania - atrybuty startDelay z timeout lub cronExpression. Atrybut startDelay (domyślnie 0) określa, kiedy wykonane zostanie zadanie, licząc od uruchomienia aplikacji. Atrybut timeout (domyślnie 60000 ms = 1 min) określa interwał (w milisekundach) między kolejnymi wykonaniami zadania. Atrybut cronExpression to możliwość wykorzystania własnej wiedzy uniksowej dotyczącej demona cron. Deklarujemy wykonanie zadanie w formacie crontab.

Może się zdarzyć, że wykonanie zadania nie zakończy się, a kolejne zostanie uruchomione. Kontrola równoległego wykonywania zadań jest możliwa przez logiczny atrybut concurrent (false/true).

Format cronExpression składa się z 6 pól oddzielonych białym znakiem (spacja/tabulacja). Poszczególne pola odpowiadają sekundom, minutom, godzinom, dniom, miesiącom, dniom tygodnia i opcjonalnie, w 7. polu, latom. Specjalne znaki to gwiazdka (*), znak zapytania (?), myślnik (-), przecinek (,) oraz ukośnik (/).

Zadanie ma dostęp do automatycznego wiązania zależności bez specjalnej konfiguracji, więc dostęp do usługi grailsowej to po prostu zadeklarowanie pola o odpowiednim typie bądź nazwie. Tworzymy usługę poleceniem grails create-service Batch, a następnie w klasie zadania def batchService (alternatywnie BatchService batchService). Spring Framework zajmie się resztą i zagwarantuje, że pole nie będzie niezainicjowane.

Usługa BatchService pobiera wszystkich użytkowników z niepustym polem email (Listing 11-4):
 def users = User.withCriteria {
isNotNull('email')
}
a następnie, wyłącznie jeśli users jest niepuste, pobiera listę zadań dla każdego z nich (Listing 11-4):
 users?.each { user ->
def inputCollection = Todo.findAllByOwner(user)
}
Niesamowita "skromność" kodu w Grails! Przypomina mi to dawne konkursy w C, w którym zawodnicy tworzyli zaawansowane programy w formie jednolinijkowców i z bardzo wyrafinowanymi konstrukcjami. Trzeba było nie lada umysłu i doświadczenia, aby docenić "zalet" takiego programowania. W Grails to "przykrycie" funkcjonalności Hibernate oraz Spring Framework wraz z dynamizmem Groovy (chociażby przez "doklejenie" metod bazodanowych) nasuwa mi takie skojarzenia. Kod nie jest jednak tak zawiły, jak to miało miejsce w tych jednolinijkowcach w C.

W poprzedniej relacji z rozdziału 10. "Reporting" (patrz Raporty w Grails - rozdział 10 z "Beginning Groovy and Grails") mieliśmy okazję poznać sposób na dotarcie do ziaren springowych za pomocą atrybutu sesyjnego GrailsApplicationAttributes.APPLICATION_CONTEXT. Tym razem autorzy zademonstrowali inny, alternatywny sposób - użycie interfejsu ApplicationContextAware. Interfejs dostarcza metodę void setApplicationContext(ApplicationContext applicationContext), która przekazuje kontekst springowy, który z kolei możemy wykorzystać do pobrania dowolnego ziarna springowego:
 EMailProperties eMailProperties = applicationContext.getBean("eMailProperties")
Tak też można, skoro Grails to "nakładka" na Spring Framework, a Groovy to "nakładka" na Javę. Nie zapomnieliśmy o tym, prawda?