Need for the 12factor principle to design cloud-native applications for better scalability, manageability, and Portability.
Need for 12factor application design for better scalability, manageability, and portability.
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.
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. Dependencies – Explicitly declare and isolate dependencies
It talks about managing the dependencies externally using dependency management tools instead of adding them to your codebase.
You also need to consider the dependencies from the operating system/ execution environment perspective as well.
Try these out:
Microservices: All the application packages will be managed through package managers like sbt, maven.
3. Configuration – Store configuration in the environment
There should be a strict separation between the code and configurations. The code must be the same irrespective of where the application is being deployed.
As per "config", what varies from the environment to the environment must be moved to configurations and managed via environment variables.
This includes:
12-factor app principles suggest saving the configuration values in the environment variables or in external_service like Hashicorp vault for password.
Some options for Kubernetes to store the configuration are Configmap, Secrets, env variables
Try these out:
4. Backing services – Treat backing services as attached resources
A backing service is any service on which your application relies for its functionality. Some of the most common types of backing services include data stores, messaging systems, caching systems, and any number of other types of services, including services that perform line-of-business functionality or security.
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 changes should be able to take care of it.
By following the interfaced-based programming allow to swap the provider dynamically without impact on the system. The plug-in-based implementation also helps you to support multiple providers.
Some options:
Catching: Redis, Memcached, CDN
Message Broker: RabbitMQ, Apache Kafka, Redis, Amazon SQS, and Amazon SNS.
5. Build, release, 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.
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.
The principle of Processes, which can be more accurately termed stateless processes. This means that no single process keeps track of the state of another process and that no process keeps track of information such as session or workflow status. A stateless process makes scaling easier
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.
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.
Recommended by LinkedIn
7. Port binding – Export services via port binding
The principle of Port Binding asserts that a service or application is identifiable to the network by port number, not a domain name. In other words, the host and port used to access the service should be provided by the environment, not baked into the application, so that you aren’t relying on pre-existing or separately configured services for that endpoint.
The reasoning is that domain names and associated IP addresses can be assigned on-the-fly by manual manipulation and automated service discovery mechanisms. Thus, using them as a point of reference is unreliable. However, exposing a service or application to the network according to port number is more reliable and easier to manage. At the least, potential issues due to a collision between port number assignment private to the network and public use of that same port number by another process publicly can be avoided using port forwarding.
The essential idea behind the principle of port binding is that the uniform use of a port number is the best way to expose a process to the network. For example, the patterns have emerged in which port 80 is conventional for web servers running under HTTP, port 443 is the default port number for HTTPS, port 22 is for SSH, port 3306 is the default port for MySQL, and port 27017 is the default port for MongoDB.
8. Concurrency – Scale-out via the process model
This talks about scaling the application. Twelve-factor app principles suggest to consider running your application as multiple processes/instances instead of running in one large system. You can still opt-in for threads to improve the concurrent handling of the requests.
The concurrency factor stresses that microservices should be able to be scaled up or down, elastically, depending on their workload. Previously, when many applications were designed as monoliths and were run locally, this scaling was achieved through vertical scaling (i.e., adding CPUs, RAM, and other resources, virtual or physical). However, now that our applications are more fine-grained and running in the cloud, a more modern approach, one ideal for the kind of elastic scalability that the cloud supports, is to scale out, or horizontally. Rather than making a single big process even larger, you create multiple processes, and distribute the load of your application among those processes.
twelve-factor app principles advocate to opt-in for horizontal scaling instead of vertical scaling.
9. Disposability – Maximize robustness with fast start-up 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.
This is especially important in cloud-native applications because, if you are bringing up an application, and it takes minutes to get into a steady state, in today’s world of high traffic, that could mean hundreds or thousands of requests get denied while the application is starting. Additionally, depending on the platform on which your application is deployed, such a slow start-up time might trigger alerts or warnings as the application fails its health check. Extremely slow start-up times can even prevent your app from starting at all in the cloud. If your application is under increasing load, and you need to rapidly bring up more instances to handle that load, any delay during start-up can hinder its ability to handle that load. If the app does not shut down quickly and gracefully, that can also impede the ability to bring it back up again after failure. The inability to shut down quickly enough can also run the risk of failing to dispose of resources, which could corrupt data.
One way of looking at this is the cattle vs. pet model. Our application instances should be treated more as cattle (i.e., no emotional attachment to them, fairly easy to replace, numbered not named, etc.), rather than pets (i.e., there is an emotional attachment, nurse back to health rather than replace, etc.).
10. Dev/prod parity – Keep development, staging, and production as similar as possible
The Dev/Prod Parity principle means all deployment paths are similar yet independent and that no deployment "leapfrogs" into another deployment target.
The figure above shows two versions of an application's code. The V1 version is targeted for release to the Production environment. A new version, V2, is targeted for a Development environment. Both V1 and V2 follow a similar deployment path, from Build to Release and then Run. Should the V2 version of the code be deemed ready for Production, the artifacts and settings relevant to V2 will NOT be copied into the Production environment.
Rather, the CI/CD process will be adjusted to set the deployment target of V2 to Production. The CI/CD process will follow the expected Build, Release, and Run pattern towards that new target.
As you can see, Dev/Prod Parity is very similar to Build, Release, and Run. The important distinction is that Dev/Prod Parity ensures the same deployment process for Production as it does Development.
The twelve-factor developer resists the urge to use different backing services between development and production.
11. Logs – Treat logs as event streams
logs should be treated as event streams: You should stream out logs in real time so that killing an instance does not cause logs to go missing. All of the log entries should be logged to stdout and stderr only. The execution environment takes care of capture, storage, curation, and archival of such stream should be handled by the execution environment.
In cloud-native applications, the aggregation, processing, and storage of these logs is the responsibility of the cloud provider or other tool suites (e.g., ELK stack, Splunk, Sumologic, etc.) running alongside the cloud platform being used. This is especially important in cloud-native applications due to the elastic scaling capabilities they have -- for example, when your application dynamically changes from 1 to over 100 instances, it can be hard to know where those instances are running and keep track of and organize all of the logs. By simplifying your application’s part in this log aggregation and analysis, an application’s codebase can be simplified and focus more on business logic. This factor helps to improve flexibility for introspecting behavior over time and enables real-time metrics to be collected and analyzed effectively over time.
12. Admin processes – Run admin/management tasks as one-off processes
Though this rule is not related to developing services, it is more about managing your application but is still essential. It says that apps should run management or admin tasks in an identical environment as the app’s regular and long-running processes. Also, it suggests using the execution environment’s built-in tool to run those scripts on the production server.
This factor discourages putting one-off admin or management tasks inside your microservices. Examples given on 12factor.net include migrating databases and running one-time scripts to do clean-up. Instead, these should be run as one-off process and they can be run as Kubernetes tasks. In this way, your microservices can focus on business logic. It also enables safe debugging and admin of production applications and enables greater resiliency for cloud-native applications.
App Security is of Utmost Importance: In an enterprise ecosystem, you must address all dimensions of security from day one when you start writing your app’s code.
Best practices to ensure security in an app includes using TLS (Transport layer security) to secure data in transit and using API keys to ensure authentication and authorization. No doubt that the realm of security is broad, but you need to take care of all things (like operating systems, firewalls, networks, database security, and more) to build a secure microservice.
The API First Approach: If you adopt the principle that all apps are backing services and they should be designed as APIs-first, you can develop a system that is free to grow, scale, and adapt to new demands and load. This means what you are creating is an API to be consumed by client apps and services.
While developing a SaaS application, if you follow all the above-mentioned best practices or principles, you can build scalable, independent, and robust enterprise applications with ease and top-notch security. Let’s now check who uses factor 12 and what are its significant business benefits.
References: