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 <[email protected]> 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 [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> 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 [email protected].
> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.