Why I No Longer Use MVC Frameworks

The worst part of my job these days is designing APIs for front-end developers. The conversation goes inevitably as:

Dev – So, this screen has data element x,y,z… could you please create an API with the response format {x: , y:, z: }

Me – Ok

I don’t even argue anymore. Projects end up with a gazillion APIs tied to screens that change often, which, by “design” require changes in the API and before you know it, you end up with lots of APIs and for each API many form factors and platform variants. Sam Newman has even started the process of institutionalizing that approach with the BFF pattern that suggests that it’s ok to develop specific APIs per type of device, platform and of course versions of your app. Daniel Jacobson explains that Netflix has been cornered to use a new qualifier for its “Experience APIs”: ephemeral. Sigh…

A couple of months ago, I started a journey to understand why we ended up here and what could be done about it, a journey that lead me to question the strongest dogma in application architecture, MVC, and where I touched the sheer power of reactive and functional programming, a journey focused on simplicity and scraping the bloat that our industry is so good at producing. I believe you might be interested in my findings.

The pattern behind every screen we use is MVC –Model-View-Controller. MVC was invented when there was no Web and software architectures were, at best, thick clients talking directly to a single database on primitive networks. And yet, decades later, MVC is still used, unabated, for building OmniChannel applications.

With the imminent release of Angular2, it might be a good time to re-evaluate the use of the MVC pattern and therefore the value MVC Frameworks bring to Application Architecture.

I first came across MVC in 1990 after NeXT released Interface Builder (It’s amazing to think that this piece of software is still relevant today). At the time, Interface Builder and MVC felt like a major step forward. In the late 90s the MVC pattern was adapted to work over HTTP (remember Struts?) and today MVC is, for all intents and purposes, the keystone of any application architecture.

Even React.js had to use an euphemism when they introduced a framework that, for once, seemed to depart significantly from the MVC dogma: “React is just the View in MVC”.

When I started to use React last year, I felt that there was something very different about it: you change a piece of data somewhere and, in an instant, without an explicit interaction between the view and the model, the entire UI changes (not just the values in fields and tables). That being said, I was just as quickly disappointed by React’s programming model, and apparently I was not alone. I share Andre Medeiros’ opinion:

React turned out to disappoint me in multiple ways, mainly through a poorly designed API which induces the programmer […] to mix multiple concerns in one component. 

As a server-side API designer, I came to the conclusion that there was no particular good way to weave API calls into a React front-end, precisely because React focuses just on the view and has no controller in its programming model whatsoever.

Facebook, so far, has resisted fixing that gap at the framework level. The React team first introduced the Flux pattern, which was equally disappointing and these days, Dan Abramov promotes another pattern, Redux, which goes somewhat in the right direction but does not offer the proper factoring to connect APIs to the front-end as I will show below.

You would think that between GWT, Android SDK and Angular, Google engineers would have a strong view (pun intended) as to what could be the best Front-End Architecture but when you read some of the design considerations of Angular2, you don’t necessarily get this warm feeling that, even at Google, people know what they are doing:

Angular 1 wasn’t built around the concept of components. Instead, we’d attach controllers to various [elements] of the page with our custom logic. Scopes would be attached or flow through, based on how our custom directives encapsulated themselves (isolate scope, anyone?).

Does a component-based Angular2 look a lot simpler? Not quite. The core package of Angular 2 alone has 180 semantics, and the entire framework comes close to a cool 500 semantics, and that’s, on top of HTML5 and CSS3. Who has time to learn and master that kind of framework to build a Web app? What happens when Angular3 comes around?

After using React and seeing what was coming in Angular2, I felt depressed: these frameworks systematically force me to use the BFF “Screen Scraping” pattern where every server-side API matches the dataset of a screen, in and out.

That’s when I had my “to hell with it” moment. I’ll just build a Web app without React, without Angular, no MVC framework whatsoever, to see if I could find a better articulation between the View and the underlying APIs.

What I really liked about React was the relationship between the model and the view. The fact that React is not template based and that the view itself has no way to request data felt like a reasonable path to explore (you can only to pass data to the view).

When you look long enough, you realize that the sole purpose of React is to decompose the view in a series of (pure) functions and the JSX syntax:

             

is nothing different than:

              V = f( M )

For instance, the Website of one of the projects I am working on right now, Gliiph, is built with such a function:

(Click on the image to enlarge it)

fig 1. The function responsible for generating the HTML of the site’s Slider component

That function is fed from the model:

(Click on the image to enlarge it)

fig 2. The mode behind the sliders

When you realize that a plain old JavaScript function can do the job just fine, then your next question is why you would use React at all?

The virtual-dom? If you feel like you need one (and I am not sure many people do), there are options and I expect more will be developed.

GraphQL? Not really. Don’t be fooled by the argument that if Facebook uses it profusely, it must be good for you. GraphQL is nothing more than a declarative way to create a view-model. Being forced to shape the model to match the view is the problem, not the solution. How could the React (as in reactive) team possibly think it’s ok to request data with “Client-specified queries”:

GraphQL is unapologetically driven by the requirements of views and the front-end engineers that write them. […] A GraphQL query, on the other hand, returns exactly what a client asks for and no more.

What the GraphQL team seems to have missed is that behind the JSX syntax, the subtle change is that functions isolate the model from the view. Unlike templates or “queries written by front-end engineers”, functions do not require the model to fit the view.

When the view is created from a function (as opposed to a template or a query) you can transform the model as needed to best represent the view without adding artificial constraints on the shape of the model.

For instance, if the view displays a value v and a graphical indicator as to whether this value is great, good or bad, there is no reason to have the indicator’s value in your model: the function should simply compute the value of the indicator from the value v provided by the model.

Now, it’s not a great idea to directly embed these computations in the the view, but it is not difficult to make the view-model a pure function as well, and hence, there is no particular good reason to use GraphQL when you need an explicit view-model:

                V = f( vm(M) )

As a veteran MDE practitioner, I can assure you that you are infinitely better off writing code than metadata, be it as a template or a complex query language like GraphQL.

This functional approach has several key benefits. First, just like React, it allows you to decompose your views into components. The natural interface they create allows you to “theme” your Web app or Website or render the view in different technologies (native for instance). The function implementations have the potential to enhance the way we implement responsive design as well.

I would not be surprised, for instance, if in the next few months, people start delivering HTML5 themes as component-based JavaScript functions. These days, that’s how I do all my Website projects, I pick up a template and immediately wrap it in JavaScript functions. I no longer use WordPress. I can get the best of HTML5 and CSS3 with pretty much the same level of effort (or less).

This approach also calls for a new kind of relationship between designers and developers. Anyone can write these JavaScript functions, especially the template designers. There is no “binding” syntax to learn, no JSX, no Angular template, just plain old JavaScript function.

Interestingly, from a reactive flow perspective, these functions can be deployed where it makes the most sense: on the server or on the client.

But most importantly, this approach allows the view to declare the minimum contract with the model and leaves the decision to the model as to what it the best way to bring this data to the view. Aspects like caching, lazy loading, orchestration, consistency are entirely under the control of the model. Unlike templates or GraphQL there is never a need to serve a direct request crafted from the view perspective.

Now that we have a way to decouple the model from the view, the next question is: how do you create a full application model from here? what would a “controller” look like? To answer that question, let’s go back to MVC.

Apple knows a thing or two about MVC since they “stole” the pattern from Xerox PARC in the early 80s and they have implemented it religiously since:

fig.3. the MVC Pattern

The core issue here is, as Andre Medeiros so eloquently puts it, that the MVC pattern is “interactive” (as opposed to Reactive). In traditional MVC, the action (controller) would call an update method on the model and upon success (or error) decide how to update the view. As he points out, it does not have to be that way, there is another equally valid, Reactive, path if you consider that actions should merely pass values to the model, regardless of the outcome, rather than deciding how the model should be updated.

The key question then becomes: how do you integrate actions in the reactive flow? If you want to understand a thing or two about Actions, you may want to take a look at TLA+. TLA stands for “Temporal Logic of Actions”, a formalism invented by Dr. Lamport, who got a Turing award for it. In TLA+, actions are pure functions:

             data’ = A (data)

I really like the TLA+ prime notation because it reinforces the fact that functions are mere transformations on a given data set.

With that in mind, a reactive MVC would probably look like:

             V = f( M.present( A(data) ) ) 

This expression stipulates that when an action is triggered, it computes a data set from a set of inputs (such as user inputs), that is presented to the model, which then decides whether and how to update itself. Once the update is complete, the view is rendered from the new model state. The reactive loop is closed. The way the model persists and retrieves its data is irrelevant to the reactive flow, and should certainly never, absolutely never, be “written by front-end engineers”. No apologies.

Actions, again, are pure functions, with no state and no side effect (with respect to the model, not counting logging for instance).

A Reactive MVC pattern is interesting because, except for the model (of course), everything else is a pure function. In all fairness, Redux implements that particular pattern, but with the unnecessary ceremony of React and a tiny bit of coupling between the model and the actions in the reducer. The interface between the actions and the model is pure message passing.

That being said, the Reactive MVC pattern, as it stands, is incomplete, it does not scale to real-world applications as Dan likes to say. Let’s take a simple example to illustrate why.

Let’s say we need to implement an application that controls a rocket launcher: once we start the countdown, the system will decrement the counter and when it reaches zero, pending all properties of the model being at nominal values, the launch of the rocket will be initiated.

This application has a simple state machine:

fig.4. the Rocket Launcher state machine

Both decrement and launch are “automatic” actions, it means that each time we enter (or re-enter) the counting state, the transition guards will be evaluated and if the counter value is greater than zero, the decrement action will be called upon and when the value is zero, the launch action will be called instead. An abort action can be undertaken at any point, which will transition the control system to the aborted state.

In MVC, that kind of logic would be implemented in the controller, perhaps triggered by a timer in the view.

This paragraph is very important, so please read carefully. We have seen that, in TLA+, the actions have no side effects and the resulting state is computed, once the model processed the action outputs and updated itself. That is a fundamental departure from the traditional state-machine semantics where the action specifies the resulting state, i.e. the resulting state is independent of the model. In TLA+, the actions that are enabled and therefore available to be triggered in the state representation (i.e. the view) are not linked directly to the action that triggered the state change. In other words, state machines should not be specified as tuples that connect two states (S1, A, S2) as they traditionally are, they are rather tuples of the form (Sk, Ak1, Ak2,…) that specify all the actions enabled, given a state Sk, with the resulting state being computed after an action has been applied to the system, and the model has processed the updates.

TLA+ semantics provides a superior way to conceptualize a system when you introduce a “state” object, separate from the actions and the view (which is merely a state representation).

The model in our example is as follows:

model = {

counter:  ,

started:  ,

aborted:  ,

launched:   }

The four (control) states of the system are associated to the following values of the model

            ready = {counter: 10, started: false, aborted: false, launched: false }

            counting = {counter: [0..10], started: true, aborted: false, launched: false }

            launched = {counter: 0, started: true, aborted: false, launched: true}

            aborted = {counter: [0..10], started: true, aborted: true, launched: false}

The model is specified by all the properties of the system and their potential values, while the state specifies the actions that are enabled, given a set of values. That kind of business logic must be implemented somewhere. We cannot expect the user could be trusted to know which actions are possible or not. There is simply no other way around it. Yet, that kind of business logic is difficult to write, debug and maintain, especially when you have no semantics available to describe it, such as in MVC.

Let’s write some code for our rocket launcher example. From a TLA+ perspective, the next-action predicate logically follows the rendering of the state. Once the current state has been represented, the next step is to execute the next-action predicate, which computes and executes the next action, if any, which in turn will present its data to the model which will initiate the rendering of a new state representation, and so on.

(Click on the image to enlarge it)

fig.5. the rocket launcher implementation

Note that in a client/server architecture we would need to use a protocol like WebSocket (or polling when WebSocket is not available) to render the state representation properly after an automatic action is triggered,

I have written a very thin, open source, library in Java and JavaScript that structures the state object with proper TLA+ semantics and provided samples that are using WebSocket, Polling and Queuing to implement the browser/server interactions. As you can see in the rocket launcher example, you should not feel obligated to use that library. The state implementation is relatively easy to code once you understand how to write it.

I believe that we have now all the elements to formally introduce a new pattern, as an alternative to MVC, the SAM pattern (State-Action-Model), a reactive, functional, pattern with its roots in React.js and TLA+.

The SAM pattern can be represented by the following expression:

         V = S( vm( M.present( A(data) ) ), nap(M))

which stipulates that the view V of a system can be computed, after an action A has been applied, as a pure function of the model.

In SAM, A (actions), vm (view-model), nap (next-action predicate) and S (state representation) are and must all be pure functions. With SAM, what we commonly call the “state” (the values of the properties of the system) is entirely confined to the model and the logic that changes these values is not visible outside the model itself.

As a side note, the next-action predicate, nap() is a call-back invoked once the state representation has been created, and, on its way to be rendered to the user.

fig.6. the State-Action-Mode (SAM) Pattern

The pattern itself is independent of any protocol (and can be implemented without difficulty over HTTP) and any client/server topology.

SAM does not imply that you always have to use the semantics of a state machine to derive the content of the view. When actions are solely triggered from the view, the next-action predicate is a null function. It might be a good practice, though, to clearly surface the control states of the underlying state machine because the view might look different from one (control) state to another.

On the other hand, if your state machine involves automatic actions, neither your actions nor your model would be pure without a next-action predicate: either some actions will have to become stateful or the model will have to trigger actions which is not its role. Incidentally, and unintuitively, the state object does not hold any “state”, it is again a pure function which renders the view and computes the next-action predicate, both from the model property values.

The key benefit of this new pattern is that it clearly separates the CRUD operations from the Actions. The Model is responsible for its persistence which will be implemented with CRUD operations, not accessible from the view. In particular, the view will never be in the position to “fetch” data, the only things the view can do are to request the current state representation of the system and initiate a reactive flow by triggering actions.

Actions merely represent an authorized conduit to propose changes to the model. They, themselves, have no side effect (on the model). When necessary, actions may invoke 3rd party APIs (again, with no side effect to the model), for instance, a change of address action would want to call an address validation service and present to the model the address returned by that service.

This is how a “Change of Address” action, calling an address validation API would be implemented:

(Click on the image to enlarge it)

fig.7. the “Change of Address” implementation

The elements of the pattern, actions and models, can be composed liberally:

Function Composition

data’ = A(B(data))

Peer Composition (same data set presented to two models)

M1.present(data’)

M2.present(data’)

Parent-Child Composition (parent model controls data set presented to the child)

M1.present(data’,M2)

function present(data, child) {

            // perform updates

            …

            // synch models

            child.present(c(data))

}

Publish/Subscribe Composition

M1.on(“topic”, present )

M2.on(“topic”, present )

Or

M1.on(“data”, present )

M2.on(“data”, present )

For architects who thinks in terms of Systems or Record and Systems of Engagement, the pattern helps clarify the interface between these two layers (fig. 8) with the model being responsible all interactions with the systems of record.

fig 8. SAM Composition model

The entire pattern itself is composable and you could implement a SAM instance running in the browser to support a wizard-like behavior (e.g. a ToDo application) interacting with a SAM instance on the server:

fig. 9 SAM instance composition

Please note that the inner SAM instance is delivered as part of the state representation generated by the outer instance.

Session rehydration should occur prior to triggering the action (fig. 10). SAM enables an interesting composition, where the view could call a third party action providing a token and a call back pointing to a system action that will authorize and validate the call, before presenting the data to the model.

fig. 10 Session Management with SAM

From a CQRS perspective, the pattern does not make a particular distinction between Queries and Commands, though the underlying implementation needs to make that distinction. A search or query “action” is simply passing a set of parameters to the model. We can adopt a convention (e.g. the underscore prefix) to differentiate queries from commands, or we could use two distinct present methods on the model:

 { _name : ‘/^[a]$/i’ } // Names that start with A or a
 { _customerId: ‘123’ } // customer with id = 123

The model would perform the necessary operations to match the query, update its content and trigger the rendering of the view. A similar set of conventions could be used for creating, updating or deleting elements of the model. There a number of styles which can be implemented to pass the action outputs to the model (data set, events, actions…). There are pros and cons to each approach and in the end it might come to preferences. I favor the data set approach.

From an exception perspective, just like in React, it is expected that the model will hold the corresponding exception as property values (either presented by the action, or returned by a CRUD operation). These property values will be used while rendering the state representation to display the exception.

From a caching perspective, SAM offers a caching option at the state representation level. Intuitively, caching the results of these state representation functions should lead to a higher hit rate since we are now triggering the cache at the component/state level rather than the action/response level.

The reactive and functional structure of the pattern makes replay and unit testing a breeze.

The SAM pattern changes completely the paradigm of front-end architectures because, on the foundation of TLA+, the business logic can be clearly delineated into:

  • Actions as pure functions
  • CRUD operations in the model
  • States which control automatic Actions

From my perspective as an API designer, the pattern pushes the responsibility of the design of APIs back to the server, with the smallest contract possible between the view and the model.

Actions, as pure functions, can be reused across models as long as a model accepts the corresponding output of the action. We can expect that libraries of actions, themes (state representations), and possibly models will flourish since they now be independently composed.

With SAM, microservices fit naturally behind the model. Frameworks like Hivepod.io can be plugged in, pretty much as-is, at that level.

Most importantly the pattern, like React, does not require any data binding or template.

Over time I expect that SAM will contribute to make the virtual-dom a permanent feature of the browser and new state representations will be directly processed via a dedicated API.

I found this journey to be transformative: decades of Object Orientation seem to be all but gone. I can no longer think in terms other than reactive or functional. The kinds of things I have been building with SAM and the speed at which I can build them has been are unprecedented. One more thing. I can now focus on designing APIs and Services that do not follow the screen scraping pattern.

I wanted to thank and acknowledge the people who kindly accepted to review this article: Prof. Jean Bezivin, Prof. Joëlle Coutaz, Braulio Diez, Adron Hall, Edwin Khodabackchian, Guillaume Laforge, Pedro Molina, Arnon Rotem-Gal-Oz.

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.


Original URL: http://feedproxy.google.com/~r/feedsapi/BwPx/~3/u8DZoE2Rh6Y/no-more-mvc-frameworks

Original article

Comments are closed.

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑

%d bloggers like this: