Статьи

Домен-управляемый дизайн

В моей стране вы не пройдете через школу, не прочитав, как жалуется Гёте Фауст, я сейчас изучаю философию — и юриспруденцию, медицину — и даже, увы! Богословие — насквозь с энтузиазмом! — Вот теперь я стою, бедный дурак.

К сожалению, ни одно из его усилий и исследований не помогло доктору осознать, что удерживает мир вместе в его самых глубоких складках.

И вот мы в IT: мы изучили языки и фреймворки, библиотеки и даже — увы — IE! Насквозь с энтузиазмом. Но сколько раз мы фокусировались на том, что удерживает приложение вместе в его самых глубоких складках? Сегодняшняя тема — бизнес-сфера.


Бизнес-логику иногда считают уникальной, и это по определению! Если бы бизнес-логика приложения не была бы уникальной, не было бы необходимости писать приложение, поскольку уже существует существующее решение (за исключением случаев, когда приложение существует, но недоступно). Следовательно, многие разработчики считают себя первооткрывателями, которые смело идут туда, где раньше не было ни одного человека. Помимо романтики, хотя сама по себе бизнес-логика может быть уникальной в значительной степени, методы ее реализации — нет. Вот почему были предложены такие интеллектуальные модели процессов, как Rational Unified Process или Scrum, а также такие методы, как итеративные и инкрементные циклы разработки. Талантливые архитекторы программного обеспечения также разработали подходы к разработке программного обеспечения; среди них Эрик Эванс, который ввел термин « дизайн, управляемый доменом» в своей книге с таким же названием.

Разработчики идут смело, туда, где раньше никто не ездил.

Я дам краткий обзор того, как Domain Driven Design может влиять на процесс консультирования, а также его основные концепции для разработки модели предметной области. Наконец, мы обсудим требования к инфраструктуре, которые необходимы для простой реализации домена.

Допустим, вы являетесь архитектором программного обеспечения в нетривиальном приложении с нетривиальной областью, как ядро ​​крупной логистической компании. Многие люди присоединяются к переговорам по планированию, в том числе руководители проектов, менеджеры по работе с клиентами, маркетинг, консультанты и так далее. Не каждый нужен для выполнения работы (я не буду делиться своим мнением о том, к кому это относится), но два человека будут играть решающую роль в процессе разработки требований: вы, архитектор и эксперт по предметной области.

Архитектор программного обеспечения (по крайней мере, в контексте бизнеса) должен иметь очень хорошее абстрактное понимание того, как работают процессы, как они проектируются и оптимизируются.

Это правда, потому что бизнес-приложения в основном предназначены для разработки эффективных и красивых цифровых эквивалентов бизнес-процессов. Специалист по предметной области должен обладать глубокими знаниями о конкретном наборе процессов, а именно о процессах, которые происходят в логистической компании и которые должны быть отражены в приложении. Я обнаружил, что бизнес-консультанты, менеджеры по продажам и маркетологи делают несколько хороших и ценных замечаний на этом пути, но пока у вас нет кого-то в команде, кто запачкал свои руки за годы работы, проект провалится. Например, ваш эксперт по домену должен знать ширину погрузочной рампы в депо и достаточно ли места для установки сканера штрих-кода.

Так что это вы, специализирующийся на цифровых бизнес-процессах, разработке программного обеспечения и [ укажите ваши любимые инструменты здесь ], а также эксперт по логистике со знаниями о клиентах, сотрудниках компании и повседневной рутине. Скорее всего, вы будете говорить в разных целях. Домен-управляемый дизайн предлагает несколько стратегий, которые могут сформировать мощный технический консалтинговый сервис. Вот мой:

  • Создать язык убиквитос
  • Создайте глоссарий ключевых слов
  • Переход от ориентированного на процесс представления к подходу, ориентированному на предметную область.
  • Создайте визуальную модель как основу вашей бизнес-логики.

Звучит забавно! Давайте углубимся в детали.

В каждой отрасли каждая группа экспертов имеет свою терминологию. Это уточняется в каждой компании и обогащается специальными условиями и названиями компаний. Подумайте об ИТ: когда такие люди, как мы, встречаются для серьезных разговоров, кто еще поймет слово? То же самое относится и к вашему домену, и первое, что нужно сделать, это определить набор терминов. Пройдите через весь набор процессов, которые должно отражать программное обеспечение, и внимательно выслушайте, как его описывает эксперт в предметной области. Любые доменные термины должны быть определены так, как это делают словари. Вы должны знать слова, которые звучат знакомо, но не в данном контексте. Некоторые компании никогда не выполняли эту работу раньше, даже если это ценно для других областей.

Сделайте специальный глоссарий ваших вездесущих терминов, будьте уверены, что он будет одобрен клиентом, и взимайте плату за консультационный процесс! Глоссарий может выглядеть так:

Выписка из глоссария.

Обратите внимание, что четко определенный глоссарий уже устанавливает зависимости и ассоциации. Как и порядок , в котором размещено несколько элементов . У вас наверняка будут занятия для тех, кто занимается бизнес-логикой! Ваш класс Order будет иметь метод, подобный getItems() . Без учета методов программирования глоссарий может заложить основу для вашей доменной модели! Наряду с этим вы создаете язык, который используется на протяжении всего проекта: в почте, на собраниях и определенно в коде! Ваш код должен отражать домен; следовательно, это должно быть определено в глоссарии. Вот практическое правило: всякий раз, когда вы создаете класс, который не назван в честь записи в вашем глоссарии, ваш вездесущий язык может быть еще недостаточно определен!

Под покровом темноты мы сместили взгляд на требования! Обычно клиент описывает, что должно делать написанное программное обеспечение. Типичное описание может быть следующим: «Нам нужен способ добавить заметки клиенту и распечатать их». Это хорошая отправная точка, но она не фокусируется на бизнес-сфере. Это вводит некоторый пользовательский интерфейс, функциональность печати и даже больше. Это наверняка понадобится в вашем приложении, но оно не является частью домена. Домен Управляемый Дизайн фокусируется на моделировании истинной цели приложения: бизнес-домен.

Все остальное должно возникнуть после того, как домен будет готов. Переключение заключается в следующем: не реализуйте процессы, а создайте чистый домен, который отражает потребности клиентов в объектах. Способ визуализации описания верхнего клиента будет выглядеть следующим образом (геттеры и сеттеры добавляются только тогда, когда это необходимо для понимания):

Простая диаграмма, которая отражает необходимый домен.

Теперь у нас есть набор классов и ассоциаций, которые ничего не делают, но отражают определения из нашего глоссария. Способна ли эта модель выполнять необходимые задачи? Конечно! Вам понадобится PrinterService и пользовательский интерфейс где-то в вашем приложении, но им просто нужно получить некоторые данные из домена. Они не нужны прямо сейчас, и его реализация не будет иметь решающего значения для результата.

Философия DDD основана на предположении, что тщательно спроектированный уровень домена может легко выполнять все необходимые процессы. Модель предметной области является масштабируемой, поскольку она построена не для решения конкретной задачи, а для отражения бизнес-концепции. Он взаимозаменяем, поскольку он не связан ни с каким конкретным программным обеспечением, даже с пользовательским интерфейсом. Вы можете использовать ту же модель в сканере штрих-кода на погрузочной платформе в депо! Как мы увидим в следующей главе, он даже не связан с другими компонентами, которые создают ваше приложение.


В одной из моих недавних статей я писал о применении принципа KISS.

В одной из моих недавних статей я писал о применении принципа KISS: большинство систем работают лучше, если они просты, а не сложны. Что ж, когда дело доходит до реализации домена, основанного на философии DDD, вы можете столкнуться с довольно радикальным подходом в современном мире структур, шаблонов и дисциплин; например, реализовать обычный объект на простом языке по вашему выбору. Нет рамочных зависимостей, нет библиотечных соглашений, нет следов каких-либо API, нет причудливых имен. Просто старый старый объект (поскольку концепция не воспринимается как серьезная без причудливого имени в мире Java, они получили его там).

Когда мы хотим отразить модель предметной области, важно определить ее состояние. В объектно-ориентированном программировании состояние объекта определяется состоянием его свойств. Аналогично, состояние модели предметной области определяется состоянием ее объектов. Следовательно, у нас должен быть способ четко определить состояние объектов. Если бы мы не смогли этого сделать, мы бы потерпели неудачу в простых случаях использования, таких как «Сколько заказов?», Потому что для ответа всегда требуется знание о состоянии всех объектов Order в домене и способ их идентификации и различения. DDD определяет два типа объектов: объекты и объекты-значения.

Сущность — это знакомое понятие, если вы знакомы с реляционными базами данных.

Таблицы в реляционной базе данных обычно имеют уникальный идентификатор, который отличает одну строку от другой. То же самое верно для сущностей. Сущность должна иметь четкий идентификатор, который уникален во всей системе. Для заказа это может быть свойство типа uint с именем orderNumber . Конечно, вы загляните в свой глоссарий, где должен быть определен правильный термин.

Сущность остается неизменной при изменении некоторых свойств. Например, вы можете добавлять или удалять элементы из заказа, но это будет тот же заказ. Что происходит, когда вы меняете orderNumber ? Ну, из POV вашего домена один заказ удаляется, а другой создается.

Объект значения — это простой контейнер для информации. Он неизменен, как только он создан. Изменение одного свойства означает, что вы измените объект значения. Объект-значение определяется всеми его свойствами; ему не нужен уникальный идентификатор. Весь объект один. Примером объекта значения может быть OrderAddress , так как он определяется именем, адресом и городом получателя. Если вы измените одно свойство, например город, OrderAddress изменится полностью.

Если вы измените одно свойство цвета, оно будет другим.

Разделение объектов на объекты-значения и сущности важно для определения состояния вашего домена — так как это основная работа по идентификации компонентов. Но также важно определить их, чтобы иметь масштабируемый обслуживаемый домен. Сущности — это объекты реального мира, такие как Персоны, Ордена или Предметы. Объекты-значения — это контейнеры для информации, такой как цвета или адреса, и они могут использоваться повторно и совместно использоваться сущностями или даже всей вашей системой. Их определение может потребовать некоторой практики, поскольку это зависит от варианта использования, если у вас есть объект значения или сущность.

Когда мы оглянемся на реферат нашего глоссария, мы увидим связи и зависимости между нашими объектами в доменном слое. В DDD это называется ассоциациями, и это модель взаимодействий, которые имеют место.

Например, предметы являются частью заказа. Если бы мы обрабатывали реляционную базу данных, это было бы отношение один ко многим (или 1: n ). Если в каждом заказе будет ровно один AddressAddress, это будет отношение один к одному. Поскольку мы не заботимся о реляционных базах данных и заботимся только о завершении домена, связь можно легко выразить двумя методами в классе Order: getItems() и getOrderAddress() . Обратите внимание, что первое — множественное число (так как элементов много), а второе — единственное. Если бы у вас было отношение «многие ко многим», вы бы дали обоим классам метод получения. Конечно, вам также нужны сеттеры — я их опустил, чтобы облегчить примеры.

Предметы и адрес заказа можно рассматривать как дочерние элементы заказа.

В DDD мы стараемся избегать отношений «многие ко многим», так как они имеют тенденцию увеличивать сложность домена. Технически это означает, что два объекта должны быть синхронизированы в течение их жизненного цикла, а синхронизация может привести к нарушению принципа СУХОЙ. Вот почему процесс уточнения модели должен стремиться к простоте. Во многих случаях ассоциация сильнее в одном направлении, чем в другом, и это хорошая идея, чтобы изменить структуру к отношениям «один ко многим». Проверьте, соответствует ли связь бизнес-логике вашего приложения. Если это происходит только в неосновных и редких случаях использования, вы можете найти другой способ получения необходимой информации.

Ассоциации строят дерево объектов, и вы должны получить конструкцию ассоциации, где каждый объект может быть получен с помощью методов получения. Это родительско-дочерняя конструкция, которая в конечном итоге приводит к одному корневому объекту. Это называется агрегацией в DDD. Хороший дизайн в конечном итоге приводит к одному агрегату, способному отражать весь домен. На данный момент мы рассмотрели лишь небольшую часть нашего глоссария, но кажется, что клиент является нашим корневым агрегатом:

Клиентский объект является родителем всех членов домена.

Агрегаты являются важной частью, так как DDD пытается изолировать домен от окружающего приложения. Если нам нравится получать информацию о клиенте, мы запрашиваем корневой агрегат и можем просматривать его дочерние элементы для доступа к информации через понятный интерфейс методов получения и установки.

DDD похож на продажи, он предоставляет одно лицо клиенту, совокупность с окружающей системой. Следовательно, он дает доступ к структурированному набору процессов, соответствующей информации и методам; например заказ.

Дизайн, управляемый доменом, похож на продажи, он предоставляет одно лицо клиенту.

Окружающее приложение получает доступ к агрегату через репозитории, которые по сути являются своего рода фасадом. Другими словами: объект домена является агрегатом, если у него есть хранилище. Хранилища предоставляют методы для запроса агрегатов. Примерами могут быть findClientByEmail(string email) или просто findAll() . Они также выполняют обновления и добавляют новые объекты в домен. Таким образом, они, вероятно, имеют такие методы, как add(Client newClient) или delete(Client toBeDeletedClient) .

В совокупности вы получаете доступ к детям только через своего родителя. Например, клиентский агрегат дает вам доступ ко всем заказам клиента. Но если вам нужен доступ к данным с другой точки зрения, чем у клиента, вы можете установить второй агрегат. Допустим, вы хотите иметь список всех заказов, независимо от того, каким клиентом они были размещены. Хранилище заказов выполнит свою работу!

Доменный слой и его репозитории.

Поскольку репозиторий является точкой входа для окружающего приложения в слой домена, именно туда входят другие игроки. Помните, что мы имеем дело с простыми объектами на данный момент.

Вы спрашивали себя, как это станет реальностью? Это инфраструктура. DDD является отличным компаньоном для фреймворков, поскольку он построен на простых объектах с простой схемой объектов-значений, объектов и агрегатов. Однако, поскольку простота — это сила в ИТ, мы теперь в состоянии отдать на аутсорсинг всю инфраструктурную часть. Давайте посмотрим под капот, и как DDD можно распространить на приложение.


Вы, возможно, заметили, что наше внимание к домену исключило постоянный слой, а также общие вещи, такие как представления или контроллеры, из нашего списка дел. Все приложение может состоять из гораздо более сложных вещей, чем простые объекты, и я хочу указать на несколько шагов, которые необходимо сделать, чтобы связать домен и приложение вместе, а также какие существуют стратегии реализации. Я приведу несколько примеров на основе FLOW3, прикладной среды, основной задачей которой является обеспечение DDD-инфраструктуры. Это не обязательно, но это не повредит, если вы прочитаете мое вступление . Чтобы применить бизнес-домен к приложению, обычно выполняются следующие шаги:

  • Реализация постоянного уровня, который сохраняет наши доменные объекты — например, базу данных MySQL.
  • Создание репозитория, который абстрагирует доступ к реляционной базе данных и обеспечивает простой интерфейс для запросов.
  • Создайте фабричный сервис, который генерирует объекты и создает совокупное дерево.
  • Предоставить сервисную инфраструктуру для внедрения логики, не относящейся к домену.
  • Создание приложения с учетом домена.

Когда вы посмотрите на комментарии к статье « Аспектно-ориентированное программирование» (AOP), вы увидите интересную дискуссию о том, должна ли фреймворк добавлять свой след через аннотации комментариев. Подход в FLOW3 основан на том, как реализовано управление через домен. Посмотрите на этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * A Client
 *
 * @FLOW3\Scope(«prototype»)
 * @FLOW3\Entity
 */
class Client {
     
    /**
     * The clients name.
     *
     * @FLOW3\Validate(type=»Text»)
     * @FLOW3\Validate(type=»StringLength», options={ «minimum»=1, «maximum»=80 })
     * @ORM\Column(length=80)
     * @var string
     */
    protected $name;
     
    /**
     * Get the Client’s name
     *
     * @return string The Client’s name
     */
    public function getName() {
        return $this->Name;
    }
 
    /**
     * Sets this Client’s name
     *
     * @param string $Name The Client’s Name
     * @return void
     */
    public function setName($name) {
        $this->name = $name;
    }
}

Это очень простой класс, и он не содержит много бизнес-логики, но он, вероятно, изменится, когда приложение будет расти. FLOW3 присутствует в некоторых аннотациях кода. Он определяет класс как сущность и добавляет некоторые правила проверки (это необязательно). Обратите внимание, что есть аннотация с именем @ORM\Column(length=80) . Это информация для слоя постоянства, и мы вернемся к этому через минуту.

FLOW3 использует здесь аннотации для поддержания чистоты домена. Вы можете использовать класс где угодно, так как он все еще простой объект. Вы можете переключиться на платформу Symfony , которая использует тот же уровень персистентности (Doctrine), поэтому код будет работать практически из коробки. Выдвигая конфигурацию фреймворка за пределы интерпретатора PHP, домен остается простым старым объектом PHP. Вы можете использовать его даже без каких-либо рамок вообще.

Но теперь, когда фреймворк знает об объекте, он теперь может рассчитать требования к таблице базы данных MySQL. Чтобы сохранить экземпляры клиента класса, FLOW3 (и Doctrine как среда персистентности) выполнят для вас следующие шаги:

  • Создайте таблицу с именем client .
  • Добавьте столбец с именем name строки типа длиной 80 символов
  • Поскольку мы не предоставили уникальный идентификатор (который требуется для сущностей), FLOW3 любезно сгенерирует его для нас и добавит для него ячейку таблицы.

Определение свойства для элементов в нашем заказе может выглядеть следующим образом:

1
2
3
4
5
6
7
8
/**
* The items.
*
* @ORM\OneToMany(mappedBy=»order»)
* @ORM\OrderBy({«price» = «ASC»})
* @var \Doctrine\Common\Collections\Collection<\LogisticApp\Domain\Model\Item>
*/
protected $items;

Обратите внимание, что это возвращает коллекцию Doctrine, которая является своего рода оболочкой для массива, такой как ArrayLists в Java. По сути это означает, что все элементы должны быть заданного типа, в данном случае Item. Я решил добавить инструкцию о том, как я хочу организовать сбор (по ценам на товары).

Аналогом в классе Item может быть:

1
2
3
4
5
6
7
/**
 * The order.
 *
 * @ORM\ManyToOne(inversedBy=»items»)
 * @var \LogisticApp\Domain\Model\Order
 */
protected $order;

Это всего лишь верхушка айсберга, но он должен дать вам представление о том, как все можно автоматизировать: Doctrine предлагает мощную стратегию сопоставления ассоциаций с таблицами, в которых хранится объект. Например, поскольку элементы будут преобразовываться в отношение «один ко многим» (один заказ может иметь много элементов) в базе данных, Doctrine автоматически добавит внешний ключ для заказа в таблицу элементов. Если вы решите добавить хранилище для элемента (сделав его совокупным), вы можете волшебным образом получить доступ к findByOrder(Order order) . Вот почему мы не заботились о базах данных или постоянстве при создании домена — это то, о чем может заботиться инфраструктура.

Если вы новичок в средах персистентности, способ отображения объектов в реляционную базу данных называется ORM ( Object-Relational-Mapping ). Он имеет некоторые недостатки производительности, которые в основном вызваны различными подходами, которые имеют реляционные базы данных и объектная модель. Есть долгие дискуссии по этому поводу. Однако в современных приложениях CRUD (не только управляемых доменом) ORM — это путь, главным образом по причинам обслуживания и расширяемости. Тем не менее, вы должны знать свой ORM и хорошо понимать, как он работает. Не думайте, что вам больше не нужны знания о базах данных!

Как вы могли заметить, ваши объекты могут быть очень сложными и иметь длинную линию обхода, если у них много детей, у которых, в свою очередь, много собственных детей.

Следовательно, как только хранилище извлекает данные из базы данных, они должны быть преобразованы в объекты интеллектуальным способом. Поскольку теперь у нас есть постоянный слой, преобразование намного сложнее, чем просто создание экземпляра объекта. Нам нужно управлять линией обхода, сводя к минимуму соответствующие обращения к базе данных. Не все дети нужны всегда, поэтому их можно получить по требованию.

Некоторые объекты будут объектами-значениями, которые нужно создавать только один раз, что может сэкономить много памяти. Вот почему любому доменному слою нужна умная фабрика, которая генерирует объекты для вас. Следовательно, в современных средах new оператор считается слишком низким уровнем для современных приложений. FLOW3 имеет большое значение для предоставления возможности создания объектов с new ключевым словом, но фоновая компиляция автоматически заменяет создание простого объекта на мощное управление объектами. Вот некоторые функции, которые должен поддерживать ваш менеджер объектов / фабрика, независимо от того, какую платформу вы используете:

  • Управление сущностями и объектами стоимости
  • Интеллектуальный способ предоставить детям предметы по запросу
  • Внедрение зависимостей и услуг

Возможно, вы нахмурились в последнем предложении. На протяжении всей статьи я подчеркивал использование простых объектов в домене, и я даже нарушал парадигму «не повторяйся» и упоминал об этом несколько раз, потому что это так важно для DDD. И теперь я говорю вам, что у вас есть зависимости и сервисы, которые должны быть частью вашего домена …

В реальном мире есть печальная правда: нет такой вещи, как чистая область. Вы почти никогда не встретите клиента, который начинает с нуля; Таким образом, вы должны удовлетворить обстоятельства, как унаследованные системы. У них может быть ужасная реализация, но компания не может избавиться от этого. Возможно, вам придется вызывать службы и API и получать данные от различных третьих сторон, и эти устаревшие системы влияют на бизнес-домен.

Все, что мы обсуждали до сих пор, важно, но вопрос о том, как фреймворк решает зависимость от недоменных сервисов, имеет решающее значение для чистого доменного дизайна. Именно поэтому команда FLOW3 потратила огромные усилия на реализацию аспект-ориентации; это способ ввести службы в домен, не касаясь кода, не нарушая правила простых старых объектов. Существуют и другие подходы, такие как перенос зависимостей между сервисами и доменом на контроллер, но аспектно-ориентированное программирование — безусловно, самый элегантный способ, который я знаю. Я хотел бы услышать ваши мысли по этой теме!

Хорошая структура может оказать вам большую поддержку, помимо упомянутых мною моментов. Например, FLOW3 прозрачно передает доменные объекты в представлении с помощью удивительно классного движка шаблонов, который называется Fluid. Написание шаблонов Fluid после создания домена так же расслабляет, как день на пляже.


Эта статья является лишь введением в дизайн, управляемый доменом. Я представил некоторые из основных концепций, но я должен признать, что может быть трудно понять только теорию. Я хочу призвать вас попробовать Domain Driven Design для своего собственного проекта в реальном мире. Вы почувствуете, что доменные концепции очень интуитивно понятны в использовании.

Я был брошен в DDD, как рыба из воды, в очень большом проекте extbase, без особого предварительного знания концепций (extbase — это структура, управляемая доменом, для создания расширений для CMS Typo3 и основана на FLOW3). Это расширило мои взгляды на то, как думать о разработке программного обеспечения, и я надеюсь, что это расширит и ваши.