Я изучаю DDD, и я думаю о создании исключений в определенных ситуациях. Я понимаю, что объект не может войти в плохое состояние, поэтому здесь исключения хороши, но во многих примерах исключения также создаются, например, если мы пытаемся добавить нового пользователя с существующей электронной почтой в базе данных.
public function doIt(UserData $userData)
{
$user = $this->userRepository->byEmail($userData->email());
if ($user) {
throw new UserAlreadyExistsException();
}
$this->userRepository->add(
new User(
$userData->email(),
$userData->password()
)
);
}
Таким образом, если пользователь с таким адресом электронной почты существует, то в сервисе приложения мы можем поймать исключение, но мы не должны контролировать работу приложения с помощью блока try-catch.
Как лучше для этого?
Ответы:
Давайте начнем с краткого обзора проблемного пространства. Одним из фундаментальных принципов DDD является размещение бизнес-правил как можно ближе к местам, где их необходимо применять. Это чрезвычайно важная концепция, потому что она делает вашу систему более «связной». Перемещение правил «вверх» обычно является признаком анемичной модели; где объекты - просто пакеты данных, и правила вводятся в эти данные для обеспечения их выполнения.
Анемичная модель может иметь большой смысл для разработчиков, только начинающих с DDD. Вы создаете
User
модель и объект,EmailMustBeUnqiueRule
который получает необходимую информацию для проверки электронной почты. Просто. Элегантный. Проблема в том, что этот «вид» мышления носит принципиально процедурный характер. Не ДДД. В конечном итоге, у вас остается модуль с десяткамиRules
аккуратно завернутых и инкапсулированных, но они полностью лишены контекста до такой степени, что их уже нельзя изменить, поскольку неясно, когда и где они применяются. Имеет ли это смысл? Это может быть само собой разумеющимся , чтоEmailMustBeUnqiueRule
будет применяться на созданиеUser
, но какUserIsInGoodStandingRule
?. Медленно, но верно, зернистость извлеченияRules
вне их контекста оставляет вас систему, которую трудно понять (и, следовательно, нельзя изменить). Правила должны быть заключены в капсулу только в том случае, если фактическое сжатие / выполнение настолько многословно, что ваша модель начинает терять фокус.Теперь перейдем к вашему конкретному вопросу: проблема с
Service
/CommandHandler
throwException
заключается в том, что ваша бизнес-логика начинает вытекать («вверх») из вашего домена. Почему вашеService
/CommandHandler
нужно знать, что электронная почта должна быть уникальной? Уровень прикладных услуг обычно используется для координации, а не для реализации. Причина этого может быть проиллюстрирована просто, если мы добавимChangeEmail
метод / команду в вашу систему. Теперь обоим методам / обработчикам команд необходимо будет включить вашу уникальную проверку. Это где разработчик может соблазн "извлечь"EmailMustBeUniqueRule
. Как объяснено выше, мы не хотим идти по этому пути.Некоторые дополнительные знания могут привести нас к более DDD ответу. Уникальность электронной почты - это инвариант, который должен применяться в коллекции
User
объектов. Есть ли в вашем домене понятие, представляющее «коллекциюUser
объектов»? Я думаю, вы, вероятно, можете видеть, куда я иду.Для этого конкретного случая (и многих других, связанных с применением инвариантов в коллекциях) лучшее место для реализации этой логики будет в вашем
Repository
. Это особенно удобно, потому что выRepository
также «знаете» дополнительную часть инфраструктуры, необходимую для выполнения такого рода проверки (хранилище данных). В вашем случае я бы разместил эту проверку вadd
методе. Это имеет смысл правильно? Концептуально, именно этот метод действительно добавляетUser
вашу систему. Хранилище данных - это деталь реализации.источник
Moving rules "upwards" is generally a sign of an anemic model
? Вверх означает, что на прикладном уровне? или к доменной модели?Email
модель должна представлять собой пакет данных, который проверяется на наличие инвариантов перед отправкой. См .: softwareengineering.stackexchange.com/questions/372338/…User
), либо признать, что этот конкретный вид инварианта не будет аккуратно инкапсулирован в вашем домене. Первый представляет собой разделение данных и поведения, что приводит к процессуальной парадигме. Это не идеально. Проверка должна существовать с данными, а не вокруг данных. Кроме того, какая польза от создания доменной службы, которая служит только для проверки этого единственного правила? Якобы этот сервис ...Repository
,User
и этот инвариант и, следовательно, не достигает пуристического видения, к которому вы все равно стремитесь. Реализации репозитория являются частью домена, и, следовательно, могут быть наделены определенными обязанностями.Вы можете наложить валидацию на каждом уровне вашего приложения. Где навязывать, какие правила зависят от вашего приложения.
Например, сущности реализуют методы, которые представляют бизнес-правила, в то время как варианты использования реализуют правила, специфичные для вашего приложения.
Если вы создаете службу электронной почты, такую как Gmail, можно утверждать, что правило «пользователи должны иметь уникальный адрес электронной почты» является бизнес-правилом.
Если вы строите процесс регистрации для новой системы CRM, это правило, вероятно, лучше всего реализовать в сценарии использования, поскольку это правило также может быть частью вашего варианта использования. Кроме того, это также может быть проще для тестирования, так как вы можете легко заблокировать вызовы репозитория в ваших тестах вариантов использования.
Для дополнительной безопасности вы также можете применить это правило в своем хранилище.
источник