Traduction et adaptation des microservices Java : un guide pratique . Parties précédentes du guide :
Examinons les problèmes inhérents aux microservices en Java, en commençant par les choses abstraites et en terminant par les bibliothèques concrètes.
Comment rendre un microservice Java résilient ?
Rappelez-vous que lorsque vous créez des microservices, vous échangez essentiellement des appels de méthode JVM contre des appels HTTP synchrones ou une messagerie asynchrone. Alors qu'un appel de méthode est généralement garanti (sauf arrêt inattendu de la JVM), un appel réseau n'est pas fiable par défaut. Cela peut fonctionner, mais cela peut ne pas fonctionner pour diverses raisons : le réseau est surchargé, une nouvelle règle de pare-feu a été mise en place, etc. Pour voir comment cela fait une différence, jetons un coup d'œil à l'exemple BillingService.Modèles de résilience HTTP/REST
Supposons que les clients puissent acheter des livres électroniques sur le site Web de votre entreprise. Pour ce faire, vous venez de mettre en place un microservice de facturation qui peut appeler votre boutique en ligne pour générer de véritables factures PDF. Pour l'instant, nous allons effectuer cet appel de manière synchrone, via HTTP (bien qu'il soit plus logique d'appeler ce service de manière asynchrone, car la génération de PDF ne doit pas nécessairement être instantanée du point de vue de l'utilisateur. Nous utiliserons le même exemple dans la section suivante. section et regardez les différences).@Service
class BillingService {
@Autowired
private HttpClient client;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
// ...
}
}
Pour résumer, voici trois résultats possibles de cet appel HTTP.
- OK : l'appel a bien abouti, le compte a été créé avec succès.
- RETARD : l'appel a abouti, mais son exécution a pris trop de temps.
- ERREUR. L'appel a échoué, vous avez peut-être envoyé une demande incompatible ou le système ne fonctionne pas.
Modèles de résilience de messagerie
Examinons de plus près la communication asynchrone. Notre programme BillingService pourrait maintenant ressembler à ceci, en supposant que nous utilisons Spring et RabbitMQ pour la messagerie. Pour créer un compte, nous envoyons maintenant un message à notre courtier de messages RabbitMQ, où plusieurs travailleurs attendent de nouveaux messages. Ces travailleurs créent des factures PDF et les envoient aux utilisateurs appropriés.@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void bill(User user, Plan plan) {
Invoice invoice = createInvoice(user, plan);
// преобразует счет, например, в json и использует его How тело messages
rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
// ...
}
}
Les erreurs potentielles semblent désormais un peu différentes, car vous ne recevez plus de réponses immédiates OK ou ERREUR comme vous le faisiez avec une connexion HTTP synchrone. Au lieu de cela, nous pouvons avoir trois scénarios potentiels qui pourraient mal tourner, ce qui pourrait soulever les questions suivantes :
- Mon message a-t-il été livré et utilisé par l'employé ? Ou est-ce perdu ? (L'utilisateur ne reçoit pas de facture).
- Mon message n'a-t-il été livré qu'une seule fois ? Ou livré plus d’une fois et traité une seule fois ? (L'utilisateur recevra plusieurs factures).
- Configuration : de « Ai-je utilisé les clés/noms de routage corrects pour l'échange » à « Mon courtier de messages est-il configuré et géré correctement ou ses files d'attente sont-elles pleines ? » (L'utilisateur ne reçoit pas de facture).
- Si vous utilisez des implémentations JMS comme ActiveMQ, vous pouvez échanger la vitesse contre la garantie de validations en deux phases (XA).
- Si vous utilisez RabbitMQ, lisez d'abord ce didacticiel, puis réfléchissez attentivement aux confirmations, à la tolérance aux pannes et à la fiabilité des messages en général.
- Peut-être que quelqu'un connaît bien la configuration de serveurs Active ou RabbitMQ, notamment en combinaison avec le clustering et Docker (quelqu'un ? ;))
Quel framework serait la meilleure solution pour les microservices Java ?
D'une part, vous pouvez installer une option très populaire telle que Spring Boot . Il facilite la création de fichiers .jar, est livré avec un serveur Web intégré comme Tomcat ou Jetty et peut être exécuté rapidement et n'importe où. Idéal pour créer des applications de microservices. Récemment, quelques frameworks de microservices spécialisés, Kubernetes ou GraalVM , sont apparus, partiellement inspirés de la programmation réactive. Voici quelques autres prétendants intéressants : Quarkus , Micronaut , Vert.x , Helidon . En fin de compte, vous devrez choisir vous-même, mais nous pouvons vous donner quelques recommandations qui ne sont peut-être pas tout à fait standard : à l'exception de Spring Boot, tous les frameworks de microservices sont généralement présentés comme incroyablement rapides, avec un démarrage presque instantané. , faible utilisation de la mémoire, évolutivité à l'infini. Les supports marketing comportent généralement des graphismes impressionnants qui mettent en valeur la plate-forme à côté du géant Spring Boot ou les unes à côté des autres. Ceci, en théorie, épargne les nerfs des développeurs prenant en charge des projets existants, dont le chargement prend parfois plusieurs minutes. Ou les développeurs travaillant dans le cloud qui souhaitent démarrer/arrêter autant de microconteneurs dont ils ont actuellement besoin en 50 ms. Le problème, cependant, est que ces temps de démarrage et de redéploiement nus (artificiels) ne contribuent guère au succès global du projet. Au moins, ils influencent beaucoup moins qu’une infrastructure-cadre solide, une documentation solide, une communauté et de solides compétences de développement. Il est donc préférable de voir les choses de cette façon : Si jusqu'à présent :- Vous laissez vos ORM fonctionner de manière effrénée, générant des centaines de requêtes pour des flux de travail simples.
- Vous avez besoin de gigaoctets infinis pour exécuter votre monolithe moyennement complexe.
- Vous avez tellement de code et la complexité est si élevée (nous ne parlons pas maintenant de démarreurs potentiellement lents comme Hibernate) que le chargement de votre application prend plusieurs minutes.
Quelles bibliothèques sont les meilleures pour les appels Java REST synchrones ?
Sur le plan technique de bas niveau, vous vous retrouverez probablement avec l'une des bibliothèques client HTTP suivantes : HttpClient natif de Java (depuis Java 11), HttpClient d'Apache ou OkHttp . Notez que je dis "probablement" ici car il existe d'autres options, allant des bons vieux clients JAX-RS aux clients WebSocket modernes . Dans tous les cas, la tendance est à la génération d’un client HTTP, plutôt que de manipuler soi-même les appels HTTP. Pour ce faire, vous devez jeter un œil au projet OpenFeign et à sa documentation comme point de départ pour des lectures plus approfondies.Quels sont les meilleurs courtiers pour la messagerie Java asynchrone ?
Très probablement, vous rencontrerez les populaires ActiveMQ (Classic ou Artemis) , RabbitMQ ou Kafka .- ActiveMQ et RabbitMQ sont des courtiers de messages traditionnels à part entière. Ils impliquent l’interaction d’un « courtier intelligent » et d’« utilisateurs stupides ».
- Historiquement, ActiveMQ a bénéficié d'une intégration facile (pour les tests), qui peut être atténuée avec les paramètres RabbitMQ/Docker/TestContainer.
- Kafka ne peut pas être qualifié de courtier « intelligent » traditionnel. Au lieu de cela, il s'agit d'une banque de messages relativement « stupide » (fichier journal) qui nécessite le traitement des consommateurs intelligents.
Quelles bibliothèques puis-je utiliser pour tester les microservices ?
Cela dépend de votre pile. Si vous disposez d'un écosystème Spring déployé, il serait judicieux d'utiliser les outils spécifiques du framework . Si JavaEE ressemble à Arquillian . Cela vaut peut-être la peine de jeter un oeil à Docker et à la très bonne bibliothèque Testcontainers , qui permet notamment de mettre en place facilement et rapidement une base de données Oracle pour des tests de développement local ou d'intégration. Pour tester des serveurs HTTP entiers, consultez Wiremock . Pour tester la messagerie asynchrone, essayez d'implémenter ActiveMQ ou RabbitMQ, puis d'écrire des tests à l'aide d' Awaitility DSL . De plus, tous vos outils habituels sont utilisés - Junit , TestNG pour AssertJ et Mockito . Veuillez noter qu'il ne s'agit pas d'une liste complète. Si vous ne trouvez pas votre outil préféré ici, veuillez le publier dans la section commentaires.Comment activer la journalisation pour tous les microservices Java ?
La journalisation dans le cas des microservices est un sujet intéressant et plutôt complexe. Au lieu d'avoir un fichier journal que vous pouvez manipuler avec les commandes less ou grep, vous disposez désormais de n fichiers journaux et vous souhaitez qu'ils ne soient pas trop dispersés. Les caractéristiques de l'écosystème forestier sont bien décrites dans cet article (en anglais). Assurez-vous de le lire et faites attention à la section Journalisation centralisée du point de vue des microservices . En pratique, vous rencontrerez différentes approches : L'administrateur système écrit certains scripts qui collectent et fusionnent les fichiers journaux de différents serveurs en un seul fichier journal et les place sur des serveurs FTP pour téléchargement. Exécution de combinaisons cat/grep/unig/sort dans des sessions SSH parallèles. C'est exactement ce que fait Amazon AWS, et vous pouvez en informer votre responsable. Utilisez un outil comme Graylog ou ELK Stack (Elasticsearch, Logstash, Kibana)Comment mes microservices se retrouvent-ils ?
Jusqu'à présent, nous avons supposé que nos microservices se connaissent et connaissent l'IPS correspondant. Parlons de configuration statique. Ainsi, notre monolithe bancaire [ip = 192.168.200.1] sait qu'il doit communiquer avec le serveur de risques [ip = 192.168.200.2], qui est codé en dur dans le fichier de propriétés. Cependant, vous pouvez rendre les choses plus dynamiques :- Utilisez un serveur de configuration basé sur le cloud à partir duquel tous les microservices extraient leurs configurations au lieu de déployer des fichiers application.properties sur leurs microservices.
- Étant donné que vos instances de service peuvent changer d'emplacement de manière dynamique, il est utile d'examiner les services qui savent où se trouvent vos services, quelles sont leurs adresses IP et comment les acheminer.
- Maintenant que tout est dynamique, de nouveaux problèmes surgissent, comme l'élection automatique d'un leader : qui est le maître qui travaille sur certaines tâches pour ne pas les traiter deux fois, par exemple ? Qui remplace un leader lorsqu’il échoue ? Sur quelle base s'effectue le remplacement ?
Comment organiser l'autorisation et l'authentification à l'aide des microservices Java ?
Ce sujet mérite également une histoire distincte. Encore une fois, les options vont de l'authentification HTTPS de base codée en dur avec des cadres de sécurité personnalisés à l'exécution d'une installation Oauth2 avec son propre serveur d'autorisation.Comment puis-je m'assurer que tous mes environnements se ressemblent ?
Ce qui est vrai pour les déploiements sans microservice l’est également pour les déploiements avec un microservice. Essayez une combinaison de Docker/Testcontainers et Scripting/Ansible.Pas de question : brièvement sur YAML
Laissons de côté les bibliothèques et les problèmes associés pendant un moment et jetons un coup d'œil rapide à Yaml. Ce format de fichier est utilisé de facto comme format pour « écrire la configuration sous forme de code ». Il est également utilisé par des outils simples comme Ansible et des géants comme Kubernetes. Pour ressentir la douleur de l'indentation YAML, essayez d'écrire un simple fichier Ansible et voyez combien vous devez modifier le fichier avant qu'il fonctionne comme prévu. Et ce malgré le format pris en charge par tous les principaux IDE ! Après cela, revenez pour terminer la lecture de ce guide.Yaml:
- is:
- so
- great
Qu’en est-il des transactions distribuées ? Test de performance? D'autres sujets ?
Peut-être un jour, dans les prochaines éditions du manuel. Pour l’instant, c’est tout. Rester avec nous!Problèmes conceptuels avec les microservices
Outre les problèmes spécifiques des microservices en Java, il existe d'autres problèmes, disons, qui apparaissent dans tout projet de microservices. Ils concernent principalement l’organisation, l’équipe et le management.Inadéquation front-end et back-end
L'inadéquation entre le frontend et le backend est un problème très courant dans de nombreux projets de microservices. Qu'est-ce que ça veut dire? Seulement, dans les bons vieux monolithes, les développeurs d’interfaces Web disposaient d’une source spécifique pour obtenir des données. Dans les projets de microservices, les développeurs front-end disposent soudainement de n sources pour obtenir des données. Imaginez que vous créez une sorte de projet de microservices IoT (Internet des objets) en Java. Disons que vous gérez des machines géodésiques et des fours industriels dans toute l'Europe. Et ces fours vous envoient des mises à jour régulières avec leurs températures, etc. Tôt ou tard, vous souhaiterez peut-être trouver des fours dans l'interface utilisateur d'administration, peut-être en utilisant les microservices de « recherche de fours ». Selon la rigueur avec laquelle vos homologues back-end appliquent la conception basée sur le domaine ou les lois sur les microservices, un microservice « trouver un four » peut renvoyer uniquement les identifiants du four et non d'autres données telles que le type, le modèle ou l'emplacement. Pour ce faire, les développeurs frontend devront effectuer un ou n appels supplémentaires (en fonction de l'implémentation de la pagination) dans le microservice « obtenir les données du four » avec les identifiants qu'ils ont reçus du premier microservice. Et bien qu'il ne s'agisse que d'un exemple simple, bien que tiré d'un projet réel (!), il démontre néanmoins le problème suivant : les supermarchés sont devenus extrêmement populaires. En effet, avec eux, vous n'avez pas besoin d'aller dans 10 endroits différents pour acheter des légumes, de la limonade, des pizzas surgelées et du papier toilette. Au lieu de cela, vous allez à un seul endroit, c'est plus facile et plus rapide. Il en va de même pour les développeurs front-end et microservices.Attentes de la direction
La direction a l’impression erronée qu’elle doit désormais embaucher un nombre infini de développeurs pour un projet (global), puisque les développeurs peuvent désormais travailler de manière totalement indépendante les uns des autres, chacun sur son propre microservice. Seul un petit travail d'intégration est nécessaire à la toute fin (peu avant le lancement). En fait, cette approche est extrêmement problématique. Dans les paragraphes suivants, nous tenterons d’expliquer pourquoi.Les « morceaux plus petits » ne sont pas synonymes de « meilleurs morceaux »
Ce serait une grave erreur de supposer qu’un code divisé en 20 parties sera nécessairement de meilleure qualité qu’un morceau entier. Même si nous considérons la qualité d'un point de vue purement technique, nos services individuels peuvent toujours exécuter 400 requêtes Hibernate pour sélectionner un utilisateur dans la base de données, traversant des couches de code non prises en charge. Encore une fois, revenons à la citation de Simon Brown : si vous ne parvenez pas à construire correctement des monolithes, il sera difficile de créer des microservices appropriés. Il est souvent extrêmement tard pour parler de tolérance aux pannes dans les projets de microservices. À tel point qu’il est parfois effrayant de voir comment fonctionnent les microservices dans des projets réels. La raison en est que les développeurs Java ne sont pas toujours prêts à étudier la tolérance aux pannes, les réseaux et d'autres sujets connexes au niveau approprié. Les « pièces » elles-mêmes sont plus petites, mais les « parties techniques » sont plus grandes. Imaginez que votre équipe de microservices soit invitée à écrire un microservice technique pour se connecter à un système de base de données, quelque chose comme ceci :@Controller
class LoginController {
// ...
@PostMapping("/login")
public boolean login(String username, String password) {
User user = userDao.findByUserName(username);
if (user == null) {
// обработка варианта с несуществующим пользователем
return false;
}
if (!user.getPassword().equals(hashed(password))) {
// обработка неверного пароля
return false;
}
// 'Ю-ху, залогинorсь!';
// установите cookies, делайте, что угодно
return true;
}
}
Maintenant, votre équipe peut décider (et peut-être même convaincre les gens d'affaires) que tout cela est trop simple et ennuyeux, au lieu d'écrire un service de connexion, il est préférable d'écrire un microservice UserStateChanged vraiment utile sans aucune exigence commerciale réelle et tangible. Et puisque certaines personnes traitent actuellement Java comme un dinosaure, écrivons notre microservice UserStateChanged en Erlang à la mode. Et essayons d'utiliser des arbres rouge-noir quelque part, car Steve Yegge a écrit qu'il faut les connaître par cœur pour postuler à Google. Du point de vue de l'intégration, de la maintenance et de la conception globale, cela est aussi mauvais que d'écrire des couches de code spaghetti dans un seul monolithe. Un exemple artificiel et ordinaire ? C'est vrai. Cependant, cela peut arriver dans la réalité.
Moins de pièces - moins de compréhension
Ensuite, la question se pose naturellement de comprendre le système dans son ensemble, ses processus et ses flux de travail, mais en même temps, vous, en tant que développeur, êtes uniquement responsable du travail sur votre microservice isolé [95 : login-101 : updateUserProfile]. Cela s'harmonise avec le paragraphe précédent, mais en fonction de votre organisation, de votre niveau de confiance et de communication, cela peut entraîner beaucoup de confusion, des haussements d'épaules et des reproches en cas de panne accidentelle de la chaîne des microservices. Et personne ne peut assumer l’entière responsabilité de ce qui s’est passé. Et ce n’est pas du tout une question de malhonnêteté. En fait, il est très difficile de relier les différentes parties et de comprendre leur place dans l’ensemble du projet.Communication et service
Le niveau de communication et de service varie considérablement en fonction de la taille de l'entreprise. Cependant, la relation générale est évidente : plus il y en a, plus c'est problématique.- Qui gère le microservice n°47 ?
- Ont-ils simplement déployé une nouvelle version incompatible d’un microservice ? Où cela a-t-il été documenté ?
- À qui dois-je m’adresser pour demander une nouvelle fonctionnalité ?
- Qui prendra en charge ce microservice à Erlang, après que le seul connaissant cette langue ait quitté l'entreprise ?
- Toutes nos équipes de microservices travaillent non seulement dans différents langages de programmation, mais également dans différents fuseaux horaires ! Comment coordonner correctement tout cela ?
GO TO FULL VERSION