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.