12-Factor Methodology for Cloud-Native Microservices: Config and Backing Services

Santosh Pai
ITNEXT
Published in
5 min readMay 31, 2023

--

In the previous two blog posts of this series, we covered the first two factors of the 12-factor methodology and discussed how to structure our codebase and handle application dependencies effectively. In this post, we’ll continue our journey by exploring the third and fourth factors: Config and Backing Services, and get pleasantly surprised that our tooling choices have kept us in good stead!

But before we dive in, let’s take a brief detour to understand Hexagonal Architecture and its relevance to our current discussion involving microservices.

Hexagonal Architecture: A brief Introduction

Somehow this always looks like an opened up football to me!

Hexagonal Architecture, also known as Ports and Adapters, is a design pattern that emphasizes the separation of concerns, particularly in terms of how information enters and leaves your application. Understanding this concept is a pre-cursor to designing efficient microservices. This architecture guides us to wrap our application in a hexagonal shape, with each side of the hexagon representing an input or output, the “port”. The adapters, on the other hand, serve as the interpreters, translating external commands or events into a language that the application understands.

This pattern encourages isolation of the core business logic from the outside world, which aligns well with the tenets of the 12-factor methodology, especially in terms of managing configs and backing services. By adopting the Hexagonal Architecture, we ensure that our application is agnostic of the nature of inputs and outputs, allowing for easier adaptability to changing business environments.

The core in affect consists primarily of the Domain Model and the Business Logic, which can then be independently tested.

Another key principle to adhere to is Domain Driven Design (DDD), and a well architected Domain Model is critical for the entire microservice chain! Will do a blog post on it if enough votes!

Now, back to our primary discussion.

Third Factor: Config

Store config in the environment.

This factor guides us to separate config from code. Let’s split our config into some categories:

  • Infrastructure: involves settings related to the infrastructure that hosts the microservices.
  • Application Config: related to the application-level settings, which can influence the behavior of an application across different environments.
  • Security: settings related to securing the microservices, such as authentication and authorization details, encryption keys, SSL/TLS certificates, OAuth tokens, etc.
  • Others: Runtime, External Service Config

Configuration data should be entirely based on the environment, which may include database URLs, credentials, secret keys, and more. By adhering to this principle, we make our application portable across various environments, from local development setups to staging and production.

Kubernetes with Kustomize: Tool choices vindicated!

Kubernetes and Kustomize can significantly streamline the management of microservice configurations, aiding in meeting the demands of the 12-factor app’s principles related to configurations.

  • Infrastructure Configuration: Kubernetes, as a container orchestration system, is designed to manage the deployment, scaling, and networking of applications. It enables you to define desired infrastructure states and manages it for you, eliminating the need to configure servers manually.
  • Service Configuration: Kubernetes provides ConfigMaps and Secrets for handling service-specific configurations. ConfigMaps allow you to decouple environment-specific configurations from your application code, while Secrets offer a secure way to store and manage sensitive information like passwords or API keys.
  • Application Configuration: Kubernetes allows you to control application-level configurations via environment variables and ConfigMaps. You can define these configurations once and then Kubernetes ensures that they are available to the application across all environments.
~/manifests-index develop > tree -L 4  infra/k8s/
infra/k8s/
├── auth
│ ├── README.md
│ ├── argo
│ │ ├── README.md
│ │ ├── base
│ │ │ ├── application.yaml
│ │ │ └── kustomization.yaml
│ │ └── overlays
│ │ └── development
│ ├── base
│ │ ├── deployment.yaml
│ │ ├── kustomization.yaml
│ │ └── service.yaml
│ └── overlays
│ └── development
│ ├── build-version-patch.yaml
│ ├── ff-mixed-mode-patch.yaml
│ ├── cm-auth-key-patch.yaml
│ └── kustomization.yaml

In our architecture, we’ve implemented a clean separation of infrastructure, service, and application configurations. Additionally, we’ve structured our system to readily accommodate the inclusion of more configuration overlays as needed. Works well!

  • Security Configuration: Kubernetes Secrets provide a secure way to store and manage sensitive data. We have ensured secure storage of sensitive data for each overlay. This approach offers us granular control over data accessibility, the ability to audit its use, and guarantees that sensitive information remains branched and unexposed in logs or through APIs.
  • External Services Configuration: Kubernetes’ Service and Ingress objects make it easy to configure how your microservices interact with external services. The Service object allows you to expose your application to other applications within the same Kubernetes cluster, while the Ingress object lets you control HTTP/HTTPS routing to your applications from outside the cluster.
  • Runtime Configuration: Kubernetes gives you control over runtime configurations via resource requests and limits, allowing you to specify how much CPU and memory each service should use. You can also control other aspects of the runtime environment using Security Contexts, Taints and Tolerations, Node Selectors, and other features.

Fourth Factor: Backing Services

Treat backing services as attached resources.

Any service that your application communicates with over a network is considered a backing service. This includes databases, ingress, message queues, caching systems, and even other microservices. The 12-factor methodology advises treating these backing services as attached resources that can be swapped without changing the codebase.

~/manifests-index develop > tree -L 3  infra/k8s/auth-mongo 
infra/k8s/auth-mongo
├── base
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays
└── development
└── kustomization.yaml

Our design strategy aligns perfectly here as well! In our architecture, every service comes with a dedicated database that can be customized per overlay using Kustomize. This allows individualization of each service’s backing resources without affecting the rest.

Similarly, strategies for Ingress and Kafka, orchestrated through Strimzi, also adhere to these customization capabilities, ensuring each component can independently adapt based on different environmental conditions or business requirements.

Conclusion

Managing configuration and backing services efficiently is integral to creating robust, scalable, and flexible microservice applications. Adhering to the principles of Hexagonal Architecture alongside the 12-factor methodology, we can build services that are not only resilient to changes but also enjoyable to work with.

And finally, here’s a thought: An effective litmus test of how well we have isolated our configuration data is the ability to open-source our application without compromising any credentials. The strength of our current design should make this a realizable goal!

Does our design pass this Litmus Test? Yes!

The next post for Build, Release, Run should be an interesting one!

--

--