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:

text
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.md

Karena 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.

text
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 terima

Memformat 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
<?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:

text
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 presentasi

Mari kita bedah formatter di atas:

  1. Constructor promotion + readonly: satu tempat deklarasi untuk seluruh struktur ADR; objek yang sudah dibuat tidak bisa dimanipulasi diam-diam.
  2. statusText(): status superseded otomatis mendapat akhiran by ADR-XXXX selama nomor pengganti diisi — tautan balik yang wajib ada saat keputusan diganti.
  3. 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
<?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:

text
SAH: proposed -> accepted
SAH: proposed -> rejected
SAH: accepted -> superseded
TIDAK SAH: superseded -> accepted

Fungsi 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:

text
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.

Referensi #