The Twelve-Factor App: Design Principles of Software-As-A-Service

Michael Zhao
7 min readApr 8, 2024

--

The Twelve-Factor App

What Is The Twelve-Factor App

The Twelve-Factor App is a set of principles and best practices for building modern software-as-a-service (SaaS) applications.

Originally introduced by engineers at Heroku, a cloud platform-as-a-service (PaaS) provider, the Twelve-Factor principles are designed to address the challenges developers face when developing applications intended for deployment in cloud environments, enabling them to create applications that are scalable, resilient, and easy to manage.

The Twelve-Factor methodology is not a rigid set of rules but rather a set of guidelines that encourage developers to adopt a modular, scalable, and maintainable approach to application development. By adhering to these principles, developers can create applications that are more predictable, easier to deploy, and less prone to errors.

SaaS applications are typical run in the web environment, it is also known as Web Apps. In this article I will use the term web app to refer to a SaaS application that applies the Twelve-Factor design concepts.

A Brief Mind Map

Below is a mind map that summarizes all the concepts of the Twelve-Factor.

Mind map of the twelve-factor app

If the text size in the diagram is too small to read, please try opening the image in a new web browser tab. If it is still not readable, please download the original image file from here. The source file of the mind map is also available via the link. Feel free to use and modify it.

The Twelve-Factor Principles

I. Codebase

One codebase tracked in revision control, many deploys

The first principle is about the source code base. It emphasizes 3 points:

  • Code must be managed with version control tools like Git, Subversion or Mercurial.
  • There should be only one code repo for an app.
    - If an app is across multiple repos, it’s a distributed system. The system should be further broken down into several single-repo apps.
    - If multiple apps are created from the same repo, the common code should be abstracted to form a separate library repo. Each app should form in its own code repo, and references the common code as dependency.
  • Multiple deployments can be produced from the same code repo.
    - Different deployments are created for different purposes.
    - Different deployments can use different versions of code.

The following diagram from the https://12factor.net/ website illustrates the relationship between code and deployments.

II. Dependencies

Explicitly declare and isolate dependencies

The target of managing dependencies of a web app is being explicit and isolate. Specifically, 3 advices are given:

  • Declare all dependencies, completely and exactly, via a dependency declaration manifest.
  • Do not rely on implicit existence of system-wide packages.
  • Ensure no implicit dependencies “leak in” from the surrounding system.

Cargo for Rust language is a good example of managing application dependencies. It applies the above principles:

  • A Cargo.toml file is used to declare all dependencies. Sources and versions of the dependency crates are specified clearly.
  • Cargo is typically installed per user, not for the whole system. So the implicit system-wide packages do not exist. For the same reason, no global dependency leaks in.

III. Config

Store config in the environment

Regarding the configuration of a web app, 2 principles are to be bared in mind:

  • Configuration should be separated from source code.
    - Different deployment environments require different configurations, it’s impossible to unify the config and fix it in the code.
    - While it’s good idea to provide recommended configurations for typical deployments.
  • A web app should work with the configurations in the form of environment variables.

IV. Backing services

Treat backing services as attached resources

The following quoted image shows the expected way of using backing services from a web app.

A backing service is any service the app consumes to support its functionalities. A backing service could be either local (like the database service) or remote (like the storage service or email service provided by the third party vendors).

The code of a web app should call the local service and remote service in the same standard way. To the app, both types of service are attached resources, accessed via a URL or other locator / credentials stored in its configuration.

Furthermore, it would be ideal if the backing services can be attached and detached at will.

V. Build, release, run

Strictly separate build and run stages

Build, release and run stages must be divided clearly without any crossing:

  • Build is triggered by developers for new code change. Any code change should produce new release. Existing releases must not be modified.
  • Releases should be tracked with unique ID’s (such as a timestamp or a serial number).
  • Do not modify the app at runtime, since there is no way to propagate those changes back to the build stage.

VI. Processes

Execute the app as one or more stateless processes

The process(es) of a web app should be stateless. To ensure data persistence, it’s essential to store it in a stateful backing service, often a database, rather than in any kind of local resource. A web app developer should keep in mind that any cached data in memory or on disk may not be accessible for future requests or tasks. Given the likelihood of multiple processes of each type of web app running concurrently, it’s probable that a subsequent request will be handled by a different process.

VII. Port binding

Export services via port binding

A web app should be self-contained. It shouldn’t depend on the existence of any external web server in the deployment environment.

To provide its service outside, a web app typically binds to a port and exports HTTP access.

VIII. Concurrency

Scale out via the process model

The following quoted diagram demonstrates the guideline of implementing concurrency of web apps.

For the purpose of workload diversity, different types of app process should be defined. Each type is in charge of a particular kind of work.

For the purpose of scaling, multiple instances of the same type of web app can be initialized. Workload of the belonging type should be balanced between the instances.

IX. Disposability

Maximize robustness with fast startup and graceful shutdown

A web app should be disposable. It means that the app needs to startup quickly and shutdown gracefully.

The startup time is the time interval from the command to launch the app being executed to the occasion when the program ready to take request and provide service. Quick startup make the deployment smooth and efficient.

Shutdown is more complex. Generally, the web app should begin to terminate itself once receiving a SIGTERM signal. But rather than quit immediately, the app needs to exit gracefully with following actions in order:

  • Stop listening to the port and taking any new request
  • Finish the requests that are being handled
  • Exit

An extra requirement regarding disposability is being robust against sudden death. The suggested practice is using a queueing backend, that returns jobs to the queue when clients disconnect.

X. Dev/prod parity

Keep development, staging, and production as similar as possible

Gaps in time, personnel and tools may exist between development and production deployments. Continuous Deployment (CD) methodologies should be applied to close the 3 types of gaps.

Here comes the analysis of the gaps and the corresponding mitigation strategies:

  • The time gap
    - Problem: Code is delivered to production weeks or months after written by the developer.
    - Mitigation: CD makes the code enter deployment / production hours or even minutes after written.
  • The personnel gap:
    - Problem: Developers only write code, Ops engineers only deploy.
    - Mitigation: Perform cross-functional development. Developers also take part in deployments.
  • The tools gap:
    - Problem: Developers may use one software stack while the production uses another.
    - Mitigation: Keep development and production as similar as possible.

XI. Logs

Treat logs as event streams

Regarding logging, a web app should do nothing more than writing its event stream to stdout directly.

The logging output stream will be handled in different ways in development and production:

  • In development environment, the output can be viewed on terminal or directed to files.
  • In production environment, the output can be routed to advanced tools for archiving or analyzing.

The receivers / consumers of the output stream are not visible to or configurable by the web app, and instead are completely managed by the execution environment.

XII. Admin processes

Run admin/management tasks as one-off processes

Quite often, developers may need to do some one-off administration or maintenance tasks for the web app. There are various reasons for such operations: database migrations, cleaning unused data / files, or fixing errors.

The admin / maintenance code must align with the production code:

  • Admin code should be delivered with the production code together to avoid synchronization issue.
  • Admin code should work with a trackable release version of the production code and the accompanying configuration.
  • Admin code should run in the identical environment of the production.

Reference

--

--