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

feat(rest-crud): add CrudRestApiBuilder #4589

Merged
merged 1 commit into from
Feb 20, 2020
Merged

feat(rest-crud): add CrudRestApiBuilder #4589

merged 1 commit into from
Feb 20, 2020

Conversation

nabdelgadir
Copy link
Contributor

Closes #3737

CrudRestApiBuilder is a model API builder that builds the default repository and controller class for a given Entity class.

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@nabdelgadir nabdelgadir self-assigned this Feb 7, 2020
@nabdelgadir nabdelgadir marked this pull request as ready for review February 7, 2020 20:01
Copy link
Contributor

@agnes512 agnes512 left a comment

Choose a reason for hiding this comment

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

Great job! I have a few questions and suggestions.

packages/rest-crud/README.md Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
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.

@nabdelgadir Good stuff 👍 I post a few comments, mostly LGTM.

inject(`datasources.${modelConfig.dataSource}`)(
repositoryClass,
undefined,
0,
Copy link
Contributor

Choose a reason for hiding this comment

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

@nabdelgadir might be a silly question: I am looking at the inject function, how do we know the index is 0 and why the member name is undefined?

Copy link
Contributor Author

@nabdelgadir nabdelgadir Feb 13, 2020

Choose a reason for hiding this comment

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

From my understanding, the only reason we're using a number (that happens to be 0 in this case) is so that it goes into this if statement, but I don't think the number actually matters. And we're only passing in member since it's required and so we're able to pass in the number as the third argument, so leaving it as undefined is the easiest way to go about it (but I don't actually know the role of member in the injection, maybe @bajtos could comment on this and why we're passing in 0?).

Copy link
Contributor

Choose a reason for hiding this comment

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

@bajtos ^ (I have the same question)

Copy link
Member

Choose a reason for hiding this comment

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

When you set the member to undefined, it means the constructor function. (The constructor does not have any name.

The index 0 is identifying the first argument of the target function (the class constructor in this case).

In this particular invocation of inject, we are decorating the first argument of the repository constructor to inject the datasource instance. It's an equivalent of the following TypeScript code:

class MyRepository /*extends ...*/ {
  constructor(
    @inject(`datasources.${modelConfig.dataSource}`)
    public dataSource: juggler.DataSource
  ) {}
}

Because decorators are converted to JavaScript calls of TypeScript API at compile time, we cannot use decorator syntax when building the repository class dynamically at runtime, we have to invoke the decorator function manually.

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 create a constant and use that instead of 0? Perhaps also one for the undefined value? So both constants can provide some clarity to the method call?

inject(`datasources.${modelConfig.dataSource}`)(
    repositoryClass,
    UNDEFINED_CLASS_CONSTRUCTOR_FUNCTION,
    INDEX_OF_INJECTED_DATASOURCE_PARAMETER_INSIDE_REPOSITORY_CONSTRUCTOR,

I know the names are silly, but you get the point.

Copy link
Contributor

Choose a reason for hiding this comment

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

Because decorators are converted to JavaScript calls of TypeScript API at compile time, we cannot use decorator syntax when building the repository class dynamically at runtime, we have to invoke the decorator function manually.

Thanks everyone for the reply. This is something I didn't know before 👍

Copy link
Contributor

@emonddr emonddr left a comment

Choose a reason for hiding this comment

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

great start, nora


if (!(modelClass.prototype instanceof Entity)) {
throw new Error(
`CrudRestController requires an Entity, Models are not supported. (Model name: ${modelName})`,
Copy link
Contributor

Choose a reason for hiding this comment

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

CrudRestController requires an Entity, Models are not supported. (Model name: ${modelName}),

What are we trying to say here? That the model needs to extend Entity?

Perhaps

CrudRestController requires a model that extends 'Entity'. (Model name: ${modelName} does not extend 'Entity')

Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we calling the file 'plugin'?

packages/rest-crud/src/crud-rest-builder.plugin.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why are we calling the file 'plugin'?

packages/rest-crud/src/crud-rest-builder.plugin.ts

Following the terminology in the spike

Copy link
Contributor

Choose a reason for hiding this comment

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

@nabdelgadir ,

Hmm. Let's not use the word plugin. We don't mention it in the overall LB4 docs.

As an example, AuthenticationStrategyProvider is a class that defines an extension point where developers can create extensions (custom authentication strategies) and register them against the extension point... but we don't call any of the files .plugin.ts .

Same for the extensions for greeter extension point : https://github.com/strongloop/loopback-next/blob/master/examples/greeter-extension/src/greeters/greeter-en.ts#L6 , we don't use .plugin.ts .

https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/providers/auth-strategy.provider.ts#L28

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 not use the word plugin. We don't mention it in the overall LB4 docs.

Considering that we are using ExtensionPoint/Extension design pattern, maybe we can use "extension" instead of "plugin"?

Copy link
Member

Choose a reason for hiding this comment

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

On the other hand, since this file contains CrudRestApiBuilder class which is bound asModelApiBuilder, maybe crud-rest.model-api-builder.ts would be a good name, using model-api-builder as the type suffix (similar to controller and repository we use elsewhere). If model-api-builder is too long, the perhaps api-builder is good enough too? (I.e. crud-rest.api-builder.ts).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like crud-rest.api-builder.ts because it's shorter (and the builder is CrudRestApiBuilder instead of CrudRestModelApiBuilder, so the Model part is implied).

packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
};
```

Now your `Product` model will have a default repository and default controller
Copy link
Contributor

Choose a reason for hiding this comment

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

So there are 2 ways that a developer can create the default controller and default repository for Product?

  1. Performing Basic steps and not performing Advanced steps
  2. Performing Advanced steps and not performing Basic steps

So they are mutually exclusive then? If so let's specify that Basic and Advanced are mutually exclusive. Another question: why would someone want to do Advanced steps if it is shorter to perform Basic steps?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So they are mutually exclusive then? If so let's specify that Basic and Advanced are mutually exclusive.

Yeah, I'll specify that, thanks!

Another question: why would someone want to do Advanced steps if it is shorter to perform Basic steps?

The advanced one helps if you only want to create the default controller or default repository but not both.

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.

Great work, @nabdelgadir ❤️

Let's make the implementation easier to understand for all maintainers, including our future selves - see my comments bellow.

packages/rest-crud/README.md Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest-builder.plugin.ts Outdated Show resolved Hide resolved
`@loopback/rest-crud` exposes two helper methods (`defineCrudRestController` and
`defineCrudRepositoryClass`) for creating controllers and respositories using
code.
`@loopback/rest-crud` can be used along with `@loopback/model-api-builder` to
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 is not entirely correct. @loopback/model-api-builder provides only Types and helpers for packages contributing Model API builders (quoting from README).

Did you perhaps mean the built-in ModelApiBooter?

Suggested change
`@loopback/rest-crud` can be used along with `@loopback/model-api-builder` to
`@loopback/rest-crud` can be used along with the built-in `ModelApiBooter` to

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case, CrudRestApiBuilder implements ModelApiBuilder (not the booter). Quoting from the README: it also "provides a contract for extensions that contribute builders for repositories and controllers".

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.

The code looks great now!

It would be great to add a test to verify the scenario when the booter detects an existing repository class and does not define a new one, see #4589 (comment).

I have few minor comments, see below.

Please allow some time for other reviewers to have a chance to comment on the new version before landing.

packages/rest-crud/src/crud-rest.api-builder.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest.api-builder.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest.api-builder.ts Outdated Show resolved Hide resolved
packages/rest-crud/src/crud-rest.api-builder.ts Outdated Show resolved Hide resolved
@nabdelgadir nabdelgadir force-pushed the crud-rest branch 2 times, most recently from 7c822d3 to bd0e910 Compare February 19, 2020 16:57
`CrudRestApiBuilder` is a model API builder that builds the default repository and controller class for a given Entity class.

Co-authored-by: Miroslav Bajtoš <[email protected]>
@nabdelgadir nabdelgadir merged commit bc5d56f into master Feb 20, 2020
@nabdelgadir nabdelgadir deleted the crud-rest branch February 20, 2020 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add CrudRestApiBuilder to @loopback/rest-crud
6 participants