Conversation
* Initializing values * Loading data
|
I have a few more patterns to add. Happy to adjust for feedback on format (and even happier to be alerted to undesirable patterns I shouldn't be recommending!). |
| {#_... | ||
| ::initialize-thing-owner | ||
| [:what [_ :thing/id id {:then not=}] | ||
| :then (o/insert! id :thing/owner nil)]}) |
There was a problem hiding this comment.
Instead of using {:then not=} here you could use o/contains? to check if the fact already exists:
[:what
[_ :thing/id id]
:then
(when-not (o/contains? o/*session* id :thing/owner)
(o/insert! id :thing/owner nil))]The problem with using {:then not=} here is that you may actually need to update :thing/id for some reason, and if you do, it'll set the :thing/owner to nil even if it already had a value.
There was a problem hiding this comment.
Interesting! I've run into that limitation with {:then not=} and worked around it sometimes when I've needed to. Using o/contains? is perfect for this.
Updated to follow your suggestion
There was a problem hiding this comment.
@oakes - one mistake I started making here pretty quickly was to do this:
[:what
[_ :thing/id id]
:when (not (o/contains? o/*session* id :thing/owner))
:then
(o/insert! id :thing/owner nil)]This case can happen, which is strange:
[:what
[id :thing/id id]
[id :thing/owner owner]
:when (not (o/contains? o/*session* id :thing/owner))
:then
(o/insert! id :thing/owner nil)]In this case, there is a match, which is counter-intuitive to me.
There was a problem hiding this comment.
I believe *session* isn't bound to anything in :when blocks. I need to fix that. I'm about to board a plane but I'll look into that soon.
There was a problem hiding this comment.
If you can, try the latest commit. It should fix this problem.
There was a problem hiding this comment.
Did you get a chance to try the latest commit and see if it fixes this?
| {::fetch-thing | ||
| [:what [_ :thing/id id {:then not=}] | ||
| :then ;; fetch-thing returns a namespaced map | ||
| (o/insert! id (fetch-thing id))] ;; `{:thing/name "name", :thing/owner 123, #_...}` |
There was a problem hiding this comment.
In this example, is the fetch-thing fn getting the data from the session via o/query-all, or from somewhere else?
There was a problem hiding this comment.
In this example, fetch-thing is returning a map from the database, not from the session. (The map here is flattened and namespaced.)
The benefit of this in my use cases is that I can put a single id for an object, and the rules will fetch all the related objects. This saves me from having to gather everything up before hand, which can often span quite a few related entities and domains.
There was a problem hiding this comment.
Yeah that makes sense. Is there any benefit to doing the db query from inside a rule, vs querying and inserting from the outside? I tend to avoid interacting with external things (databases, rendering, etc) from inside rules, though this is probably OK. One big gotcha that I mention on the readme is using query-all from within a rule that queries another rule. That could cause problems because it eliminates reactivity. But if you're just querying some external db then it won't be reactive anyway.
There was a problem hiding this comment.
Putting the queries in the rules handles the joins for you automatically, somewhat like what Pathom does. If I insert one id for an entity, the rules engine will make all the queries, find all the relations, load all the related entities, and execute the rules against them all.
I've found this to be extremely useful.
To give a concrete example: for my main use case, we have a library of rules used by different services in a microservice environment. If I'm in the service that owns an entity, I want to pass in a db connection so I can use transactions. But an entity in a service is almost always related to entities in another service. Those entities I want to fetch over a network call to another service (or from Redis, depending on what it is).
Again, I might be using the library from the front end, in which case I want to make a network call for entities, but need to make those calls differently (authorization rules).
I can pass a triple to the rules engine indicating which service the rules engine is being used from. Then the rules govern how an entity is loaded based on the fact indicating where it is being called from. These are written once for all services, but handle all the different use cases.
So the rules engine turns into a pretty powerful query system + rules engine. For my use case, the query part is as much part of the value proposition as the "normal usage" of the rules.
(One potential drawback is that all these network calls need to be blocking. We typically block on network calls anyway, so this hasn't been an issue.)
There was a problem hiding this comment.
In the case of ::fetch-thing there is no join. When you use this technique are you normally querying with more than just an id, so a join is needed? Just want to understand the use case.
|
There are a couple problems I don't yet have a good solution for, but that could be interesting. A big one is debuggability. It would be nice to know when the rules are updating. My current workaround is to wrap |
|
The most recent commit makes the doc more clear on how the joins work in using O'Doyle as a query engine |
|
Thanks i'll look them over when i have time. Just returned from my work trip so still catching up on things. |
|
One thing it may be good to add a pattern for is visibility into rules firing. Clara has tracing listener functionality. With O'Doyle, I've done one of two things:
Perhaps there are better ways to do this. But in my experience so far introducing people to O'Doyle, the most common difficulty (other than the paradigm shift) is understanding when and why rules are firing. |
|
Good points. I made a ticket for improving debugging here #19 |
|
Check out the |
#17
Initializing values. How to initialize attributes to nil to avoid problems with accumulators not matching.
Loading data. Build a graph query system to pull data from local database or services.