Single page application: from monolithic to modular
Why we believe it’s important to move to modular approach for complex frontend applications
Web applications and mobile applications are the most commonly used ways to deliver services to users and companies nowadays, so it’s easy to understand why there’s a lot of energy spent in trying to create a better ecosystem to create web/mobile applications and to reduce the difference (from an end user perspective) between the two.
Single page applications (SPA) are applications contained in a single web page, this means that you can do several interactions with the web page without having the browser to load a new page. This kind of application has become very common, the most well-known cases are facebook.com, twitter.com, gmail.com and any modern application available on the web… you can easily spot them by noting how interactions with content (eg. selecting an email from gmail.com) will not cause a full-page reload.
SPA have been instantly successful because of several reasons:
- Reaction times
On a traditional website or web application, each user interaction will cause a complete page reload, this means that the user has to wait for everything to be loaded again, even what has not changed, and provides a bad experience, overall. On a SPA, most interactions will just load the necessary data and render the result directly on the existing page thus improving reaction times and allowing for nice animations and UX effects to be implemented. This is especially critical on mobile applications where the transfer times can be very high, compared to landline cable connections.
2. Less load
Besides the initial loading of the application, SPA allows to receive from server just the data needed to render differences on the interface or, even better, just the data needed to react to user interaction. This doesn’t just reduce wait times for users but also greatly reduces the amount of requests that the servers have to serve in order to let the application run.
3. Easier deployment
Single Page Applications are much easier to deploy, at least from the frontend part, compared to traditional applications: they are usually composed by a single .html file (or very few of them), a CSS bundle and a “bunch” of Javascript files. Such static elements can easily be deployed on any web server without requiring special support or configuration. One good example of this is the ability to host SPA on static storage solutions like Amazon S3. Backend is still required but if your technological debt is not too big, you’d already separate frontend from backend.
Seems like there’s no reason to not do SPA, right? Actually, developing and maintaining a SPA can be quite troublesome and we would like to share what we learned from the last 8 years of our SPA evolution.
Single Page Applications in THRON
We started fully betting on SPA early and we released our fully SPA THRON application back in 2010. At that time, Internet Explorer 8 compatibility was mandatory because it was widely adopted in enterprise companies despite being very limited compared with Chrome, Firefox or Safari.
The initial architecture followed the MVVM pattern and, as output, it generated one HTML file, one JS file and one CSS file. Basically, the whole application was contained in just 3 files.
We relied on the following technologies:
- require.js to generate a single javascript file starting from smaller modules (bundling);
- Knockout.js and jquery to operate in DOM and perform dynamic binding;
The SPA release was a success, customers were delighted by the improved user experience.
First problems and solutions
As we kept evolving the product and adding features, the “weight” of the application was starting to become an issue, especially loading times were long. For this problem we identified a quick and easy fix: enabling minification and compression (gzip).
From monolith to modular
Compressing the assets gave us time, reducing load times but still not addressing the underlying problems: the company was growing so the needs were too.
The “monolithic” engineering team was split into more teams, so more than one team needed to work on the same application and this caused few headaches:
- Code had too many dependencies; Team A, as an example, could update a component used also by code developed by team B and this change, in worst cases (so, the common ones), would create unexpected regressions on team B code;
- Increased code duplication risk; without an exceptional communication quality and frequency among teams, we could incur in the chance of having multiple teams implementing the same (or very similar) component without realizing it;
- Deploy process was very complex: we needed to wait for all teams to end their own implementations to be able to release the application, we could not deploy just parts of it;
- Some of our technical decisions were based on the required support of obsolete browsers (ie8, ie9 as main examples) but enterprise companies were ready to let them go, so it was not impossible anymore to force them dropping support for old versions. We waited until we could deprecate anything below IE11.
Those reasons brought us to the decision of pursue a complete refactoring of our frontend SPA, abandoning the monolith structure in favour of modules:
The new architecture highlights are:
- Components called Sections that implement a specific user interface of the application. Each section instance implements a different interface. Login section, as example, implements everything the frontend needs to request users their credentials; Passport section implements the user management frontend;
- A Composer component with the only task of managing application routing and Sections rendering;
- Each Section is managed by a single engineering team;
- Each Section must be decoupled from other sections and from the composer: it has to be possible to test it on its own;
- Starting the SPA will just load the Composer which will dynamically load Section based on what’s needed to render user “route” (url);
Once defined the desired high-level architecture we started focusing on how do we switch to this architecture? Can we perform an old/new switch or should we find a way to have a progressive deployment?
Rewriting from scratch an application that grew over the years is a very risky strategy because there’s a high chance of not being able to detect and replicate behaviours and edge cases that might have not been documented well enough or, not documented at all!
It’s also worth noting that development time would be way too long to accept a complete freeze of the deployments waiting for the new release.
The choice is then forced: find a way to gradually switch from the monolith to modules, while preserving user features and experience.
This forced us to perform some adjustment to the architecture: existing legacy application will be adapted and converted in a new type of architecture component called: “Legacy Monolith”; it collects and manages all legacy sections. New product developments will be implemented as new Sections and we will gradually “chip away” Sections from the Legacy Monolith.
Composer logic will be slightly more complex, but still very simple to manage:
This is a golden chance to perform a complete technology overhaul, so here’s what we decided to use:
- Webpack.js, as composer’s module bundler. Webpack.js the most complete module bundler available on the market; it’s mature (4th major version) and has a very active community. We have been already using Webpack.js for other projects in the past years and we didn’t find any major reason to prevent using it for composer’s simple needs;
- Rollup.js as section’s module bundler. We choose to use Rollup.js instead of Webpack.js because it supports ES Module export format, which opens up the ability to optimize library imports: lets suppose Login and Passport sections both use Lodash library. Composer’s Webpack.js, during build phase, will import Login and Passport sections but will import Lodash just once. If the Export format would have been “UMD” this would not have been possible;
- Vue ecosystem. Clear requirements we have include: the ability to create easy-to-reuse web components, application state library and routing management. Our scouting easily reached 2 candidates: Vue (Vue.js, Vuex.js, Vue-router.js) and React (React.js, redux.js, react-router.js). We choose Vue because we find it easier to learn (short developer on-boarding time), with a better documentation and Vue-router really came out as an excellent routing library.
- Yeoman as scaffolding tool. All Sections require the same boilerplate, to prevent us from having to write this back all the times we use Yeoman to lay out the common basis for each new Section. Yeoman is, most probably, the most famous web scaffolding tool and we found it to be very easy to use, with great documentation and meaningful examples.
What has improved with the new architecture?
SPA Size
Partially solved. Overall application size is similar to the previous one and it might even get bigger as we keep adding features but, from an end-user perspective, the size has been greatly reduced since only Sections that are used are loaded (and they are loaded only when being used).
Regressions
Solved. Each Section has one and only one team. Interfaces between sections, if needed, are agreed between teams.
Complex deployment
Solved. Each section can be deployed on its own, without having to deploy other sections and, in most cases, without having to update the Composer.
Code duplication
Not Solved. This architecture doesn’t solve this issue.
Code Duplication: how we choose to approach it
As already described on this article, we have regular competence-specific meetings. In the case of the frontend development, we share on each meeting the status update about component creation and evolution within the new architecture, so that every practitioner is aware of what can be easily included in his/her own project. This has been strikingly clear for common features such as session management and other common libraries that are used by all teams. Common libraries have no single owner, the whole guild is the owner and the guild leader performs code reviews and validates decisions.
So what?
The frontend world is incredibly wide: every day a new framework or a new library enters the landscape with the promise of solving “this” or “that” problem, but they keep increasing fragmentation. Frontend development is still a quick-to-evolve environment and we must plan for future disruptions (think about what happened when React came out) because they will happen during the life span of your web application.
For this reason, we believe that a monolithic architecture is not adequate to frontend development: modularisation is the key to allow testing new technologies on single parts of the application and gradually extend their adoption if they prove to be mature.
Our proposed architecture allows to reduce dependencies to the minimum while still orchestrating a single page application from a central point. If you have comments or different opinion on this matter, feel free to reach out, we’d love to exchange opinions.