-
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
PoC showing how to mount an LB3 app and include its REST API in OpenAPI spec [DO NOT MERGE] #2318
Changes from 24 commits
518312d
0534f4a
c33613d
450e637
c8433d3
720991c
9217594
71d731d
8c07d48
4cdcd02
d65dff0
dc8d3b9
1ffa7ba
0f2a1c7
4c80bef
eb7942f
3eb7f2c
ce9e311
ad37e5c
5dd84b7
d53f67e
0ecf71b
f9d9fe5
b5bed33
07cc734
54579dd
8e95629
8933f63
379e0c0
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 |
---|---|---|
|
@@ -9,3 +9,4 @@ sandbox/**/* | |
*.json | ||
CHANGELOG.md | ||
*.yml | ||
examples/lb3app/legacy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Spike notes | ||
|
||
In this spike pull request, I am showing how we can allow LB3 developers to | ||
expose their legacy applications in an LB4 project and thus allow them to run | ||
both old LB3 and new LB4 endpoints in the same process. | ||
|
||
The solution has two major parts: | ||
|
||
- `app.mountExpressRouter(basePath, router, spec)` | ||
- `Lb3AppBooter` | ||
|
||
## mountExpressRouter | ||
|
||
```ts | ||
mountExpressRouter( | ||
basePath: string, | ||
router: ExpressRequestHandler, | ||
spec?: RouterSpec, | ||
): void; | ||
``` | ||
|
||
This new API is building on top of StaticAssetsRoute and allows LB4 app | ||
developers to add arbitrary set of Express routes and provide OpenAPI spec. | ||
|
||
The new request-handling pipeline has the following steps now: | ||
|
||
1. Request-preprocessing middleware like CORS, this is not customizable yet. | ||
2. Native LoopBack 4 routes (controller methods or route handlers). | ||
3. External Express routers (if the request was not handled yet). | ||
4. Static assets (if the request was not handled yet). | ||
|
||
See | ||
[external-express-routes.ts](./packages/rest/src/router/external-express-routes.ts) | ||
for most of the implementation details. | ||
|
||
## Lb3AppBooter | ||
|
||
This booter is responsible for setting up LoopBack 3 application, obtaining | ||
OpenAPI spec describing app's REST API and mounting the app on LB4. | ||
|
||
I am proposing to put this booter into a standalone npm package to allow us to | ||
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.
Loose coupling FTW. Getting rid of it from the framework (in future) will also be simpler and neater. |
||
get usage statistics from npm download numbers. | ||
|
||
See [booter-lb3app/README.md](./packages/booter-lb3app) for documentation on how | ||
to use this new booter. The implementation can be found in | ||
[lb3app.booter.ts](.//packages/booter-lb3app/src/lb3app.booter.ts), | ||
|
||
## Example app | ||
|
||
The last part is an example application exposing | ||
[loopback-getting-started](https://github.com/strongloop/loopback-getting-started) | ||
via LoopBack 4. | ||
|
||
- [lb3app/README](./examples/lb3app/README.md) describes the steps needed to add | ||
LB3 app into an LB4 project | ||
- [lb3app/legacy/](./examples/lb3app/legacy) contains the imported | ||
getting-started app | ||
- [lb3app/src/application.js](./examples/lb3app/src/application.ts) contains the | ||
modified Application class. | ||
|
||
## Next steps | ||
|
||
I am proposing to create the following user stories to turn this PoC into a | ||
production-ready implementation. | ||
|
||
1. Implement `app.mountExpressRouter()`, including test coverage and | ||
documentation. | ||
|
||
2. Describe request handling steps. Explain the order in which different | ||
middleware and route handlers are invoked in LB4, what extension points are | ||
provided. This content can go into a new section to [Advanced topics in | ||
Sequence](https://loopback.io/doc/en/lb4/Sequence.html#advanced-topics] or we | ||
can create an entirely new page. | ||
|
||
3. Implement `@loopback/booter-lb3app`, including test coverage and | ||
documentation. As part of this effort, create a new package to hold types | ||
needed by Booter implementations. The idea is to decouple API contract | ||
between Bootstrapper and Booters from the actual Bootstrapper implementation. | ||
`@loopback/booter-lb3app` should not have any runtime dependency on | ||
`@loopback/boot` (a dev-dependency for tests is ok). | ||
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 was thinking about this point little bit more and came to the conclusion that we should use a different approach. Instead of moving booter-related types to a new package that 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. Done in refactor(booter-lb3app): move boot/core/rest to dev-dependencies 8e95629 |
||
|
||
4. Create an example app based on `loopback-getting-started` | ||
(`@loopback/example-lb3app`) to showcase LB3+LB4 integration. Add acceptance | ||
tests to verify few REST endpoints, also a test to verify that LB3 endpoints | ||
are included in the OpenAPI spec produced by the LB4 app. | ||
|
||
5. Migration guide. | ||
|
||
- Start by reorganizing the | ||
[docs for LoopBack 3.x users](https://loopback.io/doc/en/lb4/LoopBack-3.x.html): | ||
keep the current page as a top-level overview. | ||
- Move most of the content into a new page with a name like "Understanding | ||
the differences". | ||
- Create a new page with a name like "Migrating from v3". Describe how to use | ||
`Lb3AppBooter`. Explain the differences (breaking changes) introduced by | ||
the migration. I believe the only difference is in the spec format: LB3 | ||
produces Swagger (OpenApi v2), LB4 produces OpenApi v3. | ||
|
||
6. Spike: dynamically updated LB3 API (routes + spec). In LB3, it's possible to | ||
change the shape of REST API at runtime by adding or removing shared methods | ||
or even entire models. The PoC version of LB3+LB4 does not support spec | ||
updates, we need to do a spike to find the best solution. (For example, | ||
`mountExpressRouter` can accept a spec getter instead of the spec object.) As | ||
for the actual request handling, the PoC version reloads route handlers in | ||
"full" mode but does not reload in "router" mode. As part of the stories | ||
created from the spike, we should fix "router" mode to reload the LB3 handler | ||
when needed. |
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,2 @@ | ||
dist | ||
*.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"bracketSpacing": false, | ||
"singleQuote": true, | ||
"printWidth": 80, | ||
"trailingComma": "all" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"editor.rulers": [80], | ||
"editor.tabCompletion": "on", | ||
"editor.tabSize": 2, | ||
"editor.trimAutoWhitespace": true, | ||
"editor.formatOnSave": true, | ||
|
||
"files.exclude": { | ||
"**/.DS_Store": true, | ||
"**/.git": true, | ||
"**/.hg": true, | ||
"**/.svn": true, | ||
"**/CVS": true, | ||
"dist": true, | ||
}, | ||
"files.insertFinalNewline": true, | ||
"files.trimTrailingWhitespace": true, | ||
|
||
"tslint.ignoreDefinitionFiles": true, | ||
"typescript.tsdk": "./node_modules/typescript/lib" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
// See https://go.microsoft.com/fwlink/?LinkId=733558 | ||
// for the documentation about the tasks.json format | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "Watch and Compile Project", | ||
"type": "shell", | ||
"command": "npm", | ||
"args": ["--silent", "run", "build:watch"], | ||
"group": { | ||
"kind": "build", | ||
"isDefault": true | ||
}, | ||
"problemMatcher": "$tsc-watch" | ||
}, | ||
{ | ||
"label": "Build, Test and Lint", | ||
"type": "shell", | ||
"command": "npm", | ||
"args": ["--silent", "run", "test:dev"], | ||
"group": { | ||
"kind": "test", | ||
"isDefault": true | ||
}, | ||
"problemMatcher": ["$tsc", "$tslint5"] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Copyright (c) IBM Corp. 2017,2018. All Rights Reserved. | ||
Node module: @loopback/example-lb3app | ||
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,118 @@ | ||
# @loopback/example-lb3app | ||
|
||
This example demonstrates how to mount your existing LoopBack 3 application in a | ||
new LoopBack 4 project. | ||
|
||
## Tutorial | ||
|
||
1. Create a new LoopBack 4 project using `lb4 app`. | ||
|
||
``` | ||
$ lb4 app | ||
``` | ||
|
||
2. Create a new directory `legacy` and copy your existing LoopBack 3 application | ||
there. You should end up with the following directory layout | ||
|
||
``` | ||
legacy/ | ||
# LoopBack 3 application in JavaScript | ||
common/ | ||
models/ | ||
# LB3 model files | ||
server/ | ||
boot/ | ||
# LB3 boot scripts | ||
public/ | ||
# front-end assets (LB4 way) | ||
src/ | ||
# LoopBack 4 application in TypeScript | ||
``` | ||
|
||
3. Move LB3 dependencies to the main package.json file and remove | ||
`legacy/package.json`. Typically you will need to add the following entries, | ||
plus any connectors or components you are using in your LB3 application. | ||
|
||
```json | ||
{ | ||
"compression": "^1.0.3", | ||
"cors": "^2.5.2", | ||
"helmet": "^3.15.0", | ||
"loopback": "^3.0.0", | ||
"loopback-boot": "^3.1.1", | ||
"loopback-connector-mysql": "^5.3.1", | ||
"serve-favicon": "^2.0.1", | ||
"strong-error-handler": "^3.2.0" | ||
} | ||
``` | ||
|
||
4. Disable error handling in your LB3 app, leave it for the new LB4 app. | ||
|
||
- Remove `legacy/server/middleware.development.json` | ||
- Edit `legacy/server/middleware.json` and remove the following two entries: | ||
- `final` >> `loopback#urlNotFound` | ||
- `final:after` >> `strong-error-handler` | ||
- Remove `strong-error-handler` from `package.json` dependencies. | ||
|
||
5. Move your front-end files from `legacy/client` to `public/` directory and | ||
disable static assets in your LB3 app by removing the following entry in | ||
`legacy/server/middleware.json`: | ||
|
||
- `files` >> `loopback#static` | ||
|
||
6. Install and configure `@loopback/booter-lb3app` to boot and mount the LB3 application: | ||
|
||
1. `npm install --save @loopback/booter-lb3app` | ||
|
||
2. Import the booter at the top of your `src/application.ts` file. | ||
|
||
```ts | ||
import {Lb3AppBooter} from '@loopback/booter-lb3app'; | ||
``` | ||
|
||
3. Register the booter in Application's constructor: | ||
|
||
```ts | ||
this.booters(Lb3AppBooter); | ||
``` | ||
|
||
## Try it out | ||
|
||
Start the new LB4 application | ||
|
||
```sh | ||
$ npm start | ||
|
||
Server is running at http://127.0.0.1:3000 | ||
``` | ||
|
||
Open the URL printed to console to view your front-end served from `public` | ||
directory or go to `http://127.0.0.1:3000/explorer` to explore the REST API. | ||
|
||
### Need help? | ||
|
||
Check out our [Gitter channel](https://gitter.im/strongloop/loopback) and ask | ||
for help with this tutorial. | ||
|
||
### Bugs/Feedback | ||
|
||
Open an issue in [loopback-next](https://github.com/strongloop/loopback-next) | ||
and we'll take a look. | ||
|
||
## Contributions | ||
|
||
- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) | ||
- [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,6 @@ | ||
// Copyright IBM Corp. 2017,2018. All Rights Reserved. | ||
// Node module: @loopback/example-lb3app | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export * from './dist'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright IBM Corp. 2017,2018. All Rights Reserved. | ||
// Node module: @loopback/example-lb3app | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
const application = require('./dist'); | ||
|
||
module.exports = application; | ||
|
||
if (require.main === module) { | ||
// Run the application | ||
const config = { | ||
rest: { | ||
port: +process.env.PORT || 3000, | ||
host: process.env.HOST || 'localhost', | ||
openApiSpec: { | ||
// useful when used with OASGraph to locate your application | ||
setServersFromRequest: true, | ||
}, | ||
}, | ||
}; | ||
application.main(config).catch(err => { | ||
console.error('Cannot start the application.', err); | ||
process.exit(1); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Copyright IBM Corp. 2017,2018. All Rights Reserved. | ||
// Node module: @loopback/example-lb3app | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
// DO NOT EDIT THIS FILE | ||
// Add any additional (re)exports to src/index.ts instead. | ||
export * from './src'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# loopback-getting-started | ||
|
||
This is the example application that accompanies the [Getting started with LoopBack](http://loopback.io/doc/en/lb3/Getting-started-with-LoopBack.html) tutorial. Follow the steps in the tutorial to create this application from scratch. | ||
|
||
NOTE: This repository has commits for each step of the tutorial, so you can pick up the tutorial at any point along the way: | ||
|
||
See [Getting started with LoopBack](http://loopback.io/doc/en/lb3/Getting-started-with-LoopBack.html) for step-by-step instructions to create this application. | ||
|
||
--- | ||
|
||
[More LoopBack examples](https://loopback.io/doc/en/lb3/Tutorials-and-examples.html) |
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.
Are these going to be permanent part of LB4? Or are we getting rid of them after some time, when?
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.
app.mountExpressRouter(basePath, router, spec)
will become a permanent part of@loopback/rest
. I see it as a generic extension point that can be useful beyond LB3 migration.Lb3AppBooter
will live in its own package, we can decide how long we want to support it and when to deprecate it, independently on the rest of the framework.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.
app.mountExpressRouter
is a good addition for extensibility and customizability.