Construction de microservices pilotés par le domaine

Chandra Ramalingam

Follow

1er juillet, 2020 – 18 min lu

Crédits images :

Le terme « micro » dans Microservices, bien qu’indicatif de la taille d’un service, n’est pas le seul critère qui fait d’une application un Microservice. Lorsque les équipes passent à une architecture basée sur les microservices, elles visent à accroître leur agilité – déployer des fonctionnalités de manière autonome et fréquente. Il est difficile de trouver une définition unique et concise de ce style architectural. J’ai aimé cette courte définition d’Adrian Cockcroft – « architecture orientée services composée d’éléments faiblement couplés qui ont des contextes délimités. »

Bien que cela définisse une heuristique de conception de haut niveau, l’architecture microservices a quelques caractéristiques uniques qui la différencient de l’architecture orientée services d’antan. Quelques-unes de ces caractéristiques, ci-dessous. Celles-ci et quelques autres sont bien documentées – l’article de Martin Fowler et Building Microservices de Sam Newman, pour n’en citer que quelques-unes.

  1. Les services ont des limites bien définies centrées sur le contexte métier, et non sur des abstractions techniques arbitraires
  2. Cacher les détails de mise en œuvre et exposer les fonctionnalités par le biais d’interfaces révélant les intentions
  3. Les services ne partagent pas leurs structures internes au-delà de leurs limites. Par exemple, pas de partage de bases de données.
  4. Les services sont résilients aux défaillances.
  5. Les équipes possèdent leurs fonctions de manière indépendante et ont la capacité de libérer des changements de manière autonome
  6. Les équipes embrassent une culture de l’automatisation. Par exemple, les tests automatisés, l’intégration continue et la livraison continue

En bref, nous pouvons résumer ce style d’architecture comme suit :

Une architecture orientée services faiblement couplée, où chaque service est enfermé dans un contexte délimité bien défini, permettant une livraison rapide, fréquente et fiable des applications.

Conception pilotée par le domaine et contextes bornés

La puissance des microservices vient de la définition claire de leur responsabilité et de la démarcation de la frontière entre eux. Le but ici est de construire une cohésion élevée à l’intérieur de la frontière et un couplage faible à l’extérieur de celle-ci. En d’autres termes, les éléments qui ont tendance à évoluer ensemble doivent être réunis. Comme dans de nombreux problèmes de la vie réelle, cela est plus facile à dire qu’à faire – les entreprises évoluent et les hypothèses changent. C’est pourquoi la capacité de remaniement est un autre élément critique à prendre en compte lors de la conception de systèmes.

La conception pilotée par le domaine (DDD) est un outil clé et, à notre avis, nécessaire lors de la conception de microservices, qu’il s’agisse de casser un monolithe ou de mettre en œuvre un projet greenfield. La conception pilotée par le domaine, rendue célèbre par Eric Evans dans son livre, est un ensemble d’idées, de principes et de modèles qui aident à concevoir des systèmes logiciels basés sur le modèle sous-jacent du domaine d’activité. Les développeurs et les experts du domaine travaillent ensemble pour créer des modèles d’entreprise dans un langage commun omniprésent. Ils lient ensuite ces modèles aux systèmes où ils ont un sens, établissent des protocoles de collaboration entre ces systèmes et les équipes qui travaillent sur ces services. Plus important encore, ils conçoivent les contours conceptuels ou les frontières entre les systèmes.

La conception de microservices s’inspire de ces concepts car tous ces principes aident à construire des systèmes modulaires qui peuvent changer et évoluer indépendamment les uns des autres.

Avant de poursuivre, passons rapidement en revue certaines terminologies de base de la DDD. Un aperçu complet de la conception pilotée par le domaine est hors de portée pour ce blog. Nous recommandons vivement le livre d’Eric Evans à tous ceux qui essaient de construire des microservices

Domaine : Représente ce que fait une organisation. Dans l’exemple ci-dessous, il s’agirait du commerce de détail ou du commerce électronique.

Sous-domaine : Une organisation ou une unité commerciale au sein d’une organisation. Un domaine est composé de plusieurs sous-domaines.

Langue omniprésente : C’est le langage utilisé pour exprimer les modèles. Dans l’exemple ci-dessous, Item est un modèle qui appartient au langage ubiquitaire de chacun de ces sous-domaines. Les développeurs, les gestionnaires de produits, les experts du domaine et les parties prenantes de l’entreprise s’accordent sur le même langage et l’utilisent dans leurs artefacts – code, documentation du produit, etc.

Fig 1. Sous-domaines et contextes délimités dans le domaine du commerce électronique

Contextes délimités : La conception pilotée par le domaine définit les contextes bornés comme « le cadre dans lequel apparaît un mot ou une déclaration qui détermine sa signification. » En bref, cela signifie la limite dans laquelle un modèle a du sens. Dans l’exemple ci-dessus, « Item » prend un sens différent dans chacun de ces contextes. Dans le contexte du catalogue, un article désigne un produit vendable, tandis que, dans le contexte du panier, il désigne l’article que le client a ajouté à son panier. Dans le contexte Fulfillment, il s’agit d’un article de l’entrepôt qui sera expédié au client. Chacun de ces modèles est différent, et chacun a une signification différente et contient éventuellement des attributs différents. En séparant et en isolant ces modèles dans leurs limites respectives, nous pouvons exprimer les modèles librement et sans ambiguïté.

Note : Il est essentiel de comprendre la distinction entre les sous-domaines et les contextes bornés. Un sous-domaine appartient à l’espace du problème, c’est-à-dire à la façon dont votre entreprise voit le problème, tandis que les contextes bornés appartiennent à l’espace de la solution, c’est-à-dire à la façon dont nous allons mettre en œuvre la solution au problème. Théoriquement, chaque sous-domaine peut avoir plusieurs contextes bornés, bien que nous nous efforcions d’avoir un seul contexte borné par sous-domaine.

Comment les microservices sont-ils liés aux contextes bornés

Maintenant, où se situent les microservices ? Est-il juste de dire que chaque contexte délimité correspond à un microservice ? Oui et non. Nous allons voir pourquoi. Il peut y avoir des cas où la limite ou le contour de votre contexte délimité est assez grand.

Fig 2. Contexte borné et microservices

Considérez l’exemple ci-dessus. Le contexte borné Pricing possède trois modèles distincts – Price, Priced items, et Discounts, chacun étant responsable respectivement du prix d’un article du catalogue, du calcul du prix total d’une liste d’articles et de l’application de remises. Nous pourrions créer un système unique qui englobe tous les modèles ci-dessus, mais cela pourrait devenir une application de taille déraisonnable. Chacun des modèles de données, comme mentionné précédemment, a ses invariants et ses règles de gestion. Au fil du temps, si nous ne faisons pas attention, le système pourrait devenir une Grande boule de boue avec des frontières obscures, des responsabilités qui se chevauchent, et probablement un retour au point de départ – un monolithe.

Une autre façon de modéliser ce système est de séparer, ou de regrouper les modèles connexes dans des microservices distincts. En DDD, ces modèles – prix, articles tarifés et remises – sont appelés agrégats. Un agrégat est un modèle autonome qui compose des modèles connexes. Vous ne pourriez modifier l’état d’un agrégat que par le biais d’une interface publiée, et l’agrégat garantit la cohérence et la bonne tenue des invariants.

Formellement, un agrégat est un cluster d’objets associés traité comme une unité pour les modifications de données. Les références externes sont limitées à un membre de l’AGGREGAT, désigné comme la racine. Un ensemble de règles de cohérence s’applique dans les limites de l’AGGREGAT.

Fig 3. Microservices dans le contexte de la tarification

Encore, il n’est pas nécessaire de modéliser chaque agrégat comme un microservice distinct. Cela s’est avéré être le cas pour les services (agrégats) de la Fig 3, mais ce n’est pas nécessairement une règle. Dans certains cas, il peut être judicieux d’héberger plusieurs agrégats dans un seul service, en particulier lorsque nous ne comprenons pas complètement le domaine d’activité. Il est important de noter que la cohérence ne peut être garantie qu’au sein d’un seul agrégat et que les agrégats ne peuvent être modifiés que par l’interface publiée. Toute violation de ceux-ci porte le risque de se transformer en une grosse boule de boue.

Context maps – Une façon de tailler des frontières précises de microservices

Une autre boîte à outils essentielle dans votre arsenal est le concept de Context maps – encore une fois, de Domain Driven Design. Un monolithe est généralement composé de modèles disparates, pour la plupart étroitement couplés – les modèles connaissent peut-être les détails intimes les uns des autres, la modification de l’un pourrait entraîner des effets secondaires sur un autre, et ainsi de suite. À mesure que vous décomposez le monolithe, il est essentiel d’identifier ces modèles – des agrégats dans ce cas – et leurs relations. Les cartes contextuelles nous aident à le faire. Elles sont utilisées pour identifier et définir les relations entre les différents contextes délimités et les agrégats. Alors que les contextes délimités définissent les limites d’un modèle – Prix, Remises, etc. dans l’exemple ci-dessus, les cartes de contexte définissent les relations entre ces modèles et entre les différents contextes. Après avoir identifié ces dépendances, nous pouvons déterminer le bon modèle de collaboration entre les équipes qui mettront en œuvre ces services.

Une exploration complète des cartes de contexte dépasse le cadre de ce blog, mais nous allons l’illustrer par un exemple. Le diagramme ci-dessous représente les différentes applications qui gèrent les paiements pour une commande eCommerce.

  1. Le contexte du panier s’occupe des autorisations en ligne d’une commande ; Le contexte de la commande traite les processus de paiement après exécution comme les règlements ; Le centre de contact gère toutes les exceptions comme la relance des paiements et le changement de la méthode de paiement utilisée pour la commande
  2. Pour simplifier, supposons que tous ces contextes sont mis en œuvre comme des services séparés
  3. Tous ces contextes encapsulent le même modèle.
  4. Notez que ces modèles sont logiquement les mêmes. C’est-à-dire qu’ils suivent tous le même langage du domaine Ubiquitous – méthodes de paiement, autorisations et règlements. Juste qu’ils font partie de contextes différents.

Un autre signe que le même modèle est réparti dans différents contextes est que tous ceux-ci s’intègrent directement à une seule passerelle de paiement et font les mêmes opérations les uns que les autres

Fig 4. Une carte de contexte incorrectement définie

Redéfinition des limites du service – Mapper les agrégats aux bons contextes

Il y a quelques problèmes qui sont très évidents dans la conception ci-dessus (Fig 4). L’agrégat de paiement fait partie de plusieurs contextes. Il est impossible de faire respecter les invariants et la cohérence entre les différents services, sans parler des problèmes de concurrence entre ces services. Par exemple, que se passe-t-il si le centre de contact change le mode de paiement associé à la commande alors que le service Commandes essaie d’enregistrer le règlement d’un mode de paiement précédemment soumis. Notez également que tout changement dans la passerelle de paiement obligerait à modifier plusieurs services et potentiellement de nombreuses équipes, car différents groupes pourraient posséder ces contextes.

Avec quelques ajustements et en alignant les agrégats sur les bons contextes, nous obtenons une bien meilleure représentation de ces sous-domaines – Fig 5. Il y a beaucoup de choses qui ont changé. Passons en revue les changements:

  1. L’agrégat Paiements a une nouvelle maison – Service de paiement. Ce service abstrait également la passerelle de paiement des autres services qui nécessitent des services de paiement. Comme un seul contexte délimité possède maintenant un agrégat, les invariants sont faciles à gérer ; toutes les transactions se produisent dans la même limite de service aidant à éviter tout problème de concurrence.
  2. L’agrégat Paiements utilise une couche anti-corruption (ACL) pour isoler le modèle de domaine principal du modèle de données de la passerelle de paiement, qui est généralement un fournisseur tiers et peut-être amené à changer. Nous nous pencherons plus en détail sur la conception de l’application d’un tel service en utilisant le pattern Ports and Adapters dans un prochain article. La couche ACL contient généralement les adaptateurs qui transforment le modèle de données de la passerelle de paiement en modèle de données agrégé Payments.
  3. Le service Panier appelle le service Payments par le biais d’appels API directs car le service Panier peut avoir à compléter l’autorisation de paiement alors que les clients sont sur le site web
  4. Prenez note de l’interaction entre le service Commandes et le service Paiement. Le service Commandes émet un événement de domaine (plus d’informations à ce sujet plus tard dans ce blog). Le service de paiement écoute cet événement et complète le règlement de la commande
  5. Le service de centre de contact peut avoir de nombreux agrégats, mais nous ne sommes intéressés que par l’agrégat Orders pour ce cas d’utilisation. Ce service émet un événement lorsque le mode de paiement change, et le service Paiements y réagit en inversant la carte de crédit utilisée précédemment et en traitant la nouvelle carte de crédit.

Fig 5. Carte de contexte redéfinie

En général, une application monolithique ou une application héritée possède de nombreux agrégats, dont les limites se chevauchent souvent. La création d’une carte contextuelle de ces agrégats et de leurs dépendances nous aide à comprendre les contours de tout nouveau microservice que nous allons arracher à ces monolithes. N’oubliez pas que le succès ou l’échec de l’architecture de microservices repose sur un faible couplage entre les agrégats et une forte cohésion au sein de ces agrégats.

Il est également important de noter que les contextes délimités sont eux-mêmes des unités cohésives appropriées. Même si un contexte a plusieurs agrégats, le contexte entier, ainsi que ses agrégats, peuvent être composés en un seul microservice. Nous trouvons cette heuristique particulièrement utile pour les domaines qui sont un peu obscurs – pensez à un nouveau secteur d’activité dans lequel l’organisation s’aventure. Il se peut que vous n’ayez pas une vision suffisante des bonnes limites de séparation, et toute décomposition prématurée des agrégats peut entraîner un remaniement coûteux. Imaginez que vous deviez fusionner deux bases de données en une seule, avec une migration des données, parce que vous avez découvert que deux agrégats vont ensemble. Mais assurez-vous que ces agrégats sont suffisamment isolés par des interfaces afin qu’ils ne connaissent pas les détails complexes les uns des autres.

Event Storming – Une autre technique pour identifier les limites des services

Event Storming est une autre technique essentielle pour identifier les agrégats (et donc les microservices) dans un système. C’est un outil utile à la fois pour casser les monolithes et lors de la conception d’un écosystème complexe de microservices. Nous avons utilisé cette technique pour décomposer l’une de nos applications complexes, et nous avons l’intention de couvrir nos expériences avec Event Storming dans un blog séparé. Dans le cadre de ce blog, nous souhaitons donner un rapide aperçu de haut niveau. Veuillez regarder la vidéo d’Alberto Brandelloni sur le sujet si vous êtes intéressé à explorer davantage.

En bref, l’Event Storming est un exercice de brainstorming entre les équipes qui travaillent sur une application – dans notre cas, un monolithe – pour identifier les différents événements et processus de domaine qui se produisent au sein d’un système. Les équipes identifient également les agrégats ou les modèles que ces événements affectent, ainsi que tout impact ultérieur. Au cours de cet exercice, les équipes identifient les différents concepts qui se chevauchent, le langage ambigu du domaine et les processus d’affaires conflictuels. Elles regroupent les modèles connexes, redéfinissent les agrégats et identifient les processus en double. Au fur et à mesure qu’elles progressent dans cet exercice, les contextes délimités auxquels appartiennent ces agrégats deviennent clairs. Les ateliers d’Event Storming sont utiles si toutes les équipes se retrouvent dans une seule pièce – physique ou virtuelle – et commencent à cartographier les événements, les commandes et les processus sur un tableau blanc de type scrum. À la fin de cet exercice, voici les résultats habituels :

  1. La liste redéfinie des agrégats. Ceux-ci deviennent potentiellement de nouveaux microservices
  2. Des événements de domaine qui doivent circuler entre ces microservices
  3. Des commandes qui sont des invocations directes d’autres applications ou utilisateurs

Nous avons montré ci-dessous un exemple de tableau à la fin d’un atelier d’Event Storming. C’est un excellent exercice de collaboration pour que les équipes se mettent d’accord sur les bons agrégats et contextes délimités. En plus d’être un excellent exercice de consolidation d’équipe, les équipes sortent de cette session avec une compréhension partagée du domaine, un langage omniprésent et des limites de service précises.

Fig 6. Tableau d’Event Storming

Communication entre microservices

Pour récapituler rapidement, un monolithe héberge plusieurs agrégats au sein d’une seule frontière de processus. Dès lors, la gestion de la cohérence des agrégats au sein de cette frontière est possible. Par exemple, si un client passe une commande, nous pouvons décrémenter l’inventaire des articles, envoyer un e-mail au client – tout cela dans une seule transaction. Toutes les opérations réussiraient, ou échoueraient. Mais, à mesure que nous brisons le monolithe et que nous répartissons les agrégats dans différents contextes, nous aurons des dizaines, voire des centaines de microservices. Les processus qui existaient jusqu’alors dans le cadre unique d’un monolithe sont désormais répartis sur plusieurs systèmes distribués. Atteindre l’intégrité et la cohérence transactionnelles à travers tous ces systèmes distribués est très difficile, et cela a un coût – la disponibilité des systèmes.

Les microservices sont également des systèmes distribués. Par conséquent, le théorème CAP s’applique également à eux – « un système distribué ne peut fournir que deux des trois caractéristiques souhaitées : la cohérence, la disponibilité et la tolérance de partition (le « C », le « A » et le « P » dans CAP). » Dans les systèmes du monde réel, la tolérance de partition n’est pas négociable – le réseau n’est pas fiable, les machines virtuelles peuvent tomber en panne, la latence entre les régions peut s’aggraver, et ainsi de suite.

Ce qui nous laisse donc le choix entre Disponibilité et Cohérence. Maintenant, nous savons que dans toute application moderne, sacrifier la disponibilité n’est pas non plus une bonne idée.

Fig 7. Théorème CAP

Concevoir des applications autour de la cohérence éventuelle

Si vous essayez de construire des transactions à travers plusieurs systèmes distribués, vous vous retrouverez à nouveau au pays des monolithes. Seulement, cette fois, ce sera la pire espèce, un monolithe distribué. Si l’un des systèmes devient indisponible, l’ensemble du processus devient indisponible, ce qui entraîne souvent une expérience client frustrante, des promesses non tenues, etc. En outre, les changements apportés à un service peuvent généralement entraîner des changements dans un autre service, ce qui conduit à des déploiements complexes et coûteux. Par conséquent, il est préférable de concevoir des applications en adaptant nos cas d’utilisation pour tolérer un peu d’incohérence en faveur de la disponibilité. Dans l’exemple ci-dessus, nous pouvons rendre tous les processus asynchrones et donc éventuellement cohérents. Nous pouvons envoyer des courriels de manière asynchrone, indépendamment des autres processus ; Si un article promis n’est pas disponible dans l’entrepôt plus tard, l’article pourrait être en rupture de stock, ou nous pourrions arrêter de prendre des commandes pour l’article au-delà d’un certain seuil.
Occasionnellement, vous pouvez rencontrer un scénario qui pourrait exiger des transactions fortes de style ACID entre deux agrégats dans des frontières de processus différentes. C’est un excellent signe pour réexaminer ces agrégats et peut-être les combiner en un seul. L’Event Storming et les Context Maps permettront d’identifier ces dépendances à un stade précoce, avant de commencer à fragmenter ces agrégats en différents processus. La fusion de deux microservices en un seul est coûteuse, et c’est quelque chose que nous devons nous efforcer d’éviter.

Favoriser l’architecture pilotée par les événements

Les microservices peuvent émettre des changements essentiels qui se produisent dans leurs agrégats. Ceux-ci sont appelés événements de domaine, et tous les services qui sont intéressés par ces changements peuvent écouter ces événements et prendre des mesures respectives dans leurs domaines. Cette méthode évite tout couplage comportemental – un domaine ne prescrit pas ce que les autres domaines doivent faire, et tout couplage temporel – la réussite d’un processus ne dépend pas de la disponibilité simultanée de tous les systèmes. Cela signifie, bien sûr, que les systèmes seront finalement cohérents.

Fig 8. Architecture pilotée par les événements

Dans l’exemple ci-dessus, le service Commandes publie un événement – Commande annulée. Les autres services qui ont souscrit à l’événement traitent leurs fonctions de domaine respectives : Le service Paiement rembourse l’argent, le service Inventaire ajuste l’inventaire des articles, et ainsi de suite. Quelques points à noter pour assurer la fiabilité et la résilience de cette intégration :

  1. Les producteurs doivent s’assurer qu’ils produisent un événement au moins une fois. En cas d’échec, ils doivent s’assurer qu’un mécanisme de repli est présent pour redéclencher les événements
  2. Les consommateurs doivent s’assurer qu’ils consomment les événements de manière idempotente. Si le même événement se reproduit, il ne devrait pas y avoir d’effet secondaire du côté du consommateur. Les événements peuvent également arriver hors séquence. Les consommateurs peuvent utiliser les champs Timestamp ou les numéros de version pour garantir l’unicité des événements.

Il n’est pas toujours possible d’utiliser l’intégration basée sur les événements en raison de la nature de certains cas d’utilisation. Veuillez jeter un coup d’œil à l’intégration entre le service Panier et le service Paiement. Il s’agit d’une intégration synchrone, et donc de quelques éléments auxquels nous devons faire attention. C’est un exemple de couplage comportemental – le service Panier appelle peut-être une API REST du service Paiement et lui demande d’autoriser le paiement d’une commande, et de couplage temporel – le service Paiement doit être disponible pour que le service Panier accepte une commande. Ce type de couplage réduit l’autonomie de ces contextes et peut entraîner une dépendance indésirable. Il y a quelques façons d’éviter ce couplage, mais avec toutes ces options, nous perdrons la capacité de fournir un feedback instantané aux clients.

  1. Convertir l’API REST en une intégration basée sur les événements. Mais cette option peut ne pas être disponible si le service de paiement n’expose qu’une API REST
  2. Le service de panier accepte une commande instantanément, et il y a un travail par lot qui récupère les commandes et appelle l’API du service de paiement
  3. Le service de panier produit un événement local qui appelle ensuite l’API du service de paiement

Une combinaison de ce qui précède avec des reprises en cas de défaillances et d’indisponibilité de la dépendance en amont – le service de paiement – peut donner lieu à une conception beaucoup plus résiliente. Par exemple, l’intégration synchrone entre le panier et les services de paiement peut être soutenue par un événement ou des tentatives basées sur des lots en cas de défaillances. Cette approche a un impact supplémentaire sur l’expérience client – les clients peuvent avoir saisi des détails de paiement incorrects, et nous ne les aurons pas en ligne lorsque nous traiterons les paiements hors ligne. Il se peut également que la récupération des paiements échoués entraîne un coût supplémentaire pour l’entreprise. Mais selon toute vraisemblance, les avantages de la résilience du service de panier en cas d’indisponibilité ou de défaillance du service de paiement l’emportent sur les inconvénients. Par exemple, nous pouvons avertir les clients si nous ne sommes pas en mesure de collecter les paiements hors ligne. En bref, il existe des compromis entre l’expérience utilisateur, la résilience et les coûts d’exploitation, et il est sage de concevoir des systèmes, en gardant ces compromis à l’esprit.

Éviter l’orchestration entre les services pour les besoins en données spécifiques des consommateurs

L’un des anti-modèles de toute architecture orientée services est que les services répondent aux modèles d’accès spécifiques des consommateurs. Habituellement, cela se produit lorsque les équipes de consommateurs travaillent étroitement avec les équipes de services. Si l’équipe travaillait sur une application monolithique, elle créerait souvent une API unique qui traverse les frontières de différents agrégats, couplant ainsi étroitement ces agrégats. Prenons un exemple. Supposons que la page des détails de la commande dans les applications Web et mobiles doive afficher sur une seule page les détails d’une commande et les détails des remboursements traités pour la commande. Dans une application monolithique, une API GET de commande – en supposant qu’il s’agisse d’une API REST – interroge les commandes et les remboursements ensemble, consolide les deux agrégats et envoie une réponse composite aux appelants. Il est possible d’effectuer cette opération sans trop de frais généraux, car les agrégats appartiennent à la même frontière de processus. Ainsi, les consommateurs peuvent obtenir toutes les données nécessaires en un seul appel.

Si les commandes et les remboursements font partie de contextes différents, les données ne sont plus présentes dans un seul microservice ou une seule frontière d’agrégat. Une option pour conserver la même fonctionnalité pour les consommateurs est de rendre le service Order responsable de l’appel du service Refunds et de créer une réponse composite. Cette approche entraîne plusieurs préoccupations :

1. Le service Order s’intègre maintenant avec un autre service purement pour supporter les consommateurs qui ont besoin des données Refunds en même temps que les données Order. Le service de commande est moins autonome maintenant, car tout changement dans l’agrégat Refunds entraînera un changement dans l’agrégat Order.

2. Le service de commande a une autre intégration et donc un autre point de défaillance à prendre en compte – si le service Refunds est en panne, le service de commande peut-il encore envoyer des données partielles, et les consommateurs peuvent-ils échouer gracieusement ?

3. Si les consommateurs ont besoin d’un changement pour récupérer plus de données de l’agrégat Refunds, deux équipes sont impliquées maintenant pour faire ce changement

4. Ce modèle, s’il est suivi à travers la plate-forme, peut conduire à un réseau complexe de dépendances entre les différents services de domaine, tout cela parce que ces services répondent aux schémas d’accès spécifiques des appelants.

Backend for Frontends (BFFs)

Une approche pour atténuer ce risque est de laisser les équipes de consommateurs gérer l’orchestration entre les différents services de domaine. Après tout, les appelants connaissent mieux les schémas d’accès et peuvent avoir le contrôle total de toute modification de ces schémas. Cette approche dissocie les services de domaine du niveau de présentation, ce qui leur permet de se concentrer sur les processus métier fondamentaux. Mais si les apps web et mobiles commencent à appeler directement différents services au lieu de l’unique API composite du monolithe, cela peut entraîner des surcharges de performance pour ces apps – appels multiples sur des réseaux à bande passante plus faible, traitement et fusion de données provenant de différentes API, etc.

Au lieu de cela, on pourrait utiliser un autre pattern appelé Backend for Front-ends. Dans ce patron de conception, un service backend créé et géré par les consommateurs – dans ce cas, les équipes web et mobile – se charge de l’intégration à travers plusieurs services de domaine purement pour rendre l’expérience frontale aux clients. Les équipes Web et mobiles peuvent désormais concevoir les contrats de données en fonction des cas d’utilisation auxquels elles répondent. Elles peuvent même utiliser GraphQL au lieu des API REST pour effectuer des requêtes flexibles et obtenir exactement ce dont elles ont besoin. Il est important de noter que ce service est détenu et maintenu par les équipes de consommateurs et non par les équipes qui possèdent les services de domaine. Les équipes frontales peuvent désormais optimiser le service en fonction de leurs besoins : une application mobile peut demander une charge utile plus petite, réduire le nombre d’appels de l’application mobile, etc. Jetez un coup d’œil à la vue révisée de l’orchestration ci-dessous. Le service BFF appelle maintenant les services de domaine Orders et Refunds pour son cas d’utilisation.

Fig 9. Backend for Frontends

Il est également utile de construire le service BFF tôt, avant de casser une pléthore de services du monolithe. Sinon, soit les services de domaine devront prendre en charge l’orchestration inter-domaine, soit les applications web et mobiles devront appeler plusieurs services directement depuis le front-end. Ces deux options entraîneront des surcharges de performance, du travail jetable et un manque d’autonomie entre les équipes.

Conclusion

Dans ce blog, nous avons abordé divers concepts, stratégies et heuristiques de conception à prendre en compte lorsque nous nous aventurons dans le monde des microservices, plus précisément lorsque nous essayons de casser un monolithe en plusieurs microservices basés sur les domaines. Beaucoup de ces sujets sont vastes en soi, et je ne pense pas que nous ayons rendu suffisamment de justice pour les expliquer en détail, mais nous voulions présenter certains des sujets essentiels et notre expérience en les adoptant. La section Further Reading (lien) comporte quelques références et du contenu utile pour quiconque souhaite poursuivre dans cette voie.

Mise à jour : les deux prochains blogs de la série sont sortis. Ces deux blogs traitent de la mise en œuvre du microservice Cart, avec des exemples de code, en utilisant les principes de conception pilotée par le domaine et les patrons de conception Ports et adaptateurs. L’objectif principal de ces blogs est de démontrer comment ces deux principes/patrons nous aident à construire des applications modulaires qui sont agiles, testables et refactorisables – en bref, être capable de répondre à l’environnement rapide dans lequel nous évoluons tous.

Mise en œuvre du microservice Cart à l’aide de la conception pilotée par le domaine et du modèle Ports et adaptateurs – Partie 1

Mise en œuvre du microservice Cart à l’aide de la conception pilotée par le domaine et du modèle Ports et adaptateurs – Partie 2

Lectures complémentaires

1. La conception pilotée par le domaine d’Eric Evans

2. La mise en œuvre de la conception pilotée par le domaine de Vaughn Vernon

3. L’article de Martin Fowler sur les microservices

4. La construction de microservices de Sam Newman

5. Event storming

7. Backend pour Frontends

8. Fallacies de l’informatique distribuée

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.