Belajar Software Architect - Design Patterns & Principles
Episode 4 of 28

Belajar Software Architect - Design Patterns & Principles

Menurunkan arsitektur ke level kode yang sehat: prinsip SOLID beserta contoh nyata, pattern GoF yang paling relevan bagi architect, dan domain-driven design sebagai alat menemukan boundary — ditutup praktik memetakan bounded context studi kasus e-commerce

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Setelah di episode 3 kalian memilih architectural style — untuk studi kasus kita: modular monolith dengan komunikasi event di bagian non-kritis — pada episode ini kita turun satu level ke design patterns & principles: fondasi yang membuat struktur besar itu tetap sehat sampai ke dalam kode.

Mengapa architect perlu peduli level ini? Karena arsitektur yang baik tidak bisa berdiri di atas kode yang busuk. Modul dengan boundary indah di diagram tetapi dipenuhi dependency acak di dalamnya akan tetap menghasilkan sistem yang sulit diubah. Sebaliknya, pemahaman pattern & principles adalah bahasa bersama antara architect dan tim implementasi — ketika kalian berkata "pisahkan lewat adapter", semua orang tahu apa maksudnya.

SOLID: Lima Prinsip Fondasi

SOLID dirumuskan Robert C. Martin sebagai lima prinsip desain berorientasi objek. Di tingkat architect, kalian tidak menghafalnya — kalian menggunakannya sebagai alat diagnosis saat review desain.

Single Responsibility Principle

Satu class/modul punya satu alasan untuk berubah. Kelas OrderService yang sekaligus menghitung pajak, memanggil payment gateway, dan mengirim email akan berubah karena tiga alasan berbeda — dan itu sumber konflik antar tim.

Open-Closed Principle

Terbuka untuk ekstensi, tertutup untuk modifikasi. Menambah metode pembayaran baru idealnya menambah class baru, bukan mengedit rantai if/else di tengah kode pembayaran.

Liskov Substitution Principle

Subclass harus bisa menggantikan parent tanpa merusak perilaku. Pelanggaran klasik: Penguin extends Bird lalu melempar error di method fly(). Di level service: API versi baru harus bisa dipakai oleh client lama tanpa surprise.

Interface Segregation Principle

Jangan paksa client bergantung pada method yang tidak ia perlukan. Interface Repository dengan 20 method generik lebih buruk daripada beberapa interface kecil yang spesifik per use case.

Dependency Inversion Principle

Modul high-level tidak boleh bergantung pada detail low-level; keduanya bergantung pada abstraksi. Ini prinsip yang paling sering muncul di diskusi arsitektur:

Dependency inversion dalam praktik
interface PaymentGateway {
    charge(orderId: string, amountCents: number): Promise<PaymentResult>
}
 
// business logic hanya kenal interface
export class CheckoutService {
    constructor(private gateway: PaymentGateway) {}
}
 
// detail (Midtrans, Stripe) menyusul tanpa menyentuh domain

Dampaknya ke arsitektur: domain layer bebas dari vendor lock-in, testing mudah dengan mock, dan penggantian provider menjadi keputusan murah — persis jenis fleksibilitas yang dibayar mahal jika hilang.

Tip

Cara cepat mendeteksi pelanggaran SOLID saat code review design: hitung kata "dan". Jika kalian menjelaskan satu class dengan banyak "dan", ia melanggar SRP. Jika menambah fitur butuh mengedit banyak file yang tidak berhubungan, OCP dan DIP kemungkinan dilanggar.

Pattern GoF yang Relevan bagi Architect

Dari 23 pattern GoF, tidak semuanya penting bagi architect. Fokuslah pada yang efeknya terasa lintas modul:

PatternMasalah yang DiselesaikanRelevansi Arsitektur
AdapterInterface pihak ketiga tidak cocokIsolasi vendor, anti-corruption layer
FacadeSubsistem kompleksSederhanakan API publik modul
StrategyAlgoritma bervariasi per konteksPluggable pricing, routing
Observer / Pub-SubNotifikasi many-to-manyFondasi event-driven (episode 13)
DecoratorTambah perilaku tanpa ubah intiMiddleware, retry/logging wrapper
FactoryPembuatan object kompleksKonstruksi service per environment

Perhatikan polanya: pattern yang naik ke level arsitektur adalah yang memengaruhi dependency antar komponen, bukan yang sekadar rapi di dalam satu file.

Contoh: Adapter untuk Isolasi Vendor

adapter/midtrans.ts
// Domain hanya kenal port-nya sendiri
import { PaymentGateway } from "../domain/ports"
 
export class MidtransAdapter implements PaymentGateway {
    async charge(orderId: string, amountCents: number) {
        // detail HTTP Midtrans dibungkus di sini,
        // termasuk mapping error ke domain error
        const res = await this.client.createTransaction({
            order_id: orderId,
            gross_amount: amountCents / 100,
        })
        return mapToDomainResult(res)
    }
}

Kalau suatu hari ganti ke Xendit, hanya file adapter yang berubah. Pola ini juga disebut hexagonal architecture atau ports and adapters — dan ia jembatan alami menuju DDD.

Domain-Driven Design: Strategis vs Taktis

DDD sering disalahpahami sebagai sekadar anotasi @Entity. Bagi architect, bagian terpenting adalah strategic design — cara membagi sistem besar menjadi bagian yang bisa dikelola.

Ubiquitous Language

Tim, kode, dan dokumen memakai istilah yang sama. Kalau bisnis menyebut "Pesanan" tapi database menyebut transaction_record, setiap diskusi membayar pajak translasi. Bahasa yang konsisten membuat model kode mencerminkan realitas bisnis.

Bounded Context

Model yang valid di satu area tidak otomatis valid di area lain. Kata "produk":

  • Di catalog: judul, deskripsi, gambar, harga tampil.
  • Di inventory: SKU, stok fisik, gudang.
  • Di shipping: berat, dimensi, origin.

Memaksa satu class Product untuk semua context menghasilkan god-object yang tak berhenti bertumbuh. Bounded context memotong sistem berdasarkan makna bisnis — dan potongan inilah kandidat terbaik untuk module boundary (episode 7).

Context Mapping

Antara dua bounded context selalu ada relasi: shared kernel, customer-supplier, conformist, atau anti-corruption layer. Memetakan relasi ini eksplisit mencegah model satu context bocor ke context lain.

Bagian tactical DDD (entity, value object, aggregate, repository) tetap penting — terutama aggregate sebagai unit konsistensi transaksional. Aggregate Order menentukan invariant mana yang dijaga dalam satu transaksi ACID; apa pun di luar agregat di-sync via event.

Praktik: Memetakan Bounded Context E-commerce

Mari praktikkan pada studi kasus kita. Langkah pertama: workshop event storming ringkas — daftar event bisnis utama:

Event bisnis utama
ProdukDitambahkan, StokBerubah,
OrderDibuat, OrderDibayar, OrderDibatalkan,
PembayaranDiterima, PembayaranGagal,
PengirimanDijadwalkan, PengirimanSelesai

Langkah kedua: kluster event berdasarkan siapa yang "memiliki" bahasanya:

case-studies/ecommerce/bounded-contexts.yaml
contexts:
  catalog:
    owns: [ProdukDitambahkan]
    language: produk, kategori, harga-tampil
  inventory:
    owns: [StokBerubah]
    language: sku, stok-fisik, reservasi
  ordering:
    owns: [OrderDibuat, OrderDibatalkan]
    language: pesanan, keranjang, checkout
  payment:
    owns: [PembayaranDiterima, PembayaranGagal]
    language: charge, refund, settlement
  shipping:
    owns: [PengirimanDijadwalkan, PengirimanSelesai]
    language: resi, kurir, ETA

Lima context, masing-masing dengan bahasa sendiri. Perhatikan bahwa ini bukan pemecahan teknis (frontend/backend/database), melainkan pemecahan berdasarkan makna bisnis — itulah kenapa boundary seperti ini awet terhadap perubahan framework.

Important

Uji kelayakan boundary: jika dua context selalu harus berubah bersamaan setiap sprint, mereka kemungkinan satu context yang terbelah. Jika satu context berisi tiga sub-domain yang jarang saling sentuh, ia kandidat dipecah. Boundary ditemukan lewat iterasi, bukan ditetapkan sekali jadi.

Kesalahan Umum

  • Pattern for pattern's sake — memakai AbstractFactoryBuilderProvider untuk masalah yang diselesaikan satu fungsi. Setiap abstraksi punya biaya pemahaman.
  • Over-abstraction di awal — membuat interface "untuk nanti" padahal hanya ada satu implementasi dan tidak ada rencana konkret implementasi kedua.
  • Anemic domain model — class penuh getter/setter tanpa perilaku; semua logika menumpuk di service raksasa. Ini SRP yang gagal terselubung.
  • DDD dogmatis — menerapkan tactical pattern lengkap di aplikasi CRUD sederhana. DDD strategis bernilai di domain kompleks; untuk CRUD, layered sederhana lebih jujur.
  • Bounded context = microservice — belum tentu. Satu context bisa berisi beberapa modul dalam monolith (pilihan episode 3), atau beberapa team di satu context besar.

Penutup

Inti yang harus dibawa pulang:

  • SOLID adalah alat diagnosis desain; DIP dan SRP paling sering muncul di diskusi arsitektur.
  • Pattern GoF yang relevan bagi architect adalah yang memengaruhi dependency lintas komponen — Adapter, Facade, Strategy, Observer, Decorator.
  • Ports and adapters mengisolasi vendor dan membuat keputusan integrasi murah untuk diubah.
  • DDD strategic (ubiquitous language, bounded context, context mapping) adalah alat utama architect untuk menemukan boundary yang awet; tactical patterns mendukung konsistensi di dalam context.
  • Studi kasus e-commerce kini punya lima bounded context sebagai dasar modularisasi.

Di episode 5 selanjutnya kita kembali ke quality attributes — kali ini secara engineering: format quality scenario yang terukur, tactics untuk performance, scalability, reliability, security, dan maintainability, serta cara menyusun scenario untuk flash sale studi kasus kita. Sampai jumpa!

Belajar Software Architect - Design Patterns & Principles | Belajar Software Architect