Skip to main content

核心后端服务应用程序接口

默认后端提供多个核心服务开箱即用,包括访问配置、日志、URL 阅读器、数据库等。

所有核心服务均可通过coreServices命名空间中的@backstage/backend-plugin-api包装

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

HTTP 路由器服务

最常见的服务之一是 HTTP 路由器服务,它用于暴露 HTTP 端点供其他插件使用。

使用服务

下面的示例展示了如何为example插头。/api/example/hello路径

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: { http: coreServices.httpRouter },
async init({ http }) {
const router = Router();
router.get('/hello', (_req, res) => {
res.status(200).json({ hello: 'world' });
});
// Registers the router at the /api/example path
http.use(router);
},
});
},
});

配置服务

您还可以通过其他配置来设置httpRouter核心服务。

  • getPath - 可用于为每个插件生成一个路径。 目前默认为 /api/${pluginId}.

您可以在调用createBackend具体如下

import { httpRouterServiceFactory } from '@backstage/backend-app-api';

const backend = createBackend();

backend.add(
httpRouterServiceFactory({
getPath: (pluginId: string) => `/plugins/${pluginId}`,
}),
);

HTTP 根路由器

HTTP 根路由器是一项允许在后端服务根节点上注册路由的服务。 这对于健康检查或其他需要在后端服务根节点上公开的路由非常有用。 它被用作支持后端httpRouter大多数情况下,您不需要直接使用该服务,而是使用httpRouter服务。

使用服务

下面的示例展示了如何在您的example后端插件来注册健康检查路由。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
rootHttpRouter: coreServices.rootHttpRouter,
},
async init({ rootHttpRouter }) {
const router = Router();
router.get('/health', (request, response) => {
response.send('OK');
});

rootHttpRouter.use(router);
},
});
},
});

配置服务

您还可以通过其他选项来配置根 HTTP 路由器服务。 这些选项会在调用createBackend.

  • indexPath - 用于转发所有未匹配请求的可选路径。 默认为 /api/app,这是负责通过后端为前端应用程序提供服务的 app-backend 插件。 configure - 这是一个可选函数,可用于配置 express 实例。 如果你想在根路由器上添加自己的中间件,如日志记录,或在后端处理请求之前要做的其他事情,这将非常有用。 它还可用于覆盖中间件的应用顺序。

您可以通过将选项传递给createBackend功能。

import { rootHttpRouterServiceFactory } from '@backstage/backend-app-api';

const backend = createBackend();

backend.add(
rootHttpRouterServiceFactory({
configure: ({ app, middleware, routes, config, logger, lifecycle }) => {
// the built in middleware is provided through an option in the configure function
app.use(middleware.helmet());
app.use(middleware.cors());
app.use(middleware.compression());

// you can add you your own middleware in here
app.use(custom.logging());

// here the routes that are registered by other plugins
app.use(routes);

// some other middleware that comes after the other routes
app.use(middleware.notFound());
app.use(middleware.error());
},
}),
);

根配置

该服务允许您从您的app-configYAML 文件。

使用服务

下面的示例展示了如何使用默认配置服务获取配置值,然后将其记录到控制台。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
log: coreServices.logger,
config: coreServices.rootConfig,
},
async init({ log, config }) {
const baseUrl = config.getString('backend.baseUrl');
log.warn(`The backend is running at ${baseUrl}`);
},
});
},
});

配置服务

您还可以通过其他配置来设置config核心服务。

  • argv - 覆盖传递给配置加载器的参数,而不是使用 process.argv * remote - 配置远程配置加载器

您可以在调用createBackend具体如下

import { rootConfigServiceFactory } from '@backstage/backend-app-api';

const backend = createBackend();

backend.add(
rootConfigServiceFactory({
argv: [
'--config',
'/backstage/app-config.development.yaml',
'--config',
'/backstage/app-config.yaml',
],
remote: { reloadIntervalSeconds: 60 },
}),
);

记录

该服务允许插件输出日志信息。 实际上有两个日志服务:一个是根日志服务,另一个是与单个插件绑定的插件日志服务,因此您将获得带有日志行中引用的插件 ID 的漂亮信息。

使用服务

下面的示例展示了如何在您的example后端插件,并创建一条警告信息,该信息将被很好地打印到控制台。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
log: coreServices.logger,
},
async init({ log }) {
log.warn("Here's a nice log line that's a warning!");
},
});
},
});

根记录仪

根日志记录器是其他根服务使用的日志记录器。 它是围绕Backstage生态系统创建子日志记录器(包括带有正确元数据和注释的插件的子日志记录器)的实现所在。

如果要覆盖所有后端日志记录的实现,则应覆盖此服务。

配置服务

下面的示例介绍了如何覆盖根日志记录器服务,为所有日志行添加附加元数据。

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

const backend = createBackend();

backend.add(
createServiceFactory({
service: coreServices.rootLogger,
deps: {
config: coreServices.rootConfig,
},
async factory({ config }) {
const logger = WinstonLogger.create({
meta: {
service: 'backstage',
// here's some additional information that is not part of the
// original implementation
podName: 'myk8spod',
},
level: process.env.LOG_LEVEL || 'info',
format:
process.env.NODE_ENV === 'production'
? format.json()
: WinstonLogger.colorFormat(),
transports: [new transports.Console()],
});

return logger;
},
}),
);

缓存

该服务可让您的插件与缓存进行交互。 它也与您的插件绑定,因此您只能在插件的私有命名空间中设置和获取值。

使用服务

下面的示例展示了如何在您的example后端插件,并从缓存中设置和获取值。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
cache: coreServices.cache,
},
async init({ cache }) {
const { key, value } = { key: 'test:key', value: 'bob' };
await cache.set(key, value, { ttl: 1000 });

// .. some other stuff.

await cache.get(key); // 'bob'
},
});
},
});

数据库

这项服务可让您的插件获得knex客户端连接到数据库,该数据库已在您的app-configYAML 文件,以满足您的持久化需求。

如果backend.database则插件将自动获得一个简单的 SQLite 3 内存数据库,该数据库的内容将在服务重启时丢失。

这项服务也是针对每个插件的,因此表名不会在不同插件之间发生冲突。

使用服务

下面的示例展示了如何访问您的example它还会从某个目录中为你的插件运行一些迁移。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
database: coreServices.database,
},
async init({ database }) {
const client = await database.getClient();
const migrationsDir = resolvePackagePath(
'@internal/my-plugin',
'migrations',
);
if (!database.migrations?.skip) {
await client.migrate.latest({
directory: migrationsDir,
});
}
},
});
},
});

发现

在构建插件时,您可能会发现需要查找另一个插件的基本 URL 才能与其通信。 例如,这可能是 HTTP 路由或某些ws为此,我们提供了一个发现服务,可以为给定的插件 ID 提供内部和外部基本 URL。

使用服务

下面的示例展示了如何在您的example后端插件,并向内部和外部的基础 URL 提出申请,为derp插件。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
discovery: coreServices.discovery,
},
async init({ discovery }) {
const url = await discovery.getBaseUrl('derp'); // can also use discovery.getExternalBaseUrl to retrieve external URL
const response = await fetch(`${url}/hello`);
},
});
},
});

身份

在使用后端插件时,您可能会发现需要与auth-backend插件,既能验证Backstage令牌,又能解构令牌,从中获取用户的实体 ref 和/或所有权声明。

使用服务

下面的示例展示了如何在您的example后端插件,并检索传入请求的用户实体 ref 和所有权要求。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
identity: coreServices.identity,
http: coreServices.httpRouter,
},
async init({ http, identity }) {
const router = Router();
router.get('/test-me', (request, response) => {
// use the identity service to pull out the header from the request and get the user
const {
identity: { userEntityRef, ownershipEntityRefs },
} = await identity.getIdentity({
request,
});

// send the decoded and validated things back to the user
response.json({
userEntityRef,
ownershipEntityRefs,
});
});

http.use(router);
},
});
},
});

配置服务

您还可以通过其他配置来设置identity核心服务。

  • issuer - 为验证 JWT 令牌设置一个可选的发行方 * algorithms - 验证 JWT 令牌的 alg 标头,默认为 ES256。有关支持算法的更多信息,请参阅 jose库文档

您可以在调用createBackend具体如下

import { identityServiceFactory } from '@backstage/backend-app-api';

const backend = createBackend();

backend.add(
identityServiceFactory({
issuer: 'backstage',
algorithms: ['ES256', 'RS256'],
}),
);

生命周期

这项服务允许您的插件注册钩子,以便在服务关闭时清理资源(例如,当 pod 被拆卸时,或当按下Ctrl+C其他核心服务也会在内部利用同样的机制来干净利落地停止自己的运行。

使用服务

下面的示例展示了如何在您的example后端插件,用于在服务关闭时清理长时间运行的间隔。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
lifecycle: coreServices.lifecycle,
logger: coreServices.logger,
},
async init({ lifecycle, logger }) {
// some example work that we want to stop when shutting down
const interval = setInterval(async () => {
await fetch('http://google.com/keepalive').then(r => r.json());
// do some other stuff.
}, 1000);

lifecycle.addShutdownHook(() => clearInterval(interval));
},
});
},
});

根的生命周期

该服务与生命周期服务相同,但只能由根服务使用。 这也是收集和执行实际生命周期钩子的实现的地方,因此如果要重写如何处理这些钩子的实现,则应重写该服务。

配置服务

下面的示例展示了如何通过监听与原始服务不同的进程事件来覆盖生命周期服务的默认实现。

class MyCustomLifecycleService implements RootLifecycleService {
constructor(private readonly logger: LoggerService) {}

#isCalled = false;
#shutdownTasks: Array<{
hook: LifecycleServiceShutdownHook;
options?: LifecycleServiceShutdownOptions;
}> = [];

addShutdownHook(
hook: LifecycleServiceShutdownHook,
options?: LifecycleServiceShutdownOptions,
): void {
this.#shutdownTasks.push({ hook, options });
}

async shutdown(): Promise<void> {
if (this.#isCalled) {
return;
}
this.#isCalled = true;

this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
await Promise.all(
this.#shutdownTasks.map(async ({ hook, options }) => {
const logger = options?.logger ?? this.logger;
try {
await hook();
logger.info(`Shutdown hook succeeded`);
} catch (error) {
logger.error(`Shutdown hook failed, ${error}`);
}
}),
);
}
}

const backend = createBackend();

backend.add(
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {
logger: coreServices.rootLogger,
},
async factory({ logger }) {
return new MyCustomLifecycleService(logger);
},
}),
);

权限

这项服务允许您的插件要求权限框架用于授权用户操作。

使用服务

下面的示例显示了如何在您的example后端通过自定义权限规则检查用户是否被允许执行某个操作。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
permissions: coreServices.permissions,
http: coreServices.httpRouter,
},
async init({ permissions, http }) {
const router = Router();
router.get('/test-me', (request, response) => {
// use the identity service to pull out the token from request headers
const { token } = await identity.getIdentity({
request,
});

// ask the permissions framework what the decision is for the permission
const permissionResponse = await permissions.authorize(
[
{
permission: myCustomPermission,
},
],
{ token },
);
});

http.use(router);
},
});
},
});

调度员

在编写插件时,您有时会希望按计划运行,或类似于通过后端插件运行的实例分配 cron 作业。 我们为此提供了一个任务调度程序,可按插件的范围分配,这样您就可以创建这些任务并协调其执行。

使用服务

下面的示例显示了如何在您的example后端来发布一个计划任务,该任务会以给定的时间间隔在整个实例中运行。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
scheduler: coreServices.scheduler,
},
async init({ scheduler }) {
await scheduler.scheduleTask({
frequency: { minutes: 10 },
timeout: { seconds: 30 },
id: 'ping-google',
fn: async () => {
await fetch('http://google.com/ping');
},
});
},
});
},
});

URL 阅读器

插件需要与用户配置的某些集成进行通信。 常用的集成包括版本控制系统 (VSC),如 GitHub、BitBucket GitLab 等。integrations部分app-config.yaml锉刀

这些 URL 阅读器基本上是对存储在这些 VCS 资源库中的文件和文件夹进行身份验证的包装器。

使用服务

下面的示例显示了如何在您的example后端插件,从 GitHub 仓库读取文件和目录。

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

createBackendPlugin({
pluginId: 'example',
register(env) {
env.registerInit({
deps: {
urlReader: coreServices.urlReader,
},
async init({ urlReader }) {
const buffer = await urlReader
.read('https://github.com/backstage/backstage/blob/master/README.md')
.then(r => r.buffer());

const tmpDir = os.tmpdir();
const directory = await urlReader
.readTree(
'https://github.com/backstage/backstage/tree/master/packages/backend',
)
.then(tree => tree.dir({ targetDir: tmpDir }));
},
});
},
});