Introduction à l'API Events de Grails 3.3

Le framework web Grails dispose d'une API appelée Events permettant à des composants de l'application de communiquer entre eux, grâce à des événements et sans dépendance à la compilation.
Il s'agit là d'événements asynchrones, et toute la plomberie est prise en charge par Grails qui, par ailleurs, laisse le choix de l'implémentation sous-jacente comme GPars ou RxJava.

On peut aussi observer que dans le monde Java EE, on retrouve ce type de fonctionnalité dans CDI 2.0 (Contexts and Dependency Injection, JSR 365).

Dans l'approche la plus simple à mettre en oeuvre, deux d'annotations (de type AST) suffisent à autoriser une communication événementielle entre deux parties :

  • @grails.events.annotation.Publisher, qui permet au retour d'une méthode d'être émis comme événement.
  • @grails.events.annotation.Subscriber, qui transforme une méthode en un abonné sur un événement.

Pour bénéficfier des Events, il est nécessaire d'ajouter une dépendance vers le plugin events dans le fichier build.gradle de l'application, ainsi qu'une dépendance vers l'implémentation de son choix comme mentionné plus haut.

Voyons ensemble un exemple de service Grails émettant un événement lorsque l'on invoque l'une de ces méthodes, de sorte que cela entraîne un traitement asynchrone, dans une ou plusieurs autre(s) partie(s) de l'application web.

Publier un événement avec l'annotation @Publisher

Dans l'exemple de service Grails ci-dessous, la méthode pay() est annotée avec @Publisher, ce qui implique que la valeur du type retournée par cette méthode sera l'événement à émettre.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import grails.events.annotation.Publisher
import grails.gorm.transactions.Transactional
import groovy.transform.CompileStatic
import groovy.transform.Immutable

enum PaymentOption {
    CREDIT, DEBIT
}

@Immutable
class PaymentEvent {
    String paymentType
    BigDecimal value
    Date datetime
}

@Transactional
@CompileStatic
class PaymentService {

    @Publisher
    PaymentEvent pay(PaymentOption paymentOption, BigDecimal value) {
    	// ...
        new PaymentEvent(paymentType: paymentOption, value: value, datetime: new Date())
    }
}

Si cela n'est pas précisé dans l'annotation, Grails utilisera comme id d'événement le nom de la méthode. Un tel id permet à un abonné d'indiquer à quel type d'événement il souhaite souscrire.
Par ailleurs, le type du retour de la méthode qui publie l'événement aurait très bien pu être un type primitif.

Définir un abonné avec l'annotation @Subscriber

Voyons maintanent comment écrire un abonné aux événements d'id pay qui seront émis, grâce à l'annotation @grails.events.annotation.Subscriber.
La classe PaymentHandler ci-dessous possède une méthode onPay() avec un paramètre recevant l'événement, annotée avec @Subscriber, et destinée à traiter les événements d'id pay de manière asynchrone.
L'annotation n'indiquant pas l'id d'événement auquel on s'intéresse, par convention, il est déduit du nom de la méthode !
En l'occurrence, notre méthode aurait pu être nommée pay(), mais Grails autorise également le nom onPay(), qui est plus parlant.

1
2
3
4
5
6
7
8
9
10
11
12
13
import grails.events.annotation.Subscriber
import groovy.util.logging.Slf4j
import groovy.transform.CompileStatic

@Slf4j
@CompileStatic
class PaymentHandler { // must be a Spring bean

    @Subscriber
    void onPay(PaymentEvent payment) {
        log.info payment.dump()
    }
}

Attention, il y a cependant une condition particulière à satisfaire pour qu'un abonné reçoive les événements : il doit être un bean Spring, ce qui est le cas des artéfacts de Grails comme les services ou les contrôleurs.
Dans une application Grails, on peut bien sûr définir des types dans des fichiers sources sous src/main/groovy, et déclarer des beans dans le fichier conf/spring/resources au moyen du DSL Spring de Grails.

Dans l'exemple de définition qui suit, nous déclarons un bean de type PaymentHandler, ce qui permettra de recevoir les événements d'id pay :

1
2
3
4
5
import odelia.gina.experiments.*

beans = {
    paymentHandler(PaymentHandler)
}

Publier différents types d'événement depuis une méthode

Si l'on a besoin de plus de souplesse, comme émettre des événements de différents types depuis la même méthode, on peut dans ce cas, définir une classe implémentant le trait Groovy grails.events.EventPublisher qui permet de publier un événement.

Au lieu d'émettre un événement du même id lorsque la méthode pay() est invoquée, cette nouvelle version de méthode publie maintenant des événements de diffétents types (debitPayment et creditPayment) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import grails.events.EventPublisher
import grails.gorm.transactions.Transactional
import groovy.transform.CompileStatic

@Transactional
@CompileStatic
class PaymentEventBusService implements EventPublisher {

    void pay(PaymentOption paymentOption, BigDecimal value) {
        def payment = new PaymentEvent(paymentType: paymentOption,
            value: value, datetime: new Date())
        if (paymentOption == PaymentOption.DEBIT)
            this.notify('debitPayment', payment)
        else
            this.notify('creditPayment', payment)
    }
}

Cette fois, si l'on devait définir des méthodes qui s'abonnent aux événements d'id debitPayment et creditPayment, nous pourrions déclarer les méthodes debitPayment() et creditPayment(), annotées avec @Subscriber dans la classe PaymentHandler :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import grails.events.annotation.Subscriber
import groovy.util.logging.Slf4j
import groovy.transform.CompileStatic

@Slf4j
@CompileStatic
class PaymentHandler { // must be a Spring bean

    @Subscriber
    void debitPayment(PaymentEvent payment) {
        log.info payment.dump()
    }

    @Subscriber
    void creditPayment(PaymentEvent payment) {
        log.info payment.dump()
    }
}

Plus loin

Pour aller plus loin, comme par exemple, s'abonner à des événements de manière dynamique ou s'abonner aux événements de GORM, vous pouvez vous reporter au guide utilisateur de Grails décrivant les Events.
Ceux-ci font également l'objet du guide Grails Events, dans la liste grandissante des guides disponibles sur le site web de Grails !