Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Designing
Code

Проектирование по модели

by
Length:LongLanguages:

Russian (Pусский) translation by Sergey Zhuk (you can also view the original English article)

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

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

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


Бизнес логика и архитектура приложения

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

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

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

Технические требования

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

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

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

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

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

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

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

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

Выбор из словаря.

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

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

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

Простая диаграмма,которая отражает требуемую модель.

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

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


Модель предметной области

В предыдущих моих статьях я писал о применении KISS принципа.

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

Сущности и Объекты-Значения

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

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

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

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

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

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

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

Ассоциации, Агрегаты и Репозитории

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

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

Товары и адрес могут быть представлены как составные части заказа. 

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

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

Объект клиента является родительским для всех остальных объектов модели.

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

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

DDD, как продажи, предоставляет одно лицо к клиенту, агрегат для окружающей системы.

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

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

Уровень модели и его репозитории.

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

Вы задавались вопросом как это стало реальным? Здесь выходит на сцену инфраструктура. DDD является отличным напарником для фреймворков, так как он построен на простых объектах из сущностей, агрегатов и объектов-значений. Однако поскольку простота это сила, когда дело касается IT, то сейчас мы в состоянии передать часть внутренней инфраструктуры. Давайте взглянем под капотом, и посмотрим как DDD может распространяться вокруг приложения.


Инфраструктура и уровень модели

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

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

Когда вы посмотрите на комментарии к статье Aspect Oriented Programming (AOP), то увидите интересную дискуссию о том должен ли добавлять фреймворк аннотации с помощью комментариев. Подход в FLOW3 основан на реализации проектирования по модели. Взгляните на этот код:

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

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

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

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

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

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

Обратная связь в классе Item будет такой:

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

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

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

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

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

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

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

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

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

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


Заключение

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

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

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.