étendre l'API existante avec des points de terminaison personnalisés

12

Je crée une API pour plusieurs clients. Les points de terminaison de base comme /userssont 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 /groupset 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.moduleenregistre 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 /usersimplé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 groupsavec 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:

    1. Le code personnalisé est conservé à l'écart du référentiel principal
  • les inconvénients:

    1. 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)

    2. 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.

    3. 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:

    1. Votre éditeur est capable de résoudre les dépendances

    2. Avant de déployer votre code, vous pouvez exécuter la commande build et disposer d'une seule distribution

    3. Vous pouvez accéder facilement à d'autres services ( /groupsbesoin de trouver un utilisateur par identifiant)

  • les inconvénients:

    1. 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:

    1. De nouvelles extensions peuvent être développées et testées facilement
  • les inconvénients:

    1. 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.

    2. Le système proxy pourrait être délicat. Si le client demande /usersau proxy de savoir quelle API d'extension écoute pour ce point de terminaison, appelle cette API et retransmet cette réponse au client.

    3. 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:

    1. De nouvelles extensions peuvent être développées et testées facilement

    2. Préoccupations séparées

  • les inconvénients:

    1. 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.

    2. 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.

    3. 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.

hrp8sfH4xQ4
la source
2
cela pourrait aider github.com/nestjs/nest/issues/3277
Question3r
Merci pour le lien. Mais je ne pense pas que je devrais avoir les extensions personnalisées dans mon code. Je vais vérifier si les microservices résoudront le problème docs.nestjs.com/microservices/basics
hrp8sfH4xQ4
Je pense que votre problème est lié à l'autorisation plutôt qu'au repos.
adnanmuttaleb
@ adnanmuttaleb voudriez-vous expliquer pourquoi =?
hrp8sfH4xQ4

Réponses:

6

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 locomotionvous devrez spécifier un plugins.jsonfichier avec le schéma suivant:

{
    "path": "relative path where plugins should be stored",
    "plugins": [
        { 
           "module":"name of service", 
           "dir":"location within plugin folder",
           "source":"link to git repository"
        }
    ]
}

exemple:

{
    "path": "./plugins",
    "plugins": [
        {
            "module": "palindrome",
            "dir": "locomotion-plugin-example",
            "source": "https://github.com/drcircuit/locomotion-plugin-example.git"
        }
    ]
}

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:

loco.then((svc) => {
    let pal = svc.locate("palindrome"); //get the palindrome service
    if (pal) {
        console.log("Is: no X in Nixon! a palindrome? ", (pal.isPalindrome("no X in Nixon!")) ? "Yes" : "no"); // test if it works :)
    }
}).catch((err) => {
    console.error(err);
});

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.

Espen
la source
Merci. Deux problèmes surviennent, il semble que nous comptions sur npm à ce moment-là et que nous devions configurer un registre auto-hébergé. La deuxième chose est que le npm privé n'est plus gratuit. J'espérais trouver une solution technique de base. Mais +1 pour l'idée :)
hrp8sfH4xQ4
1
ajout d'une implémentation de référence d'une solution rudimentaire pour ce type de système.
Espen
3

J'irais pour l'option de packages externes.

Vous pouvez structurer votre application pour avoir un packagesdossier. 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 un index.jsfichier dans le dossier racine de chaque package.

Et votre application peut exécuter une boucle dans le dossier des packages à l'aide de fset de requiretous les packages index.jsdans 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 npmscript 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.

Kalesh Kaladharan
la source
Merci pour votre réponse. Je pense que cela ressemble à la solution @ Espen déjà publiée, non?
hrp8sfH4xQ4
@ hrp8sfH4xQ4 Oui, dans une large mesure. Je l'ai ajouté après avoir lu votre commentaire sur le fait de ne pas vouloir l'utiliser 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?
Kalesh Kaladharan
btw. mais ce serait génial de supporter ça aussi ... en quelque sorte ...
hrp8sfH4xQ4
Si vous avez besoin d'une option pour ajouter des packages tiers, alors npmc'est la façon de le faire ou tout autre gestionnaire de packages. Dans de tels cas, ma solution ne suffira pas.
Kalesh Kaladharan
Si cela ne vous dérange pas, j'aimerais attendre de collecter autant d'approches que possible
hrp8sfH4xQ4