mirror of
https://github.com/kennethreitz/12factor.git
synced 2026-06-05 23:10:17 +00:00
bunch of tweaks plus attached resources diagram
This commit is contained in:
@@ -3,13 +3,15 @@
|
||||
|
||||
A *backing service* is any service the app accesses over the network as part of its normal operation. Examples include datastores (such as [MySQL](http://dev.mysql.com/) or [CouchDB](http://couchdb.apache.org/)), messaging/queueing systems (such as [RabbitMQ](http://www.rabbitmq.com/) or [Beanstalkd](http://kr.github.com/beanstalkd/)), SMTP services for outbound email (such as [Postfix](http://www.postfix.org/)), and caching systems (such as [Memcached](http://memcached.org/)).
|
||||
|
||||
Traditionally, backing services such as the database are considered "local," and part of the app. In addition to these locally-managed services, the app may also have services provided and managed by third parties. Examples include SMTP services (such as [Postmark](http://postmarkapp.com/)), metrics-gathering services (such as [New Relic](http://newrelic.com/) or [Loggly](http://www.loggly.com/)), binary asset services (such as [Amazon S3](http://aws.amazon.com/s3/)), and even API-accessible consumer services (such as [Twitter](http://dev.twitter.com/), [Google Maps](http://code.google.com/apis/maps/index.html), or [Last.fm](http://www.last.fm/api)).
|
||||
Traditionally, backing services like the database are managed by the same admins as the app's deploy. In addition to these locally-managed services, the app may also have services provided and managed by third parties. Examples include SMTP services (such as [Postmark](http://postmarkapp.com/)), metrics-gathering services (such as [New Relic](http://newrelic.com/) or [Loggly](http://www.loggly.com/)), binary asset services (such as [Amazon S3](http://aws.amazon.com/s3/)), and even API-accessible consumer services (such as [Twitter](http://dev.twitter.com/), [Google Maps](http://code.google.com/apis/maps/index.html), or [Last.fm](http://www.last.fm/api)).
|
||||
|
||||
**The twelve-factor app makes no distinction between local versus third party services.** To the app, both are backing services, accessed via a URL or other locator/credentials stored in the [config](/config). A [deploy](/codebase) of the twelve-factor app should be able to swap out a local MySQL database with one managed by a third party (such as [Amazon RDS](http://aws.amazon.com/rds/)) without any changes to the app's code. Likewise, or a local SMTP server could be swapped with a third-party SMTP service (such as Postmark) without code changes. In both cases, only the resource handle in the config needs to change.
|
||||
**The code for a twelve-factor app makes no distinction between local versus third party services.** To the app, both are backing services, accessed via a URL or other locator/credentials stored in the [config](/config). A [deploy](/codebase) of the twelve-factor app should be able to swap out a local MySQL database with one managed by a third party (such as [Amazon RDS](http://aws.amazon.com/rds/)) without any changes to the app's code. Likewise, or a local SMTP server could be swapped with a third-party SMTP service (such as Postmark) without code changes. In both cases, only the resource handle in the config needs to change.
|
||||
|
||||
A backing service like Amazon RDS provisions into *private resources*. The app has a resource handle (ideally in URL form, like `mysql://user:pass@host/db`) which points to the private resource and provides everything the app needs to access it. For a service Amazon RDS, the resource is a database. For a service like New Relic or a consumer service like Last.fm, the resource is an account or a user.
|
||||
Provisioning a database from a cloud-based backing service like Amazon RDS is done via API. Provisioning produces a *private resource*. The provisioned resource has a resource handle (typically in URL form, like `mysql://user:pass@host/db`) which points to the private resource and provides everything the app needs to access it. For a service Amazon RDS, the resource is a database. For a service like New Relic or a consumer service like Last.fm, the resource is an account or a user.
|
||||
|
||||
Further, the resources are referred to as *attached resources* to indicate their loose coupling to the deploy they are connected to. Resources can be attached to and detached from deploys at will.
|
||||
The twelve-factor app treats these resources as *attached resources*, which indicates their loose coupling to the deploy they are attached to. Resources can be attached and detached to deploys at will.
|
||||
|
||||
<img src="/images/attached-resources.png" style="float: none" alt="A production deploy attached to four backing services." />
|
||||
|
||||
For example, the process of upgrading a database to a server with more compute and memory will typically look like this:
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ This violates twelve-factor because it stores config in the codebase.
|
||||
end
|
||||
end
|
||||
|
||||
This violates twelve-factor because it uses a conditional on the RACK_ENV value which stores an deploy name like `staging` or `production`. This stores config in the codebase (the hardcoded hostnames), and uses a non-granular, non-orthogonal deploy name to choose the canonical host. Both of these things make it impossible to add new deploys without changing the code. (As a thought experiment, imagine what it would take to open-source an app that had the above block of code in it.)
|
||||
This violates twelve-factor because it uses a conditional on the `RACK_ENV` value which stores an deploy name like `staging` or `production`. This stores config in the codebase (the hardcoded hostnames), and uses a non-granular, non-orthogonal deploy name to choose the canonical host. Both of these things make it impossible to add new deploys without changing the code. (As a thought experiment, imagine what it would take to open-source an app that had the above block of code in it.)
|
||||
|
||||
## Doing it right: Hostname from an env var
|
||||
|
||||
|
||||
+6
-6
@@ -3,18 +3,18 @@
|
||||
|
||||
An app's *config* is everything that can vary between [deploys](/codebase). This includes:
|
||||
|
||||
* URLs to to app's database, memcached, and other [backing services](#)
|
||||
* Resource handles to the database, memcached, and other [backing services](#)
|
||||
* Credentials to external services such as Amazon S3 or Twitter
|
||||
* Per-deploy values such as the canonical hostname for each deploy
|
||||
|
||||
A common practice is for apps to store config as constants in the code. This is a violation of twelve-factor, which requires strict separatation of config from code. Config varies substantially across deploys, code does not. A litmus test for whether an app has all config correctly factored out of the code is whether the codebase be published as open source without compromising any credentials.
|
||||
A common practice is for apps to store config as constants in the code. This is a violation of twelve-factor, which requires **strict separatation of config from code**. Config varies substantially across deploys, code does not. A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could open sourced as-is without compromising any credentials.
|
||||
|
||||
Another approach is to store config in config files which are not checked into revision control, such as `config/database.yml` in Rails. This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it's easy to mistakenly check in a config file to the repo, and there is a tendency for config files to be scattered about in different places and different formats, making it hard to see all the config for a given deploy at one time.
|
||||
Another approach to config is storing it in config files which are not checked into revision control, such as `config/database.yml` in Rails. This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it's easy to mistakenly check in a config file to the repo, and there is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place.
|
||||
|
||||
The best place for config is in *environment variables* (often shortened to *env vars* or *env*). Env vars are easy to change between deploys without changing any code, and unlike config files, there is no chance of them being checked into the code repo accidentally.
|
||||
**The twelve-factor app stores config in *environment variables*** (often shortened to *env vars* or *env*). Env vars are easy to change between deploys without changing any code, and unlike config files, there is little chance of them being checked into the code repo accidentally.
|
||||
|
||||
One method of managing config is batching it up into named groups (often called "environments"), such as the `development`, `test`, and `production` environments in Rails. This method does not scale cleanly: as more deploys of the app are created, new environment names are necessary - for example, `staging` or `qa`. As the project grows further, developers may add their own special environments like `joes-staging`. The worst outcome of this is when the application begins using conditionals to change behavior based on the environment name - code that looks like `do_something_for_production if Rails.environment == 'production'`.
|
||||
Yet another method of managing config is batching it up into named groups (often called "environments") named after specific deploys, such as the `development`, `test`, and `production` environments in Rails. This method does not scale cleanly: as more deploys of the app are created, new environment names are necessary - for example, `staging` or `qa`. As the project grows further, developers may add their own special environments like `joes-staging`. The worst outcome of this is when the application begins using conditionals to change behavior based on the environment name - code that looks like `do_something_for_production if Rails.environment == 'production'`.
|
||||
|
||||
In a twelve-factor app, env vars are each orthogonal values, not grouped together as "environments," but independently controllable for each deploy. This is a model that scales up smoothly as the app naturally grows more deploys over its lifetime.
|
||||
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as "environments," but instead are independently controllable for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.
|
||||
|
||||
A common pattern with env vars is to fall back on sensible defaults when not set. For example, an app may use the `CANONICAL_HOST` env var for redirects, but the app will not attempt a redirect the the value is not set (which is usually desirable for development deploys). Or assuming a local memcached if the MEMCACHED_URL is not set. In this way, no env vars means the app is running as a vanilla development deploy.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
## II. Dependencies
|
||||
### Explicit dependency declaration and isolation
|
||||
|
||||
Most programming languages offer a packaging system for distributing support libraries, such as [CPAN](http://www.cpan.org/) for Perl or [Rubygems](http://rubygems.org/) for Ruby. Libraries installed through a packaging system can be installed system-wide (known as "site packages") or only into the directory containing the app (known as "vendoring" or "bundling").
|
||||
Most programming languages offer a packaging system for distributing support libraries, such as [CPAN](http://www.cpan.org/) for Perl or [Rubygems](http://rubygems.org/) for Ruby. Libraries installed through a packaging system can be installed system-wide (known as "site packages") or scoped into the directory containing the app (known as "vendoring" or "bundling").
|
||||
|
||||
A twelve-factor app *never* relies on implicit existence of system-wide packages. It declares all dependencies, completely and exactly, via a *dependency declaration* tool. Furthermore, it uses *dependency isolation* tool during execution to ensure that no implicit dependencies "leak in" from the surrounding system.
|
||||
**A twelve-factor app never relies on implicit existence of system-wide packages.** It declares all dependencies, completely and exactly, via a *dependency declaration* tool. Furthermore, it uses a *dependency isolation* tool during execution to ensure that no implicit dependencies "leak in" from the surrounding system.
|
||||
|
||||
For example, [Gem Bundler](http://gembundler.com/) for Ruby offers the `Gemfile` format for declaration and `bundle exec` for dependency isolation. In, Python there are two separate tools for these steps - [Pip](http://www.pip-installer.org/en/latest/) is used for dependency declaration and [Virtualenv](http://www.virtualenv.org/en/latest/) for dependency isolation. Regardless of tools, dependency declaration and isolation must be used together - only one or the other is not sufficient for a twelve-factor app.
|
||||
For example, [Gem Bundler](http://gembundler.com/) for Ruby offers the `Gemfile` format for dependency declaration and `bundle exec` for dependency isolation. In, Python there are two separate tools for these steps - [Pip](http://www.pip-installer.org/en/latest/) is used for declaration and [Virtualenv](http://www.virtualenv.org/en/latest/) for isolation. No matter what the toolchain, dependency declaration and isolation must always be used together - only one or the other is not sufficient for a twelve-factor app.
|
||||
|
||||
One benefit of explicit dependency declaration is that it simplifies setup for developers new to the app. The new developer can check out the app's codebase onto their development machine, requiring only the language runtime and dependency manager installed as prerequisites. They will be able to set up everything needed to run the app's code with a *deterministic build command*. For example, the build command for Ruby/Bundler is `bundle install`, while for Clojure/Leiningen it is `lein deps`.
|
||||
One benefit of explicit dependency declaration is that it simplifies setup for developers new to the app. The new developer can check out the app's codebase onto their development machine, requiring only the language runtime and dependency manager installed as prerequisites. They will be able to set up everything needed to run the app's code with a deterministic *build command*. For example, the build command for Ruby/Bundler is `bundle install`, while for Clojure/Leiningen it is `lein deps`.
|
||||
|
||||
Twelve-factor apps also do not rely on the implicit existence of any system tools. Examples include shelling out to ImageMagick or `curl`. While these tools may exist on many or even most systems, there is no guarantee that they will exist on all systems where the app may run in the future, or whether the version found on a future system will be compatible with the app. If the app needs to shell out to a system tool, that tool must be vendored into the app.
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Who should read this document?
|
||||
==============================
|
||||
|
||||
Any developer building applications run as a service. Ops engineers who deploy or run such applications.
|
||||
Any developer building applications run as a service. Ops engineers who deploy or manage such applications.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Reference in New Issue
Block a user