DDD, PHP. Доменный объект и бизнес-логика
В последнее время я был очень занят попытками понять понятия ddd и Model layer. Прочитайте тонны статей, примеров, вопросов и ответов, потратили на это много часов. И все же я не уверен, правильно ли я понял некоторые принципы.
Одним из них является ответ на вопрос: сколько бизнес-логики должно существовать в доменных объектах? В некоторых источниках говорится, что доменные объекты должны быть связаны с целой бизнес-логикой, с другой стороны, я натолкнулся на статьи, в которых я предполагал, что они должны быть как можно меньше и представлять только их значения. Это меня действительно смущает.
В моем понимании, доменные объекты - это классы, которые представляют сущности в домене.
Итак, давайте, например, перейти с сущностью Invoice. Каждый счет состоит из его пунктов. Чтобы вычислить стоимость счета-фактуры, мы должны суммировать все значения элементов (это очень простой пример, в реальном мире были бы случаи, такие как добавление налога, вычисление оплаченной стоимости и т. Д.)
class Invoice
{
public $id;
public $items = [];
public $status;
const STATUS_PAID = 'paid';
const STATUS_NOT_PAID = 'not_paid';
public function isPaid()
{
return $this->status == self::STATUS_PAID;
}
public function getInvoiceValue()
{
$sum = 0;
foreach($this->items as $item) {
$sum += $item->value;
}
return $sum;
}
}
В моем понимании метод isPaid() находится в нужном месте. Это относится к его собственным данным. Но я не уверен насчет getInvoiceValue(). Мы работаем здесь на других объектах домена.
Может быть, нам следует использовать доменные объекты только для представления данных, но использовать некоторые декораторы для выполнения более сложных задач?
Заранее спасибо.
3 ответа
Сколько бизнес-логики должно существовать в доменных объектах? [...] Я наткнулся на статьи, в которых предполагал, что он должен быть как можно меньше и представлять только его значения.
Остерегайтесь модели анемичной области, которая почти исключительно состоит из данных и не имеет поведения. DDD - это создание модели домена с богатым поведением. Таким образом, можно добавлять логику в классы домена.
DDD делает упор на хорошем объектно-ориентированном дизайне, объединяя методы и данные, тем самым продвигая высокосвязные системы.
Я не уверен, есть ли правильный ответ на такие вопросы, потому что применение DDD действительно зависит от конкретной области, к которой вы его применяете. Есть места, где ваша реализация могла бы быть абсолютно правильной, если она удовлетворяет бизнес-потребностям. В других, как вы упомянули с налогами и т.п., это не так. Поэтому я бы сказал, что вам нужно задавать вопросы о вашем домене, чтобы полностью понять, что вам нужно, прежде чем переводить их в код.
Сказав это, если у вас есть более сложный сценарий, который требует некоторых дополнительных знаний внешнего мира, чтобы придать значение счета, одним из вариантов будет явное представление этого в вашем домене. В вашем примере это может быть InvoiceProducer, который может иметь такую подпись:
class InvoiceProducer {
public function __construct(TaxProvider $taxProvider) {
$this->taxProvider = $taxProvider;
}
public function invoiceFor(array $items) {
new Invoice($items, $this->calculateValue($items));
}
private function calculateValue(array $items) {
$sum = array_reduce($items, function($acc, $item){
$acc += $item->value;
}
return $this->taxProvider->applyTaxTo($sum);
}
}
Другим вариантом будет использование какого-либо шаблона стратегии, который сделает вашу реализацию очень похожей на ту, что есть сейчас, но вы примете вызов так, как вы хотите рассчитать налогообложение:
public function getInvoiceValue(TaxProvider $taxProvider)
{
$sum = 0;
foreach($this->items as $item) {
$sum += $item->value;
}
return $taxProvider->applyTaxFor($sum);
}
Опять же, это действительно зависит от того, как работает ваш конкретный домен, но, как вы можете видеть, реализация не должна быть такой уж сложной. Подробнее о том, как все это вписывается в ваш домен.
Сколько бизнес-логики должно существовать в доменных объектах?
Все это (если возможно). Смысл DDD заключается в том, чтобы захватить вашу бизнес-логику в вашем домене - здесь можно использовать различные тактические шаблоны (агрегаты, сущности, объекты-ценности, доменные службы и т. Д.).
Доменные объекты - это классы, которые представляют сущности в домене.
Классы в вашем домене могут представлять больше, чем просто сущности. Агрегаты, сущности, объекты-значения, доменные службы и т. Д. Могут быть представлены классами в вашем домене.
Но я не уверен насчет getInvoiceValue(). Мы работаем здесь на других объектах домена.
Пример, который вы приводите для Invoice, является классическим примером Aggregate - Invoice будет содержать InvoiceItems. getInvoiceValue() в порядке здесь.
В нашем случае Счет является совокупным. Совокупный корень - это сам счет, но это также и сущность, верно? Он имеет свою собственную идентификацию (номер счета, который является уникальным).
да исправить
Что такое InvoiceItems? Могу ли я получить их напрямую из репозитория InvoiceItem (если я должен их создать), или мне всегда нужно работать только с Aggregate?
Это зависит от вашего варианта использования. Это помогает разделить модели записи и чтения (CQRS). Если вы говорите о стороне чтения (т.е. отчетности), то вы обходите модель домена и имеете объекты, которые представляют вашу модель чтения. Это может быть просто запрос к базе данных. Если вы говорите о вашей стороне записи (то есть командах, домене), то у вас обычно будет хранилище для каждого корня агрегата. Каковы ваши общие корни - вопрос моделирования. Вы хотите создать их таким образом, чтобы они обеспечивали соблюдение всех ваших бизнес-правил - в вашем примере, если счет-фактура требует загрузки InvoiceItems для обеспечения соблюдения правил (например, "не более 5 элементов на счет-фактуру"), тогда да, они должны быть загружается через совокупный корень