-
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
Spike: Operation hooks for models/repositories #1919
Comments
I have created this story as a Spike, because I feel we need to do a bit of research first. For example, where to register hook handlers - at model level or at repository level? If the hooks are registered at model level, how can we ensure that all repository implementations are honoring them? Can we find a set of hooks that is universal for all possible implementations of a given repository interface, or are the hooks coupled with a particular repository implementation and/or interface? For example, I can imagine that a KeyValue model/repository needs a different set of hooks than a CRUD model/repository. |
👍 to implement them at the repository level. Keep in mind that by design it is the repository the one that intermediates between both artifacts. Remember that I mentioned you about it in our conference call?. Usually the hooks will interact on CRUD operations on Before/After events, they will receive a record, they can mutate it and then call next hook. So it makes sense in the repository since these CRUD operations occur at this level. On WANG os, we called them updateExits 😄. |
My understanding of the new design was that it's perfectly okay for two repositories to access the same models (in the case of more complex relational data), would this mean that a third abstraction layer would need to be added to share the hooks between those repositories? Or is it bad design ifo two different repositories refer to the same model? |
By design you shouldn't have more than one repository that belongs to the same datasource pointing to the same model if we use these hooks implementation, I can't figure out a real case scenario for now. However, you can have the same model used by two different set of datasource/repository with no problem. |
@David-Mulder thank you for joining the discussion.
Could you please be more specific about the scenario you have in mind and give us an example of a model and the different repositories used with this single model? What are the requirements you are addressing with this solution? IMO, it's perfectly valid to have multiple repositories using the same model class. The situation seems pretty simple to me when all repositories are CRUD based: one can create a base repository class that implements the hooks, and then let all different repositories to inherit from that base class to ensure operation hooks are shared. When the repositories are using different data-access patterns, e.g. CRUD vs. KeyValue store, then the situation is much more difficult, because not all operation hooks can be implemented for both CRUD and KeyValue. Is this something you are looking for too? |
Hi @bajtos @marioestradarosa,
What do you think? If you can provide more regarding each one can be helpful as well. |
I think the hooks should be registered at repository level, because a single model can be attached to repositories of different type (CRUD, KeyValue, etc.), at least in theory. Depending on the repository type, certain hooks may not be possible to support.
+1
I don't understand this point, could you please explain in more details? /cc @raymondfeng |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
In my application, I would like to set a UUID in the request header. In my sequence I just add this to request.headers and then I want to access this UUID in my REST datasource so that I can pass this on as a header(or query param or body, doesn't matter) to my REST call. Important thing is to access the headers of request in my datasource. In LB3, we could just use the hooks to do this. I am assuming having operation hooks on repository should help in this scenario as well. @bajtos @marioestradarosa As long as we don't have the operation hooks, is there a workaround for the same? |
As a temporary workaround, it's possible to leverage existing LB 3.x Operation Hooks in the custom per-model repository class. An example: export class MyModelRepository extends DefaultCrudRepository<
MyModel,
typeof MyModel.prototype.id
> {
constructor(@inject('datasources.db') dataSource: juggler.DataSource) {
super(MyModel, dataSource);
(this.modelClass as any).observe('persist', async (ctx: any) => {
delete ctx.data.computed;
});
}
} |
Hi @bajtos, Above |
Quoting from https://loopback.io/doc/en/lb3/Operation-hooks.html#persist, emphasis is mine:
Both "before save" and "persist" hooks are called before the data is sent to the database. |
Discussion in the estimation meeting: maybe we can leverage interceptor to implement hooks |
Cross-posting from loopbackio/loopback-connector-mongodb#534 (comment): We need |
@bajtos Thanks for proposing this workaround (cross-posted in #2707). However, in MySQL, the But I tried using the access hook and it worked: (this.modelClass as any).observe('access', async (ctx: any) => {
delete ctx.Model.definition.properties.computed;
}); |
Is there an intension to have this completed by the December 2020 EOL for Loopback 3 or might EOL for Loopback 3 be extended? We've been delaying migration, but now we're worried that we still won't have the ability to fully migrate due to lack of features before reaching EOL. Thanks, |
@kyle2829 There's currently a documented method to use LoopBack 3-style operation hooks: https://loopback.io/doc/en/lb4/migration-models-operation-hooks.html |
Thanks @achrinza! I wasn't sure if it was safe to use judging by "temporary" wording in the documentation: "In the meantime, we are providing a temporary API for enabling operation hooks in LoopBack 4". We'll go ahead with using that method, thanks. |
@bajtos Is the |
|
Operation hook in LB3 can access httpContext through Being able to access httpContext is necessary in my use case. |
@f-w You could try injecting a getter function for export class MyModelRepository extends DefaultCrudRepository<
MyModel,
typeof MyModel.prototype.id
> {
constructor(
@inject('datasources.db') dataSource: juggler.DataSource,
// -> GETTER
@inject.getter(RestBindings.Http.CONTEXT) protected getHttpContext: Getter<Context>,
// <------
) {
super(MyModel, dataSource);
(this.modelClass as any).observe('persist', async (ctx: any) => {
// -> GETTER VALUE
const httpCtx = await this.getHttpContext();
console.log(httpCtx);
// <------
delete ctx.data.computed;
});
}
} |
The above unfortunately gives us a context not found error
|
Thanks, it worked for me, but not in the migration script, so ended up injecting an env variable to guard it. |
I would like to use this workaround to edit the
First request you will see one console log, second request you see two more. How to solve this? |
Found this https://loopback.io/doc/en/lb4/migration-models-operation-hooks.html,
Seems to work. Is there any reason why the other approach was recommended? UPDATE: not really working, it keeps the reference to the first |
I'm facing a similar issue: I've tried using my own JWTAuthenticationProvider in order to bind the user to the application context but that's going to cause trouble when multiple users hit the app at the same time since the APPLICATION context is a singleton. Does anybody have any suggestions on how to get the REQUEST context from within an observer callback? Any help would be much appreciated. |
I'm actually considering creating a custom sequence where I bind model operation hooks for this request only, and then unbind them once the request is completed. |
Nevermind. |
I also confirmed the workaround keeps the reference to the request context defined in the first repository instance.
Not only this is not working, but also poses security vulnerability. Please remove https://loopback.io/doc/en/lb4/migration-models-operation-hooks.html. |
Thanks for providing your detailed findings, looking into this right now. These are my preliminary findings:
I have not been able to replicate this. In my testing, this has always resulted in a I have tested and gotten the aforementioned behaviour with these bindings:
To help expedite this, I've created issue #6962, quoting the relevant comments. Could you please add additional information and a GitHub repo with a minium-reproduction example into issue #6962 so that we can pinpoint the issue? |
This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the |
This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the |
In LoopBack 3.x, we have a concept of Operation Hooks allowing models to register handlers for common events: access, before/after save, before/after delete, load/persist.
We should provide similar functionality in LoopBack 4 too.
See also the discussion in #1857 which triggered this feature request.
/cc @vvdwivedi @David-Mulder @marioestradarosa @raymondfeng
Acceptance criteria
The text was updated successfully, but these errors were encountered: