суббота, 23 марта 2013 г.

Практический пример построения сервиса на Oracle Service Bus

В данной заметке мы рассмотрим практический пример построения веб-сервиса, работающего по протоколу HTTP с использованием сообщений в формате SOAP (SOAP over HTTP), функциональность которого заключается в предоставлении доступа к бизнес-логике существующего приложения. Приложением является биллинговая система, реализованная на основе двухзвенной архитектуры. Связь сервисной шины предприятия и приложения будет осуществляться с помощью JDBC путем вызова хранимых процедур в базе данных биллинга. Разрабатываемый сервис будет предоставлять доступ к управлению счетами: получение данных о счете, создание счета и отмена счета.

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


Проектирование сервиса


При проектировании сервиса мы должны ответить на следующие вопросы:
  • какие операции поддерживает сервис?

  • какие шаблоны обмена, используются каждой операцией?

  • какие входящие и, если есть, исходящие сообщения используются каждой операцией?

Некоторые советы по проектированию контракта сервиса приведены в статье Проектирование контракта сервиса. В данной заметке мы разработаем сервис управления счетами (инвойсами). При этом данный сервис должен поддерживать следующие операции:

  • GetInvoice - возвращает инвойс по идентификатору. В качестве входных данных операция принимает идентификатор инвойса, а возвращает бизнес-объект - инвойс.

  • SaveInvoice - сохраняет инвойс в биллинге. При этом если указанный инвойс не существует, то он создается, иначе - обновляется. В качестве входных данных операция принимает идентификатор и сам инвойс, а возвращает идентификатор созданной или обновленной сущности.

  • CancelInvoice - отменяет выставленный счет. В качестве входных данных операция принимает идентификатор инвойса, а возвращает пустое значение.

В случае возникновения каких-либо исключительных ситуаций все операции возвращают сообщение InvoiceFault.



Физически создаваемый сервис описывается с помощью WSDL. При этом данный WSDL ссылается на XSD-схемы, описывающие сообщения, посредством которых сервис обменивается данными с внешним миром. Примем за основу размещение артефактов, характерное для Oracle Application Integration Architecture: схемы, описывающие корпоративные бизнес-объекты (Enterprise Business Objects, EBO, в нашем примере - инвойс), вынесем в подкаталог, который так и будет называться - EBO. Схемы, описывающие сообщения (Enterprise Business Messages, EBM), использующие корпоративные бизнес-объекты, вынесем в каталог EBM. WSDL-документы, описывающие сервисы (Enterprise Business Service, EBS), вынесем в каталог EBS. Для хранения данных артефактов создадим отдельный OSB-проект - MDS. Вынесение артефактов, описывающих канонические сервисы, в отдельный проект позволяет ссылаться на них из любого проекта, развернутого на шине. Тем самым мы локализуем размещение общих объектов и устраняем ад зависимостей.



Разработка адаптеров к базе данных


После разработки интерфейса сервиса можно пойти двумя путями: создать прокси-сервис, использующий данный интерфейс, и начать разработку логики сервиса. А можно создать сначала все объекты, от которых сервис будет зависеть, чтобы в последующим ничего не отвлекало от разработки его логики. Мы пойдем вторым путем - создадим адаптеры к БД биллинга, а затем сгенерируем на их основе бизнес-сервисы Oracle Service Bus.

Для соединения с базой данных и Oracle Service Bus, и Oracle SOA Suite используют DBAdapter - приложение, соответствующее стандарту JCA и позволяющее осуществлять разнообразные операции над базой данных: последовательный опрос (полинг), вызов хранимых процедур, а так же выполнение произвольных SQL-запросов. Настройка адаптера осуществляется в два этапа: во время разработки мы указываем какие операции над базой хотим выполнять с помощью адаптера, а во время развертывания настраиваем в адаптере пулы соединений, которые он будет использовать для подключения к БД.

Настройка адаптера с помощью JDeveloper

Настройка использования адаптера осуществляется с помощью JDeveloper'а. К сожалению Oracle Enterprise Pack for Eclipse (OEPE) не содержит средств для настройки JCA-адаптеров, поэтому в работе придется использовать две IDE: JDeveloper для настройки адаптеров и OEPE для разработки под OSB.

Для начала работы с адаптерами необходимо создать SOA-приложение. Запустить мастер создания нового приложения можно из главного меню JDeveloper - File -> New. В открывшемся окне мастера необходимо выбрать General -> Application -> SOA Application и нажать OK.



На первом шаге мастера необходимо указать наименование приложения, каталог для хранения его исходного кода (должен совпадать с корневым каталогом рабочего пространства Eclipse), а так же пакет по-умолчанию для Java-классов приложения.



На втором шаге мастера нам предложат задать настройки для первого проекта, входящего в приложение. Необходимо задать наименование проекта и его расположение. При этом расположение необходимо настроить таким образом, чтобы исходные коды проекта попали в подкаталог composite каталога проекта OSB. Т.е. если мы после завершения работы мастера JDeveloper'а откроем в Eclipse проект OSB, то увидим в нем подкаталог composite.



В JDeveloper'е появится окно редактирования файла composite.xml. Необходимо перетащить в поле этого окна значок Database Adapter с палитры компонентов.



Откроется мастер настройки адаптера подключения к базе данных. На первом шаге мастера необходимо задать наименование адаптера.



На втором шаге мастера необходимо выбрать или настроить соединение с БД, из которой мастер будет считывать метаинформацию, например описание интерфейсов хранимых процедур, во время настройки адаптера. Так же на данном шаге мастера необходимо задать JNDI-наименование пула соединений адаптера. Важно! Указывать нужно не JNDI-имя источника данных, а JNDI-имя пула соединений, настроенного в адаптере. Ниже мы рассмотрим процесс настройки пулов соединений для адаптера подробнее.



На третьем шаге мастера необходимо выбрать операцию, которую адаптер будет выполнять над БД. В нашем случае это - вызов хранимой процедуры (Call a Stored Procedure or Function).



На четвертом шаге мастера необходимо выбрать хранимую процедуру. Сделать это можно с помощью кнопки Browse, расположенной справа от поля Procedure. При необходимости можно явно указать имя используемой схемы в базе данных.





После выбора хранимой процедуры в окне мастера отобразится список ее параметров с указанием их типов и направления (входящие или исходящие).



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



На последнем шаге мастера нас проинформируют о том, что будут созданы XSD- и WSDL-файлы, описывающие адаптер.



После завершения работы мастера его окно закроется, а в поле External References окна редактирования композита появится значок адаптера GetInvoiceDBAdapter.



Настройка адаптеров для операций SaveInvoice и CancelInvoice осуществляется аналогично.



В Eclipse созданные артефакты добавятся в дерево проектов.



Генерация бизнес-сервисов для Oracle Service Bus

Oracle Service Bus использует адаптеры, через которые осуществляется отправка сообщений во внешнюю систему посредством механизма бизнес-сервисов.

В состав OEPE входит мастер генерации сервисов по описанию JCA-адаптеров, что существенно упрощает работу. Для генерации сервиса необходимо в дереве проектов выбрать файл с расширением .jca, в контекстном меню которого будет доступен пункт Oracle Service Bus -> Generate Service.



В окне генерации сервиса необходимо задать его название, название файла с WSDL-описанием данного сервиса, а так же выбрать каталог для их размещения. Я предпочитаю размещать бизнес-сервисы в каталоге business.



WSDL-описание сгенерируется в этом же каталоге, но затем его можно будет перенести в заранее подготовленный каталог interface.



Разработка прокси-сервиса, реализующего логику интеграции


Создание прокси-сервиса

Логика интеграции в Oracle Service Bus реализуется с помощью потока обработки сообщений, который в свою очередь описывается в прокси-сервисе. Создается прокси-сервис с помощью мастера, доступного из контекстного меню New -> Proxy Service любого из подкаталогов проекта, например, подкаталога proxy.



В мастере создания прокси-сервиса необходимо задать наименование файла. Так же здесь можно при необходимости подкорректировать его размещение.



Редактор прокси-сервиса состоит из нескольких вкладок. Нас интересуют вкладки General, Transport и Message Flow. На вкладке General необходимо задать тип сервиса - основанный на WSDL-описании веб-сервис. Для данного типа необходимо задать порт или связывание. Сделать это можно, нажав на кнопку Browse, расположенную рядом с полем WSDL Web Service.



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



Выбранный WSDL-файл, а так же порт или связывание отобразятся на вкладке общих настроек сервиса.



На вкладке Transport необходимо выбрать протокол, по которому будет доступен сервис. В нашем случае это протокол http. Так же необходимо задать URI конечной точки сервиса.



Для http сервис будет доступен по следующему URL: http://OSB_SERVER_HOST:OSB_SERVER_PORT/ENDPOINT_URI. Для домена, в котором создан OSB for Developers - URL сервиса InvoiceManagementService будет следующим: http://localhost:7001/integration/billing/InvoiceManagementService.

На вкладке Message Flow реализуется логика интеграции.

Реализация логики интеграции

Как было рассмотрено выше, для каждой операции реализуемого сервиса нам необходимо вызвать соответствующий бизнес-сервис, поэтому первое что нужно сделать - определить какая именно операция веб-сервиса вызывается. В Oracle Service Bus для этого служит блок Operational Branch, который генерирует по ветке для каждой операции, плюс ветку для операции по-умолчанию. Управление передается на ветку по-умолчанию, если вызвана операция сервиса, которая не найдена в других ветках.



Наименование операции для каждой ветки задается в свойстве Operation.



Реализация каждой ветки будет состоять из блока Route, содержащего операцию Routing, реализующую статический роутинг. Потребностей в более сложном роутинге в данном примере нет, т.к. для каждой операции заранее известен бизнес-сервис, ее реализующий.

В настройках статического роутинга необходимо указать сервис, на который выполняется перенаправление сообщения.



Выбор сервиса осуществляется с помощью кнопки Browse, расположенной справа от поля Service. В окне выбора доступны все сервисы рабочей области Eclipse, при этом можно выбирать не только бизнес-сервисы, но и прокси-сервисы, т.е. вызов прокси-сервиса из прокси-сервиса вполне допустим.



После выбора сервиса необходимо указать вызываемую операцию. Если наименование вызываемой операции совпадает с наименованием операции, из которой осуществляется маршрутизация, то можно поставить галочку Use Inbound operation for outbound, однако при использовании адаптеров имена операций совпадают с наименованиями адаптеров.



Трансформация сообщений

После настройки маршрутизации необходимо настроить трансформацию сообщений. Понятно, что сообщения, которыми оперирует сервис и описания которых сохранены в каталоге EBM, не совпадают с сообщениями, которыми оперирует адаптер. При настройке адаптера с помощью мастера генерируются XSD-схемы, описывающие входящие и исходящие сообщения. Эти сообщения содержат входные и выходные параметры соответствующей хранимой процедуры. Найти схемы можно в подкаталоге xsd каталога composite.

Основным средством трансформации XML-сообщений в Oracle Service Bus является XQuery. Создать XQuery-преобразование можно с помощью мастера, вызываемого из пункта меню New -> XQuery Transformation.



На первом шаге появившегося мастера необходимо выбрать каталог для размещения файла и его наименование.



На втором шаге мастера необходимо выбрать входящие параметры. Для операции GetInvoice это - сообщение GetInvoice, описание которого содержится в файле InvoiceEBM.xsd.



На третьем шаге мастера необходимо выбрать исходящие параметры - результат преобразования. Для операции GetInvoice это - InputParameters для адаптера GetInvoiceDBAdapter.



После завершения работы мастера в Eclipse откроется перспектива XQuery Transformation perspective, а в ней - визуальный редактор трансформации. В редакторе можно попытаться соединить параметры ExternalId и PINVOICE_ID, однако выполнить это будет невозможно, т.к. данные параметры имеют различные типы.



Нужно выполнить приведение типов. Для этого в окно Target Expression необходимо перетащить соответствующую функцию из вкладки Expression Functions (каталог функций - Type Conversion Functions).



Затем необходимо перетащить операнд данной функции с левой половины визуального редактора преобразований - поле ExternalId.



После нажатия кнопки Apply на вкладке Target Expression редактирование выражения завершится, а в редакторе появится стрелочка, соединяющая ExternalId и PINVOICE_ID.



Код XQuery-преобразования будет следующим:



Аналогичным образом необходимо настроить преобразование для ответа. Входными данными при этом будет элемент OutputParameters схемы GetInvoiceDBAdapter_sp.xsd



а выходными - GetInvoiceResponse схемы InvoiceEBM.xsd.



Само визуальное преобразование будет выглядеть следующим образом:



Использование трансформации

Созданные XQuery-преобразования необходимо добавить в поток обработки сообщений. Для замены одного из узлов XML-документа другим XML-документом служит операция Replace, расположенная на палитре компонентов в блоке Message Processing.



У операции Replace нужно настроить следующие параметры: XPath - выражение, адресующее заменяемый блок данных, In Variable - наименование переменной, в которой необходимо выполнить замену, Expression - выражение, вычисляющее новое значение заменяемого узла. Так же есть дополнительный переключатель Replace entire node/Replace node contents, который устанавливает заменять ли весь адресуемый XPath узел или только его содержимое.

Для потока обработки сообщения-запроса XPath-выражение будет следующее: ./urn:GetInvoice.



Переменная, в которой мы выполняем замену, - body. В данной переменной находится содержимое элемента soap-env:Body SOAP-запроса и очень важно, что во время прохождения по потоку обработки сообщения данная переменная всегда должна содержать soap-env:Body, а вот содержимое этого элемента может меняться перед вызовом каждого сервиса.

В качестве выражения будет использовать созданное ранее XQuery-преобразование GetInvoiceTransform. Выбрать данное преобразование можно на вкладке XQuery Resources редактора выражений.



У данного преобразования есть один параметр - getInvoice. Значение данного параметра равно значению выражения $body/urn:GetInvoice.



Итоговые настройки трансформации для запроса следующие:



Настройка трансформации для ответа выполняется аналогично, однако есть некоторые нюансы. Дело в том, что после выполнения хранимой процедуры нам возвращается сообщение, содержащее выходные параметры данной процедуры. Данного сообщения нет в визуальном редакторе XPath-выражений и поэтому соответствующие выражения нужно писать вручную. В данном случае XPath-выражение будет следующим: ./db:OutputParameters.



Второй нюанс заключается в том, что пространство имен db не определено. Необходимо указать редактору новое пространство имен. Сделать это можно на вкладке Namespace Definitions, нажав кнопку Add. В появившемся окне необходимо указать префикс (Prefix) и определение пространства имен (URI). Скопировать пространство имен можно из схемы GetInvoiceDBAdapter_sp.xsd.



В качестве переменной для выполнения замены необходимо так же, как и для запроса указать body.

В качестве выражения необходимо использовать XQuery-преобразование GetInvoiceResponseTransform.xq. Значение входящего параметра outputParameters преобразования равно значению выражения $body/db:OputputParameters. Данное выражение необходимо написать вручную.



Логика для операций SaveInvoice и CancelInvoice настраивается аналогично.



Настройка подключения к БД на сервере приложений Oracle WebLogic


Подключение к БД для Oracle Service Bus на сервере приложений Oracle WebLogic настраивается в два этапа: сначала создается источник данных для подключения к БД, а затем настраивается пул соединений, использующий этот источник данных, в адаптере DBAdapter.

Создание источника данных

Для создания источника данных в консоли администрирования сервера приложений WebLogic используется мастер, запускаемый с помощью кнопки New, расположенной на странице Services -> Data Sources. Существует несколько вариантов источников данных, для наших целей подходит Generic Data Source.



На первой странице мастера создания источника данных необходимо задать его наименование, JNDI-наименование и выбрать тип СУБД.



На второй странице мастера необходимо выбрать тип драйвера. В нашем случае нужно выбрать драйвер без поддержки распределенных XA-транзакций, т.к. в нашем сервисе будет использоваться только один источник данных - БД информационной системы.



На третьей странице мастера необходимо настроить эмуляцию распределенных транзакций. Для нашего сервиса такая эмуляция не нужна, поэтому надо снять галочку Supports Global Transactions.



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



После нажатия кнопки Next данные параметры будут преобразованы в значения свойств JDBC и появится кнопка Test Configuration, нажав которую, можно протестировать подключение к базе данных.



На следующей странице мастера необходимо выбрать сервер или кластер, на котором будет развернут источник данных.



После нажатия кнопки Finish созданный источник данных появится в таблице Data Sources.



Настройка DBAdapter'а

После создания источника данных необходимо настроить пул соединений для DBAdapter'а. Для этого необходимо открыть страницу Deployments и перейти по ссылке DbAdapter.



На странице адаптера необходимо перейти на вкладку Configuration -> Outbound Connection Pools. На данной вкладке в списке javax.resource.cci.ConnectionFactory отображаются все созданные пулы соединений.



Для создания нового пула соединений необходимо нажать кнопку New. Откроется окно Create a New Outbound Connection. В данном окне необходимо выбрать единственную предложенную фабрику соединений - javax.resource.cci.ConnectionFactory.



После нажатия кнопки Next откроется второе окно мастера создания нового пула соединений, в котором необходимо задать его JNDI-наименование. Необходимо ввести наименование, совпадающее с введенным при создании DBAdapter'а с помощью JDeveloper'а.



При нажатии кнопки Finish, в случае, если адаптер на сервере настраивается первый раз, нам предложат выбрать файл Plan.xml для хранения настроек адаптера. Можно согласиться с использованием файла по-умолчанию.



Суть здесь в следующем: параметры JCA-адаптера, измененные с помощью консоли администрирования, сохраняются в плане развертывания Plan.xml. После сохранения настроек необходимо обновить приложение с использованием нового плана, о чем нас любезно предупредят после нажатия кнопки OK.



Однако мы только создали пул соединений, но никак его не настроили. Для настройки пула необходимо вернуться на вкладку Configuration -> Outbound Connection Pools приложения DbAdapter и выбрать созданный пул из списка.



В таблице настроек пула необходимо указать JNDI-наименование источника данных в качестве значения параметра dataSourceName. Если бы мы создали источник данных с поддержкой XA-транзакций, то его наименование нужно было бы указывать в качестве значения параметра xADataSourceName.



На вкладке Transaction необходимо выбрать тип транзакций, поддерживаемый пулом соединений. Для нашего примера подойдут обычные локальные транзакции: Local Transaction.



После сохранения настроек пула в плане развертывания необходимо обновить приложение. Для этого на странице Deployments нужно выбрать - отметить галочкой - приложение DbAdapter и нажать кнопку Update. Появится окно настроек обновления приложения, в котором при необходимости можно выбрать путь до плана развертывания.



После нажатия кнопки Finish запустится обновление адаптера, по завершению которого консоль уведомит нас о том, что перезагружать сервер не нужно.



Теперь сервер приложений готов к развертыванию на нем созданных проектов Oracle Service Bus.

Развертывание проектов Oracle Service Bus на сервере приложений


Для развертывания проектов Oracle Service Bus на сервере приложений воспользуемся средствами OEPE. Прежде всего необходимо настроить подключение к серверу. Сделать это можно с помощью мастера настройки подключения, который запускается путем вызова пункта New -> Server контекстного меню вкладки Servers.



На первом шаге мастера необходимо выбрать тип и версию сервера приложений. Для развертывания примера подойдет Oracle WebLogic Server 11R1, начиная с версии 10.3.3. Здесь же можно задать наименование хоста, на котором расположен сервер, а также наименование подключения, которое будет отображаться на вкладке Servers.



На втором шаге мастера можно исправить наименование подключения, а так же указать домашний каталог сервера приложений и путь к JDK.



На третьем шаге мастера необходимо выбрать тип подключения к серверу - локальное или удаленное. Для локального подключения необходимо выбрать каталог с доменом.



На четвертом шаге мастера можно выбрать разворачиваемые на сервере конфигурационные проекты OSB.



После нажатия кнопки Finish сервер появится на вкладке Servers. При этом в дереве развернутых на сервере ресурсов появится разрабатываемый конфигурационный проект OSB. Публикацию ресурсов на сервер можно провести, нажав кнопку Publish.



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



После завершения публикации проекты появятся в дереве проектов консоли администрирования OSB.



Состояние сервера при этом изменится на Started, Synchronized.



Тестирование сервиса с помощью SOAP UI


После того, как сервис развернут на сервере приложений, его можно протестировать. Я предпочитаю пользоваться замечательной утилитой, специально предназначенной для тестирования веб-сервисов, - SOAP UI.

Прежде всего необходимо создать новый проект SOAP UI. При создании проекта нужно указать его наименование и путь к WSDL-описанию сервиса. Путь к WSDL-описанию сервиса InvoiceManagementService совпадает с адресом конечной точки, который был задан при создании прокси-сервиса на вкладке Transport, только в конце необходимо добавить ?wsdl.



Прежде всего протестируем операцию создания нового инвойса - SaveInvoice. Развернем дерево проектов SOAP UI, выберем соответствующий запрос и откроем его. В параметрах запроса уберем необязательный параметр ExternalId, а так же зададим значение других параметров инвойса. После исполнения запроса в правой части окна появится ответ, возвращенный шиной. Данный ответ можно проверить на соответствие схеме. В случае корректного ответа появится окошко Validation OK.



Теперь можно проверить работу операции GetInvoice. Значение параметра ExternalId необходимо установить равным возвращенному операцией SaveInvoice. Тогда после исполнения операции в правой части окна появятся сведения о созданном нами ранее инвойсе.



Аналогичным образом можно проверить работу операции CancelInvoice. После отмены инвойса его параметры станут недоступны и вот тут мы подходим к следующей важной проблеме, которую необходимо решить при разработке сервиса, - к обработке ошибок.

Обработка ошибок


При разработке интеграционного решения очень важно обеспечить правильную реакцию системы на ошибки. В случае синхронного веб-сервиса необходимо вернуть специальное, предопределенное в стандарте SOAP значение внутри элемента soap-env:Body - soap-env:Fault. У элемента soap-env:Fault есть дочерний элемент detail, в качестве значения которого можно передавать свою структуру, содержащую детальное описание ошибки. При разработке WSDL-описания сервиса мы предусмотрели такую структуру, она называется InvoiceFault. Теперь, чтобы облегчить разработку обработки ошибок, необходимо создать XQuery-трансформацию, заполняющую данную структуру - InvoiceFaultTransform.



В качестве входных значений трансформация будет принимать строки. Для того, чтобы задать строку в качестве входного значения, на соответствующем шаге мастера создания трансформации нужно переключить Available Source Types в значение Simple и выбрать из списка string.



Таким образом необходимо настроить три входных параметра: errorcode - код ошибки, errormessage - сообщение об ошибке и externalid - идентификатор запрашиваемого инвойса.



В качестве результата преобразования необходимо указать элемент InvoiceFault.



Само преобразование тривиально за исключением элемента externaid. Данный элемент может отсутствовать, т.е. быть незаполненным, если, например, произошла какая-то общая ошибка и externalid недоступен. Для проверки на наличие элемента будем использовать XQuery-оператор for:



В визуальном редакторе преобразование выглядит следующим образом:



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

Обработка кодов ошибок, возвращаемых хранимыми процедурами

Первая точка возникновения ошибок - это обработка ответа, возвращаемого хранимыми процедурами. Что будет, если запрашиваемого инвойса нет в базе данных? Хранимая процедура вернет соответствующий код ошибки в качестве значения параметра PERROR_CODE. Соответственно, после вызова процедуры через Routing, необходимо сначала проверить значение параметра PERROR_CODE, а затем осуществлять трансформацию. Для осуществления проверок существует блок If Then, состоящий из нескольких веток If, срабатывающих при верности какого-либо условия и блока Else, срабатывающего, если ни одно из условий не верно.



При этом необходимо настроить условие, говорящее о корректном завершении работы хранимой процедуры. Таким условием является равенство нулю кода ошибки PERROR_CODE.



В данную ветку необходимо перетащить настроенный нами ранее оператор Replace.

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



Формирование ошибки при этом будет выполняться в два этапа. Сначала, с помощью действия Replace и преобразования InvoiceFaultTransform сформируем сообщение InvoiceFault. Затем, с помощью второго действия Replace и формирования XML вручную сформируем SOAP Fault.

Формирование сообщения InvoiceFault будет выглядеть следующим образом.



Код INV-002 соответствует ошибке Инвойс не найден. В качестве значения параметра externalid подставляем значение переменной, сохраненной в потоке обработки запроса. Здесь хранится идентификатор запрошенного инвойса. В качестве значения параметра errormessage передаем значение поля PERROR_MESSAGE, возвращаемое хранимой процедурой.

На втором шаге преобразований заменяем сформированное сообщение InvoiceFault на soap-env:Fault.



В качестве значения поля faultcode установим soap-env:Client, свидетельствующее о том, что ошибка произошла по вине клиента, запросившего несуществующие данные. В качестве значения поля faultstring будем передавать краткое описание ошибки. В качестве же значения поля detail будем подставлять сформированное ранее сообщение InvoiceFault.



Аналогичным образом настраиваем ветку Else, отвечающую за обработку прочих кодов ошибок, возвращаемых хранимой процедурой.

Согласно спецификации SOAP при возврате ошибок по протоколу HTTP необходимо, чтобы статус HTTP-ответа был равен 500. Для возврата из шины ответа со статусом 500 необходимо воспользоваться действием Reply, указав в его настройках With Failure.



В итоге блок Routing, отвечающий за реализацию одной операции сервиса будет выглядеть следующим образом:



Теперь если при тестировании операции GetInvoice передать некорректное значение параметра ExternalId, то будет возвращен HTTP-ответ с кодом 500 - Internal Server Error.



Ответ при этом будет включать в себя элемент soap-env:Fault, содержащий в свою очередь элемент InvoiceFault для передачи детального описания ошибки.



Обработка вызова недопустимой операции

Блок Operational Branch в Oracle Service Bus содержит ветвь Default, срабатывающую в случае, если осуществляется вызов недопустимой операции. Например, операции, описанной в WSDL, но по каким-то причинам не реализованной на шине.

В рассматриваемом примере реализуем уведомление пользователя о недопустимости вызываемой им операции. Для этого в ветвь Default поместим блок Pipeline Pair, в поток обработки которого - блок Stage. Назовем данный Stage - ErrorStage.

Генерация ошибки как и в случае обработки кодов, возвращаемых хранимыми процедурами, будет состоять из двух блоков замены - Replace и операции Reply с настройкой With Failure.



Наиболее интересен здесь первый блок Replace, задачей которого является формирование сообщения InvoiceFault. Данный блок должен заменять любой элемент, входящий в переменную body. Для адресации любого элемента используется XPath-выражение ./*[1].



Само формирование сообщения InvoiceFault выглядит следующим образом: в качестве значения параметра errorcode передаем INV-001, что соответствует недопустимой операции, в качестве значения параметра errormessage - некое сообщение об ошибке, а вот в качестве значения параметра externalid не передаем ничего (пустые скобочки), таким образом в сообщении об ошибке элемент ExternalId сгенерирован не будет.



При тестировании с помощью SOAP UI достаточно исправить входящее сообщение, например сформировав несуществующую операцию GetInvoice2. В ответ на данное сообщение будет получено уведомление об ошибке: No Such Operation Exception.



Обработка системных ошибок

Последней точкой возникновения ошибок является сама Oracle Service Bus. При работе сервиса возможны разные ситуации, связанные с вопросами функционирования системы. Например, может быть недоступна база данных вызываемого приложения. Так же могут быть проблемы при трансформации поступающих от клиента сообщений. Например, мы определили в контракте сервиса ExternalId как строку. Сделали мы это, потому что разрабатывали канонический сервис, т.е. сервис предназначенный для интеграции со всеми системами управления счетами. Поэтому необходимо обеспечить максимальную гибкость. Однако в конкретной реализации данного сервиса, с помощью вызова хранимых процедур, параметр ExternalId должен быть числом. При конвертации произвольной строки в число может произойти ошибка, которую так же нужно обработать.

Для обработки системных ошибок в Oracle Service Bus используется механизм обработчиков ошибок - Error Handler'ов. Такой обработчик можно повесить на сервис целиком, блок Route, каждую ветвь из Pipeline Pair, а так же на отдельный блок Stage. В разрабатываемом примере создадим глобальный обработчик ошибок - повесим его на сервис целиком.



Добавим в обработчик блок Stage, в который в свою очередь будем добавлять элементы логики обработки ошибок. Прежде всего необходимо добавить логирование сообщения об ошибке с его причиной. Для этого поместим в блок Stage компонент Log. У данного компонента необходимо настроить следующие параметры: Expression - выражение, результат которого будет залогирован, Annotation - аннотация сообщения об ошибке, например, в каком сервисе она произошла, и Severity - уровень логирования, для сообщения об ошибке можно выставить уровень Error.



В качестве логируемого выражения будем записывать причину ошибки, которую OSB передает в качестве значения поля reason служебной переменной fault. Переменная fault доступна только в блоке обработки ошибок.

После логирования необходимо сделать ветвление. Ошибки валидации и трансформации необходимо обрабатывать одним образом: передавая свой код ошибки и soap-env:Client в качестве значения поля faultcode, а прочие ошибки - другим, передавая в частности soap-env:Server в качестве значения поля faultcode. Однако и в том, и в другом случае завершаться обработчик ошибок должен действием Reply с настройкой With Failure.



Выделить ошибки валидации и трансформации среди прочих типов ошибок можно по коду. Код ошибки передается в параметре errorCode переменной fault. Нас интересуют коды BEA-382505 и BEA-382513.



При формировании сообщения InvoiceFault необходимо передать код INV-003, свидетельствующий о некорректном запросе, в качестве значения поля errorcode, а в качестве значения поля errormessage можно передать выражение, вычисляющее причину сбоя трансформации:


fn:concat($fault/ctx:reason/text(), ': ''', $fault/ctx:details/ctx1:ValidationFailureDetail/ctx1:message[1], ''' in the field ''', fn:local-name($fault/ctx:details/ctx1:ValidationFailureDetail/ctx1:xmlLocation[1]/*), '''')




При этом необходимо зарегистрировать дополнительное пространство имен с префиксом ctx1 и URI: http://www.bea.com/wli/sb/stages/transform/config.



При прочих ошибках необходимо сформировать сообщение с errorcode равным INV-004 и errormessage - $fault/ctx:reason/text().



Целиком блок обработки ошибок будет выглядеть следующим образом:



Для проверки попробуем передать сообщение со значением параметра ExternalId равным alpha. Получим ошибку преобразования строки alpha к числу.




Добавление валидации входящих сообщений


Последним штрихом при разработке веб-сервиса может стать добавление валидации входящих запросов целью отказать в обслуживании некорректных запросов как можно раньше, чтобы их прохождение по шине не снижало производительность системы. В статье Валидация XML-сообщений я писал о том как важна валидация сообщений, поступающих от систем, которые мы не контролируем, а так же как осуществлять валидацию в Oracle Service Bus. Сейчас мы рассмотрим это на практике.

Для валидации сообщений в Oracle Service Bus используется действие Validate. Данное действие можно поместить в любую точку потока обработки сообщений, что позволяет валидировать сообщение на всех ключевых точках его обработки. У действия Validate есть следующие параметры: XPath - адресует валидируемый участок, таким образом можно валидировать не все сообщение, а лишь некоторые его элементы, In Variable - задает переменную, сообщение в которой будет валидироваться, Against Resource - позволяет выбрать тип или элемент схемы, относительно которого будет осуществляться валидация и, наконец, Save Variable и Raise Error отвечают за действие: сохранить результат в переменную или сгенерировать ошибку при обработке невалидного сообщения.



Справа от поля Against Resource расположена кнопка Browse, при нажатии на которую можно выбрать схему и описанный в ней элемент, относительного которого будет осуществляться валидация.



Для тестирования валидации отправим с помощью SOAP UI запрос, в котором вместо параметра ExternalId присутствует параметр External. В ответе мы увидим сообщение об ошибке валидации, в котором будет сказано какой элемент XML ожидался и какой был вместо него получен.



Заключение


Концепция сервисно-ориентированной архитектуры (SOA) и основное средство реализации данной концепции - сервисные шины предприятия (ESB) развиваются уже порядка десяти лет. За это время крупными производителями и небольшими группами разработчиков открытого исходного кода выпущено несколько продуктов, существенно облегчающих интеграцию приложений. Одним из таких продуктов является Oracle Service Bus, пример практического применения которого рассмотрен в данной заметке. Конкретно выставление счетов какой-то внешней системой по отношению к биллингу может показаться надуманным, но в реальности похожее взаимодействие приходится реализовывать очень часто.

Выставив функциональности системы в качестве веб-сервиса и реализовав в нем маршрутизацию между системами, что впрочем является темой другого, довольно интересного разговора, мы можем существенно облегчить разработку систем-клиентов. Если сегодня получать счета требуется внешнему биллингу, завтра - CRM, а послезавтра - личному кабинету клиента, то после внедрения OSB и реализации описанного сервиса, все эти системы смогут работать только с ним, абстрагируясь тем самым от того, какое API применяется в конкретном биллинге и какой именно экземпляр биллинга используется.

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

Оставайтесь на связи!

Понравилось сообщение - подпишитесь на блог и Twitter

9 комментариев:

ilyas комментирует...

Павел, у вас так много статей посвященных созданию веб-сервисов, но почему вы везде используете продукты Oracl'a?
Мне кажется в мире Java уже есть очень мощный, гибкий и к тому же бесплатный инструмент для построения веб-сервисов - это Apache CXF.

Pavel Samolisov комментирует...

Я использовал как-то Apache CXF, хороший инструмент. Хотя, могу ошибаться, но года 4 назад у него были проблемы с многопоточностью. Не знаю, как сейчас.

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

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

ilyas комментирует...

Частично соглашусь с последним абзацем, видимо OSB для того и создавался, чтобы поддержку шины осуществляли администраторы, а не программисты, что действительно критично в крупных компаниях.
С другой стороны при разработке я бы предпочел Spring+CXF - плюсы в невероятной гибкости, например у меня стояла задача переноса веб-сервисов с .Net на Джава, так
чтобы wsdl и все исходящие http-сообщения оставались до байта идентичными старой версии. CXF позволил все это сделать на столько просто и элегантно, что я был приятно удивлен.
Кроме того открытые исходные коды тоже большой плюс.
P.S. С проблемами с многопоточности в CXF не сталкивался. Даже наоборот у этой библиотеки есть возможность создавать клиента, который бы работал на NIO, т.е. без создания потока на каждый запрос.

Pavel Samolisov комментирует...

>> чтобы wsdl и все исходящие http-сообщения
>> оставались до байта идентичными старой версии.

Вот за это я и люблю решения класса ESB, что здесь реализуется подход Contract-First. Т.е. сначала проектируем сообщения, а на чем там сделана реализация сервиса нам все равно. В мире Java активно проповедует такой подход только Spring WS насколько я знаю. Не пробовали?

ilyas комментирует...

Spring-ws пробовал и честно говоря мне он показался слишком сложным и тяжело настраиваемым, не в смысле навязывания подхода Contract-First, а в плане его общей концепции, сейчас точно вспомнить не могу, но при работе с ним все время было ощущение, что как-то все неудобно.

Igorok комментирует...

Здравствуйте, Павел. Пытаюсь разобраться с OSB с помощью вашей статьи, когда в Eclipse пытаюсь сделать "необходимо в дереве проектов выбрать файл с расширением .jca, в контекстном меню которого будет доступен пункт Oracle Service Bus -> Generate Service." То появляется сообщение "An error occured while retrieving information from the JCA file". В чём может быть дело?

Pavel Samolisov комментирует...

Скорее всего в запросе используются знаки "<" или ">", я обычно временно убираю их из запроса, затем генерирую бизнес-сервис, а затем добавляю в запрос в JCA-файле и бизнес-сервисе.

Anton Kriventsev комментирует...

Павел, добрый день! Спасибо за блог.
Не понятно часть действиq в eclipse по созданию отдельного OSB проекта "MDS" и планирование каталогов EBM, EDS, EBO и как проект в Jdeveloper добавляет файлы в проект открытый в eclipse?
Адаптеры в JDEV я создал, остановился на фразе "В Eclipse созданные артефакты добавятся в дерево проектов." :-/

Workspace задан для eclipse и Jdeveloper одинаковый.
Использую SOA Quickstart 12.1.3 и Java Developer 12c, Eclipse Luna SR2 4.4.2
RDBMS Oracle 11xe

Спасибо за ответ!

Pavel Samolisov комментирует...

Идея в том, чтобы на диске правильно пересекались проекты Eclipse и JDeveloper. Впрочем, начиная с 12c, как я понимаю, проблема уже не актуальна - вся разработка ведется в JDeveloper. Я в свое время делал так - проект Eclipse для OSB, например BlaBlaAdapter, в нем каталог composite, который является корнем проекта JDeveloper. Т.к. Eclipse не умеет подтягивать изменения в каталогах, если эти изменения не были сделаны им, то после завершения работы в JDeveloper необходимо выполнить Refresh каталога в Eclipse.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!