PlantUML ERD

This commit is contained in:
Dita Aji Pratama 2026-10-11 22:36:16 +07:00
parent d63aa0aa4e
commit f9ba636837
2 changed files with 330 additions and 0 deletions

View File

@ -0,0 +1,324 @@
# ERD / PlantUML / Buat
Berikut panduan cara membuat ERD dengan PlantUML.
Acuan format standarnya adalah `plan/milestone/moop.puml` (sumber) dan
`plan/milestone/moop.png` (hasil render). Ikuti gaya itu persis — header-nya sama,
tata letak entity-nya sama, cara menulis constraint dan relasinya juga sama.
Yang perlu disiapkan:
- prefix modul/domain (misal `moop`, `chat`, `memories`) — dipakai sebagai prefix nama tabel/entity
- daftar tabel beserta kolomnya
- relasi antar tabel (siapa mereferensi siapa, wajib atau boleh kosong)
- direktori tujuan untuk pasangan file `.puml` + `.png` (bawaan: `plan/milestone/`)
Bahasa penulisan: **isi diagram tetap bahasa Inggris** (nama tabel, tipe, dan teks
constraint seperti `on update cascade` / `null priority = disabled`) supaya konsisten
dengan skema SQL dan `moop.puml`. Penjelasan di dokumen ini saja yang bahasa Indonesia.
## Penamaan file dan lokasinya
- satu diagram = satu pasangan file: `<name>.puml` dan `<name>.png`
- file `.puml` dan `.png` diletakkan berdampingan di direktori yang sama
- nama diagram mengikuti nama fitur/modul, kebab-case atau huruf kecil biasa
(contoh: `moop.puml`, `2-basic-features.puml`)
- jangan pernah men-commit `.puml` tanpa `.png` hasil render-nya, dan render ulang setiap
kali `.puml` diubah
## Header wajib
Setiap ERD dibuka persis seperti ini. Tanpa judul, tanpa legenda, tanpa blok penulis:
```plantuml
@startuml
hide circle
skinparam linetype ortho
...
@enduml
```
- `hide circle` menghapus lingkaran actor di depan nama entity.
- `skinparam linetype ortho` membuat garis relasi menyiku/lurus supaya diagram tetap enak dibaca.
- sisakan satu baris kosong setelah `@startuml` dan sebelum `@enduml`.
- jangan menambah `skinparam` lain, warna, atau blok `<style>`. File ERD tetap polos.
## Kelompokkan tabel dengan package
Cluster entity berdasarkan sub-domain memakai `package <nama> { ... }` (huruf kecil,
sedapatnya satu kata, contoh: `option`, `provider`). Semua entity dalam satu ranah
dimasukkan ke package yang sama, dan relasi ranah itu juga ditulis di dalam blok yang
sama. Antar package dipisah satu baris kosong.
```plantuml
package provider {
...entity dan relasi milik domain provider...
}
```
## Blok entity
Nama entity adalah nama tabelnya, apa adanya: berprefix nama modul, `snake_case`,
tunggal atau jamak mengikuti skema aslinya (contoh: `moop_model_set`, `moop_api`).
```plantuml
entity moop_model {
id : binary(40) <<PK>>
api : binary(40) <<FK>>
set : binary(40) <<FK>>
model : longtext
priority : int null
--
* api = moop_api.id
on update cascade
on delete cascade
* set = moop_model_set.id
on update cascade
on delete cascade
* null priority = disabled
}
```
Aturannya:
- isi entity di-indent 4 spasi (kalau di dalam package: 8 spasi untuk atribut).
- satu atribut per baris, formatnya `nama : tipe` diikuti penanda stereotype.
- `--` memisahkan bagian kolom dari bagian constraint/aturan. Selalu tulis pemisahnya,
walaupun di bawahnya tidak ada apa-apa.
- tidak ada baris kosong di dalam blok entity.
- jangan pakai ekstensi `<<primary key>>`/`{mask}` dari PlantUML, jangan pakai warna,
jangan pakai alias (`entity "foo" as f`). Nama di sebelah kiri ya nama tabel aslinya.
### Sintaks atribut
| Kondisi | Sintaks | Contoh |
|---|---|---|
| primary key | `id : binary(40) <<PK>>` | selalu baris pertama, selalu `binary(40)` |
| foreign key | `api : binary(40) <<FK>>` | kolom FK juga `binary(40)` |
| kolom unique | `type : binary(40) unique <<FK>>` | `unique` ditulis tepat setelah tipe |
| boleh null | `priority : int null` | `null` ditulis setelah tipe |
| teks | `name : longtext` | |
| angka | `priority : int` | |
Tipe ditulis sebagai tipe SQL (`binary(40)`, `longtext`, `int`), bukan tipe Python.
Kunci surrogate dan semua referensi ke baris tabel lain memakai `binary(40)`.
### Bagian constraint
Setiap baris di bawah `--` diawali `* `. Aturan yang panjangnya lebih dari satu baris
dilanjutkan di baris berikutnya dengan indent lebih dalam, tanpa mengulang tanda `*`.
Tulis di sini, berurutan seperti ini:
1. Tujuan FK: `* <kolom> = <tabel_induk>.id`, lalu `on update cascade` dan
`on delete cascade` masing-masing di baris sendiri.
2. Aturan unique/komposit: `* unique(set, type)`.
3. Integritas antar kolom: `* set_type.type must equal type`.
4. Nilai yang diizinkan: `* type availability =` lalu daftar nilainya di baris
berikutnya, diberi kutip dan dipisah pipe — `"llm" | "embedding" | "image generation"`.
5. Catatan bisnis/semantik dengan kalimat biasa: `* null priority = disabled`,
`* empty key = no bearer`, `* one default set per model type`.
Bagian constraint inilah yang membuat diagram bisa di-review tanpa membaca SQL-nya.
Kalau ada aturan di skema atau di spesifikasi produk, tempatnya ada di sini.
## Relasi
Tulis setiap relasi di baris sendiri, tepat setelah blok entity yang memilikinya
(entity anak yang punya kolom FK, atau induknya kalau relasi itu memang dikenalkan dari
sana). Tanpa label, tanpa klausa `to`, tanpa baris kosong antara entity dan relasinya.
```plantuml
moop_model_set ||--|{ moop_model_set_type
moop_model_type ||-o{ moop_model_set_type
moop_model_type ||--|| moop_model_type_default
moop_model_set_type ||--o| moop_model_type_default
moop_api ||--o{ moop_key
```
Kardinalitas selalu `induk <--> anak`, memakai notasi crow-foot:
| Kiri (induk) | Arti | Kanan (anak) | Arti |
|---|---|---|---|
| `\|\|` | tepat satu | `\|\|` | tepat satu |
| `\|\|` | tepat satu | `\|{` | satu atau lebih |
| `\|\|` | tepat satu | `o{` | nol atau lebih |
| `\|\|` | tepat satu | `o\|` | nol atau satu |
Simbolnya: `||` = wajib satu, `o|` = opsional satu, `|{` = satu atau lebih, `o{` = nol atau lebih.
**Jumlah dash bukan kardinalitas.** Yang membawa makna hanya karakter di kedua ujungnya
(`|`, `o`, `{`, `}`). Jumlah `-` adalah **knob tata letak yang mengatur orientasi**:
| Sintaks | Efek (sudah diverifikasi ke jar-nya) |
|---|---|
| `\|-` (satu dash) | **horizontal** — kedua entity berdampingan, edge-nya tidak menambah rank |
| `\|--` (dua dash) | **vertikal** — entity anak ditumpuk di bawah induknya. Ini bawaannya |
| `\|---`, `\|----` | tetap vertikal, cuma lebih panjang. Pakai saat garis saling menutupi |
Diukur pada rantai 3 entity yang identik: satu dash merender 543×95 (a→b→c berjajar),
dua dash merender 171×349 (a→b→c menurun). Menambah dash ke tiga atau empat hanya
menambah tinggi (275 → 336 px), tidak pernah mengubah orientasi.
Ini disengaja di `moop.puml`: `moop_model_type ||-o{ moop_model_set_type` memakai
**satu** dash supaya `moop_model_type` jatuh ke sebelah *kiri* tabel junction, sedangkan
semua relasi induk→anak memakai **dua** dash supaya diagram tetap mengalir dari atas ke bawah.
Jadi aturannya:
- pakai `--` sebagai default untuk semua relasi — diagramnya terbaca sebagai hierarki vertikal
- pindah ke satu `-` hanya kalau dua tabel memang perlu berdampingan agar render tidak kusut
- jangan pernah memakai jumlah dash untuk arti "kuat vs lemah" atau "identifying vs
non-identifying". Makna itu tidak ada, dan pembaca akan salah tafsir
- jangan pakai `..` (garis putus-putus). Hasilnya garis dashed dan tidak dipakai sama sekali
di format standar ini
## Menentukan posisi entity (mana yang atas/bawah, mana yang kiri/kanan)
Sisi penulisan entity pada baris relasi **adalah** posisinya. Entity di sisi kiri akan
ditempatkan **di atas** (relasi vertikal) atau **di sebelah kiri** (relasi horizontal)
dari entity di sisi kanan. Sudah diverifikasi ke jar-nya:
| Baris relasi | Hasil |
|---|---|
| `induk \|--\|{ anak` | `induk` di atas, `anak` di bawah |
| `anak }--\|\| induk` | `anak` di atas, `induk` di bawah (terbalik) |
| `induk \|-o{ anak` | `induk` di kiri, `anak` di kanan |
| `anak }o-\|\| induk` | `anak` di kiri, `induk` di kanan (terbalik) |
Dua konsekuensi yang penting:
1. **Urutan deklarasi `entity` di file tidak berpengaruh.** Menulis `anak` sebelum
`induk` tetap merender `induk` di atas, selama baris relasinya `induk ||--|{ anak`.
Jangan memperbaiki posisi dengan memindah-mindah blok entity — yang menggeser posisi
hanya baris relasi.
2. **Menulis barisnya terbalik akan membalik diagram dan merusak notasi.** Pertahankan
induk di sisi kiri, supaya crow's foot jatuh di sisi anak. Kalau karena alasan tata
letak anak harus muncul di atas induk, pindahkan *package/entity*-nya dan tetap tulis
`induk <--> anak`, jangan membalik baris relasinya.
Resep praktis supaya hasilnya enak dilihat:
- gambar hierarki utama dari atas ke bawah dengan `--`, induk selalu di kiri
- untuk tabel junction, pisahkan dua induknya: induk utama di atasnya dengan `||--|{`,
induk lookup di sampingnya dengan `||-o{` (satu dash) supaya induk itu jatuh ke kiri,
bukan menumpuk — persis kasus `moop_model_type`
- kalau dua entity sibling saling desak, beri satu dash tambahan pada salah satu
relasinya (`||---|{`) untuk membuka ruang vertikal
- kalau rantai tabelnya terlalu panjang sampai diagramnya terlalu tinggi, pecah per
sub-domain ke `package` sendiri — bukan dengan membalik baris relasi
Cek konsistensi yang wajib dilakukan:
- kalau kolom FK-nya `NOT NULL`, sisi anak harus memakai `|` (`||--|{`, `||--o|`)
- kalau kolom FK-nya nullable/opsional, sisi anak memakai `o` (`||-o{`, `||--o|`)
- kolom FK yang `UNIQUE` berarti relasi 1-ke-1 (`||--||` atau `||--o|`), bukan 1-ke-banyak
- setiap `<<FK>>` yang dideklarasikan di entity harus punya baris relasi yang cocok di
suatu tempat, dan setiap baris relasi harus punya kolom FK yang cocok. Tidak boleh ada
relasi yatim, tidak boleh ada FK yang tidak terdokumentasi.
## Relasi antar package
Kalau sebuah tabel di satu package mereferensi tabel di package lain, tetap deklarasikan
FK-nya di dalam entity anak dan gambar relasinya dari sisi anak, di dalam blok package
anaknya. Jangan membuat bagian "relations" terpisah di bawah file.
## Urutan penulisan
Aturan di bagian ini hanya untuk **keterbacaan sumbernya** — tidak mengubah tata letak
hasil render. Posisi ditentukan oleh baris relasi (lihat "Menentukan posisi entity").
- package: dari domain yang paling mandiri ke yang paling bergantung
- di dalam package: tabel induk dulu, lalu tabel junction/anaknya, lalu tabel yang
bergantung pada tabel-tabel itu
- atribut: `id` dulu, lalu FK dan kolom data, lalu kolom nullable/opsional paling akhir
## Render
Menggunakan jar yang sudah tersedia di `plan/milestone/`:
```bash
cd plan/milestone && java -jar plantuml-mit-1.2026.8.jar -tpng -charset UTF-8 moop.puml -o .
```
- `-tpng` untuk keluaran PNG, `-charset UTF-8` supaya nilai enum yang dikutip tidak rusak
- `-o <dir>` menulis gambar di samping file sumbernya
- setelah render, buka `.png`-nya dan pastikan: diagramnya mengalir atas ke bawah, tidak
ada kotak yang tumpang tindih, tidak ada garis bersilangan yang sebenarnya bisa dihindari
dengan memindah satu relasi ke satu dash (berdampingan) atau menambah dash untuk membuka
ruang vertikal, setiap entity berada di tepat satu package, dan setiap relasi benar-benar
tersambung. Ingat: memindah blok `entity` di sumber **tidak** menggeser apa pun — yang
menggeser hanya baris relasi
## Template minimal
```plantuml
@startuml
hide circle
skinparam linetype ortho
package <domain> {
entity <prefix>_parent {
id : binary(40) <<PK>>
name : longtext
--
}
entity <prefix>_type {
id : binary(40) <<PK>>
type : longtext
--
* type availability =
"a" | "b" | "c"
}
entity <prefix>_parent_type {
id : binary(40) <<PK>>
parent : binary(40) <<FK>>
type : binary(40) <<FK>>
--
* parent = <prefix>_parent.id
on update cascade
on delete cascade
* type = <prefix>_type.id
on update cascade
on delete cascade
* unique(parent, type)
}
<prefix>_parent ||--|{ <prefix>_parent_type
<prefix>_type ||-o{ <prefix>_parent_type
entity <prefix>_child {
id : binary(40) <<PK>>
parent : binary(40) <<FK>>
priority : int null
--
* parent = <prefix>_parent.id
on update cascade
on delete cascade
* null priority = disabled
}
<prefix>_parent ||--o{ <prefix>_child
}
@enduml
```
## Checklist review
- [ ] `@startuml` / `hide circle` / `skinparam linetype ortho` / `@enduml`, tidak ada yang lain di header
- [ ] semua entity dikelompokkan dalam `package`, tidak ada entity mengambang
- [ ] nama entity = nama tabel asli, berprefix modul, snake_case
- [ ] `id : binary(40) <<PK>>` jadi atribut pertama di setiap entity
- [ ] pemisah `--` ada di setiap entity
- [ ] kolom FK ditandai `<<FK>>` dan diulang di bagian constraint beserta aturan cascade-nya
- [ ] nilai enum/domain diberi kutip dan dipisah pipe
- [ ] setiap relasi di baris sendiri, tepat setelah entity-nya, induk di sisi kiri
- [ ] kardinalitas cocok dengan nullability dan uniqueness FK
- [ ] jumlah dash hanya dipakai sebagai knob tata letak (`-` = berdampingan, `--` = menumpuk), bukan sebagai makna. Tanpa `..`, tanpa tafsir "kuat vs lemah"
- [ ] induk selalu ditulis di sisi kiri setiap baris relasi, supaya dia yang muncul di atas / di kiri
- [ ] `.png` hasil render ada di samping `.puml` dan hasilnya bersih secara visual

6
instruction/erd/erd.md Normal file
View File

@ -0,0 +1,6 @@
# ERD
Berikut daftar instruction yang bisa kamu panggil.
Pembuatan Diagram
- `erd-plantuml-create.md` Membuat ERD (Entity Relationship Diagram) dengan PlantUML sesuai format standar