Статьи

Шаблонирование веб-сервисов с Velocity с использованием Camel


При создании веб-сервисов многие люди возражают против использования JAXB для привязки XML к объектам. Это может быть вызвано соображениями производительности, аллергией на сгенерированный код или просто философским убеждением, что вам не следует смешивать сервисы, ориентированные на документы, с объектно-ориентированной моделью. Независимо от причин, по которым вы не используете JAXB, я постараюсь описать возможное альтернативное решение с использованием Camel.

Я создал эту лабораторию после просмотра отличных разговоров Адриана Тренамана об использовании CXF на верблюде, поэтому я могу и не буду брать кредит на все, что вы увидите. В этом выступлении он продемонстрировал небольшую хитрость в том, что вы можете использовать Velocity в качестве движка шаблонов для генерации ваших мыльных ответов, не выполняя какой-либо сортировки или анализа XML-документов.

Его полный сеанс можно найти
здесь , но обратите внимание, что вам нужна регистрация на fusesource.com, чтобы увидеть это. Это бесплатно, поэтому, если вам интересно, я призываю вас проверить это.

Определение нашего контракта

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

Наш сервис на данный момент имеет только одну операцию, которая называется PerformCreditCheck:
Ответ представляет собой простое сообщение, содержащее поле Score и поле Details:

Верблюжий контекст

Я решил реализовать маршрут с использованием Java DSL, поэтому после настройки конечной точки CXF в контексте верблюда я указываю сканирование пакета на пакет java, где находится мой маршрут. Полный camel-context.xml выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cxf="http://camel.apache.org/schema/cxf"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
       http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"/>
  
  <cxf:cxfEndpoint xmlns:c="http://billy.laborations/CreditCheckService/" id="creditCheckEndpoint"
                   address="http://localhost:9000/creditcheck"
                   wsdlURL="META-INF/wsdl/CreditCheckService.wsdl" 
                   serviceName="c:CreditCheckService"
                   endpointName="c:CreditCheckServiceSOAP"
                   serviceClass="billy.laborations.creditcheck.DummyService">
        <cxf:properties>
            <entry key="schema-validation-enabled" value="true" />
        </cxf:properties>
  </cxf:cxfEndpoint>

  <camelContext xmlns="http://camel.apache.org/schema/spring">
    <package>billy.laborations.creditcheck</package>
  </camelContext>

</beans>

Как видите, я предоставляю wsdl для сервиса, его адрес конечной точки, а также класс реализации.

Конечная точка JAX-WS

Глядя на этот класс, все становится интересным. Поскольку я не хочу иметь дело с JAXB, я выбрал тип сервиса JAX-WS Provider, который принимает произвольные сообщения. Любой тип сообщений, попадающих в эту службу, будет вызывать метод invoke (), оставляя анализ XML и т. Д. На усмотрение разработчика. Однако, если вы посмотрите на метод, то заметите, что он настроен так, чтобы всегда выдавать исключение, как он может работать? Что ж, похоже, что Camel взломал этот метод и взломал маршрут, но этот класс все еще должен существовать для инициализации среды выполнения CXF.

package billy.laborations.creditcheck;

import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;

@WebServiceProvider(portName="CreditCheckPort", serviceName="CreditCheckService", targetNamespace="http://billy.laborations/CreditCheckService/")
@ServiceMode(Mode.PAYLOAD)
public class DummyService implements Provider<StreamSource> {

  public StreamSource invoke(StreamSource request) {
    throw new UnsupportedOperationException("Not implemented yet.");
  }
}

Маршрутизация

Маршрут реализован так:

package billy.laborations.creditcheck;

import javax.xml.transform.stream.StreamSource;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.xml.Namespaces;
import org.apache.cxf.message.MessageContentsList;


public class CreditCheckRoute extends RouteBuilder {

    public void configure() {
      // lets define the namespaces we'll need in our filters
      Namespaces ns = new Namespaces("c", "http://billy.laborations/CreditCheckService/");

        from("cxf:bean:creditCheckEndpoint").routeId("CreditCheckRoute")
        .process(new Processor(){
      public void process(Exchange exchange) throws Exception {
        
        //Cxf stores requests in a MessageContentList. The actual message is the first entry
        MessageContentsList messageList = (MessageContentsList)exchange.getIn().getBody();
        exchange.getIn().setBody(messageList.get(0), String.class);    
      }
        })
        .wireTap("seda:audit")
        .setHeader("FirstName", xpath("/c:Customer/FirstName").resultType(String.class).namespaces(ns))
        .setHeader("LastName", xpath("/c:Customer/LastName").resultType(String.class).namespaces(ns))
        .setHeader("SocialNr", xpath("/c:Customer/SocialNr").resultType(Integer.class).namespaces(ns))
        .process(new CreditCheckCalculator())
        .to("velocity:META-INF/velocity/creditCheckResponse.vm")
        .convertBodyTo(StreamSource.class)
        ;
        
        
        from("seda:audit")
        .inOnly()
        .to("log:audit")
        .to("file:target/audit/?fileName=${date:now:yyyyMMdd}.txt&fileExist=Append");
    }
}

Как видите, маршрут использует XPath для установки некоторых заголовков на основе входящих значений. Это сделано для того, чтобы наш сервис (CreditCheckCalculator) имел к ним легкий доступ.

Бизнес-сервис

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

package billy.laborations.creditcheck;

import org.apache.camel.Exchange;

public class CreditCheckCalculator implements org.apache.camel.Processor{

  public void process(Exchange exchange) throws Exception {
    String firstName = (String)exchange.getIn().getHeader("FirstName");
    String lastName = (String)exchange.getIn().getHeader("LastName");
    int socialNr = (Integer)exchange.getIn().getHeader("SocialNr");
    
    int creditScore;
    String details;
    
    if(firstName.equalsIgnoreCase("Billy")){
      creditScore = 99;
      details = firstName + " is a really good customer!";
    }else{
      creditScore = 1;
      details = firstName + " should not be trusted!";
    }
    
    exchange.getIn().setHeader("CreditScore", creditScore);
    exchange.getIn().setHeader("Details", details);  
  }
}

Генерация ответа

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

<CreditScore xmlns="http://billy.laborations/CreditCheckService/">
  <Score>$headers.CreditScore</Score>
  <Details>$headers.Details</Details>
</CreditScore>

Хитрость в том, что мы шаблонируем наш ответ, не касаясь каких-либо XML API. Шаблон скорости просто вернет объект String, который мы затем можем преобразовать в StreamSource, прежде чем передать его обратно в конечную точку JAX-WS.

Последние мысли

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

* Производительность — Нет необходимости анализировать запросы или ответные сообщения

* Стек WS — Предоставление маршрута в качестве конечных точек CXF дает нам всю мощь обработчики мыла и инжекторы CXF, т.е. для повышения безопасности и т. д.

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

Если вы заинтересованы в коде, я обязательно загрузю его. В противном случае спасибо за это время!