Bagi kita yang belajar arsitektur kode, pasti sudah tidak asing lagi dengan gambar ini:

Nah, saat mencoba menerapkannya di kode (di sini kita pakai contoh Golang), penerapan yang paling masuk akal menurutku adalah sebagai berikut:
init.go

handler.go

usecase.go

repository.go

Jadi dependency flow-nya adalah Http Handler → Usecase → Repository.
Dari sini aku menyimpulkan bahwa flow-nya sudah sesuai dengan Clean Architecture (aku beri warna merah supaya lebih jelas):

Keterangan:
- Aku mengartikan panah hitam (dari kiri ke kanan) sama dengan panah merah (dari atas ke bawah).
- Flow tersebut bisa dibaca seperti ini: dari Web (REST API) → Controller (route handler) → Usecase (business logic) → Entities (Repository).
Tapi tunggu dulu, ada yang mengganjal di benakku. Kenapa DB (database) justru berada di sini, di lapisan paling luar? Bukankah seharusnya ada di dalam, bersama entities?

Dari situlah aku menyadari sepertinya aku salah memahami maksud diagram ini.
Setelah menelisik lebih jauh, aku menyimpulkan bahwa flow yang paling masuk akal justru seperti ini:

Nah, kalau begini barulah sesuai dengan flow yang memang bisa diterapkan di kode.
Tapi masih ada yang janggal. Kenapa arah panahnya jadi tidak cocok dengan panah di dokumen asli (panah hitam yang selalu mengarah ke dalam), bahkan sebagian justru berlawanan arah (mengarah ke luar)?
Jawabannya: karena panah merah tadi adalah alur flow of control, bukan dependency.
Sebenarnya flow of control sudah dijelaskan di diagram, tepatnya di pojok kanan bawah. Supaya lebih jelas, flow yang digambarkan panah merah di bagian A dan panah ungu di bagian B berikut ini sebenarnya sama saja.

Tapi justru di sinilah muncul kebingungan lain yaitu adanya dua arah panah yang berbeda di dalam satu gambar. Kalau flow of control itu digambarkan seperti di pojok kanan bawah, lalu apa arti panah (hitam) yang ada di lingkaran-lingkaran itu?
Dan ternyata, apa yang kita implementasikan di kode (dependency antar lapisan yang mengarah ke dalam) adalah cara yang keliru. Lalu bagaimana cara yang benar?
Oke, mari kita bahas Clean Architecture secara tuntas, dengan bahasa sederhana yang (semoga) gampang dipahami.
Sebelum masuk ke detail, kita ingat kembali dulu inti dari Clean Architecture (by Uncle Bob).
Sistem (kode) yang menerapkan prinsip Clean Architecture adalah sistem yang:
- Tidak bergantung pada framework — framework diperlakukan sebagai alat, bukan kerangka yang memaksamu mengikuti aturannya.
- Mudah dites — business rules bisa diuji tanpa UI, database, atau web server.
- Tidak bergantung pada UI — UI bisa diganti (misalnya dari web ke console) tanpa mengubah logika inti.
- Tidak bergantung pada database — bisa berpindah dari SQL ke MongoDB, dll., tanpa menyentuh business rules.
- Tidak tahu apa-apa soal dunia luar — logika bisnis benar-benar terisolasi.
The Dependency Rule

Diagram Clean Architecture digambarkan sebagai lingkaran-lingkaran konsentris (seperti lapisan bawang). Semakin ke dalam, semakin high-level (kebijakan/policy). Semakin ke luar, semakin low-level (mekanisme/detail teknis).
Empat lapisan (dari dalam ke luar):
- Entities — Aturan bisnis paling inti dan paling umum (enterprise-wide). Paling jarang berubah. Misalnya, kalau navigasi halaman atau aturan security berubah, lapisan ini seharusnya tidak terpengaruh sama sekali.
- Use Cases — Aturan bisnis (business logic) yang spesifik untuk aplikasi. Mengatur alur data dari dan ke Entities untuk mencapai tujuan tertentu. Lapisan ini berubah kalau cara kerja aplikasi berubah, tapi tidak terpengaruh perubahan database atau UI.
- Interface Adapters — Penerjemah/konverter data. Mengubah format dari yang nyaman untuk Use Cases & Entities menjadi format yang nyaman untuk dunia luar (database, web). Di sinilah MVC Controller, Presenter, dan Gateways berada.
- Frameworks & Drivers — Lapisan paling luar: database, web framework, dll. Di sini hanya ada sedikit glue code. Prinsipnya: "Web itu detail. Database itu detail." Detail-detail ini sengaja ditaruh di luar supaya tidak banyak merusak inti.
Aturan utamanya: dependency (ketergantungan kode) hanya boleh menunjuk ke dalam. Kode di lingkaran dalam tidak boleh tahu apa pun tentang lingkaran luar — tidak boleh menyebut nama fungsi, class, atau variabel dari luar. Format data dari luar pun tidak boleh dipakai di dalam.
Secara teori sudah cukup jelas, bukan?
Masalahnya, begitu kita coba terapkan di kode, sering kali kita malah dihadapkan pada kebingungan: bagaimana sebenarnya cara menerapkan prinsip-prinsip ini? Agar lebih jelas, langsung saja kita masuk ke contohnya.
Penerapan di Code
Disclaimer — aku hanya akan menampilkan kode inti yang high-level saja, supaya fokus kita tetap pada arsitekturnya.
Sebagai contoh, aku punya sebuah fitur Subject. API yang akan dibuat adalah Create Subject. Ada 3 file kode yang akan aku buat (seperti yang sudah kita lihat di atas), yaitu:
1. Handler (handler.go)
Berisi http routing handler untuk create Subject sekaligus validasi (object parsing dan form field).

2. Use Case (usecase.go)
Berisi business logic untuk menangani logika berdasarkan request dari handler.

3. Repository (repository.go)
Berisi kode query untuk mengakses database.

Sesuai yang aku jelaskan di awal, dependency flow agar program bisa berjalan adalah Http Handler menginject Usecase, dan Usecase menginject Repository. Jadi misalnya Http Handler menerima request create subject dari user, request itu diteruskan ke Usecase untuk menangani logikanya, lalu diteruskan lagi ke Repository untuk menjalankan query create subject di database.
Kode tersebut memang berjalan (works), tetapi ternyata masih melanggar 2 aturan Clean Architecture, yaitu:
- Arah dependency-nya terbalik — Pada kode tersebut, Usecase bergantung (dependency) ke Repository. Ini tidak sesuai dengan panah di diagram Clean Architecture (dependency hanya boleh menunjuk ke dalam). Seharusnya Repository (atau di diagram disebut Gateways) yang bergantung ke Usecase, bukan sebaliknya.
- Inject dependency secara langsung — Pada kode tersebut kita melakukan dependency injection langsung antar lapisan (Http Handler menginject Usecase, Usecase menginject Repository), sekaligus mengakses langsung fungsi Create yang ada di dalam Repository. Padahal di Clean Architecture, kode di lingkaran dalam tidak boleh tahu apa pun tentang lingkaran luar — tidak boleh menyebut nama fungsi, class, atau variabel dari luar.
Lalu apa yang harus kita lakukan agar kode kita sesuai dengan prinsip Clean Architecture? Jawabannya adalah dengan langkah-langkah berikut:
1. Menambahkan Entities (Subject) dan Port (Interface)
Sesuai definisi Entities di teori (aturan bisnis paling inti dan enterprise-wide), Entities di kasus kita adalah objek bisnisnya sendiri, yaitu Subject.
subject.go (Entities — Subject)

Selanjutnya, untuk menghubungkan antar lapisan, kita buat interface yang berperan sebagai port, yaitu Usecase interface dan Repository interface. Perlu digarisbawahi: kedua interface ini adalah port — di diagram digambarkan sebagai Use Case Input Port dan Use Case Output Port (pojok kanan bawah), yang berada di lapisan Use Cases.
model.go

Interface (port) inilah yang nantinya berfungsi untuk menghubungkan antar lapisan.
2. Tidak Menginject Dependency Secara Langsung
Di Usecase, alih-alih menginject Repository secara langsung, lebih baik kita inject Repository interface (port).
usecase.go
Before

After

Begitu juga dengan Http Handler.
handler.go
Before

After

Ide inti dari menginject interface (bukan menginject kode secara langsung) adalah:
Usecase tidak perlu tahu apa itu Repository. Yang penting, Usecase butuh "sesuatu" yang menyediakan Create(ctx context.Context, entity Subject) error — yaitu fungsi Create dengan parameter context dan entity Subject, yang mengembalikan error (atau nil).
Usecase tidak peduli isi Repository itu apa, entah database, mocking, atau apa pun. Selama "sesuatu" itu sesuai dengan interface, maka Usecase menerimanya.
Hal yang sama berlaku untuk Http Handler yang menginject interface Usecase.
Gambar di bawah ini adalah ilustrasi yang sesuai.
Before

After (menggunakan port)

Dengan begini, Repository menjadi fleksibel: bisa diganti dengan implementasi apa pun, selama ia meng-implement Repository interface.

3. Dependency Inversion
Dependency inversion termasuk materi yang dijelaskan di Clean Architecture, tepatnya di bagian "Crossing boundaries". Tapi sebelum ke sana, kita bahas dulu konsep yang berkaitan erat dengannya.
Flow of control
Flow of control (alur kendali) adalah urutan eksekusi program: jalur yang ditempuh saat program berjalan dari satu instruksi/fungsi ke instruksi/fungsi berikutnya, mengikuti pola "siapa memanggil siapa" pada saat runtime.
Pada diagram Clean Architecture, flow of control bisa kita lihat di bagian pojok kanan bawah.

Jadi, dari lapisan Controller menuju Usecase (kalau kita lihat di lapisan bawang, arahnya masuk ke dalam), lalu dari Usecase menuju Presenter (kalau kita lihat di lapisan bawang, arahnya justru keluar). Dari Controller menuju Usecase, jalurnya lewat Usecase Input Port, yaitu interface (port) di lapisan Use Cases. Begitu juga dari Usecase menuju Presenter, lewat Usecase Output Port.
Kita terapkan ke case kita, maka ilustrasinya menjadi sebagai berikut.

Kemudian implementasi flow of control di kode kita adalah menghubungkan Http Handler, Usecase, dan Repository. Untuk kasus kita, tempat paling ideal untuk melakukannya adalah di init.go.
init.go

Kode di atas bisa dibaca sebagai: Http Handler — flow of control menuju → Usecase — flow of control menuju → Repository. Berikut ilustrasi flow of control-nya (fokus ke panah hijau).

Port kita sederhanakan supaya lebih mudah dipahami (port termasuk di lapisan Usecase).

Dengan flow seperti ini, program berjalan sebagaimana mestinya.
Dependency Inversion
Yang perlu kita pahami soal dependency inversion adalah: fokusnya bukan pada "dependency" dalam artian flow of control, melainkan pada arah "ketergantungan saat menulis kode".
Sebelum melanjutkan flow di atas, menurutku kita perlu contoh lain yang berbeda supaya materi Dependency Inversion ini lebih mudah dipahami. Contoh berikut berbeda dengan yang sebelumnya, yaitu tentang Subject Service yang bergantung ke MySQL Database.

Perhatikan panah merah: SubjectService bergantung langsung ke MySQLDatabase. Ilustrasinya seperti ini.

Kalau kita mau kode yang clean, cara ini kurang tepat, karena SubjectService bergantung langsung ke MySQLDatabase. Akibatnya, kalau suatu saat kita ingin pindah ke database management system lain (misalnya MongoDB), maka SubjectService-lah yang harus menyesuaikan diri dengan sistem database yang baru itu.
Solusi idealnya adalah mengubah dependency SubjectService agar mengarah ke sebuah interface yang berperan sebagai port, yaitu SubjectRepository interface.

Gambar di atas menunjukkan SubjectService tidak lagi bergantung langsung ke MySQL, melainkan ke SubjectRepository, yaitu interface (port) yang dibuat "agar MySQL yang menyesuaikan diri dengan kebutuhan SubjectService".
Pada kasus di atas, SubjectService membutuhkan method Save dengan parameter bertipe string yang mengembalikan error. Untuk memenuhinya, MySQL menyesuaikan diri dengan membuat MySQLSubjectRepository yang memiliki method persis sama, yaitu Save dengan parameter bertipe string yang mengembalikan error.

Ilustrasinya menjadi seperti ini.

Tapi SubjectService dan MySQLSubjectRepository belum terhubung, jadi secara kode program belum bisa berjalan. Karena itu keduanya perlu dikoneksikan.

Ilustrasinya menjadi seperti ini.

Jadi, flow of control mengalir ke kanan, tapi dependency kode tetap menunjuk ke kiri (dari sudut pandang MySQLSubjectRepository).
Dengan begitu, kalau MySQLSubjectRepository diganti dengan sesuatu yang lain (misalnya Mongo, Postgres, atau mocking), SubjectService tidak perlu menyesuaikan apa pun. Kita cukup mengubah dependency pada flow of control-nya, seperti ini.

Kembali ke contoh di awal, dengan logika dan cara yang sama, flow kodenya menjadi: Use Case tidak memanggil Repository secara langsung. Use Case mendefinisikan sebuah interface sebagai port (Use Case Output Port di lapisan Use Cases), lalu Repository (Gateways) di lingkaran luar yang "menyesuaikan diri dengan interface itu".
Jadi, flow of control mengalir ke luar, tapi dependency kode tetap menunjuk ke dalam, seperti yang diilustrasikan di bawah ini.

Lalu bagaimana dengan Entities?
Pada kasus kita, Entity-nya adalah Subject (cek subject.go). Bisa dilihat kembali pada kode usecase.go dan repository.go terdapat dependency entity model.Subject, dari situ terlihat bahwa baik Usecase maupun Repository sama-sama memiliki dependency ke entity Subject. Dengan demikian, ilustrasinya dapat digambarkan sebagai berikut.

Ilustrasi tersebut kini sudah sesuai dengan diagram The Clean Architecture by Uncle Bob di bawah ini.

Catatan:
- Baca flow dependency pada lingkaran berlapis (panah hitam).
- Baca flow of control pada pojok kanan bawah (panah ungu).
Sampai di sini, semoga sudah cukup jelas ya.
Terima kasih sudah membaca sampai akhir! Semoga bermanfaat.