Skip to content

Commit dd75f83

Browse files
committed
decomposing web apps as a way to explain immutable apps
1 parent d5bd5ee commit dd75f83

File tree

1 file changed

+193
-22
lines changed

1 file changed

+193
-22
lines changed

README.md

Lines changed: 193 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,182 @@ _Immutable Web Applications_ is a framework-agnostic methodology for building an
66

77
- Minimizes risk and complexity of live releases.
88
- Simplifies and maximizes caching.
9-
- Minimizes the need for servers and systems administration.
9+
- Minimizes the need for servers and administration of runtime environments.
1010
- Enables continuous delivery through simple, flexible, atomic deployments.
1111

1212
## Principles
1313

1414
The methodology is based on the principles of ___strictly separating___:
1515

16+
- Dynamic content from static content.
1617
- Configuration from code.
1718
- Release tasks from build tasks.
18-
- Dynamic content from static content.
1919

2020
These principles are inspired by [The Twelve-Factor App](https://12factor.net/).
2121

22-
## Concepts
22+
## Decomposing a web application
2323

24-
The following concepts define the core requirements for an _Immutable Web Application_. They are framework and infrastructure agnostic.
24+
The _Immutable Web App_ methodology was developed by through a process of decomposing web applications based on the principles declared above and then reconsidering the relationship between the different components. Before defining the methodology, this document will step through the decomposition. It starts with a monolithic web application.
2525

26-
### _Permabundles_
26+
### The Monolithic Web-Application
2727

28-
Permabundles are the static assets that make up a single-page application.
28+
There generally are three types of network traffic commonly made in by web application from the browser:
2929

30-
- They must be available at a [permalink](https://en.wikipedia.org/wiki/Permalink) that is uniquely versioned or fingerprinted and _independent of the web application environment(s)_.
30+
#### static assets
3131

32-
- They must be configured for long-term caching. (`cache-control: public, max-age=31536000, immutable`)
32+
- javascript, css, images, bundled assets
33+
- long term cached
34+
- artifacts live on the client
3335

34-
- __They must not contain any environment-specific configuration.__
36+
#### xhr/fetch
3537

36-
### `index.html` <span style="font-size:larger;">___is___</span> configuration
38+
- dynamic
39+
- no cache
3740

38-
An `index.html` exists for each deployment environment.
41+
#### the document
3942

40-
- They must contain fully-qualified references to the _permabundle_ assets.
43+
- typically in the form of `index.html`
44+
- no caching
45+
- returned for all routes that aren't physical files
4146

42-
- They must never be cached by the browser or a public cache that cannot be purged on-demand. (`cache-control: no-store`)
47+
> An image of typical network traffic for a simple single page application
4348
44-
- __They must define all environment-specific configuration.__
49+
A monolithic web application will handle the requests for all three types of requests. Express, a very popular node framework for building web applications, generally supports handling all three types of requests. Running the `express-generator` will create an application skeleton that supports routes for each:
50+
51+
```javascript
52+
var express = require('express');
53+
var path = require('path');
54+
var cookieParser = require('cookie-parser');
55+
var logger = require('morgan');
56+
57+
var indexRouter = require('./routes/index');
58+
var usersRouter = require('./routes/users');
59+
60+
var app = express();
61+
62+
app.use(logger('dev'));
63+
app.use(express.json());
64+
app.use(express.urlencoded({ extended: false }));
65+
app.use(cookieParser());
66+
67+
/* serves static assets */
68+
app.use(express.static(path.join(__dirname, 'public')));
69+
70+
/* serves the document */
71+
app.use('/', indexRouter);
72+
73+
/* serves dynamic requests */
74+
app.use('/users', usersRouter);
75+
76+
module.exports = app;
77+
```
78+
79+
A monolithic web application will also include a web application framework, like Angular, for building the single-page application assets. Using Express and Angular together in a single code base will results in a multi-stage build:
80+
81+
- First the static Angular apps are generated and
82+
- Then the assets are embedded in the creation of the executable package (docker image, rpm).
83+
84+
The package is then typically stored in a versioned repository of packages before it is deployed to a runtime environment.
85+
86+
> An image of the monolith build process: code => build static assets => build docker image with assets baked in => publish image to docker repo => deployed to server
87+
88+
#### Characteristics of the monolithic web application
89+
90+
- separation release tasks from build tasks (good)
91+
- high cohesion between the backend and frontend contract (good)
92+
- ability to test the same build artifact in multiple environments (good)
93+
- if the backend goes down, so does the frontend - serving static assets is much more reliable then dynamic backends, and gracefully handling backend outages is a better user experience than 404s and 500s (bad)
94+
- zero downtime deploys can be tricky considering caching and existing browser sessions (bad)
95+
- complicated project dependencies that represent two very different jobs (bad)
96+
- building a static web application assets
97+
- running a web server
98+
99+
### Separate frontend from backend
100+
101+
The first stage of decomposition is to separate the client from the server. This aligns with the principle of strict separation of dynamic content from static content.
102+
103+
> An image of the two separated build processes:
104+
> code => build static assets => publish to static web server with routing rules
105+
> code => build docker image => publish image to docker repo => deploy to server
106+
107+
#### The static web application
108+
109+
code base generates static assets,
110+
111+
The static assets can be deployed to a static web server that will have much higher reliability than the backend.
112+
113+
The static web apps should generally be cached for the long term, the backend should have caching turned off.
114+
115+
#### The backend api
116+
117+
code base generates an executable package.
118+
119+
generally you can read up on 12 factor app for help here, as it has been decoupled from many of the web application specific concerns
120+
121+
#### Characteristics of the frontend/backend web application
122+
123+
deployments require a little more careful sequencing to avoid zero-downtime. The backend may need to support multiple versions of an app as the frontend is deployed from one version to another. this is good, because it makes an preexisting problem more visible.
124+
125+
Code bases for generating the backend and the code base for generating the static application is separated.
126+
127+
Client side assets are often environment specific and deployed right after they are built as a part of a release.
128+
129+
### index.html as a special file
130+
131+
The second stage of decomposition continues to emphasize the strict separation of dynamic content from static content by separating `index.html`.
132+
133+
The document, `index.html`, is a bit of a special case. It may be a static file from the perspective of a specific deployment, but over the lifetime of multiple deployments, it is a dynamic file... evident by the fact that it cannot be cached at any location by the browser. Moreover, it has routing responsibilities that are unlike any of the other static assets or apis.
134+
135+
> An image of the three separated build processes:
136+
> code => build static assets => publish to static web server
137+
> code => build docker image => publish image to docker repo => deploy to server
138+
> index.html => publish to static web server that always returns index.html
139+
140+
#### `index.html`
45141

46-
_The `index.html` of your Angular app might look like this... it's all configuration!_:
142+
can be hosted by a static web server that for whatever path it is requested it always returns `index.html`
143+
144+
#### `static assets`
145+
146+
it allows us to simplify the hosting of all other static assets, all other static assets can be hosted in a completely different domain, and cached for long term if they are versioned or fingerprinted
147+
148+
#### Characteristics of the index/frontend/backend web application
149+
150+
assets can be build prior to deployment, a live release is effectively the act of updating `index.html`, but opportunities for testing them are limited.
151+
152+
### Separating code from config
153+
154+
Now that the app is decomposed into long term cached static assets, backend apis, and the special case of `index.html`, the relationships between the three parts look like this:
155+
156+
> An image of the three components and lines showing their dependencies (BEFORE)
157+
158+
- `index.html` has a strict dependency on static assets
159+
160+
- javascript in the static assets has strict dependencies on the backend apis (based on how modern web app frameworks handle configuration)
161+
162+
```typescript
163+
// This file can be replaced during build by using the `fileReplacements` array.
164+
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
165+
// The list of file replacements can be found in `angular.json`.
166+
167+
export const environment = {
168+
production: false
169+
};
170+
```
171+
172+
- the backend apis generally have no dependencies on either the static assets or index.html
173+
174+
The environment configuration that creates a strict dependency between the javascript and the api violates the principle of strict separation of configuration from code. This practice forces the static assets to be rebuilt for each environment and for any configuration change. Rebuilding the static assets introduces risk and promotes a workflow that does not distinguish between build tasks and deploy tasks, adding even more risk and complexity to releases.
175+
176+
By expanding the perception of dynamic and static, index.html can be perceived as static from the perspective of one deployment, but dynamic over the lifetime of multiple deployments. Similarly, Javascript that contains configuration is static from the perspective of one environment, but it is _conceptually_ dynamic over multiple environments because of the differences in configuration. This dynamic quality is addressed by building the assets multiple times and publishing to multiple locations.
177+
178+
Moving the configuration out of the static javascript allows it to be _truly_ static. __It can be generated once, published once, cached forever and used in multiple different environments with multiple configurations.__
179+
180+
Where does the configuation live? It must live in a location that is dynamic (and therefore never cached), it must be unique to the environment and unique to the deployment. `index.html` has all of these qualities. If configuration is moved into `index.html`, the relationships now look like this:
181+
182+
> An image of the three components and lines showing their dependencies (AFTER)
183+
184+
Consider what it looks like to have all environment configuration defined on the global scope of the `index.html` generated by the `angular-cli`:
47185

48186
```html
49187
<!doctype html>
@@ -60,7 +198,7 @@ _The `index.html` of your Angular app might look like this... it's all configura
60198
<!-- environment variables -->
61199
<script>
62200
env = {
63-
API: 'https://api.immutablewebapps.org',
201+
API: 'https://immutablewebapps.org/api',
64202
OAUTH: 'https://oauth.immutablewebapps.org',
65203
GA_TRACKING_ID: 'UA-98765432-1'
66204
}
@@ -69,17 +207,50 @@ _The `index.html` of your Angular app might look like this... it's all configura
69207
<!-- application binding -->
70208
<app-root></app-root>
71209

72-
<!-- fully-qualified permabundles -->
73-
<script src="https://immutablewebapp.org/assets/c17080f1/runtime.js" type="text/javascript"></script>
74-
<script src="https://immutablewebapp.org/assets/c17080f1/polyfills.js" type="text/javascript"></script>
75-
<script src="https://immutablewebapp.org/assets/c17080f1/styles.js" type="text/javascript"></script>
76-
<script src="https://immutablewebapp.org/assets/c17080f1/vendor.js" type="text/javascript"></script>
77-
<script src="https://immutablewebapp.org/assets/c17080f1/main.js" type="text/javascript"></script>
210+
<!-- fully-qualified static assets -->
211+
<script src="https://assets.immutablewebapp.org/c17080f1/runtime.js" type="text/javascript"></script>
212+
<script src="https://assets.immutablewebapp.org/c17080f1/polyfills.js" type="text/javascript"></script>
213+
<script src="https://assets.immutablewebapp.org/c17080f1/styles.js" type="text/javascript"></script>
214+
<script src="https://assets.immutablewebapp.org/c17080f1/vendor.js" type="text/javascript"></script>
215+
<script src="https://assets.immutablewebapp.org/c17080f1/main.js" type="text/javascript"></script>
78216

79217
</body>
80218
</html>
81219
```
82220

221+
This file has interesting qualities.
222+
223+
- It is 100% configuration!
224+
- It is strikingly similar to a kubectrl deployment YAML or a docker-compose.yaml
225+
226+
MORE STUFF HERE
227+
228+
### Conclusions
229+
230+
## Concepts
231+
232+
The following concepts define the core requirements for an _Immutable Web Application_. They are framework and infrastructure agnostic.
233+
234+
### _Permabundles_
235+
236+
Permabundles are the static assets that make up a single-page application.
237+
238+
- They must be available at a [permalink](https://en.wikipedia.org/wiki/Permalink) that is uniquely versioned or fingerprinted and _independent of the web application environment(s)_.
239+
240+
- They must be configured for long-term caching. (`cache-control: public, max-age=31536000, immutable`)
241+
242+
- __They must not contain any environment-specific configuration.__
243+
244+
### `index.html` <span style="font-size:larger;">___is___</span> configuration
245+
246+
An `index.html` exists for each environment.
247+
248+
- They must contain fully-qualified references to the _permabundle_ assets.
249+
250+
- They must never be cached by the browser or a public cache that cannot be purged on-demand. (`cache-control: no-store`)
251+
252+
- __They must define all environment-specific configuration.__
253+
83254
## Workflow
84255

85256
The workflow is defined by its separation of build tasks and release tasks. Specifically, the publishing of permabundles as a _build_ and the publishing of index.html as a _release_.

0 commit comments

Comments
 (0)