Chapter 10. Task Services with Java

Image

10.1 Inside a Task Service

10.2 Building Task Services

Task services encapsulate the non-agnostic automation logic of business processes and have a scope of execution that often corresponds to that of a business process instance. This chapter explores the design and implementation of task services with Java.

10.1 Inside a Task Service

The service logic of a task service will often consist of controller logic, which composes entity and utility services as needed to fulfill functionality (Figure 10.1). Unlike agnostic services, which are generally built as standalone, shared IT resources, task services are modeled according to business process logic and designed around the execution of that logic via the composition of other services. The following case study demonstrates a simplified business process automated by a task service.

Image

Figure 10.1 Task service design is focused on implementing non-agnostic logic that is, to a significant extent, comprised of composition logic.


Case Study Example

During the service identification phase, the Credit Approval process is decomposed into a set of individual tasks, resulting in the Process Customer Account service candidate with the capability candidates retrieveCustomerAccountInfo and sendCustomerNotification.

Image

Figure 10.2 The Process Customer Account service candidate with two candidate capabilities: retrieveCustomerAccountInfo and sendCustomerNotification.

Part of the required customer information needed for credit approval includes data about all of the accounts that a particular customer has with NovoBank. The data is retrieved from within the retrieveCustomerAccountInfo operation by leveraging the Customer entity service and an additional Account entity service. The new Account entity service uses a message entity type that represents a summary of account data presented in Example 10.1.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://account.entity.
  services.novobank.com/" xmlns:tns="http://account.entity.services.
  novobank.com/" xmlns:xs="http://www.w3.org/ 2001/XMLSchema">
  <xs:element name="findAccountByCustomer"
    type="tns:findAccountByCustomer"/>
  <xs:element name="findAccountByCustomerResponse"
    type="tns:findAccountByCustomerResponse"/>

  <xs:complexType name="findAccountByCustomer">
    <xs:sequence>
      <xs:element name="arg0" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="findAccountByCustomerResponse">
    <xs:sequence>
      <xs:element name="return"
        type="tns:account" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="account">
    <xs:sequence>
      <xs:element name="accountId" type="xs:string" minOccurs="0"/>
      <xs:element name="accountType" type="xs:string" minOccurs="0"/>
      <xs:element name="balance" type="xs:double"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>


Example 10.1

An entity service returns data found in a database, and returns message entities that reflect the needs of the calling business service consumer. For the NovoBank credit approval process, only the current balance of each of a customer’s accounts is taken into consideration. Therefore, the Account entity service returns a list of Account message entity objects, which only contain information about the type of account and its current balance. A customer can have one or more accounts, all of which are relevant when considering an issued credit request.

Both entity services are composed by the Process Customer Account service. The Customer entity service returns the appropriate customer ID for a given name, which is then used to retrieve detailed account data by calling the Account entity service. Figure 10.3 shows the dependencies between the Process Customer Account task service and the Customer and Account entity services.

Image

Figure 10.3 The Process Customer Account service composes the Customer and Account entity services.

The input parameters to the retrieveCustomerAccountInfo operation are the customer’s first and last names. The returned data is a combination of customer and account information, and the response message is a new message entity modeled on the needs of the credit approval process. The schema definition of the CustomerAccountInfo message entity in Example 10.2 contains element definitions for the retrieveCustomerAccountInfo operation of the Process Customer Account service.

<xs:schema version="1.0" targetNamespace="http://creditapproval.
  task.services.novobank.com/" xmlns:tns="http://creditapproval.
  task.services.novobank.com/" xmlns:ns1="http://account.entity.
  services.novobank.com/" xmlns:xs="http://www.w3.org/2001/
  XMLSchema" xmlns:ns2="http://customer.entity.services.
  novobank.com/">

  <xs:import namespace="http://account.entity.services.novobank.
    com/" schemaLocation="CustomerAccountService
    Service_schema3.xsd"/>
  <xs:import namespace="http://customer.entity.services. novobank.
    com/" schemaLocation="CustomerAccountService
    Service_schema1.xsd"/>

  <xs:element name="retrieveCustomerAccountInfo"
    type="tns:retrieveCustomerAccountInfo"/>

  <xs:element name="retrieveCustomerAccountInfoResponse"
    type="tns:retrieveCustomerAccountInfoResponse"/>
  <xs:complexType name="retrieveCustomerAccountInfo">
    <xs:sequence>
      <xs:element name="arg0" type="xs:string" minOccurs="0"/>
      <xs:element name="arg1" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="retrieveCustomerAccountInfoResponse">
   <xs:sequence>
      <xs:element name="return" type="tns:customerAccount Info"
        minOccurs="0"/>
   </xs:sequence>
  </xs:complexType>

  <xs:complexType name="customerAccountInfo">
    <xs:sequence>
      <xs:element name="accounts" type="ns1:account" nillable="true"
        maxOccurs="unbounded" minOccurs="0"/>
      <xs:element name="customer" type="ns2:customer" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>


Example 10.2

In Example 10.2, the Customer message entity and the Account message entity schemas are imported from separate XML schema files because they are defined in different namespaces. The resulting WSDL definition is shown in Example 10.3.

<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:tns="http://creditapproval.task.services.novobank.com/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  targetNamespace="http://creditapproval.task.services.novobank.
  com/" name="ProcessCustomerAccountServiceService">
  <types>
    <xsd:schema>
      <xsd:import namespace="http://customer.entity.services.
        novobank.com/" schemaLocation="http://192.168.1.100:8080/
        ProcessCustomerAccountServiceService/ProcessCustomerAccount
        Service/__container$publishing$subctx/META-INF/ wsdl/
        ProcessCustomerAccountServiceService_schema1.xsd"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns: soap12=
        "http://schemas.xmlsoap.org/wsdl/soap12/"/>
    </xsd:schema>
    <xsd:schema>
      <xsd:import namespace="http://creditapproval.task.
      services.novobank.com/" schemaLocation="http://192.168.
      1.100:8080/ProcessCustomerAccountServiceService/
      ProcessCustomerAccountService/__container$publishing$subctx/
      META-INF/wsdl/ProcessCustomerAccountServiceService_schema2.xsd"
      xmlns:wsdl= "http://schemas.xmlsoap.org/wsdl/"
      xmlns:soap12= "http://schemas.xmlsoap.org/wsdl/soap12/"/>
    </xsd:schema>
    <xsd:schema>
      <xsd:import namespace="http://account.entity.services.
        novobank.com/" schemaLocation="http://192.168.
        1.100:8080/ProcessCustomerAccountServiceService/Process
        CustomerAccountService/__container$publishing$subctx/META-INF/
        wsdl/ProcessCustomerAccountServiceService_schema3.xsd"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns:soap12= "http://schemas.xmlsoap.org/wsdl/soap12/"/>
    </xsd:schema>
  </types>

  <message name="retrieveCustomerAccountInfo">
    <part name="parameters"
      element="tns:retrieveCustomer AccountInfo"/>
  </message>
  <message name="retrieveCustomerAccountInfoResponse">
    <part name="parameters"
      element="tns:retrieveCustomer AccountInfoResponse"/>
  </message>
  <message name="sendCustomerNotification">
    <part name="parameters" element="tns:sendCustomer Notification"/>
  </message>

  <message name="sendCustomerNotificationResponse">
    <part name="parameters"
      element="tns:sendCustomer NotificationResponse"/>
  </message>

  <portType name="ProcessCustomerAccountService">
    <operation name="retrieveCustomerAccountInfo">
      <input message="tns:retrieveCustomerAccountInfo"/>
      <output message="tns:retrieveCustomerAccountInfo Response"/>
    </operation>
    <operation name="sendCustomerNotification">
      <input message="tns:sendCustomerNotification"/>
      <output message="tns:sendCustomerNotification Response"/>
    </operation>
  </portType>

  <binding name="ProcessCustomerAccountServicePortBinding"
    type="tns:ProcessCustomerAccountService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
      style="document"/>
    <operation name="retrieveCustomerAccountInfo">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="sendCustomerNotification">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="ProcessCustomerAccountServiceService">
    <port name="ProcessCustomerAccountServicePort"
      binding="tns:ProcessCustomerAccountServicePortBinding">
      <soap:address location="http://192.168.1.100:8080/
        ProcessCustomerAccountServiceService/
        ProcessCustomerAccountService"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        xmlns: soap12="http://schemas.xmlsoap.org/wsdl/soap12/"/>
    </port>
  </service>
</definitions>


Example 10.3

Note that the included element and message definitions for the sendCustomerNotification operation are revisited in Chapter 11.


The messages defined for a task service are ideally derived from a canonical data model, although non-standard message structures can also be defined since task services are often used in and designed for one business process. A task service that needs to compose entity services that support canonical messages should utilize or map them appropriately in the implementation logic.

The service definition and message entity schema are generally defined in the same namespace.

Performance Considerations

The interaction between the task service and the underlying entity and utility services can be resource-intensive. An invocation of a task service can result in a large number of invocations on the underlying composed set of services. Simplifying the communication path between services can help maintain performance.

For optimal performance, task services should be hosted in a location close to where the composed services reside. However, because the types of services composed by task services are generally agnostic, they are used across business domains and may be part of multiple service compositions. Options for redundant implementations of shared agnostic services can be explored to support performance requirements, as per the Redundant Implementation pattern.

Task services need to retain the state data required for their task-centric processing across service invocations. State data can be represented by local, thread-specific variables in the code if the service is implemented in Java.

Additional mechanisms can be required to allow the service to store state information temporarily in a persistent store (as per the State Repository pattern) or a dedicated state management utility service (as per the Stateful Services pattern) if the handled state becomes too large.

10.2 Building Task Services

The upcoming sections delve deeper into the architectural and programming details of task service implementations.

Implementation Considerations

The implementation logic for Java task services should be independent of the underlying technologies and code that use low-level Java APIs. This can be accomplished by abstracting lower level API functions into the utility service layer.

As an example, assume that an e-mail should be sent as part of a task service’s execution. The JavaMail API offers e-mail functionality that the service logic can utilize directly.

Alternatively, the service logic can delegate e-mail processing to an E-Mail utility service, further decoupling the task service from any dependency on underlying runtime features. The utility service can handle the details of accessing mail servers and the formatting and parsing of e-mail content.

Building task service logic can require the generation of code skeletons from service logic can require the generation of code skeletons from the interface, using the wsgen tool in the JAX-WS or implementing the logic for REST task services in a POJO with the JAX-RS annotations. Similarly, all services composed by the task service are available in WSDL format for Web services or as POJOs. When starting from WSDL, the appropriate Java interfaces and proxy classes can be generated using the same tool.

Sometimes the designed WSDL service interface document or resource representation format, such as the XML schema, is inappropriate for implementation in Java. For example, many Java classes will be generated using a data binding tool, such as the JAXB, from an interface or representation using schemas with large numbers of defined complex types.

The generation of too many classes can often be resolved by adjusting the schemas, but in many cases, the schemas are reused across many services and cannot easily be modified. Certain process automation environments can dictate the structure of schemas in a way that results in the same inflation of generated Java classes.

Task services compose entity services to retrieve business-centric data required for business task processing. The result of an invocation of one entity service operation may be used for the invocation of another, so that data from various sources can be aggregated. To avoid large data caches during invocation, ensure entity services are used to limit the amount of returned data. Efforts should be made to restrict query parameters to prevent excessively generic queries.

Data can be cached in the local heap. Ensure the data is stored in local variables so that it is scoped to only the current thread, although the multithreaded nature of Web service deployments already reinforces data storage in local variables. Data that cannot be stored in the local heap, such as amounts of data beyond the size of memory available, must be outsourced by storing it directly in a local file or database.

Simultaneously, ensure outdated data is cleared. Just as with other low-level APIs in a task service implementation, delegating the handling of local temporary caches to a utility service is advised. This type of cache service can be invoked locally as a Java class and exist in the same process as the actual service implementation, because of the potential performance impact of moving large amounts of data between processes.

The behavior of a task service often depends on the identity of the invoking caller, which needs to be transferred from the service consumer to the service if such behavior must be implemented in the service logic. If the service logic is implemented in an EJB, the caller’s identity can be discovered using the getCallerPrincipal() method on the EJB context. If the service is implemented in a JAX-RS resource class, the javax.ws.rs.core.SecurityContext interface can be used to obtain security-related information, such as user principals and user role information.

Incoming requests carry user credentials that are carried within the scope of the application server or container, translated into the Java security context java.security.Principal. Explicitly passing user identity information along with a request is often unnecessary if a proper security model has been established.

If a full Java EE environment is one of the deployment options for the task service, implementing the service logic inside a stateless session EJB is recommended. EJBs are stateless and offer a set of features and characteristics useful for task services without requiring any special coding, such as concurrent access, instance pooling, and transactional settings.

Implementing a task service as a stateless session bean allows for accessibility as a Web service via RMI/IIOP as a remote EJB, and locally via its local interface. A stateless session bean that has a Web service interface as well as a remote and local EJB interface can be deployed once and accessed in all three ways.

Multiple deployment options offer potential service consumers various choices based on performance requirements, available platforms, and other considerations. However, using local EJB interfaces requires that both service consumer and service are bundled together in a single deployment artifact and deployed on the same application server. Tight coupling between the service consumer and services should be avoided wherever possible.

For example, both service consumer and service must be redeployed together with the service consumer code if maintenance must be carried out on the service logic. The coupling is not as tight when using remote EJB interfaces, but the service consumer must still use a number of Java artifacts to provide access to the EJB. Using a Web-based service interface offers the loosest degree of coupling between service consumer and service. Figure 10.4 summarizes the different access options for service consumers with a service implemented as a stateless session EJB.

Image

Figure 10.4 Stateless session EJBs can be accessed over a variety of interfaces with varying degrees of coupling.

Since Java EE 6 and EJB 3.1, REST services that are stateless session beans or singleton beans can be marked as JAX-RS resources that can leverage the features of the EJB container. If a service is deployed in a pre-Java EE 6 container, a regular JAX-RS annotated POJO can be used to model a REST resource and the transaction handling delegated to a session EJB. When deploying services in a servlet or Java EE 6 or 7 Web Profile containers, the degree of support for transaction handling can vary from one implementation to another. Therefore, investigate the extent of support to determine whether any special application-level transaction handling is required.

A task service often composes one or more entity services to retrieve required data stored in a persistent and transactional data store. Changes to data must be made within the scope of a transaction, and multiple changes to data stored in different data stores must be made within the same transaction to avoid leaving the data stores in an inconsistent state. The task service implementation often determines the boundaries of transactions.

Web-Based Task Services

The following sections highlight issues pertaining to the creation of task services as SOAP-based Web services and REST services.

Task Services with SOAP and WSDL

Deploying a task service as a remote Java component, such as an EJB, tightly couples the service consumer and service. The process execution environment can be based on non-Java technology, if required, as Web services can be problematic in cases where a non-functional context must be preserved across service invocations.

The RMI/IIOP protocol used between Java EE application server processes automatically promotes context information about transactions or identity. This is not the case when using Web services, unless advanced WS-* standards (such as WS-Security and WS-AtomicTransaction) are used. Support for these standards is required on both the service consumer and service sides.

The following NovoBank case study example demonstrates the use of JAX-WS APIs for the creation of a SOAP-based task Web service.


Case Study Example

After the design of the Process Customer Account service is complete, the NovoBank team advances to the implementation of the service logic. The service implementation will be created in Java, and the service will be exposed as a stateless session bean. They use a JAX-WS-compliant runtime and deploy the service on a Java EE application server.

The service aggregates the existing Customer and Account entity services, which are both deployed as Web services. Even though these entity services are implemented in Java and could be invoked directly without going across the network or creating XML messages, the NovoBank team concludes that a Web service invocation will be more appropriate.

To increase the level of isolation between the task service and the aggregated entity services, both are deployed in physically different locations. The entity services interact directly with and are located close to underlying data sources, whereas the task service runs closely to its associated business process.

A JAX-WS service endpoint interface is generated for each service and imported into the project for the new task service. The JAX-WS wsimport tool is used to take the services’ WSDL definition files as input. In Example 10.4, the CustomerAccountInfo message entity returned by the retrieveCustomerAccountInfo operation is a POJO.

package com.novobank.services.task.creditapproval;

import java.util.List;
import com.novobank.services.entity.account.Account;
import com.novobank.services.entity.customer.Customer;

public class CustomerAccountInfo {
  private Customer customer;
  private List<Account> accounts;

  public Customer getCustomer() {
    return customer;
  }

  public void setCustomer(Customer customer) {
    this.customer = customer;
  }

  public List<Account> getAccounts() {
    return accounts;
  }

  public void setAccounts(List<Account> accounts) {
    this.accounts = accounts;
  }
}


Example 10.4

Since the service is implemented as a stateless session EJB, the team defines the remote interface in Example 10.5.

package com.novobank.services.task.creditapproval;
import javax.ejb.Remote;
@Remote
public interface ProcessCustomerAccountRemote {
  public CustomerAccountInfo retrieveCustomerAccountInfo(
    String firstName, String lastName);
  public void sendCustomerNotification(String firstName,
    String lastName, String content);
}


Example 10.5

The service implementation class shows how the two entity services for customer and account information and a utility service for sending e-mails to customers are reused. The implementation logic only contains business logic and delegates all other logic to the aggregated services that do not exhibit any dependencies on the underlying infrastructure, existing applications, data models, or low-level Java APIs.

Two annotations, @WebService and @Stateless, are added to make this class available as a stateless session EJB and Web service. The implementation in Example 10.6 is handled automatically upon deployment of the resulting EAR file on the application server.

package com.novobank.services.task.creditapproval;

import java.util.List;
import javax.ejb.Stateless;
import javax.jws.WebService;

import com.novobank.services.entity.account.Account;
import com.novobank.services.entity.account.AccountService;
import
  com.novobank.services.entity.account.AccountServiceService;
import com.novobank.services.entity.customer.Customer;
import com.novobank.services.entity.customer.CustomerService;
import
  com.novobank.services.entity.customer.CustomerServiceService;
import com.novobank.services.utility.e-mail.E-mailService;
import
  com.novobank.services.utility.e-mail.E-mailServiceService;

@WebService
@Stateless
public class ProcessCustomerAccountService
  implements ProcessCustomerAccountRemote {
  public CustomerAccountInfo retrieveCustomerAccountInfo(
    String firstName, String lastName) {

    CustomerAccountInfo info = new CustomerAccountInfo();

    // retrieve customer info
    CustomerService cs =
      new CustomerServiceService()
      .getCustomerServicePort();
    Customer customer =
      cs.getCustomerByName(firstName, lastName);
    info.setCustomer(customer);

    // retrieve account info
    AccountService as =
      new AccountServiceService()
      .getAccountServicePort();
    List<Account> accounts =
      as.findAccountByCustomer(
    customer.getCustomerId());
    info.setAccounts(accounts);

    return info;
}

public void sendCustomerNotification(String firstName,
  String lastName, String content) {
  // covered in Chapter 11
  }
}


Example 10.6

Some of the class names used end with ServiceService, which is the naming convention used in this particular service definition. The preceding examples used names ending in Service in the WSDL portType element. For example, the portType name in the Customer entity service is CustomerService. The associated WSDL service element containing the endpoint address for the service is therefore called CustomerServiceService.

The generated JAX-WS classes are named accordingly, such as the service interface as CustomerService and the client proxy implementation as CustomerServiceService. The resulting name in the service interface can be changed by naming the portType and service elements in the WSDL definition differently.


Task Services with REST

Modeling any one of the entity services composed by a task service as a resource may not useful. As discussed in Chapter 9, an aggregate resource can be unsuitable for handling updates across multiple entities. In this case, the solution is to model the action itself as a controller resource. The controller hides the complex coordination details of the task service, gathers the response from multiple actions, and returns the results of the execution to the caller. However, two issues arise when modeling the action as a REST controller resource:

• Multiple entities could be created or updated from a task service. How can the results be communicated in a REST-based way back to the service consumer?

• Depending on the results of the processing, a number of different subsequent actions may be performed by the service consumer. How can the service consumer be guided through the appropriate sequence of subsequent actions?

The following case study example for SmartCredit’s Credit Application Processing service illustrates methods to resolve the issues involved in modeling the action itself as a controller resource.


Case Study Example

SmartCredit wants to offer its partners and customers the ability to submit credit applications for different products, such as credit cards and lines of credit. Once an application for credit is submitted, a complex business process that can take days to complete is executed. From the applicant’s standpoint, the following functionalities must be supported:

• The caller of the service should be able to check the status of the application at any time.

• The service consumer should have the flexibility to cancel the application until a certain stage in the application process has been reached.

• Once a credit application has been approved, the process must return information about the resulting accounts.

SmartCredit has already built a number of REST services that are mostly entity services. SmartCredit now wants to build a REST service for the credit application process that must coordinate various activities between entities, such as Customers, Credit Applications, and Accounts. Designed as a task service, the Credit Application service must compose utility services to perform generic processing, such as checking the credit scores of applicants. However, not all of the complex coordination details of the business process can be exposed to the service caller.

The team decides to model the controller as a resource for this task service, which will carry out the necessary sequence of steps and hide the complex back-end service invocation sequence from the service consumer. Since this is a complex operation involving creation and updates to multiple resources that are neither safe nor idempotent, the team decides to model the service as a POST operation on a submitcreditapp controller resource, as seen in Example 10.7.

POST /submitcreditapp HTTP/1.1
Content-Type: application/xml

<submitcreditapp>
  <customer>
    <ssn>xxx-xxx-xxxx</ssn>
    <name>John Doe</name>
    <dob>12121979</dob>
    <address>...</address>
    <!-- other customer details like assets, loans etc....-->
  </customer>
  <application>
    <productcode>CC</productcode>
    <credit>5000</credit>
    <!-- other application details ...-->
  </application>
  <!--misc...-->
</submitcreditapp>


Example 10.7

The amount of information needed for a typical credit application is much more than what is presented, and modeling the response resources presents further challenges. An application entity must be created in the system once an application is submitted, which means that the application itself must be modeled as a resource. The service consumers must also be able to enquire about the status of the application and have a way to cancel a pending application.

The SmartCredit team determines that the application not only has resource state, such as customers, credit application, and accounts, but moves through a state lifecycle. The application moves from a state of not-created to pending to approved or canceled, depending on the actions taken at various points. To handle the transition management, the SmartCredit team considers modeling these services as resources at the beginning, and captures the resource addresses, operations, and representation formats in an informal service contract handed to the service consumers beforehand.

Such a contract would include:

POSThttp://smartcredit.com/submitcreditapp POST to the submitcreditapp resource to submit a new credit application

GEThttp://smartcredit.com/creditapp/{id} the creditapp is a resource, check for status information

PUThttp://smartcredit.com/creditapp/{id} PUT to the creditapp resource with a canceled status

The resource design requires up-front knowledge that must be communicated to the clients. Mid-step changes in the application, such as canceling a pending application, would change the resource URI, break the client, and result in the need to modify the service consumer to coordinate with the new cancelation service interface. Modeling the design on the Web resolves many of these issues.

On the Web, users start with a hyperlink and navigate through other linked hypermedia content to carry out additional functions, such as checking on or canceling an order status. Knowledge about the links becomes available as the user navigates through different application states. The SmartCredit team realizes that the hypermedia constraint can help model the necessary system-system interaction.

Satisfying the hypermedia constraint is vital to maintaining loose coupling between the service consumer and service so that they can evolve independently. Embedding links that the service consumer can discover and use reduces the up-front information the service consumer requires and helps transition the application through different stages in its lifecycle. A response to the POST request for the submitcreditapp resource in accordance with the hypermedia constraint is provided in Example 10.8.

Response Message

HTTP/1.1 201 Created
Location: http://smartcredit.com/creditapp/xw5f45892
Content-Length: 0


Example 10.8

Example 10.8 indicates a credit application was created on behalf of the customer, and the newly created credit application resource can be accessed at the URI pointed to by the HTTP Location header. In Example 10.9, the service consumer can access the URI to retrieve a representation of the credit application.

Request Message

GET /creditapp/xw5f45892 HTTP/1.1
Host: smartcredit.com

Response Message

HTTP/1.1 200 OK
Content-Type:application/xml

Link

http://smartcredit.com/creditapp/xw5f45892/cancel";rel=cancel
<application id="xw5f45892"
  xmlns:atom="http://www.w3.org/Atom/2005">
  <atom:link rel="self" type="application/xml"
    href="smartcredit.com/creditapp/xw5f45892"/>
  <status>
    <code>01</code>
    <message>Pending</message>
  </status>
    ...
</application>


Example 10.9

Example 10.9 includes a structural link for the application itself (described via the standard self atom link relation) and a transitional link for canceling an application (described via an HTTP link header with the value cancel). As long as the service consumer is aware of the semantics of the cancel link relation, the appropriate resource can be discovered for further actions, such as cancelation of the credit application. The information that service consumers require about operations supported by the resource at the indicated URI can be part of the documented semantics associated with the atom link. Alternatively, the service consumer can submit an OPTIONS operation to the URI to discover that the supported resource operations are PUT and POST.

As opposed to pre-publishing the resources and their associated URIs, modeling on the Web allows for dynamic discovery of the resource address. The associated operations reduced the coupling between the service and service consumer to ensure that the service can evolve the service without adversely affecting the service consumer. If an application is approved, a response containing the application resource representation could look like Example 10.10.

Response Message

HTTP/1.1 200 OK
Content-Type:application/xml

<application id="xw5f45892"
  xmlns:atom="http://www.w3.org/Atom/2005">
  ...
  <status>
    <code>00</code>
    <message>Approved</message>
    <atom:link rel="related" type="application/xml"
      href=http://smartcredit.com/accounts/zk367/>
  </status>
  ...
</application>


Example 10.10

The response indicates the application was approved, and the application resource embeds an atom link pointing to a related account resource created. The service consumer can submit a GET request to the account resource URI and obtain details about the new account as seen in Example 10.11. Note that the atom link relation type has a value of related, which is a standardized atom link relation that indicates the linked resource is related to the containing resource.

Request Message

GET /accounts/zk367 HTTP/1.1
Host:smartcredit.com

Response Message

HTTP/1.1 200 OK
Content-Type:application/xml

<account id="zk367" xmlns:atom="http://www.w3.org/Atom/2005">
  <type>CC</type>
  <limit>2000</limit>
  ...
</account>


Example 10.11

When canceling an application, the service consumer can perform an empty POST at the href URI associated with the cancel link relation. The server returns a new representation of the creditapplication resource, now canceled in Example 10.12.

Request Message

POST creditapp/xw5f45892/cancel HTTP/1.1
Host: smartcredit.com
Response Message
HTTP/1.1 200 OK
Content-Type: application/xml
<application id="xw5f45892" xmlns:atom="http://www.w3.org/
  Atom/2005">
  ...
  <status>
    <code>03</code>
    <message>Canceled</message>
  </status>
  ...
</application>


Example 10.12

Properly applying the hypermedia constraint can allow the service consumer to drive an application through various resource state transitions by pointing to new resources and associated URIs through embedded links. In Java, a combination of the structural link and transitional link constructs can implement the hypermedia constraint with JAX-RS.

The controller resource, SubmitCreditAppResource class, accepts a credit application and returns an HTTP 201 response with the HTTP Location header pointing to the newly created creditapp resource. Example 10.13 captures the SubmitCreditAppResource class implementation.

package com.smartcredit.credit.services;
  ...
@Path("/submitcreditapp)
public class SubmitCreditAppResource {

  @POST
  @Consumes("application/xml")
  public Response submitCreditApp(@Context UriInfo uri, String
    creditapp) {
    CreditApp app = createCredtitApp(creditapp);
    URI baseURI = uri.getBaseURI();
    return Response.created( URI.create(baseURI+app.getID()).
      build();
  }
}

Response Message

HTTP/1.1 201 Created
Location:http://smartcredit.com/creditapp/xw5f45892


Example 10.13

When the client makes a GET request to the URI for the new creditapp, the request is handled by the CreditAppResource class as seen in Example 10.14.

package com.smartcredit.credit.services;
    ...
@Path("/creditapp")
public class CreditAppResource {

  @GET
  @Path("{id})
  @Produces("application/xml")
  public Response getCreditApp(@Context UriInfo uri,
    @PathParam("id") int id) {

      //Get base request uri
      URI baseUri = uri.getBaseURI();
      URI requestUri = uri.getRequestURI();
      // Get application information
      CreditApp capp = getCreditApp(id);

      //Start writing a raw xml
      StringWriter sw = new StringWriter();
      XMLStreamWriter wr =
        XMLOutputFactory.newInstance()
        .createXMLStreamWriter(sw);
      wr.writeStartDocument();
      wr.writeStartElement("application");

      //Write out an Atom link for self
      wr.writeStartElement("Atom", "link",
        "http://www.w3.org/Atom/2005");
      wr.writeAttribute("rel", "self");
      wr.writeAttribute("href", requestURI.toASCIIString());
      wr.writeEndElement();

      //...write out rest of the core application data
      writeElementBody(capp);
      wr.writeEndElement();
      //End writing application information
      wr.writeEndDocument();

      // If application can be canceled write out an HTTP
      // relation link for the cancel resource

      if(canCancelApp(capp))
        return Response.ok(sw.toString()).link(requestURI.
        toASCIIString()+"/cancel", "cancel").build();
    //otherwise, return the accumulated string as xml
    // without any HTTP links...
    return Response.ok(sw.toString()).build();
  }
}


Example 10.14

The CreditAppResource class performs a check to determine if the application can be canceled, and if so, embeds an HTTP link relation for the application cancelation.


Testing Considerations

Unit testing for task services is challenging because of the number of dependencies they have on other services. As a result, testing the task service implementation can require a relatively large environment. To reduce task service dependency on other services which may or may not be available at the time of implementation, consider generating local or remote stubs used in place of the actual services invoked.

The service interface definition can be a WSDL document tooled to generate a Java interface for stubs. A simple Java class that implements this interface and returns some meaningful hardcoded data can be developed. In the service logic, an instance of a proxy to the target service is often retrieved via the standard JAX-WS mechanisms by utilizing the generated javax.xml.ws.Service instance class.

For unit testing purposes, the call can be temporarily removed to the getPort() method to retrieve a reference to the service proxy. The local implementation that can be locally invoked can instead be used to prevent creation and parsing of XML and cross-process invocations. This allows developers to focus on the actual business logic within the service implementation for testing purposes.


Case Study Example

To test the Process Customer Account service without affecting the Customer and Account services, locally invoked stubs are created to implement the appropriate service interface and return meaningful, hardcoded data. Example 10.15 is a listing of a test stub version of the AccountService.

package com.novobank.services.entity.account.test;

import java.util.LinkedList;
import java.util.List;
import com.novobank.services.entity.account.Account;
import com.novobank.services.entity.account.AccountService;

public class AccountServiceTest implements AccountService {

  public List<Account> findAccountByCustomer(String arg0) {
    Account a1 = new Account();
    a1.setAccountId("12345-0");
    a1.setAccountType("Savings");
    a1.setBalance(2500.0);

    Account a2 = new Account();
    a2.setAccountId("12345-1");
    a2.setAccountType("Checking");
    a2.setBalance(894.30);

    List<Account> list = new LinkedList<Account>();
    list.add(a1);
    list.add(a2);

    return list;
  }
}


Example 10.15

The implementation of the Process Customer Account service is temporarily changed. In Example 10.16, the retrieval of the proxy instance is replaced with an instance of the test stub AccountServiceTest to reduce dependencies on other services during testing.

...
// retrieve account info
// AccountService as = new AccountServiceService().
  getAccountServicePort();
AccountService as = new AccountServiceTest();
// all other code remains unchanged
List<Account> accounts =
  as.findAccountByCustomer(customer.getCustomerId());
info.setAccounts(accounts);
...


Example 10.16


The implementations of composed services can be wrapped into a Web service layer but deployed on a server similar to the one where the task service logic is tested. The JAX-WS code can be reinstated for retrieving a reference to the service proxy class but set to a URL of the locally deployed service, thereby avoiding dependencies on existing deployments of target services and allowing the code to be tested for remote invocation of the composed services through the JAX-WS layer.

In the integration testing phase, all instances of invoking test stub services can be replaced by invocations of the target services. Chapter 11 details how to manage endpoint addresses of composed services across development phases. For testing a REST task service, as with any other REST service, any HTTP library can be used for making the appropriate HTTP requests. For example, the curl utility can be used to issue a GET request for a credit application, as seen in Example 10.17.

curl -H "Accept: application/xml" -X GET
"http://smartcredit.com/creditapp/xw5f45892"


Example 10.17

Packaging Considerations

Task services have no specific packaging considerations beyond the resolution of dependencies towards composed services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset