-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DO NOT REVIEW] feat: implement convention based boot #824
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.tgz | ||
dist* | ||
package |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package-lock=false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Copyright (c) IBM Corp. 2017. All Rights Reserved. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be |
||
Node module: @loopback/boot | ||
This project is licensed under the MIT License, full text below. | ||
|
||
-------- | ||
|
||
MIT license | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# @loopback/boot | ||
|
||
Boot package for LoopBack 4 to bootstrap convention based projects. | ||
|
||
## Overview | ||
|
||
This package provides the ability to bootstrap a LoopBack 4 project by | ||
automatically automatically associating artifacts and configuration with an | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2x |
||
application at runtime. | ||
|
||
The package is currently consumed as a [Mixin](http://loopback.io/doc/en/lb4/Mixin.html). | ||
|
||
It will automatically find all [Controller](http://loopback.io/doc/en/lb4/Controllers.html) | ||
classes by searching through all files in `controllers` directory ending in | ||
`.controller.js` and bind them to the application using `this.controller()`. | ||
|
||
Other Mixins can support automatic Booting by overriding the `boot()` method. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change the case of |
||
See example in [Advanced use](#advanced-use). | ||
|
||
## Installation | ||
|
||
```sh | ||
npm i --save @loopback/boot | ||
``` | ||
|
||
## Basic use | ||
|
||
Using `@loopback/boot` is simple. It is a Mixin and should be added to your | ||
application Class as shown below. | ||
|
||
```ts | ||
class BootedApplication extends BootMixin(Application) { | ||
constructor(options?: ApplicationConfig) { | ||
super(options); | ||
} | ||
} | ||
``` | ||
|
||
### Configuration | ||
|
||
Configuration options for boot can be set via the `ApplicationConfig` object | ||
(`options`). The following options are supported: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am against adding boot configuration to
Just think about it - how is the code loading an application supposed to know what directories the application stores its controller source files? IMO, the configuration of file paths should be provided by the application class. The |
||
|
||
|Name|Default|Description| | ||
|-|-|-| | ||
|`boot.rootDir`|`process.cwd()`|The root directory for the project. All other paths are resolved relative to this path.| | ||
|`boot.controllerDirs`|`controllers`|String or Array of directory paths to search in for controllers. Paths may be relative to `rootDir` or absolute.| | ||
|`boot.controllerExts`|`controller.js`|String or Array of file extensions to consider as controller files.| | ||
|
||
*Example* | ||
```ts | ||
app = new BootedApplication({ | ||
rest: { | ||
port: 3000 | ||
}, | ||
boot: { | ||
rootDir: '/absolute/path/to/my/app/root', | ||
controllerDirs: ['controllers', 'ctrls'], | ||
controllerExts: ['controller.js', '.ctrl.js'], | ||
}, | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs an end tag for the code above |
||
## Advanced use | ||
|
||
An extension developer can support the **boot** process by writing a mixin and | ||
overriding the `boot()` function as shown below. If a user is using | ||
`@loopback/boot`, it will automatically boot your artifact as well. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm conflicted about this approach. On the one hand, it gives us finer control over the boot order (which is something people have complained about with LB3), but it requires users to be careful about how they use mixins, and requires an awful lot of mixin chaining. It could end up looking like this: export class MyApplication extends ThirdScript(SecondScript(FirstScript(BootMixin(RestApplication)))) {
// etc...
} It's not a tremendously painful experience, but I think it'd be nice if we added some sugar to simplify this. If you check out the TypeScript handbook page on Mixins, it shows a pattern for applying an array of mixins without requiring users to extend things in this way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I don't think Please note that a single I strongly recommend that we follow a similar approach at https://github.com/strongloop/loopback-boot/tree/master. The main idea is as follows:
export interface BootLifeCycle {
discover?(...);
load?(...);
transform?(...);
resolve?(...);
start?(...);
stop?(...);
} A booter can choose to implement what methods to implement.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comments Raymond. Looking for some clarification.
With @kjdelisle 's change, you can control Mixin order by passing them in via an array. Just trying to understand the value add of having phases for boot vs. just booting by discovering artifacts via Mixin followed by running scripts in a boot folder like current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No. Boot script is just one of the artifact types we support in LoopBack 3.x. There are different plugins for model definitions, data sources, middleware, components, and boot scripts. Each plugin is responsible for loading/compiling/executing its own kind of artifact.
One pass is usually not good enough to handle artifacts that have references to other ones. For example, model
Resolve - connect multiple artifacts, for example, set up the target model reference for a relation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. But shouldn't references be resolved by Dependency Injection when needed if boot sets up bindings properly which is what it should be doing ...? And I see the I'm also not sure what it means to start / stop an artifact ... or how stop will even be called? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea sorry I meant There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @raymondfeng Your proposed changes are out of scope for the task. If this is something we want, then we should create a new task, groom it and poker it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, the approach taken by the PR is architecturally flawed. I'm not sure if we really want to land it as is. The boot module is fundamental to the declarative support for LoopBack 4. I think we need to start with the right path from the beginning. I don't mind starting with a single There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great discussion! Let me clarify few possible confusions I anticipate from the discussion above before I post my opinions. In LoopBack 3.x, there are two versions of loopback-boot.
This is definitely the case in loopback-boot 2.x. I don't know how the new 3.x version handles boot scripts.
Even though this has been clarified that @virkt25 meant
I agree with both of you. IMO, we should not aim for a fully flexible bootstrapper in this first iteration targeting our MVP milestone. For MVP, it's ok to have a hard-coded bootstrapper with no (or very little) extension points. At the same time, I agree that mixin composition is not a good architecture for our bootstrapper and therefore we should not pursue it even for MVP, because upgrading from mixin composition to a different architecture would be too disruptive. TBH, I find Raymond's I am proposing to explore our "Sequence" design pattern we are using for composing request handling middleware and see if and how it can be applied to the boot process too. I see two benefits of such approach:
Right now, the boot process (both Let me post a mock-up boot sequence to illustrate my idea: type EnvironmentVariables = {[name: string]: string};
interface BootOptions {
projectRoot: string;
env?: EnvironmentVariables;
}
interface AppBootSequence<App> {
boot(app: App, options: BootOptions);
}
class MyBootSequence implements AppBootSequence<MyApplication> {
/* constructor receiving dependencies - I am leaving that out for brevity */
public boot(app: MyApplication, options: BootOptions) {
this.setupDataSources(app, options);
this.setupRepositoriesAndModels(app, options);
this.resolveModelRelations(app, options);
this.setupControllers(app, options);
this.start(app, env);
}
}
// setupDataSources step provided by @loopback/repository or similar
setupDataSources(app, options) {
const controllersDir = path.resolve(
options.projectRoot,
'controllers' || options.controllersDir);
const controllerExts = options.controllerExts || 'controller.js';
bootClassArtifacts(app, controllersDir, controllerExts, a => app.controller(a));
}
// usage in Application
// this code should be scaffolded by our CLI tooling
// together with MyBootSequence template
class MyApplication {
constructor(/*...*/) {
this.bind('bootstrapper').toClass(MyBootSequence)
}
async boot(env: EnvironmentVariables) {
const options: BootOptions = {
projectRoot: __dirname,
env: env,
}
const sequence = await this.get('bootstrapper');
await sequence.boot(app, options);
}
} Since this design is similar with REST Sequence Handler, I would expect it should be reasonably easy to leverage the extension point #657 and action composition #713 features that @raymondfeng is working on, to get an even more flexible solution. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me expand a bit on why I am against mixin composition. Consider (I admit my solution proposed above does not address this problem entirely, because in the Sequence design, individual sequence steps have no control of the order in which they are executed. OTOH, the action composition technique proposed by Raymond should address that part in mostly backwards-compatible way.) |
||
|
||
```ts | ||
function TestMixin<T extends Constructor<any>>(superClass: T) { | ||
return class extends superClass { | ||
constructor(...args: any[]) { | ||
super(...args); | ||
} | ||
|
||
async boot() { | ||
// Your custom config | ||
const repoDir = this.options.boot.repoDir || 'repositories'; | ||
// We call the convenience method to boot class artifacts | ||
await this.bootClassArtifacts(repoDir, 'repository.js', 'testMixinRepo'); | ||
|
||
// IMPORTANT: This line must be added so all other artifacts can be booted | ||
// automatically and regardless of the order of the Mixins. | ||
if (super.boot) await super.boot(); | ||
} | ||
}; | ||
} | ||
``` | ||
|
||
## Related resources | ||
|
||
**Coming Soon** Link to Boot Docs. | ||
|
||
## Contributions | ||
|
||
- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines) | ||
- [Join the team](https://github.com/strongloop/loopback-next/issues/110) | ||
|
||
## Tests | ||
|
||
run `npm test` from the root folder. | ||
|
||
## Contributors | ||
|
||
See [all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). | ||
|
||
## License | ||
|
||
MIT |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"content": [ | ||
"index.ts", | ||
"src/index.ts", | ||
"src/types.ts", | ||
"src/utils.ts", | ||
"src/mixins/boot.mixin.ts" | ||
], | ||
"codeSectionDepth": 4, | ||
"assets": { | ||
"/": "/docs", | ||
"/docs": "/docs" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Copyright IBM Corp. 2013,2017. All Rights Reserved. | ||
// Node module: @loopback/boot | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export * from './dist/src'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Copyright IBM Corp. 2013,2017. All Rights Reserved. | ||
// Node module: @loopback/boot | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
module.exports = require('./dist/src'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// Copyright IBM Corp. 2013,2017. All Rights Reserved. | ||
// Node module: @loopback/boot | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export * from './src'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"name": "@loopback/boot", | ||
"version": "4.0.0-alpha.1", | ||
"description": "Boot handler for LoopBack 4 apps", | ||
"engines": { | ||
"node": ">=8" | ||
}, | ||
"main": "index.js", | ||
"scripts": { | ||
"acceptance": "mocha --opts ../../test/mocha.opts dist/test/acceptance/**/*.js", | ||
"build": "lb-tsc", | ||
"build:current": "lb-tsc", | ||
"build:apidocs": "lb-apidocs", | ||
"clean": "lb-clean loopback-boot*.tgz dist package api-docs", | ||
"prepare": "npm run build", | ||
"pretest": "npm run clean && npm run build", | ||
"test": "mocha --opts ../../test/mocha.opts dist/test/", | ||
"unit": "mocha --opts ../../test/mocha.opts dist/test/unit/", | ||
"verify": "npm pack && tar xf loopback-boot*.tgz && tree package && npm run clean" | ||
}, | ||
"author": "IBM", | ||
"license": "MIT", | ||
"keywords": ["LoopBack", "Boot"], | ||
"files": [ | ||
"README.md", | ||
"index.js", | ||
"index.d.ts", | ||
"dist/src", | ||
"api-docs", | ||
"src" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/strongloop/loopback-next.git" | ||
}, | ||
"devDependencies": { | ||
"@loopback/build": "^4.0.0-alpha.7", | ||
"@loopback/core": "^4.0.0-alpha.26", | ||
"@loopback/rest": "^4.0.0-alpha.15", | ||
"@loopback/testlab": "^4.0.0-alpha.17" | ||
}, | ||
"dependencies": { | ||
"@loopback/context": "^4.0.0-alpha.23", | ||
"debug": "^3.1.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Copyright IBM Corp. 2013,2017. All Rights Reserved. | ||
// Node module: @loopback/boot | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export * from './mixins/boot.mixin'; | ||
export * from './utils'; | ||
export * from './types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd switch the trigger to
test:rebuild
or something similar.