Skip to content

Commit

Permalink
refactor(core-kernel): pass the booted service provider to boot/dispo…
Browse files Browse the repository at this point in the history
…seWhen
  • Loading branch information
faustbrian authored Nov 13, 2019
1 parent 4fc0559 commit acc5e16
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,35 @@ export class DeferredServiceProvider extends ServiceProvider {
return process.env.DEFFERED_DISABLE === "true";
}
}

export class DeferredBootServiceProvider extends ServiceProvider {
public async register(): Promise<void> {}

public name(): string {
return "stub";
}

public version(): string {
return "version";
}

public async bootWhen(serviceProvider?: string): Promise<boolean> {
return process.env.DEFFERED_ENABLE === "true" && serviceProvider === "expected-stub";
}
}

export class DeferredDisposeServiceProvider extends ServiceProvider {
public async register(): Promise<void> {}

public name(): string {
return "stub";
}

public version(): string {
return "version";
}

public async disposeWhen(serviceProvider?: string): Promise<boolean> {
return process.env.DEFFERED_DISABLE === "true" && serviceProvider === "expected-stub";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
FaultyBootServiceProvider,
RequiredFaultyBootServiceProvider,
DeferredServiceProvider,
DeferredBootServiceProvider,
DeferredDisposeServiceProvider,
} from "./__stubs__/service-providers";
import { ServiceProviderCannotBeBooted } from "@packages/core-kernel/src/exceptions/plugins";
import { InternalEvent, StateEvent } from "@packages/core-kernel/src/enums/events";
Expand Down Expand Up @@ -144,6 +146,48 @@ describe("BootServiceProviders", () => {

expect(spyBoot).not.toHaveBeenCalled();
});

it("should react if the service provider does match the expected provider", async () => {
const serviceProvider: ServiceProvider = new DeferredBootServiceProvider();
const spyBoot = jest.spyOn(serviceProvider, "boot");
serviceProviderRepository.set("stub", serviceProvider);

process.env.DEFFERED_ENABLE = "false";

await app.resolve<BootServiceProviders>(BootServiceProviders).bootstrap();

serviceProviderRepository.defer("stub");

process.env.DEFFERED_ENABLE = "true";

await app
.get<MemoryEventDispatcher>(Identifiers.EventDispatcherService)
.dispatch(InternalEvent.ServiceProviderBooted, { name: "expected-stub" });

await sleep(500);

expect(spyBoot).toHaveBeenCalled();
});

it("should not react if the service provider does not match the expected provider", async () => {
const serviceProvider: ServiceProvider = new DeferredBootServiceProvider();
const spyBoot = jest.spyOn(serviceProvider, "boot");
serviceProviderRepository.set("stub", serviceProvider);

await app.resolve<BootServiceProviders>(BootServiceProviders).bootstrap();

serviceProviderRepository.defer("stub");

process.env.DEFFERED_ENABLE = "true";

await app
.get<MemoryEventDispatcher>(Identifiers.EventDispatcherService)
.dispatch(InternalEvent.ServiceProviderBooted, { name: "another-stub" });

await sleep(500);

expect(spyBoot).not.toHaveBeenCalled();
});
});

describe("DeferredServiceProvider - disposeWhen", () => {
Expand Down Expand Up @@ -190,6 +234,26 @@ describe("BootServiceProviders", () => {
const spyDispose = jest.spyOn(serviceProvider, "dispose");
serviceProviderRepository.set("stub", serviceProvider);

await app.resolve<BootServiceProviders>(BootServiceProviders).bootstrap();

serviceProviderRepository.defer("stub");

process.env.DEFFERED_ENABLE = "true";

await app
.get<MemoryEventDispatcher>(Identifiers.EventDispatcherService)
.dispatch(InternalEvent.ServiceProviderBooted, { name: "stub" });

await sleep(500);

expect(spyDispose).not.toHaveBeenCalled();
});

it("should react if the service provider does match the expected provider", async () => {
const serviceProvider: ServiceProvider = new DeferredDisposeServiceProvider();
const spyDispose = jest.spyOn(serviceProvider, "dispose");
serviceProviderRepository.set("stub", serviceProvider);

process.env.DEFFERED_ENABLE = "false";

await app.resolve<BootServiceProviders>(BootServiceProviders).bootstrap();
Expand All @@ -200,7 +264,27 @@ describe("BootServiceProviders", () => {

await app
.get<MemoryEventDispatcher>(Identifiers.EventDispatcherService)
.dispatch(InternalEvent.ServiceProviderBooted, { name: "stub" });
.dispatch(InternalEvent.ServiceProviderBooted, { name: "expected-stub" });

await sleep(500);

expect(spyDispose).toHaveBeenCalled();
});

it("should not react if the service provider does not match the expected provider", async () => {
const serviceProvider: ServiceProvider = new DeferredDisposeServiceProvider();
const spyDispose = jest.spyOn(serviceProvider, "dispose");
serviceProviderRepository.set("stub", serviceProvider);

await app.resolve<BootServiceProviders>(BootServiceProviders).bootstrap();

serviceProviderRepository.defer("stub");

process.env.DEFFERED_ENABLE = "true";

await app
.get<MemoryEventDispatcher>(Identifiers.EventDispatcherService)
.dispatch(InternalEvent.ServiceProviderBooted, { name: "another-stub" });

await sleep(500);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,67 +26,72 @@ export class BootServiceProviders implements Bootstrapper {
@inject(Identifiers.Application)
private readonly app!: Application;

/**
* @private
* @type {ServiceProviderRepository}
* @memberof BootServiceProviders
*/
@inject(Identifiers.ServiceProviderRepository)
private readonly serviceProviders!: ServiceProviderRepository;

/**
* @returns {Promise<void>}
* @memberof RegisterProviders
*/
public async bootstrap(): Promise<void> {
const serviceProviders: ServiceProviderRepository = this.app.get<ServiceProviderRepository>(
Identifiers.ServiceProviderRepository,
);

for (const [name, serviceProvider] of serviceProviders.all()) {
for (const [name, serviceProvider] of this.serviceProviders.all()) {
const serviceProviderName: string | undefined = serviceProvider.name();

assert.defined<string>(serviceProviderName);

if (await serviceProvider.bootWhen()) {
try {
await serviceProviders.boot(name);
await this.serviceProviders.boot(name);
} catch (error) {
const isRequired: boolean = await serviceProvider.required();

if (isRequired) {
throw new ServiceProviderCannotBeBooted(serviceProviderName, error.message);
}

serviceProviders.fail(serviceProviderName);
this.serviceProviders.fail(serviceProviderName);
}
} else {
serviceProviders.defer(name);
this.serviceProviders.defer(name);
}

// Register the "enable/disposeWhen" listeners to be triggered on every block. Use with care!
this.app.events.listen(
StateEvent.BlockApplied,
async () => await this.changeState(name, serviceProvider, serviceProviders),
);
this.app.events.listen(StateEvent.BlockApplied, async () => await this.changeState(name, serviceProvider));

// We only want to trigger this if another service provider has been booted to avoid an infinite loop.
this.app.events.listen(InternalEvent.ServiceProviderBooted, async ({ data }) => {
if (data.name !== name) {
await this.changeState(name, serviceProvider, serviceProviders);
await this.changeState(name, serviceProvider, data.name);
}
});
}
}

private async changeState(
name: string,
serviceProvider: ServiceProvider,
serviceProviders: ServiceProviderRepository,
): Promise<void> {
if (serviceProviders.failed(name)) {
/**
* @private
* @param {string} name
* @param {ServiceProvider} serviceProvider
* @param {string} [previous]
* @returns {Promise<void>}
* @memberof BootServiceProviders
*/
private async changeState(name: string, serviceProvider: ServiceProvider, previous?: string): Promise<void> {
if (this.serviceProviders.failed(name)) {
return;
}

if (serviceProviders.loaded(name) && (await serviceProvider.disposeWhen())) {
await serviceProviders.dispose(name);
if (this.serviceProviders.loaded(name) && (await serviceProvider.disposeWhen(previous))) {
await this.serviceProviders.dispose(name);
}

/* istanbul ignore else */
if (serviceProviders.deferred(name) && (await serviceProvider.bootWhen())) {
await serviceProviders.boot(name);
if (this.serviceProviders.deferred(name) && (await serviceProvider.bootWhen(previous))) {
await this.serviceProviders.boot(name);
}
}
}
16 changes: 14 additions & 2 deletions packages/core-kernel/src/providers/service-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,32 @@ export abstract class ServiceProvider {
/**
* Enable the service provider when the given conditions are met.
*
* @remarks
* The [serviceProvider] variable will be undefined unless the [InternalEvent.ServiceProviderBooted]
* event triggered a state change check in which case the name of the booteed service provider will be
* passed down to this method as packages might rely on each other being booted in a specific order.
*
* @param {string} [serviceProvider]
* @returns {Promise<boolean>}
* @memberof ServiceProvider
*/
public async bootWhen(): Promise<boolean> {
public async bootWhen(serviceProvider?: string): Promise<boolean> {
return true;
}

/**
* Disable the service provider when the given conditions are met.
*
* @remarks
* The [serviceProvider] variable will be undefined unless the [InternalEvent.ServiceProviderBooted]
* event triggered a state change check in which case the name of the booteed service provider will be
* passed down to this method as packages might rely on each other being booted in a specific order.
*
* @param {string} [serviceProvider]
* @returns {Promise<boolean>}
* @memberof ServiceProvider
*/
public async disposeWhen(): Promise<boolean> {
public async disposeWhen(serviceProvider?: string): Promise<boolean> {
return false;
}

Expand Down

0 comments on commit acc5e16

Please sign in to comment.