Applications distribuées en .NET
Première implémentation avec CQRS et l'Event Sourcing
Clément Bouillier - Devcrafting - @clem_bouillier
Un peu de contexte par rapport à CQRS et l'Event Sourcing
Rappel : CQRS conceptuel
Rappel : CQRS conceptuel
- L'utilisateur interagit avec l'application et le monde réel, pour prendre des décisions
- La partie Command traite les décisions demandées par l'utilisateur
- La partie Query permet d'afficher à l'utilisateur les données lui permettant de prendre d'autres décisions
- Les événements métier sont les pivots entre Command et Query
Pourquoi CQRS et Event Sourcing ?
- Beaucoup plus de READ que de WRITE donc autant séparer les modèles
- Stocker les événements modifiant le système plutôt que son état actuel : le modèle est plus riche en information métier
- "Complexité accidentelle" limitée : pas de modèle unique (+ORM, BD unique...), moins de besoins frameworks
- Code plus simple, plus découplé, plus testable et plus orienté micro-services
Comparaison schématique d'une architecture N-tiers classique et d'une architecture orientée CQRS/Event Sourcing
Pour aller plus loin: très bonne présentation de différents styles d'architecture (dont N-tiers et CQRS)
Quelques pistes avant de commencer
Nos amis pour cette session
- La MSDN sur C# : https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx
- Mais aussi votre moteur de recherche préféré
- Object Browser dans Visual Studio
Quelques contraintes pour s'initier au TDD/TDL et au Pair Programming
- Pair Programming : les 2 personnes sont actives, le clavier DOIT être échangé
- Quand on n'a pas le clavier, il faut guider l'autre, discuter...pas attendre qu'il ait fini
- TDD/TDL : cycle red->green->refactor
- On utilise tous la même norme de code (via StyleCop + R# ?)
En pratique pour ce TD
- Repository GitHub : git clone https://github.com/devcrafting/Distributed.NETTraining.git (ou fork + clone)
- Un tag par étape, le tag de l'étape en cours sera affiché dans ce support
- Pour chaque tag, de nouvelles classes et de nouveaux tests à décommenter au fur et à mesure
- Pour les tests, on utilise NUnit et NFluent (récupérés via NuGet)
Ce que contient la solution fournie
- Une librairie pour le contexte Messaging de l'application Twitter-like modélisée en Event Storming
- Une libraire de test pour Messaging
- Dans chaque librairie, une séparation Domain (métier)/Infrastructure (technique)
- Une API HTTP (REST?!) faites avec Nancy FX, à requêter avec un client rest (plugins Advanced REST sur Chrome ou RESTClient sur Firefox)
Itération 1 : implémenter un read model permettant d'afficher une liste de tweets
Pattern Repository
- Objet représentant une liste d'objets/structures
- Avec des méthodes de manipulations du type GetByXXX, GetAll, Save
- Abstraction courante d'une entité/d'un groupe d'entité SQL ou d'une collection MongoDB par exemple
- Proche du pattern DAO, mais différent (moins proche de la base, plus proche du domaine métier)
Injection de dépendance/inversion de contrôle (DI/Ioc)
- Un concept sous-jacent : Dependency Inversion (le D de l'acronyme SOLID)
- Externaliser l'instanciation des dépendances d'un objet et les abstraire avec une interface (l'implémentation doit être un détail)
- Enregistrer les implémentations avec leurs abstractions
- Avec Nancy : la plupart des engistrements sont faits automatiquement sur convention de nommage (Super Happy Dappy Path)
Nancy FX
- Une route (i.e URL) = une fonction (ou lambda)
- Le type du retour est "négocié" en fonction du header Accept de la requête
- Il suffit donc de retourner les objets récupérer dans le repository
- Pour obtenir du JSON, il faudra utiliser "application/json" dans le client REST
Immutabilité
- Propriété d'un objet de ne pas pouvoir être modifié
- Réduit les risques d'effet de bord (très courant en programmation fonctionnelle)
- "Simulation" en objet avec des constructeurs avec paramètres, des attributs "readonly" et PAS DE SETTER
Affichage de la timeline -> tag TD_ReadModel_TimelineMessage
Nous avons besoin des éléments suivants :
- Une structure TimelineMessage (read model) - TimelineMessageTests
- Une interface du Repository de ces structures - TimelineMessageRepositoryTests
- Une implémentation du Repository avec stockage en mémoire (champs privé IEnumerable<TimelineMessage>)
- Utiliser ce repository dans le module Nancy pour la route "/messaging/timelineMessages" - MessagingModule
Retrospective
- Qu'est-ce qui c'est bien passé ?
- Quels ont été les points difficiles ?
- Qu'est-ce que vous avez appris ?
- Quels sont les points qui vous perturbent encore ?
Itération 2 : projections permettant d'obtenir les read models à partir d'événements
Evénements ayant un impact sur le read model TimelineMessage
- MessagePublished
- FollowerMessagePublished
- FollowerMessageRepublished
- FollowerMessageLiked
- MessageDeleted
- ...
Projections -> tag TD_Projections_TimelineMessage
- Une projection est classe traitant un événément à la fois pour transformer un read model à la fois (séparation des responsabilités)
- Signature typique de la méthode de traitement : void Handle([TypeEvent] @event)
- On peut conserver l'immutabilité avec une surcharge des constructeurs
Retrospective
- Qu'est-ce qui c'est bien passé ?
- Quels ont été les points difficiles ?
- Qu'est-ce que vous avez appris ?
- Quels sont les points qui vous perturbent encore ?
Itération 3 : agrégats levant des événements en conséquence de commandes
Agrégat racine (AggregateRoot)
- Objet garantissant la consistance des changements réalisés en son sein => encapsulation nécessaire (i.e pas de setters, ni de getters)
- Conserve seulement dans un état interne ce dont il a besoin pour prendre des décisions ultérieures
- L'état est construit à partir des événements que l'agrégat lève lui-même (ET UNIQUEMENT par ce biais)
- Cela permet de reconstruire un agrégat à partir de l'historique des événements qui aura été stocké (Event Sourcing)
Pattern Publish/Subscribe
- Objet gérant la diffusion des événements
- Différentes utilisations pour les Events : passé en paramètre des méthodes de l'agrégat, conserver une liste des Events non "committés" + Repository.Save, retourner la liste des Events générés par les méthodes de l'agrégat
Agrégat Message et les commandes associées -> tag TD_Agregat_Message
- Utiliser la technique du passage en paramère de l'EventPublisher (pattern Publish/Subscribe), avec une interface
- L'agrégat Message va assurer qu'un message n'est pas republié par son auteur
- Mais également qu'un message ne peut pas être republié 2 fois par le même compte
- Ou encore que la suppression ne peut se faire que par l'auteur (/!\ une suppression "administrative" serait gérée par une autre commande)
Retrospective
- Qu'est-ce qui c'est bien passé ?
- Quels ont été les points difficiles ?
- Qu'est-ce que vous avez appris ?
- Quels sont les points qui vous perturbent encore ?