Agen pengodean cepat, percaya diri, dan tidak tahu arsitektur basis kode Anda sampai Anda menuliskannya di tempat yang mereka baca: repositori. Jika tiket Anda samar, Claude Code, Codex, atau Cursor bisa menghasilkan kode yang lolos tes tetapi diam-diam melanggar batas domain, HTTP, transaksi, atau kontrak API. File DESIGN.md mengurangi tebakan itu dengan mendokumentasikan maksud arsitektur, invariant, dan keputusan desain langsung di repo.
TL;DR
DESIGN.md adalah file Markdown di repositori yang menjelaskan mengapa sistem dibentuk seperti itu: batas layer, invariant, keputusan desain, dan hal yang sengaja ditolak.
Gunakan seperti ini:
README.md -> apa proyek ini dan cara mulai
AGENTS.md -> cara build, test, lint, commit
CLAUDE.md -> instruksi khusus Claude Code
DESIGN.md -> aturan arsitektur dan keputusan desain
Untuk agen pengodean, DESIGN.md membantu mereka menghasilkan kode yang sesuai desain sistem, bukan sekadar kode yang berhasil dikompilasi.
Pendahuluan
Mode gagal yang umum terjadi: Anda meminta agen menambahkan endpoint refund ke layanan pembayaran. Agen membuat handler yang berfungsi, tetapi handler itu langsung memanggil database dari controller, menelan error gateway, dan membuat tipe uang baru karena tidak tahu Anda sudah punya Money di domain.
Diff terlihat rapi. Tes cepat lolos. Tetapi desain rusak.
Masalahnya bukan agen tidak bisa menulis kode. Masalahnya: keputusan arsitektur sering hidup di kepala engineer senior, halaman Notion, atau thread Slack lama. Agen tidak membaca semua itu. Agen membaca file di repo, mencocokkan pola, lalu menebak.
DESIGN.md adalah satu file Markdown di root repo yang memberi tahu agen:
- layer mana yang boleh bergantung ke layer lain
- invariant apa yang tidak boleh dilanggar
- pola mana yang dipilih dengan sengaja
- alternatif mana yang pernah ditolak
- sumber kebenaran kontrak API
- area kode yang tidak boleh disentuh
File ini bukan standar resmi seperti AGENTS.md. Ini konvensi praktis, mirip ARCHITECTURE.md, CONTRIBUTING.md, atau ADR. Bedanya: DESIGN.md ditulis agar mudah dibaca manusia dan agen pengodean.
Apa sebenarnya DESIGN.md itu?
DESIGN.md menjawab pertanyaan yang tidak bisa disimpulkan agen hanya dari kode:
Mengapa kode ini dibentuk seperti ini?
Contoh aturan yang cocok ditulis di DESIGN.md:
- Domain tidak pernah mengimpor framework HTTP.
- Uang selalu integer minor unit; jangan gunakan float.
- Payment gateway tidak pernah dipanggil dari request thread.
- Semua operasi payment harus idempotent dengan `idempotency_key`.
- OpenAPI spec di `api/openapi.yaml` adalah sumber kebenaran.
Kode bisa menunjukkan bahwa Account.debit() ada. Tetapi kode tidak selalu menunjukkan bahwa Account.debit() adalah satu-satunya jalur tulis yang diizinkan. Tanpa aturan tertulis, agen bisa menambahkan jalur kedua yang tampak masuk akal tetapi merusak invariant.
DESIGN.md bekerja karena dua hal:
Ada di repo
Agen dapat membacanya dengan mekanisme yang sama seperti membaca file kode.Berisi maksud, bukan detail implementasi rapuh
Aturan seperti “domain tidak mengimpor web framework” tetap berlaku walaupun file dipindahkan atau package diganti nama.
DESIGN.md vs AGENTS.md vs CLAUDE.md vs README.md
Jangan gabungkan semua instruksi ke satu file besar. Setiap file punya tugas berbeda.
| File | Audiens | Menjawab | Tingkat perubahan | Panjang ideal |
|---|---|---|---|---|
README.md |
Manusia | Apa proyek ini dan cara mulai | Berubah dengan fitur | Sedang |
AGENTS.md |
Agen pengodean | Cara build, test, lint, commit | Berubah dengan tooling | Pendek |
CLAUDE.md |
Claude Code | Instruksi khusus Claude | Berubah dengan tooling | Pendek, idealnya di bawah ~200 baris |
DESIGN.md |
Agen, engineer, reviewer | Mengapa sistem berbentuk seperti ini dan apa yang tidak boleh rusak | Berubah dengan arsitektur | Sedang, padat keputusan |
AGENTS.md adalah format terbuka untuk memandu agen pengodean. Proyek agents.md menjelaskannya sebagai format sederhana untuk instruksi operasional seperti build, test, lint, dan gaya kode.
Untuk Claude Code, dokumentasi memori Claude Code menjelaskan penggunaan CLAUDE.md. Jika Anda sudah punya AGENTS.md, pola yang bersih adalah:
# CLAUDE.md
@AGENTS.md
Tambahan khusus Claude:
- Selalu jelaskan trade-off sebelum perubahan arsitektur besar.
- Jangan membuat file generated secara manual.
Lalu di AGENTS.md, referensikan DESIGN.md:
# AGENTS.md
## Build
bash
npm install
npm test
npm run lint
## Instruksi arsitektur
Sebelum mengubah boundary, model domain, kontrak API, transaksi,
atau integrasi eksternal, baca `DESIGN.md`.
markdown
Dengan pola ini:
-
AGENTS.mdtetap pendek dan operasional -
CLAUDE.mdtetap khusus Claude -
DESIGN.mdmenyimpan keputusan arsitektur - tidak ada duplikasi
Jika Anda ingin konteks lebih dalam tentang struktur file instruksi Claude, lihat Claude Code workflows.
Apa yang harus dimasukkan ke DESIGN.md
Tulis hal yang tidak bisa ditebak dengan aman dari kode.
Masukkan bagian-bagian ini:
Bentuk sistem
Layer, modul, dan arah dependensi.Invariant
Aturan yang harus selalu benar.Keputusan utama dan alasan
Jelaskan pilihan desain dan mengapa pilihan itu dibuat.Alternatif yang ditolak
Tulis hal yang sengaja tidak dipakai agar agen tidak mengusulkannya lagi.Aturan data dan domain
Uang, waktu, timezone, ID, soft delete, tenancy.Sumber kebenaran kontrak API
Misalnya OpenAPI spec dan aturan generated code.Tempat kode baru diletakkan
Peta singkat agar logika tidak menyebar.Area yang tidak boleh disentuh
Generated files, modul legacy, migrasi lama.Instruksi saat konflik
Beri tahu agen untuk berhenti dan menandai konflik, bukan mencari workaround.
Template DESIGN.md untuk layanan API pembayaran
Salin template ini, lalu sesuaikan dengan sistem Anda.
# DESIGN.md: Layanan API Pembayaran
File ini mencatat maksud arsitektur dan keputusan desain.
Baca sebelum membuat atau mengubah kode yang menyentuh domain,
kontrak API, transaksi, integrasi eksternal, atau boundary layer.
Jika permintaan bertentangan dengan aturan di sini, berhenti dan
jelaskan konfliknya. Jangan mencari workaround diam-diam.
## Bentuk Sistem
Sistem berlapis. Dependensi hanya mengarah ke dalam:
http -> app -> domain <- infra
- `http/` berisi route, handler, middleware, dan DTO.
- `app/` berisi use case dan port.
- `domain/` berisi entity, value object, dan invariant.
- `infra/` berisi database, queue, gateway client, dan adapter eksternal.
Aturan dependensi:
- `domain/` tidak boleh mengimpor `http/`, `app/`, `infra/`, atau framework.
- `http/` tidak boleh mengakses database atau payment gateway langsung.
- `infra/` mengimplementasikan interface yang didefinisikan di `app/` atau `domain/`.
## Invariant
- Ledger entry immutable setelah ditulis.
- Koreksi ledger harus berupa entry kompensasi baru, bukan update/delete.
- Saldo akun diturunkan dari ledger, bukan field mutable yang di-set langsung.
- Uang selalu integer minor unit plus kode mata uang ISO-4217.
- Jangan pernah gunakan float untuk uang.
- Jangan campur mata uang dalam satu operasi.
- Semua panggilan payment gateway eksternal harus idempotent.
- Retry tidak boleh menyebabkan double-charge.
- Saldo tidak boleh negatif kecuali diotorisasi oleh `OverdraftPolicy`.
## Keputusan utama dan alasan
### Outbox untuk panggilan gateway
Handler tidak memanggil payment gateway secara langsung.
Handler menulis intent ke tabel outbox dalam transaksi database yang sama
dengan perubahan bisnis. Worker membaca outbox dan memanggil gateway.
Alasan:
- Gateway pernah timeout saat load tinggi.
- Panggilan inline memperburuk latency request.
- Failure handling lebih mudah dikontrol di worker.
### Satu jalur tulis untuk ledger
Hanya `Account.post_entry()` yang boleh menulis ke ledger.
Alasan:
- Jalur tulis kedua pernah menyebabkan selisih saldo.
- Semua validasi saldo harus lewat aggregate yang sama.
Tambahkan perilaku baru sebagai method domain, bukan query baru yang
menulis langsung ke ledger.
### Event sourcing hanya untuk ledger
Ledger menggunakan pola event-like append-only.
Bagian lain sistem tetap CRUD.
Alasan:
- Audit trail penuh hanya wajib untuk uang.
- Event sourcing penuh di seluruh sistem terlalu mahal.
## Alternatif yang ditolak
Jangan perkenalkan kembali pola ini:
- ORM lazy-loading lintas aggregate.
- Menyimpan saldo sebagai kolom mutable.
- Menggunakan library `Money` eksternal menggantikan `domain/money.py`.
- Webhook sinkron ke merchant dari request thread.
- Error response ad-hoc per endpoint.
## Aturan data dan domain
- Semua timestamp disimpan sebagai UTC.
- Format keluar API adalah RFC 3339.
- Tidak boleh ada naive datetime melewati boundary fungsi.
- ID adalah ULID yang dibuat di application layer.
- Soft delete tidak digunakan.
- Setiap query multi-tenant harus memiliki `tenant_id`.
- Repository method tanpa tenant scope adalah bug.
## Sumber kebenaran kontrak API
- OpenAPI 3.1 spec di `api/openapi.yaml` adalah otoritatif.
- Request/response type dihasilkan dari OpenAPI.
- Jangan edit file generated di `http/generated/`.
- Endpoint baru atau berubah harus dimulai dari update `api/openapi.yaml`.
- Setelah spec berubah, regenerate type, lalu implementasikan handler.
- Spec didesain dan direview di Apidog sebelum perubahan kode.
- Semua error response menggunakan RFC 9457 `problem+json`.
- Gunakan helper `problem()`, jangan membuat bentuk error sendiri.
## Tempat kode baru diletakkan
- Route baru: `http/routes/`
- DTO: `http/dto/`
- Use case: `app/usecases/`
- Port/interface: `app/ports/`
- Entity/value object: `domain/`
- Database adapter: `infra/db/`
- External client: `infra/clients/`
- Middleware auth/logging/idempotency: `http/middleware/`
## Di luar cakupan / jangan sentuh
- `http/generated/`: generated dari OpenAPI.
- `legacy/billing_v1/`: frozen dan sedang dimigrasi.
- `migrations/`: jangan edit migrasi yang sudah diterapkan; buat migrasi baru.
## Jika ragu
Jika perubahan yang diminta melanggar aturan di atas:
1. Jelaskan aturan mana yang dilanggar.
2. Usulkan alternatif terkecil yang tetap sesuai desain.
3. Jangan mengubah arsitektur diam-diam.
Bagian “Jika ragu” penting. Agen perlu instruksi eksplisit untuk menandai konflik. Tanpa itu, agen cenderung “membantu” dengan workaround yang justru merusak desain.
Cara membuat agen benar-benar membaca DESIGN.md
Agen tidak punya parser khusus untuk DESIGN.md. Mereka membacanya sebagai konteks. Karena itu, Anda harus menghubungkannya dari file yang memang dibaca saat startup.
Untuk Claude Code
Gunakan import di CLAUDE.md:
# CLAUDE.md
@AGENTS.md
@DESIGN.md
Atau, jika DESIGN.md cukup panjang, jangan selalu inject penuh. Referensikan secara eksplisit:
# CLAUDE.md
@AGENTS.md
Sebelum mengubah domain model, kontrak API, transaksi, atau integrasi
eksternal, baca `DESIGN.md`.
Untuk agen yang membaca AGENTS.md
Tambahkan pointer:
# AGENTS.md
## Instruksi desain
Baca `DESIGN.md` sebelum mengubah:
- boundary layer
- kontrak API
- model domain
- transaksi database
- idempotency
- integrasi eksternal
- generated code
Untuk review prompt
Saat meminta agen memperbaiki PR, rujuk aturan spesifik:
Perubahan ini melanggar aturan "http tidak boleh mengakses database langsung"
di DESIGN.md. Refactor agar handler memanggil use case di app/usecases/.
Ini biasanya lebih efektif daripada komentar umum seperti “rapikan arsitekturnya”.
Cara menulis aturan agar dipatuhi agen
Aturan yang samar mudah diabaikan. Tulis sebagai absolute yang bisa diuji.
Kurang efektif:
Sebaiknya gunakan domain layer bila memungkinkan.
Lebih efektif:
`http/` tidak boleh mengimpor repository database.
Handler hanya boleh memanggil use case di `app/usecases/`.
Kurang efektif:
Jaga error response tetap konsisten.
Lebih efektif:
Semua error response harus menggunakan `problem()` dan format RFC 9457
`application/problem+json`. Jangan membuat JSON error ad-hoc.
Kurang efektif:
Hati-hati dengan payment retry.
Lebih efektif:
Semua operasi payment harus menerima `idempotency_key`.
Retry dengan key yang sama harus menghasilkan efek bisnis yang sama dan
tidak boleh melakukan double-charge.
Anti-pola DESIGN.md
1. Mengulang kode
Hindari:
UserService menangani user.
Itu bisa dibaca dari kode. Tulis alasan dan aturan:
Semua perubahan email user harus lewat `User.change_email()` karena method
ini memvalidasi ownership, audit log, dan event domain.
2. Menjadi tutorial kontribusi
DESIGN.md bukan tempat untuk:
npm install
npm run dev
npm test
Instruksi seperti itu masuk ke README.md, CONTRIBUTING.md, atau AGENTS.md.
3. Menulis aspirasi sebagai fakta
Jangan tulis:
Sistem menggunakan CQRS.
Jika kenyataannya hanya sebagian.
Tulis:
Target: semua write path lewat command use case.
Saat ini: `legacy/` masih melewati pola ini. Jangan perluas `legacy/`.
4. Tidak punya pemilik
Tambahkan pemicu review di template PR:
## Checklist desain
- [ ] Apakah PR ini mengubah boundary layer?
- [ ] Apakah PR ini mengubah model domain atau invariant?
- [ ] Apakah PR ini mengubah kontrak API?
- [ ] Apakah PR ini menambah integrasi eksternal?
- [ ] Jika ya, apakah `DESIGN.md` sudah diperbarui?
5. Sinkronisasi terlalu detail
Jangan sinkronkan DESIGN.md baris demi baris dengan kode. Itu akan cepat basi.
Tulis keputusan yang berubah beberapa kali setahun, bukan signature fungsi yang berubah tiap minggu.
6. Bertentangan dengan file instruksi lain
Jika AGENTS.md bilang error memakai problem+json, tetapi DESIGN.md memberi format lain, agen akan memilih salah satu secara tidak konsisten.
Pisahkan:
- aturan operasional di
AGENTS.md - aturan arsitektur di
DESIGN.md
Jika harus saling menunjuk, gunakan referensi, bukan duplikasi.
DESIGN.md untuk API dan backend
DESIGN.md paling bernilai di basis kode API dan backend karena banyak aturan penting tidak terlihat dari satu file:
- kontrak API
- transaksi
- idempotency
- consistency boundary
- tenancy
- auth
- error model
- generated code
- integrasi eksternal
1. Nyatakan kontrak API sebagai sumber kebenaran
Contoh:
OpenAPI spec di `api/openapi.yaml` adalah sumber kebenaran.
Request/response type dihasilkan dari spec.
Jangan edit file generated secara manual.
Agen sering “memperbaiki” build dengan mengedit type generated. Aturan ini mencegah itu.
Jika Anda mendesain kontrak terlebih dahulu di Apidog lalu mengekspor OpenAPI ke repo, tulis jalurnya di DESIGN.md. Dengan begitu agen punya target eksplisit, bukan menebak schema.
Untuk konteks lebih lanjut, lihat designing APIs for AI agents.
2. Tetapkan batas transaksi
Contoh:
Panggilan eksternal tidak boleh dilakukan di dalam transaksi database.
Gunakan outbox untuk pekerjaan yang memanggil service eksternal.
Tanpa aturan ini, agen cenderung menulis implementasi inline:
await db.transaction(async tx => {
await tx.orders.insert(order)
await paymentGateway.charge(card) // jangan lakukan ini
})
Arahkan ke pola yang benar:
await db.transaction(async tx => {
await tx.orders.insert(order)
await tx.outbox.insert({
type: "payment.charge_requested",
payload: { orderId: order.id },
})
})
3. Jadikan idempotency sebagai invariant
Contoh:
Endpoint `POST /payments` wajib menerima `Idempotency-Key`.
Retry dengan key yang sama tidak boleh membuat charge baru.
Ini penting untuk payment, order, provisioning, dan operasi yang punya efek samping mahal.
4. Kunci model error
Contoh:
Semua error response menggunakan RFC 9457 `problem+json`.
Gunakan helper `problem()`.
Jangan membuat error envelope per endpoint.
Tanpa aturan ini, agen bisa membuat variasi seperti:
{ "error": "Invalid input" }
lalu di endpoint lain:
{ "message": "Invalid input", "code": 400 }
5. Tuliskan aturan tenancy
Contoh:
Semua query data tenant harus memiliki `tenant_id`.
Repository method tanpa tenant scope adalah bug keamanan.
Ini tidak selalu terlihat dari satu query, tetapi sangat penting untuk keamanan.
6. Tentukan aturan versioning
Contoh:
Menghapus field response, mengganti nama field, atau mengubah semantic field
adalah breaking change dan harus masuk versi API baru.
Agen sering menganggap rename field sebagai refactor kecil. Untuk API publik, itu breaking change.
Jika Anda ingin agen juga menjalankan dan memvalidasi API yang dibuatnya, kombinasi kontrak OpenAPI + DESIGN.md memberi target yang jelas. Unduh Apidog untuk workflow design-first, ekspor OpenAPI, dan validasi endpoint terhadap kontrak.
Checklist implementasi cepat
Tambahkan DESIGN.md dalam 30–60 menit dengan langkah berikut.
1. Buat file di root repo
touch DESIGN.md
2. Isi hanya keputusan yang paling sering dilanggar
Mulai dari 10–20 aturan. Jangan langsung menulis dokumen panjang.
Prioritaskan:
- layer boundary
- data invariant
- API contract source of truth
- transaction boundary
- idempotency
- generated code
- area legacy
3. Referensikan dari AGENTS.md
## Desain sistem
Baca `DESIGN.md` sebelum mengubah domain, kontrak API, transaksi,
integrasi eksternal, atau struktur layer.
4. Referensikan dari CLAUDE.md
@AGENTS.md
Untuk perubahan arsitektur, baca `DESIGN.md` terlebih dahulu.
5. Tambahkan checklist PR
- [ ] Perubahan ini tidak melanggar `DESIGN.md`
- [ ] Jika mengubah keputusan desain, `DESIGN.md` diperbarui
6. Gunakan saat review agen
Komentari pelanggaran dengan referensi eksplisit:
Ini melanggar invariant idempotency di DESIGN.md.
Tambahkan Idempotency-Key handling sebelum memanggil use case payment.
Kesimpulan
-
DESIGN.mdmencatat mengapa sistem dibentuk seperti itu. - File ini melengkapi
AGENTS.mddanCLAUDE.md, bukan menggantikannya. - Tulis aturan sebagai invariant yang jelas, bukan prosa arsitektur panjang.
- Untuk backend dan API, masukkan kontrak OpenAPI, transaksi, idempotency, tenancy, dan error model.
- Referensikan
DESIGN.mddari file instruksi agen agar terbaca saat dibutuhkan. - Tambahkan pemilik dan checklist PR agar dokumen tidak basi.
- Untuk API, kemenangan terbesar adalah menjadikan OpenAPI spec sebagai sumber kebenaran sehingga agen tidak menciptakan schema sendiri.
- Rancang kontrak terlebih dahulu. Unduh Apidog untuk mendesain API secara design-first, mengekspor OpenAPI, dan menguji endpoint yang dihasilkan agen terhadap kontrak.
FAQ
Apakah DESIGN.md standar resmi seperti AGENTS.md?
Tidak. AGENTS.md adalah format yang didefinisikan dan banyak diadopsi. DESIGN.md adalah konvensi komunitas, mirip ARCHITECTURE.md atau ADR. Gunakan sebagai pola praktis, bukan standar formal.
Apakah saya tetap butuh DESIGN.md jika sudah punya AGENTS.md?
Ya, jika sistem Anda punya aturan arsitektur yang tidak jelas dari kode.
AGENTS.md sebaiknya tetap pendek dan operasional. DESIGN.md menyimpan alasan desain yang lebih dalam. Untuk file operasional, lihat cara menulis file AGENTS.md.
Apa bedanya DESIGN.md dengan ARCHITECTURE.md?
Keduanya mirip. Perbedaannya terutama audiens dan gaya.
ARCHITECTURE.md biasanya memetakan basis kode untuk manusia. DESIGN.md ditulis agar juga efektif untuk agen: lebih deklaratif, fokus pada invariant, keputusan, dan hal yang tidak boleh dilakukan.
Berapa panjang ideal DESIGN.md?
Cukup panjang untuk menutup kesalahan yang sering dibuat agen, cukup pendek agar dibaca.
Dua sampai empat halaman aturan padat biasanya lebih baik daripada narasi lima belas halaman.
Bagaimana cara memastikan agen membaca DESIGN.md?
Referensikan dari file yang sudah dibaca agen:
# AGENTS.md
Baca `DESIGN.md` sebelum perubahan arsitektur.
Untuk Claude Code:
# CLAUDE.md
@AGENTS.md
@DESIGN.md
Atau referensikan secara selektif jika file terlalu panjang.
Apakah agen akan selalu mengikuti DESIGN.md?
Tidak. File instruksi adalah konteks, bukan enforcement keras.
Karena itu:
- tulis aturan sebagai absolute
- buat invariant yang mudah diuji
- tambahkan instruksi “jika konflik, berhenti dan jelaskan”
- gunakan review untuk menunjuk aturan yang dilanggar
Apakah DESIGN.md membantu kontrak API?
Sangat membantu.
Tulis bahwa OpenAPI spec adalah sumber kebenaran, sebutkan path file, dan larang edit manual pada generated type. Jika kontrak didesain di Apidog, agen punya target eksplisit yang bisa diikuti.
Di mana DESIGN.md sebaiknya diletakkan?
Di root repo, sejajar dengan:
README.md
AGENTS.md
CLAUDE.md
DESIGN.md
Untuk monorepo, gunakan:
DESIGN.md # aturan sistem umum
services/payments/DESIGN.md
services/billing/DESIGN.md
packages/shared/DESIGN.md
Root file berisi aturan lintas sistem. File lokal berisi aturan khusus package atau service.
Top comments (0)