2. Основные понятия
Контекст - ограниченная зона ответственности.
Сущность - модель объекта предметной области.
Сводный корень - сущность, предоставляющая общий
API.
Объект-значение - свойство объектов предметной
области.
Службы - операции характерные для предметной
области.
4. Hello world!
Система управления складом.
Задачи:
1) Упорядочить прием товаров на склад.
2) Организовать систему заявок на выдачу.
3) Оповещать менеджмент о нехватке
товаров.
5. Первый взгляд
Как я представляю
склад:
Т
О
В
А
Р
Ы
Склад
М
А
Г
А
З
И
Н
Как Geek&Poke
видят склад:
6. Единый язык
Система терминов, понятная как IT
специалисту, так и эксперту в предметной
области.
ent?
m
Ship
Goods
?
Ite
Product!
m?
7. Доменный эксперт говорит что...
“Поставщики привозят на склад контейнеры
с товарами.”
Поставщик
Контейнер
Склад
Поставщик
Контейнер
8. Доменный эксперт говорит что...
“В каждом контейнере могут быть разные
товары и в разном количестве, кроме того у
каждого товара есть срок годности.”
Контейнер
Товар:
Продукт:
Товар:
- название;
Товар:
- название;
- срок год.;
- название;
- срок год.;
- срок год.
Товар:
- название;
- срок год.
Товар:
Товар:
- название;
- название;
- срок год.;
- срок год.
9. Доменный эксперт говорит что...
“Когда товар попадает на склад, мы
записываем дату поступления.”
Склад
Товар:
Товар:
- название;
Товар:
- название;
- срок год.;
- название;
- срок год.;
- дата пос.
- срок год.;
- дата пос.
- дата пос.
11. Пишим код ..! Product
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
class Product
{
public function __construct(Name $name, DateTime $expirationDate
)
{
$this->name = $name;
$this->expirationDate = $exipationDate
;
}
public function accept(DateTime $deliveryDate)
{
$this->deliveryDate = $deliveryDate;
return $this;
}
public function isExpired()
{
return $this->expirationDate > new DateTime();
}
}
12. Пишим код ..! Container
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class Container
{
private $products;
public function __construct(array $products)
{
Assertion
::allIsInstanceOf
($products, 'Product');
$this->products = $products;
}
public function getProducts()
{
return $this->products;
}
}
13. Пишим код ..! Supplier
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class Supplier
{
public function sendProducts(Warehouse $receiver, array $products)
{
$this->sendContainer($receiver, new Container($products));
}
public function sendContainer
(Warehouse $receiver, Container $container)
{
$receiver->acceptContainer
($container);
}
}
14. Пишим код ..! Warehouse
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
class Warehouse
{
private $products;
public function acceptContainer
(Container $container)
{
$now = new DateTime();
$products = $container->getProducts();
foreach ($products as $product) {
$name = $product->getName();
$this->products[$name][] = $product->accept($now);
}
}
}
15. Доменный эксперт говорит что...
“Кажется мы забыли упомянуть что у нас
два склада, и еще один строится рядом с
кольцевой.”
Склад
- номер
- адрес
- статус
Склад
- номер
- адрес
- статус
Склад
- номер
- адрес
- статус
16. Рефакторим ..! Warehouse.
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class Warehouse implements Entity
{
private $id;
private $address;
private $status;
public function __construct(WarehouseId $id, Address $address, Status $status = null)
{
$this->id = $id;
$this->address = $address;
$this->status = $status ?: new Status(Status::CLOSED);
}
public function acceptContainer
(Container $container)
{
if ($this->status->isClosed()) {
throw new Exception("Warehouse closed"
);
}
...
}
17. Объекты-значения
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class Status
{
const CLOSED = 0;
const OPENED = 1;
1) Равенство объектов-значений
основано на равенстве их полей.
private $value;
public function __construct($value)
{
$this->value = $value;
}
3) Строковое представление
объектов-значений должно быть
однозначно.
public function isClosed()
{
return self::CLOSED === $this->value;
}
}
2) Объекты-значения неизменны.
4) Объекты значения могут хранить
не только примитивы, но и другие
объекты и даже сущности.
18. Сущности
1) Равенство сущностей основано
на равенстве их идентификаторов.
Кстати, неплохо было бы
знать какой именно
поставщик нам привез эти
помидоры.
2) Основная единица бизнеслогики.
3) Имена сущностей и их методы
должны иметь смысл в контексте
единого языка.
20. Система управления складом
1) Упорядочить прием товаров на склад.
2) Организовать систему заявок на выдачу.
3) Оповещать менеджмент о нехватке
товаров.
21. Доменный эксперт говорит что...
“Магазины подают заявки на получение
товаров со склада.”
Заявка
Магазин
Склад
Заявка
22. Доменный эксперт говорит что...
“В заявке указаны требуемые наименования
товаров и их количество.”
Заявка
- номер;
- товар => кол-во.;
- товар => кол-во.;
- товар => кол-во.;
- товар => кол-во.;
- ...
Ну и, естественно, мы не
отгружаем товары с
истекшим сроком годности.
Их надо сразу списывать.
23. Доменный эксперт говорит что...
“Если на складе достаточно товаров, то
заявке одобряется и машина с товарами
отправляется в магазин.”
Заявка
Магазин
Склад
Машина
24. Пишим код ..! Receipt
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class Receipt implements Entity
{
public function __construct(ReceiptId $id, Shop $receiver)
{
$this->id = $id;
$this->receiver = $receiver;
}
public function addRequire(Name $name, $count)
{
Assertion
::integer($count);
Assertion
::greater($count, 0);
$this->requires[$name] = $count;
}
public function getList()
{
return $this->requires;
}
}
25. Пишим код ..! Shop
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class Shop implements Entity
{
private $id;
private $address;
public function __construct(ShopId $id, Address $address)
{
$this->id = $id;
$this->address = $address;
}
public function createReceipt
(array $products)
{
$receipt = new Receipt(new ReceiptId(new DateTime), $this);
foreach ($products as $name => $count) {
$receipt->addRequire(new Name($name), $count);
}
return $receipt;
}
}
26. Пишим код ..! Warehouse
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
class Warehouse implements Entity
{
public function resolveReceipt
(Receipt $receipt)
{
$toSend = [];
foreach ($receipt->getList() as $name => $count) {
$available = $this->getAvailable($name);
if ($count > count($available)) {
throw new AvailableException
($name, $available, $count);
}
$needed = array_slice($available, 0, $count);
$toSend = array_merge($toSend, $needed);
$this->writeOff($needed, new Reason('Resolved for receipt №'
.$receipt->getId()));
}
$receipt->resolve(new DateTime());
return new Car($toSend);
}
}
27. Сводный корень
А как изменять статус
заявки?
Сущность Warehouse производит
все бизнес-операции с сущностью
Receipt и отвечает за изменение ее
статуса: одобрено или отложено.
В данном случае сущность
Warehouse является сводным
корнем.
В различных контекстах возможны
разные сводные корни, например
Receipt может быть сводным
корнем для Product.
28. Контекст
В данном примере мы имеем два
распределенных контекста: поставщиксклад и склад-магазин.
Поставщик
Контейнер
Заявка
Магазин
Склад
Поставщик
Контейнер
Машина
29. Система управления складом
1) Упорядочить прием товаров на склад.
2) Организовать систему заявок на выдачу.
3) Оповещать менеджмент о нехватке
товаров.
30. Доменный эксперт говорит что...
“Знаете, мы понятия не имеем, что нужно
знать менеджерам и как они работают с
нашим складом. Давайте поговорим об этом
завтра с Аланом.”
Код домена и задание от Алана можно
посмотреть на GitHub.