DEV Community

Rodrigo Vieira
Rodrigo Vieira

Posted on

Feign com SOAP: uma PoC

Faz tempo que se diz que devemos programar para uma interface, e não para uma implementação.

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. E, neste artigo, vou falar um pouco sobre o Spring Open 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, 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. 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.

Nesta PoC, usei como server um serviço disponibilizado gratuitamente 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:

@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);
}
Enter fullscreen mode Exit fullscreen mode

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 oferecido pelo próprio serviço:

<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>
Enter fullscreen mode Exit fullscreen mode

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, o XMLBeans ou mesmo o Axis. Na PoC, entretanto, usei o maven-jaxb2-plugin.

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

            <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>
Enter fullscreen mode Exit fullscreen mode

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:

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);
    }

}
Enter fullscreen mode Exit fullscreen mode

O Feign é altamente configurável. É possível customizá-lo usando outras bibliotecas diferentes das que ele utiliza por padrão. 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:

[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] ------------------------------------------------------------------------
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (3)

Collapse
 
fabio21777 profile image
fabio21777

muito legal, obrigado pelo post

Collapse
 
kelissonj profile image
Kélisson J.

Olá, parabéns pelo artigo, muito bem escrito e de fácil entendimento. Só estou com um pequeno problema para realizar a request.

Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; O conteúdo não é permitido no prólogo.
at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1243)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:635)
at java.xml/org.xml.sax.helpers.XMLFilterImpl.parse(XMLFilterImpl.java:357)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:688)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:775)
... 139 more

É como se na hora de enviar o conteúdo serializado, não estivesse mandando junto aquele comecinho padrão do xml.

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

Collapse
 
rodrigovp profile image
Rodrigo Vieira

Oi Kéllisson J.!

Você está usando a versão 17 do java para rodar a sua aplicação? Rodei ela novamente (pois o XML poderia ter se modificado) e funcionou ;)