AUTO1 Group

Evolution of SPA approach at AUTO1

By Oleg Tudoran

Software Engineer

< Back to list
Architecture Apr 11

Here, at AUTO1 Group, we are growing. Not only in terms of company size, number of employees, revenues, etc, but also in terms of database size, amount of servers, lines of code in applications, amount of accessible pages. For supporting this growing codebase we are adopting new approaches and new technologies. Different innovations were already introduced, but today I want to tell You about switch of a paradigm from MVC to SPA, and how implementation of SPA approach evolved.

MVC approach

We have our internal application, called Admin, which is heavily used by hundreds of colleagues in many countries. Historically, it was written as a monolith php application, based on Silex (and later Symfony) frameworks (some overview is present in this blog article). Admin application had architectural layers, which fell into a concept of Model-View-Controller (MVC). The View part, which is in essence the HTML code of all pages, was prepared on web-server using TWIG templates. Usually, one developer was working on both back-end and front-end implementations of web-page. That was called Full Stack development.

Downside of such approach became obvious when first HTML tables with complex internal structure were implemented. Lets take as an example a page that displays hundreds of cars, provides pagination, possibility to edit data, shows aggregated results, etc. Such page consists of static components (navigational menu, list of available locales, information about current user, html-template, that renders appearance of the page, etc) and dynamic components (information about cars). This kind of web-page is hard to build for a single person. Knowing that development cycle of such complex pages could be split between several software engineers, we divided responsibilities between back- and front-end developers. We decided to generate the View part of MVC architecture in client's browser instead of application's server.

And this was exactly the case, that fitted well into SPA paradigm.

SPA paradigm

SPA stands for Single-Page Applications - an architectural paradigm, that divides elements of the page into static and dynamic. Static elements of the page (such as page layout, navigational menu, user information) should be loaded into client's browser only once per user session. The page layout rarely changes. On the contrary, dynamic components of the page can be updated continuously.

Regardless of the technologies involved for rendering a page in client's browser, main difference between SPA and non-SPA approaches is whether a browser requests each time full page content (including static content, such as layout, menus, etc), or it requests only the important (dynamic) content. This difference can be seen in a diagram below.

Traditional vs SPA

First picture represents traditional request where full page contents are passed from web-server to a browser on each request. Second picture illustrates SPA approach: full page content is fetched only once, afterwords only dynamic content is downloaded and full page reload does not happen.

SPA evolution in AUTO1

Starting from a monolith application, where HTML was rendered on a server-side, we gradually moved to our current state, where a separate application serves SPA for us. But let's go step by step.

From Monolith to php-rendered SPA

Adopting of new front-end technologies, such as JavaScript library React, allowed us to start building first SPA pages. React code is executed in the web-browser. Front-end (React) developers became responsible for all the visual representation of a web-page.

For organic integration with new technology we updated existing workflow of generating HTML of the page. Specifically, we introduced several new endpoints, that provided essential information for rendering HTML page layout. Those endpoints were:

  • GET /menu - returned JSON-encoded structure for rendering navigational menu
  • GET /locales - returned JSON-encoded list of available language and country settings
  • GET /user/{id} - returned JSON-encoded data about current user

Front-end developers wrote React code, which could fetch data from aforementioned endpoints and render HTML5-compliant page layout.

Now we needed a way to pass this React code from web-server to client's browser during initial page load!

This was achieved by introducing of what we call SPA-controller: set of php application's code (written with Symfony framework), which returned very basic standard html template that contained latest version of the React code. Being executed in the browser, this code rendered HTML contents of specific SPA page.

Under the hood of SPA controller we implemented such functionality:

  • based on request URL, define exactly which SPA page layout was requested
  • check if we already have this HTML-template cached
  • (if template was not cached, or TTL of the cache is over) do a request to CDN, that contains actual version of front-end assets

    • put new HTML-template into cache
  • return HTML-template of SPA index page to a requesting browser

The response contains lightweight HTML similar to this:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"><meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
        <title>SPA index</title>
        <link href="https://cdn.xyz.de/1.2.3/main.css" rel="stylesheet">
    </head>
    <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="app"></div>
        <script type="text/javascript" src="https://cdn.xyz.de/1.2.3/main.js"></script>
    </body>
</html>

As You can see, SPA index page contains none of the data that should be shown on a page. This data will come as a result of subsequent requests. Stages of building SPA page are illustrated in the diagram (notice 3 phases):

3 phases of SPA page load

Page lifecycle consists of 3 phases:

  • fetch SPA index and render empty HTML container
  • fetch data about static page components and render a layout of the page
  • fetch main content of the page and build it into existing layout

This approach, regardless of its increased amount of requests, actually resulted in a faster page load times, mostly because caching of SPA index was introduced and because web-server did not render HTML code anymore (this responsibility was moved to the React application code, that was executed on the client-side in web-browser).

Overtime we noticed drawbacks with this approach: actual SPA version was hardcoded in a config file in php code and full php application required re-deploy each time, when a new version of React code was introduced. Deployment of a big monolith application to production environment was slow, also release notes were meaningless and annoying.

Moving SPA index outside of monolith

Having understood that keeping a reference to a latest SPA version in the code of main application was causing real problems, we started thinking about another possibilities.

The most promising was idea to create a standalone application, whose single responsibility would be to serve latest version of SPA index page to authorized users. Expected benefits were:

  • decreased time of delivering information about new SPA version to a production environment
  • decreasing time of serving SPA index page
  • utilize web-browser's capabilities, such as HTTP caching

As a result, we implemented a Golang application, that did exactly the same, as it's predecessor-monolith, but outside of php environment. The current page lifecycle started to look like this:

SPA index served by standalone application

Now, initial request to fetch SPA index was re-routed to a standalone application, that served the same HTML, as we have seen above. All other phases of SPA page lifecycle did not change.

Current state of rendering SPA pages

Implementing of a separate application, that serves SPA index pages, brought us several significant improvements in a page load lifecycle:

  • SPA index page is returned significantly faster - 80-90 milliseconds instead of 1200-1500 milliseconds in old world
  • time to deploy to production environment decreased almost 10 times (and now it is under 4 minutes)
  • moving of configuration parameter outside of monolith allowed our front-end engineers to be able to update version of SPA index directly, without help of back-end guys
  • additional configuration improvements allowed us to gradually release feature (allow switching to a new way of fetching SPA index one page by one) without a risk of braking full application
  • automated support of multiple development environments was introduced ot of the box
  • fine tuning of HTTP caching parameters allowed us to serve less requests, because of browser's caching possibilities
  • In the first hour of work with this application on production environment 75% of all requests were served by HTTP cache, with most requests not even reaching application
  • other teams have successfully adopted technology and were able to release application themselves

This is pretty good, huh?

Of course, there are challenges in current approach also. We are investigating the best way to deal with user locales in SPA index, we learn how to do proper caching and authenticating user, but this is a topic of another story!

AUTO1 Group

#spa #monolith #mvc #engineering