avatarDineshchandgr - A Top writer in Technology

Summary

The article provides an overview of the 12 Factor App methodology, which is essential for developing cloud-native applications within a Microservices Architecture.

Abstract

The article "Do you know about the 12 Factor App in Microservices Architecture?" delves into the 12 Factor App methodology, a set of best practices for building scalable and resilient cloud-native applications. It emphasizes the importance of having a single codebase, explicitly declaring and isolating dependencies, storing configurations in the environment, treating backing services as attached resources, and strictly separating build, release, and run stages. The principles also cover executing the app as stateless processes, exporting services via port binding, scaling out via the process model, ensuring disposability, maintaining dev/prod parity, treating logs as event streams, and running admin/management tasks as one-off processes. The article underscores the alignment of these principles with Microservices Architecture and highlights their role in enabling applications to be modular, independent, portable, scalable, and observable.

Opinions

  • The author believes that understanding the 12 Factor App principles is vital for any developer working with cloud-native or microservices applications.
  • The article suggests that adhering to the 12 Factor App methodology can lead to applications that are robust, resilient, and align well with the principles of Microservices Architecture.
  • The author posits that externalizing configuration and treating backing services as attached resources are key to building flexible and adaptable microservices.
  • The article conveys the opinion that the separation of build, release, and run stages is crucial for reliable and reproducible deployments.
  • It is implied that the disposability of processes and the ability to scale horizontally are important factors in achieving high availability and elasticity in cloud applications.
  • The author emphasizes the importance of keeping development, staging, and production environments as similar as possible to avoid unexpected issues during deployment.
  • The article advocates for the use of containerization and orchestration tools like Docker and Kubernetes to facilitate the implementation of the 12 Factor App principles.
  • The author encourages the practice of treating logs as event streams to improve the introspection and analysis of application behavior over time.
  • It is suggested that incorporating admin processes into the application codebase ensures consistency and avoids synchronization issues.
  • The article concludes by reiterating the benefits of the 12 Factor App approach for developing applications that meet the demands of modern cloud infrastructure, including higher throughput, lower latency, and minimal downtime.

Do you know about the 12 Factor App in Microservices Architecture?

Hello everyone. In this article, we are going to see an interesting methodology called the 12-Factor App which is used widely in Microservices Architecture. It is a set of 12 best practices to build cloud-native applications and understanding them is vital for any developer to design and build a cloud-native / microservices application. Let's dive deep inside.

Image Source: https://www.codemotion.com/magazine/wp-content/uploads/2022/05/12-factor-app-process-1024x791.png

Please check out my below article to understand some basics of Microservices

What is a 12-Factor Web App?

The 12-factor methodology is a set of twelve principles to develop applications that can be cloud-native, independent, robust, and resilient. This was originally drafted in 2011 by Heroku for applications deployed as services on their cloud platform. 12-factor app principles became very popular as it aligns with principles of Microservices and it is an influential pattern for designing scalable application architecture.

Image Source: https://agileviet.vn/content/images/2020/12/V9nAWbd.png

The 12 Factors

The following are the 12 factors that we are going to see in detail with illustrations.

  • Codebase
  • Dependencies
  • Config
  • Backing Services
  • Build, release, and Run
  • Processes
  • Port Binding
  • Concurrency
  • Disposability
  • Dev/prod parity
  • Logs
  • Admin processes

Having seen the 12 factors above, let us look one by one in detail

1. Codebase

“One codebase tracked in revision control, many deploys”

A microservice must consist of a single repository, that is tracked by a version-control system like Git (including GitHub, GitHub Enterprise, GitLab, BitBucket, etc). If there are 20 microservices in an application, then there must be 20 individual repositories. Eg: The order service will have its own repository and the payment service will have its own repository.

The single repository helps development teams to develop the microservices independently and they can be language agnostic (i.e they can be developed in different languages). This will support collaboration between teams and helps to enable proper versioning of applications.

Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/codebase.png?itok=hOd-QvBN

As seen in the above diagram, there should be a 1:1 relationship between an application and a codebase, but a one-to-many relationship between the codebase and deployments of an application.

2. Dependencies

“Explicitly declare and isolate the dependencies”

This is a very important aspect of any system development.

Most applications require the use of external dependencies to build the code and run it. Rather than packaging the dependencies inside the microservice application, these dependencies must be pulled during the build process. This will simplify the setup required for any new developers and also provide consistency between development, staging, and production environments.

Image Source: https://developer.ibm.com/developer/default/articles/creating-a-12-factor-application-with-open-liberty/images/images02.png

The first step to achieving this is to isolate the external dependencies within the application. For eg: If you use Java, then you can leverage tools like Maven and Gradle and specify the dependencies in pom.xml or build.gradle files. These tools help simplify the complexity and relieve developers from the responsibility of maintaining the dependencies.

3. Config

“Store configurations in an environment”

Configuration refers to any value that can vary across deployments (e.g., developer machine, Dev, QA, production, etc). This might be of the following

  • URL and credentials to connect to databases
  • URLs and other info for other services like Logs, Caching, etc
  • Credentials to third-party services such as Amazon AWS, Payment Providers, etc
Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/config.png?itok=BmjfdjDH

There must be a clean separation of configuration and the code. If we ship the config with the code, it will fail in other environments and also there is a risk of credentials leak. Externalizing configuration is very important for a microservice application and it enables us to deploy our applications to multiple environments regardless of which runtime we are using.

By right, the application must run in any environment without caring about the config. Having the configuration in environment variables is considered the best practice. Spring Boot provides a module called Spring Cloud config to support the externalization of the configuration. You can read more about it here https://docs.spring.io/spring-cloud-config/docs/current/reference/html/

4. Backing Services

“Treat backing resources as attached resources”

Backing services are any processes that the microservice communicates over the network during the operation. Examples include Databases (e.g. MySQL, PostgreSQL), Cache, Message broker, etc.

The Backing Services principle encourages architects and developers to treat components such as databases, email servers, message brokers, and independent services that can be provisioned and maintained as attached resources.

The resource can be swapped at any given point in time without impacting the service. Eg: Let us say that you are using the MySQL database in AWS and would like to change to Aurora. Without making any code changes to your application you can do it just with a config change.

Image Source: https://12factor.net/images/attached-resources.png

In a nutshell, the twelve-factor app treats these backing services as attached resources, which indicates their loose coupling to the deploy they are attached to.

5. Build, release, and Run

“Strictly separate build and run stages”

The deployment process for a microservice application is divided into 3 stages namely Build, Release, and Run.

Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/buildreleaserun.png?itok=tU10QwgC

In the Build stage, the source code is retrieved from the source code management tool like Git and then the dependencies are gathered and bundled into the build artifact (e.g. a WAR or JAR file). The output of this build phase is a packaged server containing all of the environment-agnostic configurations required to run the application.

The Release stage retrieves the artifacts from the build stage and applies configuration values (both environmental and app-specific) to that to produce another release. This release is tagged By labeling these releases with unique IDs which will help to roll back to the previous version of the deployment if needed.

The last stage is the Run stage, which usually occurs on the cloud provider, and usually uses tooling like containers or Terraform/Ansible to launch the application. Finally, the application and its dependencies are deployed into the newly provisioned runtime environment like Kubernetes Pods or EC2 server or Lambda functions, etc.

The Build, Release, and Run are completely ephemeral which means that all artifacts and environments can be reconstructed from scratch (if anything goes wrong in the pipeline) using assets stored in the source code repository.

6. Processes

“Execute the app as one or more stateless processes”

Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.

Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/process.png?itok=sKKO87wh

The principle of Processes states that the 12 Factor App should be stateless and it should not store the data in-memory of the application. The sticky sessions should not be used as well. The memory space or filesystem of the process/application can be used only as a temporary one.

For example, downloading a large file, operating on it, and storing the results of the operation in the database. Any data to be stored should be stored in a stateful backing service like a database or distributed cache that can be read by all the instances of the application.

When a process is stateless, instances can be horizontally scaled up and down, and having statelessness will prevent any unintended side effects.

7. Port Binding

“Export services via port binding”

The twelve-factor app is completely self-contained and does not rely on the runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.

Imaage Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/portbindings.png?itok=d8Le0wh-

The principle of Port Binding states that a service or application should be identifiable on the network by its port number and not a domain name. This is because the domain names and IP addresses can change dynamically due to the automated discovery mechanisms and hence using them is not reliable. Using a port number will always make the microservice identifiable and more reliable.

The 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. Eg: For example, port 80 is used for web servers port that uses 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. Likewise, all our microservices must have their unique port numbers defined and the applications must run on the designated ports.

8. Concurrency

“Scale out via the process model”

The principle of Concurrency talks about scaling the application. Twelve-factor app principles suggest running your application as multiple processes/instances (Horizontal Scaling) instead of running in one large system i.e Vertical scaling. By adopting containerization, applications can be scaled horizontally as per the demands.

Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/concurrancy.png?itok=aqNxheba

As shown in the diagram above, an application is exposed to the network via web servers that operate behind a load balancer. Then the web servers communicate with the Business Service that runs behind another load balancer. In case of load, the Business layer can be scaled up independently. If concurrency is not supported in the architectures like Monolith, then the entire application must be scaled up.

9. Disposability

“Maximize the robustness with fast startup and graceful shutdown”

The twelve-factor app’s processes must be disposable, meaning they can be started or stopped at a moment’s notice. This facilitates elastic scaling, rapid deployment of code or config changes, and robustness of production deploys. When the application is shutting down or starting up, an instance should not impact the application state.

Applications should aim to minimize startup time and the process must take only a few seconds to launch and be ready to receive requests or jobs.

Applications can crash due to various reasons and the system should ensure that the impact would be minimal and that the application should be stored in a valid state.

By adopting containerization into the deployment process of microservices, we can make the application disposable. 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.

10. Dev/Prod parity

“Keep development, staging, and production as similar as possible”

The dev/prod parity principle focuses on the importance of keeping development, staging, and production environments as similar as possible. This will eliminate unexpected issues when the code is deployed to production.

Image Source: https://developer.ibm.com/developer/default/articles/creating-a-12-factor-application-with-open-liberty/images/image10.png

With many applications being cloud-native and running in the cloud, they interact with a large ecosystem of services, and hence it is important that we replicate this environment when developing and testing our applications.

Tools like Docker are very helpful to implement this dev/test/prod parity. The benefit of a container is that it provides a uniform environment for running code by packaging the code and the dependencies required to run the code in the form of a docker image. Containers enable the creation and use of the same image in development, staging, and production. It also helps to ensure that the same backing services are used in every environment.

11. Logs

“Treat logs as event streams”

Logs are the stream of aggregated, time-ordered events collected from the output streams of all running processes and backing services. Logs in their raw form are typically a text format with one event per line. Logs have no fixed beginning or end but flow continuously as long as the app is operating.

Image Source: https://www.redhat.com/architect/sites/default/files/styles/embed_large/public/2021-02/logs.png?itok=LK-UfHBY

The logs factor highlights the importance of ensuring that your application doesn’t concern itself with routing, storage, or analysis of its output stream. 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 factor helps to improve flexibility for introspecting behavior over time and enables real-time metrics to be collected and analyzed effectively over time.

In order to implement this factor, logs should be treated as event streams i.e we 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.

12. Admin processes

“Run admin/management tasks as one-off processes”

There are a number of one-off processes as part of the application deployment like data migration, and executing one-off scripts in a specific environment.

Twelve-factor principles advocate 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 and they should be run in an identical environment as the regular long-running processes of the app.

They run against a release, using the same codebase and config as any process running against that release. Admin code must ship with application code to avoid synchronization issues.

In this way, your microservices can focus on business logic.

Summary

In this article, we saw the 12 Factor App principles in detail and these principles are designed to allow developers to create applications intended to run on a web scale. Developing an application to be a twelve-factor app certainly has its benefits, especially when we wish to deploy them as services on the cloud.

These principles are adopted by many companies and it is important to note that all these factors are there to help us develop an application that is modular, independent, portable, scalable, and observable.

These factors hold greater importance in the era where we demand our applications to have higher throughput and lower latency with virtually no downtime and failure. These factors when combined with microservice architecture and containerization of applications, will help us to meet the goals of a cloud-native application.

Hope you liked this article and thanks for reading a long one !!!

Please read my following article if you want to learn more about

If you like to get more updates from me, please follow me on Medium and subscribe to the email alerts.
If you are considering buying a medium membership, please buy through my referral link https://dineshchandgr.medium.com/membership
Software Development
Coding
Programming
Software Engineering
JavaScript
Recommended from ReadMedium