We wrote a Terraform module for provisioning a service. It started at forty lines. Two years later it had ninety-one input variables, six nested conditional blocks, and a count = var.enabled ? 1 : 0 on nearly every resource. Onboarding a new service to it took longer than writing the resources by hand, which is exactly what three teams quietly started doing.
The failure mode is easy to walk into. Someone needs a slightly different setup, so you add a variable instead of forking. That works. Then someone else needs another variation, so you add another. Every request is individually reasonable, and the sum of them is a module that models every past need and is comprehensible to nobody. Ours had variables that only made sense in combination with two other variables, and no way to express that except a comment somebody deleted.
The real cost showed up when we tried to change the module. Ninety-one inputs means an enormous surface of possible configurations, and the ones actually in use were a tiny, unknown subset. Any change risked breaking a caller we didn't know existed with a flag combination we'd never tested. So we stopped changing it, and it froze into something everyone routed around.
We rewrote it with a rule I've kept since: a module should encode one opinionated way to do a thing, not every possible way. We took the three configurations that were actually in production, made them three small modules with maybe eight inputs each, and accepted some duplication between them. Duplication you can read beats abstraction you can't.
The other rule was about conditionals. If a resource might or might not exist depending on a flag, that's usually two different modules wearing one costume. count on a toggle looks tidy in the code and produces awful plans, index-shifting surprises on removal, and states that differ structurally between environments.
Abstraction in infrastructure has the same tradeoff as anywhere else, but the blast radius is higher and the feedback is slower. Build modules for the cases you actually have, keep the inputs few enough to hold in your head, and let a fork be a legitimate answer. A module nobody can safely change isn't reuse. It's a fossil with a version tag.
– Sergey Shinder
Top comments (0)