我正在为多个客户创建一个API。每个客户都使用/users
之类的核心端点,但是某些端点依赖于单独的自定义。因此,用户A 可能需要一个特殊的端点/groups
,而其他客户将没有该功能。 作为附带说明,由于这些额外功能,每个客户也将使用自己的数据库架构。
我个人使用NestJs(引擎盖下的Express)。因此app.module
当前注册了我所有的核心模块(带有自己的端点等)
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module'; // core module
@Module({
imports: [UsersModule]
})
export class AppModule {}
我认为这个问题与NestJs无关,那么您在理论上将如何处理?
我基本上需要一个能够提供基本系统的基础结构。由于每个扩展都是唯一的,并且可能有多种/users
实现,因此不再有核心端点。开发新功能时,不应触摸核心应用程序。扩展应自行集成或在启动时进行集成。核心系统没有端点,但将从这些外部文件扩展。
我想到了一些主意
第一种方法:
每个扩展名代表一个新的存储库。定义一个包含所有扩展项目的自定义外部文件夹的路径。此自定义目录将包含一个文件夹groups
和一个groups.module
import { Module } from '@nestjs/common';
import { GroupsController } from './groups.controller';
@Module({
controllers: [GroupsController],
})
export class GroupsModule {}
我的API可能会遍历该目录并尝试导入每个模块文件。
优点:
缺点:
NestJs使用Typescript,因此我必须先编译代码。我将如何管理API构建以及来自自定义应用的构建? (即插即用系统)
自定义扩展非常宽松,因为它们仅包含一些打字稿文件。由于他们无权访问API的node_modules目录,因此我的编辑器将向我显示错误,因为它无法解析外部程序包依赖性。
某些扩展名可能会从另一个扩展名中获取数据。群组服务可能需要访问用户服务。这里的事情可能会变得棘手。
第二种方法: 将每个扩展名保留在API的src文件夹的子文件夹中。但是将此子文件夹添加到.gitignore文件中。现在,您可以将扩展保留在API中。
优点:
您的编辑器能够解决依赖关系
在部署代码之前,您可以运行build命令,并且将具有单个发行版
您可以轻松访问其他服务(/groups
需要通过ID查找用户)
缺点:
第三种方法:
在外部自定义文件夹中,所有扩展都是完全独立的API。您的主要API仅提供身份验证内容,并可以充当代理,将传入的请求重定向到目标API。
优点:
缺点:
部署将很棘手。您将拥有一个主要的API和 n 个扩展API,它们将开始各自的进程并监听端口。
代理系统可能很棘手。如果客户端请求/users
,则代理需要知道哪个扩展API侦听该终结点,然后调用该API并将该响应转发回客户端。
为保护扩展API(身份验证由主API处理),代理需要与这些API共享机密。因此,如果从代理服务器提供了匹配的机密,扩展API将仅传递传入的请求。
第四种方法:
微服务可能会有所帮助。我从这里https://docs.nestjs.com/microservices/basics
我可以拥有一个用于用户管理,组管理等的微服务,并通过创建一个调用这些微服务的小型api /网关/代理来使用这些服务。
优点:
可以轻松开发和测试新扩展
分离的问题
缺点:
部署将很棘手。您将拥有一个主要的API和 n 个微服务,以启动它们自己的进程并监听端口。
如果我想对其进行自定义,则似乎必须为每个客户创建一个新的网关api。因此,除了扩展应用程序外,我每次都必须创建一个自定义的消费API。那不会解决问题。
为保护扩展API(身份验证由主API处理),代理需要与这些API共享机密。因此,如果从代理服务器提供了匹配的机密,扩展API将仅传递传入的请求。
答案 0 :(得分:5)
有几种方法。您需要做的是找出最适合您的团队,组织和客户的工作流程。
如果这取决于我,我将考虑每个模块使用一个存储库,并使用像NPM这样的软件包管理器以及私有或组织范围的软件包来处理配置。然后设置构建发布管道,以推送到新构建的软件包仓库。
这样,您所需要的就是主文件和每个自定义安装的软件包清单文件。您可以独立开发和部署新版本,也可以在需要时在客户端上加载新版本。
要获得更高的平滑度,您可以使用配置文件将模块映射到路由,并编写通用的路由生成器脚本来执行大多数引导。
由于一个包可以是任何东西,所以包内的交叉依赖将很容易解决。您只需要在更改和版本管理方面受到约束即可。
在此处详细了解私有软件包: Private Packages NPM
现在,私人NPM注册管理机构要花钱,但是如果这是一个问题,那么还有其他几种选择。请查看本文以了解其他选择-免费和付费。
Ways to have your private npm registry
现在,如果您要推出自己的管理器,则可以编写一个简单的服务定位器,该服务定位器将使用一个包含必要信息的配置文件,以从存储库中提取代码,进行加载,然后提供某种方法为其检索实例。
我已经为这种系统编写了一个简单的参考实现:
检查回文的插件示例:locomotion plugin example
使用该框架查找插件的应用程序:locomotion app example
您可以通过使用npm install -s locomotion
从npm获取它来解决此问题,您需要使用以下模式指定一个plugins.json
文件:
{
"path": "relative path where plugins should be stored",
"plugins": [
{
"module":"name of service",
"dir":"location within plugin folder",
"source":"link to git repository"
}
]
}
示例:
{
"path": "./plugins",
"plugins": [
{
"module": "palindrome",
"dir": "locomotion-plugin-example",
"source": "https://github.com/drcircuit/locomotion-plugin-example.git"
}
]
}
像这样加载它: const loco = require(“ locomotion”);
然后返回一个承诺,该承诺将解决服务定位器对象,该对象具有locator方法来获取您的服务:
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);
});
请注意,这仅是参考实现,不足以用于严肃的应用程序。但是,该模式仍然有效,并显示了编写此类框架的要旨。
现在,需要扩展此功能,以支持插件配置,初始化,错误检查,还可能添加对依赖项注入的支持等。
答案 1 :(得分:2)
我会选择外部软件包选项。
您可以将应用程序构造为具有packages
文件夹。我将在该文件夹中使用UMD编译外部软件包的内部版本,以便您编译的打字稿不会对软件包产生任何问题。所有软件包的每个软件包的根文件夹上均应有一个index.js
文件。
您的应用程序可以使用fs
和require
将所有程序包index.js
放入您的应用程序,从而遍历程序包文件夹。
然后再次依赖安装是您必须要注意的事情。我认为每个软件包上的配置文件也可以解决该问题。您可以在主应用程序上使用自定义npm
脚本,以在启动应用程序之前安装所有程序包依赖项。
通过这种方式,您可以通过将软件包粘贴粘贴到packages文件夹中并重新启动应用程序,从而将新软件包添加到您的应用程序中。您编译的打字稿文件不会被触碰,您也不必为自己的软件包使用私有npm。