Skip to content

Conversation

@shimks
Copy link
Contributor

@shimks shimks commented Apr 2, 2018

This PR splits @repository decorator into two overloaded methods for clarity in their two separate use cases: repository injection and repository generation based on a model and a datasource.

I had a question come up while I was working on the PR. Is advocating repository injection through @repository(MyRepository.name) the approach we want to take? I'd think that this is something that would go against the principles of Inversion of Control, which is what our Context system is based on.

I thought the point of the context system was to allow different components of an app to be swapped out in a single file. For example, if a user has a test repository and a production repository that he needs to swap out for deployment, our Context system should theoretically provide a single place for the user to swap out the repositories. In the case with @repository(TestRepository.name), the user would have to also go into the controller to physically change TestRepository to ProductionRepository.

Maybe my understanding of Inversion of Control or our selling point of the framework is wrong, but I'd like to hear what others think about this conflict between what our framework is designed for and how we're designing the framework.

Checklist

  • 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 packages/example-* were updated

target: Object,
key?: symbol | string,
descriptor?: TypedPropertyDescriptor<Repository<T>> | number,
descriptor?: TypedPropertyDescriptor<BoundValue> | number,
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 changing this to BoundValue since it's type is any.

Copy link
Contributor Author

@shimks shimks Apr 2, 2018

Choose a reason for hiding this comment

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

Honestly, descriptor argument is pretty irrelevant considering we're never going to be using it as a propertyDescriptor since @repository has no use cases for decorating methods themselves (and if a user tries it, it errors out). My reasoning for replacing Repository<T> was to get rid of the usage of generics since we don't need it at all (at least from what I understand).

I might change the argument name descriptor to something else so that it's less confusing.

export type RepositoryDecorator = (
target: Object,
key?: string | symbol,
descriptorOrIndex?: TypedPropertyDescriptor<BoundValue> | number,
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason we can't return the type as TypedPropertyDescriptor<Repository<T>>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

^^^ same as reasoning above: to get rid of generics

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because github hid my comments, I'm copypasting from above comment:

Honestly, descriptor argument is pretty irrelevant considering we're never going to be using it as a propertyDescriptor since @repository has no use cases for decorating methods themselves (and if a user tries it, it errors out). My reasoning for replacing Repository<T> was to get rid of the usage of generics since we don't need it at all (at least from what I understand).

I might change the argument name descriptor to something else so that it's less confusing.

Copy link
Contributor Author

@shimks shimks Apr 3, 2018

Choose a reason for hiding this comment

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

Maybe I should put this PR on hold for the author of #744 (@bajtos) to hear his inputs on taking in Class.name as the argument


constructor(
@inject('repositories.<%= repositoryName %>')
@repository(<%= repositoryName %>.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

-1

Any reason we can't just do the .name in our implementation of the @repository decorator rather than have the user add it? Poor UX imo and requires people to understand what is happening here.

We should be able to resolve a Dependency given a Class imo. @repository(UserRepository).

Out of scope of this PR but I think this can apply in general to @inject as follows @inject(UserController) should be enough for us to be able to resolve this type. IIRC Angular's DI framework is capable of resolving an instance given a Class.

Copy link
Contributor Author

@shimks shimks Apr 2, 2018

Choose a reason for hiding this comment

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

Although I agree that the decorator should be able to resolve from just the class, it's outside of the scope for tasks in #744. I'll agree to change the @repository arguments in this PR so that it can resolve the dependencies just from the class, but only if everyone unanimously agrees that this is our approach going forward.

Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense to use the class for DI. However, I think we should create an issue for it and address it separately to get this patch landed for its intended purpose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

+1 I originally intended to get miroslav's feedback to TV's proposal, but since that might not be coming soon I think I'd like to go ahead with the original plan

Copy link
Member

Choose a reason for hiding this comment

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

+1 for allowing @repository(UserRepository), +1 for leaving it out of scope of #744 and creating a new (follow-up) issue.

@virkt25
Copy link
Contributor

virkt25 commented Apr 2, 2018

I agree that it seems like it's making DI a little more brittle if we follow this approach BUT there are ways around it still.

  • A user can bind a TestRepository to the same key by doing app.bind('repositories.UserRepository').to(TestRepository)
    OR
  • A user can pass an instantiated version of TestRepository to the constructor of where @repository is being used.
  • The test repository can be named the same thing and bound after the original app which would result in the same key with the new mock repository.

IoC is maintained regardless as the dependency is declared in the constructor signature instead of just instantiating a class directly in the constructor yourself which would render all of the above ways useless.

* @repository(Product, mySqlDataSource)
* public prodRepo: DefaultCrudRepository<
* Product,
* typeof Customer.prototype.id
Copy link
Contributor

Choose a reason for hiding this comment

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

typeof Product.prototype.id

@b-admike
Copy link
Contributor

b-admike commented Apr 2, 2018

I had a question come up while I was working on the PR. Is advocating repository injection through @repository(MyRepository.name) the approach we want to take? I'd think that this is something that would go against the principles of Inversion of Control, which is what our Context system is based on.

I agree with what @virkt25 said. One idea that comes to mind if you'd like to use a file to swap out test/production repositories is to use a generic key name runtimeRepository for injection and bind the different repositories to it based on perhaps environment variable(s).

[`@repository(model: string | typeof Entity, dataSource?: string | juggler.DataSource)`](http://apidocs.loopback.io/@loopback%2frepository/#1503)

```ts
@repository(modelOrRepo: string | typeof Entity, dataSource?: string | juggler.DataSource)
Copy link
Contributor

Choose a reason for hiding this comment

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

did you mean to remove the link to apidocs?


constructor(
@inject('repositories.<%= repositoryName %>')
@repository(<%= repositoryName %>.name)
Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense to use the class for DI. However, I think we should create an issue for it and address it separately to get this patch landed for its intended purpose.

@shimks shimks force-pushed the repository-decorator branch 2 times, most recently from 184a17b to 6fc8de6 Compare April 3, 2018 19:17
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.

I'm fine with this patch, but would definitely like to see @raymondfeng or @bajtos chime in when they're back.

@virkt25
Copy link
Contributor

virkt25 commented Apr 6, 2018

I would make an argument for not passing in the .name string into the @repository decorator on the basis of #1215 that was merged. getRepository takes in just the Class you are trying to resolve, the decorator should have similar usage / behaviour since it more or less is doing the same thing.

It is poor UX to have one approach require a string and another just the Class itself.

@shimks
Copy link
Contributor Author

shimks commented Apr 6, 2018

Just to make sure that we're on the same page, the implementation of @repository had always taken in a string as an argument. This PR does not add in a new @repository decorator.

I too think that it is poor UX to force users to pass in MyRepository.name instead of the just the constructor itself. I initially wanted to just give benefit of the doubt that there is a reason that we're explicitly passing in the name of the constructor, but I'll add in a commit to make this change. If @bajtos or @raymondfeng doesn't like it, I'll just revert the commit then.

target: Object,
key?: symbol | string,
descriptor?: TypedPropertyDescriptor<Repository<T>> | number,
descriptorOrIndex?: TypedPropertyDescriptor<BoundValue> | number,
Copy link
Member

Choose a reason for hiding this comment

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

Please use any instead of BoundValue. BoundValue represents something that's bound in a Context. Here, you are describing a property of some type, not a value bound via ctx.bind().

@shimks shimks force-pushed the repository-decorator branch 2 times, most recently from d2f7687 to bc43e0e Compare April 9, 2018 18:03
constructor(
@inject('repositories.TodoRepository')
@repository(TodoRepository)
public todoRepository: TodoRepository,
Copy link
Contributor

@virkt25 virkt25 Apr 9, 2018

Choose a reason for hiding this comment

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

Is the type not inferred to be TodoRepository?? -- Is there a way for it to be inferred automatically now?

This applies to all instances 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.

That is a very useful feature that I've not thought about. I think this task has been scope creeped enough that I'll create a new issue to implement this feature.

describe('repository class', () => {
let ctx: Context;

before(function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you refactor function to a named function and call that from the before. Something like givenCtx.

See our test guidelines: http://loopback.io/doc/en/contrib/style-guide.html#hooks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good find

@shimks shimks force-pushed the repository-decorator branch from bc43e0e to 763a6ab Compare April 9, 2018 19:32
@shimks
Copy link
Contributor Author

shimks commented Apr 10, 2018

@raymondfeng Can I get your review on this PR? Thanks

@shimks shimks force-pushed the repository-decorator branch from 763a6ab to d845eaa Compare April 10, 2018 16:06
// inject('repository-factory', meta)(target, key!, descriptor);
inject('', meta, resolve)(target, key!, descriptor);
// throw new Error('@repository(model, dataSource) is not implemented');
// inject('repository-factory', meta)(target, key!, descriptorOrIndex);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can get rid of this line

@shimks shimks force-pushed the repository-decorator branch from 86837c1 to c8f27a9 Compare April 10, 2018 20:14
@shimks shimks merged commit 3db07eb into master Apr 10, 2018
@shimks shimks deleted the repository-decorator branch April 10, 2018 20:41
@shimks shimks removed the review label Apr 10, 2018
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.

7 participants