The 12 Factor App Principles
To avoid apparent setbacks in Software Development, engineers keep reinventing the wheel time and again. However, the process of developing a bug-free, scalable, high-performing, and enterprise-level web app needs much more than that. For example, a tried and tested outline like the 12 Factor App.
Now, what does the term “Twelve-Factor App” mean in software development, from where does it come, and what are 12-Factor App benefits? Let’s find out.
1. What is the 12 Factor App Methodology?
The Twelve-Factor methodology is a set of twelve best practices to develop applications developed to run as a service. This was originally drafted by Heroku for applications deployed as services on their cloud platform, back in 2011. Over time, this has proved to be generic enough for any software-as-a-service (SaaS) development.
So, what do we mean by software-as-a-service? Traditionally we design, develop, deploy, and maintain software solutions to derive business value from it. But, we don’t have to engage in this process to achieve the same result necessarily. For instance, calculating applicable tax is a generic function in many domains.Now, we may decide to build and manage this service our selves or subscribe to a commercial service offering. Such service offerings are what we know as software-as-a-service.
But, 12 Factor is certainly not a solution for every company out there that is building software. All you need to analyze is your business problems and find whether 12 Factor has a solution for it or not. The right way to apply a 12-Factor app for microservices is by prioritizing your team’s needs and the problem at hand.
2. Applying Twelve-Factor Methodology
Let’s now define a simple application that we’ll try to develop with the tools and practices we just discussed. We all love watching movies, but it’s challenging to keep track of the movies we’ve already watched.
Now, who would like to start a movie and then abandon it later? What we need is a simple service to record and query movies that we’ve watched:
This is quite a simple and standard microservice with a data store and REST endpoints. We need to define a model which will map to persistence as well.
This covers the base of our simple service. We’ll go through the rest of the application as we discuss how we implement the twelve-factor methodology in the following subsections.
2.1 Codebase (One Codebase Tracked In Revision Control, Many Deploys)
The first best practice of twelve-factor apps is to track it in a version control system.12-factor app advocates that every application should have its own codebase (repos). Multiple codebases for multiple versions must be avoided. Please do note that having branches would be fine i.e. for all the deployment environments there should be only one repo but not multiple. The principle states that an app should be tracked in a single code repository and must not share that repository with any other apps.
Multiple apps sharing the same code are a violation of the twelve-factor. Here you should opt-in for shared libraries.
From the 12-factor app perspective app, deploy meaning the running instance of an app like production, staging, QA, etc. Additionally, every developer has a copy of the app running in their local development environment, each of which also qualifies as a deploy.
Different versions (the version is like a code change that is available in one environment but not in other) may be active in multiple deploys.
- Microservices: In Microservices, every service should have its own codebase. Having an independent codebase helps you to easy CI/CD process for your applications.
Twelve-factor app advocates of not sharing the code between the application. If you need to share you need to build a library and make it as a dependency and manage through package repository like maven.
2.2 Dependencies (Explicitly Declare and Isolate the Dependencies)
Next, the twelve-factor app should always explicitly declare all its dependencies. It talks about managing the dependencies externally using dependency management tools instead of adding them to your codebase.
We should do this using a dependency declaration manifest. Java has multiple dependency management tools like Maven and Gradle. We can use one of them to achieve this goal.
From the perspective of the java, you can think of Maven as a dependency manager. You will mention all the dependencies in pom.xml file and your application will download all the mentioned dependencies from maven repository or various other repositories. Maven requires us to describe a project’s dependencies in an XML file, typically known as Project Object Model (POM)
You also need to consider the dependencies from the operating system/ execution environment perspective as well.
Microservices: All the application packages will be managed through package managers like sbt, maven.
- In non-containerized environments, you can go for configuration management tools like chef, ansible, etc. to install system-level dependencies.
- For a containerized environment, you can go for dockerfile.
2.3 Configurations (Store Configurations In an Environment)
An application typically has lots of configuration, some of which may vary between deployments while others remain the same.
Anything that varies between the deployment environments is considered as configuration. This includes:
- Database connections and credentials, system integration endpoints
- Credentials to external services such as Amazon S3 or Twitter or any other external apps
- Application-specific information like IP Addresses, ports, and hostnames, etc.
In our example, we’ve got a persistent database. We’ll need the address and credentials of the database to connect to. This is most likely to change between deployments.
A twelve-factor app should externalize all such configurations that vary between deployments. This suggest saving the configuration values in the environment variables.
You should not hardcode any configuration values as constants in the codebase. This is a direct violation of 12-factor app principles.
It advocates the strict separation between the code and configurations. The code must be the same irrespective of where the application being deployed.
As per “config”, what varies for the environment to the environment must be moved to configurations and managed via environment variables.
- Microservices: Externalize the configurations from the application. In a microservice service environment, you can manage the configurations for your applications from a source control like git (spring-cloud-config) and use the environment variables to not to maintain the sensitive information in the source control.
2.4 Backing Services (Treat Backing Resources as Attached Resources)
As per 12 factor app principles, a backing service is an application/service the app consumes over the network as part of its normal operation.
Database, Message Brokers, any other external systems that the app communicates is treated as Backing service.
12-factor app can automatically swap the application from one provider to another without making any further modifications to the code base. Let us say, you would like to change the database server from MySQL to Aurora. To do so, you should not make any code changes to your application. Only configuration change should be able to take care of it.
- Microservices: In a microservice ecosystem, anything external to service is treated as attached resource. The resource can be swapped at any given point of time without impacting the service.
By following the interfaced based programming allow to swap the provider dynamically without impact on the system. Plug-in based implementation also helps you to support multiple providers.
2.5 Build, Release, and Run (Strictly Separate Build and Run Stages)
The application must have a strict separation between the build, release, and run stages. Let us understand each stage in more detail.
- Build stage: transform the code into an executable bundle/ build package.Using a tool like Maven, this is quite trivial:
mvn clean compile test package
- Release stage: get the build package from the build stage and combines with the configurations of the deployment environment and make your application ready to run.Here, we can use Packer with a provisioner like Ansible to create Docker images:
packer build application.json
- Run stage: It is like running your app in the execution environment.
Microservices: You can use CI/CD tools to automate the builds and deployment process. Docker images make it easy to separate the build, release, and run stages more efficiently. If we use Docker as the container to release our application, running the application can be simple enough:
docker run --name <container_id> -it <image_id>
2.6 Processes (Execute the App as One or More Stateless Processes)
The app is executed inside the execution environment as a process. An app can have one or more instances/processes to meet the user/customer demands.
As per 12-factor principles, the application should not store the data in in-memory and it must be saved to a store and use from there. As far as the state concern, your application should store the state in the database instead of in memory of the process.
Avoid using sticky sessions, using sticky sessions are a violation of 12-factor app principles. If you would store the session information, you can choose redis or memcached or any other cache provider based on your requirements.
By following these, your app can be highly scalable without any impact on the system
- Microservices: By adopting the stateless nature of REST, your services can be horizontally scaled as per the needs with zero impact. If your system still requires to maintain the state use the attached resources (redis, Memcached, or datastore) to store the state instead of in-memory.
2.7 Port Binding (Export Services Via Port Binding)
A traditional web application in Java is developed as a WAR or web archive. This is typically a collection of Servlets with dependencies, and it expects a conformant container runtime like Tomcat.A twelve-factor app, on the contrary, expects no such runtime dependency. It’s completely self-contained and only requires an execution runtime like Java.
In our case, we’ve developed an application using Spring Boot. Spring Boot, apart from many other benefits, provides us with a default embedded application server. Hence, the JAR we generated earlier using Maven is fully capable of executing in any environment just by having a compatible Java runtime:
java -jar application.jar
Here, our simple application exposes its endpoints over an HTTP binding to a specific port like 8080. Upon starting the application as we did above, it should be possible to access the exported services like HTTP.
An application may export multiple services like FTP or WebSocket by binding to multiple ports.
2.8 Concurrency (Scale Out Via the Process Model)
This 12 Factor principle is related to scaling the application, and it says you should deploy more copies of your application instead of making your app larger. Basically, it supports horizontal scaling of an app instead of vertical scaling.
Using this 12-Factor rule, web developers can design their apps to handle diverse workloads by giving each type of work to a particular process type. The model indeed proves very helpful when it comes time to scale an app.
This horizontally scalable and self-contained nature of 12 Factor App processes implies adding more concurrency is a reliable and easy option.
2.9 Disposability (Maximize the 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. When the application is shutting down or starting, an instance should not impact the application state.
Graceful shutdowns are very important. The system must ensure the correct state.
The system should not get impacted when new instances are added or takedown the existing instances as per need. This is also known as system disposability.
Systems do crash due to various reasons. the system should ensure that the impact would be minimal and the application should be stored in a valid state.
- Microservices: By adopting the containerization into the deployment process of microservices, your application implicitly follows this principle at a maximum extent. Docker containers can be started or stopped instantly. Storing request, state, or session data in queues or other backing services ensures that a request is handled seamlessly in the event of a container crash.
2.10 Dev/Prod Parity (Keep Development, Staging, and Production as Similar as Possible)
The twelve-factor methodology suggests keeping the gap between development and production environment as minimal as possible. This reduces the risks of showing up bugs in a specific environment.
An app that complies with 12 Factors is designed for continuous deployment by keeping the following gaps as minimum as possible:
- The Time Gap: A developer can write a code and deploy it hours or just a few minutes later.
- The Personnel Gap: Programmers or owners of the code should be closely involved in deploying it.
- The Tool Gap: The tools used for development and production should be as similar as possible.
The twelve-factor developer resists the urge to use different backing services between development and production.
- Microservices: This is an inherent feature of the Microservices that is run using the containerization techniques.
2.11 Logs (Treat Logs as Event Streams)
Logs become paramount in troubleshooting the production issues or understanding the user behavior. Logs provide visibility into the behavior of a running application.
Twelve-factor app principles advocate separating the log generation and processing the log’s information. From the application logs will be written as a standard output and the execution environment takes care of capture, storage, curation, and archival of such stream should be handled by the execution environment.
- Microservices: In Microservices, observability is the first-class citizen. Observability can be achieved through using APM tools (ELK, Newrelic, and other tools) or log aggregations tools like Splunk, logs, etc.
By following the above-mentioned guidelines all you need is to debug an issue is to go to the central dashboard of your tool and search for it.
2.12 Admin Processes (Run Admin/Management Tasks as One-Off Processes)
There is a number of one-off processes as part of the application deployment like data migration, executing one-off scripts in a specific environment.
Twelve-factor principles advocates for keeping such administrative tasks as part of the application codebase in the repository. By doing so, one-off scripts follow the same process defined for your codebase.
Ensure one-off scripts are automated so that you don’t need to worry about executing them manually before releasing the build.
Twelve-factor principles also suggest using the built-in tool of the execution environment to run those scripts on production servers.
- Microservices: Containerization also helps here to run the one-off processes as a task and shutdown automatically one done with the implementation.
That’s all for today. Hope you have enjoyed the article. Please share your thoughts or ideas or improvements in the below comments box.