#7 - DDD Tactique: Le monolithe "micro-service ready"
Encore un concept que j'ai piqué aux anciens d'Arpinum, JB Dusseault et Arnaud Lemaire. J'espère qu'ils ne m'en voudront pas trop :) D'ailleurs je ne sais pas si ils sont eux même les auteurs de ce concept, Ils nous diront en commentaire, mais quoi qu'il en soit c'est grâce à eux que nous nous sommes familiarisés avec ce concept. Merci les gars !
C'est quoi un monolithe micro-service ready, chef ?
C'est le canada-dry du micro-service. Ça en a le goût, ça en a l'odeur... mais ça n'en est pas.
En gros on garde un découpage en module logique isolés (nos bounded contexts), mais au lieu de dispatcher ces modules sur des processus ou des machines séparés, on garde tout ça au sein d'une seule application, sur un seul processus. Voir un seul thread, si on est en python... oui bon on fait ce qu'on peut.
Donc on fait "comme si" on avait des micro-services, d'un point de vue de l'architecture logique, mais on garde une architecture physique unique. Ça permet de bénéficier des avantages de la maintenabilité liée à la mise en place d'unité logique très découpées, sans avoir à se tarter de l'asynchronisme, de la com' réseau, de la tolérance aux pannes, de l'orchestration, du kubernetes et tout un tas de joyeuseté qui n'apportent pas forcément grand chose.
Ce n'est jamais vraiment évident de savoir jusqu'ou aller, en particulier sur ces sujets de gestion thread / processus. Doit-on utiliser des brokers de message pour communiquer entre nos bounded contexts ? doit on-séparer la configuration de la persistence pour chacun de nos contextes ? donc faire en sorte que chaque brique logicielle ait sa propre configuration de base de données... Ce faisant on se rapprochait beaucoup d'une architecture micro-services, et la complexité obligatoire qui en résultait nous a paru largement overkill dans un premier temps.
Temps qui peut d'ailleurs durer, avant que votre application ait vraiment besoin de passer à l'échelle. Genre, jamais pour un bon 95% des appli B2B, à mon avis.
Donc schématiquement notre appli ressemble à ça :
Le contenu des blocs va être calqué sur notre analyse stratégique, pour que chacun de nos blocs corresponde à un Bounded Contexte tel que nous les avons définis dans l'article précédent.
Les grand principes
Ok, maintenant qu'on a dit ça, on est pas plus avancé. Donc lorsque l'on a pensé l'architecture de notre système, nous avons utilisé un ensemble de règles simples pour nous aider à décider lorsque nous étions dans le flou :
- Multiple briques
L'application sera composé de plusieurs bounded contexts, isolés les uns des autres. Chaque Bounded Context dans un répertoire dédié, mais surtout il est évidemment interdit d'importer ou de faire référence à des objets externes depuis un bounded context donné.
En gros ce qu'on veut éviter ce sont des importations crado entre bounded context, qui viendraient coupler fortement nos contextes. C'est tout con, mais dans presque tous les projets django que j'ai croisé, ce principe n'était pas du tout respecté, entrainant des dépendances fortes entre les apps du projet. - Réutilisabilité
Par conséquent, chaque bounded context doit dans l'absolu pouvoir être complètement "décroché" de l'application pour être réutilisé directement dans une autre application, si tant est que la nouvelle application fonctionne sur les mêmes principes d'infra que l'appli initiale. Cela nous permet de trancher sur les limites d'influence de tel ou tel contexte. Cela signifie aussi que chaque bounded context doit être en mesure de gèrer ses points d'entrée. Donc la fourniture des endpoints d'api, de manière autonome et namespacé. - CQRS / CQRS-ES / CRUD
Chaque Bounded Context sera potentiellement architecturé selon des principes différents. Par défaut nous utiliserons une architecture CQRS, que nous détaillerons dans un futur article.
Nous opterons pour le CQRS + Event Sourcing, dans le cas ou la dimension temporelle du stockage apporte un plus. Les évolutions de joueurs et d'équipes seront un excellent cas d'usage pour l'Event Sourcing. Enfin le CRUD sera réservé aux contextes qui gère un stockage naïf, genre le moteur de blog, embarqué dans le contexte communication. - Onion / Clean Architecture
Chaque Bounded contexte est organisé en interne en suivant les principes de l'architecture en couche pour la gestion du métier. L'architecture en couche, ou clean architecture chez Uncle Bob. Pour mémoire une architecture en couche se décompose en 3,4 couches ( 3 chez nous ) et suit le schéma théorique ci-dessous :
Nous avons donc organisé notre code en trois couches :
- Infrastructure :
La couche de plus haut niveau, en charge à la fois de la gestion des requêtes web entrante, de l'intéraction avec une ligne de commande, de la persistance des données et éventuellement de leur serialisation pour envoi sur le réseau. - Application :
C'est dans cette couche que nos cas d'utilisation doivent apparaître. Nous aurons par exemple un fichier start_user_signup.py dans cette couche. Nous verrons ensuite ce que nous mettons dans ce fichier. - Domain :
C'est enfin ici que vient se loger tout le métier de notre application. Le métier est modélisé par une structure de classe et de méthodes python, qui n'ont aucune dépendance avec quoi que ce soit d'autre. Nous utilisons une règle simple pour notre code métier : Le product owner doit pouvoir lire le code et comprendre ce qui s'y passe. C'est cette règle toute conne qui va nous guider dans l'écriture de notre métier. C'est cette règle qui va permettre de nommer correctement nos entités, nos aggrégats et nos value objects. Concepts sur lesquels nous reviendrons.
De plus la règle fondamentale d'une architecture en couche est simple, une couche interne ne peux jamais dépendre d'une couche de niveau supérieure. Donc par exemple, la couche application ne peut jamais importer quoi que ce soit de la couche infrastructure. La couche Domain elle ne doit rien importer à part les libs de base du langage.
Ceci étant, en vrai, pour le moment on a pas besoin de tout ça. C'est un réflexe de partir en concevant une apli avec tout se bordel, mais on en a pas besoin, pour le moment.
Ce dont on a besoin c'est de code qui tourne, et qui fait des choses.
On appelle ça du code métier.
Et le code métier il va dans la couche domaine.
Donc.... ben on va commencer à écrire du code métier. On verra plus tard ou est-ce qu'il doit tourner, comment sont stocker ses états et tout ça.
pour le moment: on code.
Comments ()