Anti-Pattern: Over-Complex Module #

Module yang baik menyembunyikan kompleksitas implementasi dan menyediakan interface yang sederhana. Module yang buruk menyembunyikan kesederhanaan di balik abstraksi yang tidak perlu, memaksa pengguna mempelajari abstraksi tersebut sebelum bisa menggunakannya. Over-complex module adalah salah satu anti-pattern yang paling sering ditemukan di repository Terraform yang sudah mature — dimulai dengan niat baik untuk membuat module yang “fleksibel”, berakhir dengan module yang lebih sulit dipahami dari resource yang dibungkusnya.

Tanda-Tanda Module yang Terlalu Kompleks #

# ANTI-PATTERN: Module dengan puluhan variable

# variables.tf — module yang sudah tidak terkontrol
variable "enable_monitoring"         { type = bool; default = true }
variable "enable_logging"            { type = bool; default = true }
variable "enable_enhanced_logging"   { type = bool; default = false }
variable "enable_debug_logging"      { type = bool; default = false }
variable "log_retention_days"        { type = number; default = 30 }
variable "log_format"                { type = string; default = "json" }
variable "enable_metrics"            { type = bool; default = true }
variable "metrics_namespace"         { type = string; default = "" }
variable "enable_alarms"             { type = bool; default = false }
variable "alarm_email"               { type = string; default = "" }
variable "alarm_threshold_cpu"       { type = number; default = 80 }
variable "alarm_threshold_memory"    { type = number; default = 85 }
variable "alarm_evaluation_periods"  { type = number; default = 3 }
# ... 30 variable lagi

# Masalah: Tidak ada yang bisa tahu apa yang akan terjadi
# tanpa membaca semua kode internal module
TANDA-TANDA OVER-COMPLEX MODULE:

  ✗ Lebih dari 15-20 variable input
  ✗ Variable dengan nama seperti enable_feature_x (boolean flag berlebihan)
  ✗ Logika kondisional kompleks di dalam module (count = var.enable_x ? 1 : 0)
  ✗ Module mencoba menangani semua use case yang mungkin terjadi
  ✗ Dokumentasi module lebih panjang dari konfigurasi yang menggunakannya
  ✗ Pengguna module perlu baca source code untuk memahami apa yang dilakukan
  ✗ Sulit menulis test karena ada terlalu banyak kombinasi input

Abstraksi Berlebihan yang Menyembunyikan Kejelasan #

# ANTI-PATTERN: Module yang "abstrak" tapi justru membingungkan

module "my_ec2" {
  source = "./modules/ec2-abstracted"

  # Pengguna harus tebak apa arti parameter ini
  compute_tier     = "standard"     # Apa ini? Mapping ke apa?
  redundancy_level = 2              # Ini jumlah instance? AZ? Replica?
  network_exposure = "semi-public"  # Apa bedanya public, semi-public, private?
  persistence_mode = "ephemeral"    # Ini punya volume atau tidak?
}

# Abstraksi ini menyembunyikan konfigurasi yang sebenarnya —
# pengguna tidak tahu resource apa yang akan dibuat

# BENAR: Module dengan interface yang jelas
module "web_server" {
  source = "./modules/ec2"

  instance_count = 2
  instance_type  = "t3.medium"
  subnet_ids     = var.private_subnet_ids   # Jelas: private subnet
  assign_public_ip = false                  # Jelas: tidak ada public IP
}

# Dari interface ini, pengguna langsung tahu:
# - 2 instance EC2 akan dibuat
# - Di private subnet
# - Tanpa public IP

Boolean Flag yang Proliferasi #

# ANTI-PATTERN: Terlalu banyak boolean flag
module "rds" {
  source = "./modules/rds"

  enable_multi_az          = var.env == "production"
  enable_backup            = true
  enable_performance_insight = var.env == "production"
  enable_deletion_protection = var.env == "production"
  enable_enhanced_monitoring = var.env == "production"
  enable_iam_auth          = false
  enable_auto_minor_version_upgrade = true
  # ... semua flag ini harus diset oleh setiap pemanggilnya

# BENAR: Sediakan preset yang umum digunakan
module "rds_production" {
  source = "./modules/rds"
  preset = "production"  # Module handle semua opsi production secara internal

  # Atau lebih baik lagi: sediakan dua module terpisah
  # modules/rds-production/  dan  modules/rds-development/
  # yang punya default yang tepat untuk masing-masing use case
}
# Implementasi preset di dalam module
variable "preset" {
  type        = string
  default     = "development"
  description = "Preset konfigurasi: development atau production"

  validation {
    condition     = contains(["development", "production"], var.preset)
    error_message = "Preset harus 'development' atau 'production'"
  }
}

locals {
  config = {
    development = {
      multi_az                    = false
      backup_retention_period     = 1
      deletion_protection         = false
      performance_insights_enabled = false
    }
    production = {
      multi_az                    = true
      backup_retention_period     = 7
      deletion_protection         = true
      performance_insights_enabled = true
    }
  }[var.preset]
}

Module yang Mencoba Melakukan Terlalu Banyak #

# ANTI-PATTERN: "God module" yang melakukan segalanya
module "application_stack" {
  source = "./modules/application-stack"

  # Module ini membuat:
  # - VPC dan networking
  # - ECS cluster
  # - RDS database
  # - ElastiCache
  # - Load balancer
  # - Route53 records
  # - ACM certificates
  # - IAM roles
  # - CloudWatch dashboards
  # - SNS topics untuk alerting
  # Semua dalam satu module!

  # Masalah: Tidak bisa digunakan secara parsial,
  # tidak bisa diupdate sebagian, blast radius sangat besar
}

# BENAR: Satu module, satu tanggung jawab
module "networking" {
  source = "./modules/networking"
  # Hanya VPC, subnet, routing
}

module "database" {
  source    = "./modules/rds"
  vpc_id    = module.networking.vpc_id
  subnet_ids = module.networking.private_subnet_ids
}

module "application" {
  source     = "./modules/ecs-service"
  cluster_id = module.compute.cluster_id
  db_endpoint = module.database.endpoint
}

# Setiap module bisa di-update, di-test, dan di-scale secara independen

Prinsip Interface Module yang Baik #

# Interface yang baik: minimal, jelas, dengan default yang masuk akal

variable "name" {
  type        = string
  description = "Nama resource — digunakan sebagai prefix untuk semua resource yang dibuat"
}

variable "environment" {
  type        = string
  description = "Environment: dev, staging, atau production"
  validation {
    condition     = contains(["dev", "staging", "production"], var.environment)
    error_message = "Environment harus dev, staging, atau production"
  }
}

variable "vpc_id" {
  type        = string
  description = "ID VPC tempat resource akan dibuat"
}

variable "subnet_ids" {
  type        = list(string)
  description = "List subnet ID tempat resource akan didistribusikan"
}

# Opsi tambahan dengan default yang masuk akal
variable "instance_type" {
  type        = string
  description = "EC2 instance type"
  default     = "t3.medium"
}

variable "tags" {
  type        = map(string)
  description = "Tag tambahan yang akan ditambahkan ke semua resource"
  default     = {}
}

# TIDAK perlu:
# variable "enable_feature_x" — ini seharusnya bagian dari logic module
# variable "internal_timeout_seconds" — detail implementasi, bukan interface
# variable "advanced_config_object" — ini tanda module sudah overloaded

Refactoring Module yang Sudah Terlanjur Kompleks #

STRATEGI REFACTORING:

  Langkah 1: Identifikasi kelompok fungsional
    Dari 40 variable, kelompokkan ke: networking, compute, monitoring, security
    Setiap kelompok yang bisa berdiri sendiri → kandidat untuk module terpisah

  Langkah 2: Pisahkan "core" dari "optional"
    Core: variable yang hampir selalu diisi oleh semua pemanggilnya
    Optional: variable yang hanya dipakai <50% pemanggilnya
    → Optional feature bisa jadi module opsional terpisah

  Langkah 3: Gunakan moved block untuk refactoring tanpa destroy
    Saat memecah module, gunakan moved block agar resource
    tidak perlu di-destroy dan dibuat ulang

  Langkah 4: Deprecation period
    Pertahankan module lama untuk sementara
    Beri warning di description bahwa module ini deprecated
    Beri waktu pemanggilnya untuk migrasi ke module baru

Ringkasan #

  • Module yang baik punya interface minimal dan jelas — pengguna tidak perlu baca source code untuk memahami apa yang akan dibuat.
  • Lebih dari 15-20 variable adalah sinyal bahaya — kemungkinan module sedang mencoba melakukan terlalu banyak hal sekaligus.
  • Boolean flag yang proliferasi (enable_x, enable_y, enable_z) menciptakan kombinasi yang tidak terbatas dan sulit di-test — ganti dengan preset atau pisah menjadi modul terpisah.
  • “God module” yang melakukan segalanya memiliki blast radius besar dan tidak bisa diupdate sebagian — pecah menjadi modul dengan satu tanggung jawab.
  • Abstraksi hanya berguna jika menyederhanakan interface, bukan jika memaksa pengguna mempelajari sistem baru yang sama kompleksnya dengan resource aslinya.
  • Saat refactoring module yang sudah terlanjur kompleks: identifikasi kelompok fungsional, pisahkan core dari optional, gunakan moved block untuk migrasi tanpa destroy.

← Sebelumnya: Anti-Pattern: Terraform as CM   Berikutnya: Anti-Pattern: Production Failure Scenario →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact