Stop Scripting, Start Architecting: The OOP Approach to Terraform

Published: (December 10, 2025 at 11:52 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

TL;DR

The Problem: Terraform codebases often suffer from sprawl—copy‑pasted resources, tight coupling, and leaky abstractions that make scaling painful.

The Solution: Treat Terraform modules as classes and module instances as objects.

Key Mapping

OOP ConceptTerraform Equivalent
ClassChild module (e.g., ./modules/web_server/)
Objectmodule block (instantiation)
Interfacevariables.tf (inputs) and outputs.tf (getters)
Private Statelocals and internal resources
Best PracticePrefer composition (building modules from other modules) over inheritance. Use dependency injection by passing resource IDs (e.g., vpc_id) rather than looking them up internally with data sources.

An object‑oriented approach transforms messy, repetitive HCL into a scalable infrastructure architecture. By mapping OOP principles—encapsulation, abstraction, composition, and polymorphism—to Terraform modules, we can build infrastructure that is as maintainable and testable as application code.


The Problem: The Monolithic Terraform File

In the early days of a project, a single main.tf file is convenient. As infrastructure grows, this “scripting” mindset leads to fragility: hard‑coded values repeated across environments, security groups defined inline with instances, and a complete lack of reusability.

When Terraform is treated purely as a configuration script, we miss the structural benefits of software‑engineering design patterns. We need to shift from writing scripts to architecting objects.


The Core Analogy: Modules as Classes

OOP ConceptTerraform Implementation
Class Definition./modules/web_server/ – the blueprint that defines how to build something, not what to build.
Constructorvariables.tf – defines the required inputs to instantiate the class.
Public Methods / Propertiesoutputs.tf – defines the data explicitly exposed to the caller.
Private Memberslocals, resource – internal logic and state hidden from the parent scope.
Object Instancemodule "web_prod" { … } – a specific realization of the blueprint.

Visualization: The Module Interface

classDiagram
    class Module {
        >
        +variables.tf
        +outputs.tf
        -locals
        -resources
    }
    class ChildModule {
        +module "instance" {}
    }
    Module  B[networking]
    A --> C[compute]
    B --> D[VPC & Subnets]
    C --> E[EC2 Instances]

Code Example

# /modules/app_stack/main.tf
module "networking" {
  source = "../networking"
  cidr   = var.cidr
}

module "compute" {
  source    = "../compute"
  subnet_id = module.networking.private_subnet_id   # Wiring components together
  vpc_id    = module.networking.vpc_id
}

The app_stack module acts as a façade, coordinating the interaction between networking and compute layers while keeping each sub‑module focused on a single responsibility.


4. Abstraction & Reuse: The “Interface” Behavior

(Content truncated in the source; the principle continues the pattern of defining clear input/output contracts via variables.tf and outputs.tf, enabling reusable, interchangeable modules.)

Back to Blog

Related posts

Read more »