Active Record vs Repository/Service в Laravel
Glossary overview

Active Record vs Repository/Service в Laravel

1. Active Record – когда модель сама все делает

Это стандартный подход в Laravel. Модель – это не просто структура данных. Это объект, который умеет сам работать с базой. То есть ты обращаешься к ней, и она уже знает, что делать.

Пример максимально базовый:

User::where('is_active', true)->get();

Ты не пишешь SQL, не дергаешь отдельные классы. Модель User сама формирует запрос.

То же самое со связями:

public function orders()
{
    return $this->hasMany(Order::class);
}

И дальше ты спокойно пишешь:

$user->orders()->latest()->get();

Можно писать полноценный запросы:

class Product extends Model
{
public static function getPriceRange(array $categoryIds, array $filters = []): object
    {
        return static::query()
            ->active()
            ->forCategories($categoryIds)
            ->filter($filters)
            ->selectRaw('MIN(price) as min_price, MAX(price) as max_price')
            ->first();
    }
}

Почему Active Record всем заходит в начале

Плюсы:

– быстро писать
– легко читать код
– меньше файлов и абстракций
– отлично подходит для небольших и средних проектов

Но есть момент. Пока проект маленький – все ок. Когда логики становится много, модель начинает раздуваться. И вот тут начинаются проблемы.

2. Repository / Service pattern – когда все выносят наружу

Этот подход появляется, когда Active Record уже начинает мешать. Когда в модели не только связи, но и куча бизнес-логики, фильтров, условий, разных сценариев.

Суть простая:

– модель хранит только данные и базовые связи
– вся логика запросов уходит в отдельные классы

Например:

class ProductRepository
{
    public function getActiveByCategories(array $categoryIds)
    {
        return Product::whereIn('category_id', $categoryIds)
            ->where('is_active', true)
            ->get();
    }
}

А в контроллере уже так:

$products = $productRepository->getActiveByCategories($categoryIds);

Или еще дальше – через сервис:

class ProductService
{
    public function getCatalog(array $categoryIds)
    {
        return Product::whereIn('category_id', $categoryIds)
            ->where('is_active', true)
            ->with('images')
            ->get();
    }
}

Тут уже видно, что логика начинает жить отдельно от модели.

Зачем вообще усложнять

Потому что появляется контроль. Ты можешь:

– переиспользовать логику
– тестировать отдельно
– не засорять модели
– легче менять реализацию

Но есть и обратная сторона. Кода становится больше. Иногда сильно больше. И для простых проектов это реально оверкилл.

Главное отличие, если коротко

Active Record:

– модель сама работает с БД
– быстро и просто
– меньше структуры

Repository / Service:

– логика вынесена в отдельные классы
– больше контроля
– больше кода и структуры

Как обычно делают на практике

И вот тут важный момент. Почти никто не выбирает что-то одно в чистом виде.

Обычно:

– простые вещи – через Active Record
– сложная бизнес-логика – через сервисы

То есть сначала пишешь быстро, потом, когда начинает разрастаться, выносишь куски в сервисы. Не наоборот.

И это, если честно, самый живой и рабочий подход. Без фанатизма.