Computer scienceFundamentalsEssentialsSoftware constructionSoftware architecture

12-factor application: configuration, process and execution

10 minutes read

In this topic, we will explore the concept of Twelve-Factor Application, its core principles, and how to implement them effectively in software development practices. The Twelve-Factor Application is a set of best practices and architectural guidelines for developing modern, scalable, and maintainable software applications. By adhering to these principles, developers can ensure scalable, reliable, and manageable applications in any environment.

What is the Twelve-Factor Application?

The Twelve-Factor Application is a set of principles that guide the development and deployment of modern software applications. It was first introduced by Heroku and has since become a widely accepted standard for building cloud-native applications.

The Twelve-Factor Application is based on the idea that modern software development requires a new approach to application architecture and deployment. Traditional approaches to software development, such as the monolithic architecture, are often complex, difficult to scale, and hard to maintain. The Twelve-Factor Application addresses these challenges by providing a set of principles that help developers build applications that are scalable, resilient, and easy to manage.

It is based on twelve principles that cover various aspects of software development and deployment:

  1. Codebase: One codebase tracked in revision control, many deploys

  2. Dependencies: Explicitly declare and isolate dependencies

  3. Config: Store config in the environment

  4. Backing services: Treat backing services as attached resources

  5. Build, release, run: Strictly separate build and run stages

  6. Processes: Execute the app as one or more stateless processes

  7. Port binding: Export services via port binding

  8. Concurrency: Scale out via the process model

  9. Disposability: Maximize robustness with fast startup and graceful shutdown

  10. Dev/prod parity: Keep development, staging, and production as similar as possible

  11. Logs: Treat logs as event streams

  12. Admin processes: Run admin/management tasks as one-off processes

Let's take a closer look at these principles.

Factor 1: Codebase

The Codebase principle states that there should be only one codebase for each application and that the codebase should be tracked in revision control. This means that all developers should be working on the same codebase, and any changes to the code should be version controlled. This makes it easy to track changes, revert to previous versions, and collaborate on the code.

The codebase essentially means the set of code files that define the system's features, functionalities, and behaviors. The idea behind the Codebase principle is to make sure that we structure our code to maximize collaboration between developers, ensure consistency across different environments, and enable easy deployment of new application versions without breaking anything along the way.

For instance, we should write code that is independent of the environment in which it runs. This means that the same code should run seamlessly both locally on our laptops and in production on servers managed by someone else. Further, we should split our code into small, self-contained units called microservices or modules so that changes made to one part of the system don't affect others unnecessarily.

Core ideas:

  • One codebase, tracked in revision control, supporting multiple deployments

  • Treat separate applications as distinct codebases with individual repositories

  • Multiple instances of this single codebase will run autonomously and concurrently

Factor 2: Dependencies

The Dependencies principle refers to how our application relies on external resources to function properly. Such external resources, or dependencies, might include libraries, databases, authentication providers, or even third-party APIs. The Dependencies principle states that all of them should be explicitly declared and isolated from other parts of the system. This helps to prevent conflicts and makes it easier to manage dependencies.

The idea behind this principle is to ensure that we manage our dependencies in a controlled and predictable fashion so that nothing breaks unexpectedly due to external factors beyond our control. To accomplish this goal, there are several key strategies we can follow.

We should always use package managers (such as npm or pip) to install and update dependencies rather than manually copying files. Package managers allow us to specify exact versions of dependencies and automatically handle updates when necessary, which helps keep our code stable and reliable.

Core ideas:

  • Explicitly declare and isolate dependencies

  • Manage dependencies using appropriate dependency managers

  • Avoid relying on system-wide packages

  • Using containerization technologies can further enhance dependency isolation

Factor 3: Config

The Config principle states that all configuration should be stored in the environment. This means that the application should not rely on configuration files or hardcoded values but should instead use environment variables to configure the application. This makes it easy to configure the application for different environments, such as development, staging, and production.

Configuration tells various parts what they should do, who they should talk to, and how they should behave under certain circumstances. Without proper configuration management, we risk inconsistent results, deployment errors, or security vulnerabilities.

Therefore, we should separate configuration from code by storing them in environment variables, config files, or databases. We should not rely on comments, constants, or magic numbers scattered throughout the codebase because they're difficult to maintain, test, and share among team members. Appropriate tools can help to encrypt, version, and distribute sensitive data safely.

We should keep our configuration portable, allowing our application to adapt to different environments (development, staging, production). This is important for scaling and managing multiple instances of our application without incurring significant manual effort. Also, we should track and validate changes to the configuration to prevent accidentally introducing bugs.

Core ideas:

  • Keep configuration information separate from code

  • Treat configuration as an essential component of your application's infrastructure

  • Store configurations as environment variables, making it easier to manage, update and secure

  • Prevent sensitive data leaks by separating configuration from the application code

Factor 4: Backing Services

Backing services are any services our application communicates with to complete its mission. The Backing Services principle states that all backing services, such as databases and message queues, should be treated as attached resources. The application should not make assumptions about the location or availability of these services and should rely on clear contracts or endpoints for all the operations it requires. This makes it easy to scale the application and swap out backing services as needed.

Offloading heavy lifting to separate services, we can make our application more scalable and easier to maintain over time. Since our application talks to services rather than performing everything itself, we gain failure handling. If one service goes offline or returns incorrect results, our app can simply fall back to a backup option or try to communicate with the failing backing service again later.

In addition, considering backing services thoughtfully when building complex applications helps create simpler, more resilient architectures that play nicely with diverse ecosystems.

Core ideas:

  • Treat backing services, such as databases or message queues, as attached resources

  • Maintain loose coupling by exposing standardized API for other companies, data sources, or devices

  • Each backing service can be easily swapped and reattached as long as it adheres to the same interface

Factor 5: Build, Release, Run

The Build, Release, Run principle states that the build, release, and run stages should be strictly separated. This means that the application should be built and packaged separately from the environment in which it will be run. This makes deploying the application to different environments easy and ensures it is always built with the latest code and dependencies.

This principle encourages developers to think carefully about their deployment process, including building, releasing, and running their software. This includes using automated processes wherever possible, such as continuous integration and continuous delivery pipelines, to ensure consistent quality across all stages of development.

Another aspect of this principle could be making sure that any third-party dependencies used in the software are included and maintained along the entire pipeline. This can help reduce the likelihood of issues arising during deployment and make it easier to troubleshoot problems.

Core ideas:

  • Strictly separate build and run stages

  • Divide the software life cycle into three stages: build, release, and run

  • Plan for each stage to occur separately to ensure a consistent and controlled application deployment

Factor 6: Processes

The Processes principle states that the application should be executed as one or more stateless processes. This means that the application should not rely on local storage or memory but should instead store state in a backing service such as a database. This makes it easy to scale the application horizontally and ensures it can be easily restarted if a process fails.

This principle, also known as the "shared-nothing" architecture, is based on the idea that applications should be designed to be stateless and avoid shared state. This way, the application can more easily scale horizontally by adding more instances of the application without having to worry about synchronization or data consistency issues that can arise when multiple instances share state.

To achieve a share-nothing architecture, an application should store its state in a statefull data store like a database or a cache and ensure that any data that needs to be shared between instances is communicated through a shared message queue or another communication mechanism. By following this principle, an application can be designed to be more scalable, resilient, and easier to maintain over time.

Core ideas:

  • Execute the application as one or more stateless processes

  • Make the application easy to scale horizontally

  • Use shared-nothing architecture to prevent undetected dependencies between instances

Conclusion

The Twelve-Factor Application is a methodology for building and deploying modern, scalable, and maintainable software applications. It provides a solid guideline for building modern applications that can run on numerous environments without breaking or losing performance.

In summary, following the first six factors listed in the 12-Factor Application model leads to better software architecture and ensures an application is easily deployable, scalable, maintainable, and testable. We should keep one codebase per application and track it in revision control, explicitly declare and isolate dependencies via a package manager, keep configuration information separate from code, treat backing services, such as databases or message queues, as attached resources, strictly separate build and run stages, and execute the application as one or more stateless processes.

We will discuss the other six factors in the next topic.

36 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo