Введение
Domain-Driven Design — это архитектурный подход, в котором бизнес-логика становится основой кода приложения, а все ключевые части системы описываются так, как это реально устроено у бизнеса. В Laravel DDD особенно полезен для больших проектов с меняющимися требованиями и сложной бизнес-логикой. Разберём ключевые принципы и представим, как на практике устроить DDD-проект на Laravel.
Принципы DDD
- Единый язык (ubiquitous language) — бизнес и разработчики используют одни и те же термины для описания системы. В коде эти термины отражаются в названиях сущностей, сервисов и событий.
- Ограниченные контексты (bounded context) — сложную систему разбивают на отдельные области ответственности, где каждое понятие имеет свой смысл.
- Валидация бизнес-правил — централизуйте бизнес-логику, чтобы из разных частей системы не возникало противоречий.
- Моделирование предметной области — определите, что важно для вашей компании, и делайте эти сущности первоклассными жителями кода (например, Order, Invoice, Product).
Архитектура DDD в Laravel-проектах
Стандартная архитектура Laravel подходит для быстрого старта, но при усложнении проекта становится трудно масштабировать и обслуживать бизнес-логику. В DDD проекте архитектуру лучше строить по слоям:
- Domain — бизнес-объекты, сущности, value-объекты, агрегаты.
- Application — сервисы, реализующие пользовательские сценарии, операции.
- Infrastructure — работа с внешними системами (БД, очереди, API), реализация репозиториев.
- Http — контроллеры, которые обрабатывают запросы, вызывают Application сервисы и возвращают ответы.
Структура и примеры кода
Структура может выглядеть так:
app/
├── Domain/
│ ├── Order/
│ │ ├── Entities/
│ │ ├── ValueObjects/
│ │ ├── Services/
│ ├── User/
├── Application/
│ ├── Services/
│ ├── DTO/
├── Infrastructure/
│ ├── Repositories/
│ ├── Services/
│ ├── LaravelAdapters/
├── Http/
│ ├── Controllers/
Объяснения:
- Внутри Domain описаны объекты предметной области — сущности, value объекты, сервисы бизнес-логики.
- Application реализует координаторов (use-cases), которые разруливают сценарии для конечных пользователей.
- Infrastructure отвечает за интеграцию — БД, очереди, логирование, работу с Eloquent и внешними API.
- Http — обычные контроллеры Laravel, максимально тонкие, только делегируют работу в Application слой.
Bounded Contexts и примеры
Например, у интернет-магазина есть контексты «Заказы», «Пользователь», «Каталог». В каждом из них свой набор бизнес-объектов, правил и даже значение терминов («Товар» для склада и «Товар» для клиента могут отличаться).
Пример value object в Laravel:
// app/Domain/Order/ValueObjects/OrderStatus.php
namespace App\Domain\Order\ValueObjects;
class OrderStatus {
private string $status;
public function __construct(string $status) {
if (!in_array($status, ['new', 'paid', 'shipped', 'cancelled'])) {
throw new \InvalidArgumentException('Invalid order status');
}
$this->status = $status;
}
public function get(): string { return $this->status; }
}
Пример Entity:
// app/Domain/Order/Entities/Order.php
namespace App\Domain\Order\Entities;
use App\Domain\Order\ValueObjects\OrderStatus;
class Order {
private OrderStatus $status;
public function __construct(OrderStatus $status) {
$this->status = $status;
}
public function pay() {
// бизнес-логика перехода статуса
$this->status = new OrderStatus('paid');
}
}
Реализация паттерна в Laravel
- Сервис-провайдеры Laravel пригодятся для подключения Application и Domain сервисов через DI.
- Контроллеры — делают минимум: принимают запрос, вызывают Application сервис, возвращают ответ.
- Eloquent может использоваться как инфраструктурный слой: маппит Domain объекты на таблицы БД, не содержит бизнес-логики.
Пример Application Service:
namespace App\Application\Services;
use App\Domain\Order\Entities\Order;
use App\Domain\Order\ValueObjects\OrderStatus;
use App\Infrastructure\Repositories\OrderRepository;
class OrderService {
private $orderRepository;
public function __construct(OrderRepository $orderRepository) {
$this->orderRepository = $orderRepository;
}
public function payOrder($orderId) {
$order = $this->orderRepository->find($orderId);
$order->pay();
$this->orderRepository->save($order);
}
}
Контроллер:
namespace App\Http\Controllers;
use App\Application\Services\OrderService;
class OrderController extends Controller {
private $orderService;
public function __construct(OrderService $orderService) {
$this->orderService = $orderService;
}
public function pay($orderId) {
$this->orderService->payOrder($orderId);
return response()->json(['status' => 'ok']);
}
}
Тестирование домена
Domain-слой легко тестировать отдельно от инфраструктуры — пишите unit-тесты на бизнес-логику и value-объекты без обращения к БД. Для интеграционных тестов удобно использовать фабрики и моки.
Проблемы и советы
- Типичные ошибки: перенос бизнес-логики в Eloquent модели, отсутствие разделения контекстов, избыточная усложненность структуры.
- Рекомендации: внедряйте DDD постепенно, сначала вынесите бизнес-логику из контроллеров и инфраструктуры, обособьте контексты, заставьте бизнес и разработку “говорить на одном языке”.
Заключение
DDD — мощный инструмент для построения больших Laravel-проектов с устойчивой архитектурой и прозрачными бизнес-процессами. Главное — моделируйте домен, выделяйте границы и сохраняйте бизнес-логику в центре приложения, окружая её технической инфраструктурой.