Agile Front End Enterprise Architectures with React, Redux and Vanilla JS

Last month, I interviewed around 30 candidates for a Senior Front End Engineer position at Shiftgig. Since we are about to start several big projects, I was expecting to hire someone who I can rely on to help me create a solid foundation architecture that can scale. There were a few factors I was looking for: number of people working on it at the same time, complexity, and a lifetime of around three to five years. During these interviews, I’ve realized something: we (as a community) do not have a clear and common understanding of what a “big project,” or what “Front End Architecture” is really about.

It’s also good to mention that I work in a distributed team. We were looking for candidates in two different countries (US and Buenos Aires), across different cultures, and why not, different realities. After going through this interviewing process, I’ve also found that people (no matter the country) really like to talk about things including (but not limited to): Why React is really cooler than Angular and vise versa; how the diff algorithm is even way more cooler than the Angular’s digest cycle; MVC is dead in Front End; Angular 2…4…#n is super performance; and my favorite Javascript Fatigue and how we are forced to change technologies every six months (mmm yes sure). Also, if you search Front End Architecture on google, you will get a lot of posts about how to organize your folder, content for SEO, and one or two about AMD modules.

Chances are that if you have been in the industry for quite a long time, you are familiar with the following scenario: startup company starts looking for developers; company says they have to save every cent (no funding rounds have arrived yet); they like a developer, they cannot afford them, they move on; repeat last step several times. This could be called, “the MVP concept.” The company justifies that maybe they do not need a “really good” developer for now. Then, the company gets funding, needs new (complicated) features, technical debt shows up. Often, the startup can deal with it for a few months, but eventually, the front end app needs to be rebuilt, and often by this point, the developer(s) have left the job.

Let’s add some context

I really love the concept of Technical Debt, and after more than 10 years in the industry I can define it as:

When a person or a group of people make a bad decision and someone else has to pay for it. Basically, it’s like the Government.

Sometime this person is a financial person (because they understand the technical debt very well), but most of times, we are these people: the developers, the software engineers, UI architects, you name it. We are the people who rush to start a project using Angular, React, Rxjs or whatever top of the line new library that recruiters ask about. Cory House once defined us as CV Driven Architects — and yes, this dude is right.

Last year, I was invited to Minneapolis to a Front End Masters workshop. I had the chance the meet really cool and smart people, and I remember someone asked the Kyle Simpson what his opinion was about new technologies like Angular 2, React, and others. His answer was something like, “we are developing tools to solve problems what we don’t have,” end of conversation. I took the chance to make sense of this statement based on my reality, and it helped me figure out a solution.

I got inspiration for several sources, one is Minko Gechev (@mgechev), who made a presentation about how to scale SPA (Single Page Applications). His solution was really cool, however, I don’t like to couple the Angular framework again. The main problem I had to solve is how to create something that does not depend of any framework. Martin Fowler was another key point of inspiration, his book Patterns of Enterprise Application Architecture, is a first class citizen in my library, and help me solve a lot of the problems I’ve found when creating architectural patterns. Last but not least, one of my professors in my Software Engineering career, Diego Fontdevila, and his paper, Software Architecture in the Agile Life Cycle, gave me the enterprise touch for this.

Disclaimer.

This solution works well for us, but I cannot tell if it will be a good fit for you. We made a list of several problems we wanted to solve, and came up with this — but it’s not a magic wand — a good foundation.

This is an abstract, and recommends how a combination of patterns placed together, with clear definition of what an “Object Service” and an “Object Value” are, and how they can work together to provide a solid architecture for a SPA.

Architecture and Business

Architecture is all about Quality Attributes, and trade-off. This is not something that you can read on a blog post, or mimic the solution the way someone else did. We really need to involved the business — and each vertical is different. Based on this, you can select the stack of technology that you can use.

At Shiftgig, we have found that we really need to pay attention to these:

  • Maintainability: The SPA is the core of the company, thousands of users interact with this application on daily basis. The solution needs to consider a lifetime of three to five years, as well as a lot of developers working on the same codebase.
  • Testability: The Application is big in complexity, and number of features. Ensure the solution can grow without breaking previous functionality.
  • Time to Market: Yes, there are other companies doing things similar to us; we need to deliver features quickly, while keeping the quality in order to compete.

These three are very generic, but important, things to keep in mind. A lot of people also mention Performance. In my experience, however, performance in Front End works in a very different way, and unless you work with complex animation, or super complex graphs, I don’t consider this something that makes me choose between one technology or another. Performance should be about how well you can be aware of profiling, and optimizing your asynchronous operations.

That said, a very minimalist — but solid architecture — can looks like this:

Basic Architecture Diagram.

Let’s go piece by piece.

User Interface

One of the problem we have previously had with other solution like Angular was the fact they are opinionated frameworks, it works really good but it’s really easy to use it bad and a after a few months you can easily end up coupled and you can easily need to re-build the whole app at some point if you want to escalating.

Notice I won’t talk anything about performance, I don’t really consider you should changes from a library/framework to another just because the community say the performs in better, chances are, the problem it’s not performance but we made a lot of bad decisions when coding unleash of course a few cases when really you can blame the digest cycle for all you misery (I really want to hear about that).

Great! after a few rounds of iteration, Angular # was out o the equation you because I need to have control over how to interact with my architecture and even when there are a lot of smart people behind of it, it doesn’t feel good for me anymore, again, IMHO.

Vue, Inferno, React, Preact… what I really like about them was the fact that they do one thing, UI Components. This will server the purpose of not violating The Demeter Law anymore. I ended up with React, not because I think it’s cooler but because the fact that on an enterprise world I really need to trust on a solution it’s really well supported by the community and the fact that Facebook team has almost all their app is running of it gave me a big level of trust that will have support for a few more years.

Said this I chose React because:

  • Really cool developer experience / ecosystem.
  • It fits the purpose of not Violating the Demeter Law.
  • I can just “plug and play” it without braking my all whole application core.

All the component will use it owns state and will pass processed information to the store, this means, I totally support he use of setState in fact, we use it a lot, and each component will be responsible to keep it owns state until an action is trigger to pass the information to the model. Also, it will use mapStateToProps to keep in sync with the store.

A regular container can looks like:

import {Orders} from 'models'
import {ListOrdersComponent} from 'components'
function setOrderInfo (props, state) {
return {
some: props['some'],
info: props['info'],
here: props['here'],
// ... #props
}
}
const Order = React.createClass({
// info omited to convience
getInitialState () {
return {
// all order information...
some: null,
info: null,
here: null,
}
},
componentDidMount () {
this.setState(setOrderInfo)
},z create (order) {
/* Not necessary an use cause, created for sample purposes only */
let temp = Object.asign({}, order, {
some: this.state.some,
})
this.model.create(temp)
},
render () {
return (
<ListOrdersComponent
model={orders}
info={this.state}
orders={this.props.orders}
create={this.create}
/>
)
},
})
const mapStateToProps = ({orders}) => {
return {
orders,
}
}

We also separate all the Containers for Presentational components and pass the props down unless it really need to keep a own state, we have a lot of case for this. Also, in case we need to load information from different sources, we can play around with the React lifecycle hooks, specially with componentWillReceiveProps , I will talk later about how we manage different sources of async data.

Notice how I used the create method to trigger the action, React component should never use mapDispatchToProps because this creates a dependency of Redux, that is task will be handle by the models. I support the use of mapStateToProps because it has really good checking methods that can easily tell if we should or not update the component, I rather to take advantage of this than having to create it myself, the goal is not set communist architecture, but setting rules that will make us good.

In Summary:

  • React components will hold its own state for UI iterations like Modals, UI Events, Forms among others.
  • Components will read information directly of the store through Containers via mapStateToProps.
  • Components will never directly update the state of the store, they will delegate what task to the Models which can be passed through the Container via props.

Models

The Models are the only piece of the architecture that understand the UI, in fact, not always we receive all the information ready to be processed, we need to shape it in that is easy to manage into the React Components, and also re-shape it before we put it back to the state, this Model in combination with an Data Service are the responsible for this.

All Models extend from a Model class which expose an asyncOperation method, which it’s only function is to orchestrate all the computed operations required when an action it’s trigger, I took this idea from Minko’s proposal.

class Model {
constructor (store = {}, services = []) {
this.services = services
this._store = store
}
asyncOperation (action, ...rest) {
return this.services.map((service) => {
if (service[action]) {
return service[action](this._store, ...rest)
}
})
}
}

In combination with a strong naming convention for out actions methods works really great decoupling the services from the implementation, allowing use to isolate each execution, let’s see an example:

import {Model, Data} from 'models'
import store from 'pathToReduxStore'
import {firebaseService, pouchDBService, mixpanelService} from 'services'
/* services are also singletons */
class Orders extend Model {
constructor (reduxStore, Data, ...services) {
super(reduxStore, services)
this._store = reduxStore
}

create (order) {
let normalized = Data.normalizeToStore(order)
/* we can pass as much arguments as needed for each services, if the service it's not expecting a argument, it will just ignore it */
this.asyncOperation('create', normalized)
}

}
/* This export a singleton */
export let orders = new Orders(store, Data, firebaseService, pouchDBService, mixpanelService)

This can be a little confuse so let’s explain what it’s going on here; on my requirements I need to be able to:

  • Save the Order to pouchDB.
  • Trigger and action to Firebase to push a notification.
  • Log the event on mix panel.
  • Make a optimistic update to refresh the UI.

I’ve inject all the dependencies to the Orders model, this way we don’t brake any SOLID principle and also it’s was to test because we can mock all the dependencies for convenience. I’m exposing a singleton because I don’t need any other instance of the model in this case. I’m using all the Object Services needed to perform the actioned to satisfy the requirements, I provide the object value as we as the store in case the need it.

The Data layer only expose static methods which take case of happing the data to be handle by the UI and the way back to the Store.

Main idea is that the create method normalizes the data and trigger an create event on each of service, which means, executer the create method of each. Again, this is only possible using a solid naming convention. I case the service doesn’t have a method called like the action, it will just ignore it. The implementation of this methods will be explained on the Services section.

In Summary:

  • Models inherited a method called asyncOperation which is responsible to orchestrate the actions to executed when there is a UI Input and/or call to action.
  • Models expose the redux store to the services to used when needed and also all the information required for the service to execute its operations.
  • We use Data layer when needed which is the one to shape the data in a way that is understandable to the UI and the store.
  • The Model is the only one who understand the UI Component.
  • This is a key layer for the business logic.

Services

This Layer task is to execute all the operations necessary by the business in order to achieve a goal. They are just Service Objects which encapsulate all the logic to set between the app state, the back end and the UI.

Let’s see some samples:

class FirebaseService () {
/* info omitted for convinience */
_sendPushNotification (id, order) { /*...*/ }
create (order) {
let {id} = order
this.__sendPushNotification(id, order)
}
}
class PouchDB () {
/* info omitted for convinience */
create (store, order) {
/* triggers and action */
store.dispatch({
type: 'ORDER_CREATE',
payload: order,
})
}
}
class MixPanelService () {
/* info omitted for convinience */
create () { /* logs to the create event on mixpanel */ }
}
/* they all are singletons */
export let firebaseService = new FirebaseService()
export let pouchDBService = new PouchDB()
export let mixpanelService = new MixPanelService()

We have 3 different services in this case, they work independently, they just response to the asyncOpertion call of action executing their respective comments. That’s basically it, a single way to abstract all the service logic into a Object Service.

Notice that we are returning a single instance, this is the only entry point we serve to the app, a singleton for each unless we need several instance of this, this is not the case.

Important to note is how different the implementation of each service can be, we can easily isolate each implementation and don’t mix any complexity respecting the Open/Closed principle.

Finally, the PouchDB’ create method, only triggers and action, this means, it requires several operations to be executed, we will talk more about this on the Async Operation layer.

In Summary

  • We isolate each service complexity into its own implementation.
  • We can expose a single point of entry create and use several private class to orchestrate an operation.
  • The services and the models are not coupled, we respect the Open/Closed principle.
  • We only talk with the closest friend, we don’t ask, only tell. ( Law of Demeter — Wikipedia

Async Operations

There are several ways to manage asynchronous operations, Promises, Callbacks, Observables among others. When designing this I needed a way to manage several operations at the same time, as well as, execute one after other and in this process do not force the user to wait a lot until the operations are finished to release the UI.

Depends of you Use Cases and the Business, you can find a lot value using Observables, or maybe if it’s not complicated a single Promise/Callbacks operations are ok. In my case, I needed something more robust, my business logic is complex and we depends on a lot of services to be notified when some actions happens.

Finally, I needed a solution that fit not only for the purpose but for the use, something that is easy to read and do not take a lot of time to learn, said this, Rxjs was out of the equation, I needed the whole team to learn this new technology and time market is this case is more important.

Again, It’s all about trade-off and quality attributes. After reviewing several patterns, I ended up using Redux-Saga. I like the way how it implemented the functions generators and make some complexes async calls to looks ver synchronous.

Let’s see an use case for the sample:

  • The order will be send to a pouchDb and return a uniqued id.
  • This ID will be send to the Back end API.
  • Once an order is place, we need to notify all the market place that an order is up (3 different calls).
  • Use the their 3 markets posts id to send an email to the Account Manager notifying about this event.

This is a common use case in a large and complex application, our main goal as Front-end engineers is to deliver this operation successfully as well as provide a smooth user experience, I mean, we cannot just make the user pay the price for all out operations, right? this is when optimistic updates and redux-saga works really well together.

Our saga for this case can looks like:

import {marketplace} from 'gateways'
import {take, put, call} from 'redux-saga/effects'
import {backendService, notificationService} from 'services'
function* createOrder () {
try {
/* Send Order to PouchDb and get the ID */
const {payload} = yield take('ORDER_CREATE')

/* Send the ID to the Backend API */
const order = yield call(backendService.create, payload)

/* Release the UI, the user doesn't care about the rest*/
yield put({
type: 'ORDER_CREATE_COMPLETED',
payload: order
})

/* Notify the market places */
const [m1, m2, m3] = yield [call(marketplace, 'marketOne', payload), call(marketplace.exec, 'marketTwo', payload), call(marketplace.exec, 'marketThree', payload, order)]

/* Hit the Notification service to send emails... */
yield call(notificationService.sendEmail, m1, m2, m3)
yield put({type: 'ORDER_CREATE_NOFITICATIONS_COMPLETED'})

} catch (e) {
yield put({type: 'ERROR_CREATE_ORDER', payload: e})
}
}

As you can see, this is really easy to read, to maintain, to implement and to test. I won’t spend time explaining how Redux-Saga works, however, if you are interested I wrote a post about it, show me some love and take a look a it. (Async operations using redux-saga — freeCodeCamp.

A service and be really easy and really complex an the same time, but, does the user need to pay the price for this? Nope.

Notice that marketThree receive two arguments instead of one even if they appear to be called the same method, we’ll see more about this in the Gateway section.

In Summary:

  • I use redux saga to manage my async operations cause it fits all my use case very smoothly.
  • We never let the user pay the price for our business rules, we need to provide a really cool user experience, again, this is really easy to achieve using sagas/generators.
  • We need to put some time planning how the async calls can play well and do not block user operations.

Gateways

Gateways is the layer that understand the backend, this can be as complex as the business logic want to be, since, we can work with several backend teams, I needed a way to decouple this implementation and expose a single point of entry and also give me the flexibility to modify the implementation as needed. This is where the ( Command pattern — Wikipedia fit like a great candidate.

This objects extends an exec method, this is only point of the entry to the gateway, even we you can call each method as separate, I highly recommend to enforce the convention, this way, you can encapsulate all the implementation hiding it as an event, let’s see a sample:

class Gateway {
exec (command, ...rest) {
if (this[command]) {
return this[command].apply(this, rest)
}
throw Error(`${this.constructor.name} does not have a method called ${command}.`)
}
}

Just like a that, this allow us to implement the gateways like marketplace.exec(‘marketOne’) where marketOne is the name f the object as pass it as must arguments as needed.

class marketPlace extends Gateway {
/* info omitted for convinience */
marketOne (id) { /* ... */}
marketTwo (id) { /* ... */}
marketThree (id, order) { /* ... */}
}

Implementation is totally decoupled and you can pass all the arguments you need to exec , the command will take only those it cares about and ignore the rest. This allow of to do really cool tricks like this:

class marketPlace extends Gateway {
/* info omitted for convinience */
audit = {}

marketOne (id) { /* ... */}
marketTwo (id) { /* ... */}
marketThree (id, order) { /* ... */}
exec (command, ...rest) {
if (this.audit[command]) {
this.audit[command].push(rest)
} else {
this.audit[command] = [rest]
}
Gateway.prototype.exec(command, rest)
}
}

In this particularly sample, I’m saving all the operations send to the gateway in case I need to re-send them if the XHR call fails, send logs to logging service, etc, all this just decorating the point of entry, pretty neat eh?

In Summary:

  • Gateways is the layer that knows the Backend.
  • This is the only place where XHR operations are done.
  • We take advantage of the Command Patter to expose a single point of entry.

State

I won’t spend to much time on this section, nowadays, Redux is really popular around and you can easily find resources about how to use it, however, I keep a few rules with the team that allows to work really well, let’s see a few of team:

  • The store should be a pristine as possible, not UI interaction will be manage in the store.
  • Data needs to be normalized.
  • Most of reducers will be associated with a Model, this allow us to measure the level of coverage per feature (we have a testing plan in place).
  • There is a layer in between the Store and the UI, the Data Service due to a normalization. React component won’t never NEVER prepare data to be handle. Remember… ask, don’t tell.
  • Services and Async Layers are the only place to trigger actions.
  • Small piece of deleatable code can increase your team velocity.
  • Making this decision at first will let you to sleep better.

Lessons learned

At first it can be like too much boilerplate up front and I’m 100% agree with this, however, the real value is how easy is to plan a migration to others UI Technologies, yes… React won’t last forever, or at least the way it is right now, at some point we will need to support other devices, VR, etc, and technology evolves really quickly, but we can do it as smoothly as possible but also there a few other benefit of this architecture:

  • New team members can become productive really quickly working on the the UI layer.
  • You can quickly isolate your issues, this way you know the boundaries by layer, we spend a really short amount of time solving issues.
  • New features are really easy to implement, we can isolate any new feature and control the iteration between other parts of the application.
  • As you can see, most of the architecture is pure Vanilla JS, so the re-use is really easy.
  • Quality attributes are very well defined by technologies ex: Lazy loading with React Router, Testability with Layers model, etc.

Conclusion

Front end application development has evolved a lot in the last years, SPA are first class citizens on any company and the first point of entry for current and potentials clients; talking about FE architecture needs to be a easy topic for any Senior Developer, it’s not about how well you manage a technology, we need to include the business and think in terms of how expensive is keep a technical debt all around, that’s our job.

We need to able to response to changes in the economy and the market, this is why software architecture is important, Backend engineers have known this from a lot of time, now it’s time for us to get into the game, if we don’t keep good architecture, if we don’t put an effort on the internal quality, we are in the end deceiving that customers of in fact stealing from out customers because we’re slowing down their ability to compete.

Again, this worked for us at Shiftgig ☺️ it doesn’t mean it will work for you but I’m really interested in having conversation about how you guys do to manage your architectural decisions.

I do serverless stuff 🤷🏻‍♂️

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store