Nesse artigo vamos abordar mais uma especificação WS-* praticamente desconhecida: WS-ReliableMessaging ou WS-RM. Essa especificação pretende dar suporte à entrega confiável de mensagens entre clientes e servidores. Vamos aproveitar para examinar um exemplo utilizando o framework Apache CXF.

WS-RM em ação

Figura 1: WS-RM em ação

Quando trabalhamos com integração através de WebServices, certamente nos deparamos com projetos que tem requisitos que vão além do simples fato de se expor uma funcionalidade através de uma operação. E quando esse momento chega, é o nosso conhecimento sobre algumas especificações WS-* que pode fazer a diferença.

O WS-ReliableMessaging é uma dessas boas coisas que ficam escondidas. Esse padrão foi criado pelo OASIS (http://docs.oasis-open.org/ws-rx/wsrm/200608/wsrm-1.1-spec-cd-04.html) e permite que algumas perguntas sejam respondidas tais como: “uma mensagem chegou ao seu destino?”, “se houve falha, devo retransmitir a mensagem?”, “é possível garantir ordem no envio ou recebimento de uma mensagem?”. O WS-RM garante exatamente isso: que provedores de serviço serão notificados sobre o sucesso ou falha no recebimento de mensagens e mensagens enviadas em uma sequência serão recebidas da forma desejada.

Para isso, essa especificação cria alguns conceitos fundamentais:

  • RM Source e RM destination: são os nós (nodes) responsáveis pela transmissão e recebimento das mensagens e por garantir o correto uso do padrão WS-RM.
  • Sequências (sequences): uma sequência estabelece uma relação entre mensagens trocadas pelo RM Source e RM Destination. Cada mensagem recebe um número de controle.
  • ACK (Acknwoledgements): indicação enviada pelo RM Destination para o RM Source informando que ele recebeu com sucesso uma mensagem.
  • Regras de garantia de entrega: definem a política de entrega de confirmação. As políticas suportadas são “at most once” significando que no máximo uma mensagem será enviada da origem para o destino, “at least once” que garante que o envio de pelo menos uma mensagem ou erro se isso não acontecer a por último “exactly one” que garante o envio de apenas uma única mensagem ou erro se houver duplicação.

O fluxo básico é o seguinte: quando um cliente (RM Source) faz uma chamada a um serviço (RM Destination) e ambos estão configurados para utilizar o WS-RM, antes de mais nada um pedido de criação de sequência é enviado. O RM Destination responde com um “CreateSequenceResponse” que contém um identificador e uma validade.

Cada requisição para o RM Destination tem um número relativo à sequência (1,2, 3 etc). Cada mensagem também leva em seu cabeçalho o identificador único da sequência. Para cada mensagem enviada pelo RM Source, uma mensagem de ACK é retornada indicando que o RM Destination recebeu com sucesso a requisição.

Para casos de falha tanto do RM Source quanto do RM Destination, o framework compatível com WS-RM deve ser capaz de ligar com os imprevistos, repetindo mensagens, fazendo retentativas ou em casos mais graves, gerando erros específicos de não respeito às políticas de entrega.

Mais uma vez utilizaremos o Apache CXF para fazer nosso exemplo prático de WS-RM. O Apache CXF torna o trabalho muito simples e mesmo os serviços não precisam sofrer qualquer tipo de alteração. Todo suporte ao WS-RM pode ser feito através de configuração. Vamos ver na listagem 1 uma configuração de Spring com WS-RM ativado no Bus.

Listagem 1: Configuração do Spring


    <jaxws:endpoint
        id="serverOp"
        address="http://localhost:9292/hello"
        xmlns:s="http://helloworldservice.devmedia.com.br"
        implementor="br.com.devmedia.helloworldservice.SimpleNormalHelloService"
        serviceName="s:HelloService"
        endpointName="s:HelloPort"
        bus="serverBus"
        >
        <jaxws:properties>
            <entry key="schema-validation-enabled" value="true" />
         </jaxws:properties>
    </jaxws:endpoint>

     <cxf:bus name="serverBus">
        <cxf:features>
            <wsa:addressing/>
            <wsrm-mgr:reliableMessaging>
                <wsrm-policy:RMAssertion>
                    <wsrm-policy:BaseRetransmissionInterval Milliseconds="4000"/>           
                    <wsrm-policy:AcknowledgementInterval Milliseconds="2000"/>
                </wsrm-policy:RMAssertion>
                <wsrm-mgr:destinationPolicy>
                    <wsrm-mgr:acksPolicy intraMessageThreshold="0" />
                </wsrm-mgr:destinationPolicy>
            </wsrm-mgr:reliableMessaging>
        </cxf:features>
    </cxf:bus>

Um ponto importante: o WS-RM depende que o WS-Addressing esteja funcionando. Em seguida reparamos o namespace wsrm-mgr (http://cxf.apache.org/ws/rm/manager). É ele que adiciona a feature ReliableMessaging ao bus. Como parte de suas configurações vemos aqui 3 parâmetros:

  • BaseRetransmissionInterval: intervalo em millisegundos que determina o tempo mínimo de espera para que o RM Source faça o reenvio de uma mensagem que ele não tenha recebido o Ack vindo do RM Destination.
  • AcknowledgementInterval: intervalo em millisegundos utilizado pelo RM Destination para envio de acks assíncronos. Existem 2 maneiras de um ACK ser retornado: assincronamente através de um outro endpoint ou piggybacking isto é, esperando uma requisição do cliente para embutir na resposta o ACK.

Notamos também o elemento destinationPolicy. Ele é responsável por definir as configurações do RM Destination tais como tempo de expiração da sequência, parâmetros específicos para transmissão de ACKs e assim por diante. Mais detalhes em http://cxf.apache.org/docs/wsrmconfiguration.html.

Para que o cliente sejá compatível com WS-RM, precisamos apenas atribuir a ele uma instância de Bus como configurado na listagem 1. Vamos ver um trecho do cliente na listagem 2.

Listagem 2: Cliente com suporte ao WS-RM


        ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
        factory.setServiceClass(HelloService.class);
        factory.setAddress("http://localhost:9292/hello");
        factory.getFeatures().add(new LoggingFeature());
        factory.setBus(bus);
        
        HelloService helloService = factory.create(HelloService.class);
        Client client = ClientProxy.getClient(helloService);
        HTTPConduit http = (HTTPConduit) client.getConduit();
        HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
        httpClientPolicy.setDecoupledEndpoint("http://localhost:9292/decoupled_endpoint");
        http.setClient(httpClientPolicy);

Na primeira linha nos chama atenção a classe ClientProxyFactoryBean. Essa classe é utilizada pelo Apache CXF para a construção de consumidores de WebServices. Para ele também passamos uma instância de bus já configurado com WS-RM.

Um pouco mais abaixo na listagem 2, reparem em ClientProxy.getClient(). Ele retorna uma instância de Client (org.apache.cxf.endpoint.Client) e permite acesso às API's mais baixo nível do framework. O importante no nosso exemplo é configurar um “decoupled endpoint”. A interação utilizando o decoupled endpoint é um pouco diferente do que estamos acostumados.

Quando chamados um endpoint configurado com um DecoupledEndpoint associado, a requisição inicial feita pelo cliente não recebe a resposta imediatamente. O cliente recebe um código http 202 Accepted e a resposta com o resultado da chamada da operação será mandada pelo DecoupledEndpoint para o mesmo cliente, mas em uma outra conexão http. Isso é útil quando as operações são demoradas ou quando estamos trabalhando justamente com WS-RM. Para bom entendedor, fica claro que o DecoupledEndpoint nada mais é do que o ReplyTo do WS-Addressing. O HTTPClientPolicy também pode ser usado para configurar tempo de timeout de conexão, tempo máximo de espera por resposta de um serviço e muito mais!

Agora que já conhecemos servidor e cliente, vamos dar uma olhada em uma requisição SOAP contendo informações do WS-RM na Listagem 3.

Listagem 3: Requisição SOAP para criação de uma sequência.


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
    <soap:Header>
        <Action xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
		http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence
        </Action>
        <MessageID xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
		urn:uuid:534b8a1e-b85c-4a6d-9138-79454e3c1e0d
        </MessageID>
        <To xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
		http://localhost:9292/hello
        </To>
        <ReplyTo xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
            <Address>
		http://localhost:9292/decoupled_endpoint
            </Address>
        </ReplyTo>
    </soap:Header>
    <soap:Body>
        <CreateSequence
            xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/08/addressing"
            xmlns="http://schemas.xmlsoap.org/ws/2005/02/rm" >
            <AcksTo>
                <ns2:Address>
		http://localhost:9292/decoupled_endpoint
                </ns2:Address>
            </AcksTo>
            <Expires>
		PT0S
            </Expires>
            <Offer>
                <Identifier>
		urn:uuid:fcda83a9-9002-4b47-a84c-8622b75b7e42
                </Identifier>
                <Expires>
		PT0S
                </Expires>
            </Offer>
        </CreateSequence>
    </soap:Body>
</soap:Envelope>

Como disse antes, essa requisição cria uma sequência com um identificador (Identifier) e um tempo de expiração. Reparem que o elemento AcksTo contém a referência para o nosso “DecoupledEndpoint”. Agora veremos na listagem 4 um exemplo de chamada à uma operação chamada “digaAdeus”.

Listagem 4: Requisição SOAP para operação digaAdeus


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
    <soap:Header>
        <Action xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
			http://helloworldservice.devmedia.com.br/HelloServicePortType/digaAdeus
        </Action>
        <MessageID xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
			urn:uuid:7f4b814d-4553-43f9-8e42-695c79281fc9
        </MessageID>
        <To xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
		http://localhost:9292/hello
        </To>
        <ReplyTo xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
            <Address>
		http://localhost:9292/decoupled_endpoint
            </Address>
        </ReplyTo>
        <wsrm:Sequence
            xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/08/addressing"
            xmlns:wsrm="http://schemas.xmlsoap.org/ws/2005/02/rm"
            soap:mustUnderstand="1" >
            <wsrm:Identifier>
		urn:uuid:48efd7bd-782f-467e-be45-d6d299839d48
            </wsrm:Identifier>
            <wsrm:MessageNumber>
				1
            </wsrm:MessageNumber>
        </wsrm:Sequence>
    </soap:Header>
    <soap:Body>
        <ns1:digaAdeus xmlns:ns1="http://helloworldservice.devmedia.com.br/" >
            <arg0>
                <name>
		John Doe
                </name>
                <sleep>
		false
                </sleep>
            </arg0>
        </ns1:digaAdeus>
    </soap:Body>
</soap:Envelope>

No cabeçalho vemos o elemento Identifier que possui o mesmo ID da sequência retornado pelo CreateSequenceResponse que é a resposta ao CreateSequence da Listagem 3.O MessageNumber indica que essa mensagem é a primeira da sequência. É importante saber que sequências são controladas pelo framework assim como as mensagens que estão associadas a elas. Na listagem 5, o ACK enviado ao DecoupledEndpoint.

Listagem 5: Requisição ACK


ID: 5
Address: http://localhost:9292/decoupled_endpoint
Response-Code: 200
Encoding: UTF-8
Http-Method: POST
Content-Type: text/xml; charset=UTF-8
Headers: {Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[798], content-type=[text/xml; charset=UTF-8], Host=[localhost:9292], Pragma=[no-cache], SOAPAction=["http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement"], User-Agent=[Apache CXF 2.7.0]}
Payload: 
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" >
    <soap:Header>
        <Action xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
			http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement
        </Action>
        <MessageID xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
			urn:uuid:abbb5b00-ab5e-48f2-a6ec-13bd99f791f1
        </MessageID>
        <To xmlns="http://schemas.xmlsoap.org/ws/2004/08/addressing" >
			http://localhost:9292/decoupled_endpoint
        </To>
        <wsrm:SequenceAcknowledgement
            xmlns:ns2="http://schemas.xmlsoap.org/ws/2004/08/addressing"
            xmlns:wsrm="http://schemas.xmlsoap.org/ws/2005/02/rm" >
            <wsrm:Identifier>
				urn:uuid:bce14bb6-371d-4926-b9c0-45bc6186619a
            </wsrm:Identifier>
            <wsrm:AcknowledgementRange
                Lower="1"
                Upper="2" />
        </wsrm:SequenceAcknowledgement>
    </soap:Header>
    <soap:Body />
</soap:Envelope>

Como vocês podem reparar, a ação (Action) é um SequenceAcknowledgement. Um pouco abaixo vemos o elemento AcknowledgementRange para a mensagem 1 de um total de 2. O ACK ocorre entre o DecoupledEndpoint e o cliente.

Bom pessoal, é isso! No nosso artigo fizemos uma introdução ao que é o WS-RM e o que ele tem a oferecer. Recomendo que agora vocês baixem o projeto completo em https://github.com/leogsilva/CxfWsRM.git e façam variações nas configurações, parâmetros e no código fonte. Até a próxima.