# доменные объекты: почему сущности пухнут
Все знают что объекты должны быть маленькими, соблюдать принцип единой ответственности и все в таком роде. Так же мы не должны так уж часто менять их, предпочитая изменениям расширение (через композицию и отдельные точки расширения). Эти критерии разработчики часто отждествляют с поддерживаемы кодом, и даже смеют заявлять что "это по СОЛИДу".
Есть еще один критерий - логика по работе с данными должна быть там, где эти данные собственно живут. Это главное определение инкапсуляции. С этим критерием начинаются сложности, так как в нашей системе данные обычно сгруппированы в сущности. Когда разработчики начинают класть логику в сущности довольно часто они попадают в ситуацию что соблюдение инкапсуляции ломает остальные правила, объекты начинают пухнуть от логики. Мы явно нарушаем принцип единой ответственности и в целом "не менять код а расширять его" становится почти невозможно.
Как же так выходит и можно ли подружить все наши "best practices" вместе?
## Именование и выделение ролей
Один из факторов, которые сильно влияют на принятие решений это нейминг. Люди почему-то сильно недооценивают этот фактор. В СНГ это можно было бы объяснить нехваткой словарного запаса и недостаточным уровнем английского языка. Однако такая проблема есть и у нэйтив спикеров, так что скорее всего проблема кроется в чем-то другом.
Люди по природе своей любят обобщать. Мы любим использовать общие термины при обсуждении требований - "юзер перешел в карзину", "юзер оформил заказ", "юзер пригласил друга". Подобное обобщение приводит к весьма интересным эффектам как в коде так и с точки зрения приоритизации требований.

Если согласно этому разделению мы сделаем одну единственную сущность User которая обслуживает все эти юзкейсы, мы можем получить классический god object. Ситуацию в которой один и тот же объект используется по всему проекту и обслуживает огромное количество юзкейсов.
Однако не все "пользователи" выполняют одну и ту же роль. Если мы начнем разбираться начнут всплывать детали поведения - одни пользователи пассивны и после регистрации уходят с сайта. Есть пользователи которые регулярно пользуются определенными услугами и т.д. Пользователи, которые приглашают своих друзей и учавствуют в реферальной программе явно имеют свою роль - Referrer. Пользователи, которые получают услуги - Customers. Пользователи которые пользуются услугами постоянно - Regular Customers.

Более детальное разделение ролей больше необходимо для приоритизации юзкейсов. Однако и для разработчиков это дает определенные подсказки для декомпозиции стэйта системы.
## Варианты разделения сущностей
Одна из проблем произрастает из классической UML конструкции описывающей агрегацию (has a). Если у покупателя есть история заказов, значит у класса Customer должна быть коллекция оных. Или же если у пользователя есть email и пароль то логично предположить что они входят в один объект.
Рассмотрим последний пример чуть подробнее. У нас есть пользователь, который логинится по email + password. При регистрации он должен подтвердить email. Если мы 3 раза вводим пароль неправильно, то мы должны выслать подтверждение на email и заблокировать логин. При смене email-а мы так же должны дождаться подтверждения нового адреса.
```php
class User
{
const MAX_ATTEMPTS = 3;
private string $id;
private string $email;
private ?string $emailConfirmationToken;
private ?string $newEmail;
private string $password;
private int $failedLoginAttempts = 0;
private ?string $accountOwnershipToken;
private ?\DateTimeImmutable $lastLoginAttempt;
private ?\DateTimeImmutable $lastLogin;
public function login(string $password): boolean
{
$this->lastLoginAttempt = new \DateTimeImmutable();
if ($this->failedLoginAttempts > self::MAX_ATTEMPTS) {
}
if (!password_verify($password, $this->password)) {
$this->failedLoginAttempts++;
return false;
}
$this->failedLoginAttempts = 0;
return true;
}
public function resetLoginAttempts()
{
$this->failedLoginAttempts = 0;
}
public function changePassword()
{
}
}
class ChangePasswordUseCase
{
public function __construct(
private UserRepository $users
) {}
public function __invoke(ChangePassword $request)
{
$user = $this->users->get($request->userId);
if (!password_verify($request->currentPassword, $user->getPassword(), PASSWORD_DEFAULT)) {
$user->incrementFailedLoginAttempts();
if ($user->)
}
$user->setPassword(password_hash($request->newPassword, PASSWORD_DEFAULT));
}
}
class
```
Многие разработчики отождествляют доменный объект с записью в табличке, которую менеджит их ORM. Буду откровенен - для меня это так же была проблема которая не давала мне разобраться с этой проблемой на протяжелнии как минимум 3-х лет.
## Принадлежность данных и поведения

У заказа есть покупатель. Классическая конструкция из UML описывающая аргегацию (has a), вроде бы все хорошо. Но эти штуки должны еще как-то мэпиться на нашу базу, и мы хотим целостность и все такое. Стандартная практика сделать FK между табличкой с покупателями и заказами. Так же как правило ORM позволяют реализовывать такие связи путем референса на сущность. В итоге в объекте Order появляется объект Customer.