docs WIP
This commit is contained in:
parent
5cc48d54eb
commit
f63adacfcf
|
@ -1,12 +1,12 @@
|
|||
## An Initial Code Walk Through
|
||||
|
||||
At this point, you are now armed with both:
|
||||
- a high level understanding of the 5 domino process (the repo README)
|
||||
- an understanding of application state (the previous ApplicationState doc)
|
||||
At this point, you are now armed with:
|
||||
- a high level understanding of the 5 domino process (from the repo README)
|
||||
- an understanding of application state (from the previous Tutorial)
|
||||
|
||||
You are about 60% the way to understanding re-frame.
|
||||
You are currently about 60% the way to understanding re-frame.
|
||||
|
||||
In this tutorial, we'll look at some code which will get us to about 80% knowledge.
|
||||
In this tutorial, we look at code which will get us to about 80% knowledge.
|
||||
|
||||
We'll be looking at real example application from
|
||||
this repo. It is only 60 lines of code, but there's
|
||||
|
@ -216,8 +216,8 @@ These handlers are given application state as a parameter, and they
|
|||
perform a query (computation) over it, returning a "materialised view"
|
||||
of that state.
|
||||
|
||||
The data from these queries are later used in the view functions which need to render
|
||||
DOM.
|
||||
The data returned by these `query` functions
|
||||
is later used in the `view` functions which render DOM.
|
||||
|
||||
Now, the two examples below are utterly trivial. They just extract part of the application
|
||||
state and return it. So, virtually no computation. More interesting
|
||||
|
@ -257,9 +257,28 @@ re-frame will ensure the necessary calls are made, at the right time.
|
|||
|
||||
## View Functions (domino 5)
|
||||
|
||||
This is where we render the application's UI using Reagent/React.
|
||||
`view` functions transform application state data
|
||||
into `Hiccup formatted` data. `view`
|
||||
functions collectively render the entire DOM.
|
||||
|
||||
As functions do, these `views` transform data into data. They source
|
||||
`Hiccup` is ClojureScript data structures which represent DOM.
|
||||
Here's a trivial view function:
|
||||
```clj
|
||||
(defn greet
|
||||
[]
|
||||
[:div "Hello viewers"])
|
||||
```
|
||||
|
||||
And if we call it:
|
||||
```clj
|
||||
(greet)
|
||||
;; ==> [:div "Hello viewers"]
|
||||
```
|
||||
|
||||
So view functions render a DOM representation of the application state,
|
||||
obtained by subscriptions.
|
||||
|
||||
transform data into data. They source
|
||||
data from subscriptions (queries across application state), and
|
||||
the data they return is hiccup-formatted, which is a proxy for DOM.
|
||||
|
||||
|
|
|
@ -1,4 +1,58 @@
|
|||
|
||||
## What Problem Does It Solve?
|
||||
|
||||
First, we decided to build our SPA apps with ClojureScript, then we
|
||||
choose [Reagent], then we had a problem.
|
||||
|
||||
For all its considerable brilliance, Reagent (+ React)
|
||||
delivers only the 'V' part of a traditional MVC framework.
|
||||
|
||||
But apps involve much more than V. Where
|
||||
does the control logic go? How is state stored & manipulated? etc.
|
||||
|
||||
We read up on [Pedestal App], [Flux],
|
||||
[Hoplon], [Om], early [Elm], etc and re-frame is the architecture that
|
||||
emerged. Since then, we've kept a close eye on further developments like the
|
||||
Elm Architecture, Om.Next, BEST, Cycle.js, Redux, etc. They have taught us much
|
||||
although we have often made different choices.
|
||||
|
||||
re-frame does have M, V, and C parts but they aren't objects.
|
||||
It is sufficiently different in nature
|
||||
from (traditional, Smalltalk) MVC that calling it MVC would be confusing. I'd
|
||||
love an alternative.
|
||||
|
||||
Perhaps it is a RACES framework - Reactive-Atom Component Event
|
||||
Subscription framework (I love the smell of acronym in the morning).
|
||||
|
||||
Or, if we distill to pure essence, `DDATWD` - Derived Data All The Way Down.
|
||||
|
||||
*TODO:* get acronym down to 3 chars! Get an image of stacked Turtles for `DDATWD`
|
||||
insider's joke, conference T-Shirt.
|
||||
|
||||
|
||||
## Guiding Philosophy
|
||||
|
||||
__First__, above all we believe in the one true [Dan Holmsand], creator of Reagent, and
|
||||
his divine instrument the `ratom`. We genuflect towards Sweden once a day.
|
||||
|
||||
__Second__, we believe in ClojureScript, immutable data and the process of building
|
||||
a system out of pure functions.
|
||||
|
||||
__Third__, we believe in the primacy of data, for the reasons described in
|
||||
the main README. re-frame implements an infinite loop of Derived data.
|
||||
|
||||
__Fourth__, we believe that Reactive Programming is one honking great idea.
|
||||
It is a quite beautiful solution to one half of re-frame's
|
||||
data conveyance needs, but we don't take reactivity as far as, say, cycle.js.
|
||||
It doesn't take over everything in re-frame - it just does part of the job.
|
||||
|
||||
__Finally__, many years ago I programmed briefly in Eiffel where I learned
|
||||
about [command-query separation](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation).
|
||||
Each generation of
|
||||
programmers seems destined to rediscover this principle - CQRS is a recent re-rendering.
|
||||
And yet we still see read/write `cursors` and two way data binding being promoted as a good thing.
|
||||
Just say no. As programs get bigger, their use will encourage control logic into all the
|
||||
wrong places and you'll end up with a tire fire of an Architecture. IMHO.
|
||||
|
||||
## It Does Physics
|
||||
|
||||
|
@ -20,9 +74,77 @@ you've seen the videos - and if he says to do something, you do it
|
|||
this diagram, apart from being a plausible analogy, and bordering on useful,
|
||||
is practically PROOF that re-frame is doing physics.
|
||||
|
||||
## Finite State Machines
|
||||
## It does Event Sourcing
|
||||
|
||||
This perspective can be useful ...
|
||||
How did that exception happen, you wonder, shaking your head?
|
||||
What did the user do immediately prior to the exception? What
|
||||
state was the app in that this event was so disastrous?
|
||||
|
||||
To debug it, you need to know this information:
|
||||
1. the state of the app immediately before the exception
|
||||
2. What final event then caused your app to fall in a screaming mess.
|
||||
|
||||
Well, with re-frame you need to record (have available):
|
||||
1. A recent checkpoint of the app state in `app-db` (perhaps the initial state)
|
||||
2. all the events `dispatch`ed since the last checkpoint, up to the point where the exception occurred.
|
||||
|
||||
Note: that's all just data. **Pure, lovely loggable data.**
|
||||
|
||||
If you have that data, then you can reproduce the exception.
|
||||
|
||||
re-frame allows you to time travel, even in a production setting. Install the "checkpoint" state into `app-db`
|
||||
and then "play forward" through the collection dispatched events.
|
||||
|
||||
The only way the app "moves forwards" is via events. "Replaying events" moves you
|
||||
step by step towards the exception causing problem.
|
||||
|
||||
This is perfect for debugging assuming, of course, you are in a position to capture
|
||||
a checkpoint, and the events since then.
|
||||
|
||||
Here's Martin Fowler's [description of Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html).
|
||||
|
||||
## It does a reduce
|
||||
|
||||
Here's another useful way of thinking about the re-frame
|
||||
data flow.
|
||||
|
||||
**First**, imagine that all the events ever dispatched by a
|
||||
certain running app were stored in a collection.
|
||||
So, if when the app started, the user clicked on button X
|
||||
then the first item in this collection would be the event
|
||||
generated by that button, and then, if next the user moved
|
||||
a slider, the associated event would be the next item in
|
||||
the collection, and so on and so on. We'd end up with a
|
||||
collection of event vectors.
|
||||
|
||||
**Second**, remind yourself that the `combining function`
|
||||
of a `reduce` takes two parameters:
|
||||
|
||||
1. the current state of the reduction and
|
||||
2. the next collection member to fold in.
|
||||
|
||||
Then notice that `reg-event-db` event handlers take two parameters too:
|
||||
|
||||
1. the current state of `app-db`
|
||||
2. the next event to fold in
|
||||
|
||||
Which is the same as a `combining function` in a `reduce`!!
|
||||
|
||||
So now we can introduce the new mental model: at any point in time,
|
||||
the value in `app-db` is the result of performing a `reduce` over
|
||||
the entire `collection` of events dispatched in the app up until
|
||||
that time. The combining function for this reduce is the set of handlers.
|
||||
|
||||
It is almost like `app-db` is the temporary place where this
|
||||
imagined `perpetual reduce` stores its on-going reduction.
|
||||
|
||||
Now, this perspective only goes so far, because `reg-event-fx`
|
||||
event handlers produce effects beyond just application state changes.
|
||||
But it is a very interesting mental model.
|
||||
|
||||
## It does FSM
|
||||
|
||||
And this perspective can be useful ...
|
||||
|
||||
> Any sufficiently complicated GUI contains an ad hoc,
|
||||
> informally-specified, bug-ridden, slow implementation
|
||||
|
@ -49,7 +171,7 @@ So, in this section, I'm suggesting that this perspective is useful sometimes:
|
|||
|
||||
Events - that's the way we roll.
|
||||
|
||||
## Dumb Views - Part 1
|
||||
## Prefer Dumb Views - Part 1
|
||||
|
||||
A lot of events are dispatched by the DOM in response to user actions.
|
||||
|
||||
|
@ -76,7 +198,7 @@ We want that "on-click" as simple as we can make it.
|
|||
**Rule**: `views` are as passive and minimal as possible when it
|
||||
comes to handling events. They `dispatch` pure data and nothing more.
|
||||
|
||||
## Dumb Views - Part 2
|
||||
## Prefer Dumb Views - Part 2
|
||||
|
||||
Neither do we want views computing the data they render.
|
||||
That's the job of a subscription:
|
||||
|
|
|
@ -73,20 +73,21 @@ until we are back at the beginning of the loop. Each iteration has the same casc
|
|||
An `event` acts as the **1st domino**.
|
||||
|
||||
An event might be initiated by a user clicking a button, or entering a field,
|
||||
or it might be initiated by another agent, like "a websocket receiving a packet".
|
||||
or it might be caused by another agent, such as a websocket which just
|
||||
receiving a new message.
|
||||
Without the impulse of a triggering `event`, no 5 domino cascade occurs.
|
||||
So, it is only because of `events` that a re-frame app is propelled, loop iteration after loop iteration,
|
||||
from one state to the next.
|
||||
So, it is only because of `events` that a re-frame app is propelled,
|
||||
loop iteration after loop iteration, from one state to the next.
|
||||
|
||||
re-frame is `event` driven.
|
||||
|
||||
The **2nd domino**, `event handling`, involves computing how the application should
|
||||
respond/change to the new `event` occurrence.
|
||||
The **2nd domino**, `event handling`, involves computing how the
|
||||
application should respond/change to the new `event` occurrence.
|
||||
|
||||
Event handlers produce `effects` or, more accurately,
|
||||
a **description** of `effects`. These descriptions say how the state of
|
||||
an SPA itself should change, and sometimes they also say how the outside world should change
|
||||
(localstore, cookies, databases, emails, etc).
|
||||
an SPA itself should change, and sometimes they also say how the
|
||||
outside world should change (localstore, cookies, databases, emails, etc).
|
||||
|
||||
The **3rd domino** takes these descriptions (of `effects`) and actions them. Makes them real.
|
||||
|
||||
|
|
|
@ -1,59 +1,5 @@
|
|||
|
||||
|
||||
## What Problem Does It Solve?
|
||||
|
||||
First, we decided to build our SPA apps with ClojureScript, then we
|
||||
choose [Reagent], then we had a problem.
|
||||
|
||||
For all its considerable brilliance, Reagent (+ React)
|
||||
delivers only the 'V' part of a traditional MVC framework.
|
||||
|
||||
But apps involve much more than V. Where
|
||||
does the control logic go? How is state stored & manipulated? etc.
|
||||
|
||||
We read up on [Pedestal App], [Flux],
|
||||
[Hoplon], [Om], early [Elm], etc and re-frame is the architecture that
|
||||
emerged. Since then, we've kept a close eye on further developments like the
|
||||
Elm Architecture, Om.Next, BEST, Cycle.js, Redux, etc. They have taught us much
|
||||
although we have often made different choices.
|
||||
|
||||
re-frame does have M, V, and C parts but they aren't objects.
|
||||
It is sufficiently different in nature
|
||||
from (traditional, Smalltalk) MVC that calling it MVC would be confusing. I'd
|
||||
love an alternative.
|
||||
|
||||
Perhaps it is a RACES framework - Reactive-Atom Component Event
|
||||
Subscription framework (I love the smell of acronym in the morning).
|
||||
|
||||
Or, if we distill to pure essence, `DDATWD` - Derived Data All The Way Down.
|
||||
|
||||
*TODO:* get acronym down to 3 chars! Get an image of stacked Turtles for `DDATWD`
|
||||
insider's joke, conference T-Shirt.
|
||||
|
||||
|
||||
## Guiding Philosophy
|
||||
|
||||
__First__, above all we believe in the one true [Dan Holmsand], creator of Reagent, and
|
||||
his divine instrument the `ratom`. We genuflect towards Sweden once a day.
|
||||
|
||||
__Second__, we believe in ClojureScript, immutable data and the process of building
|
||||
a system out of pure functions.
|
||||
|
||||
__Third__, we believe in the primacy of data, for the reasons previously given. re-frame
|
||||
implements an infinite loop of Derived data.
|
||||
|
||||
__Fourth__, we believe that Reactive Programming is one honking great idea.
|
||||
It is a quite beautiful solution to one half of re-frame's
|
||||
data conveyance needs, but we don't take reactivity as far as, say, cycle.js.
|
||||
It doesn't take over everything in re-frame - it just does part of the job.
|
||||
|
||||
__Finally__, many years ago I programmed briefly in Eiffel where I learned
|
||||
about [command-query separation](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation).
|
||||
Each generation of
|
||||
programmers seems destined to rediscover this principle - CQRS is a recent re-rendering.
|
||||
And yet we still see read/write `cursors` and two way data binding being promoted as a good thing.
|
||||
Just say no. As programs get bigger, their use will encourage control logic into all the
|
||||
wrong places and you'll end up with a tire fire of an Architecture. IMHO.
|
||||
|
||||
|
||||
|
||||
|
@ -193,6 +139,8 @@ accessible via the `ratom` it returns.
|
|||
|
||||
Okay, that was all important background information for what is to follow. Back to the diagram ...
|
||||
|
||||
|
||||
|
||||
## Components
|
||||
|
||||
Extending the diagram, we introduce `components`:
|
||||
|
@ -201,7 +149,8 @@ Extending the diagram, we introduce `components`:
|
|||
app-db --> components --> Hiccup
|
||||
```
|
||||
|
||||
When using Reagent, your primary job is to write one or more `components`. This is the view layer.
|
||||
When using Reagent, your primary job is to write one or more `components`.
|
||||
This is the view layer.
|
||||
|
||||
Think about `components` as `pure functions` - data in, Hiccup out. `Hiccup` is
|
||||
ClojureScript data structures which represent DOM. Here's a trivial component:
|
||||
|
@ -604,10 +553,6 @@ Summary:
|
|||
even for large, deep nested data structures.
|
||||
|
||||
|
||||
## The 2nd Flow
|
||||
|
||||
|
||||
|
||||
## Event Flow
|
||||
|
||||
Events are what flow in the opposite direction.
|
||||
|
@ -656,39 +601,6 @@ database. Almost. Stretching it? We do like our in-memory
|
|||
database analogies.
|
||||
|
||||
|
||||
### Dispatching Events
|
||||
|
||||
|
||||
### Event Handlers
|
||||
|
||||
Collectively, event handlers provide the control logic in a re-frame application.
|
||||
|
||||
An event handler is a pure function of two parameters:
|
||||
|
||||
1. current value in `app-db`. Note: that's the map **in** `app-db`, not the atom itself.
|
||||
2. an event (represented as a vector)
|
||||
|
||||
It returns the new value which should be reset! into `app-db`.
|
||||
|
||||
An example handler:
|
||||
```Clojure
|
||||
(defn handle-delete
|
||||
[app-state [_ item-id]] ;; notice how event vector is destructured -- 2nd parameter
|
||||
(dissoc-in app-state [:some :path item-id])) ;; return a modified version of 'app-state'
|
||||
```
|
||||
|
||||
Handling an event invariably involves mutating the value in `app-db`
|
||||
(which is provided as the first parameter).
|
||||
An item is added here, or one is deleted there. So, often simple CRUD, but sometimes much more,
|
||||
and sometimes with async results.
|
||||
|
||||
But the `app-db` mutation is ultimately handled by re-frame (it does the `reset!`). That leaves your event
|
||||
handlers pure. As a result, they tend to be easy to test and understand. Many are almost trivial.
|
||||
|
||||
There's more to event handlers than can be covered here in this introductory tutorial. Read up on
|
||||
issues like Middleware [in the Wiki](https://github.com/Day8/re-frame/wiki#handler-middleware).
|
||||
|
||||
|
||||
|
||||
|
||||
### Control Via FSM
|
||||
|
@ -726,36 +638,6 @@ the data is distributed (and synchronized) across many objects. So implementing
|
|||
both possible and natural in re-frame, whereas it is often difficult and contrived to do so in other
|
||||
kinds of architecture (in my experience).
|
||||
|
||||
### As A Reduce
|
||||
|
||||
So here's another way of thinking about what's happening with this data flow - another useful mental model.
|
||||
|
||||
First, imagine that all the events ever dispatched by a certain running app were stored in a collection.
|
||||
So, if when the app started, the user clicked on button X then the first item in this collection
|
||||
would be the event generated
|
||||
by that button, and then, if next the user moved a slider, the associated event would be the
|
||||
next item in the collection, and so on and so on. We'd end up with a collection of event vectors.
|
||||
|
||||
|
||||
Second, remind yourself that the `combining function` of a `reduce` takes two parameters:
|
||||
|
||||
1. the current state of the reduction and
|
||||
2. the next collection member to fold in.
|
||||
|
||||
Then notice that event handlers take two parameters too:
|
||||
|
||||
1. the current state of `app-db`
|
||||
2. the next item to fold in.
|
||||
|
||||
Which is the same as a `combining function` in a `reduce`!!
|
||||
|
||||
So now we can introduce the new mental model: at any point in time, the value in `app-db` is the result of
|
||||
performing a `reduce` over
|
||||
the entire `collection` of events dispatched in the app up until that time. The combining function
|
||||
for this reduce is the set of handlers.
|
||||
|
||||
It is almost like `app-db` is the temporary place where this imagined `perpetual reduce` stores
|
||||
its on-going reduction.
|
||||
|
||||
### Derived Data, Everywhere, flowing
|
||||
|
||||
|
@ -784,33 +666,6 @@ repo issue with a suggestion.
|
|||
|
||||
Back to the more pragmatic world ...
|
||||
|
||||
### Logging And Debugging
|
||||
|
||||
How did that exception happen, you wonder, shaking your head? What did the user do immediately prior
|
||||
to the exception? What state was the app in that this event was so disastrous?
|
||||
|
||||
To debug it, you need to know this information:
|
||||
1. the state of the app immediately before the exception
|
||||
2. What final event then caused your app to fall in a screaming mess.
|
||||
|
||||
Well, with re-frame you need to record (have available):
|
||||
1. A recent checkpoint of the app state in `app-db` (perhaps the initial state)
|
||||
2. all the events `dispatch`ed since the last checkpoint, up to the point where the exception occurred.
|
||||
|
||||
Note: that's all just data. **Pure, lovely loggable data.**
|
||||
|
||||
If you have that data, then you can reproduce the exception.
|
||||
|
||||
re-frame allows you to time travel. Install the "checkpoint" state into `app-db`
|
||||
and then "play forward" through the collection dispatched events.
|
||||
|
||||
The only way the app "moves forwards" is via events. "Replaying events" moves you
|
||||
step by step towards the exception causing problem.
|
||||
|
||||
This is utterly, utterly perfect for debugging assuming, of course, you are in a position to capture
|
||||
a checkpoint, and the events since then.
|
||||
|
||||
|
||||
|
||||
|
||||
[SPAs]:http://en.wikipedia.org/wiki/Single-page_application
|
Loading…
Reference in New Issue