From e6e77f073a40648f35d981636554a152814caf20 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 13 Jan 2023 11:53:33 +0100 Subject: [PATCH] backend-plugin-api,backend-defaults: add createSharedEnvironment Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-defaults/api-report.md | 3 + .../backend-defaults/src/CreateBackend.ts | 43 +++++++++--- packages/backend-plugin-api/api-report.md | 21 ++++++ .../src/wiring/createSharedEnvironment.ts | 67 +++++++++++++++++++ .../backend-plugin-api/src/wiring/index.ts | 5 ++ 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md index 6cc58de663..07218f610e 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -5,12 +5,15 @@ ```ts import { Backend } from '@backstage/backend-app-api'; import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; +import { SharedBackendEnvironment } from '@backstage/backend-plugin-api'; // @public (undocumented) export function createBackend(options?: CreateBackendOptions): Backend; // @public (undocumented) export interface CreateBackendOptions { + // (undocumented) + env?: SharedBackendEnvironment; // (undocumented) services?: ServiceFactoryOrFunction[]; } diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index fe82fb671c..374574645f 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -33,7 +33,15 @@ import { urlReaderFactory, identityFactory, } from '@backstage/backend-app-api'; -import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; +import { + ServiceFactory, + ServiceFactoryOrFunction, + SharedBackendEnvironment, +} from '@backstage/backend-plugin-api'; + +// Internal import of the type to avoid needing to export this. +// eslint-disable-next-line monorepo/no-internal-import +import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment'; export const defaultServiceFactories = [ cacheFactory(), @@ -57,6 +65,7 @@ export const defaultServiceFactories = [ * @public */ export interface CreateBackendOptions { + env?: SharedBackendEnvironment; services?: ServiceFactoryOrFunction[]; } @@ -64,15 +73,33 @@ export interface CreateBackendOptions { * @public */ export function createBackend(options?: CreateBackendOptions): Backend { + const services = new Array(); + + // Highest priority: Services passed directly to createBackend const providedServices = (options?.services ?? []).map(sf => typeof sf === 'function' ? sf() : sf, ); - const providedIds = new Set(providedServices.map(sf => sf.service.id)); - const neededDefaultFactories = defaultServiceFactories.filter( - sf => !providedIds.has(sf.service.id), - ); + services.push(...providedServices); - return createSpecializedBackend({ - services: [...neededDefaultFactories, ...providedServices], - }); + // Middle priority: Services from the shared environment + if (options?.env) { + const env = options.env as unknown as InternalSharedBackendEnvironment; + if (env.version !== 'v1') { + throw new Error(`Unsupported shared environment version: ${env.version}`); + } + + const environmentServices = + env.services?.filter( + sf => !services.some(({ service }) => sf.service.id === service.id), + ) ?? []; + services.push(...environmentServices); + } + + // Lowest priority: Default services that are not already provided by environment or directly to createBackend + const defaultServices = defaultServiceFactories.filter( + sf => !services.some(({ service }) => service.id === sf.service.id), + ); + services.push(...defaultServices); + + return createSpecializedBackend({ services }); } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 33190277c9..2ca116c6b6 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -153,6 +153,15 @@ export function createServiceRef( config: ServiceRefConfig, ): ServiceRef; +// @public +export function createSharedEnvironment< + TOptions extends [options?: object] = [], +>( + config: + | SharedBackendEnvironmentConfig + | ((...params: TOptions) => SharedBackendEnvironmentConfig), +): (...options: TOptions) => SharedBackendEnvironment; + // @public export interface DatabaseService { getClient(): Promise; @@ -382,6 +391,18 @@ export interface ServiceRefConfig { scope?: TScope; } +// @public (undocumented) +export interface SharedBackendEnvironment { + // (undocumented) + $$type: 'SharedBackendEnvironment'; +} + +// @public (undocumented) +export interface SharedBackendEnvironmentConfig { + // (undocumented) + services?: ServiceFactoryOrFunction[]; +} + // @public export interface TokenManagerService { authenticate(token: string): Promise; diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts new file mode 100644 index 0000000000..05be5003bd --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ServiceFactory, ServiceFactoryOrFunction } from '../services'; + +/** @public */ +export interface SharedBackendEnvironmentConfig { + services?: ServiceFactoryOrFunction[]; +} + +// This type is opaque in order to allow for future API evolution without +// cluttering the external API. For example we might want to add support +// for more powerful callback based backend modifications. +// +// By making this opaque we also ensure that the type doesn't become an input +// type that we need to care about, as it would otherwise be possible to pass +// a custom environment definition to `createBackend`, which we don't want. +/** + * @public + */ +export interface SharedBackendEnvironment { + $$type: 'SharedBackendEnvironment'; +} + +/** + * This type is NOT supposed to be used by anyone except internally by the backend-app-api package. + * @internal */ +export interface InternalSharedBackendEnvironment { + version: 'v1'; + services?: ServiceFactory[]; +} + +/** + * Creates a shared backend environment which can be used to create multiple backends + * @public + */ +export function createSharedEnvironment< + TOptions extends [options?: object] = [], +>( + config: + | SharedBackendEnvironmentConfig + | ((...params: TOptions) => SharedBackendEnvironmentConfig), +): (...options: TOptions) => SharedBackendEnvironment { + const configCallback = typeof config === 'function' ? config : () => config; + + return (...options) => { + const actualConfig = configCallback(...options); + const services = actualConfig?.services?.map(sf => + typeof sf === 'function' ? sf() : sf, + ); + + return { services } as unknown as SharedBackendEnvironment; + }; +} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 6d8de91080..0d5999ad12 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +export { createSharedEnvironment } from './createSharedEnvironment'; +export type { + SharedBackendEnvironment, + SharedBackendEnvironmentConfig, +} from './createSharedEnvironment'; export type { BackendModuleConfig, BackendPluginConfig,