Welcome to Rule of Modularity, Part I. Before I get on my soapbox I'd like to quickly show the contrasting definitions of Monolith vs Modular. Keeping these simple definitions in mind when writing your playbooks/modules/recipes will certainly be a start in helping you address the common problems of non-modular IaC.
Monolith:
1: a single great stone often in the form of an obelisk or column
2: a massive structure
Modular:
1: of, relating to, or based on a module or a modulus
2: constructed with standardized units or dimensions for flexibility and variety in use
The Rule of Modularity may be the single most important rule I will discuss in this multi-part series. People are so gung-ho these days for micro-services, twelve-factor compliant applications, configuration management, containerization and cloud technologies. Don't get me wrong I love these technologies. However, when it comes to the automation of these technologies they fail at adhering to modular designs.
You may be saying, "Shellfu, wtf are you talking about? I have a playbook/module/recipe for X, I can apply it to 100 machines if I want".
While this may be true, I bet it's breaking the rule of modularity.
Doug McIlroy stated: DOTADIW, or "Do One Thing and Do It Well."
This is a cornerstone of implementing modular designs.
Often times when writing a playbook/module/recipe, we may have a need to for example:
- Install Java
- Add a User
- Add a Repository
- Install some Pre-requisites
- Tune some kernel parameters
- Deploy the application
This very well may get the job done, however, this playbook/module/recipe is not following, Do One Thing and Do It Well".
What happens when you have to write another playbook/module/recipe for another application. Well, you'd probably fork your already working code, and change some parameters that are no doubt hard coded for the previous task and keep working.
You've just duplicated your effort, adding complexity to your code base. If this is the norm for your organization, then you have a recipe for disaster. Organic growth of your code base will lead to massive duplication, and errors and bugs will start to creep in making debugging harder and painful.
What Should You Do?
Modularity should be thought of as the development of component modules that can be bolted together in a variety of ways by writing many small playbooks/modules/recipes, that are unopinionated sane defaults for each piece of the larger whole.
- One for Java Management
- One for User Management
- One for Repository Management
- One for Kernel Parameters
- One for Application Deploys

This allows you to reuse your work in your future projects. Piecing together new playbooks/modules/recipes from your now growing box of modular blocks instead of massive monolithic pieces of code. We will dive deeper into data separation later in this series along with the whys and hows. However, you should make your playbooks/modules/recipes generic. Making use of templates, making use of arbitrary hashes that can be passed to add consumer data you may not have thought to capture. ALL defaults you make for your small modular block should be able to be overridden by the consumer with ZERO modification to your code base. Again, we will go into these concepts and how to implement them. Just keep them in mind for now.
By writing pieces of reusable code, you also gain the added benefit of being able to debug those smaller pieces if something goes wrong a lot easier. You also gain the ability to rip and replace, mix and match pieces of modular code without the fear. This cannot be said for a single monolithic piece of code that tries to be the one ring to rule them all.
Next, we will be going into examples of this, and how you may start to refactor your existing code to be more modular. We won't get into data separation yet, as that is a topic among itself.