BAB 43. REPOSITORY PATTERN
Tujuan
Repository Pattern memisahkan business logic dari implementasi database sehingga:
- Database dapat diganti tanpa memengaruhi service
- Logic bisnis mudah diuji (unit testing dengan mock)
- Query database terpusat dan tidak tersebar di controller/service
Struktur Repository
app/Modules/Requests/
└── Repositories/
├── Interfaces/
│ └── RequestRepositoryInterface.php ← Kontrak
└── Eloquent/
└── RequestRepository.php ← Implementasi
Interface — Kontrak Repository
// app/Modules/Requests/Repositories/Interfaces/RequestRepositoryInterface.php
interface RequestRepositoryInterface
{
public function create(array $data): Request;
public function findById(int $id): ?Request;
public function findByIdOrFail(int $id): Request;
public function update(int $id, array $data): Request;
public function delete(int $id): bool;
public function search(array $filters): LengthAwarePaginator;
public function findActiveBySchool(int $schoolId): Collection;
}
Implementasi — Eloquent Repository
// app/Modules/Requests/Repositories/Eloquent/RequestRepository.php
class RequestRepository implements RequestRepositoryInterface
{
public function create(array $data): Request
{
return Request::create($data);
}
public function findById(int $id): ?Request
{
return Request::with(['student', 'subject', 'topic'])->find($id);
}
public function findByIdOrFail(int $id): Request
{
return Request::with(['student', 'subject', 'topic'])->findOrFail($id);
}
public function update(int $id, array $data): Request
{
$request = $this->findByIdOrFail($id);
$request->update($data);
return $request->fresh();
}
public function delete(int $id): bool
{
return Request::destroy($id) > 0;
}
public function search(array $filters): LengthAwarePaginator
{
return Request::query()
->when($filters['status'] ?? null, fn($q, $v) => $q->where('status', $v))
->when($filters['subject_id'] ?? null, fn($q, $v) => $q->where('subject_id', $v))
->where('school_id', $filters['school_id'])
->latest()
->paginate($filters['per_page'] ?? 15);
}
public function findActiveBySchool(int $schoolId): Collection
{
return Request::where('school_id', $schoolId)
->whereIn('status', ['searching', 'accepted', 'on_going'])
->get();
}
}
Binding di Service Provider
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->bind(
RequestRepositoryInterface::class,
RequestRepository::class
);
$this->app->bind(
BookingRepositoryInterface::class,
BookingRepository::class
);
$this->app->bind(
MeetingRepositoryInterface::class,
MeetingRepository::class
);
}
Unit Testing dengan Mock
// tests/Unit/Services/RequestServiceTest.php
class RequestServiceTest extends TestCase
{
public function test_create_request_dispatches_matching_job(): void
{
Queue::fake();
// Mock repository — tidak perlu database nyata
$mockRepo = $this->createMock(RequestRepositoryInterface::class);
$mockRepo->method('create')->willReturn(new Request(['id' => 1]));
$service = new RequestService($mockRepo, new MatchingEngine());
$service->createRequest(CreateRequestDTO::from([...]));
Queue::assertPushed(RunAIMatchingJob::class);
}
}
Keuntungan Repository Pattern
| Keuntungan | Penjelasan |
|---|---|
| Testable | Mudah di-mock, tidak butuh database saat unit test |
| Flexible | Ganti MySQL ke PostgreSQL tanpa ubah service |
| Maintainable | Query SQL terpusat di satu tempat |
| Reusable | Satu repository dipakai banyak service |
| Readable | Nama method mendeskripsikan intent dengan jelas |