Название может показаться запутанным, но описать вопрос в нескольких словах непросто. Поясню ситуацию:
У нас есть проект веб-приложения и проект механизма вычислений. Веб-приложение собирает пользовательский ввод и использует механизм для создания некоторого результата и представления пользователю. И пользовательский ввод, и выходные данные движка, и другие данные будут сохраняться в БД с использованием JPA.
Ввод и вывод движка состоят из объектов в древовидной структуре, например:
Class InputA { String attrA1; List<InputB> inputBs; } Class InputB { String attrB1; List<InputC> inputCs; } Class InputC { String attrC1; }
Выход двигателя в том же стиле.
- Проект веб-приложения обрабатывает сохранение данных с помощью JPA. Нам нужно сохранить ввод и вывод двигателя, а также некоторые другие данные, связанные с вводом и выводом. Такие данные могут выглядеть как дополнительные поля для определенного класса. Например:
Мы хотим сохранить дополнительное поле, поэтому оно выглядит так:
Class InputBx extends InputB{
String attrBx1;
}
Class InputCx extends InputC{
String attrCx1;
}
В мире Java OO это работает, мы можем хранить список InputBx в InputA и хранить список InputCx в InputBx из-за наследования.
Но мы сталкиваемся с проблемами при использовании JPA для сохранения расширенных объектов.
Прежде всего, требуется, чтобы проект движка превратил их класс в сущности JPA. Двигатель работал нормально сам по себе, он принимал правильные входные данные и генерировал правильные выходные данные. Нехорошо заставлять их модель становиться объектами JPA, когда другой проект пытается сохранить модель.
Во-вторых, JPA не принимает унаследованные объекты при использовании InputA в качестве записи. С точки зрения JPA известно только, что InputA содержит список InputB, и невозможно сохранить/получить список InputBx в объекте InputA.
Пытаясь решить эту проблему, мы придумали 2 идеи, но ни одна из них нас не удовлетворила:
идея 1: использовать композицию вместо наследования, поэтому мы по-прежнему сохраняем исходный InputA, и его древовидная структура включает InputB и InputC:
Class InputBx{
String attrBx1;
InputB inputB;
}
Class InputCx{
String attrCx1;
InputC inputC;
}
Таким образом, исходное дерево входных объектов может быть легко извлечено, а объекты InputBx и InputCx должны быть извлечены с использованием объектов InputB и InputC в дереве в качестве ссылок.
Хорошо то, что независимо от того, какие изменения были внесены в структуру исходного дерева входных классов (такие как изменение имени атрибута, добавление/удаление атрибутов в классах), расширенные классы InputBx и InputCx и их атрибуты автоматически синхронизируются.
Недостаток заключается в том, что эта структура увеличивает количество обращений к базе данных, а модель неудобно использовать в приложении (как на внутреннем, так и на внешнем интерфейсе). Всякий раз, когда нам нужна связанная информация о InputB или InputC, нам нужно вручную запрограммировать поиск соответствующего объекта InputBx и InputCx.
Идея 2: Вручную создайте зеркальные классы, чтобы сформировать аналогичную структуру исходных входных классов. Итак, мы создали:
Class InputAx {
String attrA1;
List<InputBx> inputBs;
}
Class InputBx {
String attrB1;
List<InputCx> inputCs;
String attrBx1;
}
Class InputCx {
String attrC1;
String attrCx1;
}
Мы могли бы использовать это как модель веб-приложения, а также объекты JPA. Вот что мы смогли получить:
Теперь проект движка можно освободить, ему не нужно привязываться к тому, как другие проекты сохраняют эти объекты ввода/вывода. Проект двигателя теперь независим.
Постоянство JPA работает просто свободно, никаких дополнительных обращений к базе данных не требуется.
Внутренний и внешний пользовательский интерфейс просто используют эту модель для получения как исходных объектов ввода, так и связанной информации без каких-либо усилий. При попытке использовать движок для выполнения вычислений мы можем использовать механизм сопоставления для перехода между исходными объектами и расширенными объектами.
Недостаток тоже очевиден:
В структуре классов есть дублирование, что нежелательно с точки зрения объектно-ориентированного программирования.
Если рассматривать его как DTO для сокращения вызовов базы данных, его можно объявить анти-шаблоном при использовании DTO в локальной передаче.
Структура не синхронизируется автоматически с исходной моделью. Поэтому, если в исходную модель внесены какие-либо изменения, нам также необходимо вручную обновить эту модель. Если кто-то из разработчиков забудет это сделать, будут проблемы с обнаружением дефектов.
Я ищу следующую помощь:
Существуют ли какие-либо существующие хорошие/лучшие практики или шаблоны для решения подобных ситуаций, с которыми мы сталкиваемся? Или какие-то антипаттерны, которых нам следует стараться избегать? Ссылки на веб-статьи приветствуются.
Если возможно, можете ли вы прокомментировать идею 1 и идею 2 с точки зрения объектно-ориентированного проектирования, практик настойчивости, вашего опыта и т. д.
Буду признателен за вашу помощь.