Needless Double Abstraction

When it comes to writing Terraform modules I find it's an extremely common practice, in most organisations, to produce a module that wraps a single resource. The end result is a module that prevents a (crap) API in front of an existing, much better API.

Let's look at an example: the AWS Security Group.

Here are the arguments that can be past to a aws_security_group (at the time of writing):

  1. description - (Optional, Forces new resource)
  2. egress - (Optional, VPC only)
  3. ingress - (Optional)
  4. name_prefix - (Optional, Forces new resource)
  5. name - (Optional, Forces new resource)
  6. revoke_rules_on_delete - (Optional)
  7. tags - (Optional)
  8. vpc_id - (Optional, Forces new resource)

At a recent engagement I noticed a Terraform module that wrapped the aws_security_group type. It did two things of note:

  1. It defined name_prefix as `${var.name_prefix}-security-group-${var.env}-
  2. It did not have an option for revoke_rules_on_delete

There are three problems with 1:

  1. Don't use security-group in the name for a Security Group - I know it's a Security Group already
  2. You've forced me to use a naming convention I might not want to use (name_prefix and env)
  3. A change to this field results in resources being recreated might can cause issues for some

There is a two issues with 2:

  1. I cannot provide the module with this value
  2. I now have to use the resource directly, breaking away from the module, and going outside of expected practice

All of these issues compound into a single set of issues:

  1. Technical debt
  2. Blockages
  3. Fragile API
  4. Do I upgrade?

Technical Debt

Each module of this nature contains hidden technical debt just waiting to bite consumers of your modules.

Blockages

Of course I'm now blocked whilst I wait for you to make the change to your module's API to include the options that I need.

Fragile API

Your API is fragile. If the underlaying resource changes its API yours is a bad, broken API in an instant.

Do I Upgrade?

And of course if you do make the changes needed to support some request from me, other consumers are now asking themselves, "Do I upgrade?" This creates a task in someone's backlog/sprint that they have to resolve, all because you wrapped a single resource in a module.

When do I write a module?

I have some basic rules that aren't hard line or perfect, but they've served me well:

  1. Are you wrapping at least two or more distinct resource types in the module?
  2. Does you module build something complex, i.e. "My module will create an Route53 record, then request and validate an ACM TLS certificate for that record, create an ALB with a listener and tie the TLS cert to TCP/443"... that's complex.
  3. Are you creating a module for something complex that's repeated at least three times elsewhere in other environments?

Sometimes these rules don't work "out of the box". Fine. Just adjust them, but please...

Stop wrapping individual resources in a module.

Michael Crilly

Michael Crilly

A simple nerd.
Brisbane, Australia