Erweitern Sie die vorhandene API um benutzerdefinierte Endpunkte

12

Ich erstelle eine API für mehrere Kunden. Die wichtigsten Endpunkte /userswerden von jedem Kunden verwendet, einige Endpunkte sind jedoch auf individuelle Anpassungen angewiesen. Es kann also sein, dass Benutzer A einen speziellen Endpunkt wünscht /groupsund kein anderer Kunde über diese Funktion verfügt. Nur als Nebenbemerkung würde jeder Kunde aufgrund dieser zusätzlichen Funktionen auch sein eigenes Datenbankschema verwenden.

Ich persönlich benutze NestJs (Express unter der Haube). Das app.moduleregistriert also derzeit alle meine Kernmodule (mit eigenen Endpunkten etc.)

import { Module } from '@nestjs/common';

import { UsersModule } from './users/users.module'; // core module

@Module({
  imports: [UsersModule]
})
export class AppModule {}

Ich denke, dieses Problem hängt nicht mit NestJs zusammen. Wie würden Sie theoretisch damit umgehen?

Ich brauche grundsätzlich eine Infrastruktur, die ein Basissystem bereitstellen kann. Es gibt keine Kernendpunkte mehr, da jede Erweiterung eindeutig ist und mehrere /usersImplementierungen möglich sein könnten. Bei der Entwicklung einer neuen Funktion sollte die Kernanwendung nicht berührt werden. Erweiterungen sollten sich selbst integrieren oder beim Start integriert werden. Das Kernsystem wird ohne Endpunkte geliefert, wird jedoch von diesen externen Dateien erweitert.

Einige Ideen kommen mir in den Sinn


Erste Ansatz:

Jede Erweiterung repräsentiert ein neues Repository. Definieren Sie einen Pfad zu einem benutzerdefinierten externen Ordner, der alle Erweiterungsprojekte enthält. Dieses benutzerdefinierte Verzeichnis würde einen Ordner groupsmit einem enthaltengroups.module

import { Module } from '@nestjs/common';

import { GroupsController } from './groups.controller';

@Module({
  controllers: [GroupsController],
})
export class GroupsModule {}

Meine API könnte dieses Verzeichnis durchlaufen und versuchen, jede Moduldatei zu importieren.

  • Profis:

    1. Der benutzerdefinierte Code wird vom Kern-Repository ferngehalten
  • Nachteile:

    1. NestJs verwendet Typescript, daher muss ich zuerst den Code kompilieren. Wie würde ich den API-Build und die Builds aus den benutzerdefinierten Apps verwalten? (Plug and Play System)

    2. Die benutzerdefinierten Erweiterungen sind sehr locker, da sie nur einige Typoskriptdateien enthalten. Aufgrund der Tatsache, dass sie keinen Zugriff auf das Verzeichnis node_modules der API haben, zeigt mir mein Editor Fehler an, da er externe Paketabhängigkeiten nicht auflösen kann.

    3. Einige Erweiterungen rufen möglicherweise Daten von einer anderen Erweiterung ab. Möglicherweise muss der Gruppendienst auf den Benutzerdienst zugreifen. Hier könnte es schwierig werden.


Zweiter Ansatz: Bewahren Sie jede Erweiterung in einem Unterordner des src-Ordners der API auf. Fügen Sie diesen Unterordner jedoch der .gitignore-Datei hinzu. Jetzt können Sie Ihre Erweiterungen in der API behalten.

  • Profis:

    1. Ihr Editor kann die Abhängigkeiten auflösen

    2. Bevor Sie Ihren Code bereitstellen, können Sie den Build-Befehl ausführen und haben eine einzige Distribution

    3. Sie können leicht auf andere Dienste zugreifen ( /groupsmuss einen Benutzer anhand seiner ID finden)

  • Nachteile:

    1. Bei der Entwicklung müssen Sie Ihre Repository-Dateien in diesen Unterordner kopieren. Nachdem Sie etwas geändert haben, müssen Sie diese Dateien zurückkopieren und Ihre Repository-Dateien mit den aktualisierten überschreiben.

Dritter Ansatz:

In einem externen benutzerdefinierten Ordner sind alle Erweiterungen vollwertige eigenständige APIs. Ihre Haupt-API würde nur das Authentifizierungsmaterial bereitstellen und könnte als Proxy fungieren, um die eingehenden Anforderungen an die Ziel-API umzuleiten.

  • Profis:

    1. Neue Erweiterungen können einfach entwickelt und getestet werden
  • Nachteile:

    1. Die Bereitstellung wird schwierig. Sie haben eine Haupt-API und n Erweiterungs-APIs, die ihren eigenen Prozess starten und einen Port abhören.

    2. Das Proxy-System könnte schwierig sein. Wenn der Client anfordert, dass /usersder Proxy wissen muss, welche Erweiterungs-API auf diesen Endpunkt wartet, ruft er diese API auf und leitet diese Antwort an den Client zurück.

    3. Um die Erweiterungs-APIs zu schützen (die Authentifizierung wird von der Haupt-API durchgeführt), muss der Proxy ein Geheimnis mit diesen APIs teilen. Daher leitet die Erweiterungs-API eingehende Anforderungen nur weiter, wenn dieses übereinstimmende Geheimnis vom Proxy bereitgestellt wird.


Vierter Ansatz:

Microservices könnten helfen. Ich habe einen Leitfaden von hier https://docs.nestjs.com/microservices/basics genommen

Ich könnte einen Microservice für die Benutzerverwaltung, Gruppenverwaltung usw. haben und diese Services nutzen, indem ich eine kleine API / ein Gateway / einen kleinen Proxy erstelle, die diese Microservices aufruft.

  • Profis:

    1. Neue Erweiterungen können einfach entwickelt und getestet werden

    2. Getrennte Bedenken

  • Nachteile:

    1. Die Bereitstellung wird schwierig. Sie haben eine Haupt-API und n Microservices, die ihren eigenen Prozess starten und einen Port abhören.

    2. Es scheint, dass ich für jeden Kunden eine neue Gateway-API erstellen müsste, wenn ich sie anpassbar haben möchte. Anstatt eine Anwendung zu erweitern, müsste ich jedes Mal eine angepasste Comsuming-API erstellen. Das würde das Problem nicht lösen.

    3. Um die Erweiterungs-APIs zu schützen (die Authentifizierung wird von der Haupt-API durchgeführt), muss der Proxy ein Geheimnis mit diesen APIs teilen. Daher leitet die Erweiterungs-API eingehende Anforderungen nur weiter, wenn dieses übereinstimmende Geheimnis vom Proxy bereitgestellt wird.

hrp8sfH4xQ4
quelle
2
Dies könnte helfen, github.com/nestjs/nest/issues/3277
Question3r
Danke für den Link. Aber ich denke nicht, dass ich die benutzerdefinierten Erweiterungen in meinem Code haben sollte. Ich werde prüfen, ob Microservices das Problem lösen wird docs.nestjs.com/microservices/basics
hrp8sfH4xQ4
Ich denke, Ihr Problem hängt eher mit der Autorisierung als mit der Ruhe zusammen.
Adnanmuttaleb
@ adnanmuttaleb würde es dir etwas ausmachen zu erklären warum =?
hrp8sfH4xQ4

Antworten:

6

Hierfür gibt es mehrere Ansätze. Sie müssen nur herausfinden, welcher Workflow für Ihr Team, Ihre Organisation und Ihre Kunden am besten geeignet ist.

Wenn dies nach mir ginge, würde ich in Betracht ziehen, ein Repository pro Modul zu verwenden und einen Paketmanager wie NPM mit privaten oder organisationsbezogenen Paketen zu verwenden, um die Konfiguration durchzuführen. Richten Sie dann Build-Release-Pipelines ein, die bei neuen Builds auf das Paket-Repo übertragen werden.

Auf diese Weise benötigen Sie lediglich die Hauptdatei und eine Paketmanifestdatei pro benutzerdefinierter Installation. Sie können unabhängig voneinander neue Versionen entwickeln und bereitstellen und bei Bedarf neue Versionen auf der Clientseite laden.

Für mehr Glätte können Sie eine Konfigurationsdatei verwenden, um Module Routen zuzuordnen und ein generisches Routengeneratorskript zu schreiben, um den größten Teil des Bootstrappings durchzuführen.

Da ein Paket alles sein kann, funktionieren Abhängigkeiten innerhalb der Pakete ohne großen Aufwand. Sie müssen nur diszipliniert sein, wenn es um Änderungen und Versionsverwaltung geht.

Weitere Informationen zu privaten Paketen finden Sie hier: Private Pakete NPM

Jetzt kosten private NPM-Registrierungen Geld, aber wenn dies ein Problem ist, gibt es auch mehrere andere Optionen. In diesem Artikel finden Sie einige Alternativen - sowohl kostenlos als auch kostenpflichtig.

Möglichkeiten, Ihre private npm-Registrierung zu haben

Wenn Sie nun Ihren eigenen Manager rollen möchten, können Sie einen einfachen Service Locator schreiben, der eine Konfigurationsdatei enthält, die die erforderlichen Informationen enthält, um den Code aus dem Repo abzurufen, ihn zu laden und dann eine Methode zum Abrufen eines Codes bereitzustellen Instanz dazu.

Ich habe eine einfache Referenzimplementierung für ein solches System geschrieben:

Das Framework: Locomotion Service Locator

Ein Beispiel für die Plugin-Überprüfung auf Palindrome: Beispiel für ein Fortbewegungs-Plugin

Eine Anwendung, die das Framework zum Auffinden von Plugins verwendet: Beispiel für eine Fortbewegungs-App

Sie können damit herumspielen, indem npm install -s locomotionSie es von npm abrufen. Verwenden Sie dazu eine plugins.jsonDatei mit dem folgenden Schema:

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

Beispiel:

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

lade es so: const loco = require ("locomotion");

Anschließend wird ein Versprechen zurückgegeben, mit dem das Service Locator-Objekt aufgelöst wird, das über die Locator-Methode verfügt, um Ihre Services abzurufen:

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);
});

Bitte beachten Sie, dass dies nur eine Referenzimplementierung ist und für eine ernsthafte Anwendung nicht robust genug ist. Das Muster ist jedoch weiterhin gültig und zeigt den Kern des Schreibens dieser Art von Framework.

Dies müsste nun um Unterstützung für Plugin-Konfiguration, Initialisierungen, Fehlerprüfung, möglicherweise Unterstützung für Abhängigkeitsinjektion usw. erweitert werden.

Espen
quelle
Vielen Dank. Es treten zwei Probleme auf, anscheinend verlassen wir uns dann auf npm und müssen eine selbst gehostete Registrierung einrichten. Die zweite Sache ist, dass private npm nicht mehr kostenlos ist. Ich hatte gehofft, eine grundlegende technische Lösung zu finden. Aber +1 für die Idee :)
hrp8sfH4xQ4
1
fügte eine Referenzimplementierung einer rudimentären Lösung für diese Art von System hinzu.
Espen
3

Ich würde für externe Pakete Option gehen.

Sie können Ihre App so strukturieren, dass sie einen packagesOrdner hat. Ich hätte UMD kompilierte Builds externer Pakete in diesem Ordner, damit Ihr kompiliertes Typoskript keine Probleme mit den Paketen hat. Alle Pakete sollten eine index.jsDatei im Stammordner jedes Pakets haben.

Und Ihre App kann mit fsund requireallen Paketen index.jsin Ihrer App eine Schleife durch den Paketordner ausführen .

Andererseits müssen Sie sich um die Installation von Abhängigkeiten kümmern. Ich denke, eine Konfigurationsdatei auf jedem Paket könnte das auch lösen. Sie können ein benutzerdefiniertes npmSkript für die Hauptanwendung verwenden, um alle Paketabhängigkeiten zu installieren, bevor Sie die Anwendung starten.

Auf diese Weise können Sie Ihrer App einfach neue Pakete hinzufügen, indem Sie das Paket kopieren, in den Paketordner einfügen und die App neu starten. Ihre kompilierten Typoskriptdateien werden nicht berührt und Sie müssen kein privates npm für Ihre eigenen Pakete verwenden.

Kalesh Kaladharan
quelle
Danke für deine Antwort. Ich denke das klingt wie die Lösung @ Espen bereits gepostet, nein?
hrp8sfH4xQ4
@ hrp8sfH4xQ4 Ja, bis zu einem gewissen Grad. Ich habe es hinzugefügt, nachdem ich Ihren Kommentar gelesen habe, dass ich ihn nicht verwenden möchte npm. Oben finden Sie eine Lösung, mit der Sie ein privates npm-Konto vermeiden können. Darüber hinaus müssen Sie meiner Meinung nach keine Pakete hinzufügen, die von jemandem außerhalb Ihrer Organisation erstellt wurden. Recht?
Kalesh Kaladharan
Übrigens. aber es wäre großartig, das auch zu unterstützen ... irgendwie ...
hrp8sfH4xQ4
Wenn Sie die Option zum Hinzufügen von Paketen von Drittanbietern npmbenötigen, können Sie dies oder einen anderen Paketmanager tun. In solchen Fällen reicht meine Lösung nicht aus.
Kalesh Kaladharan
Wenn es Ihnen nichts ausmacht, würde ich gerne warten, um so viele Ansätze wie möglich zu sammeln
hrp8sfH4xQ4