Hiya, I did consider (and have used) guarded visitors before but I discounted them because ‘action!’ ended up having the `case` statement when that visitor was interested in multiple events. I guess I could have one visitor per event ….
To be specific, these visitors are pure, read-only visitors - they won’t mutate the world they only react to specific changes in the world. Interesting - thanks Aaron. > On 8 Dec 2015, at 01:32, Aaron D. Valade <aval...@gmail.com> wrote: > > 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 > <mailto: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 > <x-msg://17/fn%20%5Bworld%20event%5D%0A%20%20...%0A%20%20(handler%20world%20event> > ... > ) => 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 > <x-msg://17/let%20%5Bpre-handler-kw%20(keyword%20(str%20'pre-'%20(name%20(:event-type%20event>))) > 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)] > <x-msg://17/when%20pre-event-observer%20(pre-event-observer%20event%20pre-entity%20world>) > (when post-event-observer (post-event-observer event post-entity new-world)))) > (assoc result :world new-world)) > > (defn build-world events observers > <x-msg://17/reduce%20process-event%20%7B:world%20%7B%7D%20:observers%20observers%7D%20events>) > > 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> 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+unsubscr...@googlegroups.com> > clojure%2bunsubscr...@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> > 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> > 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> 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 > <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+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> > 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> > 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> 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 > <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+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. > 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.