Sodium is an implementation of Functional Reactive programming (FRP) with some nice features. One of these is the support of transactions in the GUI layer. I had quite some discussions with my colleagues on what this actually means and if such a transaction concept is useful or not. In this article I sum up my current insights and opinions about transactions in Sodium.
Almost every GUI layer uses a model-view-controller architecture. These come in many flavors, such as HMVC, MVP, MVVM, MVA, and many more. But in general, all work more or less like this:
The state of the GUI is stored in a model. A view binds the state to some actual widgets. An interaction from a user fires an event, which is processed by some code. The input of the code is the old model. After the execution there is a new model which is presented by a new view.
This works well, but it is the simple case. In more complex applications, it usually happens that the processing of an event creates new events:
These new events can be created explicitly by using an event bus. This is, for example, the standard approach in Choo for interaction of components. In Angular, events are used for communications between child and parent components. New events can also be created implicitly by changing model data with registered change listeners. This is the standard approach in React.
These new events needs to be processed as part of the processing of the original event:
The order of the processing depends on the event mechanism. In general the order is not defined. This can be a problem depending on the model data processed by the event handlers. If each event handler has write access to the model data, you end up with four different versions of the model:
If two or more event handlers operate on the same data, you can get all the problems of concurrent programming. This means, the behavior of your application can sometimes be faulty.
On the opposite side, this means, that if you take care, that each event handler operates only on its private data and receives input information only from its event, then the sequence of processing the event handlers is not important. In this case, there are no concurrency issues. This is the recommended approach for using React.
Depending on your GUI framework each model change may lead to a change in the view layer. Thus, in our example you can end up with four different versions of the view:
For the user, the display of intermediate results may appear as flickering. If the model becomes faulty, the view will be faulty too, of course.
In Sodium these problems do not exist firstly because of its architectural style, the functional reactive programming style, and secondly because of its GUI transactions. A transaction is started automatically when an event is generated from the outside, the non-FRP-world.
As in the example above, while event e is processed two new events are sparked and processed. But, the thing is, that all event handlers in one transaction only see the same model data. Thus, the order of execution is not important. No event handler can notice any activity from any other handler.
When all event handlers are finished, the transaction commits, the data changes are applied to the model, and the view is updated:
This architecture removes lots of potential sources for faults as results of concurrency. But if you look at the diagram you can see three arrows entering the “Model-New” data vertex. These are three write accesses and with classical event handlers the order of the writes matters in the same way as in the classical approach introduced before.
Sodium solves this by not using event handlers with imperative write commands. Instead events are processed as streams. The processing of a stream creates a new stream. Streams can be merged. In one transactions at most one event is processed in a stream. The framework guarantees that concurrent events are merged deterministically. This picture shows the processing as a diagram.
Model data is stored in Cell-objects. Using “snapshot()” the value of a cell can be accessed in the processing of the streams. Only one stream can update the Cell object using “hold()”. This is guaranteed via the API, because the cell is defined via hold(). Therefore only one stream can update the model and there are no concurrency issues when using the FRP functions of Sodium.
The nice thing is that the developer does not need to understand all of this. She just implements the GUI logic using functional streams. The Sodium framework automatically takes care of the GUI transactions and the handling of the model data.
If you are more interested in Sodium, here are two other articles:
The best source of information is of course the book: Functional Reactive Programming