Memuat...
👋 Selamat Pagi!

7 Prinsip Clean Architecture yang Bikin Kode Mudah Dipelihara

Aplikasi Anda sering crash dan sulit di-maintain? Pelajari 7 prinsip Clean Architecture yang membuat kode tetap rapi, scalable, dan mudah dikembangkan bertahun-...

7 Prinsip Clean Architecture yang Bikin Kode Mudah Dipelihara

Pernahkah Anda mengalami mimpi buruk saat harus memodifikasi kode lama yang ditulis setahun yang lalu?

Atau mungkin Anda kesulitan menambahkan fitur baru karena kode terlalu berantakan dan saling bergantung satu sama lain?

Masalah ini sangat umum dialami developer, terutama ketika proyek mulai berkembang dan tim bertambah besar.

Clean Architecture adalah solusi yang diperkenalkan oleh Robert C. Martin (Uncle Bob) untuk mengatasi kompleksitas dalam pengembangan software modern.

Artikel ini akan membahas 7 prinsip fundamental Clean Architecture yang akan mengubah cara Anda menulis dan mengorganisir kode secara permanen.

Apa Itu Clean Architecture?

Clean Architecture adalah pendekatan desain software yang memisahkan komponen aplikasi berdasarkan tanggung jawab dan ketergantungannya.

Tujuan utamanya adalah membuat kode yang mudah diuji, fleksibel terhadap perubahan teknologi, dan dapat dipelihara dalam jangka panjang.

Bayangkan aplikasi Anda seperti rumah berlantai banyak. Setiap lantai memiliki fungsi spesifik dan tidak boleh bergantung pada lantai di atasnya.

Lantai paling bawah adalah business logic inti, sementara lantai atas adalah interface seperti UI atau API.

Dengan struktur seperti ini, Anda bisa mengganti framework atau database tanpa harus menulis ulang seluruh aplikasi.

Prinsip 1: Independence of Frameworks

Framework adalah alat, bukan fondasi aplikasi Anda.

Banyak developer terjebak dalam pola pikir "framework-first" di mana business logic terikat erat dengan Laravel, React, atau framework lainnya.

Clean Architecture mengajarkan kita untuk menjadikan framework sebagai plugin yang bisa diganti kapan saja.

Business logic harus berdiri sendiri di dalam layer paling dalam tanpa referensi langsung ke framework.

Contoh konkret: Jika Anda menggunakan Laravel, jangan letakkan business logic di dalam Controller atau Model Eloquent.

Pisahkan logic tersebut ke dalam class UseCase atau Service yang tidak bergantung pada Eloquent.

// ❌ Buruk: Business logic terikat dengan framework
class OrderController extends Controller
{
    public function create(Request $request)
    {
        $order = Order::create([
            'user_id' => auth()->id(),
            'total' => $request->total,
            'status' => 'pending'
        ]);
        
        // Business logic tercampur dengan framework
        if ($order->total > 1000000) {
            Mail::to($order->user)->send(new LargeOrderNotification($order));
        }
        
        return response()->json($order);
    }
}

// ✅ Baik: Business logic terpisah
class CreateOrderUseCase
{
    public function execute(CreateOrderRequest $request): Order
    {
        $order = new Order(
            $request->userId,
            $request->items,
            $request->total
        );
        
        $this->orderRepository->save($order);
        
        if ($order->isLargeOrder()) {
            $this->notificationService->notifyLargeOrder($order);
        }
        
        return $order;
    }
}

Dengan pemisahan ini, Anda bisa migrate dari Laravel ke Symfony atau framework lain tanpa mengubah business logic.

Prinsip 2: Independence of UI

User interface hanyalah salah satu cara untuk berinteraksi dengan aplikasi Anda.

Hari ini Anda mungkin menggunakan web interface, besok bisa jadi mobile app, atau bahkan command line interface.

Business logic tidak boleh tahu atau peduli bagaimana data ditampilkan kepada user.

Semua keputusan presentasi harus berada di layer paling luar.

// ❌ Buruk: Logic mengembalikan HTML
class ProductService
{
    public function getProductList()
    {
        $products = $this->repository->findAll();
        
        $html = '<ul>';
        foreach ($products as $product) {
            $html .= "<li>{$product->name} - Rp {$product->price}</li>";
        }
        $html .= '</ul>';
        
        return $html;
    }
}

// ✅ Baik: Logic mengembalikan data murni
class GetProductListUseCase
{
    public function execute(): array
    {
        return $this->repository->findAll();
    }
}

Controller atau Presenter bertanggung jawab mengubah data menjadi format yang sesuai untuk UI tertentu.

Prinsip 3: Independence of Database

Database adalah detail implementasi, bukan inti aplikasi.

Aplikasi Anda harus bisa berjalan dengan MySQL, PostgreSQL, MongoDB, atau bahkan in-memory storage untuk testing.

Gunakan Repository Pattern untuk mengabstraksi operasi database.

Business logic hanya berkomunikasi dengan interface Repository, bukan implementasi konkret.

// Interface di layer domain
interface OrderRepositoryInterface
{
    public function save(Order $order): void;
    public function findById(string $id): ?Order;
    public function findByUser(string $userId): array;
}

// Implementasi MySQL di layer infrastructure
class MySQLOrderRepository implements OrderRepositoryInterface
{
    public function save(Order $order): void
    {
        DB::table('orders')->insert([
            'id' => $order->id,
            'user_id' => $order->userId,
            'total' => $order->total
        ]);
    }
    
    // ... implementasi lainnya
}

// Implementasi in-memory untuk testing
class InMemoryOrderRepository implements OrderRepositoryInterface
{
    private array $orders = [];
    
    public function save(Order $order): void
    {
        $this->orders[$order->id] = $order;
    }
    
    // ... implementasi lainnya
}

Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.

Prinsip 4: Testability

Kode yang baik adalah kode yang mudah diuji tanpa menjalankan seluruh aplikasi.

Dengan Clean Architecture, Anda bisa test business logic secara terisolasi tanpa database, framework, atau external service.

Setiap UseCase harus bisa dijalankan dengan mock dependencies.

class CreateOrderUseCaseTest extends TestCase
{
    public function test_creates_order_successfully()
    {
        // Arrange
        $repository = new InMemoryOrderRepository();
        $notifier = new FakeNotificationService();
        $useCase = new CreateOrderUseCase($repository, $notifier);
        
        $request = new CreateOrderRequest(
            userId: 'user-123',
            items: [['product_id' => 'prod-1', 'qty' => 2]],
            total: 500000
        );
        
        // Act
        $order = $useCase->execute($request);
        
        // Assert
        $this->assertNotNull($order->id);
        $this->assertEquals(500000, $order->total);
        $this->assertEquals(1, $repository->count());
    }
    
    public function test_sends_notification_for_large_orders()
    {
        $repository = new InMemoryOrderRepository();
        $notifier = new FakeNotificationService();
        $useCase = new CreateOrderUseCase($repository, $notifier);
        
        $request = new CreateOrderRequest(
            userId: 'user-123',
            items: [['product_id' => 'prod-1', 'qty' => 20]],
            total: 2000000
        );
        
        $order = $useCase->execute($request);
        
        $this->assertTrue($notifier->wasNotified('large_order'));
    }
}

Test seperti ini sangat cepat karena tidak memerlukan database atau HTTP request.

Prinsip 5: Dependency Rule

Ini adalah aturan paling penting dalam Clean Architecture: dependency hanya boleh mengarah ke dalam.

Layer luar boleh bergantung pada layer dalam, tetapi tidak sebaliknya.

Layer domain (business logic) tidak boleh import atau menggunakan class dari layer infrastructure atau presentation.

Gunakan Dependency Inversion Principle untuk membalik arah dependency.

// ❌ Buruk: Domain bergantung pada infrastructure
class OrderService
{
    private MySQLOrderRepository $repository; // Konkret class
    
    public function createOrder($data)
    {
        // ...
    }
}

// ✅ Baik: Domain hanya bergantung pada abstraksi
class CreateOrderUseCase
{
    private OrderRepositoryInterface $repository; // Interface
    
    public function __construct(OrderRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }
    
    public function execute(CreateOrderRequest $request): Order
    {
        // ...
    }
}

Infrastructure layer yang mengimplementasikan interface dari domain layer.

Dengan cara ini, domain tetap bersih dan tidak tahu tentang detail implementasi.

Prinsip 6: Separation of Concerns

Setiap komponen harus memiliki satu tanggung jawab yang jelas.

Jangan campur logic validasi, business rule, dan data access dalam satu class.

Clean Architecture mendorong pemisahan yang tegas:

  • Entities: Business objects dengan business rules
  • Use Cases: Application-specific business rules
  • Controllers: Mengubah data dari/ke format yang sesuai
  • Presenters: Memformat data untuk UI
  • Repositories: Data access logic
// Entities: Pure business object
class Order
{
    private string $id;
    private string $userId;
    private array $items;
    private int $total;
    private string $status;
    
    public function isLargeOrder(): bool
    {
        return $this->total > 1000000;
    }
    
    public function canBeCancelled(): bool
    {
        return in_array($this->status, ['pending', 'confirmed']);
    }
}

// Use Case: Orchestrate business logic
class CancelOrderUseCase
{
    public function execute(string $orderId, string $userId): void
    {
        $order = $this->repository->findById($orderId);
        
        if ($order->userId !== $userId) {
            throw new UnauthorizedException();
        }
        
        if (!$order->canBeCancelled()) {
            throw new OrderCannotBeCancelledException();
        }
        
        $order->cancel();
        $this->repository->save($order);
        $this->eventPublisher->publish(new OrderCancelled($orderId));
    }
}

Setiap class memiliki fokus yang jelas dan mudah dipahami.

Prinsip 7: Screaming Architecture

Struktur folder aplikasi harus berteriak tentang apa yang dilakukan aplikasi, bukan framework apa yang digunakan.

Ketika developer baru membuka project, mereka harus langsung tahu ini aplikasi e-commerce, inventory, atau CRM.

Jangan biarkan struktur Laravel atau framework lain mendominasi organisasi kode.

// ❌ Buruk: Framework-centric
app/
  Http/
    Controllers/
  Models/
  Services/
  
// ✅ Baik: Domain-centric
src/
  Order/
    Domain/
      Order.php
      OrderItem.php
      OrderRepository.php
    Application/
      CreateOrderUseCase.php
      CancelOrderUseCase.php
    Infrastructure/
      MySQLOrderRepository.php
      OrderController.php
  User/
    Domain/
    Application/
    Infrastructure/
  Payment/
    Domain/
    Application/
    Infrastructure/

Struktur ini membuat developer langsung paham bahwa ini adalah aplikasi yang mengelola Order, User, dan Payment.

Setiap module memiliki layer domain, application, dan infrastructure yang jelas.

Implementasi Praktis di Laravel

Banyak developer Laravel bingung bagaimana menerapkan Clean Architecture karena Laravel sudah memiliki struktur MVC sendiri.

Kuncinya adalah jangan melawan framework, tapi tambahkan layer baru di dalamnya.

Buat folder src sejajar dengan app, lalu atur namespace di composer.json:

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Domain\\": "src/Domain/",
        "Application\\": "src/Application/",
        "Infrastructure\\": "src/Infrastructure/"
    }
}

Controller Laravel menjadi thin adapter yang memanggil Use Case:

class OrderController extends Controller
{
    public function __construct(
        private CreateOrderUseCase $createOrder,
        private CancelOrderUseCase $cancelOrder
    ) {}
    
    public function store(Request $request)
    {
        $validated = $request->validate([
            'items' => 'required|array',
            'items.*.product_id' => 'required',
            'items.*.quantity' => 'required|integer|min:1'
        ]);
        
        $order = $this->createOrder->execute(
            new CreateOrderRequest(
                userId: auth()->id(),
                items: $validated['items']
            )
        );
        
        return response()->json([
            'data' => OrderResource::make($order)
        ], 201);
    }
}

Controller hanya menangani HTTP concerns: validasi input, autentikasi, dan formatting response.

Semua business logic ada di Use Case.

Menangani Cross-Cutting Concerns

Beberapa concern seperti logging, caching, dan transaction management sulit ditempatkan di layer tertentu.

Solusinya adalah menggunakan Decorator Pattern atau Middleware Pattern.

class TransactionalOrderUseCase implements CreateOrderUseCaseInterface
{
    public function __construct(
        private CreateOrderUseCase $inner,
        private DatabaseTransaction $transaction
    ) {}
    
    public function execute(CreateOrderRequest $request): Order
    {
        return $this->transaction->run(function() use ($request) {
            return $this->inner->execute($request);
        });
    }
}

class LoggedOrderUseCase implements CreateOrderUseCaseInterface
{
    public function __construct(
        private CreateOrderUseCaseInterface $inner,
        private Logger $logger
    ) {}
    
    public function execute(CreateOrderRequest $request): Order
    {
        $this->logger->info('Creating order', ['user_id' => $request->userId]);
        
        try {
            $order = $this->inner->execute($request);
            $this->logger->info('Order created', ['order_id' => $order->id]);
            return $order;
        } catch (\Exception $e) {
            $this->logger->error('Order creation failed', ['error' => $e->getMessage()]);
            throw $e;
        }
    }
}

Decorator ini bisa di-compose melalui dependency injection container.

Kapan Menggunakan Clean Architecture?

Clean Architecture memang powerful, tapi tidak selalu diperlukan untuk setiap project.

Gunakan pendekatan ini ketika:

  • Project akan di-maintain dalam jangka panjang (lebih dari 1 tahun)
  • Tim development lebih dari 3 orang
  • Business logic cukup kompleks dengan banyak rules
  • Ada kemungkinan perubahan teknologi (database, framework, external service)
  • Testing dan quality assurance sangat penting

Untuk MVP atau prototype sederhana, struktur MVC standar mungkin sudah cukup.

Tapi ketika aplikasi mulai berkembang, refactor ke Clean Architecture akan sangat membantu.

Tantangan dan Trade-offs

Clean Architecture bukan silver bullet tanpa kekurangan.

Anda akan menulis lebih banyak kode karena perlu membuat interface, use case, dan repository untuk setiap fitur.

Developer baru mungkin butuh waktu untuk memahami struktur yang lebih kompleks.

Setup awal project juga memakan waktu lebih lama dibanding struktur framework standar.

Namun investasi ini terbayar ketika aplikasi berkembang dan requirement berubah dengan cepat.

Kode yang mudah ditest, fleksibel, dan maintainable akan menghemat banyak waktu dan biaya dalam jangka panjang.

Tips Migrasi ke Clean Architecture

Jangan coba refactor seluruh aplikasi sekaligus ke Clean Architecture.

Mulai dari satu module atau fitur baru, lalu terapkan prinsip Clean Architecture secara konsisten.

Identifikasi business logic yang paling sering berubah atau paling kompleks, lalu prioritaskan untuk direfactor.

Buat automated test sebelum melakukan refactoring untuk memastikan behavior tidak berubah.

Dokumentasikan struktur dan naming convention yang digunakan agar tim memiliki pemahaman yang sama.

Lakukan code review secara ketat untuk memastikan dependency rule tidak dilanggar.

Kesimpulan

Clean Architecture adalah investasi jangka panjang untuk kualitas kode yang lebih baik.

Dengan menerapkan 7 prinsip ini—independence of frameworks, UI, dan database, testability, dependency rule, separation of concerns, dan screaming architecture—Anda akan memiliki aplikasi yang mudah dipelihara dan dikembangkan.

Kode Anda akan lebih mudah dipahami, ditest, dan dimodifikasi tanpa efek samping yang tidak terduga.

Mulai terapkan prinsip ini sedikit demi sedikit dalam project Anda, dan rasakan perbedaannya dalam beberapa bulan ke depan.

Ketika tim baru bergabung, mereka akan lebih mudah onboarding karena struktur yang jelas dan predictable.

Selamat mencoba, dan semoga kode Anda semakin clean dan maintainable!

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang