The 12 Factor App: Architecture for Scalable Applications

Lecture



The 12 Factor App methodology is an important blueprint for developing scalable application architecture. Here is what it means for application architects and their architecture.

Building applications that run at web scale is hard work. Risks are everywhere - from an application going down due to network overload, to a competitor grabbing your market share because they ship more code and reach demanding users faster than you can. Any advantage you can gain toward building working code at web scale better and faster works in your favor.

Fortunately, the challenges of operating at web scale are well understood. There are solutions. One of them is the «12 Factor App», published in 2011 by Adam Wiggins. The «12 Factor App» is a set of principles that describe a way of building software which, when followed, allows companies to create code that can be reliably released, quickly scaled, and maintained in a consistent and predictable manner.

Below is a brief summary of the principles behind the «12 Factor App».

I. Codebase

One codebase tracked in version control, many deploys

Image
The 12 Factor App: Architecture for Scalable Applications

The codebase principle states that all assets related to the application, from source code to the initialization script and configuration settings, are stored in a source code repository that is accessible to developers, testers, and system administration staff. The source code repository is also accessible to all automation scripts that are part of the continuous integration / continuous delivery (CI / CD) processes which form part of the enterprise software development lifecycle (SDLC).

II. Dependencies

Explicitly declare and isolate dependencies

Image
The 12 Factor App: Architecture for Scalable Applications

The dependencies principle states that only code that is unique and relevant to the purpose of the application is stored in version control. External artifacts, such as Node.js packages, Java .jar files, or .NET DLLs, should be referenced in a dependency manifest that is loaded into memory during development, testing, and the production runtime environment. You do not want to store artifacts alongside the source code in the source code repository.

III. Configuration

Store configuration in the environment

Image
The 12 Factor App: Architecture for Scalable Applications

The configuration principle states that configuration information is injected into the runtime environment as environment variables or as settings defined in an independent configuration file. While in some cases it is acceptable to store default settings that can be overridden directly in the code, settings such as the port number, dependency URLs, and state parameters like DEBUG should exist separately and be applied at deployment. Good examples of external configuration files are a Java properties file, a Kubernetes manifest file, or a docker-compose.yml file.

The benefit of storing configuration settings separately from the application logic is that you can apply configuration settings according to the deployment path. For example, you might have one set of configuration settings for a deployment intended for the testing environment, and another set for a deployment intended for the production environment.

IV. Backing Services

Treat backing services as attached resources

Image
The 12 Factor App: Architecture for Scalable Applications

The backing services principle encourages architects to treat external components such as databases, mail servers, message brokers, and independent services that can be provisioned and maintained by systems staff as attached resources. Treating resources as backing services promotes flexibility and efficiency in the software development lifecycle (SDLC).

V. Build, Release, Run

Strictly separate the build and run stages

Image
The 12 Factor App: Architecture for Scalable Applications

The build, release, and run principle breaks the deployment process into three reproducible stages that can be created at any time. In the build stage, code is retrieved from source control and built / compiled into artifacts stored in an artifact repository, such as Docker Hub or a Maven repository. Once the code has been built, configuration settings are applied in the release stage. Then, in the Run stage, the runtime environment is scripted, provisioned by means of a tool such as Ansible. The application and its dependencies are deployed into the newly provisioned runtime environment.

The key to build, release, and run is that this process is entirely ephemeral. If anything in the pipeline is destroyed, all artifacts and environments can be rebuilt from scratch using the assets stored in the source code repository.

VI. Processes

Run the application as one or more stateless processes

Image
The 12 Factor App: Architecture for Scalable Applications

The processes principle, which could more accurately be called stateless processes, states that an application built according to the 12 Factor App structure will run as a set of stateless processes. This means that no process keeps track of the state of another process, and that no process tracks information such as session or workflow state. A stateless process makes scaling easier. When a process is stateless, instances can be added and removed to handle a particular load at a particular point in time. Because each process runs independently, the absence of state prevents unexpected side effects.

VII. Port Binding

Export services via port binding

Image
The 12 Factor App: Architecture for Scalable Applications

The port binding principle states that a service or application is identified on the network by a port number rather than by a domain name. The reason is that domain names and their associated IP addresses can be assigned on the fly through manual manipulation and automatic service discovery mechanisms. Thus, using them as a reference point is unreliable. Exposing a service or application to the network by port number, however, is more reliable and easier to manage. At the very least, potential problems arising from a conflict between assigning a port number privately for the network and the public use of that same port number by another process publicly can be avoided using port forwarding.

The core idea behind the port binding principle is that consistent use of a port number is the best way to expose a process on the network. For example, patterns have emerged in which port 80 is standard for web servers running under HTTP, port 443 is the default port number for HTTPS, port 22 is for SSH, port 3306 is the default port for MySQL, and port 27017 is the default port for MongoDB.

VIII. Concurrency

Scale out via the process model

Image
The 12 Factor App: Architecture for Scalable Applications

The concurrency principle recommends organizing processes according to their purpose, then separating those processes so they can be scaled up and down as needed. As shown in the figure above, the application is accessible on the network through web servers that run behind a load balancer. The group of web servers behind the load balancer, in turn, uses the business logic that resides in the Business Service processes, which run behind their own load balancer. If the load on the web servers increases, that group can be scaled in isolation to meet current demand. If, however, a bottleneck arises due to the load placed on the business service, that tier can be scaled independently.

Supporting concurrency means that different parts of the application can be scaled according to the needs at hand. Otherwise, when concurrency is not supported, architectures have no choice but to scale the application in its entirety.

IX. Disposability

Maximize robustness with fast startup and graceful shutdown

 The 12 Factor App: Architecture for Scalable Applications

The Disposability principle states that applications should start up and shut down gracefully. This means performing all necessary «cleanup» before the application becomes available to consumers. For example, a graceful startup ensures that all database connections and access to other network resources are operational. In addition, any other necessary setup work has been completed.

As for shutdown, disposability supports properly closing all database connections and other network resources and logging all shutdown operations, as shown in the code example above.

X. Dev / Prod Parity

Keep development, staging, and production as similar as possible

Image
The 12 Factor App: Architecture for Scalable Applications

The Dev / Prod Parity principle means that all deployment paths are similar but independent, and that a deployment does not make «jumps» to a different deployment target.

The figure above shows two versions of the application code. Version V1 is intended for release into the production environment. The new version V2 is intended for the development environment. Both V1 and V2 follow the same deployment path: from build to release and then run. If code version V2 is deemed ready for production, the artifacts and settings relating to version 2 will NOT be copied into the production environment.

Rather, the CI / CD process will be adjusted to set the deployment target of V2 to Production. The CI / CD process will follow the expected build, release, and run pattern toward this new target.

As you can see, Dev / Prod Parity is very similar to Build, Release, and Run. The important distinction is that Dev / Prod Parity provides the same deployment process for the production environment as for development.

XI. Logs

Treat logs as event streams

Image
The 12 Factor App: Architecture for Scalable Applications

The logs principle supports sending log data in a stream that various interested consumers can access. The process of routing log data should be decoupled from the processing of log data. For example, one consumer might be interested only in error data, while another consumer is interested in request / response data. Yet another consumer might be interested in storing all log data to archive events. An added benefit is that even if the application dies, the log data persists afterward.

XII. Admin Processes

Run administrative / management tasks as one-off processes

Image
The 12 Factor App: Architecture for Scalable Applications

The admin processes principle states that administrative processes are first-class citizens in the software development lifecycle and should be treated as such. The figure above shows a service named Orders, deployed as a Docker container. In addition, there is an admin service named dataSeeder, which can feed data into the Orders service. The dataSeeder service is an administrative process intended for use with Orders, as shown in the diagram below.

Image
The 12 Factor App: Architecture for Scalable Applications

However, even though dataSeeder is an administrative process, it is given a build, release, and run deployment path similar to the Orders service. It is also released according to the Codebase and Dev / Prod Parity principles. The dataSeeder admin process is not separate from the overall SDLC, but rather is part of it.

Conclusions

The 12 Factor App principles are designed to allow developers to build applications intended to run at web scale. At first they may seem overwhelming, and in many ways they are. Having to rethink the very essence of how you build software can be a daunting task.

Fortunately, implementing the principles of the «12 Factor App» is not an all-or-nothing proposition. You can adopt them in small, digestible pieces, starting with the first and then gradually moving on to the rest. The trick is to commit to following the principles and then take the first step.

See also

  • DDD
  • SOLID
  • Don’t repeat yourself
  • GRASP
  • KISS
  • YAGNI

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Software and information systems development"

Terms: Software and information systems development