backend-app-api: implement handling of default factories is ServiceRegistry
Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: Johan Haals <johan.haals@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ const ref1 = createServiceRef<{ x: number; pluginId: string }>({
|
||||
const sf1 = createServiceFactory({
|
||||
service: ref1,
|
||||
deps: {},
|
||||
factory: async ({}) => {
|
||||
factory: async () => {
|
||||
return async pluginId => {
|
||||
return { x: 1, pluginId };
|
||||
};
|
||||
@@ -39,7 +39,7 @@ const ref2 = createServiceRef<{ x: number; pluginId: string }>({
|
||||
const sf2 = createServiceFactory({
|
||||
service: ref2,
|
||||
deps: {},
|
||||
factory: async ({}) => {
|
||||
factory: async () => {
|
||||
return async pluginId => {
|
||||
return { x: 2, pluginId };
|
||||
};
|
||||
@@ -48,13 +48,43 @@ const sf2 = createServiceFactory({
|
||||
const sf2b = createServiceFactory({
|
||||
service: ref2,
|
||||
deps: {},
|
||||
factory: async ({}) => {
|
||||
factory: async () => {
|
||||
return async pluginId => {
|
||||
return { x: 22, pluginId };
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const refDefault1 = createServiceRef<{ x: number; pluginId: string }>({
|
||||
id: '1',
|
||||
defaultFactory: async service =>
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {},
|
||||
factory: async () => async pluginId => ({ x: 10, pluginId }),
|
||||
}),
|
||||
});
|
||||
|
||||
const refDefault2a = createServiceRef<{ x: number; pluginId: string }>({
|
||||
id: '2a',
|
||||
defaultFactory: async service =>
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {},
|
||||
factory: async () => async pluginId => ({ x: 20, pluginId }),
|
||||
}),
|
||||
});
|
||||
|
||||
const refDefault2b = createServiceRef<{ x: number; pluginId: string }>({
|
||||
id: '2b',
|
||||
defaultFactory: async service =>
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {},
|
||||
factory: async () => async pluginId => ({ x: 220, pluginId }),
|
||||
}),
|
||||
});
|
||||
|
||||
describe('ServiceRegistry', () => {
|
||||
it('should return undefined if there is no factory defined', async () => {
|
||||
const registry = new ServiceRegistry([]);
|
||||
@@ -101,4 +131,43 @@ describe('ServiceRegistry', () => {
|
||||
pluginId: 'catalog',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the defaultFactory from the ref if not provided to the registry', async () => {
|
||||
const registry = new ServiceRegistry([]);
|
||||
const factory = registry.get(refDefault1)!;
|
||||
expect(factory).toEqual(expect.any(Function));
|
||||
await expect(factory('catalog')).resolves.toEqual({
|
||||
x: 10,
|
||||
pluginId: 'catalog',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return the defaultFactory from the ref if provided to the registry', async () => {
|
||||
const registry = new ServiceRegistry([sf1]);
|
||||
const factory = registry.get(refDefault1)!;
|
||||
expect(factory).toEqual(expect.any(Function));
|
||||
await expect(factory('catalog')).resolves.toEqual({
|
||||
x: 1,
|
||||
pluginId: 'catalog',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle duplicate defaultFactories by duplicating the implementations', async () => {
|
||||
const registry = new ServiceRegistry([]);
|
||||
const factoryA = registry.get(refDefault2a)!;
|
||||
const factoryB = registry.get(refDefault2b)!;
|
||||
expect(factoryA).toEqual(expect.any(Function));
|
||||
expect(factoryB).toEqual(expect.any(Function));
|
||||
await expect(factoryA('catalog')).resolves.toEqual({
|
||||
x: 20,
|
||||
pluginId: 'catalog',
|
||||
});
|
||||
await expect(factoryB('catalog')).resolves.toEqual({
|
||||
x: 220,
|
||||
pluginId: 'catalog',
|
||||
});
|
||||
expect(await factoryA('catalog')).toBe(await factoryA('catalog'));
|
||||
expect(await factoryB('catalog')).toBe(await factoryB('catalog'));
|
||||
expect(await factoryA('catalog')).not.toBe(await factoryB('catalog'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,35 +14,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
AnyServiceFactory,
|
||||
ServiceFactory,
|
||||
FactoryFunc,
|
||||
ServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
export class ServiceRegistry {
|
||||
readonly #implementations: Map<string, Map<string, unknown>>;
|
||||
readonly #factories: Map<string, AnyServiceFactory>;
|
||||
readonly #providedFactories: Map<string, ServiceFactory>;
|
||||
readonly #loadedDefaultFactories: Map<Function, ServiceFactory>;
|
||||
readonly #implementations: Map<ServiceFactory, Map<string, unknown>>;
|
||||
|
||||
constructor(factories: AnyServiceFactory[]) {
|
||||
this.#factories = new Map(factories.map(f => [f.service.id, f]));
|
||||
constructor(factories: ServiceFactory<any>[]) {
|
||||
this.#providedFactories = new Map(factories.map(f => [f.service.id, f]));
|
||||
this.#loadedDefaultFactories = new Map();
|
||||
this.#implementations = new Map();
|
||||
}
|
||||
|
||||
get<T>(ref: ServiceRef<T>): FactoryFunc<T> | undefined {
|
||||
const factory = this.#factories.get(ref.id);
|
||||
if (!factory) {
|
||||
let factory = this.#providedFactories.get(ref.id);
|
||||
const { defaultFactory } = ref;
|
||||
if (!factory && !defaultFactory) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return async (pluginId: string): Promise<T> => {
|
||||
let implementations = this.#implementations.get(ref.id);
|
||||
if (!factory) {
|
||||
let loadedFactory = this.#loadedDefaultFactories.get(defaultFactory!);
|
||||
if (!loadedFactory) {
|
||||
loadedFactory = (await defaultFactory!(ref)) as ServiceFactory;
|
||||
this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory);
|
||||
}
|
||||
factory = loadedFactory;
|
||||
}
|
||||
|
||||
let implementations = this.#implementations.get(factory);
|
||||
if (implementations) {
|
||||
if (implementations.has(pluginId)) {
|
||||
return implementations.get(pluginId) as T;
|
||||
}
|
||||
} else {
|
||||
implementations = new Map();
|
||||
this.#implementations.set(ref.id, implementations);
|
||||
this.#implementations.set(factory, implementations);
|
||||
}
|
||||
|
||||
const factoryDeps = Object.fromEntries(
|
||||
|
||||
Reference in New Issue
Block a user