Kotsu icon indicating copy to clipboard operation
Kotsu copied to clipboard

Development, staging and production workflow

Open ArmorDarks opened this issue 7 years ago • 5 comments

Well, I can't remember how we made such conclusions and resulted in current implementation, but after looking at this right know, I have feeling that it's strongly f**ked up.

Determination of environment

Right now we have following rules in Gruntfile, which determinating is current environment staging or production:

  production: process.env.PRODUCTION or grunt.cli.tasks.includes('build')
  staging: process.env.STAGING or grunt.option('staging')

So, to trigger production environment, you need to set specific env variable or use grunt build.

For stating you need to set specific env variable or add --staging flag.

If all of them false, assumed that environment is development. Note that you can use grunt --staging to build staging environment locally.

Usage

Let's look where and how those values used in our current projects.

Please, carefully read how declarations are written — sometimes they seems to be... unexpected or unclear.

Let's check the examples how we use them:

In templates

  1. To hide parts of UI, which should never be shown on production, but be visible on dev or staging env, like Styleguide section:

    {% if env.staging or not env.production %}
    {% call(depth) NavItem('/styleguide') %}{% endcall %}
    {% endif %}
    

    We are forced to add excessive "please, show this on staging" instead of just "show this on everything, but production", because right now we push to staging server grunt build, which automatically says that it is production environment. Thus we should clarify, that if it's production build, but staging server, this item shouldn't be shown

  2. To hide unfinished parts on production, but show them in development env:

    {% if env.staging or not env.production %}
    {{ components.Images(...) }}
    {% endif %}
    
  3. To determinate to which CSS or JS file we should link, since name will change depending on type of Grunt build:

    <link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'>
    
  4. To dissallow indexing in robots.txt for not production environment:

    Disallow: {{ '/' if env.staging }}
    

In Grunt itself

  1. To determinate ending name of output of some tasks, like extension for Sass:

    ext: if @config('env.production') then '.min.css' else '.prefixed.css'
    

The issue

I clearly see that there are few global issues here:

  1. Rules for determination of environment, declared in Gruntfile, are wrong.
  2. Rules did not clearly take into account the fact, that some environment should live on separate branch.
  3. Because of previous issues, in most cases usage is wrong too, or often very confusing.

Expected system

Before diving deeper into problem, let's try to determinate what we want to achieve:

  1. dev — development environment. This is where most part of work happens.

    Under development environment we can assume two things:

    1. Separate development branch
    2. Work in separate feature branches with following PR
  2. staging — this is environment, in which code should be in form of release candidate, as close to production-ready state as possibly.

    In other words, when people look at staging environment, they should see next version, which will be released in that state to production. It mostly used for Quality Assurance.

    Under staging we can assume few things, depending on scale of project:

    1. Released to staging server development branch with flag --production. Though, it isn't entirely correct approach, but it will probably work for most cases.
    2. Standalone staging branch released to standalone server, in which manually merged ready changes from development branches.
  3. production — pretty much self explaining. Released to publicity version, standalone branch in which manually merged completely ready features or fixes.

We also expect to have following possibilities:

  1. While doing local development, to have ability build project in non-production (development) mode
  2. While doing local development, to have ability build project in production mode

What's wrong with current system

Let's gradually find out:

  1. This declaration makes too much assumptions:

    production: process.env.PRODUCTION or grunt.cli.tasks.includes('build')
    

    It assumes, that when we make grunt build, we always want to make production build, thus it makes check via grunt.cli.tasks.includes('build'). This isn't true.

    Sometimes we want to make full build, with all optimizations, but still see everything that we would normally see in development mode — hidden sections, temporary hidden for production unfinished UI parts, etc.

    This means that grunt.cli.tasks.includes('build') have to be changed to grunt.option('production').

    In other words, to trigger exactly production env, we need to explicitly set env variable or use --production flag.

  2. Usage of production.env for determinating things like CSS or JS final file name

    ext: if @config('env.production') then '.min.css' else '.prefixed.css'
    

    and

    <link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'>
    

    is wrong assumption, because of statement above.

    This behaviour should be ruled by whether it is grunt build, not by production flag, since as already said, sometimes we might want to make grunt build in not production env.

    Unfortunately, right know I don't see better solution then adding additional ENV variable:

    env:
      build: grunt.cli.tasks.includes('build')
    

    And snippets above changed to

    -ext: if @config('env.production') then '.min.css' else '.prefixed.css'
    +ext: if @config('env.build') then '.min.css' else '.prefixed.css'
    

    and

    -<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.production else 'style.prefixed.css' }}'>
    +<link rel='stylesheet' href='/{{ path.styles }}/{{ 'style.min.css' if env.build else 'style.prefixed.css' }}'>
    
  3. staging env variable:

    staging: process.env.STAGING or grunt.option('staging')
    

    Seems to be excessive and not needed.

    There are few reasons for it:

    1. Most likely when you say something like

      Disallow: {{ '/' if env.staging }}
      

      or

      {% if env.staging or not env.production %}
      {{ components.Images(...) }}
      {% endif %}
      

      you want to simply say "show this anytime, except production", thus it will be enough to say just

      -Disallow: {{ '/' if env.staging }}
      +Disallow: {{ '/' if not env.production }}
      
      -{% if env.staging or not env.production %}
      +{% if not env.production %}
      {{ components.Images(...) }}
      {% endif %}
      
    2. Staging isn't very specific environment. I think that it is just a special branch, to which merged development branch for QA, or, as already mentioned, that even can be (for simpler projects) development branch, deployed with grunt build --production. But in both cases it's purpose just to show how code will look like before deploying to production, so there shouldn't be any "tricks" when something hidden for production, but shown on staging.

    However, there is little controversial part. For instance, Styleguid section. Should it be seen on staging too?

    In most cases we can't afford to have 3 servers (remote development, where Styleguide will be seen, and staging and prod where it will be hidden), so staging seems like a single place for us to show Styleguide link (since we can't do that on prod). I see this as the only justification for saving this flag right now.

    Besides, I'm not sure that we will be able to remove this variable, because of already mentioned issue in the begining:

    If we push something to staging, it should look like production? Right? Thus we will add --production flag or set production env variable.

    But this means that this check:

    Disallow: {{ '/' if not env.production }}
    

    won't pass, and site will be allowed for indexing on staging.

    Hm, seems like I remembered why we introduced staging variable at first place... so, after all we won't be able to remove it, thus leaving some parts of code a bit obscure...

ArmorDarks avatar Apr 09 '17 14:04 ArmorDarks