SOAP Microservices with Spring Boot, Part 2 using Spring Webservices (spring-ws)
Tags: microservices, java, soap, docker
Published: 2020-10-14 05:20:42

This is the second part of the tutorial creating SOAP microservices with Spring Boot. On this occasion, we will use the same artifacts from the previous article [Part 1](https://dev.to/jpontdia/soap-microservices-with-spring-boot-and-cxf-on-the-monolithic-migration-path-439h), the project structure, the controllers, and the WSDL/XSD files. 

Using the same WSDL and components will help you understand the key differences between Apache CXF and Spring WS for creating SOAP webservices, and those will be discussed in another publication.

> If you want to skip the introduction and go directly to the code, then you can find it in my GitHub repository [ws-employee-soapspringws](https://github.com/jpontdia/ws-employee-soapspringws)

{% github jpontdia/ws-employee-soapspringws %}

As we discussed in the first part, we follow the contract-first approach, which means we have a previously defined WSDL/XSD, and we want to implement it with spring-ws. Once the endpoint is created, the framework can create a dynamic WSDL, but we don't want that because we want to preserve the original. So we are going to expose the original as a static WSDL. The complete explanation about the WSDL and XSD schema is in section: [`Get the WSDL for the service`](https://dev.to/jpontdia/soap-microservices-with-spring-boot-and-cxf-on-the-monolithic-migration-path-439h#GetWsdl) of the [Part 1](https://dev.to/jpontdia/soap-microservices-with-spring-boot-and-cxf-on-the-monolithic-migration-path-439h)

##Setting up the project
The project uses maven, and the properties and dependencies of the [pom.xml](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/pom.xml) are these:

```xml
<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>
	<springboot.version>2.3.4.RELEASE</springboot.version>
</properties>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web-services</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>

	<dependency>
		<groupId>org.glassfish.jaxb</groupId>
		<artifactId>jaxb-runtime</artifactId>
		<version>2.3.3</version>
	</dependency>

	<!-- Testing -->
	<dependency>
		<groupId>io.rest-assured</groupId>
		<artifactId>rest-assured</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
```
##Generating the Java classes from XSD

In spring-ws, the Java classes are only generated from the XSD files; the WSDL is not used for Java generation. The [employee.xsd](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/src/main/resources/wsdl/EmployeeServices.wsdl) is used for this purpose.

We will use the maven plugin: `jaxb2-maven-plugin` for java generation, the classes are saved in the next directory:

```bash
<source-code>/target/generated-sources/jaxb
```

Next is the configuration of the plugin in the [pom.xml](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/pom.xml)
```html
<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>jaxb2-maven-plugin</artifactId>
	<version>2.5.0</version>
	<executions>
		<execution>
			<id>xjc</id>
			<goals>
				<goal>xjc</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<sources>
			<source>${project.basedir}/src/main/resources/wsdl/employee.xsd</source>
		</sources>
	</configuration>
</plugin>
```
Generate the java classes by running in the command window in the project root:
```bash
mvn compile
```
Next image shows the generated classes:
![Generated Classes](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/assets/target-files.png?raw=true)

##Configure Spring Webservices (spring-ws)

Add the annotation `@EnableWs` to the configuration class
```java
@Configuration
@EnableWs
public class ApplicationConfiguration {
```

Configure the path for the exposed webservice; in our example, it is `/soap/service/.`

```java
@Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
    MessageDispatcherServlet servlet = new MessageDispatcherServlet();
    servlet.setApplicationContext(applicationContext);
    servlet.setTransformWsdlLocations(true);
    return new ServletRegistrationBean<>(servlet, "/soap/service/*");
}
```

Expose the static WSDL and XSD. We configure the service endpoint as: `/soap/service/EmployeeService`. We get the WSDL from the service with `http://localhost:8081/soap/service/EmployeeService.wsdl`
```java
@Bean(name = "EmployeeService")
public Wsdl11Definition defaultWsdl11Definition() {
    SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
    wsdl11Definition.setWsdl(new ClassPathResource("/wsdl/EmployeeServices.wsdl"));
    return wsdl11Definition;
}

@Bean
public XsdSchema employee() {
    return new SimpleXsdSchema(new ClassPathResource("/wsdl/employee.xsd"));
}
```

The XsdSchema definition is required because the WSDL has an imported XSD. The name of the bean must be the name of the XSD file.

*Important*
> If you have more than one XSD, then all of them must be described with XsdShema. Another approach is using the class org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection. If  for any reason the XSD files were placed in a relative path in a parent location, then you should consider serving them as static resources, and placing them inside /static directory in your classpath.
```xml
<wsdl:types>
  <xsd:schema>
    <xsd:import namespace="http://www.jpworks.com/employee"
         schemaLocation="../employee.xsd"/>
    </xsd:schema>
</wsdl:types>
```

## Implementing the service

We need to implement the portType section of the WSDL file manually
![portType diagram](https://github.com/jpontdia/ws-employee-soapcxf/blob/master/assets/porttype-diagram.png?raw=true)

Every operation is implemented using the `@PayloadRoot` and `@ResponsePayload` annotations. The `@PayloadRoot` has two elements, the namespace, and the localPart. For the operation `GetEmployeeById` the localPart is `EmployeeByIdRequest` and this value comes from the next section:

```xml
<wsdl:operation name="GetEmployeeById">
  <wsdl:input message="tns:EmployeeByIdRequest"/>
```
And the namespace value comes from the reference `tns:`, we find this value in the WSDL definitions section:

```xml
<wsdl:definitions 
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.jpworks.com/employee"
```

The java method is a direct implementation of the operation using the generated classes from the XSD:
```java
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "EmployeeByIdRequest")
@ResponsePayload
public EmployeeResponse getEmployeeById(@RequestPayload EmployeeByIdRequest parameters) {
```

The complete implementation with the fake backend service is here:
```java
package com.jpworks.datajdbc.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.jpworks.employee.*;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Slf4j
@Endpoint
@RequiredArgsConstructor
public class EmployeeEndpoint{

    private final BackendService backendService;
    private static final String NAMESPACE_URI = "http://www.jpworks.com/employee";

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "EmployeeByIdRequest")
    @ResponsePayload
    public EmployeeResponse getEmployeeById(@RequestPayload 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;
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "EmployeeByNameRequest")
    @ResponsePayload
    public EmployeesResponse getEmployeesByName(@RequestPayload 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;
    }
}
```

##Running the application
In a command window, on the root project run:
```bash
mvn spring-boot:run
```
The log in the console:
```windows

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.4.RELEASE)

2020-10-13 15:03:35.154  INFO 157100 --- [           main] com.jpworks.datajdbc.MainApplication     : Starting MainApplication on LNAR-PC0NTFTQ with PID 157100 (C:\workspace\dev\datajdbc\ws-employee-soapspringws\target\classes started by jponte in C:\workspace\dev\datajdbc\ws-employee-soapspringws)
2020-10-13 15:03:35.156 DEBUG 157100 --- [           main] com.jpworks.datajdbc.MainApplication     : Running with Spring Boot v2.3.4.RELEASE, Spring v5.2.9.RELEASE
2020-10-13 15:03:35.157  INFO 157100 --- [           main] com.jpworks.datajdbc.MainApplication     : The following profiles are active: local
2020-10-13 15:03:35.642  INFO 157100 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.ws.config.annotation.DelegatingWsConfiguration' of type [org.springframework.ws.config.annotation.DelegatingWsConfiguration$$EnhancerBySpringCGLIB$$1d99be26] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-10-13 15:03:35.689  INFO 157100 --- [           main] .w.s.a.s.AnnotationActionEndpointMapping : Supporting [WS-Addressing August 2004, WS-Addressing 1.0]
2020-10-13 15:03:35.988  INFO 157100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8081 (http)
2020-10-13 15:03:35.997  INFO 157100 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-10-13 15:03:35.998  INFO 157100 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38]
2020-10-13 15:03:36.097  INFO 157100 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-10-13 15:03:36.097  INFO 157100 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 902 ms
2020-10-13 15:03:36.268  INFO 157100 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-10-13 15:03:36.405  INFO 157100 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2020-10-13 15:03:36.414  INFO 157100 --- [           main] com.jpworks.datajdbc.MainApplication     : Started MainApplication in 1.627 seconds (JVM running for 2.19)
```
To get the wsdl of the service, write in the browser:
```html
http://localhost:8081/soap/service/EmployeeService.wsdl
```
![WSDL Generation](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/assets/wsdl-generation.png?raw=true)

Testing the application with SoapUI and the endpoint:
`http://localhost:8081/soap/service/EmployeeService`
![WSDL Generation](https://github.com/jpontdia/ws-employee-soapspringws/blob/main/assets/soapui.png?raw=true)

##Conclusion
This tutorial explained how to create a SOAP microservice with a contract-first approach, using Spring Boot and Spring Webservices (spring-ws)

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