В одном классе шаблонов проектирования у меня было интересное обсуждение моделирования логики предметной области. В частности, речь шла об изоляции логики домена . Приложение обычно делится на три части:
- Презентация (например, настольный графический интерфейс, браузер, веб-сервис)
- Доменная логика
- Инфраструктура (например, постоянное хранилище, электронная почта)
Классу было интересно, что стрелки зависимостей указывают на часть логики домена. Они спросили: «Диаграмма преднамеренно сделана неправильно? Разве доменная логическая часть не должна зависеть от постоянного хранения? » Это был отличный вопрос. И я хотел поделиться и опубликовать обсуждение и объяснение здесь.
Часто не понимают
Большинство разработчиков обычно имеют в виду это недоразумение.
И это недоразумение во многом связано с последовательностью операций. Обычно он начинается с триггера (например, пользователь нажимает кнопку или ссылку) на уровне представления, который затем вызывает что-то на уровне логики домена, который затем вызывает что-то на уровне инфраструктуры (например, обновляет запись таблицы базы данных).
Хотя это правильная последовательность операций, в реализации логического уровня предметной области есть что-то тонкое. Это как-то связано с инверсией зависимостей.
Принцип обращения зависимостей
Для уровня логики домена может потребоваться что-то из уровня инфраструктуры, например, какая-то форма доступа для извлечения из постоянного хранилища. Обычные шаблоны для этого: DAO и репозиторий. Я не буду объяснять эти две модели здесь. Вместо этого я хотел бы указать, что определения интерфейсов размещены на уровне логики домена, а их реализации размещены на другом отдельном уровне.
Размещение определений интерфейса (DAO и репозиторий) внутри уровня логики домена означает, что его определяет уровень логики домена. Это тот, который определяет, какие методы необходимы, и какие возвращаемые типы ожидаются. Это также отмечает границы логики домена.
Такое разделение между интерфейсом и реализацией может быть тонким, но ключевым. Размещение только определений интерфейса позволяет освободить логическую часть домена от деталей инфраструктуры и позволяет выполнить ее модульное тестирование без реальных реализаций. Интерфейсы могут иметь фиктивные реализации во время модульного тестирования. Эта тонкая разница имеет большое значение для быстрой проверки (понимания командой разработчиков) бизнес-правил.
Это разделение является классическим принципом инверсии зависимости в действии. Доменная логика (модули высокого уровня) не должна зависеть от DAO и реализаций репозитория (модули низкого уровня). Оба должны зависеть от абстракций. Логика домена определяет абстракции, и реализации инфраструктуры зависят от этих абстракций.
Большинство начинающих команд, которые я видел, размещают интерфейсы DAO и репозитория вместе со своими специфичными для инфраструктуры реализациями. Например, скажем, у нас есть StudentRepository
и его реализация StudentJpaRepository
. Я обычно нахожу начинающие команды, помещающие их в один пакет. Пока это нормально, так как приложение все равно будет успешно скомпилировано. Но разделение прошло, и логика предметной области больше не изолирована.
Теперь, когда я объяснил, почему и как часть логики домена не зависит от части инфраструктуры, я хотел бы коснуться того, как часть представления случайно запутывается в логике домена.
Отдельная презентация
Еще одна вещь, которую я часто вижу у начинающих команд, это то, как они в конечном итоге запутывают свою логику предметной области своей презентацией. И это приводит к этой неприятной циклической зависимости. Эта циклическая зависимость более логична, чем физическая. Что делает его все труднее обнаружить и предотвратить.
Я не буду использовать здесь пример с богатым графическим интерфейсом, поскольку Мартин Фаулер уже написал для него отличный материал . Вместо этого я буду использовать презентацию на основе веб-браузера в качестве примера.
Большинство веб-систем будет использовать веб-фреймворк для своей презентации. Эти структуры обычно реализуют некоторую форму MVC (модель-представление-контроллер). Используемая модель обычно является моделью прямо из логики предметной области. К сожалению, большинство сред MVC требуют что-то о модели. В мире Java большинство сред MVC требуют, чтобы модель следовала соглашениям JavaBean. В частности, требуется, чтобы модель имела открытый конструктор с нулевыми аргументами, а также методы получения и установки. Конструктор и сеттеры с нулевыми аргументами используются для автоматического связывания параметров (из HTTP POST) с моделью. Получатели используются при рендеринге модели в виде.
Из-за этого подразумеваемого требования к инфраструктурам MVC, используемым в презентации, разработчики добавили бы общедоступный конструктор с нулевыми аргументами, геттер и сеттеры для всех своих доменных сущностей. И они будут оправдывать это как необходимый. К сожалению, это мешает реализации логики домена. Это запутывается с презентацией. И что еще хуже, я видел доменные объекты, загрязненные кодом, который испускает строки в кодировке HTML (например, код HTML с закодированными знаками «меньше и больше») и XML, просто из-за представления.
Если все в порядке, если ваша доменная сущность реализована как JavaBean, было бы хорошо, если бы она использовалась непосредственно в вашей презентации. Но если логика домена становится немного более сложной и требует, чтобы сущность домена теряла свою JavaBean-сущность (например, больше не было общедоступного конструктора с нулевыми аргументами, больше нет сеттеров), тогда для логической части домена было бы целесообразно реализовать домен логику и адаптировать презентационную часть путем создания другого объекта JavaBean для удовлетворения его потребностей MVC.
Примером, который я часто использую, является UserAccount
который используется для аутентификации пользователя. В большинстве случаев, когда пользователь хочет изменить пароль, старый пароль также необходим. Это помогает предотвратить несанкционированное изменение пароля. Это ясно показано в коде ниже.
1
2
3
4
5
|
public class UserAccount { ... public void changePassword( String oldPassword, String newPassword) {…} } |
Но это не следует соглашениям JavaBean. И если структура представления MVC не будет хорошо работать с методом changePassword
, наивным подходом будет удаление метода с setPassword
и добавление метода setPassword
(показано ниже). Это ослабляет изоляцию логики домена и заставляет остальную команду внедрять ее повсеместно.
1
2
3
4
|
public class UserAccount { ... public void setPassword(String password) {…} } |
Разработчикам важно понимать, что презентация зависит от логики предметной области. И не наоборот. Если у презентации есть потребности (например, соглашение JavaBean), то она не должна соответствовать логике домена. Вместо этого презентация должна создавать дополнительные классы (например, JavaBeans), которые знают соответствующие объекты домена. Но, к сожалению, я все еще вижу много команд, заставляющих свои доменные объекты выглядеть как JavaBeans только из-за представления, или, что еще хуже, из-за того, что доменные объекты создают JavaBeans (например, DTO) для целей представления.
Расположение Советы
Вот совет по организации вашего приложения. Храните свои доменные объекты и репозитории в одном пакете. Храните свой репозиторий и другие реализации инфраструктуры в отдельном пакете. Держите ваши связанные с презентацией классы в своем собственном пакете. Помните, какой пакет зависит от того, какой пакет. Пакет, который содержит доменную логику, предпочтительно находится в центре всего этого. Все остальное зависит от этого.
При использовании Java пакеты будут выглядеть примерно так:
-
com.acme.myapp.context1.domain.model
- Храните здесь свои доменные сущности, объекты-значения и репозитории (только определения интерфейса)
-
com.acme.myapp.context1.infrastructure.persistence.jpa
- Разместите ваш репозиторий на основе JPA и другие реализации, связанные с сохранением JPA, здесь
-
com.acme.myapp.context1.infrastructure.persistence.jdbc
- Разместите ваш репозиторий на основе JDBC и другие реализации, связанные с персистентностью JDBC, здесь
-
com.acme.myapp.context1.presentation.web
- Разместите компоненты веб / MVC презентации здесь. Если доменные объекты, необходимые для представления, не соответствуют требованиям инфраструктуры MVC, создайте здесь дополнительные классы. Эти дополнительные классы будут адаптировать доменные объекты для целей представления и при этом отделять доменные объекты от представления.
Обратите внимание, что я использовал context1
, поскольку в данном приложении (или системе) может быть несколько контекстов (или подсистем). В следующем посте я расскажу о наличии нескольких контекстов и нескольких моделей.
Это все на данный момент. Я надеюсь, что это краткое объяснение может пролить свет на тех, кто интересуется, почему их код организован и разделен определенным образом.
Спасибо Юноне Алиенто за помощь во время этой интересной дискуссии.
Счастливых праздников!
Ссылка: | Изоляция доменной логики от нашего партнера по JCG Лоренцо Ди из блога « Адаптация и обучение» . |