Friday, August 14, 2015

Creating the Packager DSL - First feature

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
In our last segment, we did everything necessary to set up the Packager DSL repository, but it doesn't actually do anything. Let's make a start on fixing that.

User Story

First, we need a description of what we're going to do. This, in Agile, would be the User Story. Well, first-er, we need to decide what we're going to do. For that, let's look at why our project exists at all.

The Packager DSL's purpose is to provide an easy way to describe what should go into a package, primary focusing on OS-packages (RPM, DEB, MSI, etc). Eventually, it should be usable to describe (nearly) every type and construction of OS package that's reasonable to expect to use. So, any package you might install on your RedHat, Ubuntu, or Windows server should be describable with this DSL. We'll defer the actual package construction to fpm. But, all the work of collecting (and validating!) the files, figuring out versions, and constructing the invocation of fpm - that's what we'll do.

For our first feature, let's build an empty package. And that's the first stab at a user story.
I want to create an empty package.
In talking with our client (yes, I talk to myself a lot), the first question I tend to ask is "what happens if I don't receive X?" and I don't treat myself in client-mode any differently. So, what happens if we get something like package {}. That seems a bit off. Package filenames are usually constructed as:
<name>-<version>-<other stuff>.<extension>
Name and version seem to be key, so let's amend the story to require name and version.
I want to create an empty package by specifying the name and version. If either is missing, stop and inform the user.
Which immediately begs the question of "How do we stop and inform the user?" Which then leads to the question of how we're even running the DSL. The easiest thing to do is run a script, so let's do that. If we write things properly, then we can easily change the UI from a command-line script to something fancier.
I want to invoke a script, passing in the name of my DSL file. This should create an empty package by specifying the name and version. If either is missing, print an error message and stop. Otherwise, a package should be created in the directory I am in.
Hmm. "a package should be created" - what kind of package? RPM? DEB? Something else?
I want to run a script, passing in the name of my DSL file. This should create an empty package by specifying the name, version, and package format. If any of them are missing, print an error message and stop. Otherwise, an empty package of the requested format should be created in the directory I am in.

First test

The first thing we need to do is write a failing test. In TDD, this is known as Red-Green-Refactor (all three linked posts are good reading). We want to write the smallest test that can fail, then the smallest code that can pass, then take the opportunity to clean anything up (if necessary). A couple sidebars are important here.

Failing tests

Tests are worthless if they only pass. Tests exist to fail. If they don't fail, then they cannot warn you when something isn't working. As a result, the first thing we want to test is the test itself, otherwise we cannot trust it. When you write a test, it's really important to to see the test fail first.

Also, in this process, each test is the next step in the journey of creating the product. If we've followed TDD, then every feature and each line of code is already tested. We're wanting to test something that hasn't been written yet - it's the next step. We're describing what should happen once we've taken the step. If that doesn't fail, then we have no confidence that we've properly described where we're planning to go.

If you do not see the test fail, then several things are going wrong, probably a combination of:

  • Your test is worthless because it won't fail when it should in the future.
  • You don't understand the system well enough to push it beyond its edges.
  • You haven't constructed your test infrastructure well enough to exercise the problem at hand.
  • You haven't described the problem at hand properly.

Immediate Refactoring

Refactoring, as a whole, becomes much simpler with a robust and comprehensive test suite. Refactoring immediately, though, is less obviously beneficial. It's great to be able to have an opportunity to rethink your implementation right after you have it working. But, the biggest gain in my experience is that by rethinking your implementation, you end up thinking of more edge cases. Each of these becomes another test. By continuously refactoring, you keep driving the development process forward.

The test

The first test we want to write is the simplest thing that we could pass in. That would be package {}.
describe Packager::DSL do
    it "fails on an empty package" do
        expect {
            Packager::DSL.parse_dsl("package {}")
        }.to raise("Every package must have a name")
    end
end
When we run this with rake spec, it fails with a compilation error because the Packager::DSL class doesn't exist. Which makes sense - we're just now taking the first step into Packager-dsl-istan. The test tells us what the first steps should be (and in what order):
  1. Create the class
  2. Subclass it from DSL::Maker (which provides parse_dsl)
  3. Create an entrypoint called "package"
  4. Add a validation that always fails with "Every package must have a name"
Yes - always fails. We don't have a test for the success path yet, so the simplest code that could possibly work (while still driving us forward) is for the validation to always raise an error. We'll fix that as soon as we know how to make it succeed.

To make this work, we need to create a lib/packager/dsl.rb file with
require 'dsl/maker'

class Packager
    class DSL < DSL::Maker
        add_entrypoint('package') do
        end
        add_validation('package') do
            return "Every package must have a name"
        end
    end
end
rake spec still fails stating it cannot find Packager::DSL. Huh?! Ahh ... we forgot to load it in our spec_helper. We can either add a require statement in spec/spec_helper.rb or we can add it in lib/packager.rb. Either one is fine - you can always move it later if you find you need to.

Now, rake spec gives a different error - it cannot find DSL::Maker. We're not creating that - we're going to use it from RubyGems. So, let's add it to our gemspec file. (Remember - our Gemfile is delegating to our gemspec.) We want to make sure we're using at least 0.1.0 (the latest version as of this writing).
s.add_dependency 'dsl_maker', '~> 0.1', '>= 0.1.0'
After a quick bundle install, rake spec now runs cleanly. We also want to delete the other spec file we created when we added our scaffolding. So, git add . && git rm spec/first_spec.rb. We've removed a spec, so let's make sure we still have 100% cover with rake spec. Once confirmed, git commit -m "Create first spec with a real DSL".

Writing for failure first

In the same way that we want to write the test first and see it fail, we want to write for failure (or sad-day) situations first. Writing for success (or happy-day) is actually pretty easy - it's the way most people think of the world. It's the way our user story was written and what most people are going to be looking for when they evaluate your work. But, if you think back to the software you've used, the best software was the one that caught and managed the errors the best. There is nothing more frustrating that a program that blithely lets you get into a huge mess that it should've (at least) warned you about.

So, the best way to write software is to try and figure out all the different ways a person can screw up and plug those holes. You'll miss some - the universe is always making a better idiot. But, you'll catch most of them, and that's what matters.

Adding the attributes

Let's add a success. Let's allow a DSL that has a package with just a name succeed. We're going to have to amend this success when we add version and type, but the neat thing about code is that it's so moldy. (Err ... changeable.)

What does it mean for a DSL to succeed? The immediate straight-line to solve the user story would be to invoke FPM directly. While tempting, it's too big of a step to take. Right now, we're focused on parsing the DSL. So, let's make sure we're doing that right before worrying about integration with another library. For now, let's just create a data structure that represents what we put into the DSL. Ruby provides a very neat thing called a Struct which allows us to define a limited data structure without too much effort. Let's add another specification into spec/empty_package_spec.rb
    it "succeeds with a name" do
        items = Packager::DSL.execute_dsl {
            package {
                name "foo"
            }
        }

        expect(items[0]).to be_instance_of(Packager::DSL::Package)
        expect(items[0].name).to eq("foo")
    end
(I prefer using execute_dsl() because it allows Ruby to make sure my package definitions compile. You can use parse_dsl() instead.) Make sure it fails, then change lib/packager/dsl.rb to be
class Packager
    class DSL < DSL::Maker
        Package = Struct.new(:name)

        add_entrypoint('package', {
            :name => String,
        }) do
            Package.new(name)
        end
        add_validation('package') do |item|
            return "Every package must have a name" unless item.name
        end
    end
end
That should pass both the new test and the old test. Commit everything with a good message. Adding the version and type attributes should be a similar sequence of activities. Make sure to exercise the discipline of adding each one separately, ensuring that you have a failing test, then minimal code, then passing tests.

You may have to amend tests to make them pass with the new code. That's expected as the requirements change. Just make sure that the tests you have to amend are ones you either expected to amend or which make sense to amend. Don't just blindly "make the tests pass". That makes the test suite worthless.

Summary

We haven't finished the user story, but we've gotten to the point where we can parse a DSL with validations and have a predictable data structure at the end of it. Looking down the road, we will take that data structure and invoke fpm with it. Then, we'll see some really neat benefits to having the separation between parsing and execution.

Tuesday, August 11, 2015

Creating your own DSL - Parsing (with Ruby)

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
Ruby is a full-featured generic programming language with all the standard bells and whistles. So, it may seem odd that I'm suggesting it is also useful for parsing DSLs. But, Ruby has a few interesting features (both in syntax and semantics) which make it ideal for parsing DSLs:
  • All functions are method calls on an implicit object
  • Code blocks as the last parameter of a method call
  • Greedy implicit binding rules
In short, Ruby makes it possible for a DSL author to create a class that, with a little help, will treat all key-value pairs as method invocations on an object. This includes nested blocks (though we need to create a new class for each level of descent). Because these nested blocks are values of some key, that key is the method call that receives the code block. And, because Ruby doesn't require a lot of symbols like parentheses and the like, the DSL ends up looking very clean and non-program-ish. (All the DSL examples in this series are parsable in Ruby.)

Most importantly, all of the features of full Ruby language (such as branching and looping) are available for free. The DSL author doesn't have to do anything special - it's just there. The DSL user simply has to be pointed at the standard Ruby documentation (and all the internet resources) to know how to solve any problem. If the DSL author desires, they can even elide over the use of Ruby and document Ruby's syntax as part of their language. There's no requirement to trumpet the use of Ruby in your DSL.

Ruby's only contribution to DSL parsing is a function called instance_exec(). It is the magic sauce that makes a code block act as if all the functions are method calls on the object of our choosing (vs whatever object is appropriately in scope). It's also extremely low-level, which is an obstacle to DSL authors.

The docile gem (aka, a Ruby package) provides a very nice way of mapping classes (and their objects) to different levels while enhancing the error-handling. It's definitely more usable than instance_exec() on its own. But, you still have to know far more about Ruby classes and objects that a DSL author should. It also doesn't provide any facilities for validation or production, leaving those as exercises for the reader.

The dsl_maker gem wraps docile with a more explicit way of declaring the DSL structure and validation. In essence, it provides a quasi-DSL for declaring DSL parsing and validation rules. It also allows the DSL author to work with concepts that map more closely to DSL creation, such as types and nested structures without having to maintain the mapping between DSL nesting and Ruby classes.

Over the next few posts, I'm going to walk through the creation of a non-trivial DSL designed to describe how a package should be created. I will discuss creating and maintaining the parser, validator, and executor. I will also discuss distribution, testing, and all the other aspects of a good software project and how those things are handled within a DSL. You can follow along at the packager GitHub repository.

prev next

Monday, August 10, 2015

Creating your own DSL - Parsing

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
So far, we've talked about the whys and wherefores of DSLs. If you've made it this far, you probably agree that DSLs are a good idea. You have probably identified a spot in your processes where a DSL would make life so much easier. So, let's get started.

At its heart, creating a programming language (or DSL) is dealing with these three activities:
  1. Parsing the program file into a data structure
    • If there are errors in syntax, inform the user here.
  2. Validating the data structure
    • If there are errors in semantics, inform the user here.
  3. Executing the requested activities
    • If there are errors in what was attempted, inform the user here.
Before we can design our language, we need to pick what our parsing process will be. The parsing step is what deals with user interface. The parser we choose will strongly shape what kind of language we can create for our users. If we pick a very simplistic parser, that means we are constrained to a possibly unusable DSL. It doesn't matter what kind of wonderful things our DSL can do if no-one is able to work with the language itself. (For example, compare COBOL and BASIC with Java, Python, and Ruby.) On the other hand, if we pick a very complex parser, we may never end up creating the language at all.

There are hundreds of tools for creating programming languages. (Make no mistake - you're doing just that.) The problem with most of them (and the reason most DSLs are never created) is that they're far too complicated (such as writing a parser and lexer to generate an AST). Most developers simply will not be able to reframe their simple DSL in terms of tokens and similar parsing terms.

The good news is that most DSLs do not need the full treatment of a parser+lexer. Most DSLs are better ways of describing nested data structures. So, maybe we should try something like JSON or YAML. And some tools (such as Ansible and Salt) do just that. They use a YAML parser to handle step 1. This definitely solves the problem of parsing - just let someone else do it. :)

YAML and JSON, though, while very easy for the DSL author to work with, is a really poor interface for the DSL user. Data structures become extremely brittle the moment you use them for anything non-trivial. This matters because the user is the group that will be working within the DSL 100x more than the author will be working within the DSL definition. We should optimize (wherever possible) for the most usage.

The first major issue is no variables. Every programming language (and DSLs are no exception) works in problemspaces that want to reuse the same values. When using just YAML, the user ends up having to re-specify the same value over and over. When (not if!) that value changes, the user will invariably forget one place to change the value.

An enterprising DSL author might decide to create a section in their document called "variables" (or somesuch) and allow the user to specify a hashtable of key-value pairs for use elsewhere in the DSL. This would work, but it becomes very cumbersome to work with. Now, the user has to specify "This is a variable lookup" which means the author has to provide special tokens (or sigils) for doing that. Now, the user cannot just "use YAML". It's YAML+. And every DSL author will have their own unique '+'. No-one wants to invest in non-transferrable skills.

The second, larger issue is no control mechanisms. Programming languages provide three key control mechanisms:
  1. Branching (if-then-else, switch/case)
  2. Looping (for loops, while loops)
  3. Abstraction (subroutines)
Without these, data structures balloon in size because the user ends up having to repeat themselves. An example can illustrate best:

server {
      name "www1.place.com"
      ip "192.168.50.1"
      ...
}
server {
      name "www2.place.com"
      ip "192.168.50.2"
      ...
}
server {
      name "www3.place.com"
      ip "192.168.50.3"
      ...
}
(1 .. 3).each do |id|
      server {
            name "www#{id}.place.com"
            ip "192.168.50.#{id}"
            ...
      }
end

Now, imagine the server definition runs to 50 lines, all of them identical except for the two lines in the example. And instead of 3 webservers, your product is in heavy use and has 10 servers. Or 30. Which would you prefer to maintain, as a user? Your users will feel the same way.

The maintainers of Salt and Ansible immediately recognized this problem and provide an interesting solution. Their DSL files aren't actually YAML. They are Jinja2 files that render to YAML. Jinja2 is a templating language that provides variables and the control structures missing from YAML. Obviously, some products (Salt and Ansible) feel this is a workable solution. Their users must feel the same way, or they wouldn't use the product.

I don't agree. This forces users to learn two languages - YAML and Jinja2. That's twice the barrier to entry and twice the opportunity for the user to make an error. DSLs are meant to reduce the barriers to entry and reduce the potential for error. But, we still want someone else to handle all the parsing (because parsing is hard and error-prone). We need an easy way to set key-value pairs (because that's most of what we want), but we still want variables and all the valuable control structures. While 90% of all the usage of the type of DSL we're writing could be satisfied by basic YAML, we want the escape hatch of a full-on programming language for when we need it. There has to be a better tool

That better tool is Ruby.
prev

Wednesday, August 5, 2015

What makes a good DSL?

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
If someone wanted to create a human language which was really good at managing reindeer herds, it would make more sense to just use Sami. It has a lot of busat (expressive terseness) in that domain. Likewise, it makes the most sense to just use Inuit terms when building a human language focused on dealing with matters of snow and ice. These domain-specific languages (DSLs) focus their primary areas of terseness on specific areas (reindeer, snow/ice, etc), rendering them less suitable for other areas (tropical seafaring, web application devleopment, etc).

The same goes for programming languages. If you work on modern web applications, you already use several DSLs, such as SQL and CSS (for set manipulation and defining visitors on trees, respectively). These DSLs have two qualities that elevate them to the top of the DSL game. They are:

  1. Intensely Focused (Do only one thing, but do it well)
  2. Expressively Terse (Just the facts)

Intensely Focused

SQL is the standard language for interacting with data in a relational database, such as Oracle or MySQL. Relational databases store their data in sets. At its heart, SQL is a set manipulation DSL. It has roughly 40-60 keywords (depending on the dialect and how you count). But, every single one of those keywords has a single purpose focusing on one (and only one) of the basic set operations. Where SQL has keywords that do non-set things (such as collations or engine-specific hints), that's where people complain the most about how complicated SQL is. While dealing with set theory can be difficult to master, no-one complains about how SQL implements it.

CSS is even more focused. There are hundreds of properties, each with their own specific set of acceptable values (potentially depending on what type of node is affected), and that's what most people see in CSS. But, CSS isn't a DSL for setting values for properties - it's a DSL for creating actions to take when walking trees. In short, CSS is a massive Visitor pattern definer.

But, CSS doesn't allow you to take just any action - you are only allowed to set properties on nodes. It isn't a generic visitor pattern definer - it is focused on one type of visitor action. There may be hundreds of properties, but they all follow the exact same pattern of name: value [modifier];. This allows it to be more generic when it comes to the matching rules for which nodes in the tree are affected, which is the true power of CSS.

Expressively Terse

Terseness is a quality of using as few words as possible to say what you want to say. It's expressive only if every word we use is exactly the right word for the job. Or, if the concept expressed by the word is exactly the right concept. You have to truly understand what your DSL is focused on doing.

Both SQL and CSS are extreme terse. There is one and only one keyword or operand for each concept. Every concept is mapped clearly and cleanly to the problemspace at hand. If you removed any keyword or operand, you would cripple the DSL's ability to solve problems.

CSS, in specific, is extremely terse. Syntactically, the only interesting things happen in the selectors. But, even with that complexity, there is only way to specify a specific path to a node. (Depending on your structure, you may be able to specify multiple ways to get to a node, but there's only one way to specify each way.)

prevnext

Monday, August 3, 2015

Why create your own DSL?

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
DSLs are great. The problem, though, is that there are many domains that have been too small for someone to write a DSL for. SQL and CSS exist because millions of develpers need to access relational data and style web pages. There are dozens of domains that could use a DSL, from packaging to orchestration to HTTP route management to cross-product configuration management. Let alone all the domains that are specific to your organization, from the corporation down to the team.

Just because there isn't a DSL doesn't mean you aren't programming in that domain. For example, you may have to manage a heterogenous environment of Apache and Nginx web servers for various legacy reasons. They may have server-specific configurations, but they have to share a set of configuration. Someone has to ensure that a change to the Nginx configurations is both made to the Apache configurations and that the change is translated properly and that the change occurs in the same deployment.

No-one will ever create a publicly-available generic DSL for managing web server configurations. There just isn't a large enough population who have to maintain a heterogenous web server environment. And even if there was such a DSL, it wouldn't be quite as useful for your needs. It would be generic - everything available as the lowest common denonimator. Which reduces the busat of the DSL for your purposes.

Busat ("expressive terseness") is not an objective measure equivalent for all people in all places. Expressiveness is directly related to the receiver's ability to understand what was communicated. If you can limit your listeners to only those who agree on specific terms, then you can be terser while remaining as expressive. If you have a jargon, then a DSL can take advantage of that.

Compare the following examples:

server {
    hostname "host1.domain.com"
    ssl {
        key_file "/etc/ssl/key_file"
        ca_file "/etc/ssl/ca_file"
        pem_file "/etc/ssl/pem_file"
    }
    ....
}
purpose :internal_web {
    ssl_root_directory "/etc/ssl"
    domain "domain.com"
    # Other internal_web things
    ....
}

server "host1" {
    purpose :internal_web
    ....
}

If your audience can all agree on what "internal_web" means, then that's strictly better. It describes exactly what you're doing, why you're doing it, and changes become much easier to vet for correctness. But, unless you're willing to write your own DSL, you would never be able to collapse the boilerplate in the configuration.

It's highly unlikely you specifically have to maintain both Apache and Nginx configurations to do the same thing. But, it's guaranteed that your organization or team has processes unique to it. Some special snowflake way of looking at something in the development process. Something that's just really annoying to manage in the standard language. Some good places to look are:
  • Packaging and orchestration (or most other devops/operations activities)
  • Configuration file generation
    • web servers
    • monitoring
    • datastores
  • Configuration value management across environments
  • Anything that has to interact with multiple different systems
  • Anything repetitive (such as CSS, for which there is Less)
prevnext

Wednesday, July 29, 2015

Why use a DSL?

  1. Why use a DSL?
  2. Why create your own DSL?
  3. What makes a good DSL?
  4. Creating your own DSL - Parsing
  5. Creating your own DSL - Parsing (with Ruby)
  6. Creating the Packager DSL - Initial steps
  7. Creating the Packager DSL - First feature
Languages exist to communicate ideas. Most of us are familiar with generic human languages like English, Swahili, Japanese - even created languages like Esperanto and Lojban. These are able to express any idea humans can possibly come up with in a way other humans can understand. In programming terms, all human languages are Turing-complete.

Sometimes, though, some ideas are easier to express in certain languages vs. others. Supposedly, Eskimos have 50+ words for snow and the Sami have nearly 1000 words for reindeer. Given how important those topics are in those cultures, that would a lot of sense. People working together in those domains would be able to communicate more quickly because the same effort communicates more concepts. For example, "busat" (in Sami) translates to "male reindeer with a single, very large testicle". I have no idea how often this occurs, but that's probably a unique identifier in most reindeer herds.

We see this as well in programming languages. Programmers were writing object-oriented programs in ANSI C for years before Bjarne Stroustrup created C++. It's easier to write OO programs in C++ than in C. In C, you have to be extremely disciplined to make sure that you're adhering to public vs. private interfaces, that you invoke the "methods" properly (passing the invocant as the first parameter, passing the correct invocant to the right method, etc), and lots of other bookkeeping. It's just exhausting to keep track of all of that, especially across a large codebase. In C++, the language not only reduces the bookkeeping you have to do, but it also reduces the number of characters you have to read (and type, but read is more important).

Like Sami, there are programming languages that trade expressibility in one domain for another. I suspect most of the words for computing and the internet in Sami are borrowed from English (as they are in many other languages). All the usable words are already taken for other purposes. "Scripting" languages, like Ruby and Python and Javascript, make a similar set of tradeoffs. They give up the ability to write programs that execute extremely quickly (like programs written in C would do) in order to make it easy for humans to write the programs. Programs written in these languages are often much shorter (10-100x shorter) than the equivalent in C or Java. They are much more expressive when it comes to specific domains of computing. No-one would write an operating system in Perl, but these languages excel at manipulating text and talking to databases at faster-than-human-reaction-time speeds.

Expressive terseness (aka, "busat") is really important in programming because the hardest part of doing development is working within existing code. Depending on whose percentages you want to use, the maintenance phase of a project is anywhere from 60%-90% of the time and cost of that project. Maintenance, first and foremost, is an effort in reading comprehension. You can't fix a bug unless you understand the code where the bug lives, what code is connected to it, and how the various execution paths wend through that code (and the code around it). This is a lot easier to do when you're dealing with 50 lines of code than 500 (assuming equal cyclomatic complexities). The business-level concepts are easier to see and there are fewer places for bugs to hide.

SQL and CSS are good examples of DSLs that take complex domains (set manipulation and style metadata, respectively) and allow the developer to express exactly and minimally what they are trying to accomplish. Querying sets - writing joins, projections, and all the other logic that SQL provides - is extremely complicated. Doing this in any standard programming language can run to hundreds and thousands of lines with lots of cyclomatic complexity. Plenty of places for bugs to live. A DSL makes it easier to express the desire to do these three set conjunctions (using these indices for lookup), then project these 5 data points (with these manipulations), ordered in this way.

DSLs also make it much easier for people working in different languages (or even business domains) to collaborate and learn from each other within the domain. There are hundreds of forums, discussion boards, and blogs on SQL or CSS tips, tricks, and improvements. These tips work regardless of what programming language you use.

Sunday, July 5, 2015

What is production?

In What is an application?, I propose a definition for "application" as "A set of capabilities provided to a user to enable them to satisfy their desires." But, there are many other terms that are undefined. Over the next several posts, I'll define each one. The most important (after application) is "production", so I'll start there.

Let's do this with a thought experiment. Pretend that your application only has a production, however you define it. This is where your users come and where you make your money (assuming you do). There is only the one instance and, because there's only one, no-one needs a name for it. It's just "the application" - there's nothing to confuse it with. Anytime you need to make a change, you go make it in "the application" and your users immediately see it. Sounds good, right?

Of course, no-one works like this, and for good reason. Some changes are small enough that they can be made directly where your users are interacting, but the vast majority of them are not. Most changes require several hours (if not days) of work, often collaborating between multiple people and are built in stages you don't want your users to see.

So, we distinguish between where users go for the "live" application and where developers work to make changes. Stand up a clone of production, except it doesn't have live users going to it, and call it "development". Developers can make changes to it knowing they are safe from affecting the business. Production remains the place where users satisfy their desires.

So far, it seems pretty clear what production vs. development is. Production is where users go (but not developers) and development is where developers go (but not users). And, from a developer's perspective, that would be enough.

There are more stakeholders in an application than just users and developers. At minimum, you have the business owners. They define what the application is meant to do - what desires the user is attempting to satisfy and what capabilities the user will have to do so. If communication was perfect, then the business owners could tell the developers "Do this" and be assured that the necessary changes would happen exactly as they intended. This also assumes developers will never make mistakes. In real life, neither statement is remotely true. Review of work requested is a fact of life. Business owners need to assure and control the quality of what they pay for. Hence, the name "QA" (or, sometimes, "QC", for quality control).

Some organizations choose to have such review occur within the development instance. This makes a lot of sense for smaller, newer, and/or slower projects who either cannot or do not need the ongoing cost of a separate instance. In most other projects, the shortcomings of this plan become obvious very quickly. Ongoing development makes it difficult to determine if a failure is because of the work under review or the unstable nature of the development instance. Business owners are uncertain what would happen to the production instance if they approve the work done for a request. Will the change for that request work properly when users try to exercise the new capability? Were the failures in that change or in something else?

We have development, QA/QC, and production. It's pretty obvious what "production" is - it's where the users are and it has to be stable with a managed and defined process for change.

So, where does a demonstration/demo or training environment fit? It's not the production, but it needs to be stable for a smaller set of users and a limited window of time. This is where a lot of organizations stumble, attempting to tie the demo or training instance to either the existing production (slow-changing) or QA (quick-changing) environments. Except, the business needs usually require a middle-ground between the two.

Which leads to the better definition of "production". Or, rather, splitting out what constitutes "production" into different knobs we can apply to other environments.

The first knob is change management. Different environments will change within different change control regimens. This knob is based on who decides when the environment changes. Development changes whenever a developer edits a file. QA changes whenever a developer finishes some work. Production, however, changes whenever the business feels a feature is both ready for use and appropriate for release. A demo or training environment will be similarly managed by the business, not the development teams.

The second knob is the stringency of review. We've already seen how changes to production will usually go through a QA environment first before a user will see it in production. Demo and training environments also need similar review because users will be in these environments.

So, what's the difference between production, training, and demo? From a developer's perspective, often nothing. They're all strongly controlled environments with reviewed changes pushed when the business wants them.

All of this discussion leads to this:
  1. Production is where users live.
  2. Production is where change control is at its maximum (whatever that is).
  3. Production is where data robustness is at its maximum. (To be discussed in a later post.)
  4. Production is where availability is at its maximum. (To be discussed in a later post.)
  5. Multiple environments can share aspects of Production and should be treated as such in those axes.