Home Blog About

Organizing SOAP Web Services

 
DEEPAK KARANTH /

In this post, we will give you brand new ideas on how you can organize and maintain SOAP based web services during the evolution of the software.

The information in this post is critical in case your organization follows an incremental approach to software development.

Because you will be delivering incremental software, you do not have all the requirement specifications upfront. Thus when you are developing a web services endpoint to your constantly evolving system, the consistency, maintainability and extensible nature of code are important to providing new functional features on time and with quality.

But before we get into the main topic of this post, it is worth skimming over REST web services and see how easy it is to introduce new services in a scalable and maintainable manner. This will allow us to easily relate to the issues in developing hundreds of SOAP web services.

Further on, we discuss one potential solution to manage SOAP web service design and development.

We assume you already have working knowledge of both varieties of web services, especially SOAP.

REST Web services

It is quite manageable and maintainable to add new web services into a REST based system for the following reasons.

Top level segregation

Firstly, the top-level segregation of REST web services happen at the web service class level.

Given below is a sample REST service to manage Customers. This web service provides all of the CRUD actions for the Customer entity.

In the example below, line no.2 is where we group all requests for the Customer entity. All web services that exist in this class can only be called when the request url contains /Customer.

Other classes can have grouping for other entities, for e.g. Address, Orders etc. This way, each entity can have its own webservice class and unique url.

@Stateless
@Path("/Customer")
public class CustomerFacadeREST extends AbstractFacade<customer> {
  @PersistenceContext(unitName = "")
  private EntityManager em;

  public CustomerFacadeREST() {
    super(Customer.class);
  }

  @POST
  @Override
  @Consumes({"application/xml", "application/json"})
  public void create(Customer entity) {
    super.create(entity);
  }

  @PUT
  @Path("{id}")
  @Consumes({"application/xml", "application/json"})
  public void edit(@PathParam("id") Integer id, Customer entity) {
    super.edit(entity);
  }

  @DELETE
  @Path("{id}")
  public void remove(@PathParam("id") Integer id) {
    super.remove(super.find(id));
  }

  @GET
  @Path("{id}")
  @Produces({"application/xml", "application/json"})
  public Customer find(@PathParam("id") Integer id) {
    return super.find(id);
  }

  @GET
  @Override
  @Produces({"application/xml", "application/json"})
  public List<customer> findAll() {
    return super.findAll();
  }
}

Automatic classification of web methods

The second level of segregation in REST services is at the method level. Each REST method is associated with a particular HTTP Verb (POST, PUT, GET, DELETE). It gives a natural separation for the operations on a particular entity.

For example, GET is always used for a fetch request, PUT for an insert, POST for an update etc. The same is illustrated in the code sample above.

Also note that each web method can have the path annotation as well. For instance, in order to fetch the details of a particular customer using the id, the request url takes the form http://[app-context]/Customer/[id]

Other advantages of REST

The other natural outcomes of using the REST approach for developing web services are:

  1. The client only works with REST URLs unlike SOAP web services in which client stubs have to be generated using wsdl files
  2. There are well established good practices to define REST methods and also their input and output parameters
  3. Easy to define and modify the format of the parameters, e.g. JSON, XML etc.
  4. Ability to define standard error codes in case REST operations fail. For e.g. 404 if a resource is not found, 200 for OK etc.

Now let us take a look at developing SOAP web services.

SOAP Web services

In the SOAP approach, the client should generate java class files using wsdl in order to call the web services.

This leads to what I believe the biggest draw back in terms of code – the web service method developed in SOAP generally tends to be programmed for a specific use case.

Use case specific SOAP web methods

For example, when a user story to get the basic details of a particular customer is to be implemented, the web method developed specifically for that user story will look something similar to

@WebService(serviceName = "Customer")
public class CustomerWebservice {

  @WebMethod(operationName = "getCustomerDetails")
  public CustomerDetails getCustomerDetails(@WebParam(name="id") Integer id) {
    // Collapsed code ...
  }
}

As you can observe, the new web method is named getCustomerDetails and takes in customer Id as parameter. It fetches all the data it needs and constructs the CustomerDetails object.

Explosion of number of web methods

In the future, when there is a requirement to show not only customer details but also address details, then we have two options: either modify the above method to return address as well or create a new web method getCustomerDetailsWithAddress().

As you can probably guess, this method of development quickly leads to an explosion in the number of web services in the system. Many of these web methods will also be returning almost similar objects with minor variations.

There will also be an explosion of methods that span multiple entities – such as getAddressByCustomer(Integer customerId). Where do you place this method? In the Customer web service or Address web service?

A better way to organize SOAP web services

In order to solve the above problems with designing SOAP web services, we recommend a unique approach. This approach takes all the positives from the REST web services and incorporates them into the design of SOAP web services in an easy to understand manner.

The first major step is to define two types of web services, each with a distinct set of rules.

Fetch(read-only) web services

  1. Only used to fetch data (Used by clients or other services)
  2. No business logic exist here
  3. They return objects(a.k.a DTOs). These DTO’s are re-usable when invoking other web services.
  4. They do not update or add new data into the system
  5. Generally need to exist per entity – for example Customer web service, Order web service etc.

Business web services

  1. Specific to user stories i.e functionality requested by user stories. In the absence of user stories, we wouldn’t have created them at all.
  2. Conceals business logic. For example, addCustomer(Customer newCustomer) web method may need to make sure that the customer with the same details doesn’t already exist in the system, that the address is valid etc.
  3. Input validation exists i.e. for the web service to successfully execute, certain validation has to be passed. Continuing on the addCustomer example, the Name, Phone and at-least one address might be mandatory.
  4. Is capable of adding or modifying data in the system. By modification, we mean either addition, modification or deletion of data. Upon modification of a business entity, these web services return the business entity’s primary id. If they have modified many related business entities(e.g customer and customer category), they only return the key of the most important entity(e.g. customer).
  5. Returns the status of the operation – i.e success or failure. You could define more states.
  6. Will generally not be re-usable for another use case.

Example

To illustrate it with an example, let is consider building the Customer web service using this approach.

Firstly, let us create two DTOs:

Customer DTO contains the id, first name and surname.

public class CustomerDTO {

  private Integer id;

  private String firstname;

  private String surname;
  // getters and setters
}

Address DTO consists of

public class AddressDTO {

  private Integer addressId;

  private String housenumber;

  private String street;

  private String city;
  // getters and setters
} 

AddCustomer web service

Adding a customer inserts/modifies the data in the system. Hence, it should be developed as a business web service.

Let us assume that to the add the customer, we need both the customer and address details. Hence, in order for the client to pass in both at the same time, we create a AddNewCustomerDTO as follows. Remember that this DTO will not be reusable in other use cases. If you want to avoid creating a non-reusable DTO, the web service addCustomer could directly take in the CustomerDTO and the AddressDTO as parameters.

public class AddNewCustomerDTO {

  private CustomerDTO customer;

  private AddressDTO address;
  // getters and setters
} 

The add customer web service will look like:

@WebMethod(operationName = "AddCustomer")
public ResultStatus addCustomer(AddNewCustomerDTO addNewCustomerDTO) throws Exception {

  // 1. Validation to check input such as if addressDTO within addNewCustomerDTO 
  // is null, or if all parameters in the customer DTO object has been passed in 

  // 2. Add the customer by making service calls 

  // 3. Send SUCCESS or FAILURE as the result along with 
  // the main entity ID(in our case, it is the customer id) 
}

GetCustomer web service

The GetCustomer web service is a read-only operation. Hence, it will be designed as a Fetch web service.

It only returns only one high level DTO – CustomerDTO.

Note: The high-level DTO could contain other child DTOs. For instance, if the address entity always exists only within the context of a Customer object alone and not as an individual entity, then the CustomerDTO could always be populated to have the AddressDTO as a child DTO.

@WebMethod(operationName = "getCustomerById") 
public CustomerDTO getCustomerById(
  @WebParam(name = "customerId") Integer customerId) 
  throws Exception { 

// 1. Basic validation: check if customerId is passed as input

// 2. Fetch customer and address from DB using service // 3. Construct CustomerDTO and return it

}

Further suggestions

  1. Create separate java class files for fetch and business web services based on a logical separation – for e.g CustomerWebService, OrderWebService etc.
  2. When in doubt, always place business logic on the server-side. It can then be accessed by many clients using web services. Server side is probably going to far outlive your client application.
  3. When in doubt, always return less information from web services to the client. Don’t let the client have access to in-solicited information.

I hope this post provided you with useful tips to make it simpler to develop maintainable and extensible SOAP based endpoints in your software.


 
comments powered by Disqus