When bootstrap LB4 application I need to execute/load some actions on startup.
In the example above I need to register job task.
The CustomComponent is added to QueueJobApplication through QueueMixin.
The CustomComponent throw error becase don't have the repository bound to the context.
Is it possible to add QueueMixin after the RepositoryMixin are loaded all repositories to context?
export class CustomComponent implements Component {
constructor(
@repository(QueueRepository) private queueRepository: QueueRepository,
) {
this.registerProcess();
}
}
export function QueueMixin<T extends Class<any>>(superClass: T) {
return class extends superClass {
// A mixin class has to take in a type any[] argument!
// tslint:disable-next-line:no-any
constructor(...args: any[]) {
super(...args);
this.component(CustomComponent);
}
}
}
export class QueueJobApplication extends BootMixin(
QueueMixin(ServiceMixin(RepositoryMixin((RestApplication))),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
this.projectRoot = __dirname;
}
}
LB3 Example (script added under server/boot):
module.export = app => {
const {QueueRepository} = app.models;
QueueRepository.once('dataSourceAdded', () => {
// Execute/load some logic
});
}
For now I have loaded component after boot:
import {ApplicationConfig} from '@loopback/core';
import {QueueJobApplication} from './application';
import { CustomComponent } from './components';
export {QueueJobApplication};
export async function main(options: ApplicationConfig = {}) {
const app = new QueueJobApplication(options);
await app.boot();
app.component(CustomComponent);
await app.start();
const url = app.restServer.url;
console.log(`Server is running at ${url}`);
console.log(`Try ${url}/ping`);
return app;
}
@mrbatista unless you are having any problems, loading the component after boot should be fine. You might want to check out BootComponent too.
When bootstrap LB4 application I need to execute/load some actions on startup.
In the example above I need to register job task.
@mrbatista thank you for opening this issue. Your use case is an interesting one - I am expecting that many LB4 users will have similar needs and at the same time it poses few challenges for the implementation.
If we allow component constructors to receive datasource via dependency injection, then we will end up with two kinds of components:
I am concerned that such design will be difficult to use - if a component is not registered at the right time, then the application ends up in a possibly confusing error state.
I would like to propose a different solution:
Introduce a concept of a boot script. To allow Components to provide boot scripts, I think we should find a different name for this concept, for example "boot action" or "afterBoot action" "start action".
Allow components to export these actions via Provider classes, so that actions can receive dependencies via dependency injection. For example:
export class CustomComponent implements Component {
public startActionProviders: [RegisterProcessProvider];
}
export class RegisterProcessProvider implements Provider<StartAction> {
constructor(
@repository(QueueRepository) private queueRepository: QueueRepository,
) {}
value() {
return () => this.registerProcess();
}
}
Define a convention for where to put source files for application-specific start action providers. Implement a new booter that will load these files and register the providers with the app. For example:
// startup/print-info.provider.ts
export class PrintInfoProvider implements Provider<StartAction> {
constructor(
@inject(CoreBindings.APPLICATION_METADATA) private meta: ApplicationMetadata
) {}
value() {
return () => this.print();
}
print() {
const {name, version} = this.meta; // data from package.json
console.log(`${name || 'LoopBack app'} version ${version || '0.0.0'}`);
}
}
Modify app.start to invoke all "start actions". We need to carefully decide whether start actions should be executed before, after or in parallel with calling listen().
@mrbatista @raymondfeng @strongloop/loopback-next Thoughts? Can you spot any limitations in my proposal and/or perhaps propose a better solution?
@bajtos +1 on introducing scripts as a kind of artifacts. I would like to allow such scripts to participate in both start and stop optionally. There should be a booter to discover and load such scripts and invoke start as part of app.start as well as stop as part of app.stop.
We should probably introduce a new interface such as:
```ts
export interface LifeCycle {
start(): Promise
stop(): Promise
...
}
I would like to allow such scripts to participate in both start and stop optionally
Agreed 馃憤
```ts
export interface LifeCycle {
start(): Promise;
stop(): Promise;
...
}
Perhaps LifeCycleActions would be a better interface name? We may want to make all members optional.
@bajtos Your proposal looks good!
@mrbatista cool! Would you like to contribute this feature yourself? I am happy to give you pointers where to start and support you along the way.
@bajtos Yes the time as come to contribute. 馃
@mrbatista excellent! Here are few pointers to get your started, please let us know if you need any more help.
First of all, please read our DEVELOPING guide to familiarize with our conventions and learn how to setup your dev environment.
While working on the changes outlined below, don't forget to add new tests to cover all new features. It would be great if you could update/enhance documentation alongside the changes you are making.
Introduce a concept of a boot script. To allow Components to provide boot scripts, I think we should find a different name for this concept, for example "boot action" or "afterBoot action" "start action".
As mentioned in later comments, we want to introduce LifeCycleActions interface instead of a single "start action".
This new interface should be defined in @loopback/core package, for example in src/application.ts. Considering how many types are defined in that file, I think it would be best to introduce a new file src/core.types.ts and move certain types like ControllerClass and ApplicationMetadata to this new file, alongside with the new interface you are going to introduce.
Then you will need to implement a new Application method used to register LifeCycleActions providers. I am proposing the name lifeCycleProvider.
Modify
app.startto invoke all "start actions". We need to carefully decide whether start actions should be executed before, after or in parallel with calling listen().
Here you need to modify Application#start and Application#stop to invoke lifecycle listeners. If we decide to run lifecycle events in parallel with server start/stop actions, then we can make all servers to implement LifecycleActions interface and use the same code to start servers together with any custom lifecycle listeners.
I think these two changes would make for a nice pull request to get reviewed and landed before proceeding further.
Allow components to export these actions via Provider classes, so that actions can receive dependencies via dependency injection.
Enhance Component interface - add a property to hold an array of LifeCycleActions provider classes. See packages/core/src/component.ts#L21-L42
Modify mountComponent function to register all LifeCycleActions providers exported by the component, see packages/core/src/component.ts#L51-L69
Define a convention for where to put source files for application-specific start action providers. Implement a new booter that will load these files and register the providers with the app.
See https://github.com/strongloop/loopback-next/pull/1764 for an example of how to add a new Booter implementation.
@raymondfeng now that your pull request https://github.com/strongloop/loopback-next/pull/1928 has been landed, is there anything else we need to do to resolve this issue? (See the description at the top.)
@mrbatista Could you please check the solution implemented in https://github.com/strongloop/loopback-next/pull/1928 and let us know if it addresses your needs?
On the second thought, I am closing this story as done. Please open new issues for any additional changes needed to support your use cases.
Most helpful comment
@bajtos Yes the time as come to contribute. 馃