Why Review This?
The 12 factor app “best practices” are a good overview of most things you should be doing in a modern application. They provide a good overview of many architectural and dev-ops practices which people tend to learn in a more peace-meal and painful way over time.
Sources / References
While reviewing this recently myself, I read and viewed a few sources. I found this video https://www.youtube.com/watch?v=94PxlbuizCU on YouTube gave a very practical summary… especially for those who like to work in Java a lot such as myself. I also found this other one from AWS https://pages.awscloud.com/Building-Microservices-with-the-12-Factor-App-Pattern-on-AWS_0512-CON_OD.html which fills in some areas better than the other, especially when it comes to practical use with containerization and also in the area of logging. I recommend taking the time to watch both.
The Twelve Factors
Provided with my interpretation/commentary of course based on the various sources I looked at.
- Code Base – Should be checked into version control (e.g. Git). Should have one application per repository (or sub-module). E.g. if you have an API and a website, separate them so you don’t end up having to combine releases / so you aren’t inclined to couple them and muddy the waters.
- Dependencies – Should be declared. Should also be external to your code base (e.g. pull from a maven repo, don’t check JARs into your git repo). Should avoid using anything from outside the project (e.g. if you’re in Node, don’t global install anything, make sure everything is local to the project). This also kind of leads to the fact that you should not rely on something like an app server (e.g. tomcat) external to your app to host your app. Your app should be more like a spring-boot application which has is deployed with its own web server bundled in.
- Configuration – Passwords/tokens/connections/etc should be separate from your code. E.g. using profiles and ansible-secrets/etc within your code base to handle multiple environments can be bad because you have to modify your code base to support new environments. These configurations should be moved out to environment variables or some other mechanism. The use of environment variables seems to be recommended in many frameworks/technologies online these days, so I think its good to do when possible. Also note, the use of CNAMES and other flexible configuration mechanisms to abstract the location from which things are running is recommended.
- Backing Services – Databases/queues/caches/etc should be decoupled and easily changeable in your configuration. Again, prefer CNAMEs/etc for abstraction.
- Build/Release/Run – A deployment = building code, combining it with config to form a release, and executing that release in the target environment. This one sounds a little… flexible to me? E.g. Helm lets you take a template, apply values to it, and deploy the result to kubernetes. So, in that case, I guess the helm template/vars part is the “release” formation, and running it is the execution of helm? We don’t need separate docker images built for each environment in this case. If, on the other hand, we’re taking each environment’s config and generating a new docker image per environment, then we’d have one release per environment (code + config combined), and it would be nicely decoupled from the “run”, in which case this makes more direct sense. Note that the AWS video calls this later case an anti-pattern and recommends deploying the config within the environment and using one standard docker image (which sounds better). This could be achieved with config maps, secrets, etc in kubernetes, for example.
- Processes – Stateless (stick sessions = bad).
- Port Binding – Back to the end of #2/dependencies – apps should be self-contained. They should provide their own web server and should just expose a port themselves as-is and should not depend on anything else to do so.
- Concurrency – Build apps to scale out, not just up. Of course you can have more threads/pools/etc… but if you can break up your application well, you can have separate apps for each purpose and each can scale at its own pace and use resources effectively. E.g. if I have an app reading files and dumping records to Kafka, and then it reads the kafka records back out and sends them to an external service, it can be 2 separate apps. Maybe the first part that reads files needs 4 copies with large memory to keep pace, and the second one needs 10 copies with high CPU and low memory to keep pace. They can be dealt with individually and easily once designed well (and this sort of thing is a cake walk when using something like kubernetes).
- Disposability – “Servers are cattle, not pets”. You don’t want a long lived server (like an app server) where people know its name and IP and how to debug it. You want little, ephemeral, easily created and destroyed servers (or rather, containers). In the cloud, you may use auto-scaling-groups / scale sets (AWS/Azure) to easily scale up and scale down copies of a server (machine image). But even this isn’t great; it’s heavyweight and slow. If you have an container orchestrator like kubernetes (or docker swarm, etc) in the cloud, or even on-prem, you can scale up and down docker/etc containers in seconds at large scale. Either way, you should basically never need to look at a running machine image or a container unless you’re debugging something. You can create new ones trivially and you don’t care about them. They are disposable.
- Dev/Prod Parity – All your environments should be identical minus their configuration. Use the same databases, same caches, same web server versions (bundling web-servers into code like spring-boot does helps with the last one).
- Logs – View logs as a continuous stream of events and store them in an external place like ELK or Splunk. Preferably have an agent separate from your app be responsible for uploading the log stream; just let your app write to standard output.
- Admin Processes – Things like database pruning, backups, etc. should be treated as any other application you are running and they should be distinct applications separate from the application(s) they are aiding. E.g. any of those processes should basically follow these steps themselves.