Skip to content
Architecture Programming

Flat Layered Architecture

By Romi Fadli, S.Kom. 7 min read Software Design
On this page
Four flat layers stacked on the left — routes, controllers, services, repositories — joined by short lines, with an orange line labelled later: refactor leading to the concentric circles of Clean Architecture on the right

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

AsalKesepakatan saya sendiri; dekat dengan layered architecture klasik ditambah transaction script
Saya pakai untukVersi pertama service Rust yang harus cepat bekerja, dan pekerjaan bersama AI agent lokal yang jendela konteksnya kecil
Tak saya pakai untukModule yang sudah tumbuh dan akan dirawat bertahun-tahun
BahasaKini Rust; dulu juga Node.js dan Python, yang sudah saya migrasikan ke Clean Architecture
Posisi Flat Layered Architecture di antara arsitektur yang saya pakai.

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:

bash
src/
├── routes/           # path → controller, tak lebih
├── controllers/      # baca request, validasi, panggil satu service, susun response
├── services/         # satu file per operasi, masing-masing membuka execute()
├── repositories/     # akses database, dikelompokkan per database: postgresql/
├── entities/         # bentuk data yang dipakai bersama service dan repository
├── validations/      # aturan input, satu file per resource
├── middlewares/      # penanganan request lintas fitur: auth, logging, header
├── infrastructures/  # pool database, HTTP server, telemetry, konfigurasi
├── utils/            # helper kecil tanpa makna bisnis
└── main.rs           # merangkai server, route, dan middleware

Service adalah satu operasi, dan ia memanggil langsung fungsi repository yang dibutuhkannya:

rust
// services/add_item.rs
pub async fn execute(db: &DatabaseConnection, name: String) -> Result<String, String> {
    let item = ItemEntity { id: None, name };
    item_postgres_repository::create(db.get_pool(), &item)
        .await
        .map_err(|e| format!("Failed to create item: {}", e))
}
FolderBoleh memanggilTak boleh
routes/controllerberisi logika
controllers/validasi, servicemenyentuh database
services/repository, entity, utilitastahu soal HTTP
repositories/databaseberisi keputusan bisnis
Arah panggilan di antara folder-folder utama.

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:

BidangPertanyaan yang saya ajukanSumber
KedalamanApakah tiap folder langsung di bawah root source, dengan sarang hanya untuk varian sungguhan?satu tingkat
OperasiApakah tiap operasi adalah file service tersendiri dengan satu fungsi execute?satu file per operasi
ArahApakah tiap panggilan berjalan dari route ke controller ke service ke repository, tak pernah mundur?tabel panggilan
ControllerApakah controller menjauh dari database?tabel panggilan
RouteApakah route hanya memetakan path ke controller?tabel panggilan
Kesiapan tumbuhBisakah tiap service dipindah menjadi use case tanpa menulis ulang logikanya?jalur refactor
Pertanyaan yang saya pakai untuk memeriksa module datar.

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 datarMenjadi 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/
Cara tiap folder datar berpindah ketika module direfactor.

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

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

Three identical empty application nodes stacked as copies, connected to a database, a cache, object storage, a pool of queue workers, and a telemetry collector that hold all the state

Cara saya menjalankan Laravel sebagai inti platform yang dibangun tim: node tanpa state, pekerjaan lambat diserahkan ke job dan service khusus, file dijauhkan dari server, satu token internal untuk setiap login, dan setiap request, job, serta panggilan bisa diamati.

13 min read Read more →
One endpoint procedure as a column of ordered steps, a request entering at the top and the answer leaving at the bottom, each step calling its own concern module beside it

Tiap endpoint adalah satu prosedur yang membawa request dari awal sampai selesai, dengan urusannya di module di sampingnya — Transaction Script Fowler dalam bentuk yang saya berikan pada service Python kecil bertugas tunggal, dibundel jadi satu binary, sampai aturannya tumbuh.

8 min read Read more →
PDF pages of different sizes passing through a renderer that reads from and writes back to storage, producing thumbnails of one uniform size

Service Rust tanpa state yang merender halaman pertama PDF menjadi thumbnail WebP yang tajam dan berukuran seragam, membaca PDF dan menulis gambarnya lewat dua presigned link, sehingga tak ada aplikasi yang mengalirkan file atau memegang kredensial storage.

8 min read Read more →
A document's fingerprint passing through a clock, signed by a certificate below it, and returning as a time seal stamped on the same document

Timestamp authority berbahasa Rust yang menstempel hash dokumen dengan waktu bertanda tangan berbasis NTP sesuai RFC 3161, supaya tanda tangan PDF tetap bisa diverifikasi setelah sertifikatnya kedaluwarsa — menerbitkan sendiri sertifikat penanda tangannya, dengan OCSP dan CRL publik.

9 min read Read more →
A chain of three certificates, root to issuing CA to signer, signing a PDF, while an OCSP answer and a revocation list from the issuing CA reach a validator shield alongside the document

Issuing certificate authority berbahasa Rust yang memberi tiap penanda tangan PDF sertifikatnya sendiri, lalu menjawab pertanyaan setiap pembaca — masih sahkah? — lewat OCSP responder dan daftar pencabutan publik, serta menyerahkan key dan rantainya sebagai PKCS#12.

11 min read Read more →
Four concentric circles with arrows coming in from a database, a browser window, and a message queue, all pointing toward the centre.

Clean Architecture

Programming

Clean Architecture adalah salah satu arsitektur yang saya pakai: kenapa saya memilihnya, kesepakatan folder dan penamaan yang diikuti tiap module saya di Rust, Python, Dart, Node.js, dan C, pertanyaan yang saya pakai untuk memeriksa module, dan tempat saya memakai arsitektur lain.

12 min read Read more →

Related Articles