Проблемы JPA в DDD-приложениях
В сети можно иногда видеть проекты, авторы которых с энтузиазмом заявляют, что они представили пример программы, исполненной в стратегиях и тактике Domain Driven Design. Но на какую Domain Entity не посмотришь, каждая покрыта JPA аннотациями с головы до ног. Да, для Presentation-Domain-Data это нормально.
Но почему разработчики считают, что это соответствует духу и букве DDD? Не имею ни малейшего понятия. Куда хуже то, что подобное мышление иногда приходит в продуктовый код, которым потом очень тяжело управлять из-за жёсткой связи между структурой Domain Entity и таблицей в БД, чего быть не должно категорически.
DDD это не чёткий набор действий, но набор ограничений, которые накладываются на разработку. Одно из самых важных ограничений – это чёткое разделение на слои. Можно положить, что есть 3 основных: бизнес-домен, приложение и инфраструктура.
Слои, как и типично для луковичных архитектур, знают только о том, что они сами “оборачивают”. Слой приложения оборачивает бизнес-домен. А слой инфраструктуры оборачивает слои приложения и бизнес-домена. Эта идея аналогично описывается в куда более известной Clean Architecture от Роберта Мартина (Uncle Bob). Из-за этой схожести DDD и Clean Architecture рекомендуют к совместному использованию.
Именно тут заключается основная проблема с примерами “настоящих DDD-приложений”. JPA – это деталь реализации, элемент исключительно инфраструктурного уровня. Ни уровень приложения, ни тем более уровень бизнес-домена, не должны знать о @Id, @Entity, @Table, @Column, @JoinColumn и плеяде других аннотаций…
В принципе, код слоя бизнес-домена не должен иметь никаких внешних зависимостей. Не играет никакой роли, аннотации ли это или вызов метода из внешней библиотеки. Единственное разумное отступление – библиотеки для тестирования и выделенный артефакт кода с Shared Kernel, если Ваш проект действительно этого требует.
Чтобы упростить себе задачу, удобно создать отдельный модуль, в котором будет только код слоя бизнес-домена. Gradle и Maven отлично с этим подсобят.
Дальше нужно смотреть на конкретную ситуацию. Если получившаяся модель домена будет полностью совпадать со структурой хранения, а Вы используете Hibernate и Вам не хочется создавать конвертеры (мапперы) для каждого объекта, то можно прибегнуть к маппингу, например, через orm.xml, чтобы не трогать сущности.
Если модель не совпадает, можно сделать маппинг из JPA Entity в Domain Entity и обратно. Можно наследовать JPA Entity от Domain Entity и т.д., варианты есть.
Самое главное, что Вы получите – это изолированный и предсказуемый код в слое бизнес-домена, который позволит беспроблемно пережить смену технологий хранения и даже инфраструктурных фреймворков типа Spring или Jakarta EE.