What to Keep in Mind as You Upgrade Your Legacy UI Code Bases

The constant onslaught of advances in JavaScript and its subsequent frameworks over the past few years has brought with it great improvements to the act of writing code and delighting users, but it has also brought headaches for those attempting to keep up with the fast pace of the industry and support older legacy applications.

Just seven months ago, my own team was faced with such a challenge. We needed to upgrade our large UI application, which was built in AngularJS (Angular 1), and after much deliberation and research, we chose to upgrade to React. This article is intended to serve as an example of what my team considered and eventually ended up doing in order to upgrade our application. It is by no means the only way to do it, or even the best way, but so far, it has worked for us.

My hope is that readers may find some advice here to help guide them as they approach updating or even rebuilding their own sites, with as little disruption to their users and their development teams, as possible. So without further ado, let’s get started.

Why Migrate to React?

There were a number of reasons we decided to migrate to React from AngularJS, the biggest reason being Google was going to stop supporting the version of Angular our application ran on (version 1.5) on July 1st, 2018.

Essentially, this meant, there was no migration path to the latest (supported) version of AngularJS (version 1.7), so we’d need to rewrite code, regardless of what course of action we chose.

Additionally, no more support translated to no security patches, no new features to improve the framework and no further long-term support from Google teams regarding issues or bugs.

When the end of Angular support was announced we evaluated the options and chose to migrate the application, which was about 2 years old, to React.

Why React, you ask? Because it’s hot? Because it’s the JavaScript industry darling? No, not quite. There were a few other reasons too.

We chose to migrate to React because it provided benefits for both users and developers:

For one thing, the engine powering React is fast (and getting faster), much more so than the one powering our AngularJS application. For example, just to execute JavaScript code in the browser, Angular 1 takes almost 5000ms, as compared to React, which takes under 1000ms.

Link to framework comparisons: Auth0 blog - Benchmark series

A faster framework would give our users a better experience with our application: less time waiting for elements to render in the DOM, a quicker more responsive website, and make it SEO friendlier to boot, based on quick page load time. It should be noted, React must be server-rendered for these SEO benefits to be reaped.

Since React really only delivers the View of the MVC (Model View Controller framework) model, this gave our team an opportunity to deliberately re-architect how our app was built. We chose to refactor the complicated business logic, which our AngularJS application was single-handedly juggling in the browser, to backend microservices, that more closely aligns to the 12 Factor App methodology our organization subscribes to. The twelve-factor app is a methodology for building software-as-a-service apps that: Use declarative formats for setup automation, to minimize time and cost for new developers joining the project; Have a clean contract with the underlying operating system, offering maximum portability between execution environments; Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration; Minimize divergence between development and production, enabling continuous deployment for maximum agility; And can scale up without significant changes to tooling, architecture, or development practices. The twelve-factor methodology can be applied to apps written in any programming language, and which use any combination of backing services (database, queue, memory cache, etc). For more information, see this link to the 12 Factor Methodology: https://12factor.net/ And honestly, as our application grew from its inception, the code (and logic) was becoming less and less maintainable as new features were piled on to the existing code base.

Another benefit to the developers is React is also backwards compatible with its previous versions, which would (hopefully) prevent us from facing the issue we were facing now.

React code much more closely resembles true JavaScript, excluding JSX, and relies heavily on more functional programming principles, meaning our developers would become better developers overall by being exposed to programming in the style that React champions.

Here’s an example of the code used for our app’s dashboard. The first image is our old AngularJS HTML code, the second is our React’s JavaScript / JSX code for the same page. Reply in the comments section below and tell me which code looks cleaner and easier to read to you.

AngularJS Code

<md-toolbar class="transparent-toolbar"> <div class="md-toolbar-tools"> <div class="md-display-4" id="page-header"> Dashboard </div> <span flex></span> </div> </md-toolbar> <div layout="row" layout-sm="column" class="button-row"> <div flex="33" layout-padding class="layout-padding flex-33"> <md-card class="clickableOptionCard" id="manageCard" layout-align="center center" ui-sref="manage"> <p class="fa fa-briefcase"></p> <md-toolbar class="layout-align-center-center">Add/Drop</md-toolbar> </md-card> </div> <div flex="33" layout-padding class="layout-padding flex-33"> <md-card class="clickableOptionCard" id="keyCard" layout-align="center center" ui-sref="keyManage"> <div ng-include="'dashboard/template/key.template.svg'" class="key-wrapper"></div> <md-toolbar class="layout-align-center-center">Key Management</md-toolbar> </md-card> </div> <div flex="33" layout-padding class="layout-padding flex-33"> <md-card class="clickableOptionCard" id="executeCard" layout-align="center center" onClick="window.open('execution/AMT2','_blank')"> <p class="fa fa-cube"></p> <md-toolbar class="layout-align-center-center">Execution</md-toolbar> </md-card> </div> </div> <div layout="row" layout-sm="column" class="button-row"> <div flex="33" layout-padding class="layout-padding flex-33"> <md-card class="clickableOptionCard" id="triangulationCard" layout-align="center center" ui-sref="triangulation"> <div ng-include="'dashboard/template/triangulation-icon.svg'" class="triangle-wrapper"></div> <md-toolbar class="layout-align-center-center">Triangulation Report</md-toolbar> </md-card> </div> </div>

ReactJS Code

import React, { Component } from 'react'; import { NavLink } from 'react-router-dom'; import KeysImage from 'assets/images/key.svg'; import StatusImage from 'assets/images/pro-direct-circle-outline.svg'; import ReportsImage from 'assets/images/health-check.svg'; import AMT2Image from 'assets/images/truck.svg'; import Card from 'commonComponents/Card/Card'; import './Dashboard.scss'; class Dashboard extends Component { render() { return ( <div className='headerBorder'> <hr /> <div className='dashboard'> <Card> <a href='/keys'> <span className='image-wrapper'> <img src={KeysImage} /> </span> <h2>Key Management</h2> </a> </Card> <Card> <a href='/manage/'> <span className='image-wrapper'> <i className='icon_scan' /> </span> <h2>Add/Drop</h2> </a> </Card> <Card> <NavLink to='/status/change' onClick={this.resetChangeStatusPageFlag}> <span className='image-wrapper'> <img src={StatusImage} /> </span> <h2>Store Status</h2> </NavLink> </Card> <Card> <a href='/triangulation'> <span className='image-wrapper'> <img src={ReportsImage} /> </span> <h2>Triangulation Report</h2> </a> </Card> <Card> <a href={this.state.AMT2Url} target='_blank'> <span className='image-wrapper'> <img src={AMT2Image} /> </span> <h2>AMT 2.0</h2> </a> </Card> </div> </div> ); } } }; } export default Dashboard;

And finally, React is backed by Facebook (and runs most of the Facebook application) as of now, so there’s strong support that doesn’t look like it will be going away anytime soon, unlike Google, which has been known to shutter entire business units without much warning.

Migration Strategy Architecture

Once we’d decided to migrate our application, it came time to architect out what the new application ecosystem of microservices would look like.

Here’s the diagram we ended up with:

All the blue services were already existing - all the orange services and databases represent the new React UI, the new Java microservices and databases needed to support it.

While the migration was ongoing, the AngularJS and React UIs would be running in tandem - each would be supporting a number of user screens until the application was fully migrated over from Angular to React, providing as little user disruption as possible in the production environment.

It soon became clear we’d also need a third, slimmed down AngularJS UI (the UI on the far right of the diagram) that would work with the newly created backend microservices that housed the logic previously held in the original Angular UI. The idea being, if the new Angular client was hitting the REST endpoints initially to pull back all the necessary data (ensuring the backend services replicated the existing browser experience), it would be easier to port over the functionality to the React UI later on.

Our initial estimate for how long this process of rewriting the UI logic into two new microservices would take, was about four months. We’re currently on month seven with three new microservices and the end is in sight, but we’re not there yet. Between extremely complex logic, many different ways for our users to currently operate in the tool and decisions around data storage and retrieval that required more research and some trial and error before settling on a final design, it took longer than anticipated to get to this stage.

Which brings us to our next consideration: the pros and cons of undertaking such a migration.

Pros & Cons of a Migration

After we’d come up with a revised architecture of how the application would be built, we had to get buy in, not only from the development team, but also from our business partners (our product owners), that this migration from Angular to React was a beneficial and necessary step.

Since our POs are focused on supporting our users and their needs, first and foremost, and providing them with tangible, new features, and they are less concerned with things like cleaner, more maintainable code and reduced bugs for developers, I broke down the pros and cons into something they could get behind. And sell to their managers, as well.

Pros

The pros of the migration broke down like this:

Migration to the new React framework could happen in stages, with minimum disruption to the users of our existing application.

New features for users could still be developed alongside the migration efforts - luckily, our development team is staffed well enough to allow for both efforts, simultaneously.

The same level of responsiveness would be available for users, in terms of bug fixes.

Blue / green deployments and a smaller group of pilot users would be employed to ensure functionality and get fast user feedback before full releases and general availability to our user base.

Cons

I also laid out some cons that would have to be accounted for as well:

There would be some latency switching between the AngularJS and React UIs, because browser caching and lazy loading behind the scenes wouldn’t be possible immediately as a user logged in from one framework then jumped to a screen in the other framework.



The URLs of the various UIs would be slightly different, which could confuse our users.



Feature development would be slower as some developers would be dedicated to the refactoring of the microservices and migrating of existing functionality.



Our business partners would, potentially, have to test functionality in all three UIs to ensure they were all working correctly, which could be confusing and tiresome, not to mention remembering which UI was responsible for what.

Proof of Concept (POC)

The next step, after getting approval from the business team, was building a working POC to prove that what we were proposing building in React, was possible.

So that’s what I did. I built a very, very pared down version of our existing application to show how it could be done and serve as an example for the whole migration. In the POC, the React app’s navigation and dashboard buttons successfully routed back to the existing AngularJS application screens and vice versa.

Additionally, I wrote Jest and Enzyme unit tests to demonstrate how to go about unit testing the components, as test driven development (TDD) is another methodology my organization subscribes to. Jest is a unit testing framework that actually ships with React if you use the Create React App CLI to make a new React project. Enzyme was created by Airbnb and adds additional functionality to the Jest testing library for even more robust testing.

I used Cypress.io for the end-to-end tests, as it was quick and easy to set up.

Part of my due diligence for the POC included exploring moving our existing application to an Angular 2 framework. Ultimately, I decided against Angular 2 because:

The resulting framework would still have been cumbersome and logic-heavy on the front end (not in keeping with our microservice architecture).

Angular 2’s syntax was quite a departure from AngularJS, meaning it would take the development team just as long (or longer) to ramp up, regardless of which framework was chosen.

It was already an outdated framework; when I was evaluating Angular 2, Angular 4 was widely available and Angular 6 was due to be released in just a few months.

Decision made.

Independent vs. Dependent Screens

The next thing I tackled after the POC was figuring out which screens within our application could begin being migrated even while the logic refactoring was underway, as I knew the extraction of business logic from the existing AngularJS codebase to backend microservices was not going to be instantaneous.

As an aside, our team was also responsible for creating the backend microservices so no further buy in was needed there. Two of our more senior developers took point on the architecture and creation of those new services for us.

Back to the screens, the screens I identified not tied to the logic included:

Login,

Admin screens,

The user dashboard,

And the user profile screens

These screens would not only give our developers a chance to start getting familiar with how React worked, in less complex, data-driven scenarios, but it could be also be done in parallel with the developers building the new microservices to house the logic.

The majority of the screens that were dependent on the business logic that had to be refactored out, were also identified, and the thinking was: the logic extraction would be done on the backend before those screens were greenlighted for migration to React. Until that was done, they’d continue to exist in the AngularJS framework.

Screens that were tied to logic included our checkout cart and all of the screens concerned with our users’ product assortments. For instance, in order to render the correct assortments, complex logic had to run in the Angular UI that took into account what was in our databases and combine it with what was in our users’ carts. In short, the UI was responsible for a lot of computationally intensive processes that was better left to backend services.

And as we progressed further, it was decided the old screens would live on in the slimmed down Angular UI connected to the new microservices once they were completed.

Development Assumptions

When we first laid out the plan, we made certain assumptions that we were clear to call out, so our business partners were aware these things had to happen for this migration to work smoothly.

Our assumptions included:

The extraction of business logic from the front end to new, backend microservices would be complete before we migrated over the logic dependent screens.

There would be no additional feature work on existing screens while they were being migrated (i.e. we would not create the same functionality in two frameworks for a new feature, that was a waste of developer time and resources).

Business partners would be responsible for prioritizing the order of screen migration (they know our clients best and have the most direct interaction with them).

There would be no changes to the existing microservices, instead, brand new ones would be built to house the logic coming out of the original AngularJS front end.

Opportunities and Risks of the Migration

Along with our assumptions, a complete rewrite of the frontend code, afforded us an opportunity to identify places where we could improve our application, as well as acknowledge some identifiable risks that could arise before we even began.

Opportunities

Our opportunities to improve included:

Implementing the company approved UX style guide (which was not present when the app was first built).

Redesigning screens in close collaboration with our UX partners and utilizing design best practices.

Removing features and code users weren’t using.

More effective Google Analytics implementation. When we’d initially done GA with the AngularJS app, we didn’t have a great understanding of how to effectively implement it to track useful information. This time around, we have a much better idea of which metrics to capture to inform new features and better usability within the app.

And more effective Usabilla implementation for qualitative user feedback.

Risks

We had less risks that we could foresee but came up with a major one to show it wouldn’t all be sunshine and smooth sailing.

At the same as we were migrating our app, we had to consider how sunsetting an existing sister application would play into the design and structure of both the front and backend services. How to transfer the tasks that downstream application was previously responsible for into our tool’s design and user flow was an unknown that no one was certain of how to solve for yet.

But we did the best we could to see the areas for improvement as well as the pitfalls and we moved forward, knowing there would be surprises along the way.

Next Steps

Once we’d come up with our plan, our new architecture, our proof of concept, etc., we needed to ensure coordination among all the parties involved.

We needed clear direction from our product owners to prioritize the stages of the application migration.

We needed to understand the product roadmap, already in-flight features and epics and their impact.

And we had to decide the order of the screen migration based on business needs.

As soon as that coordination was done, we were ready to get started with our migration from AngularJS to React.

Progress So Far

This, I’m sure, is what you’re all most interested to hear about. How has it been since this whole process began seven months ago?

Well, we’re making progress, it’s slower than we initially thought it would be, but we’re still chipping away and getting closer to the end goal. Below are some of the technical challenges that took us longer than we initially expected.

The extraction and recreation of all the logic the original AngularJS UI held into standalone microservices has been slower than expected, but it’s happening. What we originally thought could be housed in two new services has become three, due to a need to create a service recalculate the data being served to the UI whenever a user makes a change to the data on the frontend and saves it down.

As that’s been happening, part of the team has been working on migrating the existing screens and creating brand new screens in React while ensuring that the two UIs can work together and give the user the feel of a cohesive process, even though it’s technically separate apps creating one UI experience.

We went through four different databases before settling on a final decision. MariaDB to Cassandra, to a graph database, and back to MariaDB again. It took a while to finally get that one right because we had to figure out what was already available through our organization (versus what our team would have to pay more for) and what made the most sense for housing our complex, interconnected data.

At the same time as all this was going on, a few of our developers were pulled away to help stand up another, similar application for another set of users, who until this time, had been relegated to using Excel spreadsheets to manage their store inventory, so that slowed our momentum a bit.

And finally, as I mentioned above, it became clear a third, thinned down, AngularJS UI should be built to test the new microservices and make the move to React easier later on. I and one other developer have been working on refactoring the Angular UI into that smaller version of itself, replacing the logic it used to handle with a thin controller / view layer that calls the new microservice endpoints for almost all the data that needs to be displayed to the users.

It’s been tedious maintaining existing functionality while simultaneously removing all the twisting, turning, logic once needed by the UI to handle the data. But it’s also been beneficial, as we’ve been able to identify gaps in the data that the microservices have missed, and have our colleagues provide the information we require, (which the React UI will require as well, down the road).

Conclusion

And that’s all there is to it. As I said at the start, this is in no way the only way to go about upgrading a UI application from AngularJS to React, but it’s what has worked for my team, so far.

I hope that by outlining some of things we took into consideration when making our decision and planning how we’d go about it, it may help some of you in your own migrations.

Good luck!