The SAM Pattern
SAM (State-Action-Model) is a software engineering pattern that helps manage the application state and reason about temporal aspects with precision and clarity. Modern Software Engineering practices are essentially based on Functions (Actions) and Types which encourage a sprawl of unstructured assignments and event handlers. SAM's founding principle is that State Mutations must be first class citizens of the programming model. Once that principle is accepted, proper temporal semantics can be articulated.
Let's consider the simplest application possible, an application that counts events
var model = {counter: 0}
Most software engineers would implement the application state mutation in an event handler as a simple assignment:
model.counter = model.counter + 1 ;
SAM challenges these practices and suggests isolating application state mutations:
________________________... event ...__________________________
| |
| Render
| ___________Model___________ |
v | (synchronized) | |
Action -> | Acceptor(s) -> Reactor(s) | -> Next-Action and|or State
^ |___________________________| | Representation
| |
|______________________________________________|
// 1. actions compute proposals
const proposal = {incrementBy: 1}
// 2. the model accepts, partially accepts or rejects the proposals
model.accept(proposal)
// It is recommended to factor the model as a series of acceptors and reactors
// 2.a. Acceptors validate proposals and mutate the state accordingly
// 2.b. Reactors react to state mutations and are not involved in processing proposals
// 3. evaluates whether a next-action is needed
.then(nextAction)
// 4. compute the new state representation
.then(representation)
The "View" or clients as explicitely excluded from the pattern, so the way events or requests are translated into actions are not part of the pattern. Similarly, the mechanisms by which the state is rendered or a response is returned to the client are also excluded.
SAM is unapologetically driven by simplicity and does not require any library or framework. However if you prefer using a library, the sam-pattern library makes it easy to use the pattern along with several temporal programming constructs.
This is what some developers are saying:
Daniel Neveux, Entrepreneur, Full Stack Developer, and author of the Ravioli Framework said:
SAM is simple, I have never seen a concept such minimalist and so powerful [in particular] in its decoupling potential
Having mashed up our own implementation of the SAM Pattern made it even more interesting, as we’ve won significant flexibility and speed in development, allowing us to gain more on what capitalize on the most at the engineering team, Developer Time.
This has allowed us to focus more on our business needs and less on bugs, but more importantly, It has enabled us to have the necessary time to craft our software with care and smoothness, so that we can deliver maximum value to our clients.
I don't know what it is about sam but I love it. It's great to have a simple way to organize my programs, particularly UI, but I've been using it with any async programming. I used to have no way to think about how to "factor" javascript programs in a way that made principled sense to me too much async going on "how do you manage the state? (model)" and all attempts to start managing state seem to start going off the tracks. What I like about sam is that it's just a pattern so you can implement it yourself and adapt as necessary that can seem scary at first but really its way more useful and flexible.
Here is a 10 min introduction: | Here is an in depth presentation of the pattern in the context of Angular2 |
You want to see some code? Take a look at the Fishing Game (index.html) here which you can run here.
Here is a list of up-to-date TODOMVC code samples:
This pattern was first introduced in an InfoQ article, by Jean-Jacques Dubray. A translation of the article is available in Chinese, French, Japanese and Russian
You have some questions?
If you like it, please consider sharing the word: Share on Twitter
How does SAM work?
SAM is built on one of the most robust foundation of computer science (TLA+).
SAM recommends factoring application state management along three building blocks, actions, model, and state that are invoked in well defined "steps".
- Actions are triggered by events, their role is to translate events into proposals to mutate the model
- The Model is solely responsible for the decision of accepting (or rejecting, or partially rejecting) the proposal's values
- The State function is divided into the next-action-predicate (nap) which invokes any automatic action, given the current (control) state and computing the State Representation from the Model property values.
The wiring of the pattern should propagate the new “state representation” by rendering the view or returning a reply to any client or subscriber.
Every event is processed as “step” which involves this reactive propose/accept/learn flow. That's it! That's all there is to SAM.
The pattern is best implemented with the model as a single state tree and a unidirectional data flow. In the case of Front-End applications, SAM is best implemented when the view is a pure function of the State Representation. Two-way data binding is strictly prohibited as it would entail uncontrolled model mutations.
The pattern forms a reactive loop: events trigger actions which present data to the model, which computes the state representation. The reactive flow forms a natural step and after every step a new "state representation" is created (as a pure function of the model).
As we show in many samples below, the view can be implemented as a series of stateless components which have no knowledge of the model or even the actions.
When learning SAM, we recommend that you make a clear distinction between the programming model (State, Actions, Model), the wiring (how the elements of the pattern communicate) and the Architecture (where the elements of the pattern are physically running). SAM supports different wiring approaches and allows you to write code that can easily be migrated from the client to the server or vice versa (isomorphic javascript).
The View as a Function of the Model
When learning SAM in the context of Front-End applications, the best starting point is to understand the functional relationship between the view and the model. SAM suggests decomposing the view as a series of pure functions that are fed with the properties of the State Representation.
This concept is extremely simple to implement. I generally implement the front-end components as part of "theme" singleton (because themes are interchangeable):
let theme = {} ;
// React
theme.ready = (counter,intents) => <Counter count={counter} action={intents['start']}/>
let Counter = ({ counter,action }) => (
<p>Counter: {counter} </p>
<button onClick={action}>
Start!
</button>
)
// Angular2 (with Directive)
view.ready = function(model) {
return (
`<p>Counter:${model.counter}</p>
<form onSubmit="return actions.start({});">
[Directive]<br>
<input type="text" placeHolder="AutoGrow Directive" autoGrow/><br>
[/Directive]<br>
<br>
<input type="submit" value="Start">
</form>`
) ;
}
// Snabbdom
theme.ready = (counter, intents) => h('div', [
'Counter: ',
counter,
h('br'),
h('button', {on:{click:function() { intents['start'](); }}}, 'Start')
]);
// Vanilla.js - ES6
theme.ready = (counter, intents) => `
<p>Counter: ${counter} </p>
<form onSubmit="return ${intents['start']}({});">
<input type="submit" value="Start">
</form>`
With SAM, front-end developers can focus on building the view directly from the State Representation, unencumbered from the underpinning APIs. How the we got to the current State Representation is irrelevant. This is the paradigm shift that React introduced in Front-End architectures. SAM does not always eliminate the need for interactions (request/responses), but it tends to keep a good balance between reactive and interactive modes.
We get a fair amount of questions asking "why not use templates"? The reason is simple: templates are interpreters while functions are mini-code generators. Interpreters are limited in what they enable you to express (not to mention you often have to learn a specific syntax that does not translate at all to another template interpreter). On the other hand, code generators are infinitely flexible with hardly any drawback. I would personally choose functions over templates any day.
SAM allows you to choose the framework of your choice including none, but when using a Vanilla.js implementation, you need to make sure you mitigate the risks of cross-site-scripting.
A couple of promising libraries have appeared recently (hyperHTML, lit-html and yalla.js) that offer support a "Functional HTML" approach. They, of course, are particularly good fit for the SAM Pattern.
Business Logic
Not only SAM supports a functional representation of the View, but SAM enables a complete decoupling of the application logic from the View components. If you are not convinced you can look at this vanilla.js TODO sample and the "theme" section.
SAM's factoring of the business logic is inspired from TLA+:
TLA+ is based on the idea that the best way to describe things formally is with simple mathematics, and that a specification language should contain as little as possible beyond what is needed to write simple mathematics precisely. TLA+ is especially well suited for writing high-level specifications of concurrent and distributed systems. - Dr. Leslie LamportIf you are interested in learning about TLA+, I found Stephan Merz's course the most approachable introduction to TLA+.
TLA+ is a formal specification which can be used describe, analyze and reason about systems, such as:
- sequential algorithms
- interactive systems
- reactive & distributed systems
- real-time & hybrid systems
- security-sensitive systems
TLA+ offers a uniform language where Mathematics form the basis for description and analysis of reactive and distributed systems. The key paradigm shift is that no programming language today support any "temporal" constructs. Making programming much more difficult and error prone than it ought to be.
TLA defines two levels of syntax: action formulas and temporal formulas.
- action formulas describe states and state transitions
- temporal formulas describe state sequences
TLA+ also includes concepts such as safety (something bad should never happen) and liveness (something good eventually happens).
In SAM, the business logic is decomposed in three elements: Actions, the Model and the State.
Warning I have been told by readers that it is important to emphasize that SAM does not use the word "State" in the traditional sense of computer science (where a state is an assignment of values to all possible variables). SAM suggests that the Model structure (and property values) are different from the State Representation, which there could be many of (Web, Mobile, Voice...). The State Representation also includes the Control State of the system. The State-Action elements, in SAM, specify the behavior of the system (not the Model):
A State-Action behavior is a sequence:
α1 α2 s1 −→ s2 −→ s3 −→ ...The step <si, αi, si+1> represents a transition from state si to state si+1 that is performed by action αi
Please refer to Section 2 of this paper from Dr. Lamport for a discussion on State-Action behavior.
If you don't like the State-Action-Model terminology, I could have also used the Paxos protocol terminology (PAL, Proposer, Acceptor and Learner):
Client Proposer Acceptor Learner | | | | | | | X-------->| | | | | | Request | X--------->|->|->| | | Prepare(1) | |<---------X--X--X | | Promise(1,{Va,Vb,Vc}) | X--------->|->|->| | | Accept!(1,Vn) | |<---------X--X--X------>|->| Accepted(1,Vn) |<---------------------------------X--X Response | | | | | | | View Action Model State (SAM)
In essence the Paxos protocol roles are exactly the roles of the SAM components: Actions propose values to the Model, which accepts them, which the State accesses (the Learner) to create the State Representation (which is displayed by the View).
I personally like SAM because it surfaces the concept of "State-Action" behavior, which is at the core of SAM, and leads naturally to creating the "State Representation" (i.e. the View).
So, for all intent and purposes, please be warned that SAM's Model is the assignment of values to all possible variables of the application state, while SAM's State (representation), which is sometimes contains the "control state" (such as "started" and "stopped" are two control states of a car), refers to a function that computes the current state representation and control state of the system. In general, the "State" of a system controls which actions are allowed at any given point in time. The decoupling State/Model supports a multiplicity of state functions (such as a car with a beginner or experienced modes).
Actions
Actions are functions which translate an event into a proposal which contains the values we want the model to accept. There is nothing else to it. In Redux for instance, "actions" are a data structure, which are a lot closer to an intent or event than an action. This is a fundamental difference between Redux and SAM because in Redux the reducer creates an unnecessary and unwanted coupling between the model mutations and the logic that translates intents into model property values.
// Reactive loop wiring
function action(event, present) {
// compute the values we want the model to mutate to
const proposal = the_actual_pure_function_implementing_the_action(event) ;
// present these values to the model
present(proposal) ;
// since we are in a reactive loop, the action returns nothing
}
async function changeOfAddress(address = {}, present) {
address.country = address.country || 'Australia' ;
const postalAddress = await getPostalAddress(address);
// assuming the dataset returned by the 3rd party
// service can be directly presented to the model
present(postalAddress) ;
}) ;
}
Actions are typically highly reusable across models, and we could even imagine some companies starting offering SAM actions following a SaaS model. A "change of address" action, which returns a postal address given user data is highly reusable, across many businesses.
Model
The model is a singleton that contains all the application state and (logically) exposes a single method: present(proposal). The model is responsible for accepting (or rejecting) the effects of an action. The model is where integrity rules get enforced. In other words, actions are generally triggered with some context specific data, they do not have access to the application state, for instance, when you implement a "change password" action, the action will check if the password is valid in the context of the request, but the model might enforce additional integrity rules, such that you cannot use a password that matches any of your last three passwords.
The model is also solely responsible for the persistence of the application state. As such, the Model's architecture may follow the Event/Command Sourcing Pattern with respect to the data the it accepts:
model.present = function(proposal, state) {
if (proposal.address !== undefined) {
model.shippingAddress = proposal.address ;
model.billingAddress = model.billingAddress || proposal.address ;
}
// trigger the state representation computation
state(model) ;
// since we are in a reactive loop, the present method returns nothing
}
State
This concept is unique to SAM. Its purpose is to futher decouple the Model from the View. The State function has two roles:
- Translate the model property values into a State Representation
- Compute the control state and trigger the next-action predicate
Unintuitively, the State is a pure function that does not hold any (application) “state”. The State function also computes the current "control state" of the system, but SAM does not require the State implementation to be based the semantics of finite state machines. It offers a structure that is compatible with FSMs but for the most part avoids the unecessaries intricaties of FSMs. In the Rocket Launcher example, to demonstrate the compatibility, we've have implemented the full semantics of a finite state machine where each control state is computed with pure functions such as:
// Assuming the following model structure
var model = {
counter: COUNTER_MAX,
started: false,
launched: false,
aborted: false}
// Derive the current control states of the system
const ready = model => (model.counter === COUNTER_MAX) && !model.started && !model.launched && !model.aborted
const counting =model => (model.counter <= COUNTER_MAX) && (model.counter >= 0) && model.started && !model.launched && !model.aborted)
const state = (model, render) => {
const stateRepresentation = _state(model)
if (!nextActionPredicate(stateRepresentation)) {
render(stateRepresentation, model.present)
}
}
const _state = model => {
// Compute the control state
const stateRepresentation = model
let controlState = 'oops... something went wrong, the system is in an invalid state' ;
if (ready(model)) {
controlState = 'ready' ;
}
if (counting(model)) {
controlState = 'counting' ;
}
if (launched(model)) {
controlState = 'launched' ;
}
if (aborted(model)) {
controlState = 'aborted' ;
}
// Other computations that are part of the state representation go here
return { controlState, ...stateRepresentation }
}
Depending on the number of control states of your application and the size of your model, these functions can become rather tedious to write. Again, there is no requirement to adopt such an approach, it may help in some cases and be a burden in others where a series of if-then-else on some key properties of the model would be good enough.
Once the state representation is rendered, the State function is responsible for invoking the next-action predicate (nap), which is a function of the model. The purpose of the nap() function is to determine if, given the current control state of the model, there are some automatic actions that need to be invoked. For instance in the Rocket Launcher example, the "decrement()" action is invoked by the next-action predicate while the system is in the counting state. When the counter reaches zero, it invokes the "launch()" action.
const nextAction = (stateRepresentation, present) => {
if (stateRepresentation.controlState === 'counting') {
if (stateRepresentation.counter>0) {
actions.decrement({ counter: stateRepresentation.counter }, present)
return true
}
if (stateRepresentation.counter === 0) {
actions.launch({}, present)
return true
}
}
return false
}
When you feel that a full state machine is required for your app, you may use a library such as the STAR library.
Rendering (or Consumming) the State Representation
In Front-End applications, the State Representation is typically rendered as part of the reactive loop, however, in the most general sense, the state representation is published to consumers (i.e. Learners in Paxos). Formulating the View as a series of pure functions of the state representation work well.
One of the key problems that front-end frameworks try to tackle is the wiring between the HTML events and event handlers. View components can be decoupled from actions via a list of "intents" which map the application's action to the view component's handlers. We have implemented that approch in the TODOSAM sample. All the theme's components accept an intent map:
theme.list = (todos, displayActive, displayCompleted, intents) => {
...
const label = `<label ondblclick="return actions.${intents['edit']}({'id':'${todo.id}'});">${todo.name}</label>` ;
...
}
// mapping view intents -> actions
actions.intents = {
edit: 'edit',
save: 'save',
done: 'done',
displayAll: 'displayAll',
displayActive: 'displayActive',
displayCompleted: 'displayCompleted',
toggleAll: 'toggleAll',
delete: 'delete'
}
The concept could also be extended to map the event format to be directly consumable by the action, but this coupling is less important than the coupling of the actions with the view.
Wiring
The SAM pattern can be described as a Mathematical expression (formula):
V = State( Model.present( Action( event ) ) ).then( nap )However, that expression is only a logical expression that describes the sequence in which the elements of the pattern are invoked. The expression itself is not representative of the Reactive Flow or the different wiring options.
SAM can be implemented in the browser (like React and Redux), but one of SAM's advantages is that the elements of the pattern can be distributed and composed in virtually any topology. So there is not a single way to wire the pattern.
You should also keep in mind that SAM is a reactive pattern so there is never a response that is expected: The view triggers an action, which presents its proposal to the model, which asks the state to create a state representation, which is rendered by the view and displayed in the browser. SAM could also be used with an interactive wiring where a request is wired to an action and the state representation becomes the response to the request.
Let's start with a sample where running SAM exclusively in a single process (e.g. the browser, perhaps assuming the model will communicate with a persistent store).
The model can be defined as a singleton, the actions as pure functions of an event payload, the state (stateRepresentation() and nap()) as a pure function of the model:
// Model is a singleton /////////////////////////////////////////////
const model = {} ;
model.present = function(data) {
// Logic that accepts or rejects the proposed values
// ...
// -> Reactive Loop
state(model)
// persist the new state
// this is generally guarded and optimized
model.persist()
}
model.persist = function() {
// some persistence code
...
}
// Actions are pure functions /////////////////////////////////////////////
function action1(data) {
// Logic that prepares the data to be presented to the model
// ...
// -> Reactive Loop
present(data)
// to avoid a page reload
return false
}
function action2(data) {
// Logic that prepares the data to be presented to the model
// ...
// -> Reactive Loop
present(data) ;
// to avoid a page reload
return false ;
}
// State is a pure function /////////////////////////////////////////////
function state(model) {
// Compute State Representation
const sr = stateRepresentation(model)
// Invoke next-action-predicate
if (!nap(sr)) {
// Render the view when no next action is invoked
view.render(sr)
}
}
function nap(stateRepresentation) {
if (condition(stateRepresentation)) {
const event = e(stateRepresentation)
action2(event)
return true
}
return false
}
// View is a pure function of the state representation /////////////////////////////////////////////
const view = {
render({a, b, c}) {
// render the view
let output = '${a} and ${b}
'
// ...
// wire the possible actions in the view
output = output + ``
// ...
// -> Complete Reactive Loop
display(output)
}
}
// Wiring /////////////////////////////////////////////
//
// Actions are known to the stateRepresentation() and nap()
//
// Actions -> Model
function present(proposal) {
model.present(proposal) ;
}
// View -> Display
function display(nextState) {
let view = document.getElementById("view")
view.innerHTML = nextState
}
Of course, JQuery event handlers could also be used:
...
<input id="username" type="text" class="login-username" value="username">
<input id="password" type="password" class="login-password" value="password">
<button class="button button-green center-button" id="login">Login</button>
...
<script type="text/javascript">
$('#login').click(function() {
var session = $.post( "http://authserver/v1/login", { username: $( "#username" ).val(), password:$( "#password" ).val() } ) ;
session.done(function( data ) {
var loginPanel = document.getElementById("login_panel") ;
loginPanel.innerHTML = state.render(model.present(data)) ;
;
})
}) ;
</script>
...
// another option is to implement the state, model and actions on the server:
<script type="text/javascript">
$('#login').click(function() {
var session = $.post( "http://authserver/v1/login", { username: $( "#username" ).val(), password:$( "#password" ).val() } ) ;
session.done(function( stateRepresentation ) {
var loginPanel = document.getElementById("login_panel") ;
loginPanel.innerHTML = stateRepresentation ;
;
})
}) ;
</script>
One can also use RxJS to wire events to actions.
var result = document.getElementById('submit');
var source = Rx.Observable.fromEvent(document, 'click');
var subscription = source.subscribe(function (e) {
var data = { name: document.getElementById('name').value, ... }
result.innerHTML = state.render(model.present(data));
});
Wiring is an important concern when implementing the pattern. For instance, the mobx framework wires individual property value changes to the view rendering function, which is not representative of the behavior of the system and the processing of an action as a unit of work. As you can see in the example tab below, the view renders for state data changes:
// The state of our app
var state = mobx.observable({
nrOfSeats : 500,
reservations : [],
seatsLeft : function() { return this.nrOfSeats - this.reservations.length; }
});
// The UI; a function that is applied to the state
var ui = mobx.computed(function() {
return "\nSeats left: " + state.seatsLeft +
"
Attendees: " + state.reservations.join(", ") + "";
});
// Make sure the UI is 'rendered' whenever it changes
ui.observe(function(newView) { console.log(newView) }, true);
// Put in some test data
state.reservations[0] = "Michel";
state.reservations.push("You?");
state.reservations[0] = "@mweststrate";
state.nrOfSeats = 750;
<div>Seats left: 499<hr/>Attendees: Michel</div>
<div>Seats left: 498<hr/>Attendees: Michel, You?</div>
<div>Seats left: 498<hr/>Attendees: @mweststrate, You?</div>
<div>Seats left: 748<hr/>Attendees: @mweststrate, You?</div>
Time Travel
Gunar Gessner has implemented a client-side "Time Travel" dev tool that enables you to take snapshots of the model after every action is processed and then restore the state of the system with a given snapshot.
The SAFE container implements both a client-side and server-side "Time Travel" dev tool.
Micro Container
The SAFE project (State-Action-Fabric-Element) is a micro-container for SAM implementations which can run in the browser or on node.js. At a minimum SAFE enables you to wire the elements of the pattern. It also comes with Session Management, Logging and Error Handling. The current version enables global validation and enforcement of actions, including action "hang back". Last but not least, it implements the Time Travel dev tool.
Composition
The SAM pattern offers some generous composition mechanisms.
First Actions, as pure functions, compose naturally to present a single dataset to the model:
C(data) = A(B(data))
This type of composition is a functional composition and the resulting action is considered to be a single action applied to the system from SAM's point of view. Logically you cannot compose Actions, as it would violate, the Action->Model->State step, you can only create "composite" actions.
Similarly, the State Representation (View) can be decomposed in a hierarchy of components:
V = f( M ) f( M ) = f1( g1(M) + g2(M) ) + f2( h1(M) + h2(M) )
The most interesting composition mechanisms are at the pattern level itself. For instance, SAM supports an instance level composition where one instances runs in the browser and one instance runs in the server.
// The composition has a single view V = Ss( Ms ) + Sc( Mc ) // Instance c (browser) invokes an action on instance s (server) // this action is generally invoked in the nap() function of the client V = Ss( Ms.present( As(Mc) )
Though it is theoretically possible, it is highly recommended to refrain from invoking client actions from the server. The role of the server is rather to deliver the SAM client instance as part of its state representation.
Here is a Parent/Child sample using React 15.0.2 with JSX/Babel. It shows how you can implement a complex form of wizard with a child instance and submit the resulting dataset to the parent, once the content of the form is valid.
NOTE: THIS IS JUST SOME INITIAL THOUGHTS, NEED MORE WORK:
SAM also offers an interesting alternative composition mechanism which enable to synchronize a client side and server side model, by which you present the same dataset to both the client side and server side model.
State Machines and Automatic Actions
Computation is a major topic of computer science,
and almost every object that computes is naturally viewed as a state machine.
Yet computer scientists are so focused on the languages used to
describe computation that they are largely unaware that those languages
are all describing state machines - Dr. Leslie Lamport
SAM is particularly well aligned with the semantics of State Machines except for one simple, yet fundamental difference. Classical State Machine semantics (based on Petri Nets) imply that the actions somehow connect two states, so State Machines are described as a series of tuples such as:
initialState = S0 (S0,A01,S1), (S1,A12,S2)...
This definition is somewhat of an approximation: it is not the action that decides the resulting state, it is the model, which, once the action has been applied (its value have been accepted, or not) that decides the resulting state (Aik are the allowed actions in a given state Si) :
initialState = S0 = S( M0 ) (S0, A00, A01), (S1, A10, A11, A12)... S0 = S( M0 ) S1 = S( M1 ) ...
The tuples (Si,Aik,Sk) are merely an observation of the behavior of the state machine, rather than a physical representation of its runtime.
This change of perspective (not semantics) is minute, yet fundamental. SAM would not work (as well) if the traditional semantics of State Machine would be structuring the code. The SAM semantics are inclusive of this traditional structure, and therefore strictly compatible, but SAM by no means require that one uses a graph of State and Actions.
I believe that is why some people have expressed that SAM "feels natural".
The Rocket Launcher example shows how to implement the Sx() functions:
// Derive the current state of the system
state.ready = function(model) {
return ((model.counter === COUNTER_MAX) && !model.started && !model.launched && !model.aborted) ;
}
state.counting = function(model) {
var status = ((model.counter <= COUNTER_MAX) && (model.counter >= 0) && model.started && !model.launched && !model.aborted) ;
return status ;
}
state.launched = function(model) {
return ((model.counter == 0) && model.started && model.launched && !model.aborted) ;
}
state.aborted = function(model) {
return (
( model.counter <= COUNTER_MAX) && (model.counter >= 0)
&& model.started && !model.launched && model.aborted ) ;
}
I cannot emphasize enough that it is not necessary to adopt a State Machine structure to use SAM and more often than not if-then-else will be adequate. However, sometimes it may become easier to use a strict State Machine structure. These functions can be used to validate that an action is enabled or not. They can also more naturally break down the hierarchy in from which the view is rendered.
APIs
SAM was originally designed to solve the strong coupling that MVC creates between the Front-End structure and the Back-End APIs. APIs can be composed at a couple levels in SAM:
- Actions: for APIs with no side-effect whatsoever on the model
- Model: for CRUD APIs that persist/populate the model property values (commonly called the Data Access Layer)
function getRssFeed(data) {
var options = {
url: 'http://www.ebpml.org/blog15/feed/',
headers: {
'accept': 'application/json'
}
};
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
// parse the RSS feed into a json object
parseString(body, function (err, result) {
// prepare proposed dataset
data.blog = result.rss.channel[0].item ;
// present data to the model
present(data) ;
});
}
});
}
Similarly, the model can perform all the persistence operations (which could result in rejecting some proposed values) before passing control of the Reactive Loop to the State object. That is very different from React because you cannot touch the State without triggering a rendering of the view, which makes very little sense, when you think of it. It only makes sense when you spread the model state into the various components, and even then it warps the articulation of APIs with the Front-End reactive Flow.
Headless SAM
SAM can also be used without a View, whereby it listens on incoming Action requests and returns a response which can be the model of a fraction of the model. As such SAM offers a new API implementation pattern which is particularly well suited for implementating Stateful APIs.
A node.js implementation of Headless SAM would look like this:
var express = require('express');
var app = express();
//// Model ////////////////////////////
var S0 = { ... } ;
var present = (
function (initialState) {
var model = initialState ;
return (function( data, res) {
// mutate the model
...
state(model) ;
}) ;
}
)(S0);
//// Actions ////////////////////////////
actions.addAPI('submit') ;
app.post(actions.apis.submit.path, function(req,res) {
// extract intent from request body
var intent = new Intent('submit',req.body) ;
// Compute model property values
data_ = actions.impl.submit(intent) ;
// present data to the model
presentToModel(data_, res) ;
}) ;
//// State ////////////////////////////
function state(model) {
// prepare and return API response
var response = model.response() ;
res.send(response) ;
// execute next action predicate
nap(model) ;
}
The pattern can easily be adapted to mount a user session in the model or rehydrate/dehydrate the context of the request before/after the model mutates.
Isomorphic JavaScript
Typical SAM's implementations make it easy to move JavaScript code between the client or the server (a.k.a Isomorphic JavaScript).
# | Client | Server | Implementation |
---|---|---|---|
1 | View | Actions, Model, State | Event handlers call Actions Server returns HTML |
2 | View,Actions | Model, State | Event handlers implement actions which call present() Server returns HTML |
3 | View,Model,State | Actions | Event handlers invoke Actions, response is presented to the Model on the client Server returns JSON |
4 | View,State | Actions,Model | Event handlers invoke Actions, response is presented to the State on the client Server returns JSON |
5 | View,Actions,State | Model | Event handlers implement Actions which call present(), response is presented to the State on the client Server returns JSON |
For instance, in the Blog sample we have implemented the Actions on the client and the Model and the State on the server.
In that case, we have to implement two "APIs" on the server:
- init()
- present(data)
Option #1 should be preferred when authorization (RBAC) is a concern. Option #5 is the one that pushes as much processing on the client as possible.
function present(data) {
// client side
//model.present(data) ;
// server side
$.post( "http://localhost:5425/app/v1/present", data)
.done(function( representation ) {
$( "#representation" ).html( representation );
}
);
}
function init() {
// client side
//view.display(view.init(model)) ;
// server side
$.get( "http://locahost:5425/app/v1/init", function( data ) {
$( "#representation" ).html( data );
}
);
}
Concurrency
JavaScript is a concurrent programming language. In SAM, the model is a critical section that must be synchronized. In simple applications, this is usually achieved without any specific considerations. SAM's structure where actions can invoke APIs prior to presenting their proposal to model helps significantly by isolating the way the proposal is constructed from the application state mutation. In particular, another action executed concurrently would be able to present it's proposal as if the first (long running) action would have never been triggered.
When concurrency is an issue, it's possible to use the "Bakery Algorithm", again using the work of Dr. Lamport.
How can I get started?
First, I would recommend getting familiar with the approach I call "functional HTML" perhaps starting with this article and its samples (which are not SAM based).
Then I would build a simple SAM loop [with plain vanilla Javascript](https://github.com/jdubray/sam-samples/blob/master/todomvc-app/js/app.js) and finally explore the boilerplate SAM sample, though these days I would recommend starting with the [lit-html version](https://github.com/jdubray/startbootstrap-clean-blog-lit-html)
If you are up to building something more substantial, you could download an HTML5 template of your choice and start building an application with it. To make this tutorial more realistic, we are going to build a simple Web Site with Blog and a Contact form.
The SB Admin template seems like a great starting point. The list of components are easy to identify, from the index.html file:
- ----- Structure ------
- Navigation Bar
- DropDown
- Search
- Menu
- Footer
- ----- Structure ------
- Stats Summary
- Graphs
- Tables
- ...
Many developers dislike the code that will follow (let's call it vanilla.js). Since this is a matter of preferences, there is not much point in arguing about it. I just happen to prefer a raw JavaScript/HTML5/CSS3 style because:
- it enables the greatest number of developers to become full stack developers (with Node.js)
- it enables the greatest decoupling between the View and the Model
- Vanilla.js makes it really easy to develop Isomorphic JavaScript
- And perhaps, most practically, it allows anyone to take the work of a Web designer and turn it into a beautiful Web app in a matter of minutes
The experienced developers will easily translate this code to meet their neesds. So let's go ahead and start creating the component interfaces without any further apologies:
var theme = {} ;
theme.head = function(title, includes, addons ) { } ;
theme.navBar = function(dropDowns,sideBar) { } ;
theme.adminDropDown = function(urls) { } ;
theme.search = function(search) { } ;
theme.menuItem = function(label,symbol, url, submenu, level) { } ;
theme.sideBar = function(menu,color) { } ;
theme.well = function(size,h,title,body,br) { } ;
theme.footer = function( includes ) { } ;
theme.statSummary = function(fa,label,number,link,linkLabel,color) { } ;
theme.advancedTable = function(size,id,color,heading,headers,columns,data,href,domain) { } ;
theme.ediTable = function(id,headers,data,editable, style, maxSize) { } ;
...
The implementation of each function is just a parameterization of the HTML template (the whole theme implementation can be found here):
theme.statSummary = function(fa,label,number,link,linkLabel,color) {
return (
`<div class="col-lg-3 col-md-6">
<div class="panel panel-${color}">
<div class="panel-heading">
<div class="row">
<div class="col-xs-3">
<i class="fa fa-${fa} fa-5x"></i>
</div>
<div class="col-xs-9 text-right">
<div class="huge">${number}</div>
<div>${label}</div>
</div>
</div>
</div>
<a href="${link}">
<div class="panel-footer">
<span class="pull-left">${linkLabel}</span>>
<span class="pull-right"><i class="fa fa-arrow-circle-right"></i></span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>`) ;
} ;
Code Samples
SAM Pattern Libraries
sam-pattern library General purpose library with support child instances and time travel(Jean-Jacques Dubray)SAMCAS (by Steve Pritchard)
SAM.js (by Gunar Gessner)
Rocket Launcher
Vanilla JavaScript and HTML - You can run that sample here (courtesy of Jose Pedro Dias)MobX + React (Fred Daoud)
Cycle.js (Fred Daoud)
Snabbdom ( Jose Pedro Dias). You can run the sample here.
Knockout.js (Troy Ingram)
React+Redux (following the SAM pattern) (Gunar Gessner)
Angular (following the SAM pattern) (Bruno Darrigues)
Angular+TypeScript (following the SAM pattern) (Bruno Darrigues)
Inferno
Angular2 with Dynamic templates and ComponentFactory
Vanilla.js+Redux (Fred Daoud)
Vanilla.js (Steve Pritchard)
Fishing Game
Vanilla.jsToDo
Vanilla JavaScript and HTMLVue.js (Michael Solovyov)
TODOMVC Challange (HTML+Vanilla JS, both ES5 and ES6) Angular2 Admin Template with ToDo component with RxJS This is a TodoMVC project that shows how SAM Pattern can be implemented in a typical Angular application with ngrx (@Nivani)
Blog
Node.js / Isomorphic JavaScript - BootStrapContact
Simple Web site with Contact App (Node.js+HTML)Themes
A simple Web app built on the SB Admin template (Node.js+HTML)A simple Web Site (with actions) built on the Ex-Machina template (HTML+Snabbdom) (@imnutz / Son Ngoc)
A Vanilla ES6 Builerplate Project
A full stack, vanilla.js/Node.js of the Bootstrap Clean Blog project (ES6)
Others
A dual-client (session based) Tic-Tac-Toe game with a SAM-based engine (React/Redux+Firebase) via David Fall A simple Counter App using Flyd (@imnutz / Son Ngoc)How to use Mocha and Chai to write unit tests (BDD) for SAM implementations (via Robert Blixt)
A contact manager web application built with SAM and CSP (Communicating Sequential Processes) (@imnutz / Son Ngoc)
SAM-Swift The SAM (State-Action-Model) pattern in Swift A simple CRUD application running on AWS Lambda and DynamoDB for session management
A simple counter using React 15.0.2 with JSX/Babel
A Parent/Child sample using React 15.0.2 with JSX/Babel
Another Parent/Child sample in vanilla.js
A CSS Spinner using React/JSX without "looping" through the model
SVG Clock using the very promising lit-html library (Polymer Labs)
Counter in C# (Dmitry Shalkhakov)
FRETS - An Ultralight Composable Frontend TypeScript Web Framework (Tim Bendt)
SAMx is a SAM pattern implementation for MobX and React. It was also forked here
Calculator by David Kaye
Frameworks
André Statlz has written a great paper comparing the different Unidirectional User Interface Architectures. The pattern closest to SAM is Famous, but unfortunately, they mutate the model directly from the actions (events in Famous' terms) and they do not support a nap() function.
This talk from Andrew Clark at the React 2016 Conference offers some great perspective on the different approaches (React,Cycle,Elm) starting with a simple formula...:
V = f( M )
React.js
The SAM pattern spans several libraries of the React.js nebula: React.js itself, but also Flux/Redux, including separate libraries such as Redux-Thunk and Redux-Saga. This framework (of libraries) has been built rather opportunistically, solving one problem at a time, without a coherent architecture. Further its architecture focuses heavily on the client, without much consideration for the server.
React + Redux + Thunks + Sagas | SAM |
---|---|
Single State Tree | Single Model Tree |
State is read-only | Model is presented with new proposed values |
Reducers are pure functions Reducers have access to full scope of state when processing actions |
Actions are pure functions with respect to the model Actions have no access to the model to compute the values presented to the model |
Store is updated with the output of the reducer function Listeners are notified of the update |
State Representation is created after the model has mutated Next-Action Predicate evaluates whether an automatic action needs to be triggered |
View is a function of the state | State representation is a function of the model |
Next action predicate (nap) is implemented as a Saga which couples many operations and generally relies on local state as shown in this implementation of the Rocket Launcher. | The Next Action Predicate is a pure function of the model which computes the current (control) state of the system and derives the next-action. |
The main issues I see with this framework (as a set of libraries) are:
- Redux combines the logic that computes the values with which you would update the model with the logic that decides whether the model accepts these values or not.
- As a corollary it couples the interface of the reducers to the model. One of the key roles of actions is to adapt the View-Model interface in ways that makes the model insensitive to the view semantics. The Reducers' interfaces are directly shapped by the view, which is an anti-pattern.
- Anemic actions are the default in Redux, you need to add the "thunk" library to be able to use functions as actions
- Sagas are stateful by definition and their imperative style makes it difficult to reason about what is the next action that will occur.
That being said, the deal breaker, for me, is the fact that Reducers cannot run on the server because of React's architecture. The store would need to be updated with the output of the root reducer, i.e. the entire state, which would create a high bandwidth, chatty interaction with the client.
Roma Vaivod shared a great analysis on the differences between Redux and SAM
Angular2
Angular2 | SAM |
---|---|
Two-way Data Binding | Functional HTML - V = f(M) |
Components as Classes (OOP) | Functional building blocks (View, Action, State, NAP), in SAM only the Model is an Object |
Uncontrolled View updates with Change Detection | SAM programming Step and Rendering are fully decoupled |
With all its imperfections, boilerplate and template language, Angular makes it easy to implement the SAM pattern, which in turn, I believe, adds a lot of value to Angular with a Single State Tree and the ability to control mutations outside the Angular components. SAM allows you to code your business logic coupletely decoupled from Angular itself.
For complex Angular components, I found it easier to create a local SAM implementation where all the state mutations are groupled in a single method and map event handlers to actions (which compute the proposal to mutate the application state)
Vue.js
Vue.js is an increasingly popular framework for building modern Web applications. Just like React, the basic framework focuses (mostly) on the View and you need to select from another nebula of libraries when you need to build more robust applications. In this brief analysis, I will focus on vue.js only, as well as the vuex.js library which is a Redux-like state management library. Vue is template based and relies on two-way databinding which is a major step back in Front-End architectures.
Vue + Vuex | SAM |
---|---|
Two-way Data Binding | Functional HTML - V = f(M) |
Single State Tree | Single Model Tree |
Two-Way Data Binding | Model is presented with new proposed values |
Computed properties, Watches and Methods are free to assign new values to the model In Vuex, |
Actions are pure functions with respect to the model Actions have no access to the model to compute the values presented to the model |
Store is updated with the output of the reducer function Listeners are notified of the update |
State Representation is created after the model has mutated Next-Action Predicate evaluates whether an automatic action needs to be triggered |
View is a function of the state | State representation is a function of the model |
Next action predicate (nap) is implemented as a Saga which couples many operations and generally relies on local state as shown in this implementation of the Rocket Launcher. | The Next Action Predicate is a pure function of the model which computes the current (control) state of the system and derives the next-action. |
Meiosis
Meiosis is a library build by Fred Daoud that is well aligned with the SAM pattern. The library's core focus is to help organize the data flow of web applications. The general idea behind Meiosis is to start with a model and write a function that creates the view. When you want a looser coupling between the view and the model, you can optionally configure an "actions object" which translates view events into model proposals. Once the proposal is accepted by the model, Meiosis calls the view function with the updated model. You can also define a function to determine if another action should be automatically triggered, given the new application state. Meiosis is designed to work with plain JavaScript functions which, it wires together, without tying your code to the library. Last, but not least, you can switch from one Web framework to another and by changing the view code. The remainder of the code (the model, receiving proposals, actions, and so on) remains the same.
Here is a code sample implementing the SAM pattern and using lit-html
Ravioli.js
Ravioli is a JS/TS framework written by Daniel Neveux to develop full stack apps.
Ravioli is like spaghetti bolognese, but minified and well organized. Also, it does not spread when you are hurry.
Where can I find more information?
Jean-Jacques Dubray, InfoQ, Feb. 2016 "Why I no longer use MVC Frameworks?"
Gunar Gessner, Jean-Jacques Dubray, InfoQ, Oct. 2016 "Lessons learned in implementing the SAM pattern"
Jean-Jacques Dubray, Marcus Feitoza, Sladan Ristic "Three Approximations You Should Never Use When Coding"
Jean-Jacques Dubray "HEX: A no framework approach to building modern Web Applications"
Alexander Schepanovski, March 2015 "Boiling React Down to a Few Lines in jQuery"
Simon Friis Vindum, "Functional Front-End Architectures"
Tim Branyen, "DiffHTML"
Steven Lambert, "A proposal to improve the DOM creation API: HTML Tagged Templates"
Mike Samuel, "Using type inference to make web templates robust against XSS"
owasp.org "XSS (Cross Site Scripting) Prevention Cheat Sheet"
Google Application Security
Greg Young, "CQRS and Event Sourcing"
SmartDraw "Why we ditched AngularJS"
Hamza Houaghad "This is how we decided to do Frontend at Invisible"