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
Operation hooks for persistent models (intent-based hooks) #403
Conversation
callback(err, obj); | ||
if(!err) Model.emit('changed', obj); | ||
var self = this; | ||
Model.notify('before save', { model: obj }, function(err) { |
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.
What's the convention for the event name? before-save
, beforeSave
or before save
?
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.
before save
ATM. I am open to other conventions like before-save
, save:before
or even saving
(before) and saved
(after). I'd like to avoid beforeSave
as it may be confused with the old Model.beforeSave
hook.
I like the idea of |
FWIW If the Mozilla doc is correct, Can you propose a better name? Few ideas from myself: |
Is it somewhat similar as http://nodejs.org/docs/v0.11.9/api/process.html#process_async_listeners? |
Not at all. Node's async listeners are synchronous functions that are called whenever an async method is invoked. In our case, we have an async (callback-based) function that is called before/after an operation is performed. |
I am also not very happy with the term "operation", but I could not come with a better name. E.g. "intent" does not make much sense to me. Although I guess |
See the Java conventions - http://docs.oracle.com/cd/E16439_01/doc.1013/e13981/cmp30cfg014.htm |
The Java conventions specify the names of the lifecycle events, I don't see how it can help us to pick up a better name for |
3c727b4
to
5de33f3
Compare
@ritch @raymondfeng I have finished the first cut of this feature, please review. I'll try to catch you up before the standup to discuss it online. Good news: The current implementation in Juggler works supports Major change against the proposal: I have omitted before/after Critical points to discuss and resolve before this PR can be finished:1) The current implementation of Does it mean such fix will be a breaking change? If it is, do we want to deal 2) What data should be passed to hooks from
At the moment, both Fetching the data from the database may be suboptimal:
I am thinking about the following solution: Implement two flavours of The benefit of this approach is that full model data can be fetched lazily, The drawback is adding more complexity for the developers to deal with, making the hooks more difficult to use. What do you think? Can you come up with a better solution? 3) Id-based query modified by
The problem: a hook observer is allowed to change the where object, expecting Possible solutions:
My preference is to implement the first solution (switch to Thoughts? 4) Id-based query modified by This is a similar issue like above, but this time for However, there is an option of not triggering the IMO, the ideal solution is to modify the connector API for However, since that is a breaking change, so I am proposing to leave it until At the end, we can always add code emitting the hook later in the future, Any objections? Non-critical but still important:5) The method needs to detect what propeties were changed Now consider a hook that wants to add a new property that should be changed I am thinking about the following solution: Add context methods 6) The default implementation The current implementation is thus inconsistent. Depending on the Do you consider it a problem? 6) The default implementation The current implementation is thus inconsistent. Depending on the Do you consider it a problem? |
@STRML @clarkorz @alFReD-NSH @violet-day @kevinsproles @offlinehacker @arlaneenalra @projectxmaker @bluestaralone The idea is to keep the old (broken) hooks in place for now, so that backwards compatibility is preserved. At the same time, there will be a new parallel implementation that will hopefully fix all know flaws. There isn't much documentation of the proposed solution, except the discussion in #367. Check the unit tests to see the new hooks in action. Quick overview of the hooks:
See my earlier long descriptive comment for the list of exceptions where the implementation differs from the list above. I'd like to finish most of this work by Monday/Tuesday, so please don't wait too long with your feedback. |
Note for reviewers: consider ignoring whitespace changes - https://github.com/strongloop/loopback-datasource-juggler/pull/403/files?w=1 |
Regarding current hooks in this pr, they do cover my use cases and I think the API is good, other stuff in the discussion wouldn't really affect me :) And if you are looking for a better name than observe, I would suggest |
@alFReD-NSH Thank you for the comment. Ad naming conventions, my original naming is based on the Observer design patter. Your proposal If we stay with Does anybody have a strong opinion which option is better? |
d352d8d
to
2ed6764
Compare
@raymondfeng @ritch I have finished the patch, PTAL. See the PR description field for a short summary of the new hooks. Notes related to my long comment above and our Friday's discussion: Items from my comment:
Extra items
Rejected:
Reworked:
|
2ed6764
to
476088b
Compare
@raymondfeng @ritch any comments? May I land this patch? |
What intent hooks should we use for the following use cases?
I'm particularly concerned by the |
You can either use the old My initial version had
Ah, good catch. Is the same check applicable in the situations where you are modifying data (foreign keys) too, or is it specific to deletes? If it's specific to deletes only, then we can add
Caching is tricky.
It seems to me that caching should be applied exclusively to the method Here is the caveat that is related to caching and that could prove to be difficult to fix incrementally (in a non-breaking change): the current design does not allow observers to short-circuit the usual flow of execution and provide a custom success response. On the other hand, since intent hooks are shared by multiple methods, each of the method returning different format of the data, I consider this limitation reasonable. Thoughts? In general, I'd like to focus this PR on the small core and leave out everything that can be incrementally added later. The patch is already too large for review and will create merge conflicts with at least one other PR, so I don't want to make the situation even worse by waiting until more stuff is crammed in. |
I'm fine to merge the PRs as long as other candidate of hooks can be added incrementally. BTW, I'll take the intents into consideration during the spike to clean up/consolidate connectors. |
LGTM. Please run the code with a connector such as MySQL and MongoDB to make sure no test failures are created. |
When I run
Where are the instructions for setting up the dev env for the connector? |
Copy https://github.com/strongloop/strongloop-config/blob/master/.loopbackrc to your home directory. |
Thanks. The tests run super slow now, but at least they are passing. We should really provide a local env for running the tests, but that's out of scope of this discussion. I'll verify that MySQL tests pass with the new juggler tomorrow and send a PR to run these tests as a part of MySQL connector test suite. |
@raymondfeng done. loopbackio/loopback-connector-mysql#71 adds hooks test suite to the MySQL connector and loopbackio/loopback-connector#8 fixes a problem I have discovered along the way. I had to rework the test suite a bit to make it work with the MySQL connector. I have also discovered a problem in the design of
PTAL at the six new commits and let me know if there is anything else to change before landing this patch. |
Hi guys! |
I did not get to review the code is thoroughly I would of liked to. But I do not want to hold up landing this any longer. |
@bajtos ^^^^ |
One more change: I simplified the tests to reuse the same datasource instance, I hope it will speed up the tests when running against a real database. @ritch Ok. I'd say it's most important to review the public API, implementation details can be always fixed/improved later. @aantipov Thank you for chiming in. Methods |
LGTM |
Implement infrastructure for intent-based hooks.
This patch introduces a new API for "intent-based" hooks. These hooks are not tied to a particular method (e.g. "find" or "update"). Instead, they are triggered from all methods that execute a particular "intent". The consumer API is very simple, there is a new method Model.observe(name, observer), where the observer is function observer(context, callback). Observers are inherited by child models and it is possible to register multiple observers for the same hook. List of hooks: - query - before save - after save - after delete
36dc948
to
1fd6eff
Compare
@slnode test please |
Intent-based hooks for persistent models
Connect #367, see my comment there for description of this patch.
/to @raymondfeng @ritch please review
/cc @fabien FYI, feel free to chime in too
The New Hook API
This patch introduces a new API for "intent-based" hooks. These hooks are not tied to a particular method (e.g. "find" or "update"). Instead, they are triggered from all methods that execute a particular "intent".
The consumer API is very simple, there is a new method
Model.observe(name, observer)
, where the observer isfunction observer(context, callback)
.Observers are inherited by child models and it is possible to register multiple observers for the same hook.
A list of hooks implemented by this PR follows below.
query
The
query
hook is triggered whenever a database is queried for models. Observersmay modify the query, e.g. by adding extra restrictions.
Context properties
Model
- the constructor of the model that will be queriedquery
- the query containing fieldswhere
,include
,order
, etc.Examples:
before save
The hook
before save
is triggered before a model instance is about to be modified (created, updated). The hook is triggered before the validation.Depending on which method triggered this hook, the context will have one of the following sets of properties.
Full save of a single model
Model
- the constructor of the model that will be savedinstance
- the model instance to be saved. The value is an instance ofModel
class.Partial update of possibly multiple models
Model
- the constructor of the model that will be savedwhere
- the where filter describing which instances will be affecteddata
- the (partial) data to apply during the updateExamples:
after save
The hook
after save
is called after a model change was successfully persistedto the datasource.
Depending on which method triggered this hook, the context will have one of the following
sets of properties.
Single model
Model
- the constructor of the model that will be savedinstance
- the model instance that was saved. The value is an instance ofModel
classand contains updated values computed by datastore (e.g. auto-generated id).
Partial update of possibly multiple models
Model
- the constructor of the model that will be savedwhere
- the where filter describing which instances were querieddata
- the (partial) data applied during the updateAt the moment, this second set is used exclusively by
Model.updateAll
.after delete
The hook
after delete
is triggered after some models were removed from the datasource.Context properties
Model
- the constructor of the model that will be queriedwhere
- the where filter describing which instances were queried.