12 factor app is a set of principle used to build an app to make it as independent from hosting provider as possible with keeping software development best practices.
It was created by Heroku engineers who worked on the platform and later supported thousands of customers projects on Heroku. Main appliance was SaaS however most of them applies to any web app.
- Codebase – it has to be single codebase (code tracked in version control system – git, CVS, TFS, SVN, Mercurial). If application/system has multiple components (e.g. multiple microservices), each can have separate code repository. You cannot keep different version of the same code in different locations as you loose the coherency. Also you won’t be able to deploy the same version on different environments. This point is very often formal requirement (e.g. in financial industry).
- Dependencies – they have to be declared in explicit way and isolated. So the best way is to use package solution instead of system installed libraries. For .NET folks we have nuget which is very rich repository. From the other hand in some cases we have to rely on COM libraries so we have no other option. Also you should not have a shared code between multiple apps as it leads to distributed monolith which is very inflexible option in long term perspective.
- Config – store config in the environment. Suggested approach is to keep them in environment variables. Maybe it is good for Linux, but rather a bit problematic in case of Windows. Personally I prefer central configuration database like Library in Azure (with appSettings.ENVIRONMENT.json). With growth of the project, this solution might be problematic as various developers may need some specific settings (e.g. database name, local URLs, etc). Also platform dependent solution is against 12 factor app concept so this particular one would not be acceptable. Also we should not keep the configuration in code repository as configuration contains fragile data like credentials to production to which developers should not have direct access.
- Backing services – each backing service (e.g. mail server, database server etc) is treated like an attached resource, so it should be easy to replace one by one just by changing the configuration (e.g. local instance of API with remote one etc). We should never rely on the hardcoded credentials or path(s).
- Build, release and run – separate build, config transformation and run (release/deployment). The same code is deployed from build stage in combine with defined configs. Configs are different per environment and also may change while code remains the same. Release process is usually covered by change management process. Also each release should be revertable (rarely we have to perform rollback due to unpredicted issues).
- Processes – app as one or more stateless processes. A keyword is stateless. It means that we have multiple instances of application (on the same or multiple nodes) none of them should have “own” user’s data (kept in local cache/session). Thanks to that app is resilient on scaling nodes (up and down).
- Port binding – service should be available via port binding (e.g. web app on Kestrel on port 8081). Quite often in enterprise environments, load balancer has a web page on port 443 (HTTPS) and the servers behind it may be available on various ports as HTTP (SSL offloading) as multiple instances
- Concurrency – scaling each system component separatly (e.g. front-end in Angular will need different resources than backend). A great example are microservices (or any cloud service) where each one can scale separatly based on the current load.
- Disposability – fast startup with graceful stop (no exceptions, new requests not handled).
- Dev/prod parity – keep environments as similar as possible. It is very important as various components may have significant impact on the app behaviour (e.g. database version may have different performance or execute query with other execution plan). Also such thing like presence of load balancer behaviour may impact app behaviour (in negative way) – for example if application was written in statefull way which is break of rule #6. Thanks to tools like Puppet, ARM Templates or similar we can keep environments consistent and avoid unnecessary troubleshooting.
- Logs – we should treat all gathered logs as system events, so independently on how many log sources we have, we should gather their output and order in chronological order with a common key per request (Correlation Id) which helps relate events to a request. In one project I had few separated logs connected by correlation however it is very hard to combine them as they are kept in different places (even with correlation id). The source of problem was lack of common displaying. Nowadays there is a lot or ready solutions for that (cloud native or just well known products like Elastic search + Logstach + Kibrana – known as ELK).
- Admin processes – run just once, testable in DEV/QA environments. A good sample are database migration (e.g. with Entity Framework). They are a part of codebase so easy to track, validate and repeat.
To summarize – it is worth to take these rules into account as they will improve the overall maintenance (including code, deployments, hosting changes and monitoring).
If you would like to read more on above rules, visit https://12factor.net/.