Ah, one of the eternal questions, so there isn’t really a ‘do this’ answer. My 
experience:
 
[multi-methods/protocols]
You need to include the namespace they are compiled in for those methods to 
take effect and because they are defn’d at the root you cannot provide 
collaborators easily. You cannot DI collaborators into the multi-method 
implementations. Of course, you can use thread local binding but this is less 
than satisfactory as your functions are no longer pure. Another workaround is 
to always pass a map of collaborators around…no, I can’t even finish that 
sentence.
I have also seen (defn) happening inside other fns which always makes me 
shiver, but you could defn the multi method inside the component record which 
has access to the collaborators.
[service registry]
I went the other way and had a central service that consumers registered with. 
My command-bus had a (defprotocol IHandle (can-handle [this cmd]) (handle [this 
cmd]). I then had a number of (Stuart Sierra’s) components which, when started, 
registered with their implementations of IHandle with the command-bus for the 
commands they needed. 

These command-handlers were typically injected with the collaborators and then 
delegated to the ‘domain’ fns. My actual ‘domain’ was only ever a map with was 
constructed by (reduce {} events) - lovely!

‘typed-straight-into-email’ and very incomplete pseudocode:

(ns command-bus)
(defprotocol IHandle
  (can-handle [this cmd])
  (handle [this cmd])

(defprotocol IAmACommandBus
  (register [this handler]…))

(defrecord CommandBus [registry]
  IAmACommandBus
  (register [this handler] (assoc! registry #(conj handler))))
(defn command-bus []
    (->CommandBus (atom [])))

(ns ar-1.domain)
;; the ‘pure’ domain functions which can be easily tested
(defn do-something [ar some-state]
  [ar-id (:id ar) :version (inc (:version ar)) 
  :event-type :woot {:this :worked}]))

(ns ar-1.service
  (:require [:command-bus :as command-bus] 
                [:ar-1.domain :as domain])

;; the command handler
(defrecord  DoSomethingCommandHandler [repository some-service]
  command-bus/IHandle 
  (can-handle [this cmd] (= :do-something-with-ar-1 (:command-type cmd))
  (handle [this cmd] 
    (let [ar (repository-api/hydrate repository (:ar-id cmd)
           some-state (some-service (:something cmd))]
            (domain/do-something ar some-state))..)

(defrecord DoSomethingElseCommandHandler [some-other-service]
 ….)

(defrecord DomainComponent [command-bus repository some-service 
some-other-service]
  component/Lifecycle
  (start [_]
    (command-bus/register command-bus (->DoSomethingCommandHandler repository 
some-service))
    (command-bus/register command-bus (->DoSomethingElseCommandHandler 
some-other-service))
   (stop [_] …))

(ns the-app
 (:require [command-bus :as command-bus]
                [some-service….]
                [some-other-service…]
                [ar-1.service :as ar-1])
(let [systems {:command-bus (command-bus/command-bus)
                       :ar-1 (component/using (ar-1/ar-1) [:command-bus 
:some-service :some-other-service]}]
    ;; start the system etc.))
      

Thoughts:
 - boiler plate ‘wiring’ up is done in the .service layer which contains domain 
services that delegate straight to pure domain fns
 - DomainCommandHandlers could themselves be Components but this felt cleaner
 - the very common repetition can be handled by a macro if needed
 - if no extra collaborators are needed then sure, the domain-fns can be 
multi-methods, fine
 - a lot of plumbing but it keeps it very clean.
 - start using Prismatic schema and/or Clojure core.typed from the beginning

Hopefully that is enough to get to started?


> On 22 Jul 2015, at 16:24, keegan myers <keeganmyers...@gmail.com> wrote:
> 
> I've begun building a rather complex web application in 
> Clojure/ClojureScript. After some evaluation I decided that CQRS + event 
> sourcing would fit the requirements well. As such I have a bunch of 
> aggregates (models) each in their own namespace that contain all the 
> applicable record definitions, their invariants, and the commands each 
> responds to. Predictably there are numerous operations that are similar 
> between all aggregates. 
> 
> In my controllers I will be evaluating JSON requests and creating a list of 
> commands based on those requests. I would like to dynamically dispatch the 
> commands to the aggregates and let the aggregates handle them by either 
> returning more commands or committing an event to the db. What is the best 
> practice to dynamically send a command to an aggregate's namespace? I'm aware 
> of solutions that make use of protocols and multimethods. My primary concern 
> is where should I be requiring the aggregate namespaces?
> 
> Is it best to do as some web frameworks do and just require everything (all 
> namespaces) in some kind of application wide superset (probably using 
> tools.namespace). Should I have a superset for each type of namespace (eg all 
> controllers required together, all aggregates ...) or should I only require a 
> namespace on an as needed basis. If so how would I achieve dynamic dispatch 
> between aggregates without requiring their namespace.
> 
> Sorry I'm still pretty new to Clojure. I know I can achieve this in a number 
> of ways, but I'm looking for the most idiomatic approach that will cause the 
> least headaches as this application grows.
> 
> -- 
> 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.

Reply via email to