Je crée une API pour plusieurs clients. Les points de terminaison de base comme /users
sont utilisés par chaque client, mais certains points de terminaison reposent sur une personnalisation individuelle. Il se peut donc que l' utilisateur A veuille un point de terminaison spécial /groups
et aucun autre client n'aura cette fonctionnalité. Tout comme un sidenote , chaque client utiliserait également son propre schéma de base de données en raison de ces fonctionnalités supplémentaires.
J'utilise personnellement NestJs (Express sous le capot). Donc, le app.module
enregistre actuellement tous mes modules de base (avec leurs propres points de terminaison, etc.)
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module'; // core module
@Module({
imports: [UsersModule]
})
export class AppModule {}
Je pense que ce problème n'est pas lié aux NestJ, alors comment gérez-vous cela en théorie?
J'ai essentiellement besoin d'une infrastructure capable de fournir un système de base. Il n'y a plus de noeuds finaux principaux car chaque extension est unique et plusieurs /users
implémentations pourraient être possibles. Lors du développement d'une nouvelle fonctionnalité, l'application principale ne doit pas être touchée. Les extensions doivent s'intégrer ou doivent être intégrées au démarrage. Le système principal est livré sans point d'extrémité mais sera étendu à partir de ces fichiers externes.
Quelques idées me viennent à l'esprit
Première approche:
Chaque extension représente un nouveau référentiel. Définissez un chemin d'accès à un dossier externe personnalisé contenant tous ces projets d'extension. Ce répertoire personnalisé contiendrait un dossier groups
avec ungroups.module
import { Module } from '@nestjs/common';
import { GroupsController } from './groups.controller';
@Module({
controllers: [GroupsController],
})
export class GroupsModule {}
Mon API peut parcourir ce répertoire et essayer d'importer chaque fichier de module.
avantages:
- Le code personnalisé est conservé à l'écart du référentiel principal
les inconvénients:
NestJs utilise Typescript, je dois donc d'abord compiler le code. Comment gérer la version d'API et les versions à partir des applications personnalisées? (Système plug and play)
Les extensions personnalisées sont très lâches car elles ne contiennent que des fichiers dactylographiés. Du fait qu'ils n'ont pas accès au répertoire node_modules de l'API, mon éditeur me montrera des erreurs car il ne peut pas résoudre les dépendances de packages externes.
Certaines extensions peuvent extraire des données d'une autre extension. Peut-être que le service des groupes doit accéder au service des utilisateurs. Les choses pourraient devenir délicates ici.
Deuxième approche: conserver chaque extension dans un sous-dossier du dossier src de l'API. Mais ajoutez ce sous-dossier au fichier .gitignore. Vous pouvez maintenant conserver vos extensions dans l'API.
avantages:
Votre éditeur est capable de résoudre les dépendances
Avant de déployer votre code, vous pouvez exécuter la commande build et disposer d'une seule distribution
Vous pouvez accéder facilement à d'autres services (
/groups
besoin de trouver un utilisateur par identifiant)
les inconvénients:
- Lors du développement, vous devez copier vos fichiers de référentiel dans ce sous-dossier. Après avoir changé quelque chose, vous devez recopier ces fichiers et remplacer vos fichiers de référentiel par ceux mis à jour.
Troisième approche:
Dans un dossier personnalisé externe, toutes les extensions sont des API autonomes à part entière. Votre API principale fournirait simplement les éléments d'authentification et pourrait agir comme un proxy pour rediriger les demandes entrantes vers l'API cible.
avantages:
- De nouvelles extensions peuvent être développées et testées facilement
les inconvénients:
Le déploiement sera délicat. Vous aurez une API principale et n API d'extension démarrant leur propre processus et écoutant un port.
Le système proxy pourrait être délicat. Si le client demande
/users
au proxy de savoir quelle API d'extension écoute pour ce point de terminaison, appelle cette API et retransmet cette réponse au client.Pour protéger les API d'extension (l'authentification est gérée par l'API principale), le proxy doit partager un secret avec ces API. L'API d'extension ne transmettra donc les demandes entrantes que si le secret correspondant est fourni par le proxy.
Quatrième approche:
Les microservices pourraient vous aider. J'ai pris un guide d'ici https://docs.nestjs.com/microservices/basics
Je pourrais avoir un microservice pour la gestion des utilisateurs, la gestion de groupe, etc. et consommer ces services en créant un petit api / passerelle / proxy qui appelle ces microservices.
avantages:
De nouvelles extensions peuvent être développées et testées facilement
Préoccupations séparées
les inconvénients:
Le déploiement sera délicat. Vous disposerez d'une API principale et de n microservices démarrant leur propre processus et écoutant un port.
Il semble que je devrais créer une nouvelle API de passerelle pour chaque client si je veux la personnaliser. Au lieu d'étendre une application, je devrais donc créer à chaque fois une API de consommation personnalisée. Cela ne résoudrait pas le problème.
Pour protéger les API d'extension (l'authentification est gérée par l'API principale), le proxy doit partager un secret avec ces API. L'API d'extension ne transmettra donc les demandes entrantes que si le secret correspondant est fourni par le proxy.
Réponses:
Il existe plusieurs approches à cela. Ce que vous devez faire est de déterminer quel flux de travail convient le mieux à votre équipe, à votre organisation et à vos clients.
Si cela ne tenait qu'à moi, j'envisagerais d'utiliser un référentiel par module, et d'utiliser un gestionnaire de packages comme NPM avec des packages privés ou organisationnels pour gérer la configuration. Ensuite, configurez des pipelines de publication de build qui poussent vers le référentiel de packages sur les nouvelles builds.
De cette façon, tout ce dont vous avez besoin est le fichier principal et un fichier manifeste de package par installation personnalisée. Vous pouvez développer et déployer de nouvelles versions indépendamment, et vous pouvez charger de nouvelles versions lorsque vous en avez besoin côté client.
Pour plus de fluidité, vous pouvez utiliser un fichier de configuration pour mapper les modules aux routes et écrire un script générique de générateur de routes pour effectuer la plupart des amorçages.
Puisqu'un package peut être n'importe quoi, les dépendances croisées au sein des packages fonctionneront sans trop de tracas. Il vous suffit d'être discipliné en matière de gestion des modifications et des versions.
En savoir plus sur les packages privés ici: Packages privés NPM
Désormais, les registres privés NPM coûtent de l'argent, mais si cela pose problème, il existe également plusieurs autres options. Veuillez consulter cet article pour quelques alternatives - gratuites et payantes.
Façons d'avoir votre registre npm privé
Maintenant, si vous voulez faire rouler votre propre gestionnaire, vous pouvez écrire un localisateur de service simple, qui prend un fichier de configuration contenant les informations nécessaires pour extraire le code du référentiel, le charger, puis fournir une sorte de méthode pour récupérer un par exemple.
J'ai écrit une implémentation de référence simple pour un tel système:
Le cadre: localisateur de services de locomotion
Un exemple de plugin vérifiant les palindromes: exemple de plugin de locomotion
Une application utilisant le framework pour localiser les plugins: exemple d'application locomotion
Vous pouvez jouer avec cela en le récupérant à partir de npm en utilisant
npm install -s locomotion
vous devrez spécifier unplugins.json
fichier avec le schéma suivant:exemple:
chargez-le comme ceci: const loco = require ("locomotion");
Il retourne ensuite une promesse qui résoudra l'objet localisateur de service, qui a la méthode locator pour obtenir vos services:
Veuillez noter qu'il ne s'agit que d'une implémentation de référence et qu'il n'est pas suffisamment robuste pour une application sérieuse. Cependant, le modèle est toujours valide et montre l'essentiel de l'écriture de ce type de cadre.
Maintenant, cela devrait être étendu avec la prise en charge de la configuration du plugin, les initialisations, la vérification des erreurs, peut-être ajouter la prise en charge de l'injection de dépendances, etc.
la source
J'irais pour l'option de packages externes.
Vous pouvez structurer votre application pour avoir un
packages
dossier. J'aurais des compilations UMD compilées de packages externes dans ce dossier afin que votre typage compilé n'ait aucun problème avec les packages. Tous les packages doivent avoir unindex.js
fichier dans le dossier racine de chaque package.Et votre application peut exécuter une boucle dans le dossier des packages à l'aide de
fs
et derequire
tous les packagesindex.js
dans votre application.Ensuite, l'installation des dépendances est quelque chose dont vous devez vous occuper. Je pense qu'un fichier de configuration sur chaque paquet pourrait résoudre cela aussi. Vous pouvez avoir un
npm
script personnalisé sur l'application principale pour installer toutes les dépendances du package avant de démarrer l'application.De cette façon, vous pouvez simplement ajouter de nouveaux packages à votre application en copiant coller le package dans le dossier packages et en redémarrant l'application. Vos fichiers dactylographiés compilés ne seront pas touchés et vous n'avez pas besoin d'utiliser npm privé pour vos propres packages.
la source
npm
. Ci-dessus est une solution que vous pouvez faire pour éviter un compte npm privé. De plus, je pense que vous n'avez pas besoin d'ajouter des packages créés par quelqu'un en dehors de votre organisation. Droite?npm
c'est la façon de le faire ou tout autre gestionnaire de packages. Dans de tels cas, ma solution ne suffira pas.