Evolution of the UI Architecture at RIVIGO (Part 1)

27 July 2018
Technology

An overview of how the UI architecture at RIVIGO evolved from Angular JS to React JS and then from Plain Flux to Redux.

 

Introduction

In an agile engineering environment, working on an iterative model is critical for both pace and innovation. This highlights the need for ample experimentation with regards to product development. This can often lead to situations where learnings from the first version of a product imply the need for a completely different approach to building the next version. Hence, all elements of engineering need to be designed in a way that support a technology development culture which is fast-paced and ever-evolving.

 

Objective

When considering our UI architecture, we had the following objectives to meet.

  • Fast paced development by ensuring iterative and modular workflows
  • Scalability so that continuous product changes make no impact on the dependent module
  • Reusability to eliminate the drudgery of writing similar code again
  • Adaptability to enable ease with least chances of errors
  • Quality to ensure a bug-free environment

 

Approach

Our technology stack in 2016 was as follows.

  • Angular 1.X: This was being compiled via Gulp to static html pages with all the business logic in the JS bundles produced by Gulp. We used to host such applications directly on third party hosting services like AWS S3 and then mapping it to required domain.
  • React + Flux: This was compiled via webpack 1.X and then hosted on a NodeJS server.

Creating small SPAs using Angular 1.X was blazing fast and easy to build. However, when the application’s code and business logic complexity increased, it had the following limitations.

  • The number of watchers was limited to 2000
  • The performance on mobile devices was not fluent
  • Prevalent best practices were not clear as multiple ways of doing the same thing exist
  • The performance was degraded due to two-way binding which leads to more watchers
  • The rendering was slow as more API calls per page led to multiple render cycles, which necessitated a better rendering algorithm-based framework

Upon identification of these issues, we initiated work on ReactJS. Plain flux implementation was our first choice for state management. However, the following issues surfaced with this approach.

  • More boilerplate code was required given the need to write a lot of listeners to update views when Flux store changes
  • The state management is partially through React component’s state and partially through flux store
  • Debugging would become difficult as business logic and scope scaled

We concluded that Flux served more as an implementation guideline and not a full-fledged state management system. This is why and when we evaluated Redux over plain flux. We also determined the part of the code that should be abstracted out to not let developers choose their path of writing code.

Since React JS is best suited to our case, we continued to work on it. As mentioned above, in order to have a proper state management system in picture, we replaced Flux with Redux which helped reduce boilerplate and increase abstraction. The following were charted out as key tenets in the process.

  • State management to happen only through Redux Store
  • Replacement of Redux Thunk middleware with Redux Saga middleware to improve testability
  • Migration of Webpack to version 4 and use of React-Loadable which improved the bundle size
  • Use of Reselect to memorize selectors in reducer calls
  • Code splitting implementation to reduced initial load time and bundle size further

The diagram below shows the overall Redux architecture.

 

Folder Structure 

We have the following folders at the root.

Components

A component comprises of all reusable components which can be used across the product. A single component structure is marked in the picture given above.

We kept all the component’s dependencies i.e. actions, reducers, sagas, selectors, constants etc. in one folder. We followed this practice over keeping separate folders for actions/reducers etc. This ensured that the component is 100% reusable.

Containers

A container acts as a controller for each application. The main container i.e., the App container handles all the routing for the application. Other containers are specific to applications and follow the same folder structure as that of components.

 

The architecture shown above solved the following problems for us.

Elimination of boilerplate code: Redux eliminates the need for writing watchers for a store. Once the components are connected as per our requirement, Redux does that by itself. For seeking a part of the global store for a single component, we needed to write the mapStateToProps function. This can be a repetitive task for each component. While it is important to use this in some cases, in majority of the cases the reducers can be designed in a way that mapStateToProps always fetches the entire reducer.

With the above component structure which ensured that each component had its own reducer, we did not have to write boilerplate in mapStateToProps function. We used the following smart function for retrieving a reducer from the store.

 

 

The snippet below shows how we deal with some custom scenarios where the component requires data from more than one reducer.

 

 

Abstraction of concerns (Adaptability): With the folder structure given above, we defined the abstraction around the approach to creating a component.

  • If a component has its own data, it should have a reducer unless the data is owned by the parent and propagated to the children. In the latter case, the component can be dumb.
  • Even if the reducer is shared among several components because of the requirement. The abstraction is ensured as the selectors reside inside each They memoize the calls to reducer and keep the reducer parsing logic localised to each component.
  • Any API call goes through sagas and each component has its own saga file.

We used the same architecture for more than a year until we sensed some technical debt being created.

  • Components such as date-time-picker, calendar, form element etc. had multiple copies of them across the code as developers created new components when the requirement changed even slightly.
  • Reusability of codes across repositories/verticals continued to be less and developers across verticals were coding over to solve the same things
  • We were not able to build features in vanilla JS that is independent of React and can be reused in legacy AngularJS applications.

These problems led to the inception of our own NPM module that called rivigo-ui-commons.

 

RIVIGO NPM Module

We created a new repository that had the following key components.

  • React Components: All reusable components like form element, cron scheduler, modals, toast, base components (row, column, card etc.) were placed here
  • Utilities: All utilities functions such as JsonToCsv, ApiResponseHandlers, ErrorHandlers, ErrorLoggers were placed here
  • JS Components : All components that were built in vanilla JS such that they can plugged into any tech stack were placed here (we exploited these components by using them in our legacy angular applications)

We have used several tools and libraries to build this NPM module. The entire commons module was reorganised to make it a scoped module with versioning of each individual component.

We would be coming up with another article in this series to share how we built our commons NPM module and the roadmap for further improvements.

 

 

Conclusion

One of our key leadership principles at RIVIGO is ‘1% improvement every day’. The evolution of our UI architecture is a perfect testimony of this principle. The journey we undertook from Flux to Redux to building our own NPM module and forward, has helped us build a solid foundation for building agile and robust engineering practices. The key impact areas have been elimination of repeated code, 100% code coverage and scalability.

 

In the next article of this series, we will cover the details of the architecture of our NPM module.