Notify observers for embedded relations#516
Conversation
|
/cc @bajtos @raymondfeng |
|
LGTM |
|
@raymondfeng regarding https://github.com/fabien/loopback-datasource-juggler/blob/feature/embedded-hooks/lib/relation-definition.js#L2258 Would you expect |
|
@bajtos I think I need to review this a bit - obviously, to allow partial updates, |
Yes please, send "where+data+currentInstance" to "before save" hook. AFAICT, the method The "after save" hook should have "ctx.instance" though. |
There was a problem hiding this comment.
Unrelated bug: the modelTo instance should be validated before save, similarly as EmbedsOne.prototype.create does.
There was a problem hiding this comment.
Actually, I think that call is obsolete in EmbedsOne.prototype.create - a custom validation has been setup to handle this from the parent model. I prefer to remove it and let that validation handle it. I'll investigate.
|
@bajtos there's no |
"updateAttributes" sets |
|
I have a question for you: If I read the code correctly, an update of an embedded model triggers hooks on both the embedded model and the owner model. If Product has embedded Image, then "before save" hook is triggered on both Product and Image. Is this intentional? If not, can you fix that as part of this patch please? Regardless of the answers to my two questions above, please add unit-tests to verify and describe the expected behaviour. |
|
Yes, this is fully intentional - it's the sole purpose of this PR. Before, we were able to add certain logic (property transforms, like urlify/sluggify, timestamps...) using |
|
The intended order is:
I'll add some tests to check this specifically. |
|
@bajtos PTAL |
|
AFAICT, there are no tests checking the order of hooks invoked when an embedded model is deleted/removed. Can you add some please? |
|
As I am looking at the new code, it seems to me that I was wrong when I wrote Yes please, send "where+data+currentInstance" to "before save" hook. AFAICT, the method updateById behaves like updateAttributes, thus the context should be similar or the same. If my understanding is correct, both "prototype.update" and "updateById" fetch the current model instance, apply a partial update using the request data, but then they are making a full save of the whole updated instance. In that case it is ok to use Assuming that |
|
@bajtos I think The race condition is nothing new - I'd be the first to admit that the implementation is a bit naive. Perhaps we should open a ticket: perform true partial - granular - updates for embedded models? |
|
@bajtos you'd run into the same kind of race condition with I have been working with CouchDB for a long time, and since it doesn't support any operators like MongoDB's $set and $push, they only support optimistic locking using a |
|
@bajtos here's the mixin: https://gist.github.com/fabien/20c73aec683be5392326 |
Let me repeat what I wrote while discussing "before save" hooks earlier. Each update operation has two phases that can be "full" or "partial": The first phase interprets request data as a full instance or a partial update. The second phase persists the change to the database and either updates only subset of model properties, or performs a full update (replace). The "before save" hook cares only about the second phase, it does not care whether the initial request data were partial or not. So for example, inst.setAttributes(data);
connector.update(inst.id, data); // Notice that we are not sending a full instance dataOn the other hands, the implementation of the embedded methods always updates the full instance in the database: embeddedModel.setAttributes(data); // treat data as partial, hooks don't care
modelTo.updateAttribute(propertyName, inst);
// calls under the hood something like
connector.update(inst.id, { propertyName: inst.toObject() })
// Notice that we are modifying all data of the embedded instance,
// not only the changed properties.The reason why I introduced In the case of embedded models, we are always updating the whole embedded instance, therefore we don't need to preserve the information which properties should be updated and there is no need to use Think about it in this way: the current implementation of embedded models is closer to an ideal ORM, where you always have the full
The race condition is out of scope of this pull request, I just wanted to point out what is the current behaviour of DAO methods for embedded operations. Feel free to open a new ticket, though I don't think we will have time to work on that ourselves. |
|
@bajtos I can see your point - but if I were to use the same kind of For example, I might want to filter out some properties from |
|
@bajtos Correction: I prefer to know what's going to change, and therefor having |
And this is big problem: you believe that
While I admit this is a valid use-case, "before save" hook is no the right tool for that job. When the model is not embedded, one can write the following code that will not provide Person.findById(id, function(err, person) {
person.name = 'Freddy';
person.save(cb); // ctx.instance == person
});I see that embedded models don't have an equivalent of // Assuming Person embedsOne Address
Person.findById(id, function(err, person) {
person.passport.name = 'Freddy';
person.save(cb);
});The last example above will actually not trigger the hooks on the embedded model, which is IMO a bug. Another example where the hooks are not triggered by the current implementation: Person.create({
name: 'person-name', passport: { name: 'passport-name' }
}, cb);Let's figure out first how to trigger the hooks in all cases where an embedded model is modified/deleted and then we can come back to this discussion with more context knowledge at our hands. One more thing for you to thing through: at the moment, embedded relations don't trigger the "query" hook. Is that ok? Should the hook be triggered? |
This is IMO pretty broken. Your mixin works only for a subset of methods and it does not report any error/warning when an unsupported method is called. The revision is not updated in such case and thus the next write may discard changes made via these unsupported methods. Of course, it's your code, you are free to use it, especially since you are most likley aware of all the ramifications. It's just that I would personally not recommend your mixin as a general-purpose solution for wide LoopBack audience and thus I am reluctant to optimise the hooks for that mixin. |
The change-tracking and replication implementation provides some sort of an optimistic locking scheme too, see loopback/lib/PersistedModel.js. The current implementation has a trade-off where it goes long way to ensure correctness even at the cost of a lower performance. It would be nice if LoopBack had a built-in versioning scheme that could be used both for your optimistic locking scheme and replication's bulkUpdate. A single model setting that will provide |
|
@bajtos yes, the mixin doesn't cover every interaction, but you know what? It's only meant for the limited interaction model that's possible through the remoting API. IMO this is what we should focus on to get the ball rolling, and this should apply for this PR as well. Juggler is not a true ORM in the sense that everything is in memory all the time, as a single consistent system, so we have to deal with that in many different ways (just look at the complexities the new hooks highlight afterall). There are many ways where things might get into an inconsistent state. Working with a single instance over various operations is one of them, and this is where your critique regarding situations missing the hook calls comes from I think. Again, let's optimize for the fire-once way that remoting enforces, and then build from there. |
I don't agree with that in the context of this pull request. We have already learned that providing an incomplete solution which does provide any indication on when it does not work is a bad thing to do. It creates confusion among LB users, adds more support work for us and even discourages users from using LB at all. However, as I shown above, you can bypass the hooks implemented by this patch even using vanilla REST API, so you need to rework it even if we agree to stick to REST API methods only. Let's set this disagreement aside for a while, so that you can focus on implementing a solution that covers all REST API methods, and then we can decide how to move forward. In the worst case, I can finish the patch myself to add support for the remaining use cases. |
|
@bajtos if you are referring to bypassing as being able to overwrite a embedded property directly (through the parent model), then yes, not all methods are covered. However, I actually think that we should filter out these properties by default, so you cannot set them directly, unless a user chooses not to (using some setting/flag). When this filtering is in place, I don't see any REST API method being left to cover. |
|
I'll respond to your comments later. Since #515 was landed, please consider including support for |
I am not sure how far we can get with filtering out properties pointing to embedded models. #491 is adding validation of nested models when a parent model is updated if I understand the patch correctly. strongloop/loopback#1196 is asking for creating embedded models through the parent model. |
|
I am tired of arguing for an implementation that is complete and consistent. As I see it, the current implementation of embedded models have several shortcomings. It should be marked as a "labs" feature, or at least the doc page should list all known issues. In the light of the above, while this pull request is creating new issues that we will have to fix later, it's not making the situation substantially worse, so I am inclining to let @fabien land it as it is now. Again, we should document the new known issues and edge cases, so that users are aware of the dangers waiting for them when using embedded models. @fabien @raymondfeng thoughts? The list of issues compiled from the discussion above:
|
@fabien @raymondfeng ping, what's your opinion here? The patch is more than 6 months old and does not cleanly merge with the current master, should we close it as abandoned? |
|
Is there an ETA on when a decision will be taken? |
|
Any progress on this issue? |
|
@bajtos @raymondfeng This should be addressed. |
|
I am afraid this patch looks abandoned. @protactinium @chrisamoore @ShaharHD would you like to pick this work up and get it to the state where it can be landed? |
|
I have to share our personal experience about this issue. In the past two months we've been in discussions with IBM about using the StrongLoop Standard and Professional licenses. The IBM sales main argument has been something like: "IBM is taking ownership over StrongLoop (and a new thing called API Connect) and will own the product, and as a paying customer we will have the full force of IBM fixing issues just like this". When my answer have always been that StrongLoop and API Connect are not LoopBack. Moving forward, @bajtos asking the community to assist on this issue ... @protactinium @chrisamoore |
|
I have landed these new hooks via #904 and #911, see #497 (comment) for more details. @zelphir @protactinium @chrisamoore @ShaharHD could you please try out the new hooks and let us know how they work for you? If you run into any problems, then please open new issue(s) and mention my github handle in the description. |
|
@bajtos are those fixes were pushed to the npm package of juggler or we need to reference a specific commit in our package.json? |
|
@ShaharHD I haven't released a new version, can you |
|
@bajtos thanks for making operation hooks work with embedded models! |
Fixes #497 by triggering
before/after saveandbefore/after deletehooks on the embedded model instance. Because such instances are usually transient, the expected hooks were not called.