Wednesday, August 26, 2015

Creating the Packager DSL - Retrospective

  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
  8. Creating the Packager DSL - The executor
  9. Creating the Packager DSL - The CLI
  10. Creating the Packager DSL - Integration
  11. Creating the Packager DSL - Retrospective
We've finished the first user-story. To review:
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.
Our user has a script to run and a DSL format to use. While this is definitely not the end of the project by any means, we can look back on what we've done so far and learn a few things. We're also likely to find a list of things we need to work on and refactor as we move along.

It helps to come up with a list of what we've accomplished in our user story. As we get further along in the project, this list will be much smaller. But, the first story is establishing the walking skeleton. So far, we have:

  1. The basics of a Ruby project (gemspec, Rakefile, Bundler, and project layout)
  2. A DSL parser (using DSL::Maker as the basis)
    1. Including verification of what is received
  3. An intermediate data structure representing the packaging request (using Ruby Structs)
  4. An Executor (that calls out to FPM to create the package)
  5. A CLI handler (using Thor as the basis)
    1. Including verification of what is received
  6. Unit-test specifications for each part (DSL, Executor, and CLI)
    1. Unit-tests by themselves provide 100% code coverage
  7. Integration-test specifications to make sure the whole functions properly
  8. A development process with user stories, TDD, and continuous integration
  9. A release process with Rubygems
That's quite a lot. We should be proud of ourselves. But, there's always improvements to be made.

The following improvements aren't new features. Yes, an empty package without dependencies is almost completely worthless. Those improvements will come through user stories. These improvements are ones we've seen in how we've built the guts of the project. Problems we've noticed along the way that, left unresolved, will become technical debt. We need to list them out now so that, as we work on user stories, we can do our work with an eye to minimizing these issues. In no particular order:
  • No error handling in the call to FPM
    • Calling an external command can be fraught with all sorts of problems.
  • Version strings aren't validated
  • There's no whole-package validator between the DSL and the Executor
  • We probably need a Command structure to make the Executor easier to work with
  • We probably want to create some shared contexts in our specs to reduce boilerplate
    • This should be done as we add more specs
It's important to be continually improving the codebase as you complete each new user story. Development is the act of changing something. Good developers make sure that the environment they work in is as nimble as possible. Development becomes hard when all the pieces aren't working together.

Over the next few iterations, we'll see how this improved codebase works in our favor when we add the next user stories (validating the version string, dependencies, and files).