From 6e7a1b07f3986cb9a68267077c170ecd36e428c3 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 15 Aug 2023 00:10:21 +0200 Subject: [PATCH] backend-app-api: keep track of the provided services Signed-off-by: Vincenzo Scamporlino --- .../src/wiring/BackendInitializer.test.ts | 38 +++++++++---------- .../src/wiring/BackendInitializer.ts | 28 ++++++++++---- .../src/wiring/BackstageBackend.ts | 9 ++--- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index b90bcf6691..791b9b1c3a 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -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({ id: 'a' }); const extB = createExtensionPoint({ 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({ id: 'a' }); init.add( createBackendPlugin({ diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 2e90e6eb2c..b6f79e19da 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -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; @@ -47,10 +48,12 @@ export class BackendInitializer { ExtensionPoint, { impl: unknown; pluginId: string } >(); - #serviceHolder: EnumerableServiceHolder; + #serviceHolder: EnumerableServiceHolder | undefined; + #providedServiceFactories = new Array(); + #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, 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 { + 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 { - 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 { - const lifecycleService = await this.#serviceHolder.get( + const lifecycleService = await this.#serviceHolder!.get( coreServices.lifecycle, pluginId, ); diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index beeefc1b1b..0ea55bc1c7 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -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 {