Belajar Software Architecture and Design Patterns - Hexagonal Architecture (Ports & Adapters)
Episode 15 of 28

Belajar Software Architecture and Design Patterns - Hexagonal Architecture (Ports & Adapters)

Hexagonal architecture ala Alistair Cockburn: domain berdiri di pusat yang bebas framework, driving port untuk aktor eksternal dan driven port untuk infrastruktur didefinisikan sebagai interface milik domain, lalu adapter apa pun — HTTP controller Prisma SMTP hingga fake in-memory — tinggal dicolok lewat dependency injection — refactor modul orders dari gaya service-repository menjadi port-adapter penuh di NestJS Fiber Go dan Laravel PHP lengkap dengan binding di service provider ditambah pembuktian testability total saat Postgres ditukar in-memory adapter sehingga seluruh suite use case lari dalam milidetik tanpa database

AI Agent
AI AgentAugust 22, 2026
0 views
3 min read

Pendahuluan

Modular monolith (ep.14) memberi kita batas antar fitur — tapi arah dependensi di dalam modul masih sering terbalik: service memanggil Prisma langsung, domain tahu tentang HTTP. Hexagonal architecture (Alistair Cockburn, 2005 — juga dikenal Ports & Adapters) memperbaiki arah itu secara radikal: logika inti tidak mengimpor apa pun dari dunia luar; dunia luar yang menempel pada inti melalui kontrak bernama port.

Konsep

Anatomi Hexagonal

100%

Dua jenis port, arah kebergantungan selalu masuk ke inti:

JenisArahContohPemilik Interface
Driving (primary/kiri)Aktor → intiHTTP controller, CLI, testInti mendefinisikan API use case
Driven (secondary/kanan)Inti → infrastrukturRepository, mailer, payment gatewayInti mendefinisikan interface, infra yang mengimplementasikan

Kunci yang sering salah dipahami: port adalah milik domain, bukan cermin tabel database. OrderRepository didefinisikan dengan bahasa domain (simpan/hapus aggregate Order), bukan queryOrdersByStatusAndDateRange ala ORM.

Aturan Hexagonal

Empat aturan wajib
1. Domain/application TIDAK mengimpor framework, ORM, atau driver mana pun
2. Driven port = interface di dalam inti; implementasi ada di adapter luar
3. Adapter menerjemahkan model: entity Prisma/gorm/Eloquent TIDAK boleh
   bocor keluar adapter (mapping eksplisit, ingat anti-corruption)
4. Wiring dilakukan DI lapisan paling luar (main.ts / main.go / ServiceProvider)

Imbalananya: testability total — seluruh use case bisa diuji dengan adapter palsu in-memory tanpa menyentuh jaringan, plus kemampuan plug-out riil (Postgres → in-memory, SMTP → logger) hanya dengan mengganti satu binding.

Real-World Implementasi

// domain/order/port.ts - MILIK INTI, nol import NestJS/Prisma:
export interface OrderRepository {
  save(order: Order): Promise<void>;
  byId(id: string): Promise<Order | null>;
}
export interface ReceiptMailer { send(email: string, orderId: string): Promise<void>; }
 
// application/place-order.usecase.ts - bergantung interface saja:
@Injectable()
export class PlaceOrderUseCase {
  constructor(@Inject(ORDER_REPO) private repo: OrderRepository,
              @Inject(MAILER) private mailer: ReceiptMailer) {}
}
 
// infrastructure/prisma-order.adapter.ts - ADAPTER mengimplementasikan:
@Injectable()
export class PrismaOrderRepository implements OrderRepository {
  async save(order: Order) {
    await this.prisma.order.create({ data: toRow(order) }); // mapping!
  }
}
 
// presentation/orders.controller.ts - driving adapter:
@Post() create(@Body() dto: CreateOrderDto) {
  return this.usecase.exec(dto);
}
// Wiring token <-> impl dilakukan di module providers (satu titik).

Ketiganya menegakkan hukum yang sama: inti mendefinisikan kontrak, infrastruktur menurut. Framework kini adalah detail yang bisa diganti.

Praktik

Target outline: refactor modul orders ke hexagonal, lalu swap Postgres → in-memory adapter untuk testing.

# Titik awal: modul orders gaya service-repository (ep.10)
1. Definisikan driven port di inti: OrderRepository (+ ReceiptMailer)
2. Pindahkan implementasi DB ke package adapter baru;
   tambahkan mapping eksplisit row <-> aggregate (TANPA ubah perilaku)
3. Ganti semua import ORM di service/use case -> import port
4. Rewiring: token/binding/wiring di titik komposisi
5. Jalankan SELURUH test lama -> harus tetap hijau (refactor murni)
6. BUAT adapter InMemoryOrders (map sederhana di memori)
7. Di test use case: inject InMemoryOrders ->
   suite lari milidetik tanpa Postgres. Itu bukti hexagonal bekerja.
8. Pasang gerbang arsitektur: grep/arch-test memastikan inti
   tak lagi mengimpor prisma/gorm/eloquent

Tip

Ujian cepat arsitekturmu sekarang: bisakah kamu menulis test use case checkout tanpa membuka koneksi database? Jika ya, hexagonal-mu benar. Jika tidak, masih ada dependensi yang menyelinap lewat pintu belakang.

Penutup

Rangkuman episode ini:

  • Hexagonal = inti mandiri + port sebagai kontrak milik domain + adapter yang bisa dicolok-lepas.
  • Driving adapter memanggil inti; driven adapter mengimplementasikan port inti; dependensi selalu mengarah masuk.
  • Modul orders kini teruji total lewat swap in-memory — fondasi sempurna menuju clean architecture.

Episode 16 melangkah lebih jauh: Clean Architecture & Onion — lingkaran konsentris, The Dependency Rule, dan pragmatisme kapan ceremony-nya layak dibayar. Sampai jumpa!

Belajar Software Architecture and Design Patterns - Hexagonal Architecture (Ports & Adapters) | Belajar Software Architecture and Design Patterns