Folding Copy-Pasted Code Into Modules
In one line
If you have copied the same code three times, that is a signal it should become a module. However, making a module too early is worse than copy and paste.
Why this was needed
With three environments, you get three directories.
dev/main.tf (300줄)
stage/main.tf (300줄, dev 와 거의 같음)
prod/main.tf (300줄, 인스턴스 크기만 다름)
To fix one security group rule, you have to fix three places, and you are sure to miss one. And that missed one is usually prod.
What a module does
A module is a function that takes inputs and creates a bundle of resources.
module "web" {
source = "../modules/web-service"
name = "api"
instance_size = "large" # 환경마다 다른 것만 인자로
replicas = 6
}
The three environments use the same module and pass only the differing values as arguments. When you fix a rule, you fix only one place, the module.
The conditions of a good module
1. The boundary is natural "Everything needed for one web service" is a good boundary. "All of our company's infrastructure" is a bad boundary. A module is a unit of reuse, not a unit of packaging.
2. The inputs are few A module with 30 arguments is not a module but a configuration file. Growing arguments are a signal that the boundary is wrong.
3. The outputs are clear Expose the values other modules will reference (IDs, endpoints) as outputs. If the outside has to know the internal implementation, encapsulation has failed.
4. It has a version When you fix a module, everything that uses it is affected. Attach a version so that each environment can use the module from a different point in time — try the new version first in dev and move prod up later.
Premature abstraction
You made a module out of code used in three places, but soon a fourth use appears and "just this case is different" begins. Arguments grow one by one, conditionals creep in, and in the end it becomes a module that nobody can understand.
The rule of thumb is this.
- Repeated twice: leave it alone. You do not know the pattern yet.
- Repeated three times: if the commonalities are clear, make it a module.
- Conditions multiply: split the module, or have that use case stop using the module.
Duplication is visible and fixable, but a wrong abstraction is invisible and hard to fix. If you are not sure, it is better to leave it duplicated.
What you see in the field
- A module has 40 arguments → the boundary is wrong. It has to be split.
- Environment branches like
count = var.is_prod ? 3 : 1pile up inside a module → the module is deciding on its own what should be received as an argument. - Fixing a module broke an unrelated environment → there is no version pinning.
What you will do in the next lab
In the three labs that follow, you work with drift and modules directly in OpenTofu. You read a hand-edited file with plan -refresh-only and choose among absorbing, reverting, and excluding; clean up two states fighting over one resource with a removed block and then bring in the existing value with import; and turn two copy-pasted environments into module calls with moved, without a replacement, and pin the version with a git tag. In the final quiz you judge stable boundaries, version pinning, and caller responsibility.