7.5 Service Composability
The composability of a service is an implicit byproduct of the extent to which the other service-orientation design principles have been successfully applied. The ability to compose services into new, higher-level services is an inherent, logical consequence if the other design principles outlined in this chapter are followed. For example, a service contract that is standardized allows interaction and composition between services implemented in different languages using different runtime environments. Decoupling a service implementation from its contract allows the same logic to be reused in other compositions.
With regards to the implications that service composability has for the service contract, this section highlights some of the issues and requirements for the runtime environment in which the services run. See Chapter 11 for further exploration of service composition with Java.
Runtime Environment Efficiency
A highly efficient and robust runtime environment is required for service compositions. The ability for services to participate in multiple compositions places severe challenges on runtime environments and must be taken into account when designing a service inventory.
If a service is used by multiple compositions, applying different non-functional characteristics can be necessary. One composition rarely invokes a service with no particular requirement for fast response times, whereas another composition using the same service can require support for high transaction loads with short response times. Similarly, one composition can require a reliable connection between the composition controller and its members, whereas another can be tolerant of lost messages. Applying different QoS definitions to the same service, depending on the composition, must be possible without requiring code changes in the service logic itself.
Java, as a programming language, has no built-in features that help or hinder the runtime environment. In most cases, Java-based services are hosted in a Java EE-compliant application server or an alleged Web server environment, which is Java SE-compliant. In either case, the runtime environments provide advantages over other traditional server platforms in terms of composability.
Java EE defines the management of solution logic according to the roles people perform, such as component provider, deployer, and assembler. As a result, much of the information on the execution of solution logic at runtime is not hardcoded into the logic itself but is instead stored in declarative configuration files, called deployment descriptors. The same piece of Java code can be used differently with different deployment descriptors and changed at runtime without requiring recompilation of the code. Despite being undefined by the Java EE standard, most Java EE application servers support defining individual server instances with specific runtime definitions. One instance usually runs in one process with its own configuration for elements like thread pools or memory heap sizes. In many cases, instances can be clustered to provide one logical application server across multiple physical processes or machines.
Separating runtime information about a component from its actual logic is possible because the Java EE application server runs as a container. This means that all incoming and outgoing data is intercepted and processed by the application server based on the current configuration. For example, if a certain piece of Java logic can only run within the scope of a transaction, the container can be configured to ensure a transaction is present whenever this piece of logic is invoked.
The same approaches to the runtime environment apply to Web services on two levels:
- The runtime environment that processes an incoming message, such as a SOAP or REST request message, can perform several tasks before forwarding the request to the actual service. These tasks include the ability to decrypt data sent in an encrypted form, establish handshaking between service consumer and service by returning acknowledgement that the message has been received, interpret information in the request message header indicating that the invocation of the service must be part of a distributed transaction, and convert incoming XML data into Java objects.
- For SOAP-based Web services, JAX-RPC and JAX-WS provide a mechanism and an API that allow the insertion of custom logic to parse, interpret, or change incoming and outgoing messages. The custom logic runs inside a handler. For JAX-RS-based implementations of REST services, such interception logic can be implemented by developers in special entity handlers known as entity providers. The JAX-RS 2.0 release provides added filters and interceptors otherwise not included in previous versions.
Both approaches are similar regardless of the Web services used. One is controlled by the user of the system, whereas the other is implicitly included with the runtime. Reading and manipulating incoming and outgoing messages separate from the service logic is crucial to supporting the Service Composability principle. Composing hosted services, including both composition members and composition controllers, is supported by the concept of containers and deployment descriptors and enhanced by SOAP-based Web services, such as handlers.
Ultimately, implementing a highly efficient runtime allows the developer to focus on the business logic of the actual service implementation, leaving everything else to the underlying Java platform. More advanced technologies, such as the SCA, expand on this concept by separating core business logic implementation further away from aspects of a service component, such as the protocol bindings used to interact with service consumers and other services used to fulfill functionality.
Service Contract Flexibility
Service contracts can be designed to increase the ability of a service for multiple compositions. Generally, multiple compositions are only applicable to the composition members for increasing the reusability of a service in different business contexts or business tasks. A service contract can be rewritten to enable reuse of a service without changing the core functionality of the service.
To write a flexible service contract that is reusable, recall the following approaches:
- Use generic data types or supertypes instead of concrete subtypes. If an enterprise deals with both commercial customers (CommercialCustomer) and personal customers (PersonalCustomer), evaluate whether a common supertype can be established for both (Customer) to be used in the service contract. JAXB supports polymorphism to ensure that the appropriate Java object is created when a message containing a subtype is received.
- Decouple the service contract from its underlying runtime platform by hiding details about QoS characteristics, which can change over time and will vary depending on service consumer requirements and how a service is deployed.
- Decouple the service implementation from its contract by utilizing generic APIs, such as the JAX-WS Provider API for SOAP-based Web services. For REST services, deal with generic Java types in resource methods, such as String, byte[], and InputStream. Note that generated generic service consumer or service logic results in additional code that must be developed and tested.
Standards-Based Runtime
Composition members and controllers benefit from a runtime environment that supports a wide range of accepted standards. Composing services means the services interact and interoperate. Interoperability of services is supported by a runtime environment upheld by relevant standards, such as the WS-I. Java’s APIs for SOAP-based Web services, JAX-RPC and JAX-WS, require support for the WS-I Basic Profile, which allow services to be designed with a high degree of interoperability. REST services achieve full interoperability inherently through HTTP.
Advanced standards relevant in a composition of services include the WS-Security standards for which a WS-I profile also exists, the WS-Transaction and related standards, WS-Addressing, and WS-ReliableMessaging, which supports the reliable exchange of messages between services. These advanced standards are combined in another WS-I profile known as the Reliable Secure profile.