1. Présentation et objectifs

Le but est de continuer le développement de notre architecture "à la microservice".

Nous allons utiliser Apache Pulsar, pour implémenter un début d’architecture EDA entre les micro-services game et mailing.

pokemon archi
Figure 1. Notre architecture !

2. Pré-requis

En pré-requis à ce TP, il faut avoir implémenté la partie 6. Envoi d’emails.

3. mailing-api

3.1. Démarrage d’une instance locale de pulsar

Apache Pulsar est disponible sous la forme d’une image Docker : apachepulsar/pulsar.

Démarrez une instance de pulsar sur votre poste avec la commande suivante :

docker container run -d \
    --name pulsar \
    -p 6650:6650 \
    -p 6080:8080 \
    apachepulsar/pulsar:4.0.1 \
    bin/pulsar standalone

3.2. Configuration Spring Boot

Dans votre projet mailing, importez la dépendance spring-boot-starter-pulsar :

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-pulsar</artifactId>
</dependency>

Configurez les properties suivantes dans un fichier application-local.properties, pour utiliser votre instance locale :

application-local.properties
spring.pulsar.client.service-url=pulsar://localhost:6650
spring.pulsar.admin.service-url=http://localhost:6080

3.3. Ajout d’un listener

Créez une classe ou un record qui représentera votre évènement trainer:new. Cette classe contient l’adresse mail du trainer, ainsi que son nom.

Annotez cette classe avec l’annotation @PulsarMessage(schemaType = SchemaType.JSON) pour indiquer à Spring Boot que cet objet devra être sérialisé en JSON.

Implémentez une classe NewTrainerEventPulsarAdapter. L’annotation @PulsarListener permet de marquer une méthode Java comme devant être appelée lorsqu’un message est disponible dans un topic Pulsar. Ajoutez une méthode à cette classe, qui porte l’annotation, et reçoit en paramètre votre classe d’évènement :

@PulsarListener(topics = "trainer:new", subscriptionType = SubscriptionType.Shared)
void receiveEvent(NewTrainerEvent event) {
   // TODO
}

Cette méthode écoute sur un topic <votre-nom>:trainer:new.

On utilise un nommage de topic trainer:new avec votre nom en préfixe, pour éviter les conflits entre les étudiants !

Le paramètre SubscriptionType.Shared permet de partager la connexion au topic entre plusieurs instances de votre micro-service !

Cette méthode pourra appeler la méthode de service existant par ailleurs.

Le démarrage de votre application devrait afficher les logs de connexion au topic pulsar :

o.a.p.c.impl.ConsumerStatsRecorderImpl   : Starting Pulsar consumer status recorder with config: {"topicNames":["persistent://public/default/new-trainer"],"topicsPattern":null,"subscriptionName":"org.springframework.Pulsar.PulsarListenerEndpointContainer#0","subscriptionType":"Shared","subscriptionProperties":null,"subscriptionMode":"Durable","receiverQueueSize":1000,"acknowledgementsGroupTimeMicros":100000,"maxAcknowledgmentGroupSize":1000,"negativeAckRedeliveryDelayMicros":60000000,"maxTotalReceiverQueueSizeAcrossPartitions":50000,"consumerName":null,"ackTimeoutMillis":0,"tickDurationMillis":1000,"priorityLevel":0,"maxPendingChunkedMessage":10,"autoAckOldestChunkedMessageOnQueueFull":false,"expireTimeOfIncompleteChunkedMessageMillis":60000,"cryptoFailureAction":"FAIL","properties":{},"readCompacted":false,"subscriptionInitialPosition":"Latest","patternAutoDiscoveryPeriod":60,"regexSubscriptionMode":"PersistentOnly","deadLetterPolicy":null,"retryEnable":false,"autoUpdatePartitions":true,"autoUpdatePartitionsIntervalSeconds":60,"replicateSubscriptionState":false,"resetIncludeHead":false,"batchIndexAckEnabled":false,"ackReceiptEnabled":false,"poolMessages":false,"startPaused":false,"autoScaledReceiverQueueSizeEnabled":false,"topicConfigurations":[],"maxPendingChuckedMessage":10}
o.a.p.c.impl.ConsumerStatsRecorderImpl   : Pulsar client config: {"serviceUrl":"pulsar://localhost:6650","authPluginClassName":null,"authParams":null,"authParamMap":null,"operationTimeoutMs":30000,"lookupTimeoutMs":30000,"statsIntervalSeconds":60,"numIoThreads":22,"numListenerThreads":22,"connectionsPerBroker":1,"connectionMaxIdleSeconds":25,"useTcpNoDelay":true,"useTls":false,"tlsKeyFilePath":null,"tlsCertificateFilePath":null,"tlsTrustCertsFilePath":null,"tlsAllowInsecureConnection":false,"tlsHostnameVerificationEnable":false,"concurrentLookupRequest":5000,"maxLookupRequest":50000,"maxLookupRedirects":20,"maxNumberOfRejectedRequestPerConnection":50,"keepAliveIntervalSeconds":30,"connectionTimeoutMs":10000,"requestTimeoutMs":60000,"readTimeoutMs":60000,"autoCertRefreshSeconds":300,"initialBackoffIntervalNanos":100000000,"maxBackoffIntervalNanos":60000000000,"enableBusyWait":false,"listenerName":null,"useKeyStoreTls":false,"sslProvider":null,"tlsKeyStoreType":"JKS","tlsKeyStorePath":null,"tlsKeyStorePassword":null,"tlsTrustStoreType":"JKS","tlsTrustStorePath":null,"tlsTrustStorePassword":null,"tlsCiphers":[],"tlsProtocols":[],"memoryLimitBytes":67108864,"proxyServiceUrl":null,"proxyProtocol":null,"enableTransaction":false,"dnsLookupBindAddress":null,"dnsLookupBindPort":0,"dnsServerAddresses":[],"socks5ProxyAddress":null,"socks5ProxyUsername":null,"socks5ProxyPassword":null,"description":null,"openTelemetry":null}
o.a.pulsar.client.impl.ConsumerImpl      : [persistent://public/default/trainer:new][org.springframework.Pulsar.PulsarListenerEndpointContainer#0] Subscribing to topic on cnx [id: 0x58864231, L:/127.0.0.1:38618 - R:localhost/127.0.0.1:6650], consumerId 0
o.a.pulsar.client.impl.ConsumerImpl      : [persistent://public/default/trainer:new][org.springframework.Pulsar.PulsarListenerEndpointContainer#0] Subscribed to topic on localhost/127.0.0.1:6650 -- consumer: 0

Implémentez toutes les méthodes disponibles sur le mailing service avec des listener Pulsar :

  • envoi d’un email de bienvenue (reçoit le nom d’un Trainer en paramètre, ainsi que son email)

  • envoi d’un email de fin de combat (reçoit les noms des 2 adversaires, leurs emails, et l’information de qui a gagné le combat)

4. trainer-api

4.1. Configuration

Ajoutez la dépendance spring-boot-starter-pulsar à votre projet trainer-ui.

Configurez les properties suivantes dans un fichier application-local.properties, pour utiliser votre instance locale :

application-local.properties
spring.pulsar.client.service-url=pulsar://localhost:6650
spring.pulsar.admin.service-url=http://localhost:6080

4.2. Ajout d’un producer

Spring propose un PulsarTemplate pour poster des messages dans un topic. Dans une classe de game-ui, nommée UserEventsPulsarAdapter, utilisez le PulsarTemplate (reçu en injection de dépendance) pour envoyer les évènements à destination du mailing-api dans le topic consacré <votre-nom>:trainer:new.

À titre d’exemple, voici un usage du PulsarTemplate pour envoyer un message sur un topic :

pulsarTemplate.send(
        "trainer:new",
        new NewTrainerEvent("Ash", "ash@gitlab-classrooms.org")
);
N’hésitez pas à copier/coller votre classe correspondant à l’évènement attendu depuis mailing-api.
Isolez toutes ces classes dans un package consacré. Vous pouvez aussi exposer une interface UserEventsPort dans votre couche "domaine" si elle existe, pour reprendre les principes de l’architecture hexagonale.

5. battle-api

Dans battle-api, implémentez l’envoi d’un message lors de la fin d’un combat, à destination du mailing-api dans le topic consacré <votre-nom>:battle:end, de la même manière que cela a été fait entre game-ui et mailing-api.

6. Configuration pour Clever Cloud

La configuration de l’utilisation de Pulsar sur Clever Cloud nécessite quelques ajustements :

  • l’instance de Pulsar est partagée entre tous les étudiants

  • la connexion à Pulsar nécessite une authentification

  • les topics peuvent être créés dans un tenant/namespace unique

Toutes les informations nécessaires à la connexion à Pulsar sont disponibles à 2 endroits :

clever pulsar
Figure 2. Les secrets Pulsar dans Clever Cloud
vault pulsar
Figure 3. Les secrets Pulsar dans Vault

Configurez dans vos projets mailing-api, game-ui et battle-api l’utilisation de l’instance Pulsar de Clever Cloud :

application-clever.properties
spring.pulsar.client.service-url= (1)
spring.pulsar.client.authentication.plugin-class-name=org.apache.pulsar.client.impl.auth.AuthenticationToken
spring.pulsar.client.authentication.param.token= (2)

spring.pulsar.admin.service-url= (3)
spring.pulsar.admin.authentication.plugin-class-name=org.apache.pulsar.client.impl.auth.AuthenticationToken
spring.pulsar.admin.authentication.param.token= (2)

spring.pulsar.defaults.topic.tenant= (4)
spring.pulsar.defaults.topic.namespace= (5)
  1. Utilisez la valeur de ADDON_PULSAR_BINARY_URL

  2. Utilisez la valeur de ADDON_PULSAR_TOKEN

  3. Utilisez la valeur de ADDON_PULSAR_HTTP_URL

  4. Utilisez la valeur de ADDON_PULSAR_TENANT

  5. Utilisez la valeur de ADDON_PULSAR_NAMESPACE