使用自定义端点扩展现有API

时间:2019-10-30 10:41:01

标签: javascript rest api express nestjs

我正在为多个客户创建一个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可能会遍历该目录并尝试导入每个模块文件。

  • 优点:

    1. 自定义代码远离核心存储库
  • 缺点:

    1. NestJs使用Typescript,因此我必须先编译代码。我将如何管理API构建以及来自自定义应用的构建? (即插即用系统)

    2. 自定义扩展非常宽松,因为它们仅包含一些打字稿文件。由于他们无权访问API的node_modules目录,因此我的编辑器将向我显示错误,因为它无法解析外部程序包依赖性。

    3. 某些扩展名可能会从另一个扩展名中获取数据。群组服务可能需要访问用户服务。这里的事情可能会变得棘手。


第二种方法: 将每个扩展名保留在API的src文件夹的子文件夹中。但是将此子文件夹添加到.gitignore文件中。现在,您可以将扩展保留在API中。

  • 优点:

    1. 您的编辑器能够解决依赖关系

    2. 在部署代码之前,您可以运行build命令,并且将具有单个发行版

    3. 您可以轻松访问其他服务(/groups需要通过ID查找用户)

  • 缺点:

    1. 开发时,必须将存储库文件复制到该子文件夹中。更改某些内容后,您必须将这些文件复制回去,并用更新的文件覆盖您的存储库文件。

第三种方法:

在外部自定义文件夹中,所有扩展都是完全独立的API。您的主要API仅提供身份验证内容,并可以充当代理,将传入的请求重定向到目标API。

  • 优点:

    1. 可以轻松开发和测试新扩展
  • 缺点:

    1. 部署将很棘手。您将拥有一个主要的API和 n 个扩展API,它们将开始各自的进程并监听端口。

    2. 代理系统可能很棘手。如果客户端请求/users,则代理需要知道哪个扩展API侦听该终结点,然后调用该API并将该响应转发回客户端。

    3. 为保护扩展API(身份验证由主API处理),代理需要与这些API共享机密。因此,如果从代理服务器提供了匹配的机密,扩展API将仅传递传入的请求。


第四种方法:

微服务可能会有所帮助。我从这里https://docs.nestjs.com/microservices/basics

我可以拥有一个用于用户管理,组管理等的微服务,并通过创建一个调用这些微服务的小型api /网关/代理来使用这些服务。

  • 优点:

    1. 可以轻松开发和测试新扩展

    2. 分离的问题

  • 缺点:

    1. 部署将很棘手。您将拥有一个主要的API和 n 个微服务,以启动它们自己的进程并监听端口。

    2. 如果我想对其进行自定义,则似乎必须为每个客户创建一个新的网关api。因此,除了扩展应用程序外,我每次都必须创建一个自定义的消费API。那不会解决问题。

    3. 为保护扩展API(身份验证由主API处理),代理需要与这些API共享机密。因此,如果从代理服务器提供了匹配的机密,扩展API将仅传递传入的请求。

2 个答案:

答案 0 :(得分:5)

有几种方法。您需要做的是找出最适合您的团队,组织和客户的工作流程。

如果这取决于我,我将考虑每个模块使用一个存储库,并使用像NPM这样的软件包管理器以及私有或组织范围的软件包来处理配置。然后设置构建发布管道,以推送到新构建的软件包仓库。

这样,您所需要的就是主文件和每个自定义安装的软件包清单文件。您可以独立开发和部署新版本,也可以在需要时在客户端上加载新版本。

要获得更高的平滑度,您可以使用配置文件将模块映射到路由,并编写通用的路由生成器脚本来执行大多数引导。

由于一个包可以是任何东西,所以包内的交叉依赖将很容易解决。您只需要在更改和版本管理方面受到约束即可。

在此处详细了解私有软件包: Private Packages NPM

现在,私人NPM注册管理机构要花钱,但是如果这是一个问题,那么还有其他几种选择。请查看本文以了解其他选择-免费和付费。

Ways to have your private npm registry

现在,如果您要推出自己的管理器,则可以编写一个简单的服务定位器,该服务定位器将使用一个包含必要信息的配置文件,以从存储库中提取代码,进行加载,然后提供某种方法为其检索实例。

我已经为这种系统编写了一个简单的参考实现:

框架:locomotion service locator

检查回文的插件示例: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文件。

您的应用程序可以使用fsrequire将所有程序包index.js放入您的应用程序,从而遍历程序包文件夹。

然后再次依赖安装是您必须要注意的事情。我认为每个软件包上的配置文件也可以解决该问题。您可以在主应用程序上使用自定义npm脚本,以在启动应用程序之前安装所有程序包依赖项。

通过这种方式,您可以通过将软件包粘贴粘贴到packages文件夹中并重新启动应用程序,从而将新软件包添加到您的应用程序中。您编译的打字稿文件不会被触碰,您也不必为自己的软件包使用私有npm。