Ich erstelle eine API für mehrere Kunden. Die wichtigsten Endpunkte /users
werden von jedem Kunden verwendet, einige Endpunkte sind jedoch auf individuelle Anpassungen angewiesen. Es kann also sein, dass Benutzer A einen speziellen Endpunkt wünscht /groups
und 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.module
registriert 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 /users
Implementierungen 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 groups
mit 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:
- Der benutzerdefinierte Code wird vom Kern-Repository ferngehalten
Nachteile:
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)
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.
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:
Ihr Editor kann die Abhängigkeiten auflösen
Bevor Sie Ihren Code bereitstellen, können Sie den Build-Befehl ausführen und haben eine einzige Distribution
Sie können leicht auf andere Dienste zugreifen (
/groups
muss einen Benutzer anhand seiner ID finden)
Nachteile:
- 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:
- Neue Erweiterungen können einfach entwickelt und getestet werden
Nachteile:
Die Bereitstellung wird schwierig. Sie haben eine Haupt-API und n Erweiterungs-APIs, die ihren eigenen Prozess starten und einen Port abhören.
Das Proxy-System könnte schwierig sein. Wenn der Client anfordert, dass
/users
der Proxy wissen muss, welche Erweiterungs-API auf diesen Endpunkt wartet, ruft er diese API auf und leitet diese Antwort an den Client zurück.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:
Neue Erweiterungen können einfach entwickelt und getestet werden
Getrennte Bedenken
Nachteile:
Die Bereitstellung wird schwierig. Sie haben eine Haupt-API und n Microservices, die ihren eigenen Prozess starten und einen Port abhören.
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.
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.
Antworten:
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 locomotion
Sie es von npm abrufen. Verwenden Sie dazu eineplugins.json
Datei mit dem folgenden Schema:Beispiel:
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:
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.
quelle
Ich würde für externe Pakete Option gehen.
Sie können Ihre App so strukturieren, dass sie einen
packages
Ordner 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 eineindex.js
Datei im Stammordner jedes Pakets haben.Und Ihre App kann mit
fs
undrequire
allen Paketenindex.js
in 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
npm
Skript 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.
quelle
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?npm
benötigen, können Sie dies oder einen anderen Paketmanager tun. In solchen Fällen reicht meine Lösung nicht aus.