Repository 패턴
Interface → Repository → Controller 레이어로 DB 접근을 추상화하는 아키텍처 패턴.
포스트 생성 (Repository::create)
포스트 목록 (Repository::findRecent)
목록을 불러오는 중...
1
Interface 정의 (계약)
// app/Interfaces/PostRepositoryInterface.php
namespace App\Interfaces;
interface PostRepositoryInterface
{
public function findAll(): array;
public function findById(int $id): ?object;
public function create(array $data): int|false;
public function update(int $id, array $data): bool;
public function delete(int $id): bool;
public function findRecent(int $limit = 10): array;
}
2
Repository 구현체
// app/Repositories/PostRepository.php
namespace App\Repositories;
use App\Interfaces\PostRepositoryInterface;
use App\Models\PostModel;
class PostRepository implements PostRepositoryInterface
{
public function __construct(private PostModel $model) {}
public function findAll(): array
{
return $this->model->orderBy('id', 'DESC')->findAll();
}
public function findById(int $id): ?object
{
return $this->model->find($id);
}
public function create(array $data): int|false
{
if (! $this->model->insert($data)) return false;
return (int) $this->model->getInsertID();
}
public function update(int $id, array $data): bool
{
return $this->model->update($id, $data);
}
public function delete(int $id): bool
{
return (bool) $this->model->delete($id);
}
public function findRecent(int $limit = 10): array
{
return $this->model->orderBy('id', 'DESC')->findAll($limit);
}
}
3
Controller에서 사용
// app/Controllers/Examples/Repository.php
class Repository extends BaseController
{
private PostRepositoryInterface $repo;
public function __construct()
{
// Interface 타입 힌트, 구현체 주입 (DI 컨테이너로 교체 가능)
$this->repo = new PostRepository(new PostModel());
}
public function list()
{
$posts = $this->repo->findRecent(10);
return $this->response->setJSON([...]);
}
public function store()
{
$id = $this->repo->create($this->request->getPost());
return $this->response->setJSON(['id' => $id]);
}
}
Repository 패턴의 장점
- 테스트 용이성 — Interface 구현 Mock을 주입해 DB 없이 단위 테스트.
- 저장소 교체 — DB → 캐시, DB → API 호출로 구현체만 교체 가능.
- 비즈니스 로직 분리 — Controller는 데이터 접근의 "방법"을 모름.
- 중복 쿼리 제거 —
findPublished(),findByAuthor()같은 의미있는 메서드로 응집.
서비스 레이어 vs Repository
| Service Layer | Repository | |
|---|---|---|
| 책임 | 비즈니스 로직, 트랜잭션, 오케스트레이션 | 데이터 영속화, CRUD 추상화 |
| 의존 대상 | 여러 Repository, 외부 API, 메일 등 | Model / Query Builder / 외부 DB |
| 예시 메서드 | registerUser(), placeOrder() | findById(), create() |
| 리턴 타입 | 도메인 객체 / Result 객체 | Entity / 배열 / id |
| 트랜잭션 처리 | 여기서 시작/커밋 | 관여하지 않음 |
| 레이어 위치 | Controller 바로 아래 | Service 아래, Model 위 |
전형적인 계층 구조
┌─────────────────────────────────────┐
│ Controller (HTTP 입출력 처리) │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ Service Layer (비즈니스 로직) │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ Repository (Interface) │
└─────────────┬───────────────────────┘
↓
┌─────────────────────────────────────┐
│ Model / Query Builder / DB │
└─────────────────────────────────────┘
주의: 소규모 프로젝트에서 Repository 패턴은 과한 추상화일 수 있습니다.
Model이 충분히 단순한 CRUD라면 Model 자체가 Repository 역할을 합니다.
"여러 데이터 소스를 결합해야 한다", "테스트 용이성이 중요하다" 같은 명확한 이유가 있을 때 도입하세요.