Effects
With our business logic defined and tested, we can proceed to coding and integrating effects. The tic-tac-toe example has two effects: the UI and the (faked) AI service. Let's start with the latter.
The above code occurs in provisdom.simple.app
. For the AI, the relevant part is the add-watch
. Since our session state is held in an atom, effects will generally run as watches on session-atom
. To fake the latency of a call to a service, we'll wrap the code in the watch inside a clojure.core.async/go
block, and start with a random delay. We then proceed to query for the relevant requests, call the AI to generate the next move, followed by common/respond-to
with the appropriate request and response data.
Note that the :move-x
watch gets called on every session change. So we query for ::MoveRequest
's for :x
only. If none exist, either because it is the human player's turn or the game is over, then nothing happens. We again have simpler code and less coupling due to the self-describing nature of R³. We don't need to ask specific questions of the state, such as whose turn it is or which squares are marked, but just ask for the requests that might be relevant. Also, it should be clear that while we faked it here, replacing this code with an actual AJAX call would be pretty trivial. In particular, we would want to wire up the call to common/respond-to
in the response callback, and perhaps add some error handling.
The call to view/run
is, of course, wiring up the effect to render the UI. Let's look at the implementation in provisdom.simple.view
.
The markup here as well as the styles were derived from a CodePen example by Lee Sharma. Again, we use rum to define React components. The run
function simply mounts the root app
component into the DOM. app
itself uses the rum/reactive
mixin, and though it isn't explicit, just as with the AI this just adds a watch to session-atom
and triggers the appropriate React lifecycle methods when the session is updated.
The render logic itself is pretty straightforward. As with the AI effect, we make queries of interest for requests and data, and perform some conditional rendering based on such. Notice in particular how click-handlers are conditionally added only when the associated requests exists. And of course the actual handler function is just wrapping the call to common/respond-to
.
There are a couple of key takeaways about effects in R³:
- All effects are treated equally. The view does not hold a privileged spot in the information flow, as it does in most of the examples of Unidirectional User Interface Architectures.
- Effects receive information through queries, conditionally act on that information, and may (through whatever mechanism) provide information through responses.
- General business logic should be encoded in rules, while logic that is specific to an effect implementation should be handled by that implementation. We could, for instance, have written rules to output the CSS style string for a tile, but that's specific to the implementation of rendering HTML, so really belongs with the effect.