Skip to content
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

Update LoopBack 4 docs with rest refactor changes #475

Merged
merged 14 commits into from
Oct 10, 2017

Conversation

kjdelisle
Copy link

@kjdelisle kjdelisle commented Oct 5, 2017

Overview

Refactor the docs to cover off the changes made to the framework architecture

  • Areas where we previously bound REST-specific functions should now have updated examples that leverage the new method
  • Testing-Your-Application required a large rewrite because

Related Issues

loopbackio/loopback-next#613

@kjdelisle kjdelisle self-assigned this Oct 5, 2017
@kjdelisle kjdelisle changed the title Lb4/use rest package Update LoopBack 4 docs with rest refactor changes Oct 5, 2017
Here is the swagger.json that will hook up your `GreetController` to the
`/greet` path, and associate the `greet` function with a `GET` request.

{% include code-caption.html content="src/apidef/swagger.json".js" %}
Copy link
Contributor

@crandmck crandmck Oct 5, 2017

Choose a reason for hiding this comment

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

Liquid syntax error (spurious quote). S/be:

{% include code-caption.html content="src/apidef/swagger.json" %}

@bajtos
Copy link
Member

bajtos commented Oct 6, 2017

Testing-Your-Application required a large rewrite

I am going to rewrite that page as part of loopbackio/loopback-next#553 anyways. Thanks for updating the code examples though!

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.

I am concerned that the recent changes in server-related API worsened user experience and made the application setup code too complex.

I am proposing to take another look at the current implementation and find a way how to make the setup easier to use, to ensure we keeping the high UX level which we started with.


Let's see this in action:

```js
class MySequence extends DefaultSequence {
handle(request, response) { // we provide these value for convenience (taken from the Context)
const req = this.ctx.getSync('http.request'); // but it is still available in the sequence/request context
const req = this.ctx.getSync('rest.http.request');
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to not teach new developers about getSync, because it does not work for values requiring async computation. I am proposing to update this example as follows:

class MySequence extends DefaultSequence {
  // we provide request & response for convenience (taken from the Context)
  async handle(request, response) {
    // but they are still available in the sequence/request context
    const req = await this.ctx.get('rest.http.request');
    this.send(`hello ${req.params.name}`);
  }
}

Copy link
Author

Choose a reason for hiding this comment

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

Ah, sorry, I must've glossed over that bit, was just correcting the injection key.


app.bind('authentication.strategy').to(new BasicStrategy(loginUser));
const server = app.getServer(RestServer); // The REST server has its own context!
Copy link
Member

Choose a reason for hiding this comment

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

getServer returns a promise, doesn't it?

const server = await app.getServer(RestServer);


app.bind('authentication.strategy').to(new BasicStrategy(loginUser));
const server = app.getServer(RestServer); // The REST server has its own context!
server.bind('authentication.strategy').to(new BasicStrategy(loginUser));
Copy link
Member

Choose a reason for hiding this comment

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

I feel the necessity to configure things like authentication strategy and sequence handler at server-level (and not at app-level) is rather suboptimal UX. What used to be a single line of sync code before, becomes two lines of async/await code that cannot even happen in application's constructor (because it's async).

Can we come up with a better approach that's easier to use? I am fine to leave that out of scope of this patch if you prefer.

Copy link
Author

@kjdelisle kjdelisle Oct 6, 2017

Choose a reason for hiding this comment

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

We could easily have the examples use application-level context to do their work, since it's all available at the child context of the server, but then we're potentially encouraging a pattern that might prevent use of separate strategies in multiple server instances.

If we're going to alter the examples to simplify the bindings they work with, we'll need to make a section somewhere that details more advanced usage scenarios so that users properly separate their contextual bindings.

});

(async function start() {
// Grab the REST server instance
const server = app.getServer(RestServer);
Copy link
Member

Choose a reason for hiding this comment

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

getServer returns a promise, doesn't it?

I also find it weird that we are configuring the sequence handler every time the server is started, and that there is no sequence handler configured after application's constructor was called. (I think this is sort of an addition to my previous comment.)

|`rest.http.response`|`Http.RESPONSE`|`ServerResponse`|The raw `http` response object|
|`routes.${route.verb}.${route.path}`||`RouteEntry`|Route entry specified in api-spec|
|`rest.sequence`|`SEQUENCE`|`SequenceHandler`|Class that implements the sequence for your application|

**Sequence Actions binding keys**
Copy link
Member

Choose a reason for hiding this comment

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

I think this title should include "REST" keyword, now that the sequence actions listed below are specific to RestBindings.

const route = new Route('get', '/', spec, greet);
server.route(route);
await app.start();
})();
Copy link
Member

@bajtos bajtos Oct 6, 2017

Choose a reason for hiding this comment

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

This will swallow any errors thrown from start, causing "unhandled promise rejection" warnings. You should add .catch() handler at minimum.

It makes me wonder, can we move methods like route, handler and sequence to a different class, one that does not have any dependencies so that it will always resolve synchronously, and then inject this class as a RestServer dependency?

An mock-up:

// inside RestComponent (or a mixin?)
app.rest = new RestServerConfigurator();
app.bind(RestBindings.CONFIGURATOR).toDynamicValue(() => app.rest);

// in RestServer constructor
class RestServer {
  constructor(
    @inject(RestBindings.CONFIGURATOR) configurator: RestServerConfigurator,
    /*...*/) {
      // define routes, handler, sequence etc. from the configurator
    }
 }

// usage in applications
app.rest.route('get', '/', spec, greet);

This can be extended to support multiple rest servers by replacing app.rest with app.${serverName} , where the server name is the key used in servers.${serverName} binding and in appConfig.servers object.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Let's leave this out of scope of this documentation update.

@@ -77,7 +87,7 @@ const spec = {
}
};

app.api(spec);
server.api(spec);
Copy link
Member

Choose a reason for hiding this comment

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

This feels very wrong to me. The API is tightly coupled with Controllers and controllers are defined at application level. As a user, I'd expect api() method to be available in the same place as controller().

Also, if the API is server-specific, and we have multiple REST servers configured, then each server can be providing a different REST API. Is it a good idea?

Copy link
Author

Choose a reason for hiding this comment

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

This feels very wrong to me. The API is tightly coupled with Controllers and controllers are defined at application level. As a user, I'd expect api() method to be available in the same place as controller().

My understanding was that we might have multiple controllers servicing more than one server, either of the same protocol or otherwise. Combine this with the idea that the OpenAPI spec handling is REST-specific and we end up with server-based API binding instead.

Also, if the API is server-specific, and we have multiple REST servers configured, then each server can be providing a different REST API. Is it a good idea?

I think it's a great idea! Your microservice could have a public-facing API that defines its consumable endpoints, and a private/internal API for infrastructure, exporting logs, administration, or whatever you can think of. Without server-level API binding, these scenarios aren't possible anymore.

Playing "Devil's Advocate" for a moment, if we want to strictly enforce the idea that a LoopBack application is a one-server microservice, then it would make sense to migrate some of this configuration back up to application level, and enforce the idea that you pick one, and only one server component to power the application. This would definitely make sense from a microservice architecture perspective, where you're minimizing the responsibility of each running instance and using other technologies to handle communication between services, log aggregation, etc.

I think trying to simultaneously support both philosophies is easily doable, but could be a documentation and UX nightmare for new users.

Copy link
Member

Choose a reason for hiding this comment

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

Your microservice could have a public-facing API that defines its consumable endpoints, and a private/internal API for infrastructure, exporting logs, administration, or whatever you can think of.

This is a pretty cool idea which makes a lot of sense to me 👍 I agree we should support this use case.

My concern is about ease of use for people starting with the framework, notice how many of our examples increased in size.

Anyways, this is out of scope of this pull request, because we have already updated may other places (package READMEs, example repos) to use the convention described in this pull request. To use a better convention, we will need to update both the docs and those other places.


const app = new Application({
sequence: MySequence
components: [RestComponent],
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 step backwards in terms of UX, at least IMO. Isn't it possible to configure the sequence via component-specific configuration? Something along the following lines:

const app = new Application({
  components: [RestComponent],
  rest: {
    sequence: MySequence,
  }
});

Copy link
Author

Choose a reason for hiding this comment

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

I think the current design supports your suggestion, so I'll change it.


I know this isn't a part of my PR, which is why I'm only leaving this comment
instead of fixing it.
-->
Copy link
Contributor

Choose a reason for hiding this comment

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

when I run npm start on local, there is an error complains

Liquid Exception: Invalid syntax for include tag: content="src/apidef/swagger.json".js" Valid syntax: {% include file.ext param='value' param2='value' %} in pages/en/lb4/Testing-Your-application.md
jekyll 3.3.1 | Error:  Invalid syntax for include tag:

  content="src/apidef/swagger.json".js"

Valid syntax:

  {% include file.ext param='value' param2='value' %}

branch gh-pages doesn't fail on it.
This line is not directly modified in PR, maybe somewhere else affects it?

Copy link
Contributor

Choose a reason for hiding this comment

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

oops I commented in the wrong file that's why I didn't find it, should be
https://github.com/strongloop/loopback.io/pull/475/files#diff-ad241bc342976380fb64dab3b286802bR248
their names are so similar lol


Here is a simple example:

```js
const Application = require('@loopback/core').Application;
const app = new Application(); // `app` is a "Context"
class MySequence { ... }
app.sequence(MySequence);
class MyController { ... }
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we revert this since either can be used as an example, but in the Request-level context section we expand on MySequence?

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. Let's leave usage improvements that I suggested in my comments out of scope of this pull request.

@crandmck crandmck removed the request for review from bschrammIBM October 9, 2017 17:46
@bajtos
Copy link
Member

bajtos commented Oct 10, 2017

I am going to land this pull request even if it's not perfect yet, so that I can start working on loopbackio/loopback-next#553

@kjdelisle please open a follow-up pull request to address remaining issues/comments.

I apologize for the inconvenience this may have caused.

@bajtos bajtos merged commit 3cfc79b into gh-pages Oct 10, 2017
@bajtos bajtos removed the review label Oct 10, 2017
@kjdelisle kjdelisle deleted the lb4/use-rest-package branch November 2, 2017 18:10
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.

5 participants