backend-app-api: keep track of the provided services

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2023-08-15 00:10:21 +02:00
committed by Patrik Oldsberg
parent 263f6be4f6
commit 6e7a1b07f3
3 changed files with 42 additions and 33 deletions
@@ -23,7 +23,7 @@ import {
createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { ServiceRegistry } from './ServiceRegistry';
import {
lifecycleServiceFactory,
loggerServiceFactory,
@@ -65,7 +65,7 @@ describe('BackendInitializer', () => {
const rootFactory = jest.fn();
const pluginFactory = jest.fn();
const registry = new ServiceRegistry([
const services = [
createServiceFactory({
service: rootRef,
deps: {},
@@ -82,9 +82,9 @@ describe('BackendInitializer', () => {
deps: {},
factory: () => new MockLogger(),
})(),
]);
];
const init = new BackendInitializer(registry);
const init = new BackendInitializer(services);
await init.start();
expect(rootFactory).toHaveBeenCalled();
@@ -97,7 +97,7 @@ describe('BackendInitializer', () => {
const extensionPoint = createExtensionPoint<{ values: string[] }>({
id: 'a',
});
const init = new BackendInitializer(new ServiceRegistry(baseFactories));
const init = new BackendInitializer(baseFactories);
init.add(
createBackendModule({
@@ -151,7 +151,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when plugins fail to start', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -171,7 +171,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when modules fail to start', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -192,7 +192,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate plugins', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -221,7 +221,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate modules', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -254,16 +254,14 @@ describe('BackendInitializer', () => {
it('should reject modules with circular dependencies', async () => {
const extA = createExtensionPoint<string>({ id: 'a' });
const extB = createExtensionPoint<string>({ id: 'b' });
const init = new BackendInitializer(
new ServiceRegistry([
rootLifecycleServiceFactory(),
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
})(),
]),
);
const init = new BackendInitializer([
rootLifecycleServiceFactory(),
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
})(),
]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -296,7 +294,7 @@ describe('BackendInitializer', () => {
});
it('should reject modules that depend on extension points other plugins', async () => {
const init = new BackendInitializer(new ServiceRegistry(baseFactories));
const init = new BackendInitializer(baseFactories);
const extA = createExtensionPoint<string>({ id: 'a' });
init.add(
createBackendPlugin({
@@ -30,6 +30,7 @@ import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring
import { ForwardedError, ConflictError } from '@backstage/errors';
import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha';
import { DependencyGraph } from '../lib/DependencyGraph';
import { ServiceRegistry } from './ServiceRegistry';
export interface BackendRegisterInit {
consumes: Set<ServiceOrExtensionPoint>;
@@ -47,10 +48,12 @@ export class BackendInitializer {
ExtensionPoint<unknown>,
{ impl: unknown; pluginId: string }
>();
#serviceHolder: EnumerableServiceHolder;
#serviceHolder: EnumerableServiceHolder | undefined;
#providedServiceFactories = new Array<ServiceFactory>();
#defaultApiFactories: ServiceFactory[];
constructor(serviceHolder: EnumerableServiceHolder) {
this.#serviceHolder = serviceHolder;
constructor(defaultApiFactories: ServiceFactory[]) {
this.#defaultApiFactories = defaultApiFactories;
}
async #getInitDeps(
@@ -70,7 +73,7 @@ export class BackendInitializer {
}
result.set(name, ep.impl);
} else {
const impl = await this.#serviceHolder.get(
const impl = await this.#serviceHolder!.get(
ref as ServiceRef<unknown>,
pluginId,
);
@@ -112,7 +115,16 @@ export class BackendInitializer {
`The ${coreServices.pluginMetadata.id} service cannot be overridden`,
);
}
this.#serviceHolder.add(feature);
if (
this.#providedServiceFactories.find(
sf => sf.service.id === feature.service.id,
)
) {
throw new Error(
`Duplicate service implementations provided for ${feature.service.id}`,
);
}
this.#providedServiceFactories.push(feature);
} else if (isInternalBackendFeature(feature)) {
if (feature.version !== 'v1') {
throw new Error(
@@ -155,6 +167,8 @@ export class BackendInitializer {
}
async #doStart(): Promise<void> {
this.#serviceHolder = new ServiceRegistry(this.#defaultApiFactories);
const featureDiscovery = await this.#serviceHolder.get(
featureDiscoveryServiceRef,
'root',
@@ -333,7 +347,7 @@ export class BackendInitializer {
// Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this
async #getRootLifecycleImpl(): Promise<BackendLifecycleImpl> {
const lifecycleService = await this.#serviceHolder.get(
const lifecycleService = await this.#serviceHolder!.get(
coreServices.rootLifecycle,
'root',
);
@@ -346,7 +360,7 @@ export class BackendInitializer {
async #getPluginLifecycleImpl(
pluginId: string,
): Promise<BackendPluginLifecycleImpl> {
const lifecycleService = await this.#serviceHolder.get(
const lifecycleService = await this.#serviceHolder!.get(
coreServices.lifecycle,
pluginId,
);
@@ -14,18 +14,15 @@
* limitations under the License.
*/
import { ServiceFactory, BackendFeature } from '@backstage/backend-plugin-api';
import { BackendFeature, ServiceFactory } from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { ServiceRegistry } from './ServiceRegistry';
import { Backend } from './types';
export class BackstageBackend implements Backend {
#services: ServiceRegistry;
#initializer: BackendInitializer;
constructor(apiFactories: ServiceFactory[]) {
this.#services = new ServiceRegistry(apiFactories);
this.#initializer = new BackendInitializer(this.#services);
constructor(defaultServiceFactories: ServiceFactory[]) {
this.#initializer = new BackendInitializer(defaultServiceFactories);
}
add(feature: BackendFeature | (() => BackendFeature)): void {