Git Product home page Git Product logo

snowcamp-springmodulith's Introduction

A la découverte de Spring Modulith

Prérequis

Afin de jouer cet atelier, assurez-vous d'avoir sur votre poste :

  • Java 17
  • Docker Compose

Un peu de contexte

Amis du Snowcamp bonjour,
Nous te sollicitons car nous avons besoin de ton aide.
Nous avons externalisé le développement de notre site de vente en ligne de Chartreuse auprès de ceux que nous pensions être les plus à même de le faire, à savoir Le cabinet de conseil des frères Chartreux.

Après de nombreux mois sans la moindre évolution majeure, ni prise en compte des bugs, nous avons découvert que frère Bernard, notre développeur, avait plus tendance à contrôler la qualité de nos produits qu'à s'appliquer au développement de notre site.

Nous faisons donc appel à vous pour reprendre en l'état notre backend et développer les fonctionnalités manquantes pour, espérons-le, inonder l'Europe, voire le monde de notre liqueur verte.

Exercice 1

Le premier problème que nous avons, c'est que l'application ne démarre même pas, il semble qu'il y ait une dépendance cyclique entre des composants gérant les commandes (Order) et le paiement (Payment).

Besoin d'aide ?

Si vous essayez de lancer l'application à l'aide de la commande ./gradlew bootRun, vous constaterez que l'application ne démarre pas :

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

orderController defined in file [./spring-modulith-workshop/build/classes/java/main/org/snowcamp/university/springmodulith/order/api/web/OrderController.class]
┌─────┐
|  orderManager defined in file [./spring-modulith-workshop/build/classes/java/main/org/snowcamp/university/springmodulith/order/domain/OrderManager.class]
↑     ↓
|  paymentHandler defined in file [./spring-modulith-workshop/build/classes/java/main/org/snowcamp/university/springmodulith/payment/domain/PaymentHandler.class]
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

OrderManager et PaymentHandlerdépendent l'un de l'autre, essayez de répartir la logique de PaymentHandler dans deux classes séparées pour résoudre ce problème.

Exercice 2

Le deuxième problème rencontré, c'est que nos utilisateurs n'arrivent pas passer de commandes ; il semble qu'il y ait un problème lors du paiement.
Essayez d'aller jusqu'au paiement complet... Ça coince. Nous savons que le problème est apparu quand notre père supérieur a insisté pour voir développer une fonctionnalité permettant d'être notifié des commandes complétées. Le site ne permettait même pas de mener une commande complète mais il fallait développer la notification du paiement de commande... Nous ne reviendrons pas sur cette gestion des priorités, mais s'il vous plaît, pourriez-vous faire en sorte que les notifications n'empêchent pas le traitement principal ?

Besoin d'aide ?

En utilisant le Swagger de l'application, commencez par créer un order (POST /api/vi/order).

Passez ensuite cette order en paiement (PUT /api/v1/orders/static-for-demo/state/in_payment).

Vous pouvez finalement invoquer la complétion du paiement (PUT /api/v1/payments/static-for-demo/complete).

L'API vous renvoie alors une erreur 500 et vous constatez en inspectant les logs que le problème vient du GreeterService

java.lang.RuntimeException: No greeting !!!
at org.snowcamp.university.springmodulith.greeting.configuration.GreetingConfiguration.lambda$noGreeterClient$1(GreetingConfiguration.java:29)
at org.snowcamp.university.springmodulith.greeting.domain.GreeterService.greet(GreeterService.java:25)
...
at org.snowcamp.university.springmodulith.greeting.domain.GreeterService$$SpringCGLIB$$0.greet(<generated>)
at org.snowcamp.university.springmodulith.order.domain.OrderManager.paymentComplete(OrderManager.java:101)
...
at org.snowcamp.university.springmodulith.order.domain.OrderManager$$SpringCGLIB$$0.paymentComplete(<generated>)
at org.snowcamp.university.springmodulith.payment.domain.PaymentHandler.paymentComplete(PaymentHandler.java:22)
...
at org.snowcamp.university.springmodulith.payment.domain.PaymentHandler$$SpringCGLIB$$0.paymentComplete(<generated>)
at org.snowcamp.university.springmodulith.payment.api.web.PaymentController.paymentComplete(PaymentController.java:22)
...

L'objectif n'est pour l'instant pas de corriger le problème levé par le GreeterService mais juste de faire en sorte qu'en cas d'échec, cela ne vienne pas faire échouer la complétion du paiement. Une solution pourrait être de rendre le traitement du GreeterService asynchrone.

Exercice 3 - init modulith et visualisation de la structure actuelle

Dans notre appel d'offre, nous avions demandé à Frère Bernard d'utiliser Spring Modulith pour garantir la modularisation de notre application. Nous avons des projections de croissance à l'international ambitieuses et il n'est pas exclu que nous découpions notre backend en micro services par la suite.

Il semble que la dépendance Spring Modulith soit commentée, pourriez-vous l'activer (sans observabilité ni message vers système externe pour l'instant) ?

Une fois Spring Modulith ajouté, vous allez pouvoir étudier la structure actuelle du projet.

Faites en sorte de visualiser les modules actuellement découverts par Modulith, voire d'en générer une documentation.

Besoin d'aide ?

Si vous n'êtes pas familier de Gradle, les dépendances sont dans le fichier build.gradle.kts.

Les modules vus par Modulith sont accessibles via :

ApplicationModules.of(ChartreuseShopApplication.class);

Vous pouvez simplement rendre dans la sorties standard le résultat de la commande ci-dessus.

Une autre option, est d'utiliser l'outil de génération de documentation mis à disposition par Modulith.

structure initiale

Exercice 4 - alors, c'est modulaire ?

Vous devriez également pouvoir évaluer la modularité de l'application en ajoutant un Test dans ChartreuseShopApplicationTest qui fait

ApplicationModules.of(ChartreuseShopApplication.class).verify();

Pourquoi échoue-t-il? Pourriez-vous résoudre les problèmes remontés?

Exercice 5 - module scanning

Nous avons constaté que Frère Bernard avait créé un package common, cela risque de poser un problème si ce dernier est considéré comme un module.

Il serait certainement plus sage de l'omettre du scanning...

Pour celà, créez un Bean qui implémente l'interface ApplicationModuleDetectionStrategy.

Besoin d'aide ?

Pour plus d'information sur comment configurer la détection de module, vous pouvez jeter un œil à ce lien.

Exercice 6 - internal events

En lisant la documentation de Spring Modulith, nous avons lu qu'il était préconisé d'opter pour une approche évènementielle pour ce qui est de la communication entre les modules.

Vous devriez pouvoir cloisonner les packages en procédant ainsi :

  • créez des objets évènements applicatifs sur les order
  • servez-vous de ApplicationEventPublisher pour les publier (Bean disponible dans le contexte d'applicationn spring)
  • annotez des méthodes de Component spring avec en paramétre une instance de ces évènements. Plusieurs annotations existent
    • EventListener (avec éventuellement Transactionnal) pour créer un listener synchrone (qui s'insèrera éventuellement dans la transaction parente)
    • TransactionalEventListener pour créer un listener qui recevra l'événement en fin de transaction uniquement si la transaction est réussie (possibilité de régler la phase de commit)
    • ApplicationModuleListener qui fait la même chose que le précédant, avec en plus une exécution asynchrone et une transaction dédiée

À la fin de cet exercice le test de l'étape 3 doit passer au vert.

Besoin d'aide ? L'objectif ici est de remplacer les dépendances à des beans d'autres modules en remplaçant les appels directs à des méthodes de ces beans par des envois d'évènements.

Ce genre de dépendances est présente dans la classe OrderManager, les méthodes processToPayment et paymentComplete invoquent chacune un bean différents.

Exercice 7 - tests

Lorsque nous avons dit à Frère Bernard de tester le produit, il était clair pour nous qu'il s'agissait de s'assurer du bon fonctionnement de l'application et par la suite de prévenir les régressions. Nous n'imaginions pas que ce dernier testerait tous les jours que Dieu fait la qualité gustative de nos Chartreuses Jaune, Verte et AOP.

Nous avons pensé de commencer par garantir le comportement du module qui s'assure de la commande, à savoir le module order. Pour ce faire, créez une classe de tests annotée ApplicationModuleTest dans laquelle vous écrirez deux tests :

  • écrivez un premier test qui prend en paramètre events de type AssertablePublishedEvents qui doit
    • initialiser un Order en base avec un statut en IN_PAYMENT
    • appeler la méthode paymentComplete sur l'attribut de la classe de test OrderManager annoté Autowired
    • faire un assertThat(events)... pour vérifier qu'un évènement est bien publié avec le bon id
  • écrivez un autre test qui prend un objet de paramètre de type Scenario. Le test est similaire sauf qu'au lieu d'appeler une quelconque méthode d'OrderHandler on doit
    • publier un event qui signale que la commande est payée
    • tester qu'un event de commande complète a été émis avec le bon id

Exercice 8 - reprise d'évènement

Pouvez vous jouer avec les Bean CompletedEventPublications et IncompleteEventPublications et purger les évènements et les rejouer ?

Sinon, il paraît qu'il y a un paramètre pour les rejouer au démarrage (sous spring.mod...).

Besoin d'aide ?

Vous devriez pouvoir trouver votre bonheur dans cette documentation.

Exercice 9 - external events

Les moines en charge de la gestion des stocks aimeraient recevoir un message qui leur signifie le nombre de bouteilles commandées pour chaque type.

Ils possèdent déjà un système de messaging et voudraient que notre système pousse un message contenant :

  • le type de chartreuse
  • le nombre de bouteilles

Pour ce faire, rajouter les dépendances et publier

  • un objet annoté de l'annotation @Externalized.
  • On peut même éventuellement paramétrer la clef de manière dynamique ; par exemple, dans la déclaration :
@Externalized("example::#{#this.name().toLowerCase()}") 
record MonExample(String name){}

L'objet new MonExample("YouPi") sera publié sur le topic example avec la clef youpi.

Les moines en charge des stocks vous ont monté un environnement de développement à l'image de leur système en production.

Pour vous y connecter :

docker compose exec -it kafka bash

Vous pouvez suivre les messages publiés sur un topic donné, ici par exemple sur le topic example

 /bin/kafka-console-consumer --bootstrap-server localhost:9092 --topic example --from-beginning
Besoin d'aide ?

L'objectif ici est de créer un nouveau module de gestion des stocks au même titre qu'il en existe déjà pour les commandes, le paiement, ...

Dans ce module stock, créez un nouveau listener qui écoute les évènements que vous publiez déjà depuis la méthode OrderManager::paymentComplete.

Il vous faudra certainement enrichir l'évènement existant pour savoir quels types de bouteilles de Chartreuse sont commandées.

Publiez alors depuis un Bean du module stock un évènement par type de bouteille présent dans la commande.

Chaque évènement indiquera qu'il faut décrémenter le stock de X pour un type de Chartreuse donné.

C'est cet évènement pour lequel la classe devra être annotée @Externalized.

Exercice 10 - observabilité

Nos moines en charge de l'exploitation sont tout excités après avoir assisté à une présentation OpenTelemetry et ont monté une petite infrastructure permettant la collecte de traces applicatives. Pour vous aider, à l'image des moines en charge des stocks, ils vous ont construit une petite infrastructure pour collecter les traces applicatives.

Pourriez-vous remonter les traces applicatives et les observer dans Grafana?

L'interface de Grafana est accessible à http://localhost:3000/.

illustration

Exercice 11 - visualiser la structure finale

Vous pouvez désormais générer la documentation finale afin de constater l'évolution de la structure du projet.

Besoin d'aide ?

Spring Modulith propose un Documenter à cet effet.

Vous pouvez simplement générer la documentation dans son format par défaut ainsi :

@Test
void generateDocumentation() {
    ApplicationModules modules = ApplicationModules.of(ChartreuseShopApplication.class);
    new Documenter(modules).writeDocumentation();
}

N'hésitez pas à jeter un œil à DiagramOptions et CanvasOptions qui permettent do configurer le format de votre documentation.

Par défaut, la documentation est générée sous build/spring-modulith-docs.

snowcamp-springmodulith's People

Contributors

csouchet avatar antechrestos avatar vincentbostoen avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.