À un moment donné, chaque entreprise arrive à un carrefour où elle doit s'arrêter et réévaluer les outils qu'elle utilise. Pour nous, ce moment est arrivé lorsque nous avons réalisé que l'API qui alimentait notre tableau de bord Web était devenue ingérable, difficile à tester et ne répondait pas aux normes que nous avions fixées pour notre base de code.
Arcjet est avant tout un SDK de sécurité en tant que code pour aider les développeurs à mettre en œuvre des fonctionnalités de sécurité telles que la détection de robots, la validation des e-mails et la détection des informations personnelles. Celui-ci communique avec notre API gRPC de décision hautes performances et à faible latence.
Notre tableau de bord Web utilise une API REST distincte principalement pour gérer les connexions au site et examiner les analyses des demandes traitées, mais cela inclut également l'inscription de nouveaux utilisateurs et la gestion de leur compte, ce qui signifie qu'il s'agit toujours d'une partie importante du produit.
Capture d'écran de la page d'analyse des demandes du tableau de bord Arcjet.
Nous avons donc décidé de relever le défi de reconstruire notre API à partir de zéro, cette fois en mettant l'accent sur la maintenabilité, les performances et l'évolutivité. Cependant, nous ne voulions pas nous lancer dans un énorme projet de réécriture - qui ne fonctionne jamais bien - au lieu de cela, nous avons décidé de construire une nouvelle fondation, puis de commencer avec un seul point de terminaison d'API.
Dans cet article, je vais expliquer comment nous avons abordé cela.
Lorsque la vitesse était notre priorité absolue, Next.js fournissait la solution la plus pratique pour créer des points de terminaison d'API que notre frontend pouvait consommer. Cela nous a permis un développement full-stack transparent au sein d’une seule base de code, et nous n’avons pas eu à trop nous soucier de l’infrastructure car nous avons déployé sur Vercel.
Nous nous sommes concentrés sur notre sécurité en tant que SDK de code et API de décision à faible latence donc pour le tableau de bord frontend, cette pile nous a permis de prototyper rapidement des fonctionnalités avec peu de friction.
Notre pile : Next.js, DrizzleORM, useSWR, NextAuth
Cependant, à mesure que notre produit évoluait, nous avons constaté que la combinaison de tous nos points de terminaison API et de notre code frontend dans le même projet conduisait à un désordre enchevêtré.
Tester notre API est devenu fastidieux (et est très difficile à faire avec Next.js de toute façon), et nous avions besoin d'un système capable de gérer à la fois interne et externe consommation. Au fur et à mesure que nous avons intégré davantage de plates-formes (comme Vercel, Fly.io et Netlify), nous avons réalisé que la vitesse de développement à elle seule n'était pas suffisante. Nous avions besoin d'une solution plus robuste.
Dans le cadre de ce projet, nous souhaitions également répondre à un problème de sécurité persistant concernant la manière dont Vercel vous oblige à exposer publiquement votre base de données. À moins que vous ne payiez pour leur « calcul sécurisé » d'entreprise, la connexion à une base de données distante nécessite qu'elle dispose d'un point de terminaison public. Nous préférons verrouiller notre base de données afin qu'elle ne soit accessible que via un réseau privé. La défense en profondeur est importante et ce serait une autre couche de protection.
Cela nous a amené à décider de dissocier l'interface utilisateur frontend de l'API backend.
Qu'est-ce que « l'API Golden » ? Il ne s'agit pas d'une technologie ou d'un framework spécifique, mais plutôt d'un ensemble idéal de principes qui définissent une API bien construite. Bien que les développeurs puissent avoir leurs propres préférences en matière de langages et de frameworks, il existe certains concepts valables dans toutes les piles technologiques sur lesquels la plupart peuvent s'entendre pour créer une API de haute qualité.
Nous avons déjà de l'expérience dans la fourniture d'API hautes performances.Notre API de décision est déployée à proximité de nos clients, utilise Kubernetes pour évoluer de manière dynamique et est optimisée pour les réponses à faible latence. .
Nous avons envisagé des environnements sans serveur et d'autres fournisseurs, mais avec nos clusters k8s existants déjà opérationnels, il était plus logique de réutiliser l'infrastructure en place : déploiements via Octopus Deploy, surveillance via Grafana Jaeger, Loki, Prometheus, etc.
Après une courte préparation interne de Rust vs Go, nous avons choisi Go pour sa simplicité, sa rapidité et la façon dont il atteint ses objectifs initiaux d'un excellent support pour la création de services réseau évolutifs . Nous l'utilisons également déjà pour l'API de décision et comprenons comment faire fonctionner les API Go, ce qui a finalisé la décision pour nous.
Le passage de l'API backend à Go a été simple grâce à sa simplicité et à la disponibilité d'excellents outils. Mais il y avait un problème : nous gardions l'interface Next.js et ne voulions pas écrire manuellement des types TypeScript ni maintenir une documentation séparée pour notre nouvelle API.
Entrez OpenAPI, une solution parfaitement adaptée à nos besoins. OpenAPI nous permet de définir un contrat entre le frontend et le backend, tout en nous servant également de documentation. Cela résout le problème de la maintenance d'un schéma pour les deux côtés de l'application.
L'intégration de l'authentification dans Go n'a pas été trop difficile, grâce au fait que NextAuth est relativement simple à imiter sur le backend. NextAuth (maintenant Auth.js) dispose d'API disponibles pour vérifier une session.
Cela signifiait que nous pouvions avoir un client TypeScript dans le frontend généré à partir de notre spécification OpenAPI effectuant des appels de récupération à l'API backend. Les informations d'identification sont automatiquement incluses dans l'appel de récupération et le backend peut vérifier la session avec NextAuth.
Écrire tout type de tests dans Go est très simple et il existe de nombreux exemples traitant du sujet des tests des gestionnaires HTTP.
Il est également beaucoup plus facile d'écrire des tests pour les nouveaux points de terminaison de l'API Go par rapport à l'API Next.js, notamment parce que nous souhaitons tester l'état authentifié et les appels réels à la base de données. Nous avons pu facilement écrire des tests pour le Routeur Gin et réaliser de véritables tests d'intégration sur notre base de données Postgres à l'aide de Testcontainers.
Nous avons commencé par écrire la spécification OpenAPI 3.0 pour notre API. Une approche OpenAPI favorise la conception du contrat API avant la mise en œuvre, garantissant que toutes les parties prenantes (développeurs, chefs de produit et clients) s'accordent sur le comportement et la structure de l'API avant l'écriture de tout code. Il encourage une planification minutieuse et aboutit à une conception d’API bien pensée, cohérente et conforme aux meilleures pratiques établies. Ce sont les raisons pour lesquelles nous avons choisi d'écrire d'abord la spécification et de générer le code à partir de celle-ci, et non l'inverse.
Mon outil de choix pour cela étaitAPI Fiddle, qui vous aide à rédiger et tester rapidement les spécifications OpenAPI. Cependant, API Fiddle ne prend en charge qu'OpenAPI 3.1 (que nous n'avons pas pu utiliser car de nombreuses bibliothèques ne l'avaient pas adopté), nous sommes donc restés avec la version 3.0 et avons écrit la spécification à la main.
Voici un exemple de ce à quoi ressemblaient les spécifications de notre API :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
Avec la spécification OpenAPI en place, nous avons utilisé OAPI-codegen, un outil qui génère automatiquement du code Go à partir de la spécification OpenAPI. Il génère tous les types, gestionnaires et structures de gestion des erreurs nécessaires, ce qui rend le processus de développement beaucoup plus fluide.
1 |
|
Le résultat était un ensemble de fichiers Go, l'un contenant le squelette du serveur et l'autre avec les implémentations du gestionnaire. Voici un exemple de type Go généré pour l'objet Site :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Avec le code généré en place, nous avons pu implémenter la logique du gestionnaire d'API, comme ceci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
Drizzle est un excellent ORM pour les projets JS et nous l'utiliserions à nouveau, mais déplacer le code de la base de données hors de Next.js signifiait que nous avions besoin de quelque chose de similaire pour Go.
Nous avons choisi GORM comme ORM et utilisé le Repository Pattern pour résumer les interactions avec la base de données. Cela nous a permis d'écrire des requêtes de base de données propres et testables.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Les tests sont essentiels pour nous. Nous voulions nous assurer que tous les appels à la base de données étaient correctement testés. Nous avons donc utilisé Testcontainers pour créer une véritable base de données pour nos tests, reflétant fidèlement notre configuration de production.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
Après avoir configuré l'environnement de test, nous avons testé toutes les opérations CRUD comme nous le ferions en production, en nous assurant que notre code se comporte correctement.
1 |
|
Pour tester nos gestionnaires d'API, nous avons utilisé le package httptest de Go et simulé les interactions de la base de données à l'aide de Mockery. Cela nous a permis de nous concentrer sur le test de la logique de l'API sans nous soucier des problèmes de base de données.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Une fois notre API testée et déployée, nous avons tourné notre attention vers le frontend.
Nos appels d'API précédents ont été effectués à l'aide de l'AP de récupération recommandé par Next.jsI avec mise en cache intégrée. Pour des vues plus dynamiques, certains composants utilisaient SWR en plus de la récupération, nous avons donc pourrait recevoir des appels de récupération de données à rechargement automatique et de type sécurisé.
Pour consommer l'API sur le frontend, nous avons utilisé la bibliothèque openapi-typescript, qui génère des types TypeScript basés sur notre schéma OpenAPI. Cela a facilité l'intégration du backend avec notre frontend sans avoir à synchroniser manuellement les modèles de données. Il intègre Tanstack Query, qui utilise la récupération sous le capot, mais se synchronise également avec notre schéma.
Nous migrons progressivement les points de terminaison de l'API vers le nouveau serveur Go et apportons de petites améliorations en cours de route. Si vous ouvrez l'inspecteur de votre navigateur, vous verrez ces nouvelles requêtes envoyées à api.arcjet.com
Capture d'écran de l'inspecteur du navigateur montrant un appel d'API vers le nouveau backend Go.
Alors, avons-nous atteint l'insaisissable API Golden ? Cochons la case :
Nous sommes allés plus loin avec :
Au final, nous sommes satisfaits des résultats. Notre API est plus rapide, plus sécurisée et mieux testée. La transition vers Go en valait la peine et nous sommes désormais mieux placés pour faire évoluer et maintenir notre API à mesure que notre produit se développe.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!