Architecture Pattern: Separating Model and ViewState

1,074 views
Skip to first unread message

Max Goldstein

unread,
Jan 8, 2016, 11:13:00 AM1/8/16
to Elm Discuss
There's been a lot of threads on the list lately about how to architect large applications. They basically boil down to two questions.
  • How can we separate business logic and domain data from view-specific layout and animation?
  • How can we write components that can be easily nested?
I have an idea, but first, let's review the standard Elm Architecture (as I understand it):


A few notes on the diagram: Read clockwise. Monospaced font indicates things in Elm, with capitalization indicating type. Inputs are on the left, and outputs are on the right.

One of the "smells" is that HTTP responses and clock ticks are both Actions that get handled together. The Model also must keep all information needed to render the view, so if you want to serialize the model, that's non-trivial.

Let me use the specific example of a credit card order form. When the user clicks "submit", an HTTP POST request is sent, and the submit button is disabled (perhaps with a click animation). These are separate: one is a business-level action, and the other is a UI event.

As another example, consider a drag-n-drop list of items. At any given moment we might be animating an item snapping into place, or moving it between lists. During this time, if you were asked to serialize the model and send it to the server, what would you do? There's no good answer. Therefore the state of where something is painted onscreen is separate from the model of the data itself.

With these examples in mind, please consider this pattern:


This is, essentially, the folding pattern applied twice. There are two large loops: all the way around, and through Event (skipping Action and Model). The Model contains the business data, and the ViewState contains the animations, button toggled states, field data, and so on. And Event (a union type) can be a new Model, a clock tick, or something that came from HTML.

HTML events don't map cleanly on to whether a business-logic Action or a view-level Event has occurred. Going back to our credit card order form, each digit entry can be an Event, but a completed card number can be an Action that gets saved in the model. Something is needed to determine whether a button click extends a panel or sends data to the server, or both, and I've called that something handleEvent. It's responsible for turning the low-level HTML event API into something meaningful in the application. (Alternatively, the HTML events can directly fire Actions and Events.) This sort of layer between input and application commands allows one to say, have remappable keyboard shortcuts, or have a button click and a keypress do exactly the same thing, since they send the same Event.

This is still just a diagram and I haven't written any code, so I expect this to be refined through implementation and discussion. For instance, I'm not completely sure how the model should get passed around: as NewModel Model event, or perhaps render should be render : Model -> Event -> ViewState -> ViewState, or perhaps the model should go straight to the view. I don't think this could be codified quite like StartApp, at least not yet, it's more of a pattern than a library.

I opened with two concerns, and I think this pattern solves the first one reasonably well. But I think it can also help the second one, composition. By knowing that each component has business logic in one place, and view state separately, things get easier to compose. First, the Models will probably need to be coupled from parent to child, based on what the application is, but the ViewState can be independent. When a parent receives a clock tick event, it passes it on to the child, always. (Ideally it wouldn't need to do anything, but we can work on that.) And effects can turn from "please run this opaque task" to something semantic, like "please submit this credit card data", which the parent might need to inspect.

I realize this is a bit long for a post but I'm eager to hear what people have to say about this pattern.







Peter Damoc

unread,
Jan 8, 2016, 12:12:23 PM1/8/16
to Elm Discuss
Regarding your two questions:

1. I think current Elm Architecture allows for this separation quite easily. If you take a look at Example 8, the SpiningSquarePair has no view specific data, everything is generically delegated. 

2. Now this is the main issue. As I stated in the thread I've started few days ago, I don't think there is an easy way to create components hierarchies. I would love to be proven wrong. 

You schema looks intriguing but it is not showing a hierarchy of components. 
Also, I don't view the value of splitting Actions and Events even if I understand the reasoning behind it. 

To me, this goes back to the hierarchy. In a hierarchy, what is essential state at one level might be accidental state at the level above. 
To use TEA example 8, the SpiningSquare's angle and animationState are irrelevant to the SpiningSquarePair component. It doesn't care about it, and never uses it. 
Now, the behaviour of the SpiningSquare needs those pieces of state, and this is why we have to declare the state, make sure it is updated in SpiningSquarePair's update and compose it in the SpiningSquarePair's view. 

Ideally, SpiningSquarePair should have been just a few lines that describe a component composed from a layout of two other components. 
This is the only essential information in the SpiningSquarePair. The rest is noise.

This is why I keep hoping for a better way. 

In the mean time I started exploring a sacrilegious approach that bypasses the typechecker. :)




--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Max Goldstein

unread,
Jan 8, 2016, 3:31:57 PM1/8/16
to Elm Discuss
Hi Peter,

Thanks for the constructive criticism.

This pattern is more aimed at the first problem, separating view state from business state, than the second component hierarchies. I'm hoping that once I/we really figure out the first one, that will give us insight into the second, but so far that's just a hunch.

I'm sorry you don't "value of splitting Actions and Events". This thread is partially trying to solve this separation, by keeping "how far has the panel moved to show the new page" separate from "what page are we on?".

I think SpinSquare is a bad example for this new pattern. That's a component that only animates; it doesn't use animation to transition between states. A better example would be spinning a triangle 90˚ and needing to know the orientation a component up the ladder.

I can try to make an example program that uses the pattern.

Srikumar Subramanian

unread,
Jan 8, 2016, 7:57:27 PM1/8/16
to Elm Discuss
As another example, consider a drag-n-drop list of items. ...

Not sure if you've seen this example -  Demo + Code. Not that it is perfect, but at least working code for the purpose of discussion. I'm not sure that the kind of "easy nesting" I'd prefer - nesting models within models, viewstates within viewstates and such - neatly enough without HKTs. 

Peter Damoc

unread,
Jan 9, 2016, 4:58:27 AM1/9/16
to Elm Discuss
Hi Max, 

The kind of separation that you make between Actions and Events can be abstracted into the hierarchy through a pattern where some of the component actions can become parent actions. 
I guess you would call these Actions, while calling the rest of the component's actions just Events. 
Of course, in order for this to make sense, one needs a certain amount of nesting. 

e.g. 
App
  Page
    Section
      Part
        Low level item

App would be concerned with locations, making sure the user is in the right place
Page would be concerned with layout events
Section would be concerned with building some information for the user, let's say this is a blog and the section deals with constructing the home page from the database of posts. 
Part would be something like a single entry display with some preview and "see more" kind of controls. 
Low level item would be the workhorse... these are the buttons, the links, the textfield. Concerned with Mouse positions, click, 

Now, admittedly, this is contrived and intentionally complicated. What I wanted to point out is that at each level there can be a small set of actions that are essential to that level while most of the actions of the "children" are accidental or "Events" as you call them. 

It's not just 2 levels. 

What I would split in two is system actions vs app actions. 
The system actions are pretty much standard and I would love to have them documented in one place. 
Paper.js does this brilliantly.
http://paperjs.org/reference/global/

:) 







--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Hamburg

unread,
Mar 8, 2016, 3:18:56 PM3/8/16
to Elm Discuss
This is a pattern I found myself wandering toward recently as well and I've seen others wander toward it. The StartApp model could be seen as a way to take some inputs and produce a signal of some type. StartApp just happens to produce HTML-valued signals. So, in your diagram, model construct would feed the view state construct and both of them could have their own update functions working over their own actions.

But then I think to myself "I'm new here and I really should go look at the dominant patterns". It looks like one could probably build a model type and associated update and render function that essentially plumbed in the connection between the business logic model and the view state. At that point, one would be back to the StartApp architecture.

Of course, that's all just handwaving right now.

Mark

Daniel Bachler

unread,
Mar 10, 2016, 6:22:52 AM3/10/16
to Elm Discuss
Yes I think this pattern is worth investigating further. It fits very well onto the Undo/Redo problem that was discussed a few days ago in this thread. Coming form a MVC OO world, I think of the upper half of your diagram as the "Document Model" and the lower part as the "View Model". Operations on the former should be undoable in general, operations on the latter should not.

Gary Bilkus

unread,
Mar 11, 2016, 3:51:46 PM3/11/16
to Elm Discuss
I've come to this thread while thinking about how to build a dynamic tabbed display, where each tab potentially renders a completely kind of view on an underying model.

The implementation I've thought of so far composes multiple StartApp modules at the cost of creating a dirty great big union of all the underlying Model and Config types, something similar for all the Actions, and a large number of repeated boilerplate case statements.

It works, and it encapsulates the internal details of each module, but boy is it ugly! A little taste:

Type SubPage =
   SubPage1 Page1.Page1Config Page1.Model
   | SubPage2 Page2.Page2Config Page2.Model


type Action
    = P1 Int Page1.Action
    | P2 Int Page2.Action
    | Select Int
    | Add1
    | Add2
    
type alias Model =
    { whichPage : Int
    ,  pages : Array SubPage
    }

update : Action -> Model -> (Model, Effects Action)
update action mainmodel =
  case action of
    P1 n act ->
     case Array.get n mainmodel.pages of
       Just (SubPage1 config model) ->
         let (p, fx) = config.update act model
         in 
          ({mainmodel|pages = (Array.set n (SubPage1 config p) mainmodel.pages)} , (Effects.map (P1 n) fx))
       _ -> (mainmodel, Effects.none)
    
    P2 n act ->
     case Array.get n mainmodel.pages of
       Just (SubPage2 config model) ->
         let (p, fx) = config.update act model
         in 
          ({mainmodel|pages = (Array.set n (SubPage2 config p) mainmodel.pages)} , (Effects.map (P2 n) fx))
       _ -> (mainmodel, Effects.none)

  What I want to be able to do is achieve the same result but in a way which avoids all the ugly boilerplate. And that's part of what this thread is discussing. The difficulty I'm having seems to be caused primarily by the fact that Elm doesn't have an equivalent of interfaces except in the special case of allowing structures to have more fields than they need...

Would the ideas above help with this?

Simone Vittori

unread,
Apr 5, 2016, 8:06:47 AM4/5/16
to Elm Discuss
I've been tinkering around with this pattern for a while now and I can say it worked pretty well for me. I haven't built anything exceptionally complicated here, but I managed to make a small demo of the credit card form you were talking about.

Here's a live demo + code. The pattern is pretty much like you described it, except it doesn't have a handleEvent function (I think it's simpler to directly fire Actions and Events, at least up to a certain extent); also Model and ViewState go straight to the view.

One thing that's immediately noticeable is that I haven't used StartApp, because I didn't really feel the need for it, but I don't see why not. However I'd be more inclined to go towards a modified version of StartApp that already has the concept of Actions + Events.

John Orford

unread,
Apr 5, 2016, 1:10:05 PM4/5/16
to Elm Discuss
+1 for the topic guys

this question was my first cause of cognitive dissonance I had since doing Elm arch apps up...

dare I say it, but out of curiosity, how do react / redux ppl handle this?

jphedley

unread,
Feb 3, 2017, 12:46:04 PM2/3/17
to Elm Discuss
In React Redux the View Tree knows no details of the separate Singleton App State Tree Object & its Business Logic (aka Store). The View dispatches an Action (JS Obj {type:SOME_ACTION_TYPE, payload:{...} to the Store, which Functionally Updates & notifies any views interested in these State Model changes. Sorry if this murky, for which UNIDIRECTIONAL USER INTERFACE ARCHITECTURES by Andre Staltz explains this better & compares other approaches. If you interested in learning basic principles of React Redux, Dan Abramov's free Eggehead course is excellent Getting Started with Redux

jphedley

unread,
Feb 3, 2017, 12:54:59 PM2/3/17
to Elm Discuss
Max,
       I'm very interested in this also & have been exploring some ideas too. Is this still a subject of interest to you? 

I'm writing of version of ToDo App, for which View State & Biz State are separated in my Model, for which the update function handles View State Changes, but delegates Business "Actions" to separate Biz State Function.

I may be mad, but this is what I'm exploring right now.

Cheers,
JP

Max Goldstein

unread,
Feb 3, 2017, 8:47:25 PM2/3/17
to Elm Discuss
No, it's not really an interest of mine anymore. In the year since this post I've refocused onto elm-test and its dependencies. I never got much evidence on whether this is a worthwhile pattern or not.

jphedley

unread,
Feb 4, 2017, 12:46:41 PM2/4/17
to Elm Discuss
Fair enough & I'm not sure either. But for me, it's an interesting thought experiment for which I seem to converging towards pattern reminiscent Cycle.JS.

Thanks for elm-test.
Reply all
Reply to author
Forward
0 new messages