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:
- Business logic tercampur dengan query database
- Sulit mengganti data source (misal dari API ke local storage)
- Testing sulit karena kode langsung mengakses database/API
- Query yang sama ditulis berulang di banyak tempat
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:
- TanStack Query —
useQuerysebagai repository layer untuk API calls - Laravel Eloquent — Model sebagai repository untuk database
- Android Room — DAO sebagai repository untuk SQLite
- Prisma / Drizzle — ORM sebagai repository layer
Kapan pakai Repository Pattern:
- Saat business logic tidak boleh tergantung pada data source spesifik
- Saat perlu caching layer antara kode dan data source
- Saat ingin mock data access untuk testing
- Saat data bisa datang dari berbagai sumber (API, cache, local storage)