Flat Layered Architecture
On this page
- Sekilas
- Gagasannya secara singkat
- A. Satu folder per jenis, satu tingkat
- B. Satu jalur pendek untuk tiap request
- C. Satu file per operasi
- D. Panggilan konkret, tanpa port
- Kenapa saya memilihnya
- Kesepakatan saya
- Cara saya memeriksa module
- Di mana saya tidak memakainya
- Ketika module tumbuh dan harus bertahan lama
- Rujukan
- Istilah
Flat Layered Architecture adalah bentuk yang saya berikan pada service Rust ketika ia harus cepat ada: satu folder per jenis kode, semuanya di satu tingkat, dan request yang selalu menempuh jalur pendek yang sama dari route ke controller ke service ke repository. Ini kesepakatan saya sendiri, bukan rancangan dari buku, walau mirip dengan pola-pola yang sudah dinamai orang lain sebelum saya.
Ini salah satu arsitektur yang saya pakai, dan bagi saya ia tahap, bukan tujuan akhir. Module sering dimulai datar supaya bisa bekerja secepat mungkin; ketika ia tumbuh dan harus dirawat bertahun-tahun, saya merefactor-nya ke Clean Architecture.
Sekilas
| Asal | Kesepakatan saya sendiri; dekat dengan layered architecture klasik ditambah transaction script |
|---|---|
| Saya pakai untuk | Versi pertama service Rust yang harus cepat bekerja, dan pekerjaan bersama AI agent lokal yang jendela konteksnya kecil |
| Tak saya pakai untuk | Module yang sudah tumbuh dan akan dirawat bertahun-tahun |
| Bahasa | Kini Rust; dulu juga Node.js dan Python, yang sudah saya migrasikan ke Clean Architecture |
Gagasannya secara singkat
A. Satu folder per jenis, satu tingkat
Source tree-nya punya satu folder untuk tiap jenis kode — route, controller, service, repository, entity, validasi, middleware, infrastruktur, utilitas — dan semuanya langsung berada di bawah root source. Tak ada yang bersarang lebih dalam, kecuali satu jenis memang punya varian, misalnya repository untuk database tertentu.
B. Satu jalur pendek untuk tiap request
Request selalu menempuh jalan yang sama: route memetakan path ke controller, controller memvalidasi input lalu memanggil service, dan service memanggil repository yang dibutuhkannya. Tak ada yang memanggil mundur di sepanjang jalur itu.
C. Satu file per operasi
Tiap file service memegang satu operasi sistem — menambah user, membuat laporan — yang dibuka sebagai satu fungsi bernama execute. Nama file-nya adalah nama operasinya, jadi daftar file di folder service adalah daftar kemampuan sistem itu.
D. Panggilan konkret, tanpa port
Service memanggil fungsi repository secara langsung, tanpa interface di antaranya. Itulah hal utama yang dilepas arsitektur ini dibandingkan Clean Architecture: logika bisnisnya tahu database mana yang diajaknya bicara. Sebagai gantinya, tiap panggilan bisa diikuti dengan langsung melompat ke definisinya.
Kenapa saya memilihnya
Kadang saya harus membangun dengan cepat. Clean Architecture adalah struktur jangka panjang yang lebih baik, tapi ia meminta lebih banyak sebelum request pertama terjawab: lebih banyak folder, lebih banyak interface, lebih banyak perangkaian. Ketika service harus segera bekerja, struktur datar membiarkan saya menghabiskan waktu itu untuk operasinya sendiri.
Lebih sedikit file module dan relasi yang lebih dangkal. Dalam gaya module yang saya pakai di Rust, tiap folder butuh file mod.rs sendiri untuk mendeklarasikan isinya, dan pohon lapisan serta sub-lapisan yang dalam berarti banyak file semacam itu, dengan relasi yang bercabang ke beberapa arah. Folder satu tingkat menjaga file-file itu tetap sedikit dan pendek, dan menjaga tiap relasi tetap satu langkah.
Muat di jendela konteks AI agent lokal. Ketika saya tak bisa memakai AI agent online dan harus bekerja dengan agent yang berjalan di mesin saya sendiri, jendela konteksnya — banyaknya teks yang bisa ia pegang sekaligus — kecil. Dengan Clean Architecture, sebagian besar jendela itu habis hanya untuk menggambarkan strukturnya. Dengan struktur datar, agent bisa melihat seluruh bentuk service dan masih punya ruang untuk kode yang sedang diubahnya.
Mudah direfactor belakangan. Karena tiap operasi sudah tinggal di file-nya sendiri, masing-masing sudah dekat dengan apa yang disebut use case di Clean Architecture. Pindah ke Clean Architecture sebagian besar soal memindahkan file dan menaruh interface di antara operasi dan database, bukan memikirkan ulang logikanya.
Kesepakatan saya
Service Rust saya yang mengikuti arsitektur ini punya pohon seperti berikut:
Service adalah satu operasi, dan ia memanggil langsung fungsi repository yang dibutuhkannya:
// services/add_item.rs
pub async | Folder | Boleh memanggil | Tak boleh |
|---|---|---|
routes/ | controller | berisi logika |
controllers/ | validasi, service | menyentuh database |
services/ | repository, entity, utilitas | tahu soal HTTP |
repositories/ | database | berisi keputusan bisnis |
Dua folder opsional muncul bila service membutuhkannya: queues/ dan workers/ untuk pekerjaan di latar belakang, serta ffi/ ketika service memanggil library yang ditulis dengan C.
Cara saya memeriksa module
Inilah pertanyaan yang saya ajukan kepada module datar, diambil dari kesepakatan di atas:
| Bidang | Pertanyaan yang saya ajukan | Sumber |
|---|---|---|
| Kedalaman | Apakah tiap folder langsung di bawah root source, dengan sarang hanya untuk varian sungguhan? | satu tingkat |
| Operasi | Apakah tiap operasi adalah file service tersendiri dengan satu fungsi execute? | satu file per operasi |
| Arah | Apakah tiap panggilan berjalan dari route ke controller ke service ke repository, tak pernah mundur? | tabel panggilan |
| Controller | Apakah controller menjauh dari database? | tabel panggilan |
| Route | Apakah route hanya memetakan path ke controller? | tabel panggilan |
| Kesiapan tumbuh | Bisakah tiap service dipindah menjadi use case tanpa menulis ulang logikanya? | jalur refactor |
Di mana saya tidak memakainya
Ketika module tumbuh dan harus bertahan lama
Begitu module memegang banyak operasi, dipakai bersama sistem lain, atau harus dirawat bertahun-tahun, jalan pintasnya berhenti membayar. Logika bisnis yang tahu database-nya jadi lebih sulit diuji dan diubah, dan di situlah saya merefactor-nya ke Clean Architecture. Karena struktur datar dibangun dengan perpindahan ini dalam pikiran, folder-folder-nya langsung terpetakan ke folder baru:
| Folder datar | Menjadi di Clean Architecture |
|---|---|
services/ | application/use_cases/, dikelompokkan per resource |
repositories/ | infrastructure/persistence/, di balik port yang dideklarasikan di application/ports/ |
controllers/, routes/, middlewares/ | interface/http/ |
entities/ | domain/ |
validations/ | application/validation/ |
utils/ | shared/ |
infrastructures/ | infrastructure/ |
Selama perpindahan, versi datar dan versi hasil refactor berdampingan di repository, jadi module selalu punya versi yang bekerja.
Rujukan
Arsitektur ini kesepakatan saya sendiri dan tak punya buku di belakangnya. Kerabat terdekatnya yang sudah bernama dijelaskan Martin Fowler dalam Patterns of Enterprise Application Architecture (Addison-Wesley, 2002): Transaction Script, ketika tiap operasi adalah satu prosedur yang mengerjakan seluruh pekerjaannya sendiri, dan Service Layer, yang mengumpulkan operasi-operasi itu di balik satu batas. Keduanya saya sebut sebagai kemiripan, bukan sumber.
Istilah
- Layer
- Sekelompok kode dengan satu jenis tanggung jawab, misalnya menangani request atau menjangkau database.
- Transaction script
- Cara menyusun logika ketika tiap operasi sistem adalah satu prosedur yang mengerjakan seluruh pekerjaannya dari awal sampai akhir.
- Repository
- Kode yang membaca dan menulis satu jenis data di database, supaya bagian program lainnya tak menulis query sendiri.
mod.rs- File di folder Rust yang mendeklarasikan module apa saja yang ada di folder itu. Dalam gaya module yang saya pakai, tiap folder kode punya satu.
- Jendela konteks
- Banyaknya teks yang bisa dipegang dan diolah model AI sekaligus. Model lokal biasanya punya jendela yang jauh lebih kecil daripada model online.
Technologies covered
- Rust