Restful Web Services Using Apache Cxf and Spring Example

Joaquín Ponte

SOAP Microservices with Spring Boot, Part 1 using Apache CXF

This is the first part of a series of articles where we build a SOAP microservice with Spring Boot using several libraries for the SOAP implementation. This article explains the implementation with Apache CXF Library. I created a WSDL with an imported XSD file that represents the service from a legacy system that we want to update to a microservice architecture. I will show you the steps to generate the code and implement the service.

If you want to skip the introduction and go directly to the code, then you can find it in my GitHub repository ws-employee-soapcxf

SOAP Microservices with Spring Boot and Apache CXF

A docker container created with Spring Boot exposing a SOAP endpoint for a legacy client

The tech stack for this POC is:

  • Spring Boot 2.3.4
  • Java 15
  • Apache CXF 3.4
  • REST Assured 4.3
  • Docker

Software requirements

Workstation must be properly configured with next tools:

  • Java 15 JDK - https://jdk.java.net/15/
  • Maven - https://maven.apache.org/download.cgi

Optional tools

  • Docker - https://www.docker.com/products/docker-desktop
  • Docker hub account with token access enabled

WSDL and Domain Model

In our example, we are going to work in a fictitious Employee SOAP service with 2 operations:

  • GetEmployeeById
  • GetEmployeeByName

For the demo, I separated the XSD from the WSDL. In a real scenario, this will be the most followed pattern but expect to have more than one XSD in different folders. The employee.xsd has the full domain model for the service, next diagram shows the main response sent back to the client

XSD Schema

The WSDL…

Introduction

Just wait... SOAP Microservices? Nowadays, we have different microservice architectures based on technologies like REST webservices or RPC protocols such as gRPC. So why should we be interested in implementing a microservice using an old protocol like SOAP? Well, this can be particularly useful when you are on the migration path from an old legacy monolithic application where you have other systems that are connected to it through SOAP services. There are many scenarios where these systems (clients) can not be updated, therefore can not be connected to a new infrastructure, for example:

  • The company doesn't have the source code of these clients, believe me, this happens very often or doesn't know the source version deployed on production.
  • There are no human resources or technical support to apply the changes and test the legacy clients.
  • The cost of updating them is extremely high because of the reason mentioned previously or because of the need for paying license fees
  • Updating these clients doesn't bring any business value to the organization, so there is no need to change something that is working correctly just because you are introducing a new architecture.

Among these and other reasons, we need to provide the same interface for these old clients.

Get the WSDL for the service

Getting the WSDL from an existing SOAP web service is very simple; the standardized way to do it (although it may depend on the framework used to create the webservice) is by postfixing the URL with ?WSDL or .WSDL example:

            http://mywebservice.domain:8080/endpoint?WSDL                      

This approach is commonly known as contract-first; we start with the WSDL contract, and then we use Java to implement it. Many times the WSDL comes with the domain model in separate XSD's files.

In our example, we are going to work in a fictitious Employee SOAP service with 2 operations:

  • GetEmployeeById
  • GetEmployeeByName

For the demo, I separated the XSD from the WSDL. This will be the most followed pattern but expect to have more than one XSD in different folders. The employee.xsd has the full domain model for the service. The next diagram shows the main response sent back to the client.

XSD Schema

The WSDL is used for describing the functionality of the SOAP-based webservice. The most important section for this article is the wsdl:portType. This section defines the interface for the service we want to implement: operations, input, and output parameters:

portType diagram

Next is the WSDL section for wsdl:portType:

                          <!--This element defines the service operations and the combination of input and output elements to clients-->              <wsdl:portType              name=              "EmployeeServicePortType"              >              <wsdl:operation              name=              "GetEmployeeById"              >              <wsdl:input              message=              "tns:EmployeeByIdRequest"              />              <wsdl:output              message=              "tns:EmployeeResponse"              />              </wsdl:operation>              <wsdl:operation              name=              "GetEmployeesByName"              >              <wsdl:input              message=              "tns:EmployeeByNameRequest"              />              <wsdl:output              message=              "tns:EmployeesResponse"              />              </wsdl:operation>              </wsdl:portType>                      

The complete WSDL file is here: EmployeeServices.wsdl

Setting up the project

The project uses maven, and the properties and dependencies of the pom.xml are these:

                          <properties>              <!-- Override BOM property in Spring Boot for Rest Assured and Groovy-->              <!-- With rest-assured 4.3.X upgrade the Groovy from 2.5.7 to 3.0.2 -->              <rest-assured.version>4.3.1</rest-assured.version>              <groovy.version>3.0.2</groovy.version>              <!-- Other properties-->              <java.version>15</java.version>              <cxf.version>3.4.0</cxf.version>              <springboot.version>2.3.3.RELEASE</springboot.version>              </properties>              <dependencies>              <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>              </dependency>              <dependency>              <groupId>org.projectlombok</groupId>              <artifactId>lombok</artifactId>              <optional>true</optional>              </dependency>              <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-test</artifactId>              <scope>test</scope>              <exclusions>              <exclusion>              <groupId>org.junit.vintage</groupId>              <artifactId>junit-vintage-engine</artifactId>              </exclusion>              </exclusions>              </dependency>              <!-- CXF Framework -->              <dependency>              <groupId>org.apache.cxf</groupId>              <artifactId>cxf-spring-boot-starter-jaxws</artifactId>              <version>${cxf.version}</version>              </dependency>              <!-- Required if you need to log the request/response -->              <dependency>              <groupId>org.apache.cxf</groupId>              <artifactId>cxf-rt-features-logging</artifactId>              <version>${cxf.version}</version>              </dependency>              <!-- Testing -->              <dependency>              <groupId>io.rest-assured</groupId>              <artifactId>rest-assured</artifactId>              <scope>test</scope>              </dependency>              </dependencies>                      

Notes:

  • Rest Assured. We are using the latest version at the moment of writing the article. The version 4.3.1 requires groovy 3.0.2; we archive the correct configuration by overriding the parent pom libraries by using the pom properties
  • Apache CXF. Dependency cxf-rt-features-logging is only required if we need to log the request/response. This demo prints the request/response payload on the application console.

Generating the code

We will generate the java classes that map to the domain model and the operations described in the WSDL. We are going to use the maven plugin from the Apache CXF project to generate the code.

Code generation

The XSD and wsdl files are located under the next structure:

            <source-code> ├───src     └───main         └───resources             └───wsdl                      

Next is the configuration of the plugin in the pom.xml

                          <plugin>              <groupId>org.apache.cxf</groupId>              <artifactId>cxf-codegen-plugin</artifactId>              <version>${cxf.version}</version>              <executions>              <execution>              <id>generate-sources</id>              <phase>generate-sources</phase>              <configuration>              <sourceRoot>${project.build.directory}/generated-sources/cxf</sourceRoot>              <wsdlOptions>              <wsdlOption>              <wsdl>${basedir}/src/main/resources/wsdl/EmployeeServices.wsdl</wsdl>              </wsdlOption>              </wsdlOptions>              </configuration>              <goals>              <goal>wsdl2java</goal>              </goals>              </execution>              </executions>              </plugin>                      

The plugin will generate the java classes in the next directory:

            <source-code>/target/generated-sources/cxf                      

Generate the java classes by running in the command window in the project root:

The next image shows the generated classes and highlights the one we will use as the endpoint to implement the webservice: EmployeeServicePortType.

XSD Schema

  • Domain classes. These are the XSD file elements and represent the input and output parameters of the web service. The classes are: Address, Employee, EmployeeResponse, EmployeeByIdRequest, EmployeeByNameRequest.
  • WSDL classes. These classes describe the service operations and its input and output elements: EmployeeService, EmployeeServicePortType.

Configure Apache CXF Runtime

The configuration is straightforward. In the application.yml we can override the base path for Apache CXF, by default, it is /services in our example, we will use /soap.

We need to provide the path for the service exposed: /service/employee. We provide this configuration by creating a @Bean Endpoint:

                          import              org.springframework.context.annotation.Configuration              ;              import              javax.xml.ws.Endpoint              ;              @Configuration              public              class              ApplicationConfiguration              {              @Autowired              private              Bus              bus              ;              @Bean              public              Endpoint              endpoint              (              EmployeeEndpoint              employeeEndpoint              )              {              EndpointImpl              endpoint              =              new              EndpointImpl              (              bus              ,              employeeEndpoint              );              endpoint              .              publish              (              "/service/employee"              );              return              endpoint              ;              }              }                      

Implementing the service

One of the generated classes is EmployeeServicePortType as described wsdl:portType section. This is an interface with the 2 operations and input/output parameters. In the next example, we create the class EmployeeEndpoint that implements EmployeeServicePortType. For demo purposes, we are using a fake backend that will provide the data for the service. The backend can be, for example, a datastore or another webservice.

                          import              lombok.extern.slf4j.Slf4j              ;              import              org.apache.cxf.feature.Features              ;              import              org.springframework.stereotype.Service              ;              import              com.jpworks.employee.*              ;              @Service              @Slf4j              @Features              (              features              =              "org.apache.cxf.ext.logging.LoggingFeature"              )              public              class              EmployeeEndpoint              implements              EmployeeServicePortType              {              BackendService              backendService              ;              public              EmployeeEndpoint              (              BackendService              backendService              ){              this              .              backendService              =              backendService              ;              }              @Override              public              EmployeesResponse              getEmployeesByName              (              EmployeeByNameRequest              parameters              )              {              EmployeesResponse              employeesResponse              =              new              EmployeesResponse              ();              try              {              employeesResponse              .              getEmployee              ().              addAll              (              backendService              .              getEmployeesByName              (              parameters              .              getFirstname              (),              parameters              .              getLastname              ()));              }              catch              (              Exception              e              ){              log              .              error              (              "Error while setting values for employee object"              ,              e              );              }              return              employeesResponse              ;              }              @Override              public              EmployeeResponse              getEmployeeById              (              EmployeeByIdRequest              parameters              )              {              EmployeeResponse              employeeResponse              =              new              EmployeeResponse              ();              try              {              employeeResponse              .              setEmployee              (              backendService              .              getEmployeeById              (              parameters              .              getId              ()));              }              catch              (              Exception              e              ){              log              .              error              (              "Error while setting values for employee object"              ,              e              );              }              return              employeeResponse              ;              }              }                      

In the example above, we use @Features to log the request and response in the application log.

Running the application

In a command window on the root project run:

The log in the console:

                          .   ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )   '  |____| .__|_| |_|_| |_\__, | / / / /  =========|_|==============|___/=/_/_/_/  :: Spring Boot ::        (v2.3.4.RELEASE)  2020-10-02 22:23:49.563  INFO 1 --- [           main] com.jpworks.datajdbc.MainApplication     : Starting MainApplication v1.0.1-SNAPSHOT on b6e50b2f461b with PID 1 (/app.jar started by root in /) 2020-10-02 22:23:49.572 DEBUG 1 --- [           main] com.jpworks.datajdbc.MainApplication     : Running with Spring Boot v2.3.4.RELEASE, Spring v5.2.9.RELEASE 2020-10-02 22:23:49.573  INFO 1 --- [           main] com.jpworks.datajdbc.MainApplication     : The following profiles are active: local 2020-10-02 22:23:51.163  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http) 2020-10-02 22:23:51.179  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat] 2020-10-02 22:23:51.179  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38] 2020-10-02 22:23:51.254  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext 2020-10-02 22:23:51.255  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1625 ms 2020-10-02 22:23:51.541  INFO 1 --- [           main] o.s.boot.web.servlet.RegistrationBean    : Servlet CXFServlet was not registered (possibly already registered?) 2020-10-02 22:23:51.885  INFO 1 --- [           main] o.a.c.w.s.f.ReflectionServiceFactoryBean : Creating Service {http://service.datajdbc.jpworks.com/}EmployeeEndpointService from class com.jpworks.employee.EmployeeService 2020-10-02 22:23:52.455  INFO 1 --- [           main] org.apache.cxf.endpoint.ServerImpl       : Setting the server's publish address to be /service/employee 2020-10-02 22:23:52.646  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor' 2020-10-02 22:23:52.870  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path '' 2020-10-02 22:23:52.889  INFO 1 --- [           main] com.jpworks.datajdbc.MainApplication     : Started MainApplication in 4.018 seconds (JVM running for 4.625)                      

To get the available endpoints, write in the browser:

            http://localhost:8081/soap                      

services

To get the wsdl of the service, write in the browser:

            http://localhost:8081/soap/service/employee?wsdl                      

services

We can test the application using an Http Client like PostMan or Jmeter using a POST call (for test cases, the application uses RestAssured library). But I will use the traditional SoapUI, the endpoint for the service is: http://localhost:8081/soap/service/employee.

services

On the application console you will see the request/response logged:

            2020-10-06 15:36:01.396  INFO 102696 --- [           main] com.jpworks.datajdbc.MainApplication     : Started MainApplication in 3.104 seconds (JVM running for 3.988) 2020-10-06 15:36:23.958  INFO 102696 --- [nio-8081-exec-1] o.a.c.s.EmployeeServicePortType.REQ_IN   : REQ_IN     Address: http://localhost:8081/soap/service/employee     HttpMethod: POST     Content-Type: text/xml;charset=UTF-8     ExchangeId: 872e1281-4545-45ad-9871-331d96c450cf     ServiceName: EmployeeEndpointService     PortName: EmployeeEndpointPort     PortTypeName: EmployeeServicePortType     Headers: {SOAPAction="http://www.jpworks.com/employee/GetEmployeesByName", host=localhost:8081, connection=Keep-Alive, content-type=text/xml;charset=UTF-8, Content-Length=273, accept-encoding=gzip,deflate, user-agent=Apache-HttpClient/4.5.5 (Java/12.0.1)}     Payload: <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:emp="http://www.jpworks.com/employee">    <soapenv:Header/>    <soapenv:Body>       <emp:EmployeeByNameRequest firstname="John" lastname="Miller"/>    </soapenv:Body> </soapenv:Envelope>  2020-10-06 15:36:23.995  INFO 102696 --- [nio-8081-exec-1] o.a.c.s.E.RESP_OUT                       : RESP_OUT     Address: http://localhost:8081/soap/service/employee     Content-Type: text/xml     ResponseCode: 200     ExchangeId: 872e1281-4545-45ad-9871-331d96c450cf     ServiceName: EmployeeEndpointService     PortName: EmployeeEndpointPort     PortTypeName: EmployeeServicePortType     Headers: {}     Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><EmployeesResponse xmlns="http://www.jpworks.com/employee"><employee id="1" firstname="Jeffery" lastname="Lewis" birthdate="2000-01-01" gender="M"/><employee id="2" firstname="Francis" lastname="Stevens" birthdate="1999-01-01" gender="M"/></EmployeesResponse></soap:Body></soap:Envelope>                      

Conclusion

This article explained why it is still important to consider SOAP as a tool in the migration path from any legacy application. We have used a demo WSDL and reviewed the relevant sections. We have learned how to use Apache CXF for SOAP implementation and configuration.

If you have any questions, feel free to ask in the comments; thanks for reading!

Restful Web Services Using Apache Cxf and Spring Example

Source: https://dev.to/jpontdia/soap-microservices-with-spring-boot-and-cxf-on-the-monolithic-migration-path-439h

0 Response to "Restful Web Services Using Apache Cxf and Spring Example"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel