Notes and Excerpts of 12 Factor App
Pre-read: https://12factor.net/
Excerpt from Wikipedia
The Twelve-Factor App methodology is a methodology for building software-as-a-service applications. These best practices are designed to enable applications to be built with portability and resilience when deployed to the web
Following the 12-Factor App methodology ensures that applications are designed for portability, scalability, and continuous deployment, which are essential for modern cloud environments. It promotes best practices for development, deployment, and maintenance, leading to more robust, maintainable, and scalable applications.
A - {Admin Process}
B B - {Backing Services, Build, Release, and Run}
C C C - {Codebase, Config, Concurrency}
D D D - {Dependencies, Dev/Prod Parity, Disposability}
L - {Logs}
P P - {Ports, Processes}
The 12 factors briefly
1 | Codebase | One codebase per app. One App can have multiple versions of deployments |
2 | Dependencies | All app dependencies should be externally declared in supporting build file like pom.xml or build.gradle in Java based projects or package.json in NodeJs projects |
3 | Config | All app configs like DB creds, Thread Pool configs etc must be externalized and injected into deployment environment through env vars or vm args |
4 | Backing Services | Any external resources that your app communicates with is a backing service. Every backing service should be replaceable without changing code. For example, we should be able to switch from SQLite to Postgresql |
5 | Build, Release, Run | In CI/CD pipeline script the 3 stages build, release, and deploy/run stages must be separated to easily roll-back the deployment to previous release in case of deployment failure |
6 | Processes | App should be stateless that is every instance of the app should be independent of the other instance, if there is any persistent state to be saved it must be saved to some DB |
7 | Port Binding | Every App deployment should be bound to a port for the consumers to call APIs and connect to the app. 12-factor apps are self-contained apps meaning they include their own web-server library. Example: SpringBoot app comes with Tomcat by default |
8 | Concurrency | Talks about scaling out of the app horizontally |
9 | Disposability | App Processes are designed to startup quickly and shutdown gracefully |
10 | Dev/Prod Parity | All deployment environments should be as similar as possible in terms of Tech Stack |
11 | Logs | All instances of the deployment should write logs to stdout and a centralized logging service will aggregate logs from all instances of the app |
12 | Admin Processes | All the Admin tasks like DB migration script code, report generation code, running an ad-hoc task should be a part of the same code base and during deployment we should be able to create another docker image that when run as container should start the admin tasks and run independently of the main process which might be running as another docker container process |
The 12 factors in summary
- Codebase
- A 12 factor app is always tracked in version control system like Git, Mercurial, or Subversion
- A single code base per app should be maintained
- A running instance of the app is a deploy
- A single app can have multiple deploys like dev, test/qa, staging, prod
- Note:
- If there are multiple codebases or repos, 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 12-factor app
- Multiple apps sharing the same code is a violation of 12 factor app. The solution here is to factor shared code into libraries which can be included through the dependency manager
- Dependencies
- All dependencies should be declared explicitly
- One benefit of explicit dependency declaration is that it simplifies setup for developers new to the app
- The new developer can just checkout the app’s codebase onto their development machine, requiring only the language runtime, dependency manager installed as prerequisites
- Example: Given a Java repo, the developer should have 2 prerequisites installed
- Java SDK
- A build tool like maven or gradle based on app’s techstack
- And, developer should be able to just clone the repo and do
mvn clean install
to install all the dependencies
- Example: Given a Java repo, the developer should have 2 prerequisites installed
- Examples:
- In Java we have
maven
as package manager or build tool andpom.xml
as manifest file to declare dependencies- The build command
mvn install
will install all the dependencies declared inpom.xml
- The build command
- In ruby, we have
Bundle
as package manager or build tool andGemfile
as manifest file to declare dependencies- The build command
bundle install
will install all the dependencies declared inGemfile
- The build command
- In Java we have
- Config
- Violation: Storing configs as Constants in code is a violation of 12-factor app, which requires strict separation of config from code
- An app’s config is everything that is likely to vary between deploys(DEV, TEST/QA, STAGE, PROD). This includes
- Credentials to external/backing services such as Database credentials, Azure SAS(Shared Access Signature) tokens to connect to Azure services like Event Hub, Blob Store etc.
- A litmus test for whether an app has all the configs correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials
- In SpringBoot Framework, we have Spring profiles feature to inject environment specific configs like dev, qa, stage, and prod.
- However, any secrets should still be injected via runtime args or env variables or read from keyvault in your build pipeline or added as env vars to your deployment target container or system
- Backing Services
- A backing service is any service that the app consumes over the network as a part of its normal operation
- Examples include datastores(such as MySQL or Postgresql), messaging/queuing systems(such as RabbitMQ or Kafka)
- From the apps perspective each backing service is a resource that is attached to the app
- The code for 12-factor app makes no distinction between local and third party services. To the app, both are attached resources, accessed via URL or other locator/credentials stored in the config
- A litmus test to check if backing services are attached properly to the app is by swapping the resource without making changes the codebase. Example: You should be able to connect to local DB or the cloud/Managed DB without making any code changes in the app
- A backing service is any service that the app consumes over the network as a part of its normal operation
- Build, release, run
- A proper CI/CD pipeline should be defined, and it should have separate build, release, and run stages
- build: This is where your source code is converted into the runnable artifact
- release: Your release should be tagged with semantic version or some increasing number like v99
- deploy/run: In this stage your app will be deployed into the target environment and the apps main process is triggered or launched that concludes your app deployment
- The benefit of separating these tasks into different build stages is separation of concerns and during a release or deployment stage if it fails we can roll back to previous release or deployment. Also, we can build once and reuse the artifact like docker image with tag in release or deploy/run stages.
- Processes
- Applications should be deployed as one or more stateless processes with persistent data stored on backing service
- Each process should be stateless and share-nothing with other processes. Every process must be independent and can be replaced or scaled up and down without affecting other processes
- Stateless Process:
- Meaning the application can create and consume transient state in the middle of handling a request or processing a transaction, but that should all be gone by the time the client has been given a response
- And, all long-lasting state must be external to the application/process, provided by the backing services
- So, the concept isn’t the state cannot exist, it is that it cannot be maintained within your application.
- Port binding
- Refers to the practice of the application exposing its functionality by binding to a port and listening for incoming requests.
- The application runs as a standalone service by binding to a specific port, handling incoming HTTP requests, or other protocol requests directly
- Unlike traditional web applications that rely on an external web server like Apache or Nginx to handle requests, a 12-factor app includes its own web server library(Example: Spring Boot comes with Tomcat(Application server having webserver + servlet container), Express.js)
- Concurrency
- It is advocated by scaling individual processes
- The concurrency principle advices that cloud-native applications should scale out. There was a time when, if an application reached the limit of its capacity, the solution was to increase its size. If an application could only handle some number of requests per minute, then the preferred solution was to increase its size. If an application could only handle some number of requests per minute, then the preferred solution was to simply make the application bigger i.e., adding CPUs, RAM, and other resources(Virtual or Physical) to a single monolithic application is called vertical scaling
- A much more modern approach, one ideal for the kind of elastic scalability that the cloud supports, is to scale out or horizontal scaling. Rather than making a single big process even larger, you create multiple processes, and then distribute the load of your application among these processes.
- Most cloud providers have perfected this capability to the point where you can even configure rules that will dynamically scale the number of instances of your application based on load or other runtime telemetry available in a system.
- Disposability
- Processes are designed to startup quickly and shutdown gracefully, allowing for efficient management of concurrent workloads and rapid scaling.
- Dev/Prod Parity
-
All environments should be as similar as possible
- Similar in tech stacks
- You must make use of latest tools like Docker, VsCode DevContainers, and ensure that you make use of same tech stack in dev and prod environments
- Example: You use SQLite in local and Postgresql in Prod that is not recommended. Always ensure that the tech stack is consistent in all environments
- Similar in terms of app features
- The 12-factor apps are designed for continuous deployment by keeping a small gap between development and production i.e., the dev and prod environments are as similar as possible in terms of features as well.
-
- Logs
-
Applications should produce logs as event streams and leave the execution environment to aggregate
- You should consider the aggregation, processing, and storage of logs as a non-functional requirement that is satisfied by your application, but by your cloud provider or some other tool suite running in cooperation with your platform. You can use tools like ELK(ElasticSearch, LogStash, Kibana), Splunk, Sumlogic, or any number of other tools to capture and analyze your log emissions.
- Stream logs to stdout(Standard Output): Applications should write their logs to the stdout instead of storing them in flat files or databases. Writing logs to stdout ensures that they are automatically collected by the environment, such as containers or orchestration platforms, making them readily available for further processing and analysis.
- Do not manage log files internally: The 12-factor app discourages applications from implementing their own log rotation or archival mechanisms. Instead, it recommends relying on the infrastructure’s logging mechanism to handle log rotation and storage.
- Separate application and system logs: Separating these logs helps in identifying and diagnosing issues more efficiently
- Log Aggregation and Analysis: 12-factor app encourages using centralized logging solutions or log aggregators to collect and analyze logs from mulitple instances of the application. This enables better visibility into the application’s performance and behavior, making it easier to detect anomalies and troubleshoot.
-
- Admin Processes
- If you have an app process running inside a docker container, to perform some admin tasks like run a database migration script, or some report generation task or any other admin tasks you can generate a docker image from the same code base and run as container but launch/run the admin code file or script.