Beyond the Twelve-Factor App by Kevin Hoffman

The author of the book Beyond the Twelve-Factor App, Kevin Hoffman is an Advisory Solutions Architect for Pivotal Cloud Foundry. Pivotal has made available the ebook version of this excellent resource as a free download. I have compiled key points from the book as a summary for future reference -

The Twelve-Factor App (a collection of patterns that are closely related to microservices approaches) is considered a requirement for cloud-native application architectures:
1. Codebase - One codebase tracked in revision control, many deploys
2. Dependencies - Explicitly declare and isolate dependencies
3. Configuration - Store configuration in the environment
4. Backing Services - Treat backing services as attached resources
5. Build, release, run - Strictly separate build and run stages
6. Processes - Execute the app as one or more stateless processes
7. Port binding - Export services via port binding
8. Concurrency - Scale out via the process model
9. Disposability - Maximize robustness with fast startup and graceful shutdown
10. Dev/prod parity - Keep development, staging, and production as similar as possible
11. Logs - Treat logs as event streams
12. Admin processes - Run admin/management tasks as one-off processes

A group of people within Heroku developed the 12 Factors in 2012. This is essentially a manifesto describing the rules and guidelines that needed to be followed to build a cloud-native  application.

The goal of these 12 factors was to teach developers how to build cloud-ready applications that had declarative formats for automation and setup, had a clean contract with the underlying operating system, and were dynamically scalable

Twelve-factor applications are an excellent start toward building applications that operate in the cloud, but to build cloud-native applications that truly thrive in the cloud, you need to look beyond the 12 factors.

Cloud-friendly applications don’t just run in the cloud; they embrace elastic scalability, ephemeral filesystems, statelessness, and treating everything as a service. Applications built this way can scale and deploy rapidly, allowing their development teams to add new features and react quickly to market changes.

People often use “12 factor” and “cloud native” interchangeably

A cloud-native application is an application that has been designed and implemented to run on a Platform-as-a-Service installation and to embrace horizontal elastic scaling.

Today, we need to be able to focus squarely on the one thing that we do better than our competitors and let platforms take care of our nonfunctional requirements.

By embracing cloud-native architecture, and building our applications on the assumption that everything is a service and that they will be deployed in a cloud environment, we can get all of these benefits and much more. 

1. One codebase, one application
Cloud-native applications must always consist of a single codebase that is tracked in a version control system.

The simplest example of violating this guideline is where your appli‐ cation is actually made of up a dozen or more source code repositories. This makes it nearly impossible to automate the build and deploy phases of your application’s life cycle

One codebase, one application does not mean you’re not allowed to share code across multiple applications; it just means that the shared code is yet another codebase.

2. Dependency management
A cloud-native application never relies on implicit existence of system-wide packages. For Java, this means that your applications cannot assume that a container will be managing the classpath on the server. For .NET, this means that your application cannot rely on facilities like the Global Assembly Cache.

Regardless of language, your code cannot rely on the pre-existence of dependencies on a deployment target.

3. Configuration, credentials, and code
Configuration refers to any value that can vary across deployments (e.g., developer workstation, QA, and production).

4. Backing services
A backing service is any service on which your application relies for its functionality. This is a fairly broad definition, and its wide scope is intentional. Some of the most common types of backing services include data stores, messaging systems, caching systems, and any number of other types of service, including services that perform line-of-business functionality or security.

When building applications designed to run in a cloud environment where the filesystem must be considered ephemeral, you also need to treat file storage or disk as a backing service. You shouldn’t be reading to or writing from files on disk like you might with regular enterprise applications. Instead, file storage should be a backing service that is bound to your application as a resource.

The binding of an application to its backing services should be done via external configuration.

5. Design, build, release, and run
Builds are ideally created by a Continuous Integration server, and there is a 1:many relationship between builds and deployments.

6. Stateless processes
All long-lasting state must be external to the application, provided by backing services. So the concept isn’t that state cannot exist; it is that it cannot be maintained within your application. As an example, a microservice that exposes functionality for user management must be stateless, so the list of all users is maintained in a backing service (an Oracle or MongoDB database, for instance).

Your processes can vanish at a moment’s notice with no warning, and that’s a good thing. Processes come and go, scale horizontally and vertically, and are highly disposable. This means that anything 
shared among processes could also vanish, potentially causing a cascading failure.

The filesystem is not a backing service. If processes need to share data, like session state for a group of processes forming a web farm, then that session state should be externalized and made available through a true backing service.

There are dozens of third-party caching products, including Gemfire and Redis, and all of them are designed to act as a backing service cache for your applications. They can be used for session state, but they can also be used to cache data your processes may need during startup and to avoid tightly coupled data sharing among processes.

7. Port binding
Web applications, especially those already running within an enterprise, are often executed within some kind of server container. The Java world is full of containers like Tomcat, JBoss, Liberty, and WebSphere. Other web applications might run inside other containers, like Microsoft Internet Information Server (IIS).

There must always be a 1:1 correlation between application and application server. In other words, your cloud provider might support a web app container, but it is extremely unlikely that it will support hosting multiple applications within the same container, as that makes durability, scalability, and resilience nearly impossible.

8. Concurrency
Adding CPUs, RAM, and other resources (virtual or physical) to a single monolithic application is called vertical scaling

A much more modern approach, one ideal for the kind of elastic scalability that the cloud supports, is to scale out, or horizontally.

If you are building disposable, stateless, share-nothing processes then you will be well positioned to take full advantage of horizontal scaling and running multiple, concurrent instances of your application so that it can truly thrive in the cloud.

9. Disposability
A cloud-native application’s processes are disposable, which means they can be started or stopped rapidly. An application cannot scale, deploy, release, or recover rapidly if it cannot start rapidly and shut down gracefully. We need to build applications that not only are aware of this, but also embrace it to take full advantage of the platform.

10. Environment parity
While the opportunities for creating a gap between environments are nearly infinite, the most common culprits are usually:
• Time
• People
• Resources

11. Logs 
Cloud applications can make no assumptions about the file system on which they run, other than the fact that it is ephemeral. One of the many reasons your application should not be controlling the ultimate destiny of its logs is due to elastic scalability.

When your applications are decoupled from the knowledge of log storage, processing, and analysis, your code becomes simpler, and you can rely on industry-standard tools and stacks to deal with logs.

12. Administrative processes
Examples of administrative processes that should probably be refactored into something else include:
• Database migrations
• Interactive programming consoles (REPLs)
• Running timed scripts, such as a nightly batch job or hourly import
• Running a one-off job that executes custom code only once

Don’t use your favorite programming language to spawn a new process to run your job; use something designed to run one-off tasks in a cloud-native manner. In a situation like this, you could use a solution like Amazon Web Services Lambdas, which are functions that get invoked on-demand and do not require you to leave provisioned servers up and running

Ask yourself if you really need administrative processes, or if a simple change in architecture could obviate them.

13. API first
API first gives teams the ability to work against each other’s public contracts without interfering with internal development processes

API first means that what you are building is an API to be consumed by client applications and services.

If you adopt the mentality that all applications are just backing services and that they should be designed API-first, then your system is free to grow, adapt to new load and demand, and accommodate new consumers of existing services without having to stop the world to re-architect yet another closed system.

14. Telemetry
When it comes to monitoring your application, there are generally a few different categories of data: 
• Application performance monitoring (APM) - "average number of HTTP requests per second an application is processing"
• Domain-specific telemetry - "number of widgets sold to people on iPads within the last 20 minutes"
• Health and system logs - application start, shutdown, scaling, web request tracing, and the results of periodic health checks


15. Authentication and authorization
In an ideal world, all cloud-native applications would secure all of their endpoints with RBAC (role-based access control). Every request for an application’s resources should know who is making the request, and the roles to which that consumer belongs.

Security should be something that is baked into the application’s development from day one, and not added as a bolt-on project after an application is running in production.

Comments