EJB Advocate: Всегда ли лучше использовать EJB-компоненты без фасадов в сервис-ориентированных архитектурах?

EJB Advocate оценивает использование различных форм "фасадов", включая POJO, HttpServlets, сессионные EJB-компоненты, управляемые сообщениями компоненты и методы Home EJB-компонентов управления данными, в попытке докопаться до сути того, что делает хорошей сервис-ориентированную архитектуру, реализованную с использованием J2EE™-компонентов.

Джефф Хэмбрик, инженер, IBM

Джефф Хэмбрик (Geoff Hambrick) является ведущим консультантом IBM Software Services для группы обеспечения WebSphere (WebSphere Enablement Team). Он живет в Раунд Рок, Техас (недалеко от Остина). Общей задачей группы обеспечения является поддержка предпродажных процессов, которая осуществляется с помощью подробного технического инструктажа и кратковременных совещаний по проверке концепций. За свою работу по созданию и распространению передового опыта для разработки приложений J2EE на базе IBM WebSphere Application Server в марте 2004 года Джефф был удостоен звания инженера с отличием IBM (IBM Distinguished Engineer).



22.04.2008

Из IBM WebSphere Developer Technical Journal.

В каждой рубрике EJB Advocate приводится суть типичного диалога с реальными пользователями и разработчиками в процессе предоставления рекомендаций по решению какой-либо интересной проблемы. Идентификационные детали участников диалога не сообщаются, а новаторские или проприетарные архитектуры не применяются. Более подробная информация приведена в статье "Знакомство с EJB Advocate".

EJB-компоненты без маски

Во всех предыдущих статьях EJB Advocate EJB-компоненты были заключены в фасад определенного типа. Например, в статье о перекрестных ссылках EJB разработчики использовали оболочки на чистом Java (POJO) в качестве фронтального интерфейса сессионных компонентов, чтобы скрыть сложность поиска домашних интерфейсов и создания ссылок от клиента. И даже сами сессионные компоненты были фасадами вокруг вспомогательных POJO-классов, чтобы скрыть сложность реализации EJB-компонентов от провайдеров сервисов. Короче говоря, целью разработчиков было скрыть сам факт использования EJB-компонентов. На рисунке 1 показано использование POJO в качестве уровня инкапсуляции.

Рисунок 1. UML-диаграмма, показывающая использование POJO в качестве уровня инкапсуляции
Рисунок 1. UML-диаграмма, показывающая использование POJO в качестве уровня инкапсуляции

В двух статьях (часть 1 и часть 2) о создании производительных CMP-сущностей рекомендовалось всегда использовать методы сессионных EJB-компонентов, передающих объекты передачи данных (Data Transfer Objects - DTO) в фасад и из фасада вокруг локальных CMP - как для формирования глобальной транзакции, так и для минимизации "болтливости" между уровнями. На рисунке 2 показан сессионный фасад в роли посредника между клиентским (HttpServlet) уровнем и уровнем данных (EJB-компоненты управления данными), формируя, таким образом, сервис-ориентированную архитектуру.

Рисунок 2. UML-диаграмма, показывающая сессионный EJB-компонент в роли сервис-ориентированного посредника
Рисунок 2. UML-диаграмма, показывающая сессионный EJB-компонент в роли сервис-ориентированного посредника

Представляя данную серию статей EJB Advocate, мы отмечали, что пришли к следующему выводу: не бывает плохих моделей, бывает только их плохое применение. Говоря иначе, данный вывод является предостережением: никогда не говори "никогда" или "всегда". Забудем о том, что я только что сказал "никогда". Приведенная ниже дискуссия показывает, что на самом деле я имею в виду: в некоторых ситуациях лучше всего использовать J2EE-компоненты напрямую, а в некоторых - опосредовано (через фасад какого-либо типа). До этого обсуждения я не мог четко сформулировать причины, почему нужно выбирать тот или другой подход.


Проблема: слишком много компонентов

Уважаемый EJB Advocate!

Наша группа разработчиков была рада встретиться с Вами в Вашей последней статье, в которой Вы наконец-то продемонстрировали реальный объектно-ориентированный подход к использованию EJB-компонентов управления данными. Используя прежде Smalltalk, мы были вынуждены затем переключиться на Java™ и с большим удивлением обнаружили, что J2EE-разработчики большей частью обращались к процедурному стилю кодирования.

Объектно-ориентированный это подход или нет, но если собрать вместе все Ваши статьи, получится гигантское количество классов. Начиная с класса Client:

  1. HttpServlet - для приема запроса от браузера и активизации ассоциированного сервиса.
  2. Java Server Page - для визуализации результатов, полученных от сервиса.
  3. Service Delegate - для инкапсулирования (используется сессионный EJB-компонент или нет).
  4. Service Session EJB Home - для получения удаленной ссылки на сессионный компонент.
  5. Service Session EJB Interface - чтобы скрыть реализацию заглушки (stub) от делегата.
  6. Service Session EJB Bean - для обеспечения распространения, транзакций и системы защиты.
  7. Service POJO - бизнес-логика, определяющая соответствующие задачи для выполнения.
  8. Task Delegate - для инкапсулирования (используется сессионный EJB-компонент или нет).
  9. Task Session EJB Home - для получения локальной ссылки на сессионный компонент.
  10. Task Session EJB Interface - чтобы скрыть реализацию локального EJB-компонента от делегата.
  11. Task Session EJB Bean - для обеспечения распространения, транзакций и системы защиты.
  12. Task POJO - бизнес-логика задачи (предполагая, что это фасад логического объекта).
  13. Business Object Delegate - для инкапсулирования (используется EJB-компонент управления данными или нет).
  14. Business Object Key - первичный ключ (или объект запроса) для данного объекта данных.
  15. Business Object View - объект передачи данных, минимизирующий количество вызовов между уровнями.
  16. Entity EJB Home - для получения локальной ссылки на Entity EJB.
  17. Entity EJB Interface - чтобы скрыть реализацию локального CMP-компонента от делегата.
  18. Entity EJB Implementation - логика, связанная с уровнем данных.

Этот список включает только основные компоненты приложения всего с одним Service, Task и Business Object. В него даже не входят дескрипторы развертывания и весь код, генерируемый инструментальными средствами развертывания!

Теперь представьте более сложное приложение, например, использующее модель данных, которую Вы описали в последней статье. Вероятно, в нем будет присутствовать много "сервисов", "задач" и "бизнес-объектов", имеющих много специализированных объектов передачи данных на каждом уровне для обработки преобразований. У нас нет генераторов кода, которые, похоже, имел Cross. Поэтому мы склоняемся к тому, чтобы игнорировать всех делегатов и использовать "правильные" EJB-компоненты по возможности напрямую, но не хотим пойти неверной дорогой.

Too Many Notes (Слишком много замечаний)


Компромиссы при использовании делегатов, фасадов и вспомогательных классов

Уважаемый Too Many!

Не пропустите статью по созданию и использованию генераторов кода при помощи наших инструментальных программ, основанных на Eclipse. Но хотя эти инструментальные программы помогут "бесплатно" сгенерировать многие из компонентов Вашей модели приложения, наличие ненужных компонентов во время исполнения плохо влияет на производительность. Генераторы кода на самом деле могут усугубить проблему! С другой стороны, если они легко поддаются изменению, то могут легко решить проблему.

Итак, что касается необходимости создания всех этих компонентов вручную, несколько странно защищать архитектуру, с которой не всегда согласен. Используйте классы делегатов для "полного сокрытия факта использования EJB-компонентов", как описал Cross with EJB References в первой статье, где упоминалось о необходимости учета компромиссов, хотя подробности не приводились. Кажется, сейчас самое время сделать это. Поэтому я приношу извинения за задержку с ответом, в котором я, в конечном счете, соглашусь с Вашей позицией.

Надеюсь, что данная дискуссия поможет прояснить также и Вашу позицию.

Различные типы EJB-компонентов имеют различные методы доступа
В Ваш список, приведенный выше, входят три различных типа компонентов, которые используются в полной архитектуре:

  • удаленные сервисы,
  • локальные задачи и
  • бизнес-объекты.

Каждый из них ассоциирован с различным типом EJB-компонента или стилем интерфейса (удаленный сессионный EJB, локальный сессионный EJB и локальный EJB-компонент управления данными соответственно). Код для извлечения каждого из них разный. Например, вот фрагмент кода HttpServlet для активизации метода getCustomerWithOpenOrder() фасада удаленного сессионного EJB-компонента под названием OrderEntry (мы описывали детали реализации этого метода в предыдущей статье EJB Advocate). В него включен нормальный алгоритм обработки и обработка ошибок для иллюстрации истинной сложности:

Листинг 1. Активизация удаленной сессии
try {
    // Предположим, что процедура получения id существует в сервлете суперкласса
    int cID = getCustomerId(req);

    // Пять строк кода для активизации метода удаленной сессии
    InitialContext initCtx = new InitialContext();
    Object obj = initCtx.lookup("java:comp/env/ejb/OrderEntry");       
    OrderEntryHome home = (OrderEntryHome)PortableObjectRemote(
	obj, OrderEntryHome.class
    );       
    OrderEntry ref = home.create();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Предположим, что существует JSP-метод include в сервлете суперкласса 
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Выполняется, если процедура синтаксического разбора
	не может получить корректное положительное целое число
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Выполняется, если JNDI-контекст не может быть инициализирован,
	или имя не найдено
    include("NamingException", e, req, res);		
}
catch (RemoteException e) {
    // Выполняется, если объект не может быть уточнен, создан или выполнен
    include("RemoteException", e, req, res);		
}
catch (CustomerNotFoundException e) {
    // Выполняется, если ID пользователя является корректным целым, но не найден
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Выполняется, если пользователь не имеет открытого заказа
    include("OrderNotOpenException", e, req, res);
}

Вот аналогичный фрагмент кода для локальной сессии:

Листинг 2. Активизация локального сессионного компонента
try {
    // Предположим, что процедура получения id существует в сервлете суперкласса
    int cID = getCustomerId(req);

    // Четыре строки кода для активизации метода локальной сессии
    InitialContext initCtx = new InitialContext();
    OrderEntryHome home = (OrderEntryHome)initCtx.lookup(   
        "java:comp/env/OrderEntry"
    );       
    OrderEntry ref = home.create();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Предположим, что существует JSP-метод include в сервлете суперкласса 
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Выполняется, если процедура синтаксического разбора не может получить
	корректное положительное целое число
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Выполняется, если JNDI-контекст не может быть инициализирован,
	или имя не найдено
    include("NamingException", e, req, res);		
}
catch (ClassCastException e) {
    // Выполняется, если объект не может быть приведен к типу класса home 
    include("ClassCastException", e, req, res);		
}
catch (CustomerNotFoundException e) {
    // Выполняется, если ID пользователя является корректным целым, но не найден
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Выполняется, если пользователь не имеет открытого заказа
    include("OrderNotOpenException", e, req, res);
}

Отличие здесь в том, что больше не нужно проверять удаленные исключительные ситуации, а "уточнение" заменено простым оператором cast.

Теперь сравните код локальной сессии с кодом, нужным для активизации эквивалентного метода, реализованного в (локальном) EJB-компоненте управления данными Customer:

Листинг 3. Активизация локального компонента управления данными
try {
    // Предположим, что процедура получения id существует в сервлете суперкласса
    int cID = getCustomerId(req);

    // Пять строк кода для активизации метода локального компонента
	управления данными 
    InitialContext initCtx = new InitialContext();
    CustomerHome home = (CustomerHome)initCtx.lookup(   
        "java:comp/env/Customer"
    );
    CustomerKey key = new CustomerKey(cID);
    Customer ref = home.findByPrimaryKey(key);
    CustomerData data = ref.getOpenOrder();

    // Предположим, что существует JSP-метод include в сервлете суперкласса 
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Выполняется, если процедура синтаксического разбора не может получить
	корректное положительное целое число
    include("ParseException", e, req, res);
}
catch (NamingException e) {
    // Выполняется, если JNDI-контекст не может быть инициализирован,
	или имя не найдено
    include("NamingException", e, req, res);		
}
catch (ClassCastException e) {
    // Выполняется, если объект не может быть приведен к типу класса home 
    include("ClassCastException", e, req, res);		
}
catch (FinderException e) {
    // Выполняется, если пользователь не найден
    include("FinderException", e, req, res);	
}
catch (OrderNotOpenException e) {
    // Выполняется, если ID пользователя не правильный
    include("OrderNotOpenException", e, req, res);
}

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

Обратите внимание на то, что в качестве компромисса нет необходимости передавать ID пользователя в метод getOpenOrder(), поскольку компонент представляет пользователя, ассоциированного с ID. Также обратите внимание на то, что его имя не должно указывать "ForCustomer", чтобы отличить его от других возможных методов фасада сессии.

EJB-компоненты выигрывают от сервис-ориентированных сигнатур
Несмотря на различия, каждый метод возвращает объект CustomerData с ассоциированным объектом OrderData. Каждый объект OrderData ассоциирован с ноль или более объектами LineItemData. Возврат полного "дерева" объектов передачи данных из набора параметров (также возможно дерево DTO) делает архитектуру более сервис-ориентированной путем минимизации "болтливости" между уровнями. Другими словами, один укрупненный вызов - это все, что нужно для сбора данных, необходимых в приведенном выше HttpServlet. Передача DTO-объектов (включая исключительные ситуации) делает связи между клиентом и сервисом не сохраняющими состояния (называемыми также "отсоединенными"), даже если для реализации используется EJB-компонент управления данными, поскольку следующий запрос будет использовать ID пользователя для извлечения ассоциированного логического объекта.

Факт возможной сериализации DTO-объектов делает возможным преобразование их в другие формы при помощи других сервисов, называемых посредниками (mediator). Например, JSP в приведенном выше коде может рассматриваться как посредник для визуализации CustomerData в HTML, ассоциированный с Web-страницей. Если бы для реализации Web-сервиса использовался компонент, шлюз мог бы ассоциировать посредника для преобразования CustomerData в XML-документ, являющийся частью SOAP/HTTP-ответа для не J2EE-клиентов.

Хорошие сервис-ориентированные архитектуры обеспечивают слабое связывание между клиентским кодом, для того чтобы реализацию можно было адаптировать к текущим условиям без изменения клиентского кода. Простейшим примером слабого связывания является кодирование сервиса так, что его реализация может быть удаленной или локальной в зависимости от текущей конфигурации. В качестве более сложного примера представьте, что реализация сервиса submit() должна быть разной в зависимости от категории пользователя (золотой, серебряный, бронзовый, не указано). Home-метод любой версии EJB-компонента, который поддерживает интерфейс этого же компонента, можно связать в пространстве имен JNDI. Имя проектируется так, чтобы оно включало как тип компонента, так и категорию. Во время исполнения категория добавляется к имени для извлечения нужной реализации прозрачно для кода клиента.

Шаблоны делегатов могут значительно упростить клиентский код
Еще одним преимуществом хорошо спроектированных сервис-ориентированных сигнатур, независимо от типа реализующих их EJB-компонентов, является то, что возможно реализовать шаблон делегата (или обнаружитель сервиса). Например, для создания метода общего суперкласса HttpServlet, кэширующего EJB home (или даже ссылку на сессию) и обрабатывающего ошибки обычным способом, можно использовать схему наследования шаблонов, которая обсуждалась в февральской статье.

Но суперкласс шаблона является просто одной формой шаблона делегата (или обнаружителя сервиса). Мы рассматривали другие формы в предыдущих статьях, но не приводили исходный код.

Например, обычно применяемый шаблон делегата, который мы рассматривали в январской статье, использует отдельный Java-класс, доступ к которому осуществляется при помощи оператора new следующим образом:

Листинг 4. Активизация сервиса через делегата с использованием оператора new
try {
    // Предположим, что процедура получения id существует в сервлете суперкласса
    int cID = getCustomerId(req);

    // Две строки кода для активизации сервиса
    OrderEntryDelegate ref = new OrderEntryDelegate();
    CustomerData data = ref.getOpenOrderForCustomer(cID);

    // Предположим, что существует JSP-метод include в сервлете суперкласса 
    include("CustomerWithOpenOrder", data, req, res);
}
catch (ParseException e) {
    // Выполняется, если процедура синтаксического разбора не может получить
	корректное положительное целое число
    include("ParseException", e, req, res);
}
catch (ServiceException e) {
    // Выполняется, если сервис нельзя активизировать  
    include("CustomerNotFoundException", e, req, res);
}
catch (CustomerNotFoundException e) {
    // Выполняется, если ID пользователя является корректным целым числом,
	но не может быть найден
    include("CustomerNotFoundException", e, req, res);
}
catch (OrderNotOpenException e) {
    // Выполняется, если ID пользователя не имеет открытого заказа
    include("OrderNotOpenException", e, req, res);
}

Этот фрагмент кода показывает, что поскольку Вы минимизировали "движущиеся части", строки кода, которые нужно написать для нормального пути и пути обработки ошибок, соответствующим образом сократились. Пока количество потенциальных клиентов, которые будут использовать класс делегата, больше единицы, накладные расходы оправданы.

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

Нужно быть внимательным и не изобретать заново J2EE-среду с делегатами
Одной из проблем, связанных с "новым" подходом к делегированию, является создание и инициализация экземпляра делегата при каждой его активизации, что требует дополнительного времени и генерирует лишние данные, если не используется их кэширование. При кэшировании действительных ссылок этот процесс должен быть потоконезависимым (thread safe), особенно в контексте многопоточного клиента, аналогично HttpServlet.

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

Листинг 5. Реализация делегата с использованием шаблона Singleton
public class OrderEntryDelegate {
    private static OrderEntryDelegate instance = 
        new OrderEntryDelegate();

    public static singleton() { return instance; }

    // Здесь вставьте остальной код делегата, включая кэшированные переменные
}

Еще одной проблемой делегатов, использующих новый или singleton подход, является то, что класс делегата тесно связан с клиентским кодом, даже несмотря на то, что имеется возможность изменить реализацию путем замены JAR-файла, в котором он упакован. Некоторые разработчики обходят эту проблему тесного связывания, создавая интерфейс на чистом Java, который возвращается классом, выступающим в роли фабрики. Этот класс Factory имеет статический метод, как и класс singleton, но в отличие от него возвращает интерфейс:

Листинг 6. Реализация делегата с использованием шаблона Factory
public interface OrderEntryDelegate {
    // Вставить сигнатуры сервисов
}

public class OrderEntryDelegateImpl
implements OrderEntryDelegate {
    // Вставить реализации делегатов
}

public class OrderEntryFactory {
    private OrderEntryDelegate instance = 
        new OrderEntryDelegateImpl();

    public static OrderEntryDelegate getDelegate() {
        return instance;        
    }
}

Естественно, такой подход добавляет два дополнительных "Замечания", которые нужно учитывать в архитектуре. Для устранения одного из классов некоторые разработчики комбинируют фабрику с реализацией по умолчанию, заставляя ее реализовывать интерфейс делегата (очень похоже на работу с JNDI InitialContext). А для получения возможности повторного использования некоторые группы разработчиков используют интерфейс Delegate как часть локального интерфейса EJB и классов реализации компонентов, как показано ниже:

Листинг 7. Повторное использование интерфейса delegate в локальном сессионном EJB-компоненте
public interface OrderEntry 
extends OrderEntryDelegate, EJBLocalObject {
}

public class OrderEntryBean 
implements OrderEntryDelegate, SessionBean {
   // Вставить реализации 
}

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

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

Листинг 8. Реализация делегата с использованием шаблона Factory Finder
public interface Factory {
}

public interface OrderEntryFactory extends Factory {
    public OrderEntryDelegate getDelegate();
}

public class OrderEntryFactoryImpl implements OrderEntryFactory {
    private OrderEntryDelegate instance = 
        new OrderEntryDelegateImpl();

    public OrderEntryDelegate getDelegate() {
        return instance;        
    }
}

public class FactoryFinder {
    private HashMap factories = new HashMap();
    public FactoryFinder() {
       // Связывание реализаций, возможно с использованием переменных окружения
    }
    public Factory getFactory(String name) {
        return (Factory)factories.get(name);
    }
}

До J2EE в этом направлении развивались корпоративные интегрированные Java-среды большинства разработчиков. Но должно быть очевидно, что такой экстремальный подход к делегатам - это полное изобретение заново интегрированной среды EJB:

  • FactoryFinder - это эквивалент JNDI Context.
  • Factory - это эквивалент Home.
  • Delegate - это эквивалент локального интерфейса EJB.
  • DelegateImpl - это эквивалент локальной реализации EJB.

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

Классы helper могут тестироваться вне контейнера и минимизируют потребность в EJB-компонентах
Но прежде чем отказаться от подхода delegate, мы должны рассмотреть преимущества использования вспомогательных классов вместо EJB для полного сокрытия использования J2EE от прикладных программистов (клиентских или серверных программ), как хотел Cross. Большинство групп разработчиков, которые использовали шаблон helper, обратило внимание на преимущества модульного тестирования без EJB-контейнера. При комбинировании с основанным на Factory (или FactoryFinder) шаблоне delegate класс Helper может замещать DelegateImpl для тестирования функциональной верификации (Functional Verification Testing - FVT), в котором совместно тестируется несколько сервисов. Для разрешения такого замещения класс Helper всего лишь должен расширять соответствующий интерфейс:

Листинг 9. Повторное использование интерфейса delegate в классе helper
public class OrderEntryHelper
implements OrderEntryDelegate {
    // Вставить реализации 
}

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

Для тестирования и эксплуатации, системы реализации сессионных компонентов являются реальными фасадами, которые передают информацию классу helper во всех случаях:

Листинг 10. Реализация сессионного компонента, использующего шаблон Helper
public class OrderEntryBean 
implements OrderEntryDelegate, SessionBean {
   private OrderEntryHelper helper = null;
   public void ejbCreate() {
       helper = new OrderEntryHelper();
   }

   // Вставить реализации сервисов
   public CustomerData getOpenOrderForCustomer(int cID) {
       return helper.getOpenOrderForCustomer(cID);
   }
}

Делегат уровня бизнес-объектов SVT и эксплуатации может быть заменен CMP-реализацией, как показано здесь, или даже JDBC-реализацией (если Вы не читали CMP-дискуссию в наших последних двух статьях). В любом случае архитектура может быть настроена на использование только реализаций сессионных компонентов в "самых крайних" делегатах для минимизации использования EJB-компонентов, но получения, все же, возможности реализации транзакций, системы защиты и даже распространения, которые они обеспечивают.

Так сколько же "замечаний" у нас остается? На самом деле их даже больше, чем прежде, но они возникают в другое время:

  • Только один раз - интерфейс FactoryFinder, Factory.
  • Поэтапно - интерфейс DelegateFactory, интерфейс Delegate, реализация Helper, DTO-объекты Key, Query и View.
  • UT и FVT - реализация FVTFactory (возвращает реализацию helper, если используются сервисы сессии/задания), CachingDelegate (для сервисов данных).
  • SVT и эксплуатация - реализация SVTFactory, реализация Delegate, EJB Home, интерфейс EJB, реализация EJB.

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

Контекст выполнения J2EE может упростить сигнатуры и ассоциированный код
Многие забывают одну вещь, когда скрывают интегрированную J2EE-среду за классами delegates и helper, - контекст выполнения J2EE не доступен. Фактически, многие J2EE-программисты забывают использовать его для упрощения сигнатур сервисов.

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

Листинг 11. Активизация неизвестного типа EJB-компонента с использованием шаблона Template Inheritance
public class GetOpenOrderServlet extends CustomerServlet
{
    public void doGet(
        OrderEntryDelegate ref,
        HttpServletRequest req,
        HttpServletResponse res
    ){
        try {
    	    // ref передается из суперкласса шаблона    
    	    CustomerData data = ref.getOpenOrder();

	    // Предположим, что JSP-метод include существует в суперклассе
	    include("CustomerWithOpenOrder", data, req, res);
        }
        catch (OrderNotOpenException e) {
            // Выполняется, когда ID пользователя не имеет открытого заказа
            include("OrderNotOpenException", e, req, res);
        }
    }
}

Этот код показывает, что способность наследования ID пользователя из J2EE-контекста делает сигнатуру сервиса такой же, независимо от того, реализован он в пользовательском сессионном EJB-компоненте или EJB-компоненте управления данными.

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

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

Так на чем же мы остановились в нашем анализе? Все взвесив (даже при наличии неудовлетворенной потребности в облегченной среде модульного тестирования EJB-компонентов), я должен согласиться с Вами, что при устранении отдельных классов delegate и прямой активизации соответствующего EJB-компонента архитектура становится проще.

Надеюсь, это поможет.
Ваш EJB Advocate


Вопрос о CMP без фасадов сессии, на который не получен ответ

Уважаемый EJB Advocate!

Ваш анализ был относительно полезен, но мы боимся, что сбили Вас с темы, которая интересует нас на самом деле, - прямого вызова логического объекта Customer при использовании сервис-ориентированного подхода. Наши рассуждения состояли в том, что Customer представляет "персистентную" сессию с взаимосвязями со всеми данными, требующимися для нужных нам методов. А поскольку мы используем укрупненные не сохраняющие состояния сервис-ориентированные вызовы, аналогичные предложенным Вами, фасад сессии кажется лишним.

Вот три строки кода, о котором идет речь, после получения ссылки на home (мы кэшировали его при запуске клиентской программы):

CustomerKey key = new CustomerKey(cID);
Customer ref = home.findByPrimaryKey(key);
CustomerData data = ref.getOpenOrder();

Нам сначала понравилась идея наследования ID пользователя из J2EE-контекста для упрощения сигнатур, но в подобных ситуациях (когда вовлечен агент пользовательского сервиса) нужно быть внимательным - в "персистентный сессионный" CMP все равно нужно передавать ID пользователя или использовать CMR "текущего пользователя".

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

Спасибо.
Too Many Notes (Слишком много замечаний)


Специализированные методы Home компонентов управления данными могут решить проблему

Too Many Notes!

Рассматривали ли Вы использование специализированных методов EJB Home логического объекта? В данной статье я впервые их действительно рекомендую, поскольку Ваша группа разработчиков, кажется:

  1. Стремится минимизировать количество компонентов и
  2. Очень комфортно себя чувствует, используя OO CMP с CMR.

Специализированные методы Home EJB-компонентов управления данными реализовать довольно легко: просто переместите код из фасада сессии в метод ejbHome<methodName> класса реализации EJB:

Листинг 12. Реализация метода home EJB-компонента управления данными
    public CustomerData  ejbHomeGetOpenOrderForCustomer(int id) 
    throws OrderNotOpenException
    {
	// Получить DTO Customer 
	CustomerKey key = CustomerKey new(id)
	Customer ref = findByPrimaryKey(key);
	return ref.getDataWithOpenOrder();
    }

Затем поместите метод в интерфейс EJB Home:

public CustomerData ejbHomeGetOpenOrderForCustomer(int id);

При использовании метода Home компонента управления данными клиентский код получается еще проще, чем для сессионного EJB-компонента, даже если не кэшировать ссылку на Home:

Листинг 13. Активизация метода home EJB-компонента управления данными.
    InitialContext initCtx = new InitialContext();
    CustomerHome home = (CustomerHome)initCtx.lookup(   
        "java:comp/env/Customer"
    );
    CustomerData data = home.getOpenOrderForCustomer(cID);

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

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

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

Ваш EJB Advocate


Заключение

Подводя итоги данной дискуссии, можно отметить следующие специфические отличия каждого типа EJB-компонента:

  1. Способ извлечения Home из JNDI-контекста (имя, использованное для поиска, и необходимость сужения или приведения типов).
  2. Способ доступа к ссылке на компонент из Home (необходим ли ключ или запрос, создавать или искать используемый метод, можно ли обратиться к сервису напрямую в самом home).
  3. Параметры, передаваемые при активизации метода service (передавать ли параметр ID).
  4. Природа возвращаемых результатов (Disconnected, Connected).
  5. Природа генерируемых ошибок (Checked, Unchecked).

Наилучшие EJB-компоненты являются сервис-ориентированными, с функциями, которые являются:

  1. Укрупненными - функции разработаны для минимизации количества вызовов между уровнями.
  2. Не сохраняющими состояния - все ассоциированные с данной функцией данные передаются в одном вызове, что позволяет обработать запрос любого клиента любым работающим экземпляром в любом порядке.
  3. Посредническими - передаваемые данные являются сериализуемыми, что позволяет легко их преобразовывать.
  4. Адаптируемыми - связи между клиентом и реализацией сервиса являются логическими (слабо связанными), а не физическими (тесно связанными). Слабое связывание позволяет заменять различные реализации без изменения приложения, а также, при необходимости, распределять или размещать совместно клиент и сервер.

Классы POJO delegate часто используются сервисами для упрощения активизации сервисов обычным способом. Классы Helper используются для упрощения создания сервисов. Но классы delegate и helper часто развиваются из простых в сложные, для того чтобы обеспечить адаптируемость, как описано выше:

  1. Методы суперкласса - во время исполнения не нужен дополнительный экземпляр.
  2. Оператор New - создает экземпляр класса реализации delegate.
  3. Singleton - возвращает ссылку на один экземпляр объекта delegate.
  4. Factory - класс, возвращающий реализацию интерфейса delegate.
  5. FactoryFinder - класс, возвращающий Factory, как описано выше.

Проблема такого развитии заключается в том, что классы delegate избыточны в интегрированной J2EE-среде. Вот некоторые функциональные возможности основанных на FactoryFinder классов delegate и helper, которые нужно принимать во внимание:

  1. Разрешение модели чистого Java-программирования как на клиенте, так и на сервере.
  2. Разрешение UT и FVT без необходимости EJB-контейнера.
  3. Разрешение развертывания для SVT и эксплуатации без затрагивания реализаций сервиса.
  4. Минимизация необходимости в EJB-компонентах.
  5. По существу, изобретение заново подхода Local Session EJB.

Аналогично, вот некоторые функциональные возможности EJB-компонентов, которые нужно принимать во внимание:

  1. Использование J2EE-контекста может упростить сигнатуры сервиса.
  2. Качество сервиса прозрачно для программистов.
  3. Инструментальные средства развертывания, необходимые для генерирования классов развертывания.
  4. Необходимость платформы времени исполнения для реализации.
  5. Можно реализовать облегченную версию UT/FVT интегрированной J2EE-среды почти с такой же легкостью, что и полный шаблон FactoryFinder delegate.

Учитывая, что я - EJB Advocate, Вы можете понять, что я склонен использовать J2EE-компоненты без отдельных классов delegate. Возможно, сейчас самое время, чтобы корпоративные Java-программисты "демаскировали" их, поэтому вот несколько практических правил, о которых нужно помнить при прямом отображении EJB-компонентов:

  1. Если имеется естественный объект-"шлюз", тесно связанный с пользователем, активизирующим функцию, а также взаимосвязи, поддерживаемые используемой моделью данных, которая предоставляет доступ ко всем нужным для функции данным, рассмотрите возможность отображения сервиса в виде специализированного метода home логического объекта используя CMR. Короче говоря, метод Home становится эквивалентом фасада сессии.
  2. Если имеются только случайные композиции данных для чтения или обновления, составленные из данных в параметрах сервиса, без "персистентных" взаимосвязей между ними, рассмотрите возможность отображения сервиса в виде метода сессионного компонента.
  3. Тем не менее, в правиле 2 рассмотрите возможность поиска кластеров связанных данных, которые могут применить правило 1, для реализации повторного использования.

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

В любом случае всегда помните о том, что EJB Advocate никогда не говорит "никогда" или "всегда".

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=WebSphere, Технология Java
ArticleID=302152
ArticleTitle=EJB Advocate: Всегда ли лучше использовать EJB-компоненты без фасадов в сервис-ориентированных архитектурах?
publish-date=04222008