Award-Winning Fjords Thomas Reynolds

Programming With Facts

Redux is an infinitely deep tree and every piece of code that accesses it needs to know where to find each piece of data.

MobX is a set of inter-dependent silos which prefer to be singletons and require a lot of magic (such as decorators and observables).

I could write several thousand words on the difficulties of building large Redux or MobX apps, but I'm sure there are a couple hundred such posts already online.

Instead, I want to try to envision another possible approach to the problem of client-side state management.

My goals are to:

My solution, is to borrow from Datomic's schemaless fact-based database. If you know anything about logic programming, this is going to look a bit familiar.

What is a Fact?

What if we had a completely flat database? Any piece of code could relate any data to any specific object in the system. If module A needs users to have email addresses and module B needs users to have physical address, a schema-less approach will allow them to work without ever having to step on each others toes.

Simply put, a fact is a record (or database row) with a unique id (think guid or whatever their primary key would be), the name of the fact (such as email) and the value of that fact (demo@example.com).

[ uniqueId, variableName, value ]

If I were going to model myself, it might look something like this:

[ 1, "name",  "Thomas"                         ]
[ 1, "email", "me@tdreyno.com"                 ]
[ 1, "url",   "https://awardwinningfjords.com" ]

That's just about it. Imagine your entire Redux store as a long list of facts known about the application state.

Because the format is flat and simple, it would be very easy to build tooling to inspect, debug and add time traveling abilities.

All values must be primitives, meaning serialization is trival. Simply JSON.stringify to localstorage and "offline" mode is pretty much done. Want to upload offline changes to the server? Count the number of rows on the server, count the number on the client. Fill the server in on the X number of new changes.

Querying

Okay, okay. So constantly searching over a massive list of rows for the data you're looking for isn't going to be very fast. It should be surprising that a solution that mimicks a database will need a query language.

Querying looks almost exactly like the data format, but with variables linking rows together.

For example, if I wanted to query for all the data on a single user, I can use the wildcard _ character:

[1, _, _]

Which will get me all rows with 1 in the id position.

If I wanted all emails in the system:

[_, "email", _]

In addition to wildcards, you can also write functions to narrow down queries. If I wanted all the urls on SSL, I could ask:

[_, "url", contains("https://")]

Finally, variables can be used to relate multiple queries. If I wanted the email of people named Thomas:

["?id", "name",  "Thomas"]
["?id", "email", "?email"]

Strings starting with ? are variables. If the variable appears in more than 1 row, they will create subqueries. Because the first row only matches the id 1, the second row will ask for the email of row 1. The result will be an object with all matched variables:

{ id: 1, email: "me@tdreyno.com" }

These queries are automatically cached/computed using a Rete network. They will be automatically recalculated reactively when values which are not wildcards are changed (new users named "Thomas" and new emails).

Rules

The final piece of the puzzle is adding derived state. With Redux these live in selectors. In MobX they are computed. In this system, they are just more rows in the database.

Say I wanted to provide a fact about whether the user uses SSL. I can write a Rule which, when matched, will add a fact. When the facts triggering the rule change, the derived fact can be cleaned up. Other pieces of code don't need to know the difference between "real" state and derived state.

Here's an example:

rule(

    ["?id", "url", contains("https://")]

).then(match =>

    [match.id, "usesSSL", true]

)

Now, for every user id which has a url containing https://, a coresponding usesSSL fact will be created for that id.

If the value of url changes, or if the user is removed, the derived fact will be cleaned up.

Demo

The now requisite TodoMVC demo can be found at:

The React integration layer provides the subscribe HOC which turns a query into React props. For example:

subscribe(
    ["global", "ui/filter", "?todoFilter"],
    ["global", "doneCount", "?completedCount"],
    ["global", "activeCount", "?activeTodoCount"],
    collect("?completed", [_, "todo/completed", true]),
)

Which creates props for three pieces of UI state (todoFilter, completedCount and activeTodoCount). It also uses collect to turn a subquery into an array. In this example, an array of all todos which have completed set to true.

Next Steps

My plans are to find a mid-sized side project, or convert an existing Redux or MobX project, to get a better feeling for how the approach works on non-toy projects.

I'd love feedback! Thanks for reading.

Further reading