12 Factor Apps
7 min read hello world software-engineering · 12-factorWhat is 12-factor?
12 Factor is a methodology for building(language agnostic) software-as-a-service apps that:
Directly from the manifesto:
- Use declarative formats for setup automation, to minimize time and cost for new developers joining the project
- Have a clean contract with the underlying operating system, offering maximum portability between execution environments
- Are suitable for deployment on modern cloud platforms, obviating the need for servers and systems administration
- Minimize divergence between development and production, enabling continuous deployment for maximum agility
- 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 Practice | Good Practice |
---|---|
Use the file system to maintain the different versions | Use a source code management system. Git, SVN, Mercury, as examples. |
2. Dependencies
Explicitly declare and isolate dependencies.
You need the ability to specify what gets used and which version of it we are using
Language | Description | Example |
---|---|---|
Go | go.mod file | require(contrib.go.opencensus.io/exporter/ocagent v0.4.12) |
Java | pom.xml, build.gradle, build.xml | <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.1.4</version></dependency> |
Node | package.json | "dependencies": { "@cp/athena-agent-utils": "^1.1.15"} |
Python | requirements.txt | Django==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:
- URL/URI
- Port Numbers
- User Name/Password or other auth credentials
- Debug/Feature flags
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
- Puppet
- Kubernetes ConfigMaps
- Spring’s configuration server
- Databases/Data
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
- Database connections like Postgres, Mongo, Dgraph/Neo4j
- 3rd Party APIs consumed over the network
- Queue systems like Rabbit MQ/SQS
- Email/SMTP
- Caching Systems like Redis or Memcache
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
- Build (creating deliverables)
- Release (combine deliverable with the externalized configurations) In today’s context it is often docker images, but can also include .exe or other executables
- Run the release/application. Every time you start the application, it is from the same release, providing consistent, repeatable steps.
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:
- HTTP
- HTTPS/TLS
- FTP
- UDP
- TCP
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
- There is concurrency
- It restarts quickly
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
- Domain-specific telemetry
- Performance monitoring
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:
- When shipping orders are received: date/time
- Where shipping orders are coming from city/province/country
- Types of customer that is shipping: business/industry or individual
For streaming media companies it would be:
- What are people watching
- When are they watching it
- Where are they watching it from (geographically)
- Where are they watching it from (device: Desktop, Mobile, SmartTV)
- How long are they watching
App Performance Monitoring (APM)
APM provides data regarding how well your application/service performs. Moreover, it would include data such as:
- Where does a request fail
- When does a request fail
- What is the latency of the request, and potentially sub-stages of the request
Authentication/Authorization (Security)
Security in your application should not be an afterthought. Take the time to think through the design of
- How you secure access to your application/service.
- Is going to be role-based, activity based, or some combination of both.
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.
- OAuth/OAuth2
- SSO (Single Sign-On)
- MFA (Multi-Factor Authentication)
API First
Last but not least (note the irony here), design your API first. It can help
- Drive the conversation with stakeholders
- Identify missing/incomplete requirements
- Jump start documentation efforts
- Define use cases and acceptance criteria
Some high-level takeaways
- Provide signs of life, when your app starts
- Use leverage env variables
- Log when something happens
- Don’t assume that things are up. If it’s not up, retry connections
- Do not hard code port numbers, URLs, … anything but logic. Defaults are okay, but they should be overrideable
- Handle renewing credentials
- Clean shutdowns: close connections, files, and then log the shutdown
- 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
- The Twelve-Factor App
- Beyond the 12 Factor App by Kevin Hoffman
- Kelsey Hightower HashiCorp Tech Talk
- Joe Kutner 5 Years of 12 Factor Apps
comments powered by Disqus