Skip to content

Latest commit

 

History

History
204 lines (129 loc) · 11.8 KB

twelve-factor-app.adoc

File metadata and controls

204 lines (129 loc) · 11.8 KB

The twelve-factor app

Overview

A methodology for building SaaS apps, in any language, that are cloud-friendly, portable and easy to get started with and contribute to for new members. They also aim to have parity between development and production environments.

1. Codebase

One codebase tracked in revision control, many deploys.

There is always a one-to-one correlation between the codebase and the app:

  • If there are multiple codebases, it’s not an app – it’s a distributed system. Each component in a distributed system is an app, and each can individually comply with twelve-factor.

  • Multiple apps sharing the same code is a violation of twelve-factor. The solution here is to factor shared code into libraries which can be included through the dependency manager.

There is only one codebase per app, but there will be many deploys of the app. A deploy is a running instance of the app. This is typically a production site, and one or more staging sites (including a local deploy on developers' machines).

2. Dependencies

Explicitly declare and isolate dependencies.

A twelve-factor app never relies on implicit existence of system-wide packages. It declares all dependencies, completely and exactly, via a dependency declaration manifest. Furthermore, it uses a dependency isolation tool during execution to ensure that no implicit dependencies “leak in” from the surrounding system. The full and explicit dependency specification is applied uniformly to both production and development.

Twelve-factor apps also do not rely on the implicit existence of any system tools. Even if the tool is available on most systems, it will not be available on all, and so it should be packaged with the app.

How

Containerization helps achieve this, and so does encapsulation of build requirements (JDK, build tool like Gradle, etc.).

3. Config

Store config in the environment (as opposed to in the app’s codebase).

An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes:

  • Resource handles to the database, Memcached, and other backing services

  • Credentials to external services such as Amazon S3 or Twitter

  • Per-deploy values such as the canonical hostname for the deploy

Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.

How

This can be achieved by maintaining a separate repository for configuration, that is injected or deployed into each environment (e.g. as files, config-maps in the containers or command-line arguments), and can use templating languages (freemarker, terraform) to facilitate managing the variation between environments and generating the final configs for each deployment environment.

4. Backing services

Treat backing services as attached resources.

A backing service is any service the app consumes over the network as part of its normal operation. Examples include datastores (such as MySQL or CouchDB), messaging/queueing systems (such as RabbitMQ or Beanstalkd), SMTP services for outbound email (such as Postfix), and caching systems (such as Memcached).

The code for a twelve-factor app makes no distinction between local and third party services.

A deploy of the twelve-factor app should be able to swap out a local MySQL database with one managed by a third party (such as Amazon RDS) without any changes to the app’s code.

How

This can be achieved by treating each backing resource as external (remote) and accessed by externally managed configuration (whether local or remote).

5. Build, release, run

Strictly separate build and run stages.

A codebase is transformed into a (non-development) deploy through three stages:

  • The build stage is a transform which converts a code repo into an executable bundle known as a build. Using a version of the code at a commit specified by the deployment process, the build stage fetches vendors dependencies and compiles binaries and assets. This is triggered by developers pushing new code.

  • The release stage takes the build produced by the build stage and combines it with the deploy’s current config. The resulting release contains both the build and the config and is ready for immediate execution in the execution environment. This is triggered by developers, or sometimes an automated process to create periodic releases.

  • The run stage (also known as “runtime”) runs the app in the execution environment, by launching some set of the app’s processes against a selected release. This is executed automatically, but can be triggered to restart by a developer action such as requesting to deploy a new release.

The twelve-factor app uses strict separation between the build, release, and run stages. For example, it is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage.

How

CI/CD pipeline tools (GitLab’s pipeline, GitHub’s actions) can facilitate the build stage. The release stage is often managed via release tags (in Git for example) - something immutable that represents a particular version. Runtime can be achieved by a container executor or orchestrator, or job schedulers with access to the deployed release artifacts and configuration combined.

6. Processes

Execute the app as one or more stateless processes.

Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database. The local filesystem can be used temporarily as a cache for assets in-transit. The twelve-factor app never assumes that anything cached in memory or on disk will be available on a future request or job – with many processes of each type running, chances are high that a future request will be served by a different process. Even when running only one process, a restart (triggered by code deploy, config change, or the execution environment relocating the process to a different physical location) will usually wipe out all local (e.g., memory and filesystem) state.

Web sticky sessions are a violation of twelve-factor and should never be used or relied upon. Session state data is a good candidate for a datastore that offers time-expiration, such as Memcached or Redis.

7. Port binding

Export services via port binding.

The twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.

How

Use common embedded webserver libraries, such as Jetty, Netty, Tomcat, which run within the app. This is quite standard nowadays.

8. Concurrency

Scale out via the process model.

In the twelve-factor app, processes are a first class citizen. Processes in the twelve-factor app take strong cues from the unix process model for running service daemons. Using this model, the developer can architect their app to handle diverse workloads by assigning each type of work to a process type. For example, HTTP requests may be handled by a web process, and long-running background tasks handled by a worker process.

How

When scaling or trying to increase throughput, deploy additional instances of each application process, as opposed to trying to make each process intrinsically more parallelized. This makes the code much simpler, allows for log-segragation and process-segragation so each process can be handled independently and can scale horizontally.

9. Disposability

Maximize robustness with fast startup and graceful shutdown.

The twelve-factor app’s processes are disposable, meaning they can be started or stopped at a moment’s notice. This facilitates fast elastic scaling, rapid deployment of code or config changes, and robustness of production deploys.

Processes should strive to minimize startup time. Processes shut down gracefully when they receive a SIGTERM signal from the process manager.

Processes should also be robust against sudden death, in the case of a failure in the underlying hardware. While this is a much less common occurrence than a graceful shutdown with SIGTERM, it can still happen.

How

Avoid in-app caching and expensive start-up tasks. Enable graceful shutdown features of the webserver (most server libraries support this for SIGTERM signals). To recover from sudden deaths, can leverage a queue-based architecture to pick up from where things were left off if necessary.

10. Dev/prod parity

Keep development, staging, and production as similar as possible.

Common gaps between dev and prod are:

  • The time gap: A developer may work on code that takes days, weeks, or even months to go into production.

  • The personnel gap: Developers write code, ops engineers deploy it.

  • The tools gap: Developers may be using a stack like Nginx, SQLite, and OS X, while the production deploy uses Apache, MySQL, and Linux.

The twelve-factor app is designed for continuous deployment by keeping the gap between development and production small. The twelve-factor developer resists the urge to use different backing services between development and production, even when adapters theoretically abstract away any differences in backing services.

How

  • Make the time gap small: a developer may write code and have it deployed hours or even just minutes later.

  • Make the personnel gap small: developers who wrote code are closely involved in deploying it and watching its behavior in production.

  • Make the tools gap small: keep development and production as similar as possible. E.g. leverage in-memory Postgres or MongoDB instances for unit/integration testing.

Use the same tools, configuration management and deployment procedures for each environment, and templating languages (as mentioned in #3) to only differentiate what needs to be differ between environments, leaving as much as possible consistent.

11. Logs

Treat logs as event streams.

Logs provide visibility into the behavior of a running app. Logs are the stream of aggregated, time-ordered events collected from the output streams of all running processes and backing services. A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to stdout.

How

Write logs to stdout, and use logging frameworks such as logback through slf4j to configure formatting and additional output streams. Use consistent logging formats, through logger framework configuration but also in the log messages themselves to allow for easier machine understanding later. For example, access logs should be done consistently for a web app, so the various request and response attributes are easily parsed.

12. Admin processes

Run admin/management tasks as one-off processes.

One-off admin processes should be run in an identical environment as the regular long-running processes of the app. They run against a release, using the same codebase and config as any process run against that release. Admin code must ship with application code to avoid synchronization issues.

How

This can apply to continuous or periodic admin processes as well, and can be achieved by separating the executable for such processes from the web-app executable process. This also facilitates #6 and #9, as it maintains the separate concerns as separate physical processes, and also keeps the web app itself lightweight and disposable as it is not concerned with running and handling long-running tasks that are executed say daily. Those processes are executed and managed separately, but from the same codebase and environment.