понедельник, 18 февраля 2013 г.

Пример разработки веб-сервиса по контракту с использованием Oracle SOA Suite и Spring Framework

В прошлых заметках мы рассмотрели теоретические вопросы проектирования контракта сервиса и валидации сообщений с помощью XShema и Schematron. В данной статье продемонстрируем использование этих знаний на практике: создадим веб-сервис, основываясь на его контракте. В качестве технологической платформы будем использовать Oracle SOA Suite и Spring Framework.

Разработка контракта сервиса


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



Контракт сервиса опишем в виде WSDL-документа. Для реализации сервиса с помощью Oracle SOA Suite достаточно описать только т.н. абстрактный WSDL, включающий в себя сообщения и тип порта. Текст WSDL-документа может быть следующим:

<definitions targetNamespace="urn:CalculatorService"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             xmlns:tns="urn:CalculatorService"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
             xmlns:types="urn:CalculatorService/types"
             xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/">
  <types>
    <xsd:schema targetNamespace="urn:CalculatorService/types"
                xmlns:tns="urn:CalculatorService/types"
                elementFormDefault="qualified">              
      <xsd:complexType name="tBinaryOperation">
        <xsd:sequence>
          <xsd:element name="a" type="xsd:decimal" minOccurs="1" maxOccurs="1"/>
          <xsd:element name="b" type="xsd:decimal" minOccurs="1" maxOccurs="1"/>
        </xsd:sequence>
      </xsd:complexType>
      <xsd:complexType name="tResult">
        <xsd:sequence>
          <xsd:element name="result" type="xsd:decimal" minOccurs="1" maxOccurs="1"/>          
        </xsd:sequence>
      </xsd:complexType>
      <xsd:element name="Plus" type="tns:tBinaryOperation"/>
      <xsd:element name="PlusResponse" type="tns:tResult"/>
      <xsd:element name="Minus" type="tns:tBinaryOperation"/>
      <xsd:element name="MinusResponse" type="tns:tResult"/>
      <xsd:element name="Mul" type="tns:tBinaryOperation"/>
      <xsd:element name="MulResponse" type="tns:tResult"/>
      <xsd:element name="Div" type="tns:tBinaryOperation"/>
      <xsd:element name="DivResponse" type="tns:tResult"/>
    </xsd:schema>
  </types>
  <message name="PlusMessage">
    <part name="parameters" element="types:Plus"/>
  </message>
  <message name="PlusResponseMessage">
    <part name="parameters" element="types:PlusResponse"/>
  </message>
  <message name="MinusMessage">
    <part name="parameters" element="types:Minus"/>
  </message>
  <message name="MinusResponseMessage">
    <part name="parameters" element="types:MinusResponse"/>
  </message>
  <message name="MulMessage">
    <part name="parameters" element="types:Mul"/>
  </message>
  <message name="MulResponseMessage">
    <part name="parameters" element="types:MulResponse"/>
  </message>
  <message name="DivMessage">
    <part name="parameters" element="types:Div"/>
  </message>
  <message name="DivResponseMessage">
    <part name="parameters" element="types:DivResponse"/>
  </message>
  <portType name="CalculatorServicePortType">
    <operation name="Plus">
      <input message="tns:PlusMessage"/>
      <output message="tns:PlusResponseMessage"/>
    </operation>
    <operation name="Minus">
      <input message="tns:MinusMessage"/>
      <output message="tns:MinusResponseMessage"/>
    </operation>
    <operation name="Mul">
      <input message="tns:MulMessage"/>
      <output message="tns:MulResponseMessage"/>
    </operation>
    <operation name="Div">
      <input message="tns:DivMessage"/>
      <output message="tns:DivResponseMessage"/>
    </operation>
  </portType>
</definitions>

В CalculatorServicePortType мы описали четыре операции: Plus, Minus, Mul и Div, каждая операция принимает на вход соответствующее сообщение, содержащее в себе элемент типа tBinaryOperation, а в качестве результата возвращает соответствующее сообщение, содержащее в себе элемент типа tResult.



Реализация сервиса с помощью медиатора


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

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



В мастере настройки медиатора нам необходимо ввести его имя и задать шаблон, по которому будет создан медиатор. Т.к. мы создаем сервис по уже описанному контракту, то выберем шаблон Interface Definition from WSDL, а также укажем путь к созданному WSDL-файлу.

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



Реализация бизнес-логики сервиса


Для реализации бизнес-логики сервиса будем использовать сервисный компонент Spring Context. Как мы уже рассматривали, WebLogic SCA-контейнер позволяет использовать Spring Framework и выставлять компоненты как сервисы и ссылки. Начиная с версии 11.1.1.3 SCA-контейнер Oracle SOA Suite так же поддерживает интеграцию со Spring Framework.

Для того, чтобы добавить контекст Spring в композит, необходимо перетащить сервисный компонент Spring Context с палитры компонентов в поле композита. После этого откроется окно настройки Spring Context'а, в котором необходимо задать название компонента и наименование файла контекста.



Для хранения исходных кодов классов и интерфейсов, используемых в Spring Context'е необходимо создать каталог src в корне каталога исходных кодов композита. После этого необходимо создать интерфейс Calculator, описывающий внутреннюю реализацию сервиса калькулятора. Для создания интерфейса нужно в меню New... выбрать General -> Java, Java Interface.



Затем в окне создания интерфейса указать его название и пакет, после чего нажать кнопку OK.



После этого необходимо выбрать каталог для хранения исходников на Java, лучше всего выбрать "Путь к вашему композиту"/src.



Код интерфейса Calculator следующий:

package name.samolisov.service;

public interface Calculator {
  
  public double plus(double a, double b);
  
  public double minus(double a, double b);
  
  public double mul(double a, double b);
  
  public double div(double a, double b);
}

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

package name.samolisov.service;

public class CalculatorImpl implements Calculator {
  public CalculatorImpl() {
    super();
  }

  public double plus(double a, double b) {
    return a + b;
  }

  public double minus(double a, double b) {
    return a - b;
  }

  public double mul(double a, double b) {
    return a * b;
  }

  public double div(double a, double b) {
    return a / b;
  }
}

Теперь рассмотрим описание контекста Spring'а. В данном контексте мы объявляем компонент Calculator, реализуемый классом name.samolisov.service.CalculatorImpl. Данный компонент выставляется в виде сервиса CalculatorService, имеющего интерфейс name.samolisov.service.Calculator.

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sca="http://xmlns.oracle.com/weblogic/weblogic-sca"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd 
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd 
                           http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd 
                           http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd 
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
                           http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool-2.5.xsd 
                           http://xmlns.oracle.com/weblogic/weblogic-sca META-INF/weblogic-sca.xsd">  
  
  <sca:service name="CalculatorService" target="Calculator" 
               type="name.samolisov.service.Calculator"/>
  
  <bean id="Calculator" class="name.samolisov.service.CalculatorImpl"/>
  
</beans>



Перед тем, как связать Spring Context BusinessLogic и медиатор необходимо скомпилировать проект. В JDeveloper для этого можно воспользоваться командой Make (Ctrl + F9) или Rebuild (Alt+F9). После связывания компонентов, JDeveloper выдаст предупреждение о том, что будет создан файл Calculator.wsdl, основанный на классе name.samolisov.service.Calculator.



После нажатия кнопки OK на предупреждении появится окно связывания операций. В нем мы должны связать операцию Plus из разработанного нами контракта с операцией plus из созданного файла Calculator.wsdl.



Настройка медиатора


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

Прежде всего включим валидацию сообщений по схеме. Данная валидация включается для каждой операции независимо. Для этого необходимо отметить галочку Validate Syntax (XSD). Включение валидации по схеме предотвратит нас от передачи неверных данных сервису, например строк вместо чисел.



Далее необходимо настроить трансформацию сообщений. Для этого необходимо выбрать действие Select an existing mapper file or create a new one на строчке Transform Using.



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



Откроется окно визуального XSLT-редактора. В нем необходимо настроить трансформацию. В нашем случае трансформация тривиальна: присваиваем значение параметра a параметру arg0, а значение параметра b - параметру arg1.




Настройка трансформации для ответа выполняется аналогично.



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



В появившемся окне будут перечислены варианты типов объектов для перенаправки сообщения. Нас интересует вариант Service - перенаправить сообщение на вход сервиса.



Появится окно Target Service, в котором необходимо выбрать сервис CalculatorService из компонента BusinessLogic и соответствующую операцию.



Появится предупреждение о том, что выбранный сервис имеет Java-интерфейс и требует генерации WSDL-интерфейса.



После нажатия кнопки OK появится окно выбора вариантов генерации интерфейса, можно оставить существующий WSDL-файл без изменений, можно заменить его, а можно сгенерировать новый файл, добавив суффикс к имени существующего.



Настройка валидации по схеме и трансформации для оставшихся операций сервиса выполняется аналогично операции Plus.

Для операции Div нам нужно выполнить дополнительную валидацию. Как известно делить на нуль нельзя, соответственно необходимо перед вызовом сервиса убедиться в том, что делитель не равен нулю. Сделать это можно с помощью Schematron. Напомню, что мы его подробно рассматривали в статье Валидация XML-сообщений.

Файл с кодом Schematron-фалидации представляет собой обычный XML-документ с расширением sch. Создать данный файл в JDeveloper можно с помощью мастера New..., выбрав в нем опцию General -> XML, XML Document.



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



Текст файла div.sch следующий:

<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.ascc.net/xml/schematron">
    <ns uri="urn:CalculatorService/types" prefix="types"/>
    
    <pattern name="Check Divisor">
        <rule context="/types:Div">
            <assert test="types:b != '0'">
                Делитель не может быть равен нулю
            </assert>
        </rule>
    </pattern>
</schema>

В данном примере мы проверяем, что значение элемента /types:Div/types:b не равно нулю, в противном случае выдаем предупреждение.

Подключить файл к медиатору можно настроив действие Validate Semantic у соответствующей операции.



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



После нажатия кнопки OK появится окно со списком подключенных валидаций. Можно добавить дополнительные файлы валидации, с помощью кнопки "зеленый плюс".



Тестирование композита


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





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





Теперь протестируем как сервис обрабатывает деление на нуль. Как мы видим, сервис возвращает ошибку ORAMED-01301: [Payload Custom Validation] с сообщением, закодированным нами в Schematron-файле: "Делитель не может быть равен нулю".





Заключение


Мы рассмотрели практический пример реализации сервиса по контракту с помощью медиатора и Spring Framework. При этом мы убедились, что компоненты Oracle SOA Suite реализуют довольно мощные средства для валидации сообщений, в том числе и с точки зрения семантики, а так же обладают хорошей интеграцией с Java-платформой, позволяя реализовывать сложную бизнес-логику не только в сторонних Java-сервисах, таких как EJB-компоненты, но и непосредственно внутри самого композита. Если вы заинтересовались интеграцией Oracle SOA Suite и Spring Framework, то рекомендую данную презентацию.

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

3 комментария:

Иван Сергеев комментирует...

Павел, добрый день!

Спасибо за статью, очень интересно.
В своем композите использую Spring component, в котором живет JAVA класс. В нем есть глобальная переменная static, которая возвращает значение из класса. Переменная в процессе жизни меняет свое значение. При отладке класса локально в JDeveloper класс возвращает нужное значение и отрабатывает правильно.
После деплоя композита на сервер, Spring возвращает только первоначальное значение переменной. Нет динамики в работе с возвращаемой переменной.
Подскажите пожалуйста в чем особенность со Spring и какие свойства надо указать в Spring.xml.

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

Иван, если можно вышлите класс и композит на samolisov@gmail.com, попробую вам помочь.

Вообще статическая переменная - это переменная класса, ее время жизни совпадает со временем жизни класса. Если отображается одно и то же значение статической переменной, то это может свидетельствовать либо о том, что класс каждый раз загружается заново, что странно. У вас один экземпляр сервера приложений, кластера нет?

Иван Сергеев комментирует...

Нет, кластера нет.

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

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