1. Introduction
You must have heard the term Cloud-native applications. In this article, we’ll discuss this term and 15-factor methodology.
Cloud native technologies empower organizations to build and run scalable applications in modern, dynamic environments such as public, private, and hybrid clouds. Containers, service meshes, microservices, immutable infrastructure, and declarative APIs exemplify this approach.
Cloud Native Computing Foundation
These techniques enable loosely coupled systems that are resilient, manageable, and observable. Combined with robust automation, allow engineers to make high-impact changes frequently and predictably with minimal toil.
Cloud-native applications are software applications designed specifically to leverage cloud computing principles and take full advantage of cloud-native technologies and services. These applications are built and optimized to run in cloud environments, utilizing the scalability, elasticity, and flexibility offered by the cloud.
2. Important characteristics of cloud-native applications
- Microservices: Cloud-native applications are often built using the microservices architecture, where the application is broken down into smaller, loosely coupled services that can be developed, deployed, and scaled independently.
- Containerization: A cloud-native application is typically packaged and deployed using containers, such as Docker containers. Containers provide a lightweight and consistent environment for running applications, making them highly portable across different cloud platforms and infrastructure.
- Scalability and Elasticity: A cloud-native application is designed to scale horizontally. This allows the application to handle increased loads by adding more instances of services. A cloud-native application can automatically scale up or down based on the load using cloud-native orchestration platforms like Kubernetes.
- Devops: A cloud-native application follows DevOps principles which promotes collaboration between development and operation teams. DevOps practices use continuous integration, continuous delivery and automated deployment pipelines to streamline the software development and deployment processes.
- Resilience and fault tolerance: A cloud-native application is designed to handle failures. Fault tolerance and high availability is achieved by techniques such as distributed architecture, load balancing and automated failure recovery.
- Cloud-Native services: A cloud-native application takes advantage of various cloud-native services provided by various cloud platforms such as caching systems, identity management services, managed databases etc. This allows developers to focus on delivery and less of managing infrastructure.
3. Development principles of cloud native: 15-Factor methodology
The twelve-factor app is a methodology for building software-as-a-service apps. The engineering team at Heroku cloud platform introduced the 12-Factor methodology. The 12-Factor methodology is a set of development principles which guide the design and development of cloud-native applications.
Later, Kevin Hoffman expanded the 12-Factor methodology and added additional factors. This revised approach is known as 15-Factor methodology. This is mentioned in Kevin Hoffman’s book “Beyond the Twelve-Factor App”.
The factors added in 15-Factor app are API first, Telemetry and Authentication and authorization.
- Codebase
The application is always tracked in a version control system, such as Git and Subversion. There is only one codebase per application. There is a one-to-one relationship between a codebase and an application. Multiple applications should not share the same code.
However, there can be multiple deployments of the application, such as development, staging and production. The codebase is same across all deployments but the versions can be different. For example, production can have an older version of deployment than development. - Dependency management
The application declared all its dependencies completely, exactly and clearly, via a dependency declaration manifest. For example, in a Java application pom.xml is used to declare dependencies. The declaration of dependencies applies to all deployments, such as development and production.
The application should not rely on the implicit existence of any system tools except language runtime and dependency manager. This helps developers to setup the application from scratch using the codebase as all dependencies are resolved by the dependency manager. - Configuration
An application configuration or config is anything which vary between deployments (such as development, staging and production environment, etc.). Following are few examples of config of an application:
1. Resource handles to the databases
2. Credentials to external services
3. Deployment specific URLs of the external services
The application should never store config as constants in the code. The application should store config in environment variables, commonly known as ‘env vars’ or ‘env’ in shortened form. Following are the benefits of storing config in environment variables:
1. Environment variables can be changed without changing the code.
2. Environment variables can’t be checked in the codebase even by mistake.
3. Environment variables are language and OS agnostic. - Backing services
A backing service is any service the application consumes over the network as part of its normal operation. The examples of backing services are databases (such as Oracle and MySQL), caching systems (such as Redis) and messaging queues (such as RabbitMQ).
The backing service may be managed by same system administrator who deploy the application or the backing service may be a third party service. The application makes no distinction between local and third party services. To the application both type of services are just resources. The application should be able to switch from a local service to a third-party service or vice versa without code change. For example, if the database is down, the application should be able to connect to the backup without any code change. - Design, build, release, run
There are three stages to convert a codebase to a release:
1. Build: The build stage converts code repository into an executable known as ‘build’.
2. Release: The release stage combines the ‘build’ generated in the build stage with the current configuration. The result is known as the ‘release’ and can be executed in the current environment. Every release should always have a unique release ID. A release cannot be mutated once it is created. Any change must create a new release.
3. Run: This stage runs the application in the current environment. This stage is also known as “runtime”. The application should use a strict separation between the three stages (build, release and run). - Processes
The application is executed in the execution environment as one or more processes. The application processes should be stateless. Any data which should be saved, must be stored in a stateful backing service, such as database.
The application should never assume that the data in cache or runtime memory will be available for the next request or job. The reason is such data can be lost in case of restart or system failure. - Port binding
The application can be executed in a container. The application should export services via port mapping. For example, the web application exports HTTP service by binding to a port, and listening to the incoming requests on that port. This is implemented by declaring a dependency of a webserver library to the application. - Concurrency
The application should use the process model to scale out. The application processes are a first class citizen. The developers handle workload by assigning task to a process. This makes it easy to add more concurrency. One point to note is that the application processes should not run as a daemon. - Disposability
The application processes are disposable. That is they can be started and stopped anytime. This results in east and fast scaling, fast development and deployment of changes. In case of web process, graceful shutdown results in listener on the service port to go down (thereby refusing any new requests), allowing any current requests to finish, and then exiting. - Environment parity
The application should keep development, staging, and production as similar as possible. Sometime, developers choose to use different backing services for development and production. Such as different database in development and production. The twelve-factor application resists to use different backing services between development and production even if it is possible using various adapters. The application is designed for continuous deployment which helps in keeping the gap between development and production small. As soon as the development is done, the code can be deployed and released in production. - Logs
Logs provide information about what is going on in the application. The logs are usually written to a file on disk called logfile in a server based environment. Logs help you know track the flow and the exceptions.
The application should not be concerned about the writing, storage and management of the logs. Instead, it is the responsibility of the running process to write the logs. The execution environment, collates all the log streams and route it to log indexing and analysis system such as Splunk or a data warehouse system like Hadoop/Hive. - Administrative processes
Sometimes, developers would want to run administrative or maintenance tasks for the app, such as migration jobs, cleaning up data files. The administrative processes should be treated as isolated processes. The code for these administrative tasks should be version controlled, packaged alongside the application, and executed within the same environment. It is recommended that the administrative tasks are executed once and then discarded. The administrative tasks should be treated as independent processes. - API first
In a cloud-native ecosystem, various services interact with each other through APIs. Adopting an API-first approach during the design phase of a cloud-native application promotes the division of work among multiple teams. API first approach allows teams to develop their solutions based on API. This approach results in more reliable and testable integration with other systems. In this approach, the solution can be changed without affecting the integration with other services. - Telemetry
A microservice application is a system distributed across the cloud. So it becomes essential to have access to accurate and comprehensive data from each component of the system. To effectively monitor and control applications remotely, you need various types of telemetry data. Telemetry data such as logs, health status, traces, metrices play an important role in remote monitoring the system’s behavior and its effective management. - Authentication and authorization
Security is a critical aspect of every application. Authentication and authorization are just the starting point for securing the application. Authentication enables us to verify the identity of the users accessing the application. After authentication, we then proceed to evaluate whether the user has the required permission to perform the action.
4. Conclusion
In conclusion, mastering the principles of 15-factor apps is a pivotal step towards building resilient, scalable, and maintainable applications in the dynamic landscape of modern software development. By adhering to these factors, developers empower themselves to create systems that are not only efficient but also adaptable to changing requirements.