Je suis affecté à un projet où nous avons une vingtaine de micro-services. Chacun d'eux est dans un référentiel séparé sans aucune référence à aucun autre, à l'exception d'un package Nuget où nous conservons du code générique comme des fonctions mathématiques. Chaque service référence les autres par points de terminaison.
L'avantage de ceci est:
Chaque service est hautement indépendant. (En réalité, ce point est à discuter, car un changement à l'API d'un service est susceptible d'en affecter plusieurs autres)
Meilleure pratique - selon les personnes à qui j'ai parlé
Les inconvénients sont:
Pas de réutilisation de code.
Certains objets DTO sont définis plusieurs fois (peut-être jusqu'à 10 secondes)
Chaque classe d'assistance ServiceCommunication qui enveloppe les points de terminaison d'un service pour en faciliter l'utilisation est dupliquée plusieurs fois, une fois pour chaque dépôt.
Je pense que ce qui suit est une meilleure façon de structurer le projet: Un repo. Chaque micro-service fournit une classe d'assistance Server.Communication qui encapsule les extrémités et une sélection de types Server.Dto que la classe Server.Communication renvoie à partir de ses appels d'API. Si un autre service souhaite l'utiliser, il l'inclura.
J'espère avoir bien expliqué le problème. Est-ce une meilleure solution qui résoudra certains de mes problèmes ou vais-je finir par créer des problèmes imprévus?
Aucune réutilisation de code n'est généralement comprise comme un argument de vente pour les microservices!
Si cela ne semble pas être un avantage - en particulier si tous les microservices sont développés par une seule équipe, en utilisant une pile technologique et déployés ensemble - alors peut-être n'avez-vous pas besoin de microservices, mais d'un ensemble de bibliothèques. Et vous pouvez maintenir toutes les bibliothèques/services dans un seul monorepo.
Si nous ignorons les arguments d'évolutivité pendant un moment, les bibliothèques sont largement préférables aux microservices. Une API de microservice est effectivement typée dynamiquement, ce qui peut être une source d'erreurs - tout comme vous l'avez vécu. En revanche, les API de bibliothèque sont généralement typées statiquement, ce qui peut empêcher toute une classe d'erreurs via la vérification de type au moment de la compilation. De plus, Intellisense est sympa. Les bibliothèques qui s'exécutent dans le même processus ont tendance à être beaucoup plus faciles à utiliser que systèmes distribués, qui ont leurs propres défis, notamment en ce qui concerne les pannes de réseau, la cohérence et les transactions distribuées.
L'utilisation d'une architecture de microservice signifie que vous acceptez ces inconvénients parce que les microservices vous permettent de résoudre des problèmes encore plus importants, tels que évolutivité organisationnelle (laissez différentes équipes se développer et déployer leurs services indépendamment) et évolutivité technique (mettre à l'échelle les différentes parties du système séparément).
Il y a possibilité de compromis entre les bibliothèques et les microservices qui peuvent vous faciliter la vie un peu.
Par exemple, un microservice peut fournir un bibliothèque client pour se connecter à ce microservice. Cette bibliothèque gère les détails de connexion et fournit un ensemble d'objets de transfert de données. Si elles sont conçues correctement, les anciennes versions de bibliothèque sont toujours en mesure d'interagir avec les versions plus récentes du microservice, ce qui leur permet d'être mises à jour quelque peu indépendamment. Cependant, cela nécessite que tous les utilisateurs de ce microservice utilisent le même langage de programmation et que ces utilisateurs puissent mettre à niveau leur bibliothèque cliente dans un délai raisonnable. Une telle approche peut être utile pour une équipe qui a commencé à utiliser des microservices sans comprendre les implications, mais n'est généralement pas viable si vous utilisez des microservices pour leurs avantages d'évolutivité organisationnelle.
Une version plus douce de ceci est d'utiliser un langage de description de service qui fournit une définition d'API indépendante du langage. Les clients peuvent ensuite utiliser des outils de génération de code pour générer des DTO et des bibliothèques de connexion dans leur langage de programmation. Cela dissocie avec succès les services d'un autre, tout en évitant la possibilité d'erreurs de discordance d'API. Le fait de devoir travailler dans ce langage de description de service rend également plus difficile la rupture accidentelle de la rétrocompatibilité. Mais cela vous oblige à apprendre des outils supplémentaires, et le code généré peut être difficile à utiliser. C'est généralement la voie à suivre dans les environnements plus d'entreprise.
Vous pouvez également être en mesure d'appliquer méthodes de test avancées pour exclure une incompatibilité d'API, par exemple enregistrer/relire les tests d'intégration: tout d'abord, vous exécutez les cas de test d'un client par rapport à un véritable microservice et enregistrez les demandes et les réponses. L'artefact de cet enregistrement est partagé par le client et le microservice. Les enregistrements peuvent ensuite être utilisés dans les tests client pour fournir un microservice simulé qui rejoue les réponses en conserve. Les enregistrements sont également utilisés par le microservice pour vérifier que le service continue de fournir les mêmes réponses. Malheureusement, la mise à jour de ces enregistrements pour tenir compte des changements peut être difficile. Les enregistrements peuvent également être très fragiles, par ex. si les métadonnées non pertinentes comme les dates exactes ne sont pas nettoyées. Je travaille actuellement sur la transition d'une suite de tests similaire, mais la fragilité de dépendre des réponses enregistrées exactes rend cela incroyablement difficile.