Merge pull request #18834 from backstage/tudi2d/remove-shared-environments
Remove "Shared Environments" concept from new backend system
This commit is contained in:
@@ -5,15 +5,12 @@
|
||||
```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[];
|
||||
}
|
||||
|
||||
@@ -16,17 +16,10 @@
|
||||
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
createSharedEnvironment,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { createBackend } from './CreateBackend';
|
||||
|
||||
const fooServiceRef = createServiceRef<string>({ id: 'foo', scope: 'root' });
|
||||
const barServiceRef = createServiceRef<string>({ id: 'bar', scope: 'root' });
|
||||
|
||||
describe('createBackend', () => {
|
||||
it('should not throw when overriding a default service implementation', () => {
|
||||
expect(() =>
|
||||
@@ -85,91 +78,4 @@ describe('createBackend', () => {
|
||||
}),
|
||||
).toThrow('The core.pluginMetadata service cannot be overridden');
|
||||
});
|
||||
|
||||
it('should throw if an unsupported InternalSharedEnvironment version is passed in', () => {
|
||||
expect(() =>
|
||||
createBackend({
|
||||
env: {} as any,
|
||||
}),
|
||||
).toThrow(
|
||||
"Shared environment version 'undefined' is invalid or not supported",
|
||||
);
|
||||
expect(() =>
|
||||
createBackend({
|
||||
env: { version: {} } as any,
|
||||
}),
|
||||
).toThrow(
|
||||
"Shared environment version '[object Object]' is invalid or not supported",
|
||||
);
|
||||
expect(() =>
|
||||
createBackend({
|
||||
env: { version: 'v2' } as any,
|
||||
}),
|
||||
).toThrow("Shared environment version 'v2' is invalid or not supported");
|
||||
});
|
||||
|
||||
it('should prioritize services correctly', async () => {
|
||||
const backend = createBackend({
|
||||
env: createSharedEnvironment({
|
||||
services: [
|
||||
createServiceFactory({
|
||||
service: coreServices.rootHttpRouter,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return {
|
||||
use() {},
|
||||
};
|
||||
},
|
||||
}),
|
||||
mockServices.config.factory({
|
||||
data: { root: 'root-env' },
|
||||
}),
|
||||
createServiceFactory({
|
||||
service: fooServiceRef,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return 'foo-env';
|
||||
},
|
||||
}),
|
||||
createServiceFactory({
|
||||
service: barServiceRef,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return 'bar-env';
|
||||
},
|
||||
}),
|
||||
],
|
||||
})(),
|
||||
services: [
|
||||
createServiceFactory({
|
||||
service: fooServiceRef,
|
||||
deps: {},
|
||||
factory: async () => 'foo-backend',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect.assertions(3);
|
||||
backend.add(
|
||||
createBackendPlugin({
|
||||
pluginId: 'test',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
config: coreServices.config,
|
||||
foo: fooServiceRef,
|
||||
bar: barServiceRef,
|
||||
},
|
||||
async init({ config, foo, bar }) {
|
||||
expect(config.get('root')).toBe('root-env');
|
||||
expect(foo).toBe('foo-backend');
|
||||
expect(bar).toBe('bar-env');
|
||||
},
|
||||
});
|
||||
},
|
||||
})(),
|
||||
);
|
||||
|
||||
await backend.start();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,13 +36,8 @@ import {
|
||||
import {
|
||||
ServiceFactory,
|
||||
ServiceFactoryOrFunction,
|
||||
SharedBackendEnvironment,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
// Internal import of the type to avoid needing to export this.
|
||||
// eslint-disable-next-line @backstage/no-forbidden-package-imports
|
||||
import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment';
|
||||
|
||||
export const defaultServiceFactories = [
|
||||
cacheServiceFactory(),
|
||||
configServiceFactory(),
|
||||
@@ -65,7 +60,6 @@ export const defaultServiceFactories = [
|
||||
* @public
|
||||
*/
|
||||
export interface CreateBackendOptions {
|
||||
env?: SharedBackendEnvironment;
|
||||
services?: ServiceFactoryOrFunction[];
|
||||
}
|
||||
|
||||
@@ -81,22 +75,6 @@ export function createBackend(options?: CreateBackendOptions): Backend {
|
||||
);
|
||||
services.push(...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(
|
||||
`Shared environment version '${env.version}' is invalid or not supported`,
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
|
||||
@@ -222,15 +222,6 @@ export function createServiceRef<TService>(
|
||||
config: ServiceRefConfig<TService, 'root'>,
|
||||
): ServiceRef<TService, 'root'>;
|
||||
|
||||
// @public
|
||||
export function createSharedEnvironment<
|
||||
TOptions extends [options?: object] = [],
|
||||
>(
|
||||
config:
|
||||
| SharedBackendEnvironmentConfig
|
||||
| ((...params: TOptions) => SharedBackendEnvironmentConfig),
|
||||
): (...options: TOptions) => SharedBackendEnvironment;
|
||||
|
||||
// @public
|
||||
export interface DatabaseService {
|
||||
getClient(): Promise<Knex>;
|
||||
@@ -476,18 +467,6 @@ export interface ServiceRefConfig<TService, TScope extends 'root' | 'plugin'> {
|
||||
scope?: TScope;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SharedBackendEnvironment {
|
||||
// (undocumented)
|
||||
$$type: '@backstage/SharedBackendEnvironment';
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface SharedBackendEnvironmentConfig {
|
||||
// (undocumented)
|
||||
services?: ServiceFactoryOrFunction[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TokenManagerService {
|
||||
authenticate(token: string): Promise<void>;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
ServiceFactoryOrFunction,
|
||||
} from '../services';
|
||||
import {
|
||||
createSharedEnvironment,
|
||||
InternalSharedBackendEnvironment,
|
||||
} from './createSharedEnvironment';
|
||||
|
||||
const fooService = createServiceRef<string>({ id: 'foo', scope: 'root' });
|
||||
const fooFactory = createServiceFactory({
|
||||
service: fooService,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return 'foo';
|
||||
},
|
||||
});
|
||||
|
||||
const barService = createServiceRef<number>({ id: 'bar', scope: 'root' });
|
||||
const barFactory = createServiceFactory({
|
||||
service: barService,
|
||||
deps: {},
|
||||
async factory() {
|
||||
return 0xba5;
|
||||
},
|
||||
});
|
||||
|
||||
describe('createSharedEnvironment', () => {
|
||||
it('should create an empty shared environment', () => {
|
||||
const env = createSharedEnvironment({});
|
||||
expect(env).toBeDefined();
|
||||
const internalEnv = env() as unknown as InternalSharedBackendEnvironment;
|
||||
expect(internalEnv).toEqual({
|
||||
$$type: '@backstage/SharedBackendEnvironment',
|
||||
version: 'v1',
|
||||
services: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a shared environment with services', () => {
|
||||
const env = createSharedEnvironment({
|
||||
services: [fooFactory, barFactory()],
|
||||
});
|
||||
const internalEnv = env() as unknown as InternalSharedBackendEnvironment;
|
||||
expect(internalEnv.version).toBe('v1');
|
||||
expect(internalEnv.services?.length).toBe(2);
|
||||
expect(internalEnv.services?.[0]?.service.id).toBe('foo');
|
||||
expect(internalEnv.services?.[1]?.service.id).toBe('bar');
|
||||
});
|
||||
|
||||
it('should create a shared environment with options', () => {
|
||||
const env = createSharedEnvironment((options?: { withFoo?: boolean }) => {
|
||||
const services = new Array<ServiceFactoryOrFunction>();
|
||||
if (options?.withFoo) {
|
||||
services.push(fooFactory());
|
||||
}
|
||||
services.push(barFactory);
|
||||
return { services };
|
||||
});
|
||||
const internalEnv1 = env() as unknown as InternalSharedBackendEnvironment;
|
||||
expect(internalEnv1.version).toBe('v1');
|
||||
expect(internalEnv1.services?.length).toBe(1);
|
||||
expect(internalEnv1.services?.[0]?.service.id).toBe('bar');
|
||||
|
||||
const internalEnv2 = env({
|
||||
withFoo: true,
|
||||
}) as unknown as InternalSharedBackendEnvironment;
|
||||
expect(internalEnv2.version).toBe('v1');
|
||||
expect(internalEnv2.services?.length).toBe(2);
|
||||
expect(internalEnv2.services?.[0]?.service.id).toBe('foo');
|
||||
expect(internalEnv2.services?.[1]?.service.id).toBe('bar');
|
||||
});
|
||||
|
||||
it('should not allow duplicate service factories', () => {
|
||||
expect(() =>
|
||||
createSharedEnvironment({
|
||||
services: [fooFactory, fooFactory()],
|
||||
})(),
|
||||
).toThrow(
|
||||
"Duplicate service implementations provided in shared environment for 'foo'",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* The configuration options passed to {@link createSharedEnvironment}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SharedBackendEnvironmentConfig {
|
||||
services?: ServiceFactoryOrFunction[];
|
||||
}
|
||||
|
||||
/**
|
||||
* An opaque type that represents the contents of a shared backend environment.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface SharedBackendEnvironment {
|
||||
$$type: '@backstage/SharedBackendEnvironment';
|
||||
|
||||
// NOTE: 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.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
|
||||
const exists = new Set<string>();
|
||||
const duplicates = new Set<string>();
|
||||
for (const { service } of services ?? []) {
|
||||
if (exists.has(service.id)) {
|
||||
duplicates.add(service.id);
|
||||
} else {
|
||||
exists.add(service.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicates.size > 0) {
|
||||
const dupStr = [...duplicates].map(id => `'${id}'`).join(', ');
|
||||
throw new Error(
|
||||
`Duplicate service implementations provided in shared environment for ${dupStr}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Here to ensure type safety in this internal implementation.
|
||||
const env: SharedBackendEnvironment & InternalSharedBackendEnvironment = {
|
||||
$$type: '@backstage/SharedBackendEnvironment',
|
||||
version: 'v1',
|
||||
services,
|
||||
};
|
||||
return env;
|
||||
};
|
||||
}
|
||||
@@ -14,11 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createSharedEnvironment } from './createSharedEnvironment';
|
||||
export type {
|
||||
SharedBackendEnvironment,
|
||||
SharedBackendEnvironmentConfig,
|
||||
} from './createSharedEnvironment';
|
||||
export type {
|
||||
BackendModuleConfig,
|
||||
BackendPluginConfig,
|
||||
|
||||
Reference in New Issue
Block a user