Composite Entity паттерн

Добавлено : 28 Dec 2008, 18:06

Содержание

1 Контекст
2 Проблема
3 Ограничения
4 Решение
    4.1 Структура
    4.2 Участники и обязанности
        4.2.1 CompositeEntity
        4.2.2 CoarseGrainedObject
        4.2.3 DependentObject1, DependentObject2 и DependentObject3
    4.3 Стратегии
        4.3.1 Стратегия Composite Entity Contains Coarse-Grained Object
        4.3.2 Стратегия Entity Implements Coarse-Grained Object
        4.3.3 Стратегия Lazy Loading
        4.3.4 Стратегия Store Optimization (Dirty Marker)
        4.3.5 Стратегия Composite Transfer Object
5 Выводы
6 Примеры
    6.1 Реализация паттерна Composite Entity
        6.1.1 Пример 8.18 Entity Implements Coarse-Grained Object
    6.2 Реализация стратегии Lazy Loading
        6.2.1 Пример 8.19 Реализация стратегии Lazy Loading
    6.3 Реализация стратегии Store Optimization (Dirty Marker)
        6.3.1 Пример 8.20 SkillSet Dependent Object Implements DirtyMarker Interface
        6.3.2 Пример 8.21 Реализация стратегии Store Optimization
    6.4 Реализация стратегии Composite Transfer Object
        6.4.1 Пример 8.22 Реализация стратегии Composite Transfer Object
        6.4.2 Пример 8.23 Создание составного Transfer Object
7 Связанные паттерны
8 Компонент управления данными как зависимый объект: проблемы и рекомендации
    8.1 Случай 1: Зависимый объект зависит от двух родительских объектов
    8.2 Случай 2: Зависимый объект уже существует как компонент управления данными

1 Контекст

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

2 Проблема

В приложении Java 2 Platform, Enterprise Edition (J2EE) клиенты - приложения, JSP -страницы (JavaServer Pages), сервлеты, компоненты JavaBeans - обращаются к компонентам управления данными через их удаленные интерфейсы. Следовательно, каждый клиентский вызов потенциально направляется через сетевые заглушки и скелетные коды, даже если клиент и корпоративный компонент расположены в одной и той же JVM, OS, или машине. Если компоненты управления данными являются специализированными объектами, клиенты вынуждены вызывать больше индивидуальных методов компонента, что приводит к большим расходам сетевых ресурсов.

Компоненты управления данными представляют распределенные персистентные бизнес-объекты. Как при разработке, так и при миграции приложения на платформу J2EE, уровень модульности объектов является очень важным моментом при решении вопроса, что реализовывать в виде компонента управления данными. Каждый такой компонент должен представлять бизнес-объекты общего назначения, то есть реализующие сложное поведение за простыми операциями получения и установки значений полей. Такие объекты общего назначения обычно имеют зависимые объекты. Зависимым объектом называется объект, который не имеет реальной значимости без связи со своим предком общего назначения.

Повторно возникающей проблемой является прямое отображение объектной модели в модель Enterprise JavaBeans (EJB) (а именно, модель компонентов управления данными). При этом между компонентами создаются взаимоотношения без анализа типа объектов - общего назначения или специализированных (или зависимых). Определение того, что сделать объектом общего назначения, а что специализированным объектом, обычно трудная задача и лучше всего может быть решена при помощи моделирования взаимоотношений моделями Unified Modeling Language (UML - унифицированный язык моделирования).

Использование специализированных компонентов управления данными затрагивает ряд вопросов:

  • Взаимоотношения между сущностями - прямое отображение объектной модели на EJB-модель не учитывает взаимоотношений между объектами. Отношения между объектами прямо преобразуются в отношения между компонентами. В результате, компонент управления данными может содержать или хранить удаленную ссылку на другой компонент. Однако, работа с удаленными ссылками к распределенным объектам требует иной техники и семантики, нежели работа с ссылками к локальным объектам. Кроме увеличения сложности кода уменьшается гибкость, поскольку компонент управления данными должен меняться при любых изменениях в его взаимоотношениях.

    Кроме того, нет никакой гарантии корректности ссылок компонента на другой компонент с течением времени. Такие ссылки устанавливаются динамически с использованием домашнего объекта компонента и первичного ключа для экземпляра данного компонента управления данными. Это подразумевает наличие больших эксплуатационных накладных расходов на проверку корректности каждой такой ссылки компонента на компонент.

  • Управляемость - реализация специализированных объектов в виде компонентов управления данными приводит к большому числу таких компонентов в системе. Компонент управления данными определяется при помощи нескольких классов. Для каждого компонента управления данными разработчик должен предоставить классы домашнего интерфейса, удаленного интерфейса, реализации компонента и первичного ключа.

    Кроме того, контейнер может генерировать классы для поддержки реализации компонента управления данными. При создании компонента эти классы реализуются в контейнере как реальные объекты. Короче говоря, контейнер создает ряд объектов для поддержки экземпляра компонента управления данными. Большое число компонентов приводит к существованию еще большего числа классов и кода, которые должна поддерживать команда разработчиков. Это приводит также к большому числу объектов в контейнере, что может негативно отразиться на производительности приложения.

  • Сетевая производительность - специализированные компоненты управления данными потенциально имеют больше межкомпонентных взаимоотношений. Компоненты управления данными являются распределенными объектами. Когда один компонент вызывает метод другого компонента, этот вызов обрабатывается контейнером как удаленный вызов, даже если оба компонента расположены в одном и том же контейнере или JVM. При росте количества межкомпонентных взаимоотношений уменьшается масштабируемость системы из-за большой сет??вой нагрузки.

  • Зависимость от схемы базы данных - Если компоненты управления данными являются специализированными, каждый экземпляр компонента обычно представляет одну запись в базе данных. Это не свойственно проектированию компонентов управления данных, поскольку эти компоненты больше подходят для выполнения функций общего назначения. Реализация специализированного компонента управления данными обычно является прямым представлением схемы базы данных в проекте компонента. Когда клиенты используют эти специализированные компоненты, они по существу работают на уровне записей базы данных, поскольку каждый компонент является одной записью базы данных. Из-за того, что компонент управления данными прямо моделирует одну строку базы данных, клиенты становятся зависимыми от ее схемы. При изменении схемы должны также меняться и определения компонента. Более того, поскольку клиенты работают на таком же уровне модульности, они должны следить и реагировать на эти изменения. Зависимость от схемы ведет к потере гибкости и увеличивает затраты на обслуживание при необходимости изменения схемы.

  • Модульность объектов (функциональность общего назначения против специализированной) - модульность объектов влияет на передачу данных между корпоративным компонентом и клиентом. В большинстве приложений клиентам обычно необходим больший объем данных, чем одна или две строки таблицы. В такой ситуации реализация каждого из этих специализированных объектов в виде компонентов управления данными означает, что клиент должен управлять отношениями между всеми этими объектами. В зависимости от требований клиент, вероятно, будет должен выполнить большое количество операций поиска среди нескольких компонентов для получения необходимой информации.

3 Ограничения

  • Компоненты управления данными лучше всего реализовывать в виде объектов общего назначения из-за высоких расходов, связанных с каждым компонентом. Каждый компонент управления данными реализуется при помощи нескольких объектов, таких как домашний EJB-объект, удаленный объект, реализация компонента и первичный ключ, и каждый такой объект управляется службами контейнера.

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

  • Специализированные компоненты управления данными являются результатом прямого отображение объектной модели в EJB-модель. Обычно специализированные компоненты отображаются на схему базы данных. Такое отображение «компонент-запись базы данных» вызывает проблемы, связанные с производительностью, управляемостью, безопасностью и обработкой транзакций. Взаимоотношения между таблицами реализуются как взаимоотношения между компонентами. Это означает, что компоненты хранят ссылки на другие компоненты для реализации этих специализированных взаимоотношений. Управление межкомпонентными взаимоотношениями является очень ресурсоемким, поскольку эти взаимоотношения должны устанавливаться динамически при помощи домашних объектов компонентов и первичных ключей корпоративных компонентов.

  • Клиентам не обязательно знать детали реализации схемы базы данных для использования и поддержки компонентов управления данными. При использовании специализированных компонентов управления данными отображение обычно выполняется таким образом, что каждый экземпляр компонента отображает одну строку базы данных. Такое отображение создает зависимость между клиентом и схемой базы данных, поскольку клиенты имеют дело со специализированными компонентами, а они являются, по существу, прямым представлением этой схемы. Это приводит к возникновению тесной связи между схемой базы данных и компонентами управления данными. Изменения в схеме вызывают соответствующие изменения в компонентах, и, более того, требуют соответствующих изменений в клиентах.

  • Увеличивается сетевой трафик в приложениях из-за взаимодействий между специализированными компонентами управления данными. Чрезмерный межкомпонентный трафик часто приводит к уменьшению производительности. Каждый вызов метода компонента выполняется через сетевой уровень, даже если вызывающий компонент находится в том же самом адресном пространстве, что и вызываемый компонент (то есть, и клиент, или вызывающий компонент, и вызываемый компонент находятся в одном и том же контейнере). Хотя некоторые поставщики контейнеров оптимизируют данную ситуацию, разработчик не может надеяться на такую оптимизацию во всех контейнерах.

  • Дополнительный трафик может быть обнаружен также между клиентом и компонентами управления данными, поскольку клиент для выполнения требований может взаимодействовать со многими специализированными компонентами. Желательно уменьшить взаимодействие между компонентами управления данными и уменьшить трафик между клиентом и уровнем корпоративных компонентов.

4 Решение

Используйте Composite Entity для моделирования, представления и управления набором взаимосвязанных персистентных объектов вместо представления их в виде индивидуальных специализированных компонентов управления данными. Компонент Composite Entity представляет граф объектов.

Для того чтобы понять это решение, прежде всего определим, что подразумевается под персистентными объектами и рассмотрим их взаимоотношения.

Персистентным объектом называется объект, который хранится в каком-либо хранилище данных. Несколько клиентов обычно используют персистентные объекты совместно. Персистентные объекты можно подразделить на два типа: объекты общего назначения и зависимые объекты.

Объект общего назначения является самодостаточным. Он имеет свой собственный цикл жизни и управляет своими взаимоотношениями с другими объектами. Каждый объект общего назначения может ссылаться или содержать один или более других объектов. Циклом жизни этих объектов обычно управляет объект общего назначения. Поэтому эти объекты называются зависимыми объектами. Зависимый объект может быть простым объектом, или может, в свою очередь, содержать другие зависимые объекты.

Цикл жизни зависимого объекта тесно связан с циклом жизни объекта общего назначения. Клиент может только косвенно обращаться к зависимому объекту через объект общего назначения. То есть, зависимые объекты прямо не предоставляются клиентам, поскольку ими управляет их родительский объект. Зависимые объекты не могут существовать сами по себе. Они всегда должны иметь свой объект общего назначения (или родительский объект) для оправдания своего существования.

Обычно вы можете рассматривать взаимоотношения между объектом общего назначения и его зависимыми объектами в виде дерева. Объект общего назначения является корнем дерева (корневым узлом). Каждый зависимый объект может быть автономным зависимым объектом (листовым узлом), то есть потомком объекта общего назначения. Зависимый объект может, также, иметь отношения «родитель-потомок» с другими зависимыми объектами, и в этом случае он рассматривается как узел разветвления.

Компонент Composite Entity может представлять объект общего назначения и все его зависимые объекты. Такое объединение взаимосвязанных персистентных объектов в один компонент управления данными очень сильно уменьшает число необходимых для приложения компонентов управления данными. В результате создается компонент управления данными самого общего назначения, который может лучше использовать все преимущества компонентов управления данными, чем специализированный компонент.

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

4.1 Структура

Хотя существует много стратегий реализации паттерна Composite Entity, сначала мы рассмотрим стратегию, представленную диаграммой классов, изображенной на рисунке 8.17. Здесь Composite Entity содержит объект общего назначения, а объект общего назначения содержит зависимые объекты.

Рисунок 8.17 Диаграмма классов Composite Entity

Диаграмма последовательности действий, представленная на рисунке 8.18, показывает взаимодействия для этого паттерна.

4.2 Участники и обязанности

4.2.1 CompositeEntity

CompositeEntity представляет собой компонент управления данными общего назначения. CompositeEntity может быть объектом общего назначения, либо может хранить ссылку на объект общего назначения. В разделе «Стратегии» рассмотрены различные стратегии реализации Composite Entity.

4.2.2 CoarseGrainedObject

CoarseGrainedObject представляет собой объект, имеющий свой собственный цикл жизни и управляющий своими собственными взаимоотношениями с другими объектами. Объектом общего назначения может быть Java-объект, содержащийся в Composite Entity. Composite Entity может быть также и сам объектом общего назначения, хранящим зависимые объекты. Эти стратегии рассмотрены в разделе «Стратегии».

4.2.3 DependentObject1, DependentObject2 и DependentObject3

Зависимый объект - это объект, зависящий от объекта общего назначения, который управляет его циклом жизни. Зависимый объект может содержать другие зависимые объекты; следовательно, внутри Composite Entity может содержаться дерево объектов.

4.3 Стратегии

В данном разделе рассматриваются различные стратегии реализации Composite Entity. Обсуждаются возможные альтернативы и варианты для персистентных объектов (общего назначения и зависимых) и использование объектов Transfer Object.

4.3.1 Стратегия Composite Entity Contains Coarse-Grained Object

В данной стратегии Composite Entity хранит или содержит объект общего назначения. Объект общего назначения продолжает иметь взаимоотношения со своими зависимыми объектами. В разделе «Структура» данного паттерна эта стратегия описана в качестве основной.

4.3.2 Стратегия Entity Implements Coarse-Grained Object

В данной стратегии сам Composite Entity является объектом общего назначения и имеет атрибуты и методы этого объекта. Зависимые объекты являются атрибутами объекта Composite Entity. Поскольку Composite Entity является объектом общего назначения, компонент управления данными управляет всеми взаимоотношениями между объектом общего назначения и зависимыми объектами.

На рисунке 8.19 изображена диаграмма классов этой стратегии.

Рисунок 8.19 Диаграмма классов стратегии Composite Entity Implements Coarse-Grained Object

Диаграмма последовательности действий для этой стратегии изображена на рисунке 8.20.

4.3.3 Стратегия Lazy Loading

Composite Entity может быть составлен из нескольких уровней зависимых объектов в дереве объектов. Загрузка всех зависимых объектов при вызове EJB-контейнером метода Composite Entity ejbLoad() может занять значительное время и ресурсы. Одним из способов оптимизации этого процесса является использование стратегии «ленивой» загрузки для загрузки зависимых объектов. При вызове метода ejbLoad() сначала загружаются только наиболее критичные для клиентов паттерна Composite Entity зависимые объекты. Позднее, когда клиенты обращаются к еще не загруженному из базы данных зависимому объекту, Composite Entity может выполнить загрузку по требованию. То есть, если некоторые из зависимых объектов не используются, они не загружаются при инициализации. Однако, когда клиентам впоследствии понадобятся эти зависимые объекты, они будут загружены в тот момент времени. После загрузки зависимых методов последующие вызовы контейнером метода ejbLoad() должны включать эти зависимые объекты для перезагрузки, для того чтобы синхронизировать изменения с персистентным объектом.

4.3.4 Стратегия Store Optimization (Dirty Marker)

При сохранении полного графа объектов во время вызова ejbStore() возникает общая проблема для управляемой компонентом персистенции. Поскольку EJB-контейнер никак не может узнать, какие именно данные изменились в компоненте управления данными и его зависимых объектах, груз работы по определению объектов в графе Composite Entity, которые нужно сохранить после обновления, ложится на разработчика. Это может быть сделано путем реализации в зависимых объектах специального метода, например isDirty(), который вызывается контейнером для проверки того, изменился ли объект после предыдущей операции ejbStore().

Общим решением может быть использование интерфейса DirtyMarker как показано на диаграмме классов, изображенной на рисунке 8.21. Идея состоит в том, чтобы реализовать в зависимых объектах интерфейс DirtyMarker, который даст возможность вызывающей стороне (обычно методу ejbStore()) узнать о факте изменения состояния зависимого объекта. Таким образом, вызывающая сторона может выполнить операцию получения данных для последующего сохранения.

Рисунок 8.21 Диаграмма классов стратегии Store Optimization

На рисунке 8.22 изображена диаграмма последовательности действий, показывающая пример взаимодействия для этой стратегии.

Клиент выполняет обновление в Composite Entity, что приводит к обновлению в DependentObject3. Доступ к DependentObject3 осуществляется через его родителя - DependentObject2. Composite Entity является родителем DependentObject2. При выполнении этого обновления в DependentObject3 вызывается метод setDirty(). Затем, после того как контейнер вызовет метод ejbStore() этого экземпляра Composite Entity, метод ejbStore() может проверить, какой зависимый объект устарел, и выборочно сохранить изменения в базе данных. Флаг изменения сбрасывается после успешной записи данных.

Интерфейс DirtyMarker может также включать методы, которые могут распознать другие типы состояний персистенции зависимого объекта. Например, если в Composite Entity добавлен новый зависимый объект, метод ejbStore() должен уметь распознать, какую операцию использовать в данном случае. Ведь зависимый объект не устарел, а является новым объектом. После расширения интерфейса DirtyMarker методом isNewz(), метод ejbStore() может вызвать операцию вставки, а не обновления. Аналогично, после добавления метода isDeleted(), метод ejbStore() может вызвать, при необходимости, операцию удаления.

Когда ejbStore() вызывается без промежуточных обновлений в Composite Entity, ни один из зависимых объектов не обновляется.

Эта стратегия исключает огромные расходы ресурсов при сохранении в базе данных полного графа зависимых объектов при вызове контейнером метода ejbStore().

ПРИМЕЧАНИЕ:

Спецификация EJB 2.0 описывает стратегию Lazy Loading и стратегию Store Optimization. Спецификация версии 2.0 является последним черновиком на момент написания данной статьи. Однако, есть возможность использовать эти стратегии и в реализациях, предшествующих спецификации 2.0. Следите, пожалуйста, за разработкой EJB 2.0 и за тем, как эти стратегии будут окончательно оформлены в спецификации.

4.3.5 Стратегия Composite Transfer Object

Используя Composite Entity, клиент может получить всю необходимую информацию только за один удаленный вызов метода. Поскольку Composite Entity либо реализует, либо хранит объект общего назначения и иерархию (или дерево) зависимых объектов, он может создать требуемый Transfer Object и возвратить его клиенту, применяя паттерн Transfer Object (см. раздел "Transfer Object" на стр. 261). Диаграмма последовательности действий для этой стратегии приведена на рисунке 8.23.

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

Итак, компонент может собрать все свои данные в один составной Transfer Object и возвратить его клиенту. Также эта стратегия позволяет компоненту управления данными возвратить клиенту только необходимые данные. Если клиенту нужны данные только от подмножества зависимых объектов, возвращаемый составной Transfer Object может содержать данные, собранные только из необходимых объектов, а не из всех зависимых объектов. Такое приложение будет использовать стратегию Multiple Transfer Objects паттерна Transfer Object (см. раздел "Transfer Object" на стр. 261).

5 Выводы

  • Устраняет межкомпонентные взаимоотношения

    При использовании паттерна Composite Entity зависимые объекты собираются в один компонент управления данными, устраняя все межкомпонентные взаимоотношения. Этот паттерн предоставляет центральное место для управления и взаимоотношениями, и иерархией объектов.

  • Улучшает управляемость, уменьшая количество компонентов управления данными

    Как было рассмотрено выше, реализация персистентных объектов как специализированных компонентов управления данными приводит к большому числу классов, которые должны быть разработаны и которые необходимо поддерживать. Использование Composite Entity уменьшает число EJB-классов и количество кода, а также облегчает поддержку. При этом улучшается управляемость приложения, поскольку в нем присутствует меньше компонентов общего назначения по сравнению с намного большим числом специализированных компонентов.

  • Улучшает сетевую производительность

    Объединение зависимых объектов улучшает общую производительность. Такое объединение устраняет все сетевые взаимодействия между зависимыми объектами. Если бы каждый зависимый объект был бы реализован как специализированный компонент управления данными, межкомпонентные взаимодействия привели бы к большому расходу сетевых ресурсов.

  • Уменьшает зависимость от схемы базы данных

    Использование паттерна Composite Entity приводит к реализации компонентов управления данными общего назначения. Схема базы данных скрыта от клиентов, поскольку отображение компонента на схему происходит внутри компонента общего назначения. Изменения схемы базы данных могут потребовать изменения компонентов Composite Entity. Однако, клиенты не меняются, поскольку компоненты Composite Entity не предоставляют схему внешнему миру.

  • Увеличивает модульность объектов

    При использовании паттерна Composite Entity клиент обычно ищет один компонент управления данными, а не большое количество специализированных компонентов. Клиент производит запрос данных от Composite Entity. Composite Entity может создать составной Transfer Object, содержащий все данные из компонента, и возвратить Transfer Object клиенту. Это уменьшает трафик между клиентом и бизнес-уровнем.

  • Способствует созданию составного Transfer Object

    При использовании данной стратегии частота взаимодействий между клиентом и компонентом управления данными уменьшается, поскольку компонент Composite Entity может возвратить составной Transfer Object, предоставляя механизм передачи сериализованных Transfer Object из компонента Composite Entity. Хотя Transfer Object возвращает все данные в одном удаленном вызове, объем возвращаемых при этом данных намного больше объема данных, возвращаемых отдельными удаленными вызовами для получения индивидуальных свойств компонента. На этот компромисс необходимо идти тогда, когда целью является устранение повторных удаленных вызовов и множественных операций поиска.

  • Затраты при многоуровневых графах зависимых объектов

    Если граф зависимых объектов, управляемых компонентом Composite Entity, имеет много уровней, увеличивается расход ресурсов при загрузке и сохранении зависимых объектов. Он может быть уменьшен при помощи оптимизационных стратегий для загрузки и сохранения, но здесь может возникнуть расход ресурсов, связанный с проверкой устаревших объектов при сохранении и загрузке требуемых объектов.

6 Примеры

Рассмотрим приложение Professional Service Automation (PSA), в котором бизнес-объект Resource реализован с использованием паттерна Composite Entity. Resource представляет собой информацию о сотруднике, назначенному проектам. Каждый объект Resource может иметь различные зависимые объекты, например:

  • BlockOutTime - Этот зависимый объект представляет период времени, в течение которого Resource не доступен по таким причинам как учеба, отпуск и т.д. Поскольку каждый ресурс может иметь несколько блокированных промежутков времени, взаимоотношение Resource-BlockOutTime является отношением один-ко-многим.

  • SkillSet - Этот зависимый объект представляет Skill, которым владеет Resource. Поскольку каждый ресурс может иметь несколько специальностей, взаимоотношения Resource-SkillSet являются отношениями один-ко-многим.

6.1 Реализация паттерна Composite Entity

Паттерн для бизнес-объекта Resource реализуется как Composite Entity (ResourceEntity). Это показано в примере 8.18. Отношение один-ко-многим объекта со своими зависимыми объектами (объекты BlockOutTime и SkillSet) реализуются при помощи коллекций.

6.1.1 Пример 8.18 Entity Implements Coarse-Grained Object

package corepatterns.apps.psa.ejb;

import corepatterns.apps.psa.core.*;
import corepatterns.apps.psa.dao.*;
import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

public class ResourceEntity implements EntityBean {
 
public String employeeId;
 
public String lastName;
 
public String firstName;
 
public String departmentId;
 
public String practiceGroup;
 
public String title;
 
public String grade;
 
public String email;
 
public String phone;
 
public String cell;
 
public String pager;
 
public String managerId;
 
 
// Коллекция зависимых объектов BlockOutTime
 
public Collection blockoutTimes;

 
// Коллекция зависимых объектов SkillSet
 
public Collection skillSets;

  ... 

 
private EntityContext context;
// реализация методов компонента управления данными
public String ejbCreate(ResourceTO resource) throws
 
CreateException {
   
try {
     
this.employeeId = resource.employeeId;
      setResourceData
(resource);
      getResourceDAO
().create(resource);
   
} catch(Exception ex) {
     
throw new EJBException("Reason:" + ...);
   
}
   
return this.employeeId;
}
 
public String ejbFindByPrimaryKey(String primaryKey)
 
throws FinderException {
   
boolean result;
   
try {
     
ResourceDAO resourceDAO = getResourceDAO();
      result =
        resourceDAO.selectByPrimaryKey
(primaryKey);
   
} catch(Exception ex) {
     
throw new EJBException("Reason:" + ...);
   
}
   
if(result) {
     
return primaryKey;
   
}
   
else {
     
throw new ObjectNotFoundException(...);
   
}
  }
 
 
public void ejbRemove() {
   
try {
     
// Удалить зависимые объекты
     
if(this.skillSets != null) {

       
SkillSetDAO skillSetDAO = getSkillSetDAO();
        skillSetDAO.setResourceID
(employeeId);
        skillSetDAO.deleteAll
();
        skillSets =
null;
     
}
     
if(this.blockoutTime != null) {
       
BlockOutTimeDAO blockouttimeDAO =
            getBlockOutTimeDAO
();
        blockouttimeDAO.setResourceID
(employeeId);
        blockouttimeDAO.deleteAll
();
        blockOutTimes =
null;
     
}

     
// Удалить ресурс из персистентного хранения
     
ResourceDAO resourceDAO = new
       
ResourceDAO(employeeId);
      resourceDAO.delete
();
   
} catch(ResourceException ex) {
     
throw new EJBException("Reason:"+...);
   
} catch(BlockOutTimeException ex) {
     
throw new EJBException("Reason:"+...);
   
} catch(Exception exception) {
     
...
   
}
  }
 
public void setEntityContext(EntityContext context)
  {
   
this.context = context;
 
}
 
 
public void unsetEntityContext() {
   
context = null;
 
}
 
 
public void ejbActivate() {
   
employeeId = (String)context.getPrimaryKey();
 
}
 
 
public void ejbPassivate() {
   
employeeId = null;
 
}
 
 
public void ejbLoad() {
   
try {
     
// загрузить информацию о ресурсе из
     
ResourceDAO resourceDAO = getResourceDAO();
      setResourceData
((ResourceTO)
       
resourceDAO.load(employeeId));
     
     
// Загрузить при необходимости другие зависимые объекты
     
...
   
} catch(Exception ex) {
     
throw new EJBException("Reason:" + ...);
   
}
  }
 
 
public void ejbStore() {
   
try {
     
// Сохранить информацию о ресурсе
     
getResourceDAO().update(getResourceData());

     
// Сохранить при необходимости зависимые объекты
     
...
   
} catch(SkillSetException ex) {
     
throw new EJBException("Reason:" + ...);
   
} catch(BlockOutTimeException ex) {
     
throw new EJBException("Reason:" + ...);
   
}
   
...
 
}
 
public void ejbPostCreate(ResourceTO resource) {
  }

 
// Метод для получения Resource Transfer Object
 
public ResourceTO getResourceTO() {
   
// создать новый Resource Transfer Object
   
ResourceTO resourceTO = new
       
ResourceTO(employeeId);

   
// скопировать все значения
   
resourceTO.lastName = lastName;
    resourceTO.firstName = firstName;
    resourceTO.departmentId = departmentId;
    ...
   
return resourceTO;
 
}

 
public void setResourceData(ResourceTO resourceTO) {
   
// скопировать значения из Transfer Object в компонент управления данными
   
employeeId = resourceTO.employeeId;
    lastName = resourceTO.lastName;
    ...
 
}

 
// Метод для получения зависимых Transfer Object
 
public Collection getSkillSetsData() {
   
// Если skillSets не загружен, загрузить его первым.
    // См. Реализацию стратегии Lazy Load.

   
return skillSets; 
 
}
 
...

 
// другие необходимые методы get и set
 
...

 
// Бизнес-методы компонента управления данными
 
public void addBlockOutTimes(Collection moreBOTs)
 
throws BlockOutTimeException {
   
// Примечание: moreBOTs является коллекцией
    // объектов BlockOutTimeTO
   
try {
     
Iterator moreIter = moreBOTs.iterator();
     
while(moreIter.hasNext()) {
       
BlockOutTimeTO botTO = (BlockOutTimeTO)
                         
moreIter.next();
       
if (! (blockOutTimeExists(botTO))) {
         
// добавить BlockOutTimeTO в коллекцию
         
botTO.setNew();
          blockOutTime.add
(botTO);
       
} else {
         
// BlockOutTimeTO уже существует, добавление невозможно
         
throw new BlockOutTimeException(...);
       
}
      }
    }
catch(Exception exception) {
     
throw new EJBException(...);
   
}
  }

 
public void addSkillSet(Collection moreSkills)
 
throws SkillSetException {
   
// аналогично реализации addBlockOutTime()
   
...
 
}

 
...

 
public void updateBlockOutTime(Collection updBOTs)
 
throws BlockOutTimeException {
   
try {
     
Iterator botIter = blockOutTimes.iterator();
      Iterator updIter = updBOTs.iterator
();
     
while (updIter.hasNext()) {
       
BlockOutTimeTO botTO = (BlockOutTimeTO)
         
updIter.next();
       
while (botIter.hasNext()) {
         
BlockOutTimeTO existingBOT =
           
(BlockOutTimeTO) botIter.next();
         
// сравнить значения ключей для поиска BlockOutTime
         
if (existingBOT.equals(botTO)) {
           
// Найти BlockOutTime в коллекции
            // заменить старый BlockOutTimeTO новым
           
botTO.setDirty(); // modified old dependent
           
botTO.resetNew(); // not a new dependent
           
existingBOT = botTO;
         
}
        }
      }
    }
catch (Exception exc) {
     
throw new EJBException(...);
   
}
  }

 
public void updateSkillSet(Collection updSkills)
 
throws CommitmentException {
   
// аналогично updateBlockOutTime...
   
...
 
}

 
...

}

6.2 Реализация стратегии Lazy Loading

Предположим, что при первой загрузке Composite Entity контейнером в методе ejbLoad() должны быть загружены только данные о ресурсе. В эти данные включены атрибуты, перечисленные в компоненте ResourceEntity, и исключены коллекции зависимых объектов. Зависимые компоненты могут быть загружены позже, когда клиент вызовет бизнес-метод, которому будут необходимы эти зависимые объекты. Метод ejbLoad() должен следить за такой загрузкой зависимых объектов и включать их позже для перезагрузки.

Соответствующие методы для класса ResourceEntity показаны в примере 8.19.

6.2.1 Пример 8.19 Реализация стратегии Lazy Loading

...
public Collection getSkillSetsData() {
throws SkillSetException {
 
checkSkillSetLoad();
 
return skillSets;
}

private void checkSkillSetLoad()
throws SkillSetException {
 
try {
   
// Стратегия Lazy Load...Загрузка по требованию
   
if (skillSets == null)
     
skillSets =
        getSkillSetDAO
(resourceId).loadAll();
 
} catch(Exception exception) {
   
// Нет специальности, сгенерировать исключительную ситуацию
   
throw new SkillSetException(...);
 
}
}

...

public void ejbLoad() {
 
try {
   
// загрузить информацию о ресурсе
   
ResourceDAO resourceDAO = new
     
ResourceDAO(employeeId);
    setResourceData
((ResourceTO)resourceDAO.load());
     
   
// Если «лениво» загружаемые объекты уже
    // были загружены, они должны быть перезагружены.
    // Если они не загружены, не загружать их
    // здесь... «ленивая» загрузка сделает это позже.
   
if (skillSets != null) {
     
reloadSkillSets();
   
}
   
if (blockOutTimes != null) {
     
reloadBlockOutTimes();
   
}
   
...
   
throw new EJBException("Reason:"+...);
 
}
}

...

6.3 Реализация стратегии Store Optimization (Dirty Marker)

Для использования стратегии Store Optimization зависимые объекты должны иметь реализованный интерфейс DirtyMarker, как показано в примере 8.20. Метод ejbStore() для оптимизации этой стратегии показан в примере 8.21.

6.3.1 Пример 8.20 SkillSet Dependent Object Implements DirtyMarker Interface

public class SkillSetTO implements DirtyMarker,
  java.io.Serializable
{
 
private String skillName;
 
private String expertiseLevel;
 
private String info;
  ...

 
// флаг обновления
 
private boolean dirty = false;

 
// флаг новой записи
 
private boolean isnew = true;

 
// флаг удаления
 
private boolean deleted = false;

 
public SkillSetTO(...) {
   
// инициализация
   
...
   
// есть новый TO
   
setNew();
 
}

 
// методы get, set и other для SkillSet
  // все методы set и modifier
  // должны вызывать setDirty()
 
public setSkillName(String newSkillName) {
   
skillName = newSkillName;
    setDirty
();
 
}
 
...

 
// методы DirtyMarker
  // используемые только для измененных Transfer Object
 
public void setDirty() {
   
dirty = true;
 
}
 
public void resetDirty() {
   
dirty = false;
 
}
 
public boolean isDirty() {
   
return dirty;
 
}

 
// используемые только для новых Transfer Objects
 
public void setNew() {
   
isnew = true;
 
}
 
public void resetNew() {
   
isnew = false;
 
}
 
public boolean isNew() {
   
return isnew;
 
}

 
// используемые только для удаленных объектов
 
public void setDeleted() {
   
deleted = true;
 
}
 
public boolean isDeleted() {
   
return deleted;
 
}
 
public void resetDeleted() {
   
deleted = false;
 
}

}

6.3.2 Пример 8.21 Реализация стратегии Store Optimization

...

 
public void ejbStore() {
   
try {
     
// Загрузить обязательные данные
     
getResourceDAO().update(getResourceData());

     
// Оптимизация записи для зависимых объектов
      // проверить dirty и записать
      // Проверить и записать фиксации
     
if (skillSets != null) {
       
// Получить DAO для записи
       
SkillSetDAO skillSetDAO = getSkillSetDAO();
        Iterator skillIter = skillSet.iterator
();
       
while(skillIter.hasNext()) {
         
SkillSetTO skill =
           
(SkillSetTO) skillIter.next();
         
if (skill.isNew()) {
           
// Это новый подчиненный, вставить его
           
skillSetDAO.insert(skill);
            skill.resetNew
();
            skill.resetDirty
();
         
}
         
else if (skill.isDeleted()) {
           
// Удалить Skill
           
skillSetDAO.delete(skill);
           
// Удалить из списка подчинения
           
skillSets.  remove(skill);
         
}
         
else if (skill.isDirty()) {
           
// Записать Skill, он был модифицирован
           
skillSetDAO.update(skill);
           
// Записано, сбросить флаг dirty.
           
skill.resetDirty();
            skill.resetNew
();
         
}
        }
      }

     
// Аналогично реализовать store optimization
      // для других зависимых объектов, например

      // BlockOutTime, ...
     
...
   
} catch(SkillSetException ex) {
     
throw new EJBException("Reason:"+...);
   
} catch(BlockOutTimeException ex) {
     
throw new EJBException("Reason:"+...);
   
} catch(CommitmentException ex) {
     
throw new EJBException("Reason:"+...);
   
}
  }

 
...

6.4 Реализация стратегии Composite Transfer Object

Теперь рассмотрим ситуацию, когда клиент должен получить все данные из ResourceEntity, а не только какую-то их часть. Это можно сделать при помощи стратегии Composite Transfer Object, как показано в примере 8.22.

6.4.1 Пример 8.22 Реализация стратегии Composite Transfer Object

public class ResourceCompositeTO {
 
private ResourceTO resourceData;
 
private Collection skillSets;
 
private Collection blockOutTimes;

 
// конструкторы Transfer Object
 
...

 
// методы get и set
 
...
}

ResourceEntity предоставляет метод getResourceDetailsData() для возврата в ResourceCompositeTO составного Transfer Object, как показано в примере 8.23.

6.4.2 Пример 8.23 Создание составного Transfer Object

...
public ResourceCompositeTO getResourceDetailsData() {
 
ResourceCompositeTO compositeTO =
   
new ResourceCompositeTO (getResourceData(),
        getSkillsData
(), getBlockOutTimesData());
 
return compositeTO;
}
...

7 Связанные паттерны

  • Transfer Object

    Паттерн Composite Entity использует паттерн Transfer Object для создания объекта Transfer Object и возврата его клиенту. Паттерн Transfer Object используется для сериализации объекта общего назначения и дерева зависимых объектов, либо части дерева при необходимости.

  • Session Facade

    Если зависимыми объектами должны быть компоненты управления данными, а не произвольные Java-объекты, попробуйте использовать паттерн Session Facade для управления межкомпонентными взаимодействиями.

  • Transfer Object Assembler

    Когда необходимо получить составной Transfer Object из Composite Entity (см. абзац "Способствует созданию составного Transfer Object" в разделе "Выводы") этот паттерн аналогичен паттерну Transfer Object Assembler. Однако, в этом случае источники данных для всех Transfer Object являются частью самого Composite Entity, тогда как для Transfer Object Assembler источниками данных могут быть различные компоненты управления данными, сессионные компоненты, объекты DAO, Java-объекты и т.д.

8 Компонент управления данными как зависимый объект: проблемы и рекомендации

Обычно мы разрабатываем зависимые объекты в виде Java-объектов, имеющих прямые взаимоотношения с родительским объектом общего назначения. Однако, возможна ситуация, когда зависимый объект сам является компонентом управления данными. Это может случиться:

  1. Если зависимый объект зависит от двух различных родительских объектов (как в случае ассоциативных классов).
  2. Если зависимый объект уже существует как компонент управления данными в этом же приложении, либо импортирован из другого приложения.

В этих случаях поведение зависимого объекта, возможно, не должно прямо зависеть и управляться одним объектом общего назначения. Итак, что же делать, если зависимый объект является компонентом управления данными? Если вы видите зависимый объект, который не полностью зависит от его родительского объекта? Или если вы не можете идентифицировать его единственный родительский объект?

Рассмотрим каждую ситуацию детальнее.

8.1 Случай 1: Зависимый объект зависит от двух родительских объектов

Рассмотрим эту ситуацию на следующем примере. Commitment представляет собой соединение между Resource и Project.

На рисунке 8.24 показан пример диаграммы классов с взаимоотношениями между Project, Resource и Commitment.

Рисунок 8.24 Зависимый объект с двумя родительскими объектами

Commitment является зависимым объектом. Project и Resource являются объектами общего назначения. Каждый объект Project связан с объектами Commitment отношением один-ко-многим. Каждый объект Resource также связан с объектами Commitment отношением один-ко-многим. Итак, является ли Commitment зависимым объектом Project или Resource? Ответ можно получить после анализа взаимодействий этих трех объектов. Если вы сделаете Commitment зависимым от Project, то когда Resource обращается к своему списку объектов Commitment, он должен сделать это через объект Project. С другой стороны, если Commitment зависит от Resource, то когда Project обращается к своему списку объектов Commitment, он должен сделать это через Resource. Оба этих выбора вводят в проект взаимоотношения компонент-компонент.

Но, что если Commitment сделан как компонент управления данными, а не как зависимый объект? Тогда взаимоотношения между Project и его списком объектов Commitment и между Resource и его списком объектов Commitment будут иметь тип сущность-компонент. Это только ухудшило ситуацию тем, что сейчас существуют два взаимоотношения компонент-компонент.

Отношения компонент-компонент использовать не рекомендуется из-за накладных расходов, связанных с управлением и поддержкой таких отношений.

8.2 Случай 2: Зависимый объект уже существует как компонент управления данными

В этом случае может показаться, что единственным способом смоделировать эти взаимоотношения является хранение первичного ключа зависимого объекта в объекте общего назначения. Когда объекту общего назначения необходимо обратиться к зависимому объекту, это приводит к вызову типа «компонент-компонент». Диаграмма классов этого примера показана на рисунке 8.25.

Диаграмма последовательности действий для этого сценария показана на рисунке 8.26. Composite Entity использует ссылку на зависимый объект для поиска необходимых зависимых компонентов управления данными. Зависимый объект в данном случае выступает в качестве прокси к зависимому компоненту.

Хотя такой подход может удовлетворить требование использования зависимого компонента из родительского компонента, он не является элегантным решением. Вместо него, для устранения сложности разработки и управления межкомпонентными взаимоотношениями, рассмотрите использование сессионного компонента, помогающего управлять взаимоотношениями между компонентами управления данными. Итак, мы узнали, что паттерн Session Facade помогает устранить эту проблему и обеспечивает лучший способ управления взаимоотношениями компонент-компонент.

В связи с вышеизложенным мы рекомендуем избегать взаимоотношений типа компонент-компонент и вынести такие взаимоотношения в сессионный компонент, используя паттерн Session Facade (см. раздел "Session Facade" на стр. 291).