Статьи

Изоляция логики домена

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

  1. Презентация (например, настольный графический интерфейс, браузер, веб-сервис)
  2. Доменная логика
  3. Инфраструктура (например, постоянное хранилище, электронная почта)

Классу было интересно, что стрелки зависимостей указывают на часть логики домена. Они спросили: «Диаграмма преднамеренно сделана неправильно? Разве доменная логическая часть не должна зависеть от постоянного хранения? » Это был отличный вопрос. И я хотел поделиться и опубликовать обсуждение и объяснение здесь.

Часто не понимают

Большинство разработчиков обычно имеют в виду это недоразумение.

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

Хотя это правильная последовательность операций, в реализации логического уровня предметной области есть что-то тонкое. Это как-то связано с инверсией зависимостей.

Принцип обращения зависимостей

Для уровня логики домена может потребоваться что-то из уровня инфраструктуры, например, какая-то форма доступа для извлечения из постоянного хранилища. Обычные шаблоны для этого: 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 Лоренцо Ди из блога « Адаптация и обучение» .