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
movedblock untuk migrasi tanpa destroy.
← Sebelumnya: Anti-Pattern: Terraform as CM Berikutnya: Anti-Pattern: Production Failure Scenario →