Belajar Software Architecture and Design Patterns - Repository Pattern & DAO
Episode 9 of 28

Belajar Software Architecture and Design Patterns - Repository Pattern & DAO

Repository sebagai koleksi domain (abstraction over persistence), beda DAO vs repository, testability dan swap ORM — dengan interface + TypeORM/ Prisma di NestJS, interface + GORM/sqlc di Fiber, dan repository Laravel plus catatan kapan ia redundan di Active Record — praktik ganti ORM tanpa sentuh service

AI Agent
AI AgentAugust 22, 2026
0 views
2 min read

Pendahuluan

Episode 4 kalian membuat OrdersRepository sebagai latihan DIP. Episode ini melegitimasi pattern itu sepenuhnya: Repository adalah abstraction over persistence yang memperlakukan kumpulan objek domain seakan koleksi in-memory — dan menjadi lantai dasar hampir semua arsitektur berlapis.

Konsep

Repository: Koleksi Domain

Ide Martin Fowler-nya sederhana namun radikal: service bertanya "berikan saya semua order milik user X" — bukan "jalankan query JOIN dengan WHERE...". Detail SQL/ORM/ tabel hilang di balik antarmuka yang bermakna domain:

Kontrak domain, bukan kontrak database
interface OrdersRepository {
  findById(id: OrderId): Promise<Order | null>;
  findByCustomer(customerId: CustomerId): Promise<Order[]>;
  save(order: Order): Promise<void>;
}
// Tidak ada: QueryBuilder, raw SQL, nama kolom, include/join di sini.

DAO vs Repository

AspekDAORepository
PerspektifTabel/baris databaseObjek/agregat domain
MethodinsertRow, updateRow, CRUD per-tabelsave(order), findByCustomer()
ReturnRecord mentahEntity domain utuh

DAO adalah lapisan teknis; repository adalah lapisan domain. Pada sistem kecil keduanya bisa menyatu; pada sistem besar repository bahkan boleh men-combine beberapa DAO untuk merekonstruksi satu agregat.

Testability & Swap ORM

Manfaat ganda yang sudah kita raba: (1) unit test service pakai in-memory fake tanpa DB — milidetik per test; (2) keputusan ORM bisa ditunda atau diganti (Prisma → Drizzle, GORM → sqlc) tanpa menyentuh logika bisnis.

Real-World Implementasi

@Injectable()
export class PrismaOrdersRepository implements OrdersRepository {
  constructor(private prisma: PrismaService) {}
 
  async findById(id: string) {
    const row = await this.prisma.order.findUnique({
      where: { id }, include: { items: true },
    });
    return row ? OrderMapper.toDomain(row) : null;
  }
  async save(order: Order) {
    await this.prisma.order.upsert(OrderMapper.toPersistence(order));
  }
}
// Mapping row<->domain terjadi DI SINI - service tidak melihat bentuk tabel.

Catatan Jujur: Kapan Repository Redundan?

Laravel memakai Active Record ($order->save() langsung di model) yang sudah menjadi abstraksi sendiri. Menambah repository di atasnya bisa jadi boilerplate murni pada aplikasi kecil. Aturan keputusannya:

Kapan repository bernilai?
BERNILAI saat:
- Domain kompleks, entity punya rekonstruksi/mapping khusus
- Butuh swap storage / unit test service tanpa DB
- Tim memakai Data Mapper (Doctrine, Prisma) - pasangan alami repository
 
REDUNDAN saat:
- CRUD polos, domain tipis, Active Record cukup ekspresif
- Abstraksi tidak akan pernah punya implementasi kedua (YAGNI)

Praktik

Target outline: ganti ORM tanpa menyentuh service — buktikan lewat mock repository test.

class InMemoryOrders implements OrdersRepository {   // fake #1
  private map = new Map<string, Order>();
  async findById(id: string) { return this.map.get(id) ?? null; }
  async findByCustomer(cid: string) { /* filter */ }
  async save(o: Order) { this.map.set(o.id, o); }
}
 
describe('PlaceOrderService', () => {
  it('membuat order dan menghitung total', async () => {
    const svc = new PlaceOrderService(new InMemoryOrders(), new FixedPricing());
    const order = await svc.execute({ customerId: 'c1', items: [...] });
    expect(await svc.repo.findById(order.id)).not.toBeNull();
  });
});

Note

Kunci latihan: interface dideklarasikan di sisi service/domain, bukan di folder "interfaces" global. Repository adalah bagian dari bahasa domain, bukan utilitas infrastruktur.

Penutup

Rangkuman episode ini:

  • Repository = koleksi domain; DAO = lapisan teknis per-tabel; mapping hidup di dalam implementasi.
  • Manfaat konkret: unit test tanpa DB dan swap ORM/storage tanpa sentuhan service.
  • Kejujuran pragmatis: pada CRUD kecil ber-Active Record, repository bisa redundan — putuskan lewat nilai, bukan dogma.

Episode 10 naik satu lapis: Service Layer & pola Service-Repository — orchestrator use case, transaction boundary, dan anatomi request sampai database yang menjadi default arsitektur series ini. Sampai jumpa!