domino-clj

mafcocinco 2020-02-07T16:21:08.000900Z

I'm seeing what I think is unexpected behavior. I've boiled it down to a simple example which I'm hoping is either a bug or can be explained such that I can understand how I'm using domino incorrectly. Here is the code:

mafcocinco 2020-02-07T16:21:21.001Z

mafcocinco 2020-02-07T16:22:52.002900Z

I would expect :output-1 to be resolved prior to :output-2 being called. However, that does not seem to be the case. The result of function-1 is making its way into domino.core/db but the value passed to function-2, which clearly has a dependency on :input-1 and :output-1, is nil for :output-1.

mafcocinco 2020-02-07T16:23:03.003200Z

Am I doing something incorrect in setting up this model?

mafcocinco 2020-02-07T16:23:40.003600Z

I'm using version 0.2.0 which probably should be upgraded.

mafcocinco 2020-02-07T16:31:44.004Z

Just to be sure, I switched to 0.3.2 and same output.

mafcocinco 2020-02-07T16:32:19.004700Z

And for completeness, here is the state of domino.core/db

{:input-1 "hello",
 :input-2 "good-bye",
 :output-1 {:input-1 "hello", :input-2 "good-bye"},
 :output-2
 {:message {:input-1 "hello", :output-1 nil}, :outputs {:output-2 nil}}}

yogthos 2020-02-07T16:32:21.004800Z

yeah I was just trying it locally with 0.3.2 and seeing the same as well

yogthos 2020-02-07T16:32:35.005100Z

it does look like a bug to me

yogthos 2020-02-07T16:33:43.005600Z

oh

mafcocinco 2020-02-07T16:33:50.005800Z

maybe not a bug 🙂

yogthos 2020-02-07T16:34:00.006100Z

nope nevermind 🙂

mafcocinco 2020-02-07T16:34:36.006800Z

Something must have changed because I'm pretty sure this was working in an earlier version. I'm going to try it against something prior to 0.2.0 which is what I was using.

yogthos 2020-02-07T16:37:19.007200Z

hmm yeah I was thinking we had a test case for a similar scenario

mafcocinco 2020-02-07T16:38:02.007700Z

I'm a little confused because I wasn't using any version prior to 0.2.0.

yogthos 2020-02-07T16:38:42.008100Z

for example this test seems like it should also fail:

(deftest test-cascading-events
  (test-graph-events
    [{:inputs  [[:a]]
      :outputs [[:b] [:c]]
      :handler (fn [ctx {:keys [a]} {:keys [b c]}] {:b (+ a b) :c (+ a c)})}
     {:inputs  [[:c]]
      :outputs [[:d]]
      :handler (fn [ctx {:keys [c]} _] {:d (inc c)})}]
    [[[:a] 1] [[:b] 1]]
    {::core/db       (assoc default-db :a 1 :b 2 :c 1 :d 2)
     ::core/change-history [[[:a] 1]
                      [[:b] 1]
                      [[:b] 2]
                      [[:c] 1]
                      [[:d] 2]]}))

yogthos 2020-02-07T16:40:08.008900Z

it looks like we set this test up to use the original value of c though

yogthos 2020-02-07T16:40:39.009400Z

ah ok, so initial db is

(def default-db {:a 0, :b 0, :c 0, :d 0, :e 0, :f 0, :g 0 :h {:i 0}})

yogthos 2020-02-07T16:41:15.010100Z

and we expect d to be 2 which mean c has to be set to 1

yogthos 2020-02-07T16:41:38.010500Z

but it looks like the same cascading scenario

mafcocinco 2020-02-07T16:48:26.014Z

Is ::core/db the initial state of the db or what you expect to get? If it is what you expect to get, then the test looks like it would break as the value of :c in the first event would be nil and (+ 1 nil) (which is what (+ a c) would translate to) will throw a null pointer expection. Assuming that is not happening in the test suite, then it would seem that ::core/db is being used as the initial state? Maybe it it is being used (incorrectly) for initial state and expected output or something like that?

mafcocinco 2020-02-07T16:49:16.014500Z

Or maybe test-graph-events is doing something else to the initial value of :c?

yogthos 2020-02-07T16:53:42.015100Z

if I recall correctly, the ::core/db that the event sees should be the latest updated version

mafcocinco 2020-02-07T16:55:06.016100Z

Wouldn't c be nil in the first event handler?

yogthos 2020-02-07T16:57:19.016500Z

it uses the default-db that sets it to 0

mafcocinco 2020-02-07T16:58:40.016700Z

okay. That makes sense

mafcocinco 2020-02-07T16:58:52.017Z

that is part of how test-graph-events works.

yogthos 2020-02-07T16:59:27.017500Z

(let [schema {:model
                [[:a {:id :a}]
                 [:b {:id :b}]
                 [:c {:id :c}]
                 [:d {:id :d}]]
                :events
                [{:inputs  [:a]
                  :outputs [:b :c]
                  :handler
                           (fn [ctx {:keys [a] :as inputs}
                                {:keys [b c]
                                 :as outputs}]
                             (println "event 1"
                                      "\ninputs:" inputs
                                      "\noutputs:" outputs)
                             {:b (+ a b) :c (+ a c)})}
                 {:inputs  [:c]
                  :outputs [:d]
                  :handler
                           (fn [ctx {:keys [c] :as inputs} outputs]
                             (println "event 2"
                                      "\ninputs:" inputs
                                      "\noutputs:" outputs)
                             {:d (inc c)})}]}]
    (-> (core/initialize schema {:c 0})
        (core/transact [[[:a] 1] [[:b] 1]])))

yogthos 2020-02-07T16:59:41.017800Z

so here's what I'm seeing from that

event 2 
inputs: {:c 0} 
outputs: {:d nil}
event 1 
inputs: {:a 1} 
outputs: {:b 1, :c 0}
event 2 
inputs: {:c 1} 
outputs: {:d 1}

yogthos 2020-02-07T17:01:51.018300Z

it's also a question why event 2 gets triggered before 1

mafcocinco 2020-02-07T17:04:47.019100Z

I think it has something to do with the second handler taking 2 inputs, 1 of which is an output from a previous handler

mafcocinco 2020-02-07T17:04:59.019200Z

mafcocinco 2020-02-07T17:05:05.019600Z

The above breaks

mafcocinco 2020-02-07T17:05:19.019900Z

c is nil when the second handler is executed.

mafcocinco 2020-02-07T17:06:32.021100Z

Perhaps domino thinks it can run the second handler when it has partial input (i.e. a is set as part of the transaction) instead of waiting until it has a value for c or all dependent handlers have executed.

mafcocinco 2020-02-07T17:07:01.021700Z

(-> (domino/initialize schema) (domino/transact [[[:a] 1] [[:b] 1]]) :domino.core/db)

yogthos 2020-02-07T17:07:07.021900Z

yeah it's looking like the handler gets picked up for the initial transaction with the first set of values

mafcocinco 2020-02-07T17:07:24.022300Z

yes, when it should be waiting as it only has partial input

yogthos 2020-02-07T17:07:31.022500Z

so likely a problem with the way the graph is built then

yogthos 2020-02-07T17:08:19.023300Z

the handler should get triggered a second time when a new value is generated by the first handler

mafcocinco 2020-02-07T17:09:58.025300Z

yes. Weather or not it is triggered the first time is perhaps a matter of opinion. IMO, it should not as the graph should indicate that it will eventually have all inputs (i.e. there is a clear dependency between event one and event two which will result in a complete set of input). But, that is just one opinion. If we run events with partial input, we just need to be explicit about it in the docs as handlers will have to be written to account for this.

mafcocinco 2020-02-07T17:10:36.025500Z

i.e. c is nill in the above example.

mafcocinco 2020-02-07T17:11:02.026100Z

Either way, there is definitely a bug as the second handler is never invoked with the complete set of input.

yogthos 2020-02-07T17:12:09.027Z

I think events being triggered multiple times would mostly affect effectful events

yogthos 2020-02-07T17:12:33.027700Z

when they're pure then it should be idempotent, and it's just an optimization to ensure they're not being evaluated unnecessarily

yogthos 2020-02-07T17:12:59.028500Z

but there are cases where you might want to have effectful events, so at least documenting that would be important

mafcocinco 2020-02-07T17:13:07.028800Z

I mostly agree. However, the event handler itself will still need to account for the fact that c is nil, for example.

yogthos 2020-02-07T17:13:16.029200Z

yup

mafcocinco 2020-02-07T17:13:26.029400Z

That is what I was referring to.

mafcocinco 2020-02-07T17:13:41.030Z

Just need to make sure that people know they might get incomplete input.

yogthos 2020-02-07T17:13:47.030200Z

right

yogthos 2020-02-07T17:14:07.030700Z

but the other issue with the handler not being triggered does need fixing 🙂

yogthos 2020-02-07T17:14:29.031300Z

and we should add a test for that one for the future

mafcocinco 2020-02-07T17:14:29.031400Z

yup.

mafcocinco 2020-02-07T17:14:47.031700Z

We have the test, in the code above. 🙂

mafcocinco 2020-02-07T17:17:26.032200Z

do you want me to add an issue to the github page?

mafcocinco 2020-02-07T17:17:53.032600Z

Slack is not the best place to document things like this. 😉

yogthos 2020-02-07T17:22:56.032800Z

yeah sounds like a plan

yogthos 2020-02-07T17:23:05.033100Z

and yeah we should just add the test from here in

mafcocinco 2020-02-07T17:28:44.033400Z

One other thing I noticed, which I will include in the issue

mafcocinco 2020-02-07T17:28:54.033500Z

mafcocinco 2020-02-07T17:31:02.035400Z

Note line 11: This does not actually work as the map that is passed into the handler has a key called :c which has a value of nil. Would it be possible to strip nil values from the input map so that destructuring worked? The effect would still be the same but if we are executing the handlers with partial inputs, it would be nice to be kind to our users and make sure destructuring worked as expected.

mafcocinco 2020-02-07T17:40:18.035700Z

https://github.com/domino-clj/domino/issues/24

yogthos 2020-02-07T17:40:48.036100Z

perfect, and I agree with the destructuring point

mafcocinco 2020-02-07T17:41:35.036900Z

I don't think I will have time to look at this in the next 2 weeks (work is pretty busy right now). If it is not already picked up, I'll have a look around then when I come up for air.

mafcocinco 2020-02-07T17:41:43.037200Z

for now, I can work around it in my code.

yogthos 2020-02-07T18:57:07.037700Z

sounds like a plan, if I get a chance to look before then I'll let you know