Service w grails

Nie długi rozdział książki Definitive guide to grails, tym razem podejmuje tematykę serwisów.

Często stosowana w aplikacjach jest warstwa serwisów (service layer), która zawiera w sobie operacje biznesowe. Dzięki nim można wprowadzić warstwę abstrakcji oraz ograniczyć zależności pomiędzy warstwami mvc. Serwisów można używać dla:

  • potrzeby scentralizowania logiki biznesowej w API
  • przypadki użycia aplikacji operują na wielu klasach domenowych i operacji lepiej nie mieszać w jednym kontrolerze
  • pewne procesy powinny być zhermetyzowane poza klasami domenowymi

Serwisy zazwyczaj mają wiele zależności(jdbc, orm).

Serwisy w grails nie rozszerzają żadnej klasy i znajdują się w grails-app/services/. Serwis tworzy się poleceniem create-service name. Do nazwy pliku zostaje dodana końcówka Service.

Serwisy domyślnie są singletonami. Dzięki korzystaniu ze springa mogą zostać wstrzyknięte(injected) do kontrolera (poprzez autowiring). Następuje to poprzez utworzenie pola o takiej nazwie jak serwis

class StoreController {
  def storeService

}

Aby wstrzyknąć serwis do innego należy postąpić tak samo. Nie należy tworzyć samemu instancji serwisów, tylko pozostawić to grails (traci się wtedy transakcyjność).
W serwisach są transakcje i są one zgodne z ACID:

  • atomicity – cząsteczkowe
  • consistency – spójne
  • isolation – ilozacyjne, odseparowane
  • durability – trwałe

Serwisy posiadają statyczną zmienną transactional, która ustawiona na true zapewnia transakcyjność. Można włączyć transakcyjność dla wybranych metod poprzez ustawienie transactional na false i wywołanie klasy domenowej z metodą withTransaction.

// turn off automatic transaction management
static transactional = false
void someServiceMethod() {
  Album.withTransaction {
    // everything in this closure is happening within a transaction
    // which will be committed when the closure completes
  }
}

Jeśli w przekazanym domknięciu wystąpi błąd nastąpi rollback transakcji.

Zakres serwisów

  • prototype – nowy serwis jest tworzony zawsze jeśli jest wstrzyknięty do innej klasy
  • request – nowy serwis na każdy request
  • flash – nowy dla obecnego i następnego
  • flow, conversation tak jak w grails web flow
  • session – dla całej sesji dla danego użytkownika
  • singleton – domyślny tylko jeden

Dla zasięgów flow, flash i conversation należy zaimplementować Serializable i mogą one być używane tylko z web flow. Deklaracja zasięgu odbywa się poprzez:

static scope = 'request'

Testowanie serwisów odbywa się poprzez testy integracyjne (dependency injection). Jeśli ma być test jednostkowy to trzeba utworzyć instancję (brak dependency injection).

Wystawianie serwisów odbywa się poprzez zmienną expose która jest listą

static expose = ['jmx', 'xfire']

Poprzez pluginy są dostępne różne możliwości jak np jmx, rmi, axis2 itd.

Porządki w faces-config

Pierwszy raz jak zobaczyłem w jaki sposób jest zorganizowana nawigacja w jsf bardzo mi się to spodobało. Moja radość z tego powodu trwała do momentu gdy pojawiło się trochę więcej stron, które korzystały w większości z tych samych reguł nawigacyjnych. Rozwiązując test na javablackbelt pojawiło mi się pytanie związane z tym problemem. Nie znałem odpowiedzi ale zaznaczyłem taką odpowiedź, którą bym chciał aby była prawdziwa. I tak się stało.


<navigation-rule>
  <from-view-id>*</from-view-id>>
  <navigation-case>
    <from-outcome>something</from-outcome>>
    <to-view-id>/page.jsp</to-view-id>>
  </navigation-case>
</navigation-rule>

Poprsostu genialne rozwiązanie. Gwiazdka zastępuje nam dowolną stronę czyli mamy globalną nawigację dla wszystkich stron, a pozostało tylko obsłużenie specyficznych zachowań strony.

A może ktoś zna sposób jak jeszscze można zrefaktoryzowac faces-config?