Feign com SOAP: uma PoC
Tags: spring, soap, tutorial, java
Published: 2022-04-24 21:52:40

Faz tempo que se diz que devemos [programar para uma interface, e não para uma implementação](https://www.amazon.com.br/Padrões-Projetos-Soluções-Reutilizáveis-Orientados/dp/8573076100/). 

E, de uns anos pra cá, ao menos no mundo Java, isso está sendo levado ao extremo, por diversos frameworks. Neles, não precisamos mais implementar interfaces, mas simplesmente escrevê-las (seguindo algumas regras, claro). O framework faz todo o resto.

Recentemente, escrevi sobre a [evolução dos DAOs em Java, chegando ao Spring Data](https://dev.to/rodrigovp/do-jdbc-ao-spring-data-ou-e-possivel-reduzir-codigo-361f). E, neste artigo, vou falar um pouco sobre o [Spring Open Feign](ou simplesmente Feign).

Esse framework é bem conhecido quando o assunto é criar uma integração com serviços REST. Em resumo: cria-se uma interface, coloca-se umas anotações e pronto! Temos um client REST se conectando com outra aplicação sem a necessidade de instanciarmos um `RestTemplate` ou outros objetos, o que é uma boa notícia, pois fazemos o mesmo trabalho com menos código.

Mas, recentemente na [empresa onde trabalho atualmente](https://www.vr.com.br), surgiu uma dúvida: temos alguns sistemas legados, com interface SOAP, e pensamos: será que o Feign também suporta esse protocolo? 

Para aqueles que estiverem sem paciência: sim :D. O código está disponível [aqui](https://github.com/rodrigovp/teste-open-feign). E, como sempre, com testes automatizados!

Ela foi construída usando a versão 17 do Java, mas é possível rodá-la também em versões inferiores da JVM. E, se for a versão 8, algumas dependências não serão necessárias. Veja o pom.xml para mais informações ;)

## O serviço a ser acessado

Para escrever o _client_, é necessário termos o servidor antes, que suporte o protocolo SOAP. Pode-se construir um, mas é um tanto difícil encontrar material para desenvolver um serviço SOAP hoje em dia. Se quiser pesquisar a respeito, há um bom artigo do sempre excelente [Baeldung](https://www.baeldung.com/spring-boot-soap-web-service).

Nesta PoC, usei como _server_ um [serviço disponibilizado gratuitamente](https://www.dataaccess.com/webservicesserver/NumberConversion.wso) que faz conversões simples:

- de número simples para número em extenso
- de um valor em dólares para o mesmo valor em extenso

Esse serviço possui também uma interface web, para melhor entendimento. Mas vamos para a interface SOAP, que é a que nos interessa.

## A interface SOAP

A interface Java é bem simples:

```java
@FeignClient(
        name = "dataAccess",
        url = "${dataaccess.soap.url}",
        configuration = DataAccessSOAPConfiguration.class)
interface DataAccessSOAP {

    @PostMapping(produces = MediaType.TEXT_XML_VALUE, consumes = MediaType.TEXT_XML_VALUE)
    NumberToWordsResponse porExtenso(@RequestBody NumberToWords request);

    @PostMapping(produces = MediaType.TEXT_XML_VALUE, consumes = MediaType.TEXT_XML_VALUE)
    NumberToDollarsResponse dolaresPorExtenso(@RequestBody NumberToDollars request);
}
```

Para quem já trabalhou com o Feign, a anotação `@FeignClient` é a mesma que se usa quando a integração é usando o REST. E, pra quem já trabalhou com Spring MVC, a anotação `@PostMapping` não é tão incomum. 

É possível também que estamos trabalhando com XML (vide os parâmetros das anotações). Afinal, quando a integração é com SOAP, dificilmente não usamos XML :D

Há também algumas classes de requisição e resposta. Essas classes são criadas por meio do [WSDL](https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL) oferecido pelo próprio serviço:

```xml
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="http://www.dataaccess.com/webservicesserver/" name="NumberConversion" targetNamespace="http://www.dataaccess.com/webservicesserver/">
    <types>
        <xs:schema elementFormDefault="qualified" targetNamespace="http://www.dataaccess.com/webservicesserver/">
            <xs:element name="NumberToWords">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="ubiNum" type="xs:unsignedLong"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="NumberToWordsResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="NumberToWordsResult" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="NumberToDollars">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="dNum" type="xs:decimal"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="NumberToDollarsResponse">
                <xs:complexType>
                    <xs:sequence>
                        <xs:element name="NumberToDollarsResult" type="xs:string"/>
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:schema>
    </types>
    <message name="NumberToWordsSoapRequest">
        <part name="parameters" element="tns:NumberToWords"/>
    </message>
    <message name="NumberToWordsSoapResponse">
        <part name="parameters" element="tns:NumberToWordsResponse"/>
    </message>
    <message name="NumberToDollarsSoapRequest">
        <part name="parameters" element="tns:NumberToDollars"/>
    </message>
    <message name="NumberToDollarsSoapResponse">
        <part name="parameters" element="tns:NumberToDollarsResponse"/>
    </message>
    <portType name="NumberConversionSoapType">
        <operation name="NumberToWords">
            <documentation>Returns the word corresponding to the positive number passed as parameter. Limited to quadrillions.</documentation>
            <input message="tns:NumberToWordsSoapRequest"/>
            <output message="tns:NumberToWordsSoapResponse"/>
        </operation>
        <operation name="NumberToDollars">
            <documentation>Returns the non-zero dollar amount of the passed number.</documentation>
            <input message="tns:NumberToDollarsSoapRequest"/>
            <output message="tns:NumberToDollarsSoapResponse"/>
        </operation>
    </portType>
    <binding name="NumberConversionSoapBinding" type="tns:NumberConversionSoapType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="NumberToWords">
            <soap:operation soapAction="" style="document"/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
        <operation name="NumberToDollars">
            <soap:operation soapAction="" style="document"/>
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
    <binding name="NumberConversionSoapBinding12" type="tns:NumberConversionSoapType">
        <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="NumberToWords">
            <soap12:operation soapAction="" style="document"/>
            <input>
                <soap12:body use="literal"/>
            </input>
            <output>
                <soap12:body use="literal"/>
            </output>
        </operation>
        <operation name="NumberToDollars">
            <soap12:operation soapAction="" style="document"/>
            <input>
                <soap12:body use="literal"/>
            </input>
            <output>
                <soap12:body use="literal"/>
            </output>
        </operation>
    </binding>
    <service name="NumberConversion">
        <documentation>The Number Conversion Web Service, implemented with Visual DataFlex, provides functions that convert numbers into words or dollar amounts.</documentation>
        <port name="NumberConversionSoap" binding="tns:NumberConversionSoapBinding">
            <soap:address location="https://www.dataaccess.com/webservicesserver/NumberConversion.wso"/>
        </port>
        <port name="NumberConversionSoap12" binding="tns:NumberConversionSoapBinding12">
            <soap12:address location="https://www.dataaccess.com/webservicesserver/NumberConversion.wso"/>
        </port>
    </service>
</definitions>
``` 

Repare que alguns elementos do XML possuem nomes iguais aos nomes das classes de requisição e resposta. Como boa prática, essas classes são geradas por frameworks que lêem esse WSDL e nos retornam as classes correspondentes. Digo boa prática porque escrever essas classes na mão dá muito trabalho.

No passado, existiam diversos frameworks que faziam esse trabalho pra gente, como o [CXF](https://cxf.apache.org/), o [XMLBeans](https://xmlbeans.apache.org/) ou mesmo o [Axis](https://axis.apache.org/axis/). Na PoC, entretanto, usei o [maven-jaxb2-plugin](https://github.com/highsource/maven-jaxb2-plugin).

O código para a geração das classes de requisição e resposta está abaixo:

```xml
			<plugin>
				<groupId>org.jvnet.jaxb2.maven2</groupId>
				<artifactId>maven-jaxb2-plugin</artifactId>
				<version>0.14.0</version>
				<dependencies>
					<!-- Dependência necessária para gerar os stubs no Java 17 -->
					<dependency>
						<groupId>org.glassfish.jaxb</groupId>
						<artifactId>jaxb-runtime</artifactId>
						<version>2.3.3</version>
					</dependency>
				</dependencies>
				<executions>
					<execution>
						<id>dataaccess</id>
						<goals>
							<goal>generate</goal>
						</goals>
						<configuration>
							<schemaDirectory>${project.basedir}/src/main/resources/schema/dataaccess</schemaDirectory>
							<schemaIncludes>
								<include>dataaccess.wsdl</include>
							</schemaIncludes>
							<cleanPackageDirectories>false</cleanPackageDirectories>
							<generateDirectory>${project.build.directory}/generated-sources/dataaccess</generateDirectory>
							<generatePackage>br.org.rodnet.dataaccess.stub</generatePackage>
						</configuration>
					</execution>
				</executions>
			</plugin>
``` 

Explicando alguns pontos:

- com o _goal_ configurado como _generate_, as classes serão geradas quando o comando `mvn compile` for chamado.
- o `schemaDirectory` e o `schemaIncludes` apontam onde está o WSDL do servidor. Basicamente copiei o WSDL fornecido pelo servidor para um arquivo.
- as classes serão geradas na pasta `target/generate-sources/dataccess`, no pacote `br.org.rodnet.dataaccess.stub`

Por último, há também a classe `DataAccessSOAPConfiguration`:

```java
class DataAccessSOAPConfiguration {

    private static final JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
            .withMarshallerSchemaLocation("${dataaccess.soap.url} ${dataaccess.soap.wsdl}")
            .build();

    @Bean
    Encoder soapEncoder() {
        return new SOAPEncoder(jaxbFactory);
    }

    @Bean
    Decoder soapDecoder() {
        return new SOAPDecoder(jaxbFactory);
    }

}
```

O Feign é altamente configurável. [É possível customizá-lo usando outras bibliotecas diferentes das que ele utiliza por padrão.](https://www.baeldung.com/spring-cloud-openfeign) E, no nosso caso, precisamos configurar o _Encoder_ e o _Decoder_ para que ele suporte o SOAP.

Resumidamente falando:

- _Encoder_ é a classe que prepara o objeto para ser enviado para o servidor
- _Decoder_ é a classe que receberá a resposta e a converte para os objetos que precisamos

Ou seja, para trabalharmos com SOAP, precisamos sobrescrever o `Encoder` e o `Decoder` (que por _default_) lida com REST. Por isso que normalmente não se cria esses beans quando o protocolo é o REST.

Ao rodar os testes da aplicação:

```bash
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.02 s - in br.org.rodnet.testeopenfeign.TesteOpenFeignApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  18.014 s
[INFO] Finished at: 2022-04-24T18:38:26-03:00
[INFO] ------------------------------------------------------------------------
```

## Conclusão

Espero que tenha ficado claro como utilizar o Feign em sistemas que lidam com SOAP. 

O Feign é um _framework_ bem versátil, e com ele, foi possível escrevermos a integração com sistemas legados de forma simples, sem muita complexidade. Isso sem contar o "programar para uma interface, não para uma implementação" levado ao extremo.

Até a próxima!