Skip to main content

新的后端系统

状态

新的后端系统已经发布,可以投入生产使用,许多插件和模块已经迁移。 我们建议所有插件和部署迁移到新系统。

后端设置示例请参见后端-下一步软件包.

概览

建立新的 Backstage 后端系统是为了帮助更简单地安装后端插件,并保持项目的最新状态。 它还改变了基础,使插件和系统本身的演进变得更加容易,同时将中断或导致破坏性更改的情况降至最低。 您可以在《Backstage》中阅读更多有关原因的信息。原 RFC.

新系统的目标之一是减少建立Backstage后端和安装插件所需的代码。 这是一个如何在新系统中创建、添加功能和启动Backstage的示例:

import { createBackend } from '@backstage/backend-defaults';

// Create your backend instance
const backend = createBackend();

// Install all desired features
backend.add(import('@backstage/plugin-catalog-backend'));

// Start up the backend
await backend.start();

帮助实现后端设置更加精简的一个显著变化是引入了依赖注入系统,该系统与 Backstage 前端的依赖注入系统非常相似。

积木

本节将介绍构建新系统的高层次构件,这些构件都以某种方式存在于我们当前的系统中,但在新系统中都被提升为一级关注点。

后端

这就是后端实例本身,可以将其视为部署单元。 它本身没有任何功能,只是负责将各种东西连接起来。

您可以自行决定部署多少个不同的后端。 您可以将所有功能集中在一个后端,也可以将其拆分成多个较小的部署。 这一切都取决于您对扩展和隔离单个功能的需求。

插件

插件提供实际功能,就像我们现有的系统一样。 它们完全独立运行。 如果插件之间要通信,它们必须通过线路通信。 插件之间不能通过代码直接通信。 由于这种限制,每个插件都可以被视为自己的微服务。

服务

服务提供实用程序,帮助简化插件的实施,这样每个插件就不需要从头开始实施一切。 既有许多内置服务,如用于日志记录、数据库访问和读取配置的服务,也可以导入第三方服务或创建自己的服务。

服务也是单个后端安装的定制点。 您既可以用自己的实现覆盖服务,也可以对现有服务进行较小的定制。

扩展点

许多插件都有可以扩展的方式,例如目录的实体提供者或 Scaffolder 的自定义操作。 这些扩展模式现在都被编码为扩展点(Extension Points)。

扩展点看起来有点像服务,因为你依赖它们就像依赖服务一样。 一个关键的区别是,扩展点是由插件自己注册和提供的,基于每个插件想要公开的自定义内容。

扩展点也是与插件实例本身分开输出的,而且一个插件还可以同时暴露多个不同的扩展点。 这样,随着时间的推移,单个扩展点的演进和废弃都会变得更容易,而不是处理单一的大型 API 表面。

模块

模块使用插件扩展点为插件添加新功能。 例如,它们可以添加一个单独的目录实体提供程序或一个或多个 Scaffolder 操作。 模块基本上是插件的插件。

每个模块只能扩展一个插件,而且模块必须与该插件一起部署在同一个后端实例中。 不过,模块只能通过其注册的扩展点与其插件通信。

与插件一样,模块也可以访问服务,并依赖于自己的服务实现。 不过,它们将与自己扩展的插件共享服务,不存在特定于模块的服务实现。

创建插件

使用createBackendPlugin所有插件都必须有一个 ID 和一个注册方法。 插件还可以接受一个选项对象,该对象可以是可选的,也可以是必选的。 选项会传递给注册方法的第二个参数,然后推断出选项类型,并将其转发给返回的插件工厂函数。

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

// export type ExamplePluginOptions = { exampleOption: boolean };
export const examplePlugin = createBackendPlugin({
// unique id for the plugin
pluginId: 'example',
// It's possible to provide options to the plugin
// register(env, options: ExamplePluginOptions) {
register(env) {
env.registerInit({
deps: {
logger: coreServices.logger,
},
// logger is provided by the backend based on the dependency on loggerServiceRef above.
async init({ logger }) {
logger.info('Hello from example plugin');
},
});
},
});

然后,可以使用返回的插件工厂函数在后端安装插件:

backend.add(examplePlugin());

如果我们想让插件也接受选项,我们就会接受选项作为注册方法的第二个参数:

export const examplePlugin = createBackendPlugin({
pluginId: 'example',
register(env, options?: { silent?: boolean }) {
env.registerInit({
deps: { logger: coreServices.logger },
async init({ logger }) {
if (!options?.silent) {
logger.info('Hello from example plugin');
}
},
});
},
});

在安装过程中将该选项传递给插件的过程如下:

backend.add(examplePlugin({ silent: true }));

创建模块

关于模块的一些事实

  • 一个模块只能扩展一个插件,但可以与该插件注册的多个 "扩展点 "交互。

模块取决于ExtensionPoint目标插件的库包导出的内容,例如@backstage/plugin-catalog-node并不直接声明对插件包本身的依赖性。

下面是一个示例,说明如何创建一个模块,使用catalogProcessingExtensionPoint:

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

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

扩展点

模块对扩展点的依赖与普通依赖一样,只需在deps节。

定义扩展点

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

export interface ScaffolderActionsExtensionPoint {
addAction(action: ScaffolderAction): void;
}

export const scaffolderActionsExtensionPoint =
createExtensionPoint<ScaffolderActionsExtensionPoint>({
id: 'scaffolder.actions',
});

注册扩展点

扩展点由插件注册并由模块扩展。

后端服务

默认后端提供多个核心服务服务依赖关系使用其ServiceRef中的deps插件或模块的部分,然后将这些实现转发给init方法。

服务参考

AServiceRef是对一个接口的命名引用,随后用来解析具体的服务实现。 在概念上,这与ApiRef服务提供了以前位于PluginEnvironment如配置、日志和数据库。

启动时,后端将确保服务在传递给依赖于它们的插件/模块之前已被初始化。 ServiceRefs 包含一个作用域,用于确定创建服务的 serviceFactory 是否会为每个插件/模块创建一个新实例,或者是否会共享该实例。plugin范围的服务将为每个插件/模块创建一次,而root范围的服务将在每个后端实例中创建一次。

定义服务

import {
createServiceFactory,
coreServices,
} from '@backstage/backend-plugin-api';
import { ExampleImpl } from './ExampleImpl';

export interface ExampleApi {
doSomething(): Promise<void>;
}

export const exampleServiceRef = createServiceRef<ExampleApi>({
id: 'example',
scope: 'plugin', // can be 'root' or 'plugin'

// The defaultFactory is optional to implement but it will be used if no other factory is provided to the backend.
// This is allows for the backend to provide a default implementation of the service without having to wire it beforehand.
defaultFactory: async service =>
createServiceFactory({
service,
deps: {
logger: coreServices.logger,
plugin: coreServices.pluginMetadata,
},
// Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance.
async factory({ logger, plugin }) {
// plugin is available as it's a plugin scoped service and will be created once per plugin.
return async ({ plugin }) => {
// This block will be executed once for every plugin that depends on this service
logger.info('Initializing example service plugin instance');
return new ExampleImpl({ logger, plugin });
};
},
}),
});

覆盖服务

在此示例中,我们用自定义的日志流向 GCP 的日志服务替换默认的根日志服务实现。rootLoggerServiceRef有一个'root'范围,这意味着该服务没有特定于插件的实例。

import {
createServiceFactory,
rootLoggerServiceRef,
LoggerService,
} from '@backstage/backend-plugin-api';

// This custom implementation would typically live separately from
// the backend setup code, either nearby such as in
// packages/backend/src/services/logger/GoogleCloudLogger.ts
// Or you can let it live in its own library package.
class GoogleCloudLogger implements LoggerService {
static factory = createServiceFactory({
service: rootLoggerServiceRef,
deps: {},
async factory() {
return new GoogleCloudLogger();
},
});
// custom implementation here ...
}

// packages/backend/src/index.ts
const backend = createBackend();

// supplies additional or replacement services to the backend
backend.add(GoogleCloudLogger.factory());

测试

用于测试后端插件和模块的实用程序可在@backstage/backend-test-utils.startTestBackend返回 HTTP,可与supertest来测试插件。

import { startTestBackend } from '@backstage/backend-test-utils';
import request from 'supertest';

describe('My plugin tests', () => {
it('should return 200', async () => {
const { server } = await startTestBackend({
features: [myPlugin()],
});

const response = await request(server).get('/api/example/hello');
expect(response.status).toBe(200);
});
});

包裹结构

有关软件包架构的详细说明,请参阅Backstage架构概述该系统需要考虑的最重要的软件包是backend,plugin-<pluginId>-backend,plugin-<pluginId>-nodeplugin-<pluginId>-backend-module-<moduleId>.

  • plugin-<pluginId>-backend 包含插件本身的实现。 * plugin-<pluginId>-node 包含扩展点和模块或其他插件可能需要的任何其他实用程序。 * plugin-<pluginId>-backend-module-<moduleId> 包含通过扩展点扩展插件的模块。 * backend 是后端本身,它将所有东西连接到你可以部署的东西上。