Deni.dev Logo
15-07-2026
8 mnt baca

Membongkar Ilusi Clean Architecture: Panduan Desain Evolusioner Melawan 'Over-Engineering' di Era Microservices

#Clean Architecture#Software Architecture#Software Engineering#Microservices

Membongkar Ilusi Clean Architecture: Panduan Desain Evolusioner Melawan 'Over-Engineering' di Era Microservices

Bagi sebagian besar pengembang perangkat lunak, Clean Architecture (yang dipopulerkan oleh Robert C. Martin atau "Uncle Bob") dipandang sebagai kitab suci. Gambar lingkaran konsentris dengan panah ketergantungan yang mengarah ke dalam—dari Frameworks di lapisan terluar hingga Entities di inti terdalam—sering kali dianggap sebagai solusi mutlak untuk semua masalah kompleksitas kode.

Namun, mari kita jujur. Berapa kali Anda melihat sebuah proyek kecil berubah menjadi labirin birokrasi kode? Proyek di mana untuk menambahkan satu kolom baru di database, Anda harus menyentuh tujuh file berbeda: dari DatabaseModel, DataMapper, RepositoryInterface, RepositoryImpl, UseCase, Presenter, hingga ViewModel.

Ketika arsitektur yang dirancang untuk mempermudah perubahan justru menjadi penghambat utama kecepatan rilis (time-to-market), di situlah kita terjebak dalam over-engineering.

Artikel ini tidak akan mengulangi teori dasar Clean Architecture yang membosankan. Sebaliknya, kita akan membedah bagaimana menerapkan prinsip Clean Architecture secara evolusioner dan pragmatis—menyeimbangkan kesucian arsitektur dengan realitas produktivitas tim.


Anatomi Esensial: Apa yang Sebenarnya Ingin Diselamatkan oleh Clean Architecture?

Sebelum kita memotong kompas (shortcut), kita harus memahami esensi di balik lingkaran konsentris tersebut. Tujuan utama Clean Architecture sebenarnya sangat sederhana: Pemisahan Perhatian (Separation of Concerns) dan Kemerdekaan terhadap Eksternalitas.

   ┌─────────────────────────────────────────┐
   │          Presentation / Web / UI        │
   │   ┌─────────────────────────────────┐   │
   │   │       Use Cases / Business      │   │
   │   │   ┌─────────────────────────┐   │   │
   │   │   │        Entities         │   │   │
   │   │   └─────────────────────────┘   │   │
   │   └─────────────────────────────────┘   │
   └─────────────────────────────────────────┘

Ada tiga pilar utama yang menjaga sistem Anda tetap kokoh:

  1. Inti Bisnis yang Murni (The Pure Domain): Aturan bisnis Anda (Entities & Use Cases) tidak boleh tahu apakah mereka berjalan di atas ExpressJS, FastAPI, PostgreSQL, MongoDB, atau bahkan CLI.
  2. Aturan Dependensi (Dependency Rule): Kode di lapisan dalam tidak boleh menyebutkan atau mengetahui apa pun tentang kode di lapisan luar.
  3. Pemisahan Batas (Boundary Separation): Komunikasi antar lapisan dijembatani oleh Interface (Abstraksi), sehingga detail implementasi dapat ditukar tanpa merusak logika bisnis.

Jebakan Batman Clean Architecture: Mengapa Tim Anda Mulai Frustrasi

Masalah klasik terjadi ketika sebuah tim menerapkan Clean Architecture secara literal dan dogmatis pada setiap skenario kerja. Berikut adalah beberapa gejala "penyakit" arsitektur yang sering muncul:

1. Sindrom Kode Anemis dan Passthrough Use Cases

Sering kali, sebuah Use Case hanya bertindak sebagai jembatan malas yang memanggil fungsi Repository secara langsung tanpa melakukan kalkulasi bisnis apa pun.

// Contoh Use Case yang tidak berguna (hanya melakukan bypass)
export class GetUserByIdUseCase {
  constructor(private userRepository: UserRepository) {}

  async execute(id: string): Promise<User> {
    return this.userRepository.findById(id); // Nol logika bisnis!
  }
}

Jika 80% dari Use Cases di aplikasi Anda hanya seperti ini, Anda sedang membayar "pajak boilerplate" yang sangat mahal tanpa mendapatkan manfaat apa pun.

2. Duplikasi Model yang Berlebihan (Mapping Hell)

Demi menjaga batas isolasi yang suci, Anda membuat UserDbModel, UserEntity (Domain), dan UserResponseDto. Anda kemudian menulis ratusan baris kode mapper hanya untuk memindahkan data dari satu objek ke objek lain yang strukturnya 99% sama.


Solusi: Pendekatan Evolusioner (Evolutionary Clean Architecture)

Bagaimana kita menikmati manfaat Clean Architecture tanpa harus tersiksa oleh birokrasinya? Jawabannya adalah Arsitektur Evolusioner. Jangan mendesain aplikasi hari pertama Anda seolah-olah ia akan melayani 10 juta pengguna di hari kedua.

Berikut adalah taktik pragmatis yang bisa Anda terapkan sekarang:

1. Gunakan Aturan "Three-Strikes" untuk Abstraksi

Jangan membuat interface untuk Repository jika Anda yakin 100% aplikasi Anda tidak akan bermigrasi dari PostgreSQL dalam 3 tahun ke depan.

  • Fase 1 (Pragmatis): Biarkan Use Case memanggil kelas database konkret secara langsung.
  • Fase 2 (Refaktor): Ketika Anda mulai menulis Unit Test yang membutuhkan mocking kompleks, atau saat ada kebutuhan menggunakan database sekunder, saat itulah Anda mengekstraknya menjadi sebuah Interface.

2. Kelompokkan Berdasarkan Fitur (Vertical Slice Architecture), Bukan Lapisan Teknis

Alih-alih membagi folder proyek berdasarkan struktur horizontal seperti ini:

- controllers/
- usecases/
- entities/
- repositories/

Gunakan pendekatan Vertical Slice. Satukan semua komponen yang berubah bersama-sama di dalam satu modul fitur:

- features/
  - register-user/
    - register-user.controller.ts
    - register-user.usecase.ts
    - user.entity.ts
    - register-user.db.ts

Dengan cara ini, jika Anda ingin mengubah cara registrasi pengguna bekerja, Anda hanya perlu fokus pada satu folder, bukan melompati empat direktori yang berbeda.

3. Pragmatisme Pemetaan Data (Pragmatic Mapping)

Gunakan pendekatan Shared Kernel untuk model data jika aplikasi Anda masih dalam skala kecil hingga menengah. Tidak ada salahnya membiarkan Entity Domain Anda menggunakan dekorator ORM (seperti Prisma atau TypeORM) secara langsung selama skema databasenya masih identik dengan skema bisnis.

Jika di kemudian hari struktur database harus dioptimalkan untuk performa (misalnya denormalisasi), barulah Anda memisahkan model database dari model domain.


Contoh Implementasi: Dari Kompleks ke Pragmatis

Mari kita lihat bagaimana kita bisa menulis kode yang tetap memiliki batasan tanggung jawab yang jelas (Clean), namun sangat ringkas dan mudah dibaca.

Versi Over-Engineered (Menyiksa Developer)

Di sini, kita memiliki interface database terpisah, mapper manual, dan DTO yang berlebihan untuk kasus sederhana.

// domain/user.repository.ts
export interface UserRepository {
  save(user: UserEntity): Promise<void>;
}

// domain/user.entity.ts
export class UserEntity {
  constructor(public readonly id: string, public readonly email: string) {}
}

// usecase/register-user.ts
export class RegisterUserUseCase {
  constructor(private userRepo: UserRepository) {}
  async execute(email: string): Promise<void> {
    const user = new UserEntity(generateId(), email);
    await this.userRepo.save(user);
  }
}

Versi Pragmatis & Evolusioner (Tetap Bersih, Lebih Cepat)

Kita menghilangkan interface yang tidak perlu dan memanfaatkan kapabilitas ORM modern secara langsung, tanpa mengorbankan keterbacaan atau kemampuan untuk diuji (testability).

// features/register-user/register-user.ts
import { db } from "@/shared/database";

// Entity & Aturan Bisnis diletakkan berdampingan dengan usecase-nya jika bersifat spesifik
export class User {
  static create(email: string) {
    if (!email.includes("@")) throw new Error("Email tidak valid");
    return { id: crypto.randomUUID(), email };
  }
}

// Use Case langsung mengeksekusi operasi database lewat abstraksi ringan (Query Builder / ORM)
export async function registerUserUseCase(email: string) {
  const newUser = User.create(email);
  
  // Pragmatis: Langsung simpan menggunakan instance DB global/konkret
  await db.user.create({
    data: { id: newUser.id, email: newUser.email }
  });

  return newUser;
}

Mengapa ini tetap "Clean"? Karena aturan validasi bisnis (User.create) tetap terisolasi dari detail database (db.user.create). Namun, kita memangkas 3 file perantara yang tidak memberikan nilai tambah nyata pada tahap ini.


Kapan Harus Menggunakan "Full Clean Architecture"?

Bukan berarti Clean Architecture versi lengkap itu buruk. Ada kondisi spesifik di mana struktur ketat tersebut sangat layak untuk diinvestasikan:

Kondisi Proyek Pendekatan Arsitektur Alasan
Startup Tahap Awal (MVP) Pragmatic / Vertical Slice Kecepatan iterasi dan validasi pasar adalah segalanya.
Sistem Monolitik Skala Besar Pragmatic Clean Architecture Menjaga kode tetap modular tanpa terjebak boilerplate berlebih.
Enterprise / Multi-team Strict Clean Architecture Aturan yang ketat mencegah tim yang berbeda saling merusak kode satu sama lain.
Domain Sangat Kompleks (Fintech/Logistik) Clean + DDD (Domain-Driven Design) Menjaga logika kalkulasi bisnis yang rumit agar tidak tercampur dengan bug framework.

Kesimpulan

Clean Architecture bukanlah sebuah hukum fisika yang mutlak. Ia adalah sebuah panduan taktis. Desain perangkat lunak yang baik adalah desain yang paling murah biaya perawatannya dan paling mudah untuk diubah saat kebutuhan bisnis bergeser.

Jangan biarkan pencarian akan "arsitektur yang sempurna" menghentikan Anda dari merilis produk yang bermanfaat bagi pengguna. Mulailah dengan sederhana, jaga agar batas-batas domain Anda tetap bersih secara logis, dan lakukan refaktor ke arah arsitektur yang lebih ketat hanya ketika kode Anda sendiri yang memintanya.

Arsitektur terbaik adalah arsitektur yang berevolusi bersama dengan pemahaman Anda tentang masalah yang sedang Anda selesaikan.

Menikmati artikel ini? Berikan apresiasi Anda!