Software is often more of an art form than it is a science. This is one of the reasons software tends to vary substantially between tools and developers. By following guidelines and design patterns though it can help keep the code consistent and clean. This allows the code to be understandable between engineers and allow them to move between tools efficiently. In this post, I’ll talk about what some of the best practices and software design patterns followed in the BoltOps tools.
Convention Over Configuration
DHH of Rails popularized the convention over configuration concept. He described Rails as a framework where you do not painfully have to set up 100 configurations variables just to get started. Things worked out of the box because of a starting set of conventions. This pattern is one of the most significant benefits of using Rails. The BoltOps tools follow the conventions over configuration pattern, and things work out of the box quickly.
You will see that the conventions are not just baked into the tools but are found throughout the entire system; all the way to EC2 instances tags, ECS service names, Security Group are names, etc. This simplifies the tooling interfaces and makes life dramatically easier because the commands become concise and memorable. At the same time, the naming conventions guide you to keep your AWS account organized. Lastly, the tools provide the ability to override the conventions when necessary. This results in you getting the best of both worlds: the ease of use via conventions and the ability to customized if required.
Let’s walk through a few examples of the convention over configuration pattern found in the tools.
Lono is a tool that generates CloudFormation templates and parameters files. It also comes with lifecycle commands to launch the CloudFormation stack with these generated files. By convention, the name of the template and parameter files are the same as the name of the stack you launch. An example explains this convention best. Let’s launch a stack named “hi-web-stag” and explicitly specify the template and parameter names to use:
lono cfn create hi-web-stag --template hi-web-stag --param hi-web-stag
By convention, since the template and param file is the same as the stack name, the command can be shortened down to:
lono cfn create hi-web-stag
Both commands are equivalent and work just as well. The shorter command is easier on the eyes and saves your precious finger energy. If you need to override the convention, you can easily with the
lono cfn create hi-web-stag --template my-template --param my-param
Lono conventions are covered in more detail on the Conventions page of its official documentation
Onto the next example: ufo. Ufo is a tool that makes it simple and easy to build and deploy Docker images to AWS ECS. It also follows a naming convention for its ECS service and task definition names. By convention when you run:
ufo ship hi-web-prod
It will register an ECS task definition with the name hi-web-prod and then deploy that newly registered task definition to an ECS service with the same name: hi-web-prod.
This common sense naming scheme and helps you keep things well organized. And just like in the lono example, you can easily override the naming convention if required. Here’s an example of how to override the task definition:
ufo ship hi-web-prod --task my-task
Cloud Specific But No Tool Lock-In
I find it fascinating that many people promote cloud agnostic tools and center their debate around agnosticism as a core reason to as to why the tool is the better fit. What seems to be missing in the debate is that fact that while you won’t be locked into a cloud provider (in theory), you’ll be locked into the tool itself. This is not a much better position to be in either.
While the advantage touted about agnostic tools is multi-cloud support, the reality is that each cloud works differently. An agnostic interface typically results in the lowest common denominator interface being exposed at the common level. Sadly, this sometimes results in getting the worse of each cloud world and you are limited by the tool.
The BoltOps tools are designed to be a lightweight layer on top of the powerful plumbing of AWS. This is done for several reasons.
Easier to Understand
It is easier to understand the underlying resources that the tool is managing. Both Lono and ufo expose the raw template as part of the usage. This puts you a lot closer to the ground level and helps you understand the nuts and bolts of what is going on. The understanding helps you change and customize things down the road.
Easier to Update
It is easier to update and maintain the tool when AWS updates their offering. There is simply less surface area to maintain. For example, the tools do not provide a GUI. Instead, the GUI is handled by the AWS Console. Why rebuild the AWS Console when you can simply tag your resources properly? Let AWS do the hard work. When AWS updates the Console with latest and greatest updates, there is pretty much nothing to change on your side.
Easier to Replace
It is easier to replace the tool for something that might be a better fit. Whether that’s something you’d rather build in-house or a new tool that BoltOps builds that is a better fit than the original one, it is easy to do. Since the tool itself is lightweight, it is not much work to entirely replace.
More Time to Work on Other Things
It takes less work to build the actual tool so we can spend time elsewhere. A 10x engineer does not work 10x more than a 1x engineer. It’s about working smarter with the limited time that we have. Since the tools try to be as lightweight as possible, that means we end up building less. As the old Unix joke goes “less is more”. This is great because then we have more time to work on things that really matter.
Ultimately, the decision to be lightweight leads to these tools having no “tool lock-in.” You should use the tool if it’s a great fit for what you’re trying to achieve. If not, it is not an overwhelming amount of work to switch it out for another tool that works better for you!
Batteries included but removable
This “Batteries included but removable” expression is from the Docker project. This means that the tools have already made some default choices for you to make it easier to use. However, you can override the default choices if needed.
An example of this in the BoltOps tools can be found in jack. Jack is a wrapper tool that shells out and calls the awsclieb tool’s
eb command under the hood. When you install jack with the package installer, it will install a vendor copy of the
eb command that can be found at
/opt/bolts/embedded/bin/eb. This spares you from the work of having to install eb command yourself. However, if you already have the
eb command installed then jack auto-detects and prefers that version over the embedded eb version. You can override this behavior and specify the eb path explicitly by setting the JACK_EB_BIN environment variable. Here’s the source code explaining precedence:
# Auto detects the eb binary for use, will use an eb binary based on the following precedence: # # 1. JACK_EB_BIN environmental variable # 2. eb detected using the load path. # For example: /usr/bin/local/eb if /usr/bin/local/ is earliest in the load path. # 3. /opt/bolts/embedded/bin/eb - This comes packaged with the the bolts toolbelt. # https://boltops.com/toolbelt # # If an eb installation is not detected it'll display a message and exit the program.
This makes it easier to get started with the tool.
The software design philosophies with the BoltOps tools mean that they can be entirely and quickly removed and you’ll still be perfectly fine. The tools are optional convenience tools. You do not have to use them. This gives flexibility and power back to you as engineers.
The tools are consciously kept simple. It is often better to build another tool for the specific product rather than try to make the same tool be the “god” tool and one cure-all for anything and everything. This is especially true given how fast AWS innovates and changes. By the time you are done with the tool, AWS has already come out with a new service that your tool does not support.
Often your time is better invested in working on other specific tools than adding features to the same generic tool. This does not mean you should never write tools with reusable software abstractions. Design patterns are incredibly useful. Ultimately, it depends and is about what you are trying to get done.