Ikhtisar Clean Architecture
Fondasi pengembangan aplikasi yang scalable, mudah dirawat, dan mudah diintegrasikan dengan berbagai teknologi.
Prinsip
- Separation of concerns: Pisahkan logika bisnis, presentasi, dan data.
- Dependency inversion: Layer atas tidak bergantung pada detail layer bawah.
- Testability: Setiap bagian mudah di-test secara terpisah.
- Independence: Framework, UI, dan database bisa diganti tanpa mengubah domain.
Lapisan
- Core (Application): Core (Application): Berisi business logic murni, entity, value object, dan use case. Tidak bergantung pada framework atau library eksternal.
- Infrastructure (Infra Adapters): Infrastructure (Infra Adapters): Detail teknis seperti database, framework, third-party service. Implementasi adapter dan komunikasi eksternal.
- Components: Components: UI Components, controller, presenter, dan adapter untuk interaksi user. Semua komponen UI diletakkan di direktori components agar mudah integrasi dengan library lain seperti shadcn.
Port & Adapter
- Port: Repository Port
Adapter: Repository Adapter (misal: Prisma, MongoDB, REST API)
Dependency: Core → Port → Infra Adapter → Infrastructure - Port: Controller Port
Adapter: Controller Adapter (misal: Next.js API Route, Express)
Dependency: Components → Adapter → Infrastructure
Contoh Struktur Project
src/ ├── core/ // Entity, Value Object, Use Case, Service ├── infrastructure/ // Infra Adapters, DB, API, External Service ├── components/ // UI Components (Next.js, React, shadcn, dsb) ├── types/ // Shared Types └── ... // File lain sesuai kebutuhan
Interface & Dependency Injection
Untuk menghubungkan interface dari domain ke infrastructure, gunakan dependency injection (IoC container). Domain hanya mendefinisikan interface (misal UserRepository), sedangkan implementasi detail (misal PrismaUserRepository) ada di infrastructure.
- Domain: Mendefinisikan interface/abstraction.
- Infrastructure: Mengimplementasikan interface tersebut.
- IoC Container: Melakukan binding antara interface dan implementasi saat aplikasi berjalan.
Contoh binding dengan inversify
:
// core/user/UserRepository.ts export interface UserRepository { findById(id: string): Promise<User>; } // infrastructure/user/PrismaUserRepository.ts import { injectable } from "inversify"; import { UserRepository } from "../../core/user/UserRepository"; @injectable() export class PrismaUserRepository implements UserRepository { // implementasi detail } // infrastructure/ioc/container.ts import "reflect-metadata"; import { Container } from "inversify"; import { UserRepository } from "../../core/user/UserRepository"; import { PrismaUserRepository } from "../user/PrismaUserRepository"; const TYPES = { UserRepository: Symbol.for("UserRepository"), }; const container = new Container(); container.bind<UserRepository>(TYPES.UserRepository).to(PrismaUserRepository); export { container, TYPES };
Dengan cara ini, domain tetap terpisah dari detail teknis, dan aplikasi mudah di-maintain serta di-test.