-
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
feat(rest-crud): add CrudRestApiBuilder #4589
Conversation
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.
Great job! I have a few questions and suggestions.
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
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.
@nabdelgadir Good stuff 👍 I post a few comments, mostly LGTM.
inject(`datasources.${modelConfig.dataSource}`)( | ||
repositoryClass, | ||
undefined, | ||
0, |
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.
@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?
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.
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?).
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.
@bajtos ^ (I have the same question)
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.
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.
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.
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.
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.
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 👍
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
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.
great start, nora
|
||
if (!(modelClass.prototype instanceof Entity)) { | ||
throw new Error( | ||
`CrudRestController requires an Entity, Models are not supported. (Model name: ${modelName})`, |
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.
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')
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.
Why are we calling the file 'plugin'?
packages/rest-crud/src/crud-rest-builder.plugin.ts
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.
Why are we calling the file 'plugin'?
packages/rest-crud/src/crud-rest-builder.plugin.ts
Following the terminology in the spike
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.
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
.
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.
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"?
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.
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
).
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 like crud-rest.api-builder.ts
because it's shorter (and the builder is CrudRestApiBuilder
instead of CrudRestModelApiBuilder
, so the Model
part is implied).
bcc3000
to
0b75cca
Compare
}; | ||
``` | ||
|
||
Now your `Product` model will have a default repository and default controller |
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.
So there are 2 ways that a developer can create the default
controller and default
repository for Product?
- Performing Basic steps and not performing Advanced steps
- 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?
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.
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.
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.
Great work, @nabdelgadir ❤️
Let's make the implementation easier to understand for all maintainers, including our future selves - see my comments bellow.
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
0b75cca
to
5aef80d
Compare
packages/rest-crud/README.md
Outdated
`@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 |
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 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?
`@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 |
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.
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".
packages/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
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.
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/boot/src/__tests__/acceptance/crud-rest-builder.acceptance.ts
Outdated
Show resolved
Hide resolved
7c822d3
to
bd0e910
Compare
`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]>
bd0e910
to
5286f45
Compare
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 machinepackages/cli
were updatedexamples/*
were updated👉 Check out how to submit a PR 👈