Как решить эту проблему: несоответствие модели приложения и модели движка при использовании сохраняемости JPA?

Название может показаться запутанным, но описать вопрос в нескольких словах непросто. Поясню ситуацию:

  1. У нас есть проект веб-приложения и проект механизма вычислений. Веб-приложение собирает пользовательский ввод и использует механизм для создания некоторого результата и представления пользователю. И пользовательский ввод, и выходные данные движка, и другие данные будут сохраняться в БД с использованием JPA.

  2. Ввод и вывод движка состоят из объектов в древовидной структуре, например:

    Class InputA {
      String attrA1;
      List<InputB> inputBs;
    }
    
    Class InputB {
      String attrB1;
      List<InputC> inputCs;
    }
    
    Class InputC {
      String attrC1;
    }
    

Выход двигателя в том же стиле.

  1. Проект веб-приложения обрабатывает сохранение данных с помощью 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. Вот что мы смогли получить:

  1. Теперь проект движка можно освободить, ему не нужно привязываться к тому, как другие проекты сохраняют эти объекты ввода/вывода. Проект двигателя теперь независим.

  2. Постоянство JPA работает просто свободно, никаких дополнительных обращений к базе данных не требуется.

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

Недостаток тоже очевиден:

  1. В структуре классов есть дублирование, что нежелательно с точки зрения объектно-ориентированного программирования.

  2. Если рассматривать его как DTO для сокращения вызовов базы данных, его можно объявить анти-шаблоном при использовании DTO в локальной передаче.

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


Я ищу следующую помощь:

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

  2. Если возможно, можете ли вы прокомментировать идею 1 и идею 2 с точки зрения объектно-ориентированного проектирования, практик настойчивости, вашего опыта и т. д.

Буду признателен за вашу помощь.


person khtwo    schedule 10.04.2015    source источник
comment
Хм, мне сложно представить, что ты делаешь, когда все так абстрактно. Можете ли вы дать представление о том, какие данные вы представляете?   -  person Neil McGuigan    schedule 12.04.2015
comment
Нил, рад видеть тебя здесь. Я использовал абстрактный класс, потому что я думаю, что этот тип проблемы является общим. Например, это может быть такое отображение: InputA = City, InputB = TravelAgent, InputC = TravelPlan, InputBx содержит технологические инструкции для TravelAgent, InputCx содержит другую настройку TravelPlan. Движку нужна только информация в InputA InputB и InputC, а пользовательский интерфейс покажет всю информацию, включая информацию в InputBx и InputCx.   -  person khtwo    schedule 12.04.2015