Create your own
Lesson illustration

Understanding Twelve-Factor Apps

Hello! Welcome back to our course on preparing for microservices interviews.

In our last few lessons, we've focused on the design of microservices, using Domain-Driven Design and the Single Responsibility Principle to define clean, business-aligned service boundaries. We now have a solid foundation for deciding what services to build. Today, we shift our focus to how to build them so they can thrive in a modern cloud environment.

This lesson covers the Twelve-Factor App methodology, a set of best practices for building scalable, resilient, and maintainable cloud-native applications. For mid-to-senior level interviews, especially at companies operating at scale, a deep understanding of these principles is non-negotiable. It demonstrates that you think beyond just writing code and consider the entire application lifecycle, from development to deployment and operations.

By the end of this lesson, you will be able to explain the twelve-factor app methodology and its significance for building robust Spring Boot microservices.

1. From "Cloud-Ready" to "Cloud-Native"

Before diving into the factors, it's important to understand the philosophy behind them. Many applications can be made "cloud-ready" – tweaked to run on the cloud. However, "cloud-native" applications are designed from the ground up to leverage the full power of cloud platforms, embracing principles like automation, disposability, and horizontal scaling. The Twelve-Factor App methodology, originally drafted by engineers at Heroku, provides the blueprint for building truly cloud-native software.

Cloud Native vs Cloud Ready | 12 Factor App

To understand this distinction better, watch the introductory part of this video from Telusko. It clearly explains the difference between cloud-ready and cloud-native applications, which sets the stage for why the 12-Factor methodology is so important.

Watch from the beginning (00:00) to 03:36. Focus on how the speaker frames the 12-Factor App as a set of standards for building cloud-native applications.

2. The Twelve Factors: A High-Level View

The methodology consists of twelve principles. Think of them as a checklist to ensure your application is architected for portability, resilience, and continuous deployment.

The 12 Factor App
A visual summary of the twelve factors, each representing a best practice for building cloud-native applications. We will explore each of these in detail.

To get a comprehensive overview and see how these factors apply in a Spring Boot context, the following article from Baeldung is an excellent resource. We'll be referring to its structure as we go.

Twelve-Factor Methodology in a Spring Boot Microservice

This Baeldung article provides a thorough walkthrough of each of the twelve factors with specific relevance to Spring Boot development. It will serve as our primary reference for this lesson.

Skim through the entire article to get a sense of all twelve factors. Don't worry about mastering every detail yet; we will highlight the most critical ones. Pay attention to the bolded principle for each factor.

3. Deep Dive into Key Factors with Spring Boot

While all twelve factors are important, some have a particularly high impact on microservice architecture and are frequent topics in interviews. Let's explore these in more detail, focusing on their practical implementation with Spring Boot.

The following video from a Spring I/O conference is a great practical guide. It demonstrates how Spring Boot's philosophy naturally aligns with many of these principles.

Building 12-Factor Spring Boot Applications: Simplicity, Scalability, and Best Practices @ Spring IO

This presentation is given by a developer advocate and highlights how Spring Boot is an 'ally' in building 12-Factor applications. It covers 7 of the 12 factors with practical examples and demos.

Watch the introduction from 01:46 to 11:24. This will give you a great overview of the challenges of modern cloud apps and which 7 factors the speaker will focus on. You'll see how Spring Boot's features map directly to these principles.

Now, let's break down the most crucial factors.

I. Config: Separate Configuration from Code

This is one of the most important factors. The principle states: Store configuration in the environment.

An application's code should be identical across all deployments (dev, staging, prod), but the configuration (database URLs, credentials, external service endpoints) will vary. Hardcoding config into your application is a major anti-pattern because:

  • It requires a code change and a full redeployment just to update a configuration value.
  • It poses a security risk, as sensitive credentials can be accidentally committed to version control.

Spring Boot Implementation:
Spring Boot excels at this. You can externalize configuration using application.properties or application.yml and use Spring Profiles to manage environment-specific files (e.g., application-prod.yml). The values themselves are sourced from environment variables, which are set by the deployment platform (like Kubernetes).

Microservices with Spring Cloud Config
This diagram illustrates a more advanced pattern for Factor III using Spring Cloud Config. A central server provides configuration to all microservices, allowing for dynamic updates without restarting services. This is a production-grade approach to configuration management.

For a practical look at how Spring Boot handles configuration, refer back to the Spring I/O video.

Building 12-Factor Spring Boot Applications: Simplicity, Scalability, and Best Practices @ Spring IO

Watch this segment on the 'Config' factor. The speaker discusses why externalizing configuration is vital and demonstrates how Spring Boot achieves this through properties, environment variables, and more advanced tools like Spring Cloud Config.

Watch from 17:21 to 21:06. Pay close attention to the use of ${ENV_VAR} syntax in application.properties and the mention of secret management tools like HashiCorp Vault.

II. Backing Services: Treat as Attached Resources

The principle: Treat backing services (databases, message brokers, caching systems) as attached resources, accessible via a URL or other locator stored in the config.

Your application should make no distinction between a local MySQL database and one managed by a cloud provider. Swapping one for another should only require a change in configuration, not code.

Spring Boot Implementation:
Spring's abstraction layers, like Spring Data JPA, are perfect for this. Your repository interfaces are not tied to a specific database vendor. By changing the JDBC driver dependency in your pom.xml and updating the spring.datasource.url in your configuration, you can switch databases without altering your business logic.

III. Processes: Execute as Stateless, Share-Nothing Processes

The principle: Applications execute as one or more stateless processes.

This is the key to achieving horizontal scalability. A stateless process stores no request-specific data in memory or on the local filesystem. Any necessary state must be persisted in a stateful backing service (like a database or a distributed cache).

Why is this critical? If you scale up to 10 instances of your service, any of those 10 instances must be able to handle any user's request. If instance #1 handles a login and stores the user's session in its own memory, a subsequent request from that user that gets routed to instance #2 will fail because the session data is missing. This is often called the "sticky session" problem, which is an anti-pattern in cloud-native design.

The Spring I/O video has an excellent demo of this.

Building 12-Factor Spring Boot Applications: Simplicity, Scalability, and Best Practices @ Spring IO

This segment on the 'Processes' factor is one of the most important. The speaker demonstrates scaling a Spring Boot application and shows how, by using Redis for session state, the application behaves consistently across multiple instances.

Watch from 28:00 to 33:31. Notice how the host ID changes on refresh (indicating load balancing across different instances), but the session counter remains consistent because the state is stored externally in Redis.

Test your understanding!

An e-commerce application has a CartService. To improve performance, a developer decides to implement an in-memory cache using a ConcurrentHashMap within the service instance to store users' shopping carts.

  1. Which 12-Factor principle does this design violate?
  2. What specific problem will occur when this service is deployed to a cloud platform like Kubernetes and scaled to 3 replicas?
  3. How would you refactor this to be 12-Factor compliant?
Show answer
  1. This design violates Factor VI: Processes. The service is now stateful, as it stores request-specific data (the shopping cart) in the memory of a specific process instance.

  2. When scaled to 3 replicas, a user's requests might be load-balanced across all three. If a user adds an item to their cart (request hits replica 1), and then goes to checkout (request hits replica 2), the cart will appear empty. The state is "stuck" on replica 1, leading to data inconsistency and a broken user experience.

  3. To be compliant, the state must be moved to a backing service (Factor IV). The CartService should be refactored to store the cart data in an external, distributed cache like Redis or a database like PostgreSQL/MongoDB. This ensures that any instance of CartService can retrieve any user's cart, making the service itself stateless and horizontally scalable.

IV. Dev/Prod Parity: Keep Environments as Similar as Possible

The principle: Keep development, staging, and production environments as similar as possible.

This factor aims to eliminate the classic "it worked on my machine" problem. Discrepancies between environments (e.g., using an H2 in-memory database in dev but PostgreSQL in prod, or developing on Windows but deploying to Linux) introduce risk and make bugs harder to reproduce.

Modern Implementation:
Containerization is the primary solution here. By packaging your application and its dependencies into a Docker container, you create a portable artifact that runs identically everywhere. Tools like Cloud Native Buildpacks (which Spring Boot has built-in support for) and Docker Compose are essential for achieving dev/prod parity.

The Spring I/O video includes a powerful demo showing how to run a production container image locally to debug an issue, connecting it to the same backing services used in the cloud.

Building 12-Factor Spring Boot Applications: Simplicity, Scalability, and Best Practices @ Spring IO

Watch this final demo on 'Dev/Prod Parity'. It's a great illustration of how containers bridge the gap between development and production.

Watch from 35:32 to 40:47. The key idea is using the same buildpack to create a container for local execution that was used for the cloud deployment, and then injecting the same environment variables to connect to the cloud database. This makes the local environment a near-perfect replica of production.

Conclusion

The Twelve-Factor App methodology is more than a technical checklist; it's a philosophy for building modern, robust software. As a developer aiming for senior roles, you are expected to design systems that are not only functional but also scalable, maintainable, and operable in a distributed cloud environment. The twelve factors provide the guiding principles to achieve that.

Key Takeaways:

  • Purpose: The Twelve-Factor App is a manifesto for building cloud-native applications that are portable, resilient, and scalable.
  • Code vs. Config: Strictly separate your codebase from environment-specific configuration. Store config in environment variables.
  • Statelessness is Key: Design your services as stateless processes that share nothing. Offload all state to backing services (e.g., databases, distributed caches).
  • Dependencies: Explicitly declare dependencies (e.g., in pom.xml) and treat backing services as swappable, attached resources.
  • Embrace Parity: Use containerization (Docker) to minimize the differences between your development and production environments.
  • Logs as Streams: Treat logs as event streams directed to stdout, and let a separate, centralized service handle their aggregation and analysis.

Next Up

We've now covered how to design services and how to build them according to cloud-native best practices. But what if you're not starting from scratch? Most real-world work involves dealing with existing monolithic applications. In our next lesson, we will explore proven strategies for migrating from a monolith to microservices, with a focus on the strangler fig pattern.

Can't find a good explanation? Sign up and we'll make it for you

Sign up