Reification of Request/Response (R³)

Andre Staltz' article "Unidirectional User Interface Architectures" covers several variations of modern client-side architectures. All of these share some common characteristics:

  • The user interface (UI) is a source of information for the application logic
  • Rendering the (UI) generally is a "special" effect, distinguished from other effects such as making requests to a web service.
  • The UI is generally taken as a function of the current state.
  • Information flows in a circuit: state yields UI, UI yields events, events lead to state updates, which leads to UI updates, and so on.

The popularity of this particular architecture family probably stems from the corresponding popularity of React. React allows the user interface to updated in a purely functional manner, where markup is a function of state. By comparison, previous approaches generally focused on direct DOM manipulation. The article (from 2015) describes 6 specific examples, which already seems like a lot. New "frameworks" with variations on this theme continue to pop up. The reason behind this constant churn doesn't seem to be discussed much, but clearly stems at least partly from the desire to manage incidental complexity. UI applications already tend to have a fair amount of intrinsic complexity. There are many touch points with the "outside world", and detailed logic to manage those interactions and the internal state. Further, this logic tends to evolve rapidly as bugs are fixed, new features added, etc. So it's hard enough already. If the framework itself adds any incidental complexity, the UI application becomes increasingly unwieldy. Changes touch more and more code, and induce more bugs. At some point the situation becomes intolerable, we hold a big meeting with the team and call for the nuclear option, a complete rewrite of the UI, often including a change to the current sexiest framework.

Sound familiar?

The incidental complexity seems to arise from a few different sources.

  • Managing logical consistency of state. Most languages (even functional ones) do this in an essentially imperative way. Reasoning about correctness of state transformations becomes exponentially more difficult as more logic (in the form of conditional branches) is added. Thus new/changing requirements tend to cause an explosion in code complexity, increasing the difficulty of modifying the code without breaking it.
  • Managing effects. UI code touches the outside world, not only rendering UI allowing for user interaction, but also making requests to external services, receiving push notifications, and the like. Incidental complexity can arise here from multiple sources, not the least of them being that different effects are generally treated very differently. The code implementing effects often contains it's own conditional logic which makes it tightly coupled to the state as well as multiplying complexity.
  • Managing temporal consistency of state, e.g. handling asynchrony. The "outside world" is by definition beyond the control of our program. Users can and will do things which are theoretically illegal, like clicking "Submit" twice in rapid succession. Requests to servers may return data after some other event occurs which renders that request invalid. Manually ensuring that we are always in a "valid" state in the face of asynchronous and potentially "bad" behavior is daunting - and generally ignored, in my experience, leading to the dreaded "Heisenbug".

The Reification of Request/Response, or R³, is an attempt to minimize this incidental complexity. The main features of R³ are:

  • Abstraction and reification of a request for information from the outside world, and the potential response to that request. "Abstraction" is in the sense that there is no notion of what effects are triggered by the existence of a request. The request is simply pure data, and reified by its explicit existence in the state, rather than being implied by some combination of state and logic in an effect handler.
  • The use of forward-chaining logic programming to handle logical consistency. Rather than a "database" containing the state of our application, we have a "rulebase", alternatively called a "session". The session combines data as facts, and logic in the form of rules expressing the logical consistency amongst those facts. The rules sytem includes a Truth Maintenance System (TMS), an engine which ensures that when facts change or are added, other facts which are logically related are also modified to ensure logical consistency (or else results in a really ugly failure).

The first bullet is really the core idea, and the use of forward-chaining rules isn't strictly necessary. But the R³ idea originated from the constraints of using rules with maximum logical rigor, and R³ seems to facilitate the use of forward-chaining rules with the attendant benefits.

results matching ""

    No results matching ""