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.