Skip to main content

构建后端插件和模块

注意:如果您现有的后端和/或后端插件尚未 > 使用新的后端系统,请参阅 migrating

本节介绍如何创建自己的后端插件模块它们有时统称为后端。特点是采用者添加到他们的后端.

创建新插件

本指南假定您已经建立了一个后端项目。 即使您只想开发一个用于发布的插件,我们仍然建议您在一个标准的 Backstage monorepo 项目中进行,因为您最终往往需要多个软件包。 有关如何建立一个新项目的说明,请参阅我们的入门文件。

要创建后端插件,请运行yarn new选择backend-plugin这将在plugins/<pluginId>-backend,这将是您插件的主要入口。

插件

一个基本的后端插件可能如下所示:

// src/plugin.ts
import {
createBackendPlugin,
coreServices,
} from '@backstage/backend-plugin-api';
import { createExampleRouter } from './router';

export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
// Declare dependencies to services that you want to consume
logger: coreServices.logger,
httpRouter: coreServices.httpRouter,
},
async init({
// Requested service instances get injected as per above
logger,
httpRouter,
}) {
// Perform your initialization and access the services as needed
const example = createExampleRouter(logger);
logger.info('Hello from example plugin');
httpRouter.use(example);
},
});
},
});

// src/index.ts
export { examplePlugin as default } from './plugin';

当您依赖plugin在上面的示例中,日志记录器可能会用您的插件 ID 标记消息,HTTP 路由器可能会用您的插件 ID 作为 API 路由的前缀,具体取决于所使用的实现。

参见关于命名模式的文章了解如何为插件和相关后端系统项目选择最佳名称/ID 的详细信息。

模块

后端模块用于扩展插件它们必须始终安装在与其扩展的插件或模块相同的后端实例中,并且一次只能扩展该插件中的一个插件和模块。 模块使用扩展点插件注册,同时还能依赖于服务最后一点值得重申:注入的plugin范围内的服务将与目标插件稍后接收的服务完全相同,即它们将使用目标插件的pluginId的模块。

模块依赖于目标插件的库包导出的扩展点,例如@backstage/plugin-catalog-node这样做是为了避免直接依赖和可能导致插件包的重复安装,而库包的重复安装应始终得到支持。 不过,带有扩展点的模块通常会从同一个包中导出其扩展点,因为扩展点一般只用于内部定制,包的版本可以保持同步。

要创建后端模块,请运行yarn new选择backend-module这将在plugins/<pluginId>-backend-module-<moduleId>.

下面的示例说明了如何使用catalogProcessingExtensionPoint:

// src/module.ts
import { createBackendModule } from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { MyCustomProcessor } from './MyCustomProcessor';

export const catalogModuleExampleCustomProcessor = createBackendModule({
pluginId: 'catalog',
moduleId: 'example-custom-processor',
register(env) {
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
logger: coreServices.logger,
},
async init({ catalog }) {
catalog.addProcessor(new MyCustomProcessor(logger));
},
});
},
});

// src/index.ts
export { catalogModuleExampleCustomProcessor as default } from './module';

参见关于命名模式的文章了解如何为模块和相关后端系统项目选择最佳名称/ID 的详细信息。

请注意,我们将要与之交互的扩展点放在了deps在初始化模块时,我们可以交替依赖扩展点和服务。 如果模块的实现需要,也可以同时依赖多个扩展点。

每个模块包只能包含一个模块,但该模块可以扩展多个扩展点。 模块还可以使用配置来有条件地启用或禁用某些扩展。 这种模式只能用于相互关联的扩展,否则最好创建一个单独的模块包,并配备自己的模块。

HTTP 处理程序

由于模块可以访问与其扩展的插件相同的服务,因此它们也可以注册自己的 HTTP 处理程序。 有关 HTTP 服务的更多信息,请参阅核心服务在注册 HTTP 处理程序时,必须尽量避免将来与插件本身或其他模块发生冲突。 推荐的命名模式是将处理程序注册在/modules/<module-id>路径,其中<module-id>是模块的 kebab-case ID,例如/modules/example-custom-processor/v1/validators在标准后端设置中,完整路径为<backendUrl>/api/catalog/modules/example-custom-processor/v1/validators.

数据库访问

这同样适用于执行自身迁移并与数据库交互的模块。 这些模块将在与目标插件相同的逻辑数据库实例上运行,因此必须注意选择表名,以免与插件的表名相冲突。 推荐的命名模式为<package name>__<table name>例如@backstage/backend-tasks软件包创建了名为backstage_backend_tasks__<table>如果使用默认附件此外,您还需要确保它使用类似前缀的迁移状态表进行内部记账,以免与插件本身使用的主表相冲突。 具体做法如下:

await knex.migrate.latest({
directory: migrationsDir,
tableName: 'backstage_backend_tasks__knex_migrations',
});

定制

有几种配置和自定义插件和模块的方法。

扩展点

无论何时您想允许模块动态配置您的插件,例如目录后端允许目录模块注入额外实体提供者的方式,您都可以使用扩展点机制。 这方面的详细说明和代码示例见扩展点架构文章而下面的示例则更细化地说明了如何为插件实现扩展点:

import { createExtensionPoint } from '@backstage/backend-plugin-api';

// This is the extension point interface, which is how modules interact with your plugin.
export interface ExamplesExtensionPoint {
addExample(example: Example): void;
}

// This is the extension point reference that encapsulates the above interface.
export const examplesExtensionPoint =
createExtensionPoint<ExamplesExtensionPoint>({
id: 'example.examples',
});

// The following shows how your plugin would register the extension point
// and use the features that other modules have registered.
export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
// We can share data between the extension point implementation and our init method.
const examples = new Array<Example>();

// This registers the implementation of the extension point, which is internal to your plugin.
env.registerExtensionPoint(examplesExtensionPoint, {
addExample(example) {
examples.push(example);
},
});

env.registerInit({
deps: { logger: coreServices.logger },
async init({ logger }) {
// We can access `examples` directly
logger.info(`The following examples have been registered: ${examples}`);
},
});
},
});

这是一种非常常见的扩展点类型,模块有机会注册供插件使用的功能。 在这种情况下,模块可以注册示例,然后由我们的示例插件使用。

配置

您的插件或模块可以利用应用程序配置来配置自己的内部行为。coreServices.rootConfig这种模式非常适合需要在不同环境下进行不同定制的情况。

import { coreServices } from '@backstage/backend-plugin-api';

export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: { config: coreServices.rootConfig },
async init({ config }) {
// Here you can read from the current config as you see fit, e.g.:
const value = config.getOptionalString('example.value');
},
});
},
});

在添加自定义配置选项之前,请务必阅读配置文档特别是关于为自己的插件定义配置中介绍了如何为特定插件建立配置模式。