AccueilContact

Anti-Patterns en Go

Publié dans Développement Web
11 juillet 2024
8 min read
Anti-Patterns en Go

Common Anti-Patterns in Go Web Applications

Au cours de ma carrière, j’ai été confronté à un moment où je n’étais plus enthousiasmé par les logiciels que je développais. Mon aspect préféré du travail était les détails de bas niveau et les algorithmes complexes. Après être passé aux applications orientées utilisateur, ils avaient disparu en grande partie. Il semblait que la programmation consistait à déplacer des données d’un endroit à un autre en utilisant des bibliothèques et des outils existants. Ce que j’avais appris jusqu’alors sur les logiciels n’était plus aussi utile. La plupart des applications web ne résolvent pas de défis techniques difficiles. Elles devraient modéliser correctement le produit et permettre de l’améliorer plus rapidement que la concurrence. Cela semble ennuyeux au début, mais ensuite vous réalisez que soutenir cet objectif est plus difficile qu’il n’y paraît. Il y a un ensemble entièrement différent de défis. Même s’ils ne sont pas aussi complexes sur le plan technique, les résoudre a un impact massif sur le produit et est profondément satisfaisant. Le plus grand défi auquel les applications web sont confrontées est de ne pas devenir une grosse boule de boue ingérable. Cela vous ralentit et peut vous mettre hors jeu. Voici pourquoi cela se produit en Go et comment j’ai appris à l’éviter.## Le Découplage Est la Clé Une grande raison pour laquelle les applications deviennent difficiles à maintenir est le couplage fort. Dans les applications fortement couplées, tout ce que vous touchez a des effets secondaires inattendus. Chaque tentative de refactoring découvre de nouveaux problèmes. Finalement, vous décidez qu’il est préférable de réécrire le projet à partir de zéro. Dans un produit en pleine croissance, vous ne pouvez pas vous permettre de geler tout le développement pour refaire ce que vous avez construit. Vous n’avez aucune garantie que vous ferez tout correctement cette fois-ci. En revanche, les applications faiblement couplées maintiennent des frontières claires. Elles permettent de remplacer une partie défectueuse sans effet sur le reste du projet. Elles sont plus faciles à construire et à maintenir. Alors pourquoi sont-elles si rares ? Les microservices nous ont promis un découplage lâche, mais nous sommes après leur ère de hype, et des applications ingérables existent toujours. Parfois, c’est même pire, et nous tombons dans le piège du monolithe distribué, traitant les mêmes problèmes qu’auparavant avec une surcharge réseau supplémentaire.## DRY Introduit le Couplage Le couplage fort est courant car on nous enseigne tôt la règle du « Ne vous répétez pas » (DRY). Les règles courtes sont faciles à retenir, mais il est difficile de capturer tous les détails en trois mots. Le Pragmatic Programmer propose une version plus longue : Chaque connaissance doit avoir une représentation unique, non ambiguë et autoritaire au sein d’un système. « Chaque connaissance » est assez extrême. La réponse à la plupart des dilemmes de programmation est « cela dépend », et DRY ne fait pas exception. Lorsque vous faites en sorte que deux choses utilisent une abstraction commune, vous introduisez un couplage. Si vous suivez DRY trop strictement, vous ajoutez des abstractions avant qu’elles ne soient nécessaires.## Être DRY en Go Comparé à d’autres langages modernes, Go est explicite et dépouillé de fonctionnalités. Il n’y a pas beaucoup de sucre syntaxique pour cacher la complexité. Nous sommes habitués aux raccourcis, donc au début, il est difficile d’accepter la verbeuse de Go. C’est comme si nous avions développé un instinct pour trouver des façons « plus intelligentes » d’écrire du code. Le meilleur exemple est la gestion des erreurs. Si vous avez de l’expérience en écriture de Go, ce fragment semble naturel : Pour les nouveaux venus, répéter ces trois lignes encore et encore semble enfreindre la règle DRY. Ils cherchent souvent un moyen d’éviter cette redondance, mais cela ne se termine jamais bien. Finalement, tout le monde accepte que c’est ainsi que Go fonctionne. Cela vous fait vous répéter, mais ce n’est pas la duplication que DRY vous dit d’éviter.## Un Modèle Unique Accouple Votre Application Il y a une fonctionnalité en Go qui introduit un couplage fort et vous fait penser que vous suivez DRY. C’est l’utilisation de plusieurs tags dans une seule structure. Cela semble être une bonne idée car nous utilisons souvent des modèles similaires pour différentes choses. Voici une façon populaire de garder un modèle unique. Cette approche nécessite quelques lignes de code et vous oblige à maintenir une seule structure. Cependant, adapter tout dans un seul modèle nécessite de nombreuses astuces. L’API ne doit pas exposer certains champs, donc ils sont masqués en utilisant . Un seul point de terminaison API utilise le champ, donc l’ORM le saute, et le tag le cache des réponses JSON régulières. Plus important encore, cette solution vous donne l’un des pires problèmes : un couplage fort entre l’API, le stockage et la logique. Lorsque vous voulez mettre à jour quelque chose dans la structure, vous n’avez aucune idée de ce qui peut changer. Vous pouvez rompre le contrat de l’API en modifiant le schéma de la base de données ou corrompre les données stockées lors de la mise à jour des règles de validation. Plus la structure est complexe, plus vous rencontrez de problèmes. Par exemple, le tag signifie JSON, pas HTTP. Que se passe-t-il lorsque vous introduisez des événements qui se marient également en JSON mais avec un format légèrement différent de la réponse API ? Vous continuerez à ajouter des astuces pour que cela fonctionne. Finalement, votre équipe évite tout changement dans la structure car elle ne sait pas ce qui peut casser après y avoir touché.## La Duplication Supprime le Couplage La manière la plus simple de réduire le couplage est d’utiliser des modèles séparés. Nous extrayons la partie spécifique à l’API sous forme de modèles HTTP : Et la partie liée à la base de données sous forme de modèles de stockage : Au début, il semblait que nous utiliserions le même modèle « utilisateur » partout. Maintenant, il est clair que nous avons évité la duplication trop tôt. Les structures API et de stockage sont similaires mais suffisamment différentes pour utiliser des modèles séparés. Dans les applications web, les vues que votre API renvoie (modèles de lecture) ne sont pas les mêmes que celles que vous stockez dans la base de données (modèles d’écriture). Le code de stockage ne doit rien savoir des modèles HTTP, donc nous devons convertir les structures. C’est tout le code dont vous avez besoin : des fonctions qui mappent un type à un autre. Rédiger un tel code banal peut sembler ennuyeux, mais c’est essentiel pour le découplage. Il est tentant de créer une solution générique pour mapper les structures, comme la sérialisation ou l’utilisation de . Résistez. Rédiger le code de base prend moins de temps et d’efforts que de déboguer des cas particuliers de mappage. Les fonctions simples sont faciles à comprendre pour tout le monde dans votre équipe. Les convertisseurs ésotériques seront difficiles à saisir, même pour vous, après un certain temps.## Générer le Code de Base Si vous vous inquiétez d’écrire tout ce code à la main, il existe une manière idiomatique de l’éviter. Utilisez des bibliothèques qui génèrent le code de base pour vous. Vous pouvez générer des choses comme : Modèles HTTP et routes à partir de la définition OpenAPI ( et d’autres bibliothèques). Modèles de base de données et code associé à partir du schéma SQL ( et d’autres ORM). Modèles à partir de fichiers ProtoBuf. Le code généré vous donne des types forts, donc vous ne passez plus à des fonctions génériques. Vous conservez des vérifications à la compilation et n’avez pas besoin d’écrire le code manuellement. Voici à quoi ressemblent les modèles générés. Parfois, vous voudrez peut-être même écrire un outil de génération de code. Ce n’est pas si difficile, et le résultat est un code Go régulier que tout le monde peut lire et comprendre. L’alternative courante est , qui est terrible à comprendre et à déboguer. Bien sûr, d’abord, considérez si l’effort en vaut la peine. Dans la plupart des cas, écrire le code à la main sera assez rapide.## Ne Pas Abuser des Bibliothèques Utilisez le code généré uniquement pour ce qu’il est censé faire. Vous voulez éviter d’écrire le code de base à la main, mais vous devriez quand même conserver quelques modèles dédiés. Ne finissez pas avec l’anti-pattern du modèle unique. Il est facile de tomber dans ce piège lorsque vous voulez suivre DRY. Par exemple, les projets et génèrent du code à partir de requêtes SQL. sqlc permet d’ajouter des tags JSON aux modèles générés et vous permet même de choisir entre et . sqlboiler ajoute , , et tags à tous les modèles par défaut. Il est clair que les gens utilisent ces modèles non seulement pour le stockage. En parcourant les problèmes de sqlc, j’ai trouvé des développeurs demandant encore plus de flexibilité, comme ou . Quelqu’un mentionne même qu’ils ont besoin d’une façon de masquer les champs sensibles dans l’API REST. Tout cela encourage à garder un seul modèle pour de nombreuses responsabilités. Cela vous permet d’écrire moins de code, mais considérez toujours si le couplage en vaut la peine. De même, méfiez-vous de la magie cachée dans les tags de structure. Par exemple, considérez le modèle de permissions que gorm prend en charge : Vous pouvez également utiliser des comparaisons assez complexes en utilisant la bibliothèque , comme référencer d’autres champs : Cela vous fait gagner un peu de temps en écrivant le code, mais vous renoncez aux vérifications à la compilation. Il est facile de faire une faute de frappe dans un tag de structure, et c’est un risque de l’utiliser pour des zones sensibles comme la validation et les autorisations. C’est aussi déroutant pour quiconque n’est pas familier avec la syntaxe ésotérique de la bibliothèque. Je ne veux pas critiquer les bibliothèques mentionnées. Elles ont toutes leurs utilisations, mais ces exemples montrent comment nous avons tendance à pousser DRY à l’extrême pour ne pas avoir à écrire plus de code.## Éviter les Noms de Tag Implicites La plupart des bibliothèques n’exigent pas que les tags soient présents et utilisent les noms de champ par défaut. Lors de la refonte du projet, quelqu’un peut renommer un champ, sans savoir qu’il modifie une réponse API ou un modèle de base de données. S’il n’y a pas de tags, cela peut rompre votre contrat API ou même corrompre votre stockage. Remplissez toujours tous les tags. Même si vous devez taper le même nom deux fois, ce n’est pas contre DRY.## Séparer la Logique des Détails d’Implémentation Découpler l’API du stockage et utiliser des modèles générés est un bon début. Mais nous gardons toujours la validation dans les gestionnaires HTTP. La validation est juste une partie de la logique métier que l’on trouve dans la plupart des applications web. Souvent, il y en aura plus, comme : montrer des champs uniquement dans des cas particuliers, vérifier les autorisations, masquer des champs en fonction du rôle, calculer le prix, prendre des mesures en fonction de quelques facteurs. Mélanger la logique avec les détails d’implémentation (comme la garder dans les gestionnaires HTTP) est un moyen rapide de livrer un MVP. Mais cela introduit également le pire type de dette technique. C’est pourquoi vous vous retrouvez avec un verrouillage du fournisseur et pourquoi vous continuez à ajouter des astuces pour prendre en charge de nouvelles fonctionnalités.## Votre Application Web n’est pas un CRUD Les tutoriels présentent souvent des « CRUD simples », de sorte qu’ils semblent être le bloc de construction de toute application web. C’est un mythe. Si tout ce que votre produit nécessite est un CRUD, vous perdez du temps et de l’argent à l’écrire de zéro. Les frameworks et les outils sans code facilitent le démarrage des CRUD, mais nous payons toujours des développeurs pour construire des logiciels personnalisés. Même Copilot de GitHub ne saura pas comment fonctionne votre produit en dehors du code de base. Ce sont les règles spéciales et les détails étranges qui rendent votre application différente. Ce n’est pas une logique que vous saupoudrez sur les quatre opérations CRUD. C’est le cœur du produit que vous vendez. À l’étape MVP, il est tentant de commencer par un CRUD pour construire rapidement une version fonctionnelle. Mais c’est comme utiliser un tableur au lieu d’un logiciel dédié. Vous obtenez des résultats similaires au début, mais chaque nouvelle fonctionnalité nécessite plus de hacks.## Garder les Choses Simples Disons que vous voulez créer un utilisateur avec un champ ID. L’approche la plus simple peut ressembler à ceci : Ce code fonctionne. Cependant, vous ne pouvez pas dire si la structure est correcte à un moment donné. Vous comptez sur quelque chose pour appeler la validation et gérer correctement l’erreur. Une autre approche suit la bonne vieille encapsulation. Ce fragment est plus explicite et verbeux. Si vous créez un nouveau et ne recevez aucune erreur, vous êtes sûr qu’il est valide. Sinon, vous pouvez facilement mapper l’erreur à une réponse appropriée spécifique à votre API. Quelle que soit l’approche que vous choisissez, vous devez modéliser la complexité essentielle de l’ID de l’utilisateur. Du point de vue de la mise en œuvre pure, garder l’ID dans une chaîne est la solution la plus simple. Go est censé être simple, mais cela ne signifie pas que vous devriez utiliser uniquement des types primitifs. Pour des comportements complexes, utilisez un code qui reflète le fonctionnement du produit. Sinon, vous vous retrouverez avec un modèle simplifié de celui-ci.

Source de l’article


Share

Article précédent
Applications eBPF - User vs Kernel Probes

Articles similaires

Choses étranges apprises en écrivant un émulateur x86
12 juillet 2024
1 min
© 2024, All Rights Reserved.

Liens Rapides

Partenariats et opportunités publicitairesContactez nous

Réseaux Sociaux