Laravel에서 Repository Pattern: 지저분한 코드를 정리하세요

발행: (2025년 12월 28일 오후 08:55 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

Repository Pattern in Laravel: Clean Up Your Messy Code의 커버 이미지

Laravel 마스터리

문제

이와 같은 컨트롤러를 본 적 있나요?

public class OrderController extends Controller
{
    public function show($id)
    {
        $order = Order::with(['customer', 'items.product'])
            ->where('id', $id)
            ->first();

        return response()->json($order);
    }

    public function getUserOrders($userId)
    {
        // Same query duplicated! 😱
        $orders = Order::with(['customer', 'items.product'])
            ->where('customer_id', $userId)
            ->get();

        return response()->json($orders);
    }
}

문제점

  • 🔴 모든 곳에서 중복된 쿼리
  • 🔴 컨트롤러가 Eloquent에 강하게 결합됨
  • 🔴 데이터베이스 없이는 테스트가 불가능함
  • 🔴 비즈니스 로직이 데이터 접근과 뒤섞여 있음

솔루션: Repository 패턴

1단계 – 인터페이스 생성

interface OrderRepositoryInterface
{
    public function find(int $id): ?Order;
    public function findWithRelations(int $id): ?Order;
    public function findByCustomer(int $customerId): Collection;
}

2단계 – Repository 구현

class OrderRepository implements OrderRepositoryInterface
{
    protected $model;

    public function __construct(Order $model)
    {
        $this->model = $model;
    }

    public function findWithRelations(int $id): ?Order
    {
        return $this->model
            ->with(['customer', 'items.product'])
            ->find($id);
    }

    public function findByCustomer(int $customerId): Collection
    {
        return $this->model
            ->with(['customer', 'items.product'])
            ->where('customer_id', $customerId)
            ->orderBy('created_at', 'desc')
            ->get();
    }
}

3단계 – Service Provider에 등록

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            OrderRepositoryInterface::class,
            OrderRepository::class
        );
    }
}

4단계 – 컨트롤러 정리

class OrderController extends Controller
{
    protected $orderRepository;

    public function __construct(OrderRepositoryInterface $orderRepository)
    {
        $this->orderRepository = $orderRepository;
    }

    public function show(int $id)
    {
        $order = $this->orderRepository->findWithRelations($id);

        if (! $order) {
            return response()->json(['message' => 'Not found'], 404);
        }

        return response()->json($order);
    }

    public function getUserOrders(int $userId)
    {
        $orders = $this->orderRepository->findByCustomer($userId);
        return response()->json($orders);
    }
}

Benefits

  • 중복 없음 – 쿼리 로직이 한 곳에 존재
  • 쉬운 테스트 – 데이터베이스 대신 리포지토리를 모킹
  • 유연성 – 비즈니스 로직을 건드리지 않고 데이터 소스 전환
  • 클린 코드 – 컨트롤러는 HTTP 관련만 담당
  • 재사용성 – 동일 리포지토리를 컨트롤러, 잡, 커맨드 등에서 사용 가능

고급: 기본 저장소

abstract class BaseRepository
{
    protected $model;

    public function all(): Collection
    {
        return $this->model->all();
    }

    public function find(int $id): ?Model
    {
        return $this->model->find($id);
    }

    public function create(array $data): Model
    {
        return $this->model->create($data);
    }

    public function update(int $id, array $data): bool
    {
        return $this->model->find($id)?->update($data) ?? false;
    }

    public function delete(int $id): bool
    {
        return $this->model->find($id)?->delete() ?? false;
    }
}

기본 저장소 확장

class ProductRepository extends BaseRepository
{
    public function __construct(Product $model)
    {
        parent::__construct($model);
    }

    public function getFeatured(int $limit = 10): Collection
    {
        return $this->model
            ->where('is_featured', true)
            ->where('stock', '>', 0)
            ->limit($limit)
            ->get();
    }

    public function searchAndFilter(array $filters)
    {
        $query = $this->model->query();

        if (!empty($filters['search'])) {
            $query->where('name', 'like', "%{$filters['search']}%");
        }

        if (!empty($filters['min_price'])) {
            $query->where('price', '>=', $filters['min_price']);
        }

        return $query->paginate(15);
    }
}

테스트를 쉽게

리포지토리 없이

// Must set up entire database
$order = Order::factory()->hasItems(3)->create();
$response = $this->getJson("/api/orders/{$order->id}");

리포지토리와 함께

// Just mock the repository!
$orderRepo = Mockery::mock(OrderRepositoryInterface::class);
$orderRepo->shouldReceive('find')
    ->with(1)
    ->andReturn($mockOrder);

$this->app->instance(OrderRepositoryInterface::class, $orderRepo);

Common Pitfalls

❌ Don’t Return Query Builder

// Bad
public function getActive()
{
    return $this->model->where('active', true); // Query builder!
}

✅ Do Return Concrete Results

// Good
public function getActive(): Collection
{
    return $this->model->where('active', true)->get();
}

✅ Keep Repository for Data Access Only

// Good – Repository only handles data
public function create(array $data): Order
{
    return $this->model->create($data);
}

// Business logic in Service
class OrderService
{
    public function placeOrder(array $data): Order
    {
        $order = $this->orderRepository->create($data);
        Mail::to($order->customer)->send(new OrderCreated($order));
        return $order;
    }
}

빠른 체크리스트

Before implementing the Repository Pattern, ask yourself:

  • 내 컨트롤러가 데이터베이스 쿼리를 직접 수행하고 있나요?
  • 같은 쿼리를 여러 곳에서 중복하고 있나요?
  • 데이터베이스 없이 내 코드를 테스트하기가 어려운가요?
  • Eloquent / Query Builder / Raw SQL 사이를 쉽게 전환하고 싶나요?
  • 단순 CRUD 앱보다 더 복잡한 것을 만들고 있나요?

If you answered yes to 2+ questions, the Repository Pattern will help you!

결론

Repository 패턴은 단순한 CRUD 앱에 항상 필요한 것은 아니지만, 애플리케이션이 성장하면 매우 귀중해집니다. 이것은 다음을 제공합니다:

  • 깨끗하고 테스트 가능한 코드
  • 중앙 집중식 데이터 접근 로직
  • 데이터 소스를 변경할 수 있는 유연성
  • 더 나은 관심사의 분리

작게 시작하세요 – 가장 복잡한 모델부터 구현하고, 필요에 따라 확장하세요.

전체 심층 분석을 원하시나요?

이것은 요약본입니다! 전체 가이드를 보려면:

  • ✨ 더 고급 예제 (캐싱, 서비스‑layer 통합)
  • ✨ 실제 블로그 시스템 구현
  • ✨ 완전한 테스트 전략
  • ✨ 전자상거래 주문 관리 예제

Medium에서 전체 기사 읽기:

👉 Repository Pattern in Laravel: From Problem to Solution

더 많은 Laravel 팁을 원하시면 팔로우해주세요:

👉 masteryoflaravel on Medium

Repository Pattern에 대한 여러분의 경험은 어떠신가요? 좋아하시나요? 아니면 싫어하시나요? 댓글로 이야기 나눠요! 💬

Back to Blog

관련 글

더 보기 »

Laravel FAQ (초보자부터 고급까지)

Laravel가 여전히 중요한 이유와 이 FAQ가 해결하는 문제 Laravel은 안전하고 유지보수가 용이한 PHP 애플리케이션을 가장 빠르게 배포할 수 있는 방법 중 하나입니다 — 간단한 사이트부터…