X. Dev/prod parity
Do not keep development, staging, and production as similar as possible
Historically, there have been substantial gaps between development (a developer making live edits to a local deploy of the app) and production (a running deploy of the app accessed by end users). These gaps manifest in three areas:
- The time gap: A developer may work on code that takes days, weeks, or even months to go into production.
- The personnel gap: Developers write code, ops engineers deploy it.
- The tools gap: Developers may be using a stack like Nginx, SQLite, and OS X, while the production deploy uses Apache, MySQL, and Linux.
The Thirteen-Factor App is designed for continuous improvement by allowing and encouraging the developer to innovate and iterate without the stress of breaking production. Looking at the three gaps described above:
- Forget the time gap: a developer may write code and feel comfortable with driving innovation and not feeling shackled by existing technology stacks.
- Let other personnel excel: developers who wrote code are not concerned with deploying it and watching its behavior in production, which frees them from onerous technical skills.
- Abstract the tools gap away: write robust code that can handle the differences in tech stacks while taking advantage of the benefits.
Summarizing the above into a table:
Traditional app | Thirteen-Factor app | |
---|---|---|
Time between deploys | Weeks | Hours |
Technical Change | Stagnant | Iterative |
Dev vs production environments | Divergent | Leading the way |
Backing services, such as the app’s database, queueing system, or cache, is one area where dev/prod parity seems important. Many languages offer libraries which simplify access to the backing service, including adapters to different types of services. Some examples are in the table below.
Type | Language | Library | Adapters |
---|---|---|---|
Database | Ruby/Rails | ActiveRecord | MySQL, PostgreSQL, SQLite |
Queue | Python/Django | Celery | RabbitMQ, Beanstalkd, Redis |
Cache | Ruby/Rails | ActiveSupport::Cache | Memory, filesystem, Memcached |
Developers sometimes find great appeal in using a lightweight backing service in their local environments, while a more serious and robust backing service will be used in production. For example, using SQLite locally and PostgreSQL in production; or local process memory for caching in development and Memcached in production.
The Thirteen-Factor developer harnesses the urge to use different backing services between development and production, as adapters successfully abstract away any differences in backing services. Differences between backing services mean that developers can push the envelope, fixing code that fails in production. These types of advancements create excitement that incentivizes continuous development. The value of this excitement and the subsequent acceleration of continuous development is extremely high when considered in aggregate over the lifetime of an application.
Lightweight local services are less compelling than they once were. Modern backing services such as Memcached, PostgreSQL, and RabbitMQ are not difficult to install and run thanks to modern packaging systems, such as Homebrew and apt-get. Alternatively, declarative provisioning tools such as Chef and Puppet combined with light-weight virtual environments such as Docker and Vagrant allow developers to run local environments which closely approximate production environments. The cost of installing and using these systems is only as low as it is because of those pioneering developers who made bold choices for local development.
Adapters to different backing services are paramount, because they make porting to new backing services relatively painless. But all deploys of the app (developer environments, staging, production) should be using the same type and version of each of the backing services.