Skip to content

Conversation

@shimks
Copy link
Contributor

@shimks shimks commented Feb 15, 2018

This PR would utilize the new changes made in loopbackio/loopback-next#994 so that async start is not overridden if it can be overridden.

I left a couple of decisions undecided because I wasn't sure whether i should be changing some of these or not. Here are my main concerns:

  • In the WidgetApplication example under Application.md, server.api(WidgetApi) is used in start. The function call can't be moved into the constructor since the app is only extending from Application and not RestApplication; api() function can't be called because it doesn't exist for the application. Should the example be left as it is?
  • In Context.md, we configure multiple servers inside the start function. I think this should be left as is, but does anybody think otherwise?

@shimks shimks requested a review from bajtos February 15, 2018 21:52
@shimks shimks requested a review from bschrammIBM as a code owner February 15, 2018 21:52
@shimks shimks force-pushed the rest-config-out-of-start branch from 9ae1b5d to 30cdc4c Compare February 15, 2018 21:53
Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great!

```

You can then bind the full spec to the application using `server.spec()`. This is done on the server level, because each server instance can expose a different (sub)set of API.
You can then bind the full spec to the application using `app.api()`. Normally, this is done on the server level because each server instance can expose a different (sub)set of API, but since `RestApplication` uses only one REST server, you can bind the spec at application level.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am proposing a slightly different phrasing:

You can then bind the full spec to the application using `app.api()`.
This works well for applications with a single REST server, because
there is only one API definition involved.

If you are building an application with multiple REST servers,
where each server provides a different API, then you need 
to call `server.api()` instead.

The example below defines a `Route` that will be matched for `GET /`. When the `Route` is matched, the `greet` Operation (above) will be called. It accepts an OpenAPI [OperationObject](https://github.com/OAI/OpenAPI-Specification/blob/0e51e2a1b2d668f434e44e5818a0cdad1be090b4/versions/2.0.md#operationObject) which is defined using `spec`.
The route is then attached to a valid server context running underneath the
application.
application.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing space, please remove.

const server = await app.getServer(RestServer);
const route = new Route('get', '/', spec, greet);
server.route(route);
app.route(route); // attaches route to RestServer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move route registration out of async start() function.

I think we no longer need async start function and can call app.start() directly:

// greet is a basic operation
 function greet(name: string) {
   return `hello ${name}`;
 }

const route = new Route('get', '/', spec, greet);
app.route(route);

app.start();

(async function start() {
const server = await app.getServer(RestServer);
server.sequence(MySequence);
app.sequence(MySequence);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, please move this line out of async start() function and consider removing the start function completely.

@shimks
Copy link
Contributor Author

shimks commented Feb 27, 2018

@bajtos May I get another review on this PR so that it could be landed? Thanks

Copy link
Contributor

@b-admike b-admike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏

@virkt25
Copy link
Contributor

virkt25 commented Feb 28, 2018

The function call can't be moved into the constructor since the app is only extending from Application and not RestApplication; api() function can't be called because it doesn't exist for the application. Should the example be left as it is?

I think we should remove this or create a new issue to track the removal. If it's simple just do it in this PR. This way we don't have misleading information in docs that will confuse users and provide a poor experience for anyone following the docs.

@shimks
Copy link
Contributor Author

shimks commented Feb 28, 2018

You're thinking of just removing server.api(WidgetApi) entirely right? That would allow us to move things that are inside start to the constructor, but then the question becomes what is the start function for? What do we advocate users to put inside the start function? I'm not sure what this is myself after we decided on getting rid of multi server support.

@virkt25
Copy link
Contributor

virkt25 commented Feb 28, 2018

Yes getting rid of server.api() if it doesn't work in the context of that example.

As for what start is, it's not a function user's should need to override ... just call to start their server(s)

@shimks shimks force-pushed the rest-config-out-of-start branch from 2f6358b to d903c1b Compare February 28, 2018 18:29
Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but please consider addressing my first comment to simplify the WidgetApplication example.

import {SamoflangeController, DoohickeyController} from './controllers';
import {WidgetApi} from './apidef/';

export class WidgetApplication extends Application {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, you should extend RestApplication here. The only reason why you would not want to, would be if your application was exposing multiple REST servers, which is not the case AFAICT.

This will also allow you to get rid of app.component(RestComponent) below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only problem I have with that is the page the docs is about: Application. IMO an example shown in this page should be vanilla Application instead of RestApplication. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, let's keep using Application then 👍

Could you perhaps extend Tips for application setup to mention RestApplication + when and why to use it? It's ok to leave that out of this pull request though.

// other components to configure them before launch.
const server = await app.getServer(RestServer);
server.bind('rest.port').to(8080);
server.api(WidgetApi);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change in the context of the original example. The original example assumed api-first approach, where WidgetApi contained Swagger spec describing REST API, with extensions like x-controller-name and x-method used to link Swagger endpoints with controller method implementing them.

Since api-first support is out of scope of our upcoming release, I am ok to rework this example to code-first approach, where the REST API is described on controller classes/methods using decorators like @get and @param.

In other words, this change is fine with me too 👍

// inject your spec here!
server.api(spec);
server.bind("rest.port").to(3001);
await super.start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we still need to call super.start()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@b-admike I think in this case it's enough to just inherit start() function from server. No need to override it.

const route = new Route('get', '/', spec, greet);
app.route(route); // attaches route to RestServer

app.start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't app.start() returning a promise and thus should be called with await? (see https://github.com/strongloop/loopback-next/blob/master/packages/core/src/application.ts#L128-L136)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's a myth.

  1. await can only be used within an async function. The await keyword helps VM to suspend the execution and wait for the resolution/rejection of the promise or value.

  2. In this case, you cannot use await as the module will be wrapped into a function by Node.js but it's not async.

  3. The code before the change declares an async function and execute it immediately.

  4. It's perfectly fine to call an async function or a function returning a promise without await. The return value is Promise. You have to manually call .then() or .catch() to set up result handler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification Raymond. We don't really need to show .then() or .catch() handlers in this example. Disregard my comment!

server.sequence(MySequence);
await app.start();
})();
app.start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as @b-admike : https://github.com/strongloop/loopback.io/pull/621/files#r171920566

Other than that LGTM 👍 awesome clarification of the current behavior of rest server.

const route = new Route('get', '/', spec, greet);
app.route(route); // attaches route to RestServer

app.start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification Raymond. We don't really need to show .then() or .catch() handlers in this example. Disregard my comment!

@shimks shimks changed the title [WIP] move RestServer config out of async start Move RestServer config out of async start Mar 2, 2018
@shimks shimks merged commit 80a996a into gh-pages Mar 5, 2018
@shimks shimks deleted the rest-config-out-of-start branch March 5, 2018 15:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants