Crazy Idea #78: Full-Stack Elm Architecture

I've recently dug into using Redux with React apps, and it's got tons in common with Elm – both have an 'immutable state tree' and represent changes to that tree using Actions. Elm has some awesome benefits like being strongly typed, and having language constraints to enforce things like pure functions. That said, among other things, Redux has the benefit of being used by more than 8 people.

I read a post by David Gilbertson explaining his idea of an "Obvious API" inspired by Redux actions. While some of the criticisms in the comments are spot on (how on earth are you meant to get any interesting information from an HTTP log full of only POST /api 200?), I still thought it was a neat first stab at a pattern.

However... the separated nature of it still bugged me. If you're going to use (basically) the Elm Architecture, you might as well get the benefits like time-travel debugging. So the frontend and the backend need to share an immutable state tree. Which really isn't as scary as it sounds... a JavaScript Object can model a state tree, and we'll come to the immutability in a bit!

So how should we share that state tree? Let's think about an idealised version of how a task-list app might work with a registered user.

  1. The user types a task into a text box and clicks "submit".
  2. The browser sends a "create task" action to the server, with the value just entered.
  3. The server uses an update function (or a 'reducer', for Redux geeks) to update its internal state.
  4. The server sends the new updated state to the client.
  5. The client updates to show the new task.

One thing that immediately pops out is the need for the server to maintain a state tree for each registered user. To do this we can just pop the state tree in a server-side session for each user, and maybe persist it to a database of some kind when we need to.

For debugging, the server could store a list of actions it's received, and a corresponding list of its previous states. It could expose an API for inspecting/changing between states too, for the use of a nice Redux-style time travelling debug tool!

The state the server stores is the authoritative data of the app, so we could use something document-storage-ish like Mongo to store all the list of previous states per-user. It's not necessary for the model to work, but it would have a massive benefit of allowing what I'm thinking of as "Full-Stack Undo": to undo any change, you can just tell the server to roll back its state!

Instead of using the typical HTTP request/response cycle, it'd be really neat to use websockets. That way, we'd get another big benefit – the server can send new state to the frontend whenever it gets updated externally! That means it'd be super easy to add features like collaborative editing – it's just two people editing the same state, and being fed new state whenever it changes.

The huge stonking problem with this idea as I see it is that it's massively space-inefficient. Maybe we could use reversible 'patches' instead of storing the entire state for every action. Or maybe just set a hard limit on the number of previous states we can store at once, popping the oldest entries off the end as the list fills up.

Also, if multiple people are working on the same state at once, it doesn't really account for race conditions. The simple way of doing it: the server queues all of its actions, and any action that causes a problem can just be ignored. Maybe an "error" flag could be set in the user's state, so they get notified. But a much smarter way of doing it is to resolve those conflicts automatically where possible, and this model doesn't really account for that.

(That said, neither does the traditional model of multiple people editing a shared-memory database - but I digress.)

Here's one idea: instead of having multiple states per user, the server really does have just ONE state tree. Which is... massive. It includes all the data, all the users, all their permissions to edit task-lists and everything else. BUT, and this is important – the server also doesn't send the whole state tree to each client. It sends a subtree which is relevant at that point.

So, if I'm editing "Love Letter to Five Guys" and Dave from Accounting is editing "8 Ways to Cook Quinoa With Low-Calorie Sadness", I don't get any of his health-food status updates. Also, when I'm debugging the app, I should be able to narrow my debugger down to only look at a particular subsection of the state, and only see actions which directly affected that subsection. If I change between states in the debugger, it should only affect that bit of the state on the server, too!

It'd be really nice to have Elm's strong static functional typing for this, as I really love pattern-matching on Messages (action types). But I might have to stick with JS objects as something a bit more easily serialised.

Also, it'd be really neat for the client to subscribe to parts of the state tree using a JSON path. Maybe there could be some meta-information in the state tree around which parts of the tree are visible to which users, and business logic in the reducers to control what is modifiable.

The real litmus test will be to build an app using these ideas! Then hopefully I can extract out the mechanisms which can be reused for future projects, and make something resembling a framework. So... watch this space.