12 Factor Apps

      7 min read           ·

What is 12-factor?

12 Factor is a methodology for building(language agnostic) software-as-a-service apps that:

Directly from the manifesto:

  1. Use declarative formats for setup automation, to minimize time and cost for new developers joining the project
  2. Have a clean contract with the underlying operating system, offering maximum portability between execution environments
  3. Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration
  4. Minimize divergence between development and production, enabling continuous deployment for maximum agility
  5. Can scale up without significant changes to tooling, architecture, or development practices.

Factors

1. Codebase

One codebase, tracked in version control that we can run many deployments from.

Bad PracticeGood Practice
Use the file system to maintain the different versionsUse a source code management system. Git, SVN, Mercury, as examples.
Bad practiceGood practice

2. Dependencies

Explicitly declare and isolate dependencies.

You need the ability to specify what gets used and which version of it we are using

LanguageDescriptionExample
Gogo.mod filerequire(contrib.go.opencensus.io/exporter/ocagent v0.4.12)
Javapom.xml, build.gradle, build.xml<dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.1.4</version></dependency>
Nodepackage.json"dependencies": { "@cp/athena-agent-utils": "^1.1.15"}
Pythonrequirements.txtDjango==1.10.5

3. Configuration

Store config in the environment

First, let’s back up and define what a configuration is. Configurations are everything likely to vary between deployments. These include:

Now back to our regularly scheduled programming. 12 Factor implies that you should not store your configuration in a file in your application. (Ahem, cough, cough) Python/Django core/settings/dev.py But you can source those configs using other tools

Why bother? It reduces the likelihood of developers accidentally committing/sharing secret credentials. You ensure that your application/service does not have logic switching based on the environment.

4. Backing Services

Treat backing services as attached resources.

Backing services can include

The key feature to remember here is that the services can be attached or detached at will. So, if a backing service is unavailable, we can replace/failover to another one with the possible need to update the configuration(s).

5. Build, Release, Run

Strictly separate build and run stages

If you already adhere to the 1-codebase and Configuration principles, then this should be relatively easy to accomplish. Use Continuous Integration/Delivery tools like CircleCI, Jenkins, Travis to manage the

6. Processes

Execute the app as one or more stateless processes

The more stateless an application is, the more fault tolerant it becomes. If a single service/microservice fails, the core application does not fail. In short, your application should fail gracefully.

If state information is needed, use a backing service like a database to maintain the state data. Using things like sticky sessions adds an extra layer of complexity the ROI.

7. Port Binding

Export services via port binding

Make your services self contained and easily replaceable, by exposing your service behind a specific port and running as it’s own process. The communication protocol to communicate with the service can be:

8. Concurrency

Scale out via the process model

You may have noticed by now that a lot of the Principles build on each other. This one follows in that vein. Concurrency in this context means that if you want to scale up your application/service, you should be able to deploy/run your release as additional instances, instead of adding more memory to the CPU to increase throughput.

This principle can be even more advantageous if it can scale(STOP/START), restart, or clone itself as needed.

9. Disposability

Maximize robustness with fast startup and graceful shutdown

The goal here is to minimize user impact as much as possible. If/when your service stops, your user should not be negatively impacted because of either

You need to ensure that when the process shuts down, it closes any connections or file system access.

10. Dev/Prod Parity

Keep development, staging, and production as similar as possible

This factor is almost self-explanatory. To keep short, don’t go long period (or several commits) between releases.

11. Logs

Treat logs as event streams

Instead of just sending logs to a log file, also send them to StdOut/StdErr. This approach can be beneficial because logs go to different locations. Providing logs to StdOut, then aggregation tools like Fluentd and LogStash can consolidate across multiple instances.

Note what is not said here. You can still use log files, but log files should not be the only logging.

12. Admin Processes

Run admin/management tasks as one-off processes.

Administrative stuff like Creating/updating database schemas should be a one-off task that happens once and happens separate from the main application. One-offs would typically incorporated as scripts

Beyond the 12 Factors (Adaptations for the cloud)

Telemetry

Take specific measurements of something and then transmit those measurements elsewhere.

Running applications or services in the cloud means that you may not have the same level of access and insight into what it is doing as you would if it were running locally. As a result, you may need to send signals (or data) out to communicate the general health and status.

Ok Great, ET should phone home, but what to say? There are 3 typical categories of data:

Health

Most cloud providers provide health data. AWS CloudWatch, Google Cloud Stackdriver, Azure Monitor to name a few.

Domain Specific Telemetry

Domain-specific telemetry is data selected by you and directly make sense to your business. This type of data is typically fed into big data systems and used for analysis and forecasting.

For large shipping/logistics companies this may include:

For streaming media companies it would be:

App Performance Monitoring (APM)

APM provides data regarding how well your application/service performs. Moreover, it would include data such as:

Authentication/Authorization (Security)

Security in your application should not be an afterthought. Take the time to think through the design of

Keep audit trails/logs of who is accessing your application/service, when they access it, and the permissions/roles.

There are plenty of Auth tools that allow you to treat security as another backing service.

API First

Last but not least (note the irony here), design your API first. It can help

Some high-level takeaways

  1. Provide signs of life, when your app starts
  2. Use leverage env variables
  3. Log when something happens
  4. Don’t assume that things are up. If it’s not up, retry connections
  5. Do not hard code port numbers, URLs, … anything but logic. Defaults are okay, but they should be overrideable
  6. Handle renewing credentials
  7. Clean shutdowns: close connections, files, and then log the shutdown
  8. Design your API as if it is a backing service (even if it is not). It allows you to grow and adapt your application to new demands, consumers(user/clients) and load.

Resources

Disagree? Did I miss something? Comment below or you can Tweet me with #software-engineering .
comments powered by Disqus