Immutable Web Apps

Introduction

Immutable Web Applications is a framework-agnostic methodology for building and deploying static single-page applications that:

Minimizes risk and complexity of live releases.

Simplifies and maximizes caching.

Minimizes the need for servers and administration of runtime environments.

Enables continuous delivery through simple, flexible, atomic deployments.

Principles

The methodology is based on the principles of strictly separating:

Configuration from code.

Release tasks from build tasks.

Dynamic content from static content.

Concepts

The following concepts define the core requirements for an Immutable Web Application. They are framework and infrastructure agnostic.

Static assets are independent of the web application environment(s)

Static assets are the files (javascript, css, images) that are generated from a build of a web application codebase. They become immutable when they do not contain anything environment-specific and they are published to a unique, independent location.

Static assets must not contain anything that is environment-specific

All of the leading application frameworks (Angular CLI, Create React App, Ember CLI, Vue CLI 3) recommend defining environment values at compile time. This practice requires that the static assets are generated for each environment and regenerated for any change to an environment.

Immutable Web Applications reference environment variables that are defined on the global scope and reference one of two ways:

Directly from the window object

object Through an injected service that wraps the environment variables

export class UserService { webServiceUrl: String; constructor( private http: HttpClient ) { - // remove any configuration that is hardcoded or included during compilation - this.webServiceUrl = 'https://api.myapp.com' + // use globally scoped environment variables that are unique to the deployment + this.webServiceUrl = window.env.API } getUsers() { return this.http.get(`${this.webServiceUrl}/users`); } }

The values for the environment variables are set on an index.html that is unique to each environment.

Static assets must be hosted at locations that are unique and independent of the web application environment(s).

Static assets that do not contain anything environment-specific can be built once, published to a unique location, and then used in multiple environments of the web application.

These static assets share the same qualities as hosted javascript libraries on content delivery networks (CDN) (Google Hosted Libraries, cdnjs, jsDelivr, UNPKG):

https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js

The location of the jquery library above, referenced by innumerable web applications, is both independent of the applications and uniquely versioned.

https://assets.myapp.com/apps/1.0.2/main.js

Similarly, the location of the web application javascript files are unique and hosted at a location that is dedicated to static assets. The static asset web server is a repository for versions of the web application.

Configure the static assets for long-term caching

Static assets that do not contain anything environment-specific and are hosted at a unique and permanent location may be configured to be cached by the browser (almost) indefinitely:

cache-control: public, max-age=31536000, immutable

index.html is deployable configuration

The HTML document of a single-page application (often index.html ) is not static. It varies from environment to environment, and deploy to deploy. The HTML document is a composition of the environment-specific configuration and immutable static assets that define the web application.

index.html contains fully-qualified references to the static assets

- <!-- not unique, not independent --> - <script src="main.js" type="text/javascript"></script> + <!-- permanent, reuseable --> + <script src="https://assets.myapp.com/apps/1.0.2/main.js" type="text/javascript"></script>

index.html defines the values of the globally scoped environment variables

<script> env = { API : 'https://api.myapp.com' , GA : 'UA-126263119-1' } </script>

index.html must never be cached

To allow for web application environments to be changed instantly, index.html must never be cached by the browser or a public cache that cannot be purged on-demand:

cache-control: no-store

Example

The index.html of most single-page web applications is typically a small document. By including versioned references to the web application static assets and setting the environment variables it is effectively a deployment manifest:

<!doctype html> <html lang= "en" > <head> <meta charset= "utf-8" > <title> My App </title> <base href= "/" > <meta name= "viewport" content= "width=device-width, initial-scale=1" > <link rel= "icon" type= "image/x-icon" href= "favicon.ico" > </head> <body> <!-- environment variables --> <script> env = { API : 'https://api.myapp.com' , GA : 'UA-126263119-1' } </script> <!-- application binding --> <app-root></app-root> <!-- fully-qualified static assets --> <script src= "https://assets.myapp.com/apps/1.0.2/main.js" type= "text/javascript" ></script> </body> </html>

Workflow

The Immutable Web App separates release tasks from build tasks into two distinct workflows.

Building

The codebase of an Immutable Web App has the responsibility of building static assets and publishing them to a static web server. Each state of the codebase can be represented by a set of static assets at a unique location. Not every state of the codebase needs to be published, but no single state of the codebase should ever need to be published more than once.

Generally the codebase is a source controlled code repository integrated with a continuous integration system that is capable of building, versioning, and publishing static assets to a static web server.

An example of this might be:

An Angular project hosted in a GitHub repo. The repo is integrated with TravisCI to build and version assets when commits are pushed to the master branch. Versioned assets are published to a unique location in an AWS S3 bucket.

Releasing

The index.html files are managed independently of the codebase and they serve as a manifest for each environment. They should be considered configuration files and managed accordingly. Additionally, There needs to be a mechanism for modifying or replacing the index.html in each web application environment. The act of changing an index.html is effectively a deployment.

An example of this might be:

A set of index.html files, one for each environment, hosted in a Github repo. The repo is integrated with TravisCI to publish an index.html file when it is modified to an AWS S3 bucket. There is an index.html and S3 bucket dedicated to each web application environment.

Infrastructure

The infrastructure to support Immutable Web Apps is composed of three parts:

Web Application Server : A static web server to host a web application environment by serving index.html .

: A static web server to host a web application environment by serving . Static Asset Server : A static web server to host the immutable static assets.

: A static web server to host the immutable static assets. API: One or more publicly exposed endpoints to interact with the web application backend.

The two static web servers have different behaviors:

Web Application Server Static Asset Server Content index.html Static assets ( *.js , *.css , images, etc) Routing always index.html the physical file at the Url Cache-Control no store cache-control: public, max-age=31536000, immutable Instances One per web application environment One per web application

Benefits

Promoted deployments

Building static assets is a complicated process that often involves:

Resolving dependencies

Downloading libraries

Transpiling

Minifying

Bundling

Uglifying, Code Splitting, Tree Shaking, Autoprefixing… the list goes on…

These processes are time consuming, rely heavily upon external dependencies, and often behave in a seemingly non-deterministic way. They are not processes that should be concluded by immediately publishing the generated assets to a production environment without validation. Even the act of publishing the multiple large static assets is a process that may be interrupted and leave the web application environment in a corrupt state.

Immutable Web Applications are generated once and published once to a permanent location. This process happens in advance of a live release. They can be validated in staging environments and promoted to the production environment without being regenerated at significantly lower risk.

Atomic live releases

A live releases of an Immutable Web App is the act of publishing a single index.html file. The deployment is instantaneous and all assets are available immediately without any risk of cache being corrupted at the time of the release.

Rollbacks are as risky as deployments and often riskier. For Immutable Web Apps, the same qualities of a deploy apply to a rollback. Notably, in the event of a rollback most browsers will still have the previous assets cached.

In the unlikely event that a browser attempts to load a stale version of index.html , all of the assets of the previous version will still be available and uncorrupted.

Simplified caching

Managing cache-control headers can be intimidating, especially when the web application infrastructure leverages public caches like the ones used by CDNs. The two simplest concepts in caching are: “Always cached”, and “Never cached”. Immutable Web Apps embraces these concepts completely separating and the code that can be “Always cached” from configuration that is “Never cached”.

Simplified routing

The leading application frameworks do not separate the location of static assets from the index.html in their deployment recommendations. Instead, they recommend adding routing rules to the web server that returns index.html for all paths that do not resolve to a physical file. The implementation of these routing rules may differ between web servers and mistakes often result in paths that resolve to the wrong resource.

Separating the hosting of index.html and the static assets eliminates this risk. The static assets server always serves a physical file represented by the url and the web application server always serves index.html for any url.

Background

Immutable Web Applications methodology builds on several trends in web application development:

Modern Application Frameworks: Angular, React, Vue, and Ember have enabled teams to build increasingly complex single-page static apps. Tools like webpack have improved the ability to create, optimize, and manage build artifacts.

DevOps: The culture of DevOps has enabled web application developers to decompose and reevaluate their web application infrastructure to better serve the requirements of their web applications.

Maturing Application Patterns & Practices: Backend applications and services are converging around a set of best practices that support portability, scalability, and high availability. This trend has dramatically increased the tools and services available, especially related to containers and container orchestration. Many of these practices are just now starting to be applied to static single-page web applications.

Influences

The Twelve-Factor App : This methodology for building web apps is based on separation of concerns in order to achieve portability and robustness. Immutable Web Apps separate several of the same concerns in order to achieve similar objectives.

JAMstack: The Immutable Web Apps methodology is completely aligned with the best practices of the JAMstack.

Projects

Talks & Articles

Future Work

Immutable Web Applications is not yet actively supported by any application framework, tools, or services. Most static, single-page applications should be able to adopt the Immutable Web Applications methodology by making several minor structural changes to the codebase, build process, and infrastructure. Until there is widespread support, however, web applications that use advanced code-splitting techniques may find adoption to be complicated or impractical.

Achieving widespread support will require:

Building tutorials, documentation, and examples of how to build Immutable Web Applications using the leading application frameworks and tools.

Proposing changes to existing frameworks and tools to support Immutable Web Applications.

Advocacy and education of Immutable Web Applications through blog posts and presentations.

Acknowledgements

Immutable Web Apps was developed based on the research and experiences building web applications at Meltwater. Learn more about the things we are doing at our engineering blog: underthehood.meltwater.com.

immutablewebapps@gmail.com @immutablewebapp