Ответ
Доменный слой — это ядро приложения, содержащее бизнес-логику, правила и сущности предметной области. Он полностью независим от инфраструктуры (БД, HTTP, фреймворка) и слоя представления. В моих проектах на PHP (часто с DDD-подходом) он включает:
- Сущности (Entities): Объекты с идентификатором, жизненным циклом (например,
User,Order). - Объекты-значения (Value Objects): Неизменяемые объекты без идентификатора (например,
EmailAddress,Money). - Агрегаты (Aggregates): Кластер связанных сущностей и VO с корневым агрегатом (например,
Orderкак корень сOrderItemвнутри). - Доменные сервисы (Domain Services): Логика, не принадлежащая естественным образом какой-либо сущности.
- События домена (Domain Events): Факты, произошедшие в домене (например,
OrderWasPlaced).
Пример сущности Order с бизнес-правилами:
class Order
{
private string $id;
private OrderStatus $status;
/** @var OrderLine[] */
private array $lines = [];
private ?DateTimeImmutable $paidAt = null;
private function __construct(string $id) {
$this->id = $id;
$this->status = OrderStatus::NEW;
}
public static function create(string $id): self {
return new self($id);
}
public function addLine(Product $product, int $quantity): void {
// Доменное правило: нельзя изменить оплаченный заказ
if ($this->status === OrderStatus::PAID) {
throw new DomainException('Cannot modify a paid order.');
}
// Доменное правило: минимальное количество
if ($quantity < 1) {
throw new DomainException('Quantity must be at least 1.');
}
$this->lines[] = new OrderLine($product, $quantity);
}
public function markAsPaid(): void {
// Доменное правило: оплатить можно только новый заказ
if ($this->status !== OrderStatus::NEW) {
throw new DomainException('Only new orders can be paid.');
}
$this->status = OrderStatus::PAID;
$this->paidAt = new DateTimeImmutable();
$this->recordEvent(new OrderPaid($this->id)); // Генерация события
}
public function totalAmount(): Money {
// Вычисление на основе правил домена
$total = Money::USD(0);
foreach ($this->lines as $line) {
$total = $total->add($line->subtotal());
}
return $total;
}
}
Главный принцип: Доменный слой — это самая важная и стабильная часть. Его можно протестировать изолированно, без БД или веб-сервера, что я и делаю с помощью юнит-тестов.
Ответ 18+ 🔞
Давай разжую, что такое этот ваш доменный слой, а то некоторые думают, что это просто папка Domain/ в проекте, куда скидывают всё подряд, как в помойку.
Представь, что твоё приложение — это мужик. Так вот, доменный слой — это его мозг и характер. Всё, что он знает про мир (как работает заказ, что такое деньги, кто такой пользователь) и все его принципы («я не работаю по выходным», «не дам в долг больше тысячи»). А всё остальное — база данных, HTTP-запросы, красивые кнопки на фронте — это уже его руки, ноги и кошелёк. Мозг должен работать, даже если кошелёк украли, а руки отрубили. Понимаешь? Полная независимость, ёпта.
Вот из чего этот мозг состоит, если ковырять:
- Сущности (Entities): Это главные герои, у которых есть паспорт (ID) и своя история.
Пользователь,Заказ. Они живут, меняются, но остаются собой. - Объекты-значения (Value Objects): Это как купюры или почтовые адреса.
EmailAddress,Money. Две сотни рублей — это те же две сотни рублей, неважно, какие у них серийные номера. Они неизменяемые, и это, блядь, очень важно. - Агрегаты (Aggregates): Это когда несколько сущностей сбиваются в кучку под предводительством одного главного — корня агрегата. Например,
Заказ(корень) и внутри негоПозицииЗаказа. Трогать позиции можно только через заказ, иначе получится пиздец и несогласованность. - Доменные сервисы (Domain Services): Иногда появляется логика, которая не лезет ни в одну сущность. Ну типа «перевести деньги с одного счёта на другой». Это не поведение счёта и не поведение пользователя — это отдельная операция. Вот для этого они.
- События домена (Domain Events): Это когда в домене что-то охуенное или не очень произошло, и надо об этом громко объявить.
ЗаказБылОплачен. Чтобы другие части мозга (или даже другие системы) отреагировали: отправили смс, начислили бонусы.
А теперь смотри, как это выглядит в коде, без всей этой вашей шелухи инфраструктуры. Чистая бизнес-логика, ядрёна вошь!
class Order
{
private string $id;
private OrderStatus $status;
/** @var OrderLine[] */
private array $lines = [];
private ?DateTimeImmutable $paidAt = null;
private function __construct(string $id) {
$this->id = $id;
$this->status = OrderStatus::NEW;
}
public static function create(string $id): self {
return new self($id);
}
public function addLine(Product $product, int $quantity): void {
// Доменное правило номер раз: в оплаченный заказ нихуя нельзя пихать!
if ($this->status === OrderStatus::PAID) {
throw new DomainException('Cannot modify a paid order.');
}
// Правило два: ну ты хоть одну штуку-то закажи, а?
if ($quantity < 1) {
throw new DomainException('Quantity must be at least 1.');
}
$this->lines[] = new OrderLine($product, $quantity);
}
public function markAsPaid(): void {
// А вот так: оплатить можно только свежий заказ, а не тот, который уже отменили.
if ($this->status !== OrderStatus::NEW) {
throw new DomainException('Only new orders can be paid.');
}
$this->status = OrderStatus::PAID;
$this->paidAt = new DateTimeImmutable();
$this->recordEvent(new OrderPaid($this->id)); // Бдыщ! И событие выстрелило
}
public function totalAmount(): Money {
// И тут не просто сложение, а могут быть свои заморочки: скидки, налоги.
$total = Money::USD(0);
foreach ($this->lines as $line) {
$total = $total->add($line->subtotal());
}
return $total;
}
}
И главная мысль, которую ты должен вынести, чувак: этот слой — святая святых. Его можно взять и протестировать голым, без базы данных, без веб-сервера, на голом PHPUnit. Просто создал объекты и проверяешь, работают ли твои бизнес-правила. Если эта часть работает, значит, ядро твоего приложения живое. А всё остальное — просто обвязка, которую можно хоть завтра выкинуть и переписать. Доверия к инфраструктуре — ебать ноль, а к домену — сто пудово.