clara

http://www.clara-rules.org/
Konstantinos Tzanidakis 2020-01-07T14:32:01.048300Z

Hi all, I want to serialize/deserialize the working-memory separately so I started off by using a simple implementation for IWorkingMemorySerializer that serializes to an atom (as per Clara's docs) and deserializing from the atom by derefing it. That works perfectly fine, in that I am able to run queries and get the same results from both the original and deserialized session. However, if I add the extra step to write to fressian and read fressian to the ser/deserializer of the IWorkingMemorySerializer , all queries in the deserialized version of the session return with nil: eg. ({:?crn nil}) while the same query on the original is returning ({:?crn {:fact-type :client-representative-notification, :name "Alice", :client "Acme", :level :high}}) . I should note that I am fairly new to clara rules. Has anyone come across something like this before?

ethanc 2020-01-07T15:09:51.050100Z

@konstantinos562, Do you have a small snippet, it would help me reproduce it locally?

Konstantinos Tzanidakis 2020-01-07T15:12:24.050600Z

Hi @ethanc do you need the rules as well?

Konstantinos Tzanidakis 2020-01-07T15:13:08.050900Z

(defrecord WorkingMemoryAtomSerializer [input output]
  d/IWorkingMemorySerializer
  (serialize-facts [_ facts]
    (reset! state (write-fressian facts)))
  (deserialize-facts [this]
    (-> @state io/input-stream read-fressian)))

Konstantinos Tzanidakis 2020-01-07T15:14:12.051800Z

with (def state (atom nil)) in the namespace

Konstantinos Tzanidakis 2020-01-07T15:14:36.052100Z

and the read and write fressian as follows

Konstantinos Tzanidakis 2020-01-07T15:14:40.052500Z

(defn write-fressian [obj]
  (let [baos (ByteArrayOutputStream.)
        wr   (frz/create-writer baos)]
    (frz/write-object wr obj)
    (.toByteArray baos)))

(defn read-fressian [b]
  (-> b frz/create-reader frz/read-object))

Konstantinos Tzanidakis 2020-01-07T15:14:47.052800Z

sorry for the long posting all

Konstantinos Tzanidakis 2020-01-07T15:15:50.054Z

NB: I have replaced fressian with nippy/freeze and nippy/thaw and it works perfectly fine now. I am not sure what the issue was (most probably I am doing something wrong there)

2020-01-07T15:16:11.054300Z

@konstantinos562 not sure what frz ns is referring to

2020-01-07T15:16:23.054700Z

You may just not have setup good read/write handlers for your facts

2020-01-07T15:16:35.055200Z

Fressian’s defaults tend to be insufficient

Konstantinos Tzanidakis 2020-01-07T15:16:36.055400Z

sorry it's the requirement: [clojure.data.fressian :as frz]

2020-01-07T15:16:55.055900Z

or they will cause many clj data structures to become Java structures - or not the same as before

2020-01-07T15:17:33.057Z

you can see Clara defines a more robust set of fressian handlers for clj to clj serialization https://github.com/cerner/clara-rules/blob/master/src/main/clojure/clara/rules/durability/fressian.clj#L231

2020-01-07T15:17:47.057600Z

however, there really is no compelling reason to not just use nippy if you have it set up

2020-01-07T15:18:03.058200Z

I wouldn’t have used Fressian to impl Clara’s session serialization if I were to do it again today. it is difficult to work with in what it provides by default and I don’t think it is any better than something like nippy or perhaps a few others. It is also not documented super well and at the “primitive” level makes some unavoidable Java collections that are awkward to convert from (and also wasting perf time)

Konstantinos Tzanidakis 2020-01-07T15:18:11.058600Z

for this example, I have not set up any handlers but the facts are fairly simple

[{:fact-type :client-representative :name "Alice" :client "Acme"}
 {:fact-type :support-request :client "Acme" :level :high}]

2020-01-07T15:18:19.058800Z

(this is all pluggable and could be implemented with something else on Clara’s side just as well too)

2020-01-07T15:18:47.059200Z

try to Fressian write/read that and see that it returns you the facts in the same structure

2020-01-07T15:18:54.059400Z

leaving Clara session/rules out of the test

2020-01-07T15:19:11.059600Z

that may give some insight

Konstantinos Tzanidakis 2020-01-07T15:20:13.060400Z

@mikerod simply using the fressian write/read does work correctly in REPL

Konstantinos Tzanidakis 2020-01-07T15:20:46.061100Z

yes, I think I will use the nippy as it seems to be something in the fressian side that I cannot track at the moment

2020-01-07T15:21:17.061700Z

> simply using the fressian write/read does work correctly in REPL You’ve analyzed waht is returned? is it still a clj vector with 2 clj maps etc?

Konstantinos Tzanidakis 2020-01-07T15:21:24.062Z

thank you very much for the prompt responses and help!

Konstantinos Tzanidakis 2020-01-07T15:21:50.062800Z

@mikerod yes it returns exactly the same facts that are serialized; i.e. a vector of two clj maps

2020-01-07T15:21:55.062900Z

I’d have to experiment some to look close, can’t right now

2020-01-07T15:23:19.063200Z

I have some examples in http://www.metasimple.org/2018/02/19/clj-fressian-ext.html

2020-01-07T15:23:25.063500Z

where things don’t work out-of-the-box with conversions and types

2020-01-07T15:23:43.063700Z

Under “Default handlers”

2020-01-07T15:23:59.064Z

However, didn’t mention anything with vectors/maps there

2020-01-07T15:26:52.064500Z

I immediately see that types at least aren’t preserved

(let [r (serde-obj [{:fact-type :client-representative :name "Alice" :client "Acme"}
                    {:fact-type :support-request :client "Acme" :level :high}])]

  (type r))
;;= java.util.Arrays$ArrayList

2020-01-07T15:27:09.065100Z

this isn’t immediately meaning Clara wouldn’t accept it - but it is already on shaky ground

Konstantinos Tzanidakis 2020-01-07T15:27:14.065200Z

oh I see

Konstantinos Tzanidakis 2020-01-07T15:27:25.065500Z

@mikerod thank you very much on this!

2020-01-07T15:27:53.066Z

you could attempt to just add the handlers from clara.rules.durability.fressian

2020-01-07T15:28:15.066500Z

only issue there being you may not want to deal with the “identity preserving” parts potentially

2020-01-07T15:28:57.067100Z

of how you’d do that

Konstantinos Tzanidakis 2020-01-07T15:29:01.067300Z

that makes sense

2020-01-07T15:29:26.067700Z

has 2 dynamically scoped bindings you add for it’s internal caching

2020-01-07T15:29:40.068Z

you can see it via pform/thread-local-binding

2020-01-07T15:29:53.068300Z

d/node-id->node-cache doesn’t matter for working memory - so you can leave that off

2020-01-07T15:30:18.068800Z

d/clj-struct-holder is required I’m fairly sure - it is used to preserve object identity during (de)serialization