Lewati ke konten utama

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

KeuntunganPenjelasan
TestableMudah di-mock, tidak butuh database saat unit test
FlexibleGanti MySQL ke PostgreSQL tanpa ubah service
MaintainableQuery SQL terpusat di satu tempat
ReusableSatu repository dipakai banyak service
ReadableNama method mendeskripsikan intent dengan jelas