Support us

Знакомство с предметно-ориентированным проектированием (DDD)

Оставить комментарий
Знакомство с предметно-ориентированным проектированием (DDD)

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

В этой статье изложена справочная информация об основных концепциях, приемах и паттернах, подробно описанных в книге Эрика Эванса «Domain Driven Design: Tackling Complexity in the Heart of Software» и книге Джимми Нильссона «Applying Domain-Driven Design and Patterns with Examples in C# .NET». В некоторых случаях я прямо процитировал отрывки из этих книг, благодарю Эрика Эванса и Джимми Нильссона за то, что предоставили мне разрешение на это.

Зачастую говорят, что предметно-ориентированное проектирование — это правильно организованная объектная ориентация, но на самом деле DDD далеко не сводится к объектной ориентации.

Что такое предметно-ориентированное проектирование?

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

Данная методология помогает понимать границы решаемой проблемы, а также справляться с еще более сложной задачей — правильно очерчивать эти границы при описании проблемы. Важно и то, что предметно-ориентированное проектирование приучает нас и к другим полезным практикам, в частности к разработке через тестирование (TDD), использованию паттернов и непрерывному рефакторингу.

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

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

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

Постановка задачи

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

Описание в коде

class Person {
   public Registration bookCourse(Course c) { ' }
}
abstract class Registration {
    public abstract void accept();
    public abstract void cancel();
}
class ReservedRegistration extends Registration { ' }
class AcceptedRegistration extends Registration { ' }
interface CourseRepository {
    public List<Course> find(');
}

Описание в виде UML-схемы

Универсальный язык

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

Описывайте назначение, а не саму реализацию

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

Рассмотрим следующую ситуацию:

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

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

Ожидающий получает статус Вероятно, статус обозначается как флажок или поле. Возможно, эксперту-предметнику известна какая-то другая система, например таблица с информацией, и она наталкивает именно на такой вариант реализации.
Посылаемый через шину сообщений Это техническая реализация. Факт пересылки данных именно через шину сообщений никак не связан с самой предметной областью.
Для обработки Слово «обработка» очень неоднозначное. Что именно происходит при обработке?
На платежном шлюзе Еще одна реализация. Гораздо важнее сам факт того, что в системе происходят платежи, но конкретная реализация платежа на данном этапе является несущественной.

 

Стремитесь к глубокому пониманию ситуации

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

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

Оказывается, на момент записи на курс у человека нет статуса. Результатом процесса записи на курс является факт регистрации. Но если курс полон, то у данного человека будет «резервная регистрация» (standby registration). Управление всеми резервными регистрациями происходит в листе ожидания.

Рефакторинг языка

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

Давайте выполним рефакторинг описания регистрации на курс.

При регистрации человека на курс ему выдается резервная регистрация. В случае если на курсе освобождается место, а платеж от ожидающего уже получен, то регистрация считается выполненной, и происходит зачисление на курс. Если свободных мест нет, то регистрация считается резервной и в таком качестве попадает в лист ожидания. Лист ожидания управляется «в порядке поступления».

Конкретные примеры

При работе с экспертами-предметниками обычно удобно обсуждать проблему на конкретных примерах. Зачастую удобно описывать примеры из предметной области в виде историй «разработки через реализацию поведений» (BDD) и шаблонов сценариев.

Рассмотрим описанную выше ситуацию на конкретных примерах, перефразировав ее в виде BDD-шаблонов:

История: регистрация на курс
Я как человек заинтересованный в дополнительном образовании
Хочу записаться на курс
Чтобы освежить имеющиеся навыки и освоить новые

 

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

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

Сценарий: свободных мест на курсе нет
Дано: на курс Python 101 набирается 10 слушателей
Уже есть 10 человек, зарегистрированных на курс Python 101
Когда я регистрируюсь на курс 'Python 101'
Моя регистрация на Python 101 считается резервной
И такая резервная регистрация попадает в лист ожидания.

 

Стратегическое проектирование

Стратегическое проектирование — это построение наиболее общей целостной модели. В центре стратегического проектирования лежит то множество деталей, из которых состоит общая модель, а также на взаимосвязях между этими частями. Так мы можем заблаговременно выполнить определенную часть проектирования, и этого достаточно для устойчивого развития модели, а также для того, чтобы модель не получалась «монолитной».

Такие детали, которые при предметно-ориентированном проектировании можно считать «малыми моделями», существуют в ограниченных контекстах. Способ взаимосвязи этих ограниченных контекстов обычно называют «картой контекстов» (context mapping).

Ограниченные контексты

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

Контексты можно создавать (в частности) по следующим принципам:

• по организации команды;

• по структуре и компоновке базы кода;

• по использованию в конкретной части предметной области.


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

Карты контекстов

Составление карт контекстов — это часть процесса проектирования, в ходе которой вычерчиваются точки контакта и переходы между ограниченными контекстами. Особое внимание уделяется «картографированию» имеющегося предметного ландшафта, а работа с возможными трансформациями осуществляется уже позднее.

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

Паттерны для создания карт контекстов

Существует ряд паттернов, применяемых при картографировании контекста. Некоторые из этих паттернов описаны ниже.

Разделяемое ядро (Shared Kernel)

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

Будьте осторожны при работе с разделяемыми ядрами! Их сложно проектировать и поддерживать, такой паттерн бывает эффективен лишь при работе очень опытных команд!

Команды разработчиков на стороне заказчика/исполнителя (Customer/Supplier Development Teams)

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

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

Конформист (Conformist)

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

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

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

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

Предохранительный уровень

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

Паттерн «Предохранительный слой» отлично подходит для работы с устаревшими системами или базами кода.

Отдельные пути

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

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

ИСТОЧНИК

Место солидарности беларусского ИТ-комьюнити

Далучайся!

Хотите сообщить важную новость? Пишите в Telegram-бот

Главные события и полезные ссылки в нашем Telegram-канале

Обсуждение
Комментируйте без ограничений

Релоцировались? Теперь вы можете комментировать без верификации аккаунта.

Комментариев пока нет.