In our previous article on Cloud Native we understood how this term refers to a methodological approach and a set of practices. We also identified the business needs that led to its development. Now all that’s left is to see how these principles translate into practice.
We talked abstractly about changes in processes, architecture, infrastructure, and communication. What emerges in practice is **the need for an agile, decoupled, and modular application ecosystem.
Where to start: 12-Factor design
Let’s start from the heart of every application: the codebase. In our scenario, it must be designed to be easily replicable with all its dependencies, both application and system dependencies (containers + dev-prod parity), there must be a strong separation between code and configurations (e.g., credentials or database connection parameters), and it must be stateless and share-nothing, meaning it should not assume that memory and filesystem are persistent between requests, making it effectively simple to replicate.
These are just some of the elements that allow us to make our application Cloud Native, capable of making the most of Cloud environments, and they are part of a methodology called 12Factor, referring to 12 general principles: best practices for developing and designing modern applications and/or modernizing existing ones.
Dependency management
To run correctly, every application has a series of dependencies, both application-level (e.g., language runtime, vendor libs) and system-level (os-libs, kernel version, Linux or Windows).
For example, a PHP application will certainly need the PHP runtime at a specific version (e.g., 5.6, 7.1, 7.2), certain extensions must be present (e.g., mcrypt, mysql, redis), and all required application dependencies must be compiled and installed correctly (e.g., https://getcomposer.org). The exact same scenario is also applicable to other runtimes such as Java, Ruby, Python, or Go.
Today, this problem can be solved very simply thanks to the use of Docker and Containers, which have become a standard for isolating and packaging an application with all its dependencies, making it easily portable and executable on every environment, both development and production, native or cloud.

Configurations
Once the dependency management issue is resolved, another one remains open: each environment may need a specific configuration, such as access credentials for a service, a database connection, a logging service, or persistent storage. In traditional applications, this information is stored in constants within the code (hard coding), or in static configuration files, presenting us with the same situation we encountered with system-wide dependencies: how can we make our application portable and configurable without modifying the codebase?
One of the key principles of the 12-factor methodology, and therefore of modern applications, is to decouple configuration from the application layer. There are several very simple techniques, such as the use of environment variables (natively supported by Docker), which are configurations available to the application but injected at runtime by the environment executing it, thus creating a clear separation between the code and the service configurations, which will no longer be part of the codebase.
Stateless
In a Cloud Native architecture, every process is stateless, meaning it does not assume that its behavior may depend on previous executions, nor that components such as memory and filesystem are shared and persistent between consecutive executions.
So, how can we run a traditional workload like a CMS — generally based on the concept of persistent filesystem and database — on a Cloud Native architecture like Kubernetes?
Well, the answer is very simple, and it is to use modernization techniques to make the traditional workload suitable for running in modern stateless cloud environments, without necessarily having to completely rewrite the codebase.
For example, let’s take the case of a traditional application like Drupal, meaning a stack based on PHP + Nginx/Apache + MySQL. In this scenario, a modernization process should focus on the following aspects:
- Use of an object storage (e.g., S3) for the CMS’s dynamic assets
- Streaming of service logs instead of keeping them on the filesystem (e.g., Stackdriver)
- Use of a cloud vendor’s native service for the relational database (e.g., CloudSQL, RDS)
Every traditional workload can be modernized and brought to the cloud without undertaking a complete refactoring. With the right modifications, it is possible to make an application easier to scale horizontally, more resilient to errors, and more performant, being able to natively leverage every service provided by the cloud vendor.
This approach, therefore, in addition to providing the immediate advantage of being able to migrate your workloads to the cloud right away, allows you to modernize every aspect also with a view toward microservices development.

Processes
What impact does this application model have on the organization and workflow? Everything we have described so far had a clear business objective: to react quickly to the changing demands of the market, with short and continuous development and release cycles. We have obtained the conditions to do so; what’s missing is the how.
In a classic development scenario, it is very easy to find a clear separation between the development team (devs) and the operations team (ops). This was acceptable when the interval between one deployment and another could be days or weeks. But our model requires rapid and continuous releases, ideally at intervals of hours, and consistency of the technology stack across various deployments.
The solution is to steer the organization toward a DevOps culture, with multidisciplinary and collaborative teams, responsible for a single service and able to independently follow it from development to deployment. Combined with the use of automated systems for Continuous Integration / Continuous Delivery (CI / CD), DevOps teams are able to reduce development and deployment to extremely short cycles and, thanks to the isolation and decoupling of the services they work on, are ideally independent regarding technology choices.
The introduction of CI/CD leads to a further modification of the workflow: the clear separation into three phases of the codebase lifecycle. We distinguish these three phases as build, release, and runtime, which our codebase will traverse in a single direction:
BUILD: Makes a specific state of the repository (code version) executable. Binary code is compiled, appropriate assets are included, and necessary dependencies are bundled.
RELEASE: The code obtained at the end of the build is combined in this phase with the set of configurations specific to the deployment.
EXECUTION or RUNTIME: This phase sees the code running in the final target environment.

This process is always implemented in a single direction to ensure automation: therefore, every code change must involve a new deployment with a specific release ID.
The set of optimizations and operational transformations we have described are part of a methodology that allows us to be more efficient, optimize operational costs, and reduce time-to-market.
In upcoming articles, we will see with a concrete example the transition from a monolithic application to a Cloud Native application, applying the principles of the 12-Factor App methodology.



