Repository Pattern — Design Patterns

Repository Pattern menyediakan abstraksi antara business logic dan data access. Kode bisnis tidak peduli apakah data datang dari database, API, cache, atau file

Repository Pattern menyediakan abstraksi antara business logic dan data access. Kode bisnis tidak peduli apakah data datang dari database, API, cache, atau file — semua diakses lewat interface yang sama.

Analogi: Perpustakaan. Kamu minta buku ke petugas (repository). Petugas yang tahu apakah buku ada di rak, gudang, atau perlu dipesan dari cabang lain. Kamu hanya bilang "saya mau buku X".

Masalah yang diselesaikan:

Tanpa Repository (kode berantakan):

// Kode bisnis langsung akses API — tightly coupled
async function getActiveUsers() {
  const res = await fetch("/api/users?status=active");
  const users = await res.json();
  return users.filter(u => u.lastLogin > Date.now() - 86400000);
}

// Di tempat lain, query yang sama ditulis lagi...
async function sendNewsletter() {
  const res = await fetch("/api/users?status=active"); // duplikat!
  const users = await res.json();
  // kirim email...
}

Dengan Repository:

class UserRepository {
  constructor(apiClient) {
    this.api = apiClient;
    this.cache = new Map();
  }

  async getAll() {
    if (this.cache.has("all")) return this.cache.get("all");
    const users = await this.api.get("/users");
    this.cache.set("all", users);
    return users;
  }

  async getActive() {
    const users = await this.getAll();
    return users.filter(u => u.status === "active");
  }

  async getRecentlyActive(hours = 24) {
    const users = await this.getActive();
    const cutoff = Date.now() - hours * 3600000;
    return users.filter(u => u.lastLogin > cutoff);
  }

  async getById(id) {
    const users = await this.getAll();
    return users.find(u => u.id === id) || null;
  }

  async create(userData) {
    const user = await this.api.post("/users", userData);
    this.cache.delete("all"); // Invalidate cache
    return user;
  }
}

// Business logic jadi bersih
const userRepo = new UserRepository(apiClient);

async function dashboard() {
  const active = await userRepo.getRecentlyActive(48);
  console.log(`${active.length} users active in 48h`);
}

Repository untuk multiple data sources:

// Interface yang sama, implementasi berbeda
class LocalStorageUserRepo {
  getAll() {
    return JSON.parse(localStorage.getItem("users") || "[]");
  }
  create(user) {
    const users = this.getAll();
    users.push({ ...user, id: Date.now() });
    localStorage.setItem("users", JSON.stringify(users));
  }
}

class ApiUserRepo {
  async getAll() {
    const res = await fetch("/api/users");
    return res.json();
  }
  async create(user) {
    const res = await fetch("/api/users", {
      method: "POST",
      body: JSON.stringify(user)
    });
    return res.json();
  }
}

// Switch data source tanpa mengubah business logic!
const repo = navigator.onLine
  ? new ApiUserRepo()
  : new LocalStorageUserRepo();

Repository di dunia nyata:

Kapan pakai Repository Pattern: