BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles The SAM Pattern: Lessons Learned Building Functional Reactive Front-End Architectures

The SAM Pattern: Lessons Learned Building Functional Reactive Front-End Architectures

Reactive programming techniques are becoming more prevalent in the constantly changing JavaScript landscape. This article series hopes to provide a snapshot of where we're at, sharing multiple techniques; variations on a theme. From new languages like Elm to the way Angular 2 has adopted RxJS, there's something for every developer, no matter what they're working with.

This InfoQ article is part of the series "Reactive JavaScript". You can subscribe to receive notifications via RSS.

 

Key takeaways

  • Front-End architectures are evolving rapidly towards a functional reactive model
  • Functional HTML, unidirectional data flow or single state tree are key elements of that model
  • The role of RxJS and immutability is overrated 
  • The SAM pattern is different because it focuses on implementing "application state mutation" correctly
  • SAM can be implemented with your favorite Web Framework

Modern User Experience requires an architecture that is able to continuously “react” not just to user inputs, but also to its broader environment (Friends and Players, Physical Sensors, the Uber driver approaching your location…). GUIs have evolved to become one node in a wide, dynamic, distributed system and as such they’re subject to its intricacies — from concurrency to component failures.

In that context, Front-End Architectures are currently undergoing a major transformation towards a functional reactive model. Several frameworks, libraries, and even languages have emerged and aggressively compete to lead that transformation:

In February, Jean-Jacques published an article describing a new functional, reactive pattern inspired by React.js and TLA+: the SAM pattern.

SAM recommends factoring the business logic underlying a graphical user interface along three concepts: actions, model and state. Actions propose values to the model, which is solely in charge of accepting them. Once accepted, the state certifies that all subscribers are notified, especially the view (which is considered the “state representation”). Every event is processed as a “step”, which consists of a propose/accept/learn flow. This concept provides a stronger foundation to deal with event ordering and effects (such as back-end API calls).

SAM is framework agnostic and several members of the community that formed around the pattern [1] went on to build a series of developer tools and code samples using different Frameworks, ranging from Vanilla JavaScript to AWS Lambda and pretty much anything in between.

In this article, we’ll report some of the lessons learned when implementing the SAM pattern. The goal of this article is not to compare and to rank the merits of frameworks or libraries; that type of comparison has been done many times before (for instance, Matt Raible’s Comparing Hot JavaScript Frameworks).

We rather focus on some practical aspects of front-end architectures that have a direct incidence on the delivery and the maintainability of modern end-user applications:

  • Programming model (Component-based view, Consistency, Side-Effects…)
  • Wiring (RxJS)
  • Architecture (Universal JavaScript)

What Problem are we all Trying to Solve?

Front-end programming models are based on events and callbacks, which have been traditionally wired following the Observer pattern.

For instance, when we want to draw a rectangle on a mouse drag gesture, the handlers associated to the mouse events would look like this:

function onMouseDown(event) {
  rectangle = { from: event.position, to: event.position }
  isMouseDown = true
}

function onMouseMove(event) {
  if (isMouseDown) {
    rectangle.to = event.position;
    draw(rectangle);
  }
}

function onMouseUp(event) {
  isMouseDown = false
}

However, state management has remained a core issue:

Callback-driven code can be obtuse because event handlers only communicate with the rest of the system using state [and generally] suffer from concurrency errors, including dropped events, data races, and starvation.

...which has some tangible impact on code quality. Sean Parent reported in 2007 that, at Adobe:

  • 1/3 of the code in Adobe’s desktop applications is devoted to event handling logic
  • 1/2 of the bugs reported during a product cycle exist in this code

Ingo Maier and Martin Odersky from the EPFL provided a more detailed list of issues:

  • Side Effects
  • Encapsulation
  • Composability
  • Resource Management
  • Separation of Concern
  • Data Consistency
  • Uniformity
  • Abstraction
  • Semantic Distance

They conclude:

Many examples in user interface programming are [...] hard to implement with observers, such as selecting a set of items, stepping through a series of dialogs, editing and marking text – essentially every operation where the user goes through a number of steps.

How does Functional Reactive Programming approach the problem?

The general direction is to apply a pure functional reactive approach to UI construction where:


However, the elephant in the room is: How to weave side-effects (e.g. queries and updates) in a Functional Reactive programming model? Frameworks like Cycle.js, Elm and to a certain degree Redux as well, focus on isolating the effects from the business logic. Richard Feldman explains how effects can be expressed as data and the benefits this approach from a functional testing perspective. SAM does not mandate, but supports such a separation. That being said, the value of unit testing is far from proven and one should weigh the pros and cons of unit testing considering its overhead.The functional relationship between the View and the Model (a.k.a Functional HTML) is so powerful that at this point, it is certain that the days of the MVC pattern are counted. It is also a given that templates and data binding have become the new Flash, and industry leading frameworks will quickly adopt this new paradigm, which naturally decomposes the view in stateless components.

Everyone should be aware that Functional Reactive Frameworks are all still works in progress and subject to significant refactoring as they struggle to find an effective factoring of the business logic. Just considering the Redux community alone, the number of libraries that address specific questions is already exploding: redux-sagas, redux-gen, redux-loop, redux-effects, redux-side-effects, redux-thunks, rx-redux, redux-rx... and that’s not even counting React itself, GraphQL or Relay.

What have we learned with SAM?

  1. SAM can be implemented with your favorite Framework

SAM is a pattern which can be readily implemented with the majority of popular front-end frameworks, such as Angular and React.

David Fall has contributed a superb dual-client React/Redux Tic-Tac-Toe implementation; Bruno Darrigues, who works at Orange, is using SAM with Angular 1.5 and has contributed a TypeScript implementation; Fred Daoud provided a Cycle.js implementation; Troy Ingram delivered a Knockout.js implementation and Michael Solovyov a Vue.js one.

Moreover, you can implement the SAM pattern on top of Vanilla JavaScript. Naturally, special attention has to be given to cross-site scripting (XSS). Yet, even frameworks like React — which escape all values by default — can be vulnerable.

  1. Semantics matter

SAM makes a very distinct separation between Intent and actual Mutation. Actions present values to the Model, but should by no means control if and how the Model mutates. That would require the action to have the entire knowledge of the system.

For example, a user clicks on a button. The action of clicking just indicates the user’s intent to do something. It is the Model’s concern to know whether that action is allowed and what should happen.

Hence, SAM is aligned with Key Design Principles of Software Architecture:

  • Separation of concerns: Divide your application into distinct features with as little overlap in functionality as possible. The important factor is minimization of interaction points to achieve high cohesion and low coupling.
  • Single Responsibility: Each component or module should be responsible for only a specific feature or functionality, or aggregation of cohesive functionality.
  • Least Knowledge: A component or object should not know about internal details of other components or objects.
  • Don’t repeat yourself (DRY): You should only need to specify intent in one place. For example, in terms of application design, specific functionality should be implemented in only one component; the functionality should not be duplicated in any other component.

Naturally, one can write an webapp without any of these. Still, SAM is a very promising alternative if you’re looking for maintainability, extensibility and reusability.

  1. Time-traveling 2.0

Redux got famous for it’s time traveling and live code editing abilities. It turns out that SAM is flexible enough to allow for that as well — and to build on top of it.

As we revert the Model back in time, the State reacts. If we’re using a VirtualDOM library (e.g. React) the View will react to the new State as well. The additional layer comes from the nap() function. By entering a new State, the nap() function is triggered and may dispatch its own actions.

Thus, we’re able to test Model, State and nap() with time-travel. There’s a live proof-of-concept built with SAM DevTools. You’ll notice that can go back in time but you can’t remain in the counter == 10 state, because nap() will immediately trigger the launch (hasLaunched = true).

Live code editing can be achieved with webpack. And if you’re using React, Dan Abramov’s react-hot-loader works fine with SAM as well. There is also a server side version which is part of SAM’s SAFE middleware.

  1. The View can be fully Decoupled from the Model

This is probably the most important outcome when using SAM. SAM is unique in the sense that unlike MVx patterns, the View is strictly isolated from the Model via both the Actions and the State function.

                                                                     V = S(M)
                                                      In SAM, State is a pure function.

 

Thomas J. Buhr explains:

A good Front-End Architecture should enable you to pin your modularized functions to the UI components in the most decoupled way possible. That way the technology backing the components can be swapped out and all your business logic is not in a hostage situation (at the mercy of the next great late framework)

SAM’s Model — which is traditionally referred to as “application state” or just “State” in Flux/Redux — is composed of a set of property values. The State function is responsible for building the State Representation from the Model property values. The control state of the application is generally derived from the property values, there is no need to encumber the model with all kinds of view specific properties.

David Fall explained it when implementing the two-player session for his Tic-Tac-Toe example:

It is possible to remove the reliance on a ‘'showJoinSessionForm'’ Model Property and to instead rely on deriving the visibility in a Container Component that wraps my Form Component. For instance, I could have a State called JoinSession that is true if gameType === 'Join Game' and session === undefined. Those two conditions that make up that State should be enough to assert that the Form Component should be displayed. Once the Model accepts a valid 'session' key, the JoinSession State is no longer true thus the Form Component would not be rendered.

In addition, the view can be decomposed into stateless components which know nothing about where they are being rendered and how their events are wired back the application’s actions.

SAM supports (but does not mandate) the use of a Virtual-dom library. This concept was originally made popular by React and several libraries have been built since, e.g. virtual-dom, mithril and snabbdom. Jose Pedro Dias 1 has contributed a SAM implementation using Sabbdom.

  1. Universal JavaScript can be Achieved without Effort

SAM’s implementations are naturally universal. Any element of the pattern can be deployed on the client or the server or migrated as needed:

Effects can be invoked from both the Actions and the ModelThe “Blog” sample illustrates how the same code (actions, model, state) can be deployed in the client, or in Node.js, even in AWS Lambda, in a serverless architecture.

The factoring of Side-Effects is possibly the most intense research topic in frameworks like Elm, React/Redux and Cycle.js (which are both influenced by Elm’s approach). Greg Weber, the author of redux-side-effect explains:

Keep in mind that redux is inspired by Elm. In the latest version of Elm, the reducer returns both the new state and Effects. This library [redux-side-effect] attempts to mimic how Effects are handled in Elm within the constraints of Javascript and Redux.

The redux-effects library include drivers for the following types of effects:

  • setTimeout/setInterval/requestAnimationFrame
  • HTTP Requests
  • Cookie get/set
  • Location (window.location) binding and setting
  • Generate random numbers
  • Dispatch actions in response to window/document events (e.g. scroll/resize/popstate/etc)
  • localStorage effects driver
  • Automatically decorate your fetch requests with credentials stored in state if the url matches a certain pattern.

On the other hand, SAM does not mandate such a clear separation. SAM rather focuses on achieving a reliable mutation of the application state. SAM’s semantics (inherited from TLA+) are aligned with the paxos protocol:

Actions propose values to the Model which accepts them (or not) and the State makes that the system learns about these changes.

With SAM, state mutations are exclusively controlled by the model and are invisible to the Actions and State. This means that Actions, which are responsible for enriching and validating user intents are allowed to invoke HTTP requests, and present the results to the model only when the HTTP requests return. Similarly, there is no particular reason to invoke an update to the persistence layer outside the model (e.g. in a dedicated Elm task) since the application state generally depends on the success or failure of the update and the intermediate state representation (“updating”) may not be relevant to the user, who only cares about the outcome.

There is of course an argument to be made about the testability of this kind of code, but for instance, API virtualization tools such as MounteBank can be used (rather than building stubs) to create a controlled testable environment.

The Redux community is so far, overwhelmingly and singlehandedly, recommending using (stateful) “Sagas” to handle side-effects. That choice is somewhat surprising considering that Sagas break the first principle of Redux and modern Functional Reactive Front-End architectures, i.e. being based on a single state tree. SAM’s “next-action-predicate” (the nap() function) provides a similar capability, albeit in a functional (i.e. stateless) way. In other words the nap() function relies on the current application state (the Model property values) to decide if it needs to trigger an automatic action. It does not maintain a state of its own like sagas. The next-action can, of course, be a long running action, with side-effects, that will ultimately present its data to the model.

  1. The “Step” concept is unique to SAM

Dr. Lamport explains that:

Programming languages provide no well-defined notion of a program step

Because of its TLA+ foundation, SAM supports the concept of a “Step” which encapsulates state mutations. The flow of a SAM step always includes three phases: propose (actions), accept (model) and learn (state/view). By contrast in Elm tasks/commands or Redux Sagas, there is no particular notion of “step” and effects can be triggered arbitrarily without any relation to a specific state transition (i.e. an action).

The Step concept enables SAM to support generic action authorization and cancellation mechanisms. These concepts are implemented in the SAM’s State Action Fabric Element (SAFE).

  1. The use of RxJs as a Wiring Mechanism is highly Overrated

RxJS is a popular library which implements reactive extensions for JavaScript. There is/was a wide belief that RxJs and streams of events, in particular, are the way to go to “wire” the elements of frameworks such as Cycle.js:

Cycle.js is simply an architecture for building reactive web applications: a set of ideas about how you should structure your app using RxJS.

Even Google’s Angular Team is working on “ng-rx”, and Netflix’s Ben Lesh recently published the redux-observable middleware.

The problem with RxJS is that the wiring happens via a subscription. When we create an “observable” variable, parts of your program need to subscribe to it, and as a corollary, unsubscribe as well. Worse, if you subscribe twice to the same observable you generally instantiate two execution threads.

In a recent post, André Medeiros explained:

in Cycle.js, we only allow subscribe() to happen inside drivers [which handle effects]. This means that application [logic] is unaware of subscriptions. It becomes hard to understand and debug an application when developers make the assumption that there is only one execution for each of the Observables coming from drivers.

Michel Weststrate, the creator of MobX adds:

you will make mistakes in managing those subscriptions and either oversubscribe (continue subscribing to a value or store that is no longer used in a component) or undersubscribe (forgetting to listen for updates leading to subtle staleness bugs).

The general problem with observer-based programming models is that they do not naturally lead to a critical section where proposed mutations are accepted. They are too “reactive”. They drive us right back to the initial problem of Front-End architecture where events (now packaged as observables or streams) are directly wired to event handlers that still need to synchronize their operations one way or another.

By contrast, SAM’s TLA+ foundation provides a set of semantics focused on deciding which actions are “allowed” at any given time. SAM’s semantics even allow a distinction between the actions that can be initiated and the ones that have already been initiated and can present data (action cancellation). This determination is solely based on the current application state (from the last step) regardless of the path that was taken to reach that state. SAM semantics are easier to reason about because they are solely based on the present, as opposed to Rx or Saga semantics which need to know about the past (what was subscribed to).

Conclusion

Front-end architectures are evolving rapidly: new libraries and constant refactoring of existing ones seem to arrive on a weekly basis. The interest for a Functional Reactive foundation is genuine and appears to be here to stay, though we might need to define with more precision what it truly means. Concepts such as functional HTML, a unidirectional data flow or a single state tree already deliver a significant value, while reactive extensions (wiring) and the handling of effects seem to require more research. Templates and Data Binding do not seem to be moving forward with this transition.

SAM introduces three key semantics when compared to current approaches. First, SAM requires a clear separation between proposing and accepting model mutations (easier side-effects management). Second, SAM encourages developers to translate system events into dedicated actions (more modular code). Last, SAM introduces the concept of State function which interprets the Model property values to derive both the State Representation and next-action. Overall, these semantics help control the order in which the model mutates which is important when your GUI is part of a wide, dynamic distributed system.

You can learn more about the SAM pattern here and download the SAFE middleware here. Feel free to join our discussion room on Gitter.

References

1 Members of the SAM community who have contributed code samples: Jose Pedro Dias, Fred Daoud, Troy Ingram, Gunar C. Gessner, Bruno Darrigues, Michael Solovyov, Son Ngoc, Robert Blixt, David Fall.

About the Authors

Gunar C. Gessner is a Software Engineer, working on web development for the past eight years. He's fanatic about software quality and currently focuses on JavaScript, using ES2016, Node and React. He earned his B.Eng. in Electrical Engineering and formerly worked with embedded electronics. 

 

Jean-Jacques Dubray is the founder of xgen.io and gliiph. He has been building Service Oriented Architectures, and API platforms for the last 15 years. He is a former member of the research staff at HRL and earned his Ph.D. from the University of Provence (Luminy campus), home of the Prolog language. He is the inventor of the BOLT methodology.

 

Reactive programming techniques are becoming more prevalent in the constantly changing JavaScript landscape. This article series hopes to provide a snapshot of where we're at, sharing multiple techniques; variations on a theme. From new languages like Elm to the way Angular 2 has adopted RxJS, there's something for every developer, no matter what they're working with.

This InfoQ article is part of the series "Reactive JavaScript". You can subscribe to receive notifications via RSS.

Rate this Article

Adoption
Style

BT