What I’ve done in a similar situation is having a protocol defined like so:

    (defprotocol Transition
(valid? [this world input] “Checks if the transition is valid for this input given the state of the world”) (action! [this world input] “Changes the state of the world based upon the input and returns a new world state”)

Then you can define a runner:

    (defn check-transitions
      [transitions]
      (fn [world input]
        (let [run-t (fn [world t]
                      (if (valid? t world input)
                        (action! t world input)
                        world))]
          (reduce run-t world transitions)))

Now, just implement your transitions and then for each input you receive, run it through the check-transitions function. The =valid?= predicate function is called to check if the transition is interested in the input event and then we only call =action!= when needed. By running a reduce, we then iterate over the transitions sequentially, passing the new world state to each transition.

You could also wrap the =world= item in an atom and then change the =check-transitions= into a transducer that could be the xform for a channel to hook up to whatever is generating your inputs. Or even just leave the =world= item as local state in a closure of the xform and just emit the new worlds from the other end of the channel.

On 8 Dec 2015, at 1:19, Colin Yates wrote:

Thanks Jason,

I don’t particularly want dynamic registration; when the ‘world’ is instantiated it can now about the observers.

I could do this but it is missing the ‘filter out uninteresting events’ bit. I want each observer to declare its interest.

Your ‘middleware pattern’ however is something I would use to do the delegation (i.e. the actual glue that pushes to each interested observer) as the world itself shouldn’t really care.

Thanks for the thoughts - keep them coming!

On 7 Dec 2015, at 17:15, Jason Felice <jason.m.fel...@gmail.com> wrote:

It looks like you want dynamic registration of event handlers, which is not something I've done. If you *didn't* want that, then this the middleware pattern:

(defn null-processor
[world event]
world)

(defn some-other-middleware
[handler]
(fn [world event]
  ...
  (handler world event)
  ...
  ) => world'

(def processor
(-> root-middleware
 some-other-middleware
 ...))

Each processor can respond to some subset of events and ignore the rest. In this case I folded "basket" into world.

I've thought a bit about making data-driven middleware and how to register or deregister, but not come up with a decent solution – mostly because ordering is often important.


On Mon, Dec 7, 2015 at 6:01 AM, Colin Yates <colin.ya...@gmail.com <mailto:colin.ya...@gmail.com>> wrote:
Hi all,

(apologies for the wall of text but I think the context might be useful)

I am using event sourcing so the world, at any given point in time is simply a `reduce` over those events. Throughout the application different things are interested in different events.

A design that is emerging is the notion of a 'world' which knows how to consume the various events and allows other parts of the system to respond to certain states having happened. All of this is in-memory and could be client or server side (yay for .cljc and ClojureScript).

In concrete terms imagine I have am modelling shopping baskets (again, all in memory). I might be interested in knowing:
- whenever a certain item is added to a basket
- whenever a basket is cancelled
- whenever a complete basket is ordered

I could of course just filter the event log and pick out those events but I typically want the entire entity _as it was when that event happened_, so the event itself isn't sufficient.

My question is how best to model the 'I am interested in pre-x and post-y'. In general, it is interesting to know the event, the aggregate root (shopping basket) that event is associated with and the world (both the aggregate root and the world as they were at the time of the event).

I could have an EventObserver: (defprotocol EventObserver (observe [this event entity world]) which the world notifies. One part of the system will have one set of EventObservers, another will have a different set of EventObservers. Also, some parts need to know _before_ the event and others _after_ the event.

I don't want each Observer to have to specify every single event so a protocol defining a pre/post method for each event wouldn't work because (AFAIK) you can't have a default implementation of a protocol and you can't have a partial implementation of a protocol.

Where I am at is thinking that the world understands a map of EventObservers, with one key for each pre/post event:

{:pre-event-one EventObserver :post-event-one EventObserver
:pre-event-two EventObserver :post-event-two EventObserver}

etc.

and each Observer can register their own map of EventObservers. I can optimise the code by either having the world handle nil EventObserver or having a default fully-populated map of EventObservers which Observers can simple assoc their own handlers onto.

Building the world is then trivially (usual disclaimer - hacky stream-of-consciousness code):

(defn- locate-entity [world entity] ...)
(defn- update-entity! [world entity] ...)

(defn- process-event [{:keys [observers world] :as result} event]
(let [pre-handler-kw (keyword (str 'pre-' (name (:event-type event)))) post-handler-kw (keyword (str 'post-' (name (:event-type event)))
      pre-entity (locate-entity world event)
      new-world (update-entity world entity)
      post-entity (locate-entity new-world event]
 (do all (for [o observers
:let [pre-event-observer (pre-handler-kw o) post-event-observer (post-handler-kw o)]] (when pre-event-observer (pre-event-observer event pre-entity world)) (when post-event-observer (post-event-observer event post-entity new-world))))
(assoc result :world new-world))

(defn build-world [events observers]
(reduce process-event {:world {} :observers observers} events))

The above code could be improved in a myriad of ways, but hopefully it is clear enough to highlight the problem: what mechanism is idiomatic in Clojure to implement the Observers where each Observer is interested in a subset of before and after a subset of events.

If you are thinking 'duh, this is obvious - use X' or 'what! that's not true of course you can do X with protocols' then yep, I have almost certainly overlooked something.

Finally - yeah, at times like this I really miss AOP.

Thanks for still reading :-)

Colin



--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com <mailto:clojure@googlegroups.com> Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com <mailto:clojure%2bunsubscr...@googlegroups.com>
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en <http://groups.google.com/group/clojure?hl=en>
---
You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com <mailto:clojure+unsubscr...@googlegroups.com>. For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en <http://groups.google.com/group/clojure?hl=en>
---
You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com <mailto:clojure+unsubscr...@googlegroups.com>. For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to