An environment agnostic Rails application

Although this thread started to me from the single bug (unexpected “feature”) I want to address the issue more widely.

There is a methodology (approach) to develop portable, scalable and maintainable web-services called “The Twelve-Factor App”. On of it’s “factors” is the environment agnostic approach to configuration (although it is not called so explicitly in the document, I think this two words expresses the sense): The Twelve-Factor App

It is almost possible to be environment agnostic with Rails. But there are some issues.

The issue I mentioned is this: rails/database_tasks.rb at 3ba176d0a07728e9505609967b518d85129bff24 · rails/rails · GitHub

And here is some consequences: Undesired interaction between figaro/active_record for rake db tasks (rake runs twice for db tasks) · Issue #225 · laserlemon/figaro · GitHub

Rails tries to be in two environments in a single run. It looks bad. It’s surprising. It prevents effective environment management (homogeneous config applicable to any environment).

What do you about environment agnostic approach and about it implicit support by Rails (e.g. everything works if you doesn’t mention any environment enywhere in your project)?

+1 on at least discussing what can be done about this.

The dev-env-switching-to-test thing has caused me lots of headaches over the years, including in areas other than the db handling mentioned here - the most recent coincidentally being today.

Another way to think of the problem is that on local dev environments, you default to “development” whenever you run rake or start up the app, but it’s normal practice for CI environments to hardcode their container environments to “test”.

That sets up a built-in opportunity for problems due to different behavior on CI than on local dev boxes - especially for any config value that is different between dev/test env, but is persisted (e.g. setting environment variables) between the Rakefile invocation and the test helper switching the ENV to test.

When you get more complex apps and situations, e.g. Procfiles for various environments which start multiple processes, and nested scripts/rake tasks/test suites, especially ones like cucumber/integration that need to precompile assets on the fly but maybe not start up services, it can be hard to sort out what config values came from where, and why.

The 12-factor app guidelines (http://12factor.net/config) are correct in saying that you should try to be environment agnostic, but also valid criticism of its omissions/vagueness/hand-waviness in this area: http://cloudfactor.io/factor/three/

I’m not sure what the fix is, but in general moving away from fixed “environments” to more granular config values seems good.

This could look something like:

  • Deprecating the use of Rails.env and RAILS_ENV

  • Assuming that development is no different than any other non-test environment, move all config settings from generated development.rb to be conditional/commented entries in application.rb, and allow/require people to override them as appropriate for their current environment. E.g.

  • Likewise requiring test- specific code or libraries that currently rely on setting/checking test environment to instead directly read or set granular Rails configuration values.

For example, people or libraries could set env vars (just examples, but you get the idea) RAILS_EAGER_LOAD or RAILS_ACTION_CONTROLLER_PERFORM_CACHING before app startup to override any defaults in application.rb, and (admittedly hard, alotofwork, or impossible in some cases) libraries could also override values on the fly after initialization as appropriate.

That would be a big change with lots of ripples, but definitely a move in the right direction in an increasingly cloud-native 12-factor-ish world.

Thanks,

– Chad

I’m glad to see the rational start of this discussion.

Here is some of the rest of my thoughts.

I have exactly the same problems with development tasks and CI in the past. One of the reasons I have found 12factor reasonable.

About criticism. I think that phrase has been misunderstood a lot. While keeping in mind overall idea here is what I can say about it:

Env vars are granular controls, each fully orthogonal to other env vars in the codebase. The application itself does not do any assumptions about the environment it is launched in.

“You WILL have logical groupings of configuration”, absolutely! But this is not a responsibility of the application. It is the responsibility of an administrator to give all the required information to an application about an environment inside of which it is launched.

Environment variables is the uniform mechanism of exchanging environmental information. That’s why they’re called just that.

An application should just take the env. A developer should just fill the env with useful information. And a developer may use any possible tools to make this task easy.

I’m sure you know or heard about tools like chruby, rbenv, rvm… This is what I’m talking about. Environment management can be easy and flexible. If you can run “rvm 1.9 do gem update” to switch the entire interpreter, why wouldn’t you do something similar to switch “logical groupings of configuration”?

Did you know that you are able to do something like that: “rvm 1.9,2.3 do gem update”? Now imagine that you are able to do that: “env development,test do rake db:drop”. Oh my, this is so explicit and obvious that you can’t even make a mistake in what database you just actually wiped out!

About config/environments/*.rb. First of all, you can map all the values to ENV in config/application.rb so there are no “hard” problems.

But there is more to say. Let’s remember what values we actually talking about:

  • 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 the deploy As you see, just a fraction of config/environments are actual “environmental” configs. But what about the rest of configs? They’re outside of context of 12factor. They’re the part of some application logic. You really do not want to overwrite them often, unless some appreciable change to the application happens. I would say config/environments are rather “mods” than “environments”. Deterministic models of behaviour.

If you do not wish to suffer you follow this The Twelve-Factor App. Surely, there just have to be some variations between environments. But are they so significant and unpredictable? Think about a modern development cycle and try to figure out really different application states. I think, there are three of them:

  • non-interactive automation,
  • debugging,
  • production-ready. Yes, they’re just familiar test, development and production from config/environments. If more of them exist, there would be more of them by default in a new rails app.

Does it any good to let those configs be in the codebase? It is. How many times have you seen flags (args) like “–debug” and “–no-interaction”? I would bet you rarely think about how many configs does a single flag change. There just exists a concrete purpose for each of these flags.

For any distinct purpose you create a flag, everything beneath are implementation details.

Actually, this part is already works wery well. As I mentioned at the beginning of the paragraph, environment-specific parameters can be set from ENV. And others are managed with RAILS_ENV clearly.

There are not so many problems so far.

Environments from config/database.yml are accessed by Rails explicitly. This makes the usage of gems like figaro and config almost pointless, because you either ruin your development process or just brings +1 config file into the codebase because are still required to have database.yml for things to work.

This problem is almost entirely related to developer’s rake tasks. Even a complete rethinking of env usage in rake tasks will not break any existing deployments.

Even a minor version can make a huge step forward.

вівторок, 12 січня 2016 р. 06:39:51 UTC+2 користувач Chad Woolley написав: