A Better React/Rails Architecture
A common refrain among us developers is that we spend a fantastic amount of our day sitting in front of the computer screen quietly, with our heads full of internal state. Here on the Styling team at Stitch Fix, we are lessening the amount of state a developer needs to have in their head at any given time by moving towards an application architecture that makes use of microservices and micro front ends, connected by a central API.
As our applications grow and become more complex, it gets harder and harder to keep track of all of the mental threads required to solve the problems at hand. Just as our mental load grows, so does the heaviness of our development environment. Writing our tests becomes harder, and running them is potentially slower, too. CI and deploy times creep up. If multiple teams are involved in building a single application, our monolith runs the risk of becoming even more intimidating to new contributors, and we have to lean heavily on human communication to fill in the gaps.
We recognize that microservices are not a panacea. This is not a blog post about how everyone should move to microservices. Our motivations are as much organizational as they are technical. We hope that by illustrating our past and future architecture, you will be able to use the pieces of our approach that are relevant to your own systems.
Our Previous Approach
Our specific setup consists of front end web applications with corresponding Rails back ends. This affords us many benefits! But it also poses some unique challenges. It can be particularly tempting to diverge from React’s single page application paradigm when we are working with a back end framework that defaults to serving up disparate views.
Our previous architecture consisted of a mix of Rails views, and large, full-page React components. We had some residual Rails views that served up HTML and vanilla JavaScript. These views were a holdover from before we adopted React (the Cretaceous era, if you’re a React developer). As soon as the React components were served up, they functioned as their own discrete applications, making basic API calls to their connecting Rails layer.
Diverging from an accepted pattern means… you guessed it! More domain knowledge that needs to be passed around, and more to keep in mind as you program.
Of course, we ran into the issues mentioned in the previous section. We recently onboarded a new developer onto our team. As we got her development environment up and running, we confronted some of the challenges of getting set up with a monolith head-on. Errant dependencies required domain knowledge to track down. In order to get one thing working, everything had to work. And there was a lot of everything! Our new architecture aims to help some of these roadblocks fade into the past.
We did have a stepping-stone to our new architecture, though: a handful of microservices that allowed us to talk to other services within the organization. We are building upon this as we transition to our new approach.
Where We Are Headed
Our Back End
On the back end side of things, we are starting to make broader use of microservices. We now use them for both application- and organization-level needs. For example, we are moving towards having company-wide merchandising and warehouse services. Our team doesn’t own these, which gives us more room to be the experts in our domain. We have plans to begin to transition towards a large, federated graph that many teams across the engineering organization will also use.
As far as our React/Rails integration layer goes, Rails continues to be our centralized API, through which our front ends can access the aforementioned application data. We now use a pact to verify the contract between the front and back ends. This allows us to make sure that we are testing the seams of our newly decomposed applications. (And it means we rely less on our fallible human selves to be the keepers of our contracts!)
Our Front End
Now that our React user interfaces are decoupled from the more traditional Rails Model/View/Controller pattern, we have more freedom to give them the room to breathe as full-fledged applications.
We have made spinning up a micro front end easy, to facilitate better developer velocity. We have an agreed-upon starter app, built with Create-React-App. We are also in the process of developing a distinct, contained component library, tailored to our team’s specific expert use needs. Our new front end library utilizes automatic deployments with semantic releases— we get the benefits of the contained CI without the drawbacks of a monolith. If we find that it collectively improves our experience as developers, we hope to roll it out to some of our other existing repositories.
Another benefit we get from extracting our functional, presentation-level React components into a separate library, is that we can isolate them from our API. We now have an abstracted front end level API layer. Its sole purpose is to manage data coming in from the back end (where our pacts come into play!). This is a way to provide systematic scaffolding around domain knowledge.
The Takeaway
By modularizing our front and back ends in such a way, no one person is responsible for keeping an entire app in their head all the time. As developers, it’s important to have a strong sense of our broader architectural ecosystems. When we remove that feeling of “I need to know how allllll of this works, but just enough so that I can rescue it when it breaks”, we facilitate the mental room to iterate on our team’s systems with greater depth.