Setiap codebase menyimpan puluhan keputusan arsitektur: memakai monolith modular, berkomunikasi lewat event, menyimpan uang sebagai integer cents. Enam bulan kemudian, programmer baru menatap keputusan-keputusan itu dan bertanya: “kenapa begini?” — dan sering tidak ada yang ingat jawabannya. Architecture Decision Records (ADR) adalah kebiasaan menulis keputusan penting sebagai dokumen kecil yang hidup berdampingan dengan kode. Pada chapter penutup ini kita akan mempelajari bentuk ADR, membangun formatter dan aturan transisi status-nya dalam PHP, serta melihat contoh terisi yang siap Anda contek untuk proyek berikutnya.
Apa Itu ADR #
Sebuah ADR adalah dokumen pendek — idealnya satu halaman — yang mencatat satu keputusan arsitektur beserta alasannya. Format yang paling umum dipakai komunitas berasal dari Michael Nygard dan terdiri dari lima bagian: judul, status, context, decision, dan consequences. ADR menulis keputusan, bukan deskripsi: dokumen “kita memilih SQLite untuk modul laporan” adalah ADR, sedangkan dokumen “cara kerja modul laporan” bukan ADR melainkan dokumentasi biasa.
Ciri pembeda lainnya: ADR tidak diedit setelah disetujui. Ketika keputusan berubah, ADR lama tidak dihapus atau ditulis ulang, melainkan ditandai superseded dengan tautan ke ADR penggantinya. Riwayat berpikir tim tetap utuh — Anda bisa melihat keputusan mana yang pernah diambil, kapan diganti, dan mengapa.
Template dan Cara Menyimpannya #
ADR disimpan di dalam repositori kode, biasanya di folder docs/adr/, dengan penomoran urut yang menjadi identitas tetapnya:
docs/adr/
├── 0001-menggunakan-modular-monolith.md
├── 0002-uang-disimpan-sebagai-integer-cents.md
├── 0003-komunikasi-antar-modul-dengan-event.md
├── 0004-sqlite-untuk-modul-laporan.md
└── 0005-postgresql-untuk-modul-order.mdKarena ADR hanyalah berkas markdown, ia ikut ter-review dalam pull request, terlihat oleh siapa pun yang membaca kode, dan bisa dirujuk dari komentar kode maupun diskusi. Isi setiap ADR mengikuti template ini — baris judul sengaja ditulis polos karena nomor dan judulnya sudah terbawa nama berkas; sebagian tim menambahkan awalan # di depannya agar tampil sebagai heading di penampil markdown, dan itu cuma selera format.
ADR-NNNN: Judul keputusan
## Status
accepted
## Context
Latar belakang masalah, kekuatan yang mempengaruhi,
dan pilihan yang tersedia saat keputusan diambil.
## Decision
Kalimat "Kami memilih ..." beserta alasannya.
## Consequences
- Konsekuensi positif
- Konsekuensi negatif / trade-off
- Risiko yang kita terimaMemformat ADR dengan Kode #
Karena ADR berbentuk teratur, kita bisa mengotomatiskan pembuatannya — mencegah dokumen “kreativ” yang formatnya bebas antar penulis. Berikut formatter yang menyusun data ADR menjadi markdown siap simpan:
<?php
final class Adr
{
public function __construct(
public readonly string $number,
public readonly string $title,
public readonly string $status,
public readonly string $context,
public readonly string $decision,
public readonly array $consequences,
public readonly ?string $supersededBy = null,
) {}
public function statusText(): string
{
$text = ucfirst($this->status);
if ($this->status === 'superseded' && $this->supersededBy !== null) {
$text .= " by ADR-{$this->supersededBy}";
}
return $text;
}
public function toMarkdown(): string
{
$markdown = "ADR-{$this->number}: {$this->title}\n\n"
. "## Status\n\n{$this->statusText()}\n\n"
. "## Context\n\n{$this->context}\n\n"
. "## Decision\n\n{$this->decision}\n\n"
. "## Consequences\n\n";
foreach ($this->consequences as $consequence) {
$markdown .= "- {$consequence}\n";
}
return $markdown;
}
}
$adr = new Adr(
number: '0002',
title: 'Uang disimpan sebagai integer cents',
status: 'accepted',
context: 'Harga dan total duluan disimpan sebagai float; pembulatan menyebabkan selisih laporan.',
decision: 'Kami menyimpan seluruh nominal sebagai integer cents (1 rupiah = 100 cents).',
consequences: [
'Tidak ada lagi galat pembulatan pada laporan keuangan',
'Perlu kebiasaan mengalikan/membagi 100 di batas presentasi',
],
);
echo $adr->toMarkdown();Output yang Dihasilkan:
ADR-0002: Uang disimpan sebagai integer cents
## Status
Accepted
## Context
Harga dan total duluan disimpan sebagai float; pembulatan menyebabkan selisih laporan.
## Decision
Kami menyimpan seluruh nominal sebagai integer cents (1 rupiah = 100 cents).
## Consequences
- Tidak ada lagi galat pembulatan pada laporan keuangan
- Perlu kebiasaan mengalikan/membagi 100 di batas presentasiMari kita bedah formatter di atas:
- Constructor promotion +
readonly: satu tempat deklarasi untuk seluruh struktur ADR; objek yang sudah dibuat tidak bisa dimanipulasi diam-diam. statusText(): statussupersededotomatis mendapat akhiranby ADR-XXXXselama nomor pengganti diisi — tautan balik yang wajib ada saat keputusan diganti.toMarkdown(): merangkai kelima bagian template secara berurutan; daftar consequences dirangkai per baris dengan awalan-.
Dengan formatter seperti ini, tim bisa membuat ADR baru dari satu panggilan fungsi (atau command CLI kecil) lalu menyimpan hasilnya dengan file_put_contents()
ke docs/adr/0006-judul-keputusan.md.
Status dan Pergantian Keputusan #
Status ADR mengikuti daur hidup keputusan: usulan (proposed) bisa diterima (accepted) atau ditolak (rejected), dan keputusan yang diterima kelak bisa digantikan (superseded). Transisi ini punya aturan — misalnya ADR yang sudah digantikan tidak bisa “hidup lagi” begitu saja. Aturan tersebut layak ditegakkan kode, bukan hanya kebiasaan:
<?php
/**
* @param array<string, list<string>> $transisi tabel transisi yang sah
*/
function cekTransisi(array $transisi, string $from, string $to): string
{
if (!isset($transisi[$from])) {
return "TIDAK DIKETAHUI: status '$from' tidak dikenal";
}
if (!in_array($to, $transisi[$from], true)) {
return "TIDAK SAH: $from -> $to";
}
return "SAH: $from -> $to";
}
$transisi = [
'proposed' => ['accepted', 'rejected'],
'accepted' => ['superseded'],
'rejected' => [],
'superseded' => [],
];
$pengujian = [
['proposed', 'accepted'],
['proposed', 'rejected'],
['accepted', 'superseded'],
['superseded', 'accepted'],
];
foreach ($pengujian as [$from, $to]) {
echo cekTransisi($transisi, $from, $to) . "\n";
}Output yang Dihasilkan:
SAH: proposed -> accepted
SAH: proposed -> rejected
SAH: accepted -> superseded
TIDAK SAH: superseded -> acceptedFungsi cekTransisi() membaca tabel transisi sebagai data, sehingga tim bisa memperketat atau melonggarkan aturan tanpa menyentuh logika pemeriksaan. Tiga transisi pertama pada pengujian di atas adalah alur normal; transisi keempat — menghidupkan kembali ADR yang sudah digantikan — ditolak, karena keputusan yang benar-benar mau dibangkitkan lagi harus ditulis sebagai ADR baru yang menyupersede penggantinya. Riwayat tetap linear dan jujur.
Contoh ADR Terisi #
Berikut contoh ADR nyata tentang pilihan database untuk modul laporan — perhatikan bahwa context memuat tekanan nyata, decision menyebut pilihan eksplisit, dan consequences tidak menutup-nutupi trade-off:
ADR-0004: SQLite untuk modul laporan
## Status
accepted
## Context
Modul laporan membaca data penjualan harian untuk
dianalisis tim. Volume data kecil (puluhan ribu baris
per bulan) dan hanya diakses tiga orang analis. Server
MySQL utama sudah padat, dan tim tidak ingin menambah
beban operasional database server baru.
## Decision
Kami memakai SQLite sebagai basis data modul laporan,
dengan berkas tunggal yang dicadangkan setiap malam.
## Consequences
- Tidak ada server tambahan yang harus diawasi
- Analis bisa menyalin berkas .sqlite dan menganalisis
secara lokal dengan perkakas apa pun
- Tulis-bersamaan dari banyak proses akan terblokir
(dapat diterima karena penulis hanya satu job malam)
- Bila volume tumbuh ke jutaan baris, keputusan ini
perlu diganti (lihat ADR-0005 untuk PostgreSQL)Kapan Menulis ADR dan Bagaimana Membiasakannya #
Tidak semua keputusan layak menjadi ADR — jika demikian, folder docs/adr/ akan tenggelam oleh dokumen tentang penamaan variabel. Patokan praktisnya: tulis ADR untuk keputusan yang signifikan (memengaruhi banyak bagian sistem) dan sulit dibalik (mengubahnya nanti mahal). Contoh yang layak: memilih monolith vs microservices
, memilih gaya komunikasi event-driven
, strategi versioning API, atau penyimpanan password hashing.
Untuk pembiasaan di tim, mulai dari ukuran paling kecil: sepakati template, simpan di repo, dan pasang aturan sederhana dalam review — pull request yang mengubah keputusan arsitektur harus menyertakan ADR baru atau revisi status ADR lama. ADR pertama biasanya ditulis mundur, mendokumentasikan keputusan besar yang sudah diambil (cara yang sama seperti kita menulis ulang seluruh perjalanan section ini), lalu dokumen berikutnya tertulis pada saat keputusannya benar-benar terjadi.
Ringkasan #
Architecture Decision Records menutup lingkaran dari seluruh section ini: modular monolith, API-first, BFF, event-driven, dan DDD adalah keputusan — dan keputusan layak dicatat alasannya. ADR menjaga konteks, keputusan, dan konsekuensinya tetap terbaca tahun kemudian; status yang hanya bergerak maju menjaga riwayat berpikir tetap jujur; dan folder docs/adr/ yang hidup dalam pull request membuat arsitektur menjadi percakapan berkelanjutan, bukan warisan bisu. Selamat menulis ADR pertama Anda — dan selamat menutup Course PHP ini dengan kebiasaan yang membuat proyek Anda bertahan lebih lama dari pada sekadar kode.