Chapter 4. Loosely-coupling Services
In the previous chapter, we explored how we can take functionality in our existing applications and expose them as services. When we do this, we often find that the service interface we create is tightly coupled to the underlying implementation. We can make our architecture more robust by reducing this coupling. By defining our interface around our architecture, rather than around our existing application interfaces, we can reduce coupling. We can also reduce coupling by using a routing service to avoid physical location dependencies. In this chapter, we will explore how service virtualization through the Mediator and the Service Bus of the Oracle SOA Suite can be used to deliver more loosely-coupled services. Loose coupling reduces the impact of change on our systems, allowing us to deploy new functions more rapidly into the market. Loose coupling also reduces the maintenance costs associated with our deployments.
Coupling
Coupling is a measure of how dependent one service is upon another. The more closely one service depends on another service, the more tightly coupled they are. There have been a number of efforts to formalize metrics for coupling, and they all revolve around the same basic items:
- Number of input data items: Basically, the number of input parameters of the service.
- Number of output data items: The output data of the service.
- Dependencies on other services: The number of services called by this service.
- Dependencies of other services on this service: The number of services that invoke this service.
- Use of shared global data: The number of shared data items used by this service. This may include database tables or shared files.
- Temporal dependencies: Dependencies on other services being available at specific times.
Let us examine how each of these measures may be applied to our service interface. The principles below are relevant to all services, but widely re-used services have a special need for all of the items.
Number of input data items
A service should only accept as input the data items required to perform the service being requested. Additional information should not be passed into the service because this creates unnecessary dependencies on the input formats. This economy of input allows the service to focus just on the function it is intended to provide and does not require it to understand unnecessary data formats. The best way to isolate the service from changes in data formats that it does not use is to make the service unaware of those data formats.
For example, a credit rating service should only require sufficient information to identify the individual being rated. Additional information, such as the amount of credit being requested or the type of goods or services for which a loan is required, is not necessary for the credit rating service to perform its job.
Tip
Services should accept only the data required to perform their function and nothing more.
When talking about reducing the number of data items input or output from a service, we are talking about the service implementation, not a logical service interface that may be implemented using a canonical data model. The canonical data model may have additional attributes not required by a particular service, but these should not be part of the physical service interface. Adding attributes to "make a service more universal" only serves to make it harder to maintain.
Number of output data items
In the same way that a service should not accept inputs that are unnecessary for the function it performs, a service should not return data that is related only to its internal operation. Exposing such data as part of the response data will create dependencies on the internal implementation of the service that are not necessary.
Sometimes, a service needs to maintain its state between requests. State implies that state information must be maintained at least in the client of the service, so that it can identify the state required by the service when making further requests. However, the state information in the client is often just an index into the state information held in the service. We will return to this subject later in the chapter.
Tip
Services should not return public data that relates to their own internal processing.
Dependencies on other services
Generally, re-use of other services to create a new composite service is a good thing. However, having dependencies on other services does increase the degree of coupling because there is a risk that changes in those services may impact the composite service, and consequently, any services with dependencies on the composite service. We can reduce the risk that this poses by limiting our use of functionality in other services to just that required by the composite.
Tip
Services should reduce the functionality required of other services to the minimum required for their own functionality.
For example, a dispatching service may decide to validate the address it receives. If this functionality is not specified as being required, because, let's say, all addresses are validated elsewhere, then the dispatching service has an unnecessary dependency that may cause problems in the future.
Dependencies of other services on this service
Having a widely used service is great for re-use, but the greater the number of services that make use of this service, the greater impact a change in this service will have on other services. Extra care must be taken with widely re-used services to ensure that their interfaces are as stable as possible. This stability can be provided by following the guidelines in this section.
Tip
Widely re-used services should focus their interface on just the functionality needed by clients and avoid exposing any unnecessary functions or data.
Use of shared global data
Shared global data in the service context is often through dependencies on a shared resource such as data in a database. Such use of shared data structures is subversive to good design because it does not appear in the service definitions and so the owners of the shared data may be unaware of the dependency. Effectively, this is an extra interface into the service. If this is not documented, then the service is very vulnerable to the shared data structure being changed unknowingly. Even if the shared data structure is well documented, any changes required must still be synchronized across all users of the shared data.
Note
Avoid the use of shared global data in services unless it is absolutely necessary. If it is absolutely necessary, then the dependency needs to be clearly documented in all users of the shared data. A service's data should only be manipulated through its defined interface. Consider creating a wrapper service around the shared data.
Temporal dependencies
Not all service requests require an immediate response. Often a service can be requested, and the response may be returned later. This is a common model in message-based systems and allows for individual services to be unavailable without impacting other services. Use of queuing systems allows temporal or time decoupling of services, so that two communicating services do not have to be available at the same instant in time. The queue allows messages to be delivered when the service is available rather than when the service is requested.
Tip
Use asynchronous message interfaces to reduce dependencies of one service on the availability of another.
Reducing coupling in stateful services
A stateful service maintains context for a given client between invocations. When using stateful services, we always need to return some kind of state information to the client. To avoid unnecessary coupling, this state information should always be opaque. By opaque, we mean that it should have no meaning to the client other than as a reference that must be returned to the service when requesting follow on operations. For example, a numeric session ID has no meaning to the client and may be used by the service as an index into a table stored either in memory or in a database table. We will examine how this may be accomplished later in this section.
A common use of state information in a service is to preserve the position in a search that returns more results than can reasonably be returned in a single response. Another use of state information might be to perform correlation between services that have multiple interactions, such as between a bidding service and a bidding client.
Whatever the reason may be, the first task, when confronted with the need for state in a service, is to investigate the ways to remove the state requirement. If there is definitely a need for state to be maintained, then there are two approaches that the service can follow.
- Externalize all state and return it to the client.
- Maintain state within the service and return a reference to the client.
In the first case, it is necessary to package up the required state information and return it to the client. Because the client should be unaware of the format of this data, it must be returned as an opaque type. This is best done as an <any>
element in the schema for returning the response to the client. An <any>
element may be used to hold any type of data, from simple strings through to complex structured types.
For example, if a listing service returns only 20 items at a time, then it must pass back sufficient information to enable it to retrieve the next 20 items in the query.
In the following XML Schema example, we have the XML data definitions to support two operations on a listing service:
searchItems
nextItems
The searchItems
operation will take a
searchItemsRequest
element for input and return a searchItemsResponse
element. The searchItemsResponse
has within it a searchState
element. This element is a sequence that has an unlimited number of arbitrary elements. This can be used by the service to store sufficient state to allow it to deliver the next 20 items in the response. It is important to realize that this state does not have to be understood by the client of the service. The client of the service just has to copy the searchState
element to the continueSearchItemsRequest
element to retrieve the next set of 20 results.
The preceding approach has the advantage that the service may still be stateless, although it gives the appearance of being stateful. The sample schema below could be used to allow the service to resume the search where it left off without the need for any internal state information in the service. By storing the state information (the original request and the index of the next item to be returned) within the response, the service can retrieve the next set of items without having to maintain any state within itself. Obviously, the service for purposes of efficiency could maintain some internal state, such as a database cursor, for a period of time, but this is not necessary.
An alternative approach to state management is to keep the state information within the service itself. This still requires some state information to be returned to the client, but only a reference to the internal state information is required. In this case, there are a couple of options for dealing with this reference.
One is to take state management outside of the request/response messages and make it part of the wider service contract, either through the use of WS-Correlation or an HTTP cookie for example. This approach has the advantage that the service can generally take advantage of state management functions of the platform, such as support for Java services, to use the HTTP session state.
Note
Use of WS-Correlation
It is possible to use a standard correlation mechanism such as WS-Correlation. This is used within SOA Suite by BPEL to correlate process instances with requests. If this approach is used, however, it precludes the use of the externalized state approach discussed earlier. This makes it harder to swap out your service implementation with one that externalizes all its state information. In addition to requiring your service to always internalize state management, no matter how it is implemented, your clients must now support WS-Correlation.
The alternative is to continue to keep the state management in the request/response messages and deal with it within the service. This keeps the client unaware of how the state is managed because the interface is exactly the same for a service that maintains internal state and a service that externalizes all states. A sample schema for this is shown below. Note that unlike the previous schema, there is only a service-specific reference to its own internal state. The service is responsible for maintaining all the required information internally and using the externalized reference to locate this state information.
The Oracle Service Bus ( OSB) in SOA Suite enables us to hide a services native state management and expose it as an abstract state management that is less tightly coupled to the way state is physically handled by the service.
Some web service implementations allow for stateful web services, with state managed in a variety of proprietary fashions.
We want to use native state management when we internalize session state because it is easier to manage. The container will do the work for us using mechanisms native to the container. However, this means the client has to be aware that we are using native state management because the client must make use of these mechanisms. We want the client to be unaware of whether the service uses native state management, its own custom state lookup mechanism, or externalizes all session state into the messages flowing between the client and the service. The latter two can look the same to the client and hence make it possible to switch services with different approaches. However, the native state management explicitly requires the client to be aware of how state is managed.
To avoid this coupling, we can use the OSB or Mediator to wrap the native state management services, as shown in the following diagram. The client passes a session state element of unknown contents back to the service façade, which is provided by the OSB or Mediator. The OSB or Mediator then removes the session state element and maps it onto the native state management used by the service, such as placing the value into a session cookie. Thus we have the benefits of using native state management without the need for coupling the client to a particular implementation of the service. For example, a service may use cookies to manage session state, and by having the OSB or Mediator move the cookie value to a field in the message, we avoid clients of the service having to deal with the specifics of the services state management.
Service abstraction tools in SOA Suite
Earlier versions of the SOA Suite had the Oracle Enterprise Service Bus. This component has become the Mediator in 11g. In Chapter 2, Writing your First Composite, we introduced the Mediator component of an SCA Assembly. This provides basic routing and transformation abilities. The SOA Suite also includes the Oracle Service Bus. The Oracle Service Bus can also be used for routing and transformation but provides a much richer environment than the Mediator for service abstraction. At first glance, it is not clear whether to use the Oracle Service Bus or the Mediator to perform service abstraction. In this section, we will examine the pros and cons of using each and give some guidance on when to use one and when to use the other.
Do you have a choice?
The Oracle Service Bus currently only runs on the WebLogic platform. The rest of the SOA Suite has been designed to run on multiple platforms such as WebSphere and JBoss. If you need to run on these other platforms then, until OSB becomes multi-platform, you have no choice but to use the Mediator.
When to use the Mediator
Because the Mediator runs within an SCA Assembly, it has the most efficient bindings to other SCA Assembly components, specifically the BPEL engine. This lets us focus on using the Mediator to provide service virtualization services within SCA assemblies. The Mediator enables the virtualization of inputs and outputs within an SCA Assembly. This leads us to four key uses of the Mediator within SCA.
- Routing between components in an SCA Assembly
- Validation of incoming messages into an SCA Assembly
- Transformation of data from one format to another within an SCA Assembly
- Filtering to allow selection of components to invoke based on message content
The Mediator is an integral part of SCA Assemblies and should be used to adapt SCA Assembly formats to the canonical message formats, which we will talk about later in this chapter.
When to use Oracle Service Bus
The Oracle Service Bus runs in a separate JVM to the other SOA Suite components and so there is a cost associated with invoking SOA Suite components in terms of additional inter-process communication and hence time. However, the OSB has some very powerful capabilities that make it well suited to be the enterprise strength Service Bus for a more general enterprise-wide virtualisation role. As it is separate from the other components, it is easy to deploy separately and use as an independent Service Bus.
The Service Bus can be used to virtualize external services, where external may mean outside the company but also includes non-SCA services. OSB makes it very easy for operators to modify service endpoint details at runtime, making it very flexible in managing change.
The Service Bus goes beyond routing and transformation by providing the ability to throttle services, restricting the number of invocations they receive. This can be valuable in enforcing client contracts and ensuring that services are not swamped by more requests than they can handle.
Tip
What should I use to virtualize my services?
Service virtualization within an SCA Assembly is the job of the Mediator. The Mediator should be used to ensure that the SCA Assembly always presents a canonical interface to clients and services. Service virtualization of non-SCA components should be done with the Oracle Service Bus. Oracle Service Bus may also be used to transparently enforce throughput restriction on services.
Do you have a choice?
The Oracle Service Bus currently only runs on the WebLogic platform. The rest of the SOA Suite has been designed to run on multiple platforms such as WebSphere and JBoss. If you need to run on these other platforms then, until OSB becomes multi-platform, you have no choice but to use the Mediator.
When to use the Mediator
Because the Mediator runs within an SCA Assembly, it has the most efficient bindings to other SCA Assembly components, specifically the BPEL engine. This lets us focus on using the Mediator to provide service virtualization services within SCA assemblies. The Mediator enables the virtualization of inputs and outputs within an SCA Assembly. This leads us to four key uses of the Mediator within SCA.
- Routing between components in an SCA Assembly
- Validation of incoming messages into an SCA Assembly
- Transformation of data from one format to another within an SCA Assembly
- Filtering to allow selection of components to invoke based on message content
The Mediator is an integral part of SCA Assemblies and should be used to adapt SCA Assembly formats to the canonical message formats, which we will talk about later in this chapter.
When to use Oracle Service Bus
The Oracle Service Bus runs in a separate JVM to the other SOA Suite components and so there is a cost associated with invoking SOA Suite components in terms of additional inter-process communication and hence time. However, the OSB has some very powerful capabilities that make it well suited to be the enterprise strength Service Bus for a more general enterprise-wide virtualisation role. As it is separate from the other components, it is easy to deploy separately and use as an independent Service Bus.
The Service Bus can be used to virtualize external services, where external may mean outside the company but also includes non-SCA services. OSB makes it very easy for operators to modify service endpoint details at runtime, making it very flexible in managing change.
The Service Bus goes beyond routing and transformation by providing the ability to throttle services, restricting the number of invocations they receive. This can be valuable in enforcing client contracts and ensuring that services are not swamped by more requests than they can handle.
Tip
What should I use to virtualize my services?
Service virtualization within an SCA Assembly is the job of the Mediator. The Mediator should be used to ensure that the SCA Assembly always presents a canonical interface to clients and services. Service virtualization of non-SCA components should be done with the Oracle Service Bus. Oracle Service Bus may also be used to transparently enforce throughput restriction on services.
When to use the Mediator
Because the Mediator runs within an SCA Assembly, it has the most efficient bindings to other SCA Assembly components, specifically the BPEL engine. This lets us focus on using the Mediator to provide service virtualization services within SCA assemblies. The Mediator enables the virtualization of inputs and outputs within an SCA Assembly. This leads us to four key uses of the Mediator within SCA.
- Routing between components in an SCA Assembly
- Validation of incoming messages into an SCA Assembly
- Transformation of data from one format to another within an SCA Assembly
- Filtering to allow selection of components to invoke based on message content
The Mediator is an integral part of SCA Assemblies and should be used to adapt SCA Assembly formats to the canonical message formats, which we will talk about later in this chapter.
When to use Oracle Service Bus
The Oracle Service Bus runs in a separate JVM to the other SOA Suite components and so there is a cost associated with invoking SOA Suite components in terms of additional inter-process communication and hence time. However, the OSB has some very powerful capabilities that make it well suited to be the enterprise strength Service Bus for a more general enterprise-wide virtualisation role. As it is separate from the other components, it is easy to deploy separately and use as an independent Service Bus.
The Service Bus can be used to virtualize external services, where external may mean outside the company but also includes non-SCA services. OSB makes it very easy for operators to modify service endpoint details at runtime, making it very flexible in managing change.
The Service Bus goes beyond routing and transformation by providing the ability to throttle services, restricting the number of invocations they receive. This can be valuable in enforcing client contracts and ensuring that services are not swamped by more requests than they can handle.
Tip
What should I use to virtualize my services?
Service virtualization within an SCA Assembly is the job of the Mediator. The Mediator should be used to ensure that the SCA Assembly always presents a canonical interface to clients and services. Service virtualization of non-SCA components should be done with the Oracle Service Bus. Oracle Service Bus may also be used to transparently enforce throughput restriction on services.
When to use Oracle Service Bus
The Oracle Service Bus runs in a separate JVM to the other SOA Suite components and so there is a cost associated with invoking SOA Suite components in terms of additional inter-process communication and hence time. However, the OSB has some very powerful capabilities that make it well suited to be the enterprise strength Service Bus for a more general enterprise-wide virtualisation role. As it is separate from the other components, it is easy to deploy separately and use as an independent Service Bus.
The Service Bus can be used to virtualize external services, where external may mean outside the company but also includes non-SCA services. OSB makes it very easy for operators to modify service endpoint details at runtime, making it very flexible in managing change.
The Service Bus goes beyond routing and transformation by providing the ability to throttle services, restricting the number of invocations they receive. This can be valuable in enforcing client contracts and ensuring that services are not swamped by more requests than they can handle.
Tip
What should I use to virtualize my services?
Service virtualization within an SCA Assembly is the job of the Mediator. The Mediator should be used to ensure that the SCA Assembly always presents a canonical interface to clients and services. Service virtualization of non-SCA components should be done with the Oracle Service Bus. Oracle Service Bus may also be used to transparently enforce throughput restriction on services.
Oracle Service Bus design tools
The Oracle Service Bus can be configured either using the Oracle Workshop for WebLogic or the Oracle Service Bus Console.
Oracle Workshop for WebLogic
Oracle Workshop for WebLogic provides tools for creating all the artifacts needed by the Oracle Service Bus. Based on Eclipse, it provides a rich design environment for building service routings and transformations for deployment to the Service Bus. In future releases, it is expected that all the Service Bus functionality in the Workshop for WebLogic will be provided in JDeveloper. The current versions of JDeveloper do not have support for Oracle Service Bus. Note that there is some duplication functionality between JDeveloper and Workshop for WebLogic. In some cases, such as WSDL generation, the functionality provided in the Workshop for WebLogic is superior to that provided by JDeveloper. In other cases, such as XSLT generation, the functionality provided by JDeveloper is superior.
Oracle Service Bus Console
In Chapter 2, Writing your First Composite,we introduced the Oracle Service Bus console and used it to build a proxy service that invoked an SCA Assembly.
Oracle Workshop for WebLogic
Oracle Workshop for WebLogic provides tools for creating all the artifacts needed by the Oracle Service Bus. Based on Eclipse, it provides a rich design environment for building service routings and transformations for deployment to the Service Bus. In future releases, it is expected that all the Service Bus functionality in the Workshop for WebLogic will be provided in JDeveloper. The current versions of JDeveloper do not have support for Oracle Service Bus. Note that there is some duplication functionality between JDeveloper and Workshop for WebLogic. In some cases, such as WSDL generation, the functionality provided in the Workshop for WebLogic is superior to that provided by JDeveloper. In other cases, such as XSLT generation, the functionality provided by JDeveloper is superior.
Oracle Service Bus Console
In Chapter 2, Writing your First Composite,we introduced the Oracle Service Bus console and used it to build a proxy service that invoked an SCA Assembly.
Oracle Service Bus Console
In Chapter 2, Writing your First Composite,we introduced the Oracle Service Bus console and used it to build a proxy service that invoked an SCA Assembly.
Service Bus overview
In this section, we will introduce the key features of the Oracle Service Bus and show how they can be used to support service virtualization.
Service Bus message flow
It is useful to examine how messages are processed by the Service Bus. Messages normally target an endpoint in the Service Bus known as a proxy service. Once received by the proxy service the message is processed through a series of input pipeline stages. These pipeline stages may enrich the data by calling out to other web services, or they may transform the message as well as providing logging and message validation. Finally, the message reaches a routing step where it is routed to a service known as a business service. The response, if any, from the service is then sent through the output pipeline stages, which may also enrich the response or transform it before returning a response to the invoker.
Note that there may be no pipeline stages and the router may make a choice between multiple endpoints. Finally, note that the business service is a reference to the target service, which may be hosted within the Service Bus or as a standalone service. The proxy service may be thought of as the external service interface and associated transforms required to make use of the actual business service.
Note
The proxy service should be the canonical interface to our service (see later in the chapter for an explanation of canonical interfaces). The Business Service is the physical implementation interface. The pipelines and routing step transform the request to and from canonical form.
Service Bus message flow
It is useful to examine how messages are processed by the Service Bus. Messages normally target an endpoint in the Service Bus known as a proxy service. Once received by the proxy service the message is processed through a series of input pipeline stages. These pipeline stages may enrich the data by calling out to other web services, or they may transform the message as well as providing logging and message validation. Finally, the message reaches a routing step where it is routed to a service known as a business service. The response, if any, from the service is then sent through the output pipeline stages, which may also enrich the response or transform it before returning a response to the invoker.
Note that there may be no pipeline stages and the router may make a choice between multiple endpoints. Finally, note that the business service is a reference to the target service, which may be hosted within the Service Bus or as a standalone service. The proxy service may be thought of as the external service interface and associated transforms required to make use of the actual business service.
Note
The proxy service should be the canonical interface to our service (see later in the chapter for an explanation of canonical interfaces). The Business Service is the physical implementation interface. The pipelines and routing step transform the request to and from canonical form.
Virtualizing service endpoints
To begin our exploration of the Oracle Service Bus, let us start by looking at how we can use it to virtualize service endpoints. By virtualizing a service endpoint, we mean that we can move the location of the service without affecting any of the services' dependants.
Moving service location
To virtualize the address of our service, we use the business service in the Service Bus. We covered creating a business service in Chapter 2, Writing your First Composite. Note that we are not limited to services described by WSDL. In addition to already defined business and proxy services, we can base our service on XML or messaging systems. The easiest to use is the WSDL web service.
Tip
Endpoint address considerations
When specifying endpoints in the Service Bus, it is generally not a good idea to use localhost or 127.0.0.1. Because the Service Bus definitions may be deployed across multiple nodes, there is no guarantee that business service will be co-located with the Service Bus on every node the Service Bus is deployed upon. Therefore, it is best to ensure that all endpoint addresses use virtual hostnames. Machines that are referenced by a virtual hostname should have that hostname in the local hosts file pointing to the loopback address (127.0.0.1
) to benefit from machine affinity.
When we selected the WSDL we wanted to use in Chapter 2, Writing your First Composite, we were taken to another dialog that introspects the WSDL, identifies any ports or bindings, and asks us for which one we wish to use. Bindings are mappings of the WSDL service onto a physical transport mechanism such as SOAP over HTTP. Ports are the mapping of the binding onto a physical endpoint such as a specific server.
Note that if we choose a port, we do not have to provide physical endpoint details later in the definition of the business service, although we may choose to do so. If we choose a binding because it doesn't include a physical endpoint address, we have to provide the physical endpoint details explicitly.
If we have chosen a binding, we can skip the physical endpoint details. If, however, we chose a port or we wish to change the physical service endpoint or add additional physical service endpoints, then we hit the Next>> button to allow us to configure the physical endpoints of the service.
This dialog allows us to do several important things:
- Modify the Protocol to support a variety of transports.
- Choose a Load Balancing Algorithm. If there is more than one endpoint URI, then the Service Bus will load balance across them according to this algorithm.
- Change, add, or remove Endpoint URI s or physical targets.
- Specify retry logic, specifically the Retry Count, the Retry Iteration Interval, and whether or not to Retry Application Errors (errors generated by the service called, not the transport).
Note
Note that the Service Bus gives us the ability to change, add, and remove physical endpoint URIs as well as change the protocol used at runtime. This allows us to change the target services without impacting any clients of the service, providing us with virtualization of our service location.
Using Adapters in Service Bus
The Service Bus can also use adapter definitions created in JDeveloper. To use an adapter from JDeveloper, we cannot directly import the WSDL, we need to import the artifacts in the following order:
- The XSD generated by the adapter using Select Resource Type | Interface | XML Schema
- The WSDL generated by the adapter using Select Resource Type | Interface | WSDL
- The JCA file generated by the adapter using Select Resource Type | Interface | JCA Binding
The WSDL can then be used as a business service. Make sure that references in the JCA file are configured in the WebLogic Server.
The proxy service provides the interface and adaption to our business service, typically joining them by a routing step, as we did in Chapter 2, Writing your First Composite. There are other types of actions besides routing flows. Clicking on Add an Action allows us to choose the type of Communication we want to add. Flow Control allows us to add If .. Then … logic to our routing decision. However, in most cases, the Communication items will provide all the flexibility we need in our routing decisions. This gives us three types of routing to apply:
- Dynamic Routing allows us to route to the result of an XQuery. This is useful if the endpoint address is part of the input message.
- Routing allows us to select a single static endpoint.
- Routing Table allows us to use an XQuery to route between several endpoints. This is useful when we want to route to different services, based on a particular attribute of the input message.
For simple service endpoint virtualization, we only require the Routing option.
Having selected a target endpoint, usually a business service, we can then configure how we use that endpoint. In the case of simple location virtualization, the proxy service and the business service endpoints are the same, and so we can just pass on the input message directly to the business service. Later on, we will look at how to transform data to allow virtualization of the service interface.
Selecting a service to call
We can further virtualize our endpoint by routing different requests to different services, based upon the values of the input message. For example, we may use one address lookup service for addresses in our own country and another service for all other addresses. In this case, we would use the routing table option on the add action to provide a list of possible service destinations.
The routing table enables us to have a number of different destinations, and the message will be routed based on the value of an expression. When using a routing table, all the services must be selected based on the same expression; the comparison operators may vary, but the actual value being tested against will always be the same. If this is not the case, then it may be better to use "if … then … else" routing. The routing table may be thought of as a "switch statement", and as with all switch statements, it is a good practice to add a default case.
In the routing table, we can create additional cases, each of which will have a test associated with it. Note that we can also add the default case.
We need to specify the expression to be used for testing against. Clicking on the <Expression> link takes us to the XQuery /XSLT Expression Editor. By selecting the Variable Structures tab and selecting a new structure, we can find the input body of the message, which lets us select the field we wish to use as the comparison expression in our routing table.
When selecting in the tab on the left of the screen, the appropriate XPath expression should appear in the Property Inspector window. We can the click on the XQuery Text area of the screen prior to clicking on the Copy Property to transfer the property XPath expression from the property inspector to the XQuery Text area. We then complete our selection of the expression by clicking on the Save button.
In the example, we are going to route our service based on the country of the address. In addition to the data in the body of the message, we could also route based on other information from the request. Alternatively, by using a message pipeline, we could base our lookup on data external to the request.
Once we have created an expression to use as the basis of comparison for routing, then we select an operator and a value to use for the actual routing comparison. In the following example, if the country value from the expression matches the string uk (include the quotes), then the LocalAddressLookup service will be invoked. Any other value will cause the default service to be invoked, as yet undefined in the following example:
Once the routing has been defined, then it can be saved, as shown in Chapter 2, Writing your First Composite.
Note that we have shown a very simple routing example. The Service Bus is capable of doing much more sophisticated routing decisions. A common pattern is to use a pipeline to enrich the inbound data and then route based on the inbound data. For example, a pricing proxy service may use the inbound pipeline to look up the status of a customer, adding that status to the data available as part of the request. The routing service could then route high value customers to one service and low value customers to another service, based on the looked up status. In this case, the routing is based on a derived value rather than on a value already available in the message.
In summary, a request can be routed to different references, based on the content of the request message. This allows messages to be routed based on geography or pecuniary value for example. This routing, because it takes place in the composite, is transparent to clients of the composite and so aids us in reducing coupling in the system.
Moving service location
To virtualize the address of our service, we use the business service in the Service Bus. We covered creating a business service in Chapter 2, Writing your First Composite. Note that we are not limited to services described by WSDL. In addition to already defined business and proxy services, we can base our service on XML or messaging systems. The easiest to use is the WSDL web service.
Tip
Endpoint address considerations
When specifying endpoints in the Service Bus, it is generally not a good idea to use localhost or 127.0.0.1. Because the Service Bus definitions may be deployed across multiple nodes, there is no guarantee that business service will be co-located with the Service Bus on every node the Service Bus is deployed upon. Therefore, it is best to ensure that all endpoint addresses use virtual hostnames. Machines that are referenced by a virtual hostname should have that hostname in the local hosts file pointing to the loopback address (127.0.0.1
) to benefit from machine affinity.
When we selected the WSDL we wanted to use in Chapter 2, Writing your First Composite, we were taken to another dialog that introspects the WSDL, identifies any ports or bindings, and asks us for which one we wish to use. Bindings are mappings of the WSDL service onto a physical transport mechanism such as SOAP over HTTP. Ports are the mapping of the binding onto a physical endpoint such as a specific server.
Note that if we choose a port, we do not have to provide physical endpoint details later in the definition of the business service, although we may choose to do so. If we choose a binding because it doesn't include a physical endpoint address, we have to provide the physical endpoint details explicitly.
If we have chosen a binding, we can skip the physical endpoint details. If, however, we chose a port or we wish to change the physical service endpoint or add additional physical service endpoints, then we hit the Next>> button to allow us to configure the physical endpoints of the service.
This dialog allows us to do several important things:
- Modify the Protocol to support a variety of transports.
- Choose a Load Balancing Algorithm. If there is more than one endpoint URI, then the Service Bus will load balance across them according to this algorithm.
- Change, add, or remove Endpoint URI s or physical targets.
- Specify retry logic, specifically the Retry Count, the Retry Iteration Interval, and whether or not to Retry Application Errors (errors generated by the service called, not the transport).
Note
Note that the Service Bus gives us the ability to change, add, and remove physical endpoint URIs as well as change the protocol used at runtime. This allows us to change the target services without impacting any clients of the service, providing us with virtualization of our service location.
Using Adapters in Service Bus
The Service Bus can also use adapter definitions created in JDeveloper. To use an adapter from JDeveloper, we cannot directly import the WSDL, we need to import the artifacts in the following order:
- The XSD generated by the adapter using Select Resource Type | Interface | XML Schema
- The WSDL generated by the adapter using Select Resource Type | Interface | WSDL
- The JCA file generated by the adapter using Select Resource Type | Interface | JCA Binding
The WSDL can then be used as a business service. Make sure that references in the JCA file are configured in the WebLogic Server.
The proxy service provides the interface and adaption to our business service, typically joining them by a routing step, as we did in Chapter 2, Writing your First Composite. There are other types of actions besides routing flows. Clicking on Add an Action allows us to choose the type of Communication we want to add. Flow Control allows us to add If .. Then … logic to our routing decision. However, in most cases, the Communication items will provide all the flexibility we need in our routing decisions. This gives us three types of routing to apply:
- Dynamic Routing allows us to route to the result of an XQuery. This is useful if the endpoint address is part of the input message.
- Routing allows us to select a single static endpoint.
- Routing Table allows us to use an XQuery to route between several endpoints. This is useful when we want to route to different services, based on a particular attribute of the input message.
For simple service endpoint virtualization, we only require the Routing option.
Having selected a target endpoint, usually a business service, we can then configure how we use that endpoint. In the case of simple location virtualization, the proxy service and the business service endpoints are the same, and so we can just pass on the input message directly to the business service. Later on, we will look at how to transform data to allow virtualization of the service interface.
Selecting a service to call
We can further virtualize our endpoint by routing different requests to different services, based upon the values of the input message. For example, we may use one address lookup service for addresses in our own country and another service for all other addresses. In this case, we would use the routing table option on the add action to provide a list of possible service destinations.
The routing table enables us to have a number of different destinations, and the message will be routed based on the value of an expression. When using a routing table, all the services must be selected based on the same expression; the comparison operators may vary, but the actual value being tested against will always be the same. If this is not the case, then it may be better to use "if … then … else" routing. The routing table may be thought of as a "switch statement", and as with all switch statements, it is a good practice to add a default case.
In the routing table, we can create additional cases, each of which will have a test associated with it. Note that we can also add the default case.
We need to specify the expression to be used for testing against. Clicking on the <Expression> link takes us to the XQuery /XSLT Expression Editor. By selecting the Variable Structures tab and selecting a new structure, we can find the input body of the message, which lets us select the field we wish to use as the comparison expression in our routing table.
When selecting in the tab on the left of the screen, the appropriate XPath expression should appear in the Property Inspector window. We can the click on the XQuery Text area of the screen prior to clicking on the Copy Property to transfer the property XPath expression from the property inspector to the XQuery Text area. We then complete our selection of the expression by clicking on the Save button.
In the example, we are going to route our service based on the country of the address. In addition to the data in the body of the message, we could also route based on other information from the request. Alternatively, by using a message pipeline, we could base our lookup on data external to the request.
Once we have created an expression to use as the basis of comparison for routing, then we select an operator and a value to use for the actual routing comparison. In the following example, if the country value from the expression matches the string uk (include the quotes), then the LocalAddressLookup service will be invoked. Any other value will cause the default service to be invoked, as yet undefined in the following example:
Once the routing has been defined, then it can be saved, as shown in Chapter 2, Writing your First Composite.
Note that we have shown a very simple routing example. The Service Bus is capable of doing much more sophisticated routing decisions. A common pattern is to use a pipeline to enrich the inbound data and then route based on the inbound data. For example, a pricing proxy service may use the inbound pipeline to look up the status of a customer, adding that status to the data available as part of the request. The routing service could then route high value customers to one service and low value customers to another service, based on the looked up status. In this case, the routing is based on a derived value rather than on a value already available in the message.
In summary, a request can be routed to different references, based on the content of the request message. This allows messages to be routed based on geography or pecuniary value for example. This routing, because it takes place in the composite, is transparent to clients of the composite and so aids us in reducing coupling in the system.
Using Adapters in Service Bus
The Service Bus can also use adapter definitions created in JDeveloper. To use an adapter from JDeveloper, we cannot directly import the WSDL, we need to import the artifacts in the following order:
- The XSD generated by the adapter using Select Resource Type | Interface | XML Schema
- The WSDL generated by the adapter using Select Resource Type | Interface | WSDL
- The JCA file generated by the adapter using Select Resource Type | Interface | JCA Binding
The WSDL can then be used as a business service. Make sure that references in the JCA file are configured in the WebLogic Server.
The proxy service provides the interface and adaption to our business service, typically joining them by a routing step, as we did in Chapter 2, Writing your First Composite. There are other types of actions besides routing flows. Clicking on Add an Action allows us to choose the type of Communication we want to add. Flow Control allows us to add If .. Then … logic to our routing decision. However, in most cases, the Communication items will provide all the flexibility we need in our routing decisions. This gives us three types of routing to apply:
- Dynamic Routing allows us to route to the result of an XQuery. This is useful if the endpoint address is part of the input message.
- Routing allows us to select a single static endpoint.
- Routing Table allows us to use an XQuery to route between several endpoints. This is useful when we want to route to different services, based on a particular attribute of the input message.
For simple service endpoint virtualization, we only require the Routing option.
Having selected a target endpoint, usually a business service, we can then configure how we use that endpoint. In the case of simple location virtualization, the proxy service and the business service endpoints are the same, and so we can just pass on the input message directly to the business service. Later on, we will look at how to transform data to allow virtualization of the service interface.
Selecting a service to call
We can further virtualize our endpoint by routing different requests to different services, based upon the values of the input message. For example, we may use one address lookup service for addresses in our own country and another service for all other addresses. In this case, we would use the routing table option on the add action to provide a list of possible service destinations.
The routing table enables us to have a number of different destinations, and the message will be routed based on the value of an expression. When using a routing table, all the services must be selected based on the same expression; the comparison operators may vary, but the actual value being tested against will always be the same. If this is not the case, then it may be better to use "if … then … else" routing. The routing table may be thought of as a "switch statement", and as with all switch statements, it is a good practice to add a default case.
In the routing table, we can create additional cases, each of which will have a test associated with it. Note that we can also add the default case.
We need to specify the expression to be used for testing against. Clicking on the <Expression> link takes us to the XQuery /XSLT Expression Editor. By selecting the Variable Structures tab and selecting a new structure, we can find the input body of the message, which lets us select the field we wish to use as the comparison expression in our routing table.
When selecting in the tab on the left of the screen, the appropriate XPath expression should appear in the Property Inspector window. We can the click on the XQuery Text area of the screen prior to clicking on the Copy Property to transfer the property XPath expression from the property inspector to the XQuery Text area. We then complete our selection of the expression by clicking on the Save button.
In the example, we are going to route our service based on the country of the address. In addition to the data in the body of the message, we could also route based on other information from the request. Alternatively, by using a message pipeline, we could base our lookup on data external to the request.
Once we have created an expression to use as the basis of comparison for routing, then we select an operator and a value to use for the actual routing comparison. In the following example, if the country value from the expression matches the string uk (include the quotes), then the LocalAddressLookup service will be invoked. Any other value will cause the default service to be invoked, as yet undefined in the following example:
Once the routing has been defined, then it can be saved, as shown in Chapter 2, Writing your First Composite.
Note that we have shown a very simple routing example. The Service Bus is capable of doing much more sophisticated routing decisions. A common pattern is to use a pipeline to enrich the inbound data and then route based on the inbound data. For example, a pricing proxy service may use the inbound pipeline to look up the status of a customer, adding that status to the data available as part of the request. The routing service could then route high value customers to one service and low value customers to another service, based on the looked up status. In this case, the routing is based on a derived value rather than on a value already available in the message.
In summary, a request can be routed to different references, based on the content of the request message. This allows messages to be routed based on geography or pecuniary value for example. This routing, because it takes place in the composite, is transparent to clients of the composite and so aids us in reducing coupling in the system.
Selecting a service to call
We can further virtualize our endpoint by routing different requests to different services, based upon the values of the input message. For example, we may use one address lookup service for addresses in our own country and another service for all other addresses. In this case, we would use the routing table option on the add action to provide a list of possible service destinations.
The routing table enables us to have a number of different destinations, and the message will be routed based on the value of an expression. When using a routing table, all the services must be selected based on the same expression; the comparison operators may vary, but the actual value being tested against will always be the same. If this is not the case, then it may be better to use "if … then … else" routing. The routing table may be thought of as a "switch statement", and as with all switch statements, it is a good practice to add a default case.
In the routing table, we can create additional cases, each of which will have a test associated with it. Note that we can also add the default case.
We need to specify the expression to be used for testing against. Clicking on the <Expression> link takes us to the XQuery /XSLT Expression Editor. By selecting the Variable Structures tab and selecting a new structure, we can find the input body of the message, which lets us select the field we wish to use as the comparison expression in our routing table.
When selecting in the tab on the left of the screen, the appropriate XPath expression should appear in the Property Inspector window. We can the click on the XQuery Text area of the screen prior to clicking on the Copy Property to transfer the property XPath expression from the property inspector to the XQuery Text area. We then complete our selection of the expression by clicking on the Save button.
In the example, we are going to route our service based on the country of the address. In addition to the data in the body of the message, we could also route based on other information from the request. Alternatively, by using a message pipeline, we could base our lookup on data external to the request.
Once we have created an expression to use as the basis of comparison for routing, then we select an operator and a value to use for the actual routing comparison. In the following example, if the country value from the expression matches the string uk (include the quotes), then the LocalAddressLookup service will be invoked. Any other value will cause the default service to be invoked, as yet undefined in the following example:
Once the routing has been defined, then it can be saved, as shown in Chapter 2, Writing your First Composite.
Note that we have shown a very simple routing example. The Service Bus is capable of doing much more sophisticated routing decisions. A common pattern is to use a pipeline to enrich the inbound data and then route based on the inbound data. For example, a pricing proxy service may use the inbound pipeline to look up the status of a customer, adding that status to the data available as part of the request. The routing service could then route high value customers to one service and low value customers to another service, based on the looked up status. In this case, the routing is based on a derived value rather than on a value already available in the message.
In summary, a request can be routed to different references, based on the content of the request message. This allows messages to be routed based on geography or pecuniary value for example. This routing, because it takes place in the composite, is transparent to clients of the composite and so aids us in reducing coupling in the system.
Virtualizing service interfaces
We have looked at how to virtualize a service endpoint. Now let's look at how we can further virtualize the service by abstracting its interface into a common format, known as canonical form. This will provide us further flexibility by allowing us to change the implementation of the service with one that has a different interface but performs the same function. The native format is the way the data format service actually uses, the canonical format is an idealized format that we wish to develop against.
Physical versus logical interfaces
Best practice for integration projects was to have a canonical form for all messages exchanged between systems. The canonical form was a common format for all messages. If a system wanted to send a message, then it first needed to transform it to the canonical form before it could be forwarded to the receiving system, which would then transform it from the canonical form to its own representation. This same good practice is still valid in a service-oriented world and the Service Bus is the mechanism SOA Suite provides for us to do this.
Tip
Canonical data and canonical interface
The canonical data formats should represent the idealized data format for the data entities in the system. The canonical interfaces should be the idealized service interfaces. Generally, it is a bad idea to use existing service data formats or service interfaces as the canonical form. There is a lot of work being done in various industry-specific bodies to define standardized canonical forms for entities that are exchanged between corporations.
The benefits of a canonical form are as follows:
- Transformations are only necessary to and from canonical form, reducing the number of different transformations required to be created
- Decouples format of data from services, allowing a service to be replaced by one providing the same function but a different format of data
This is illustrated graphically by a system where two different clients make requests for one of the four services, all providing the same function but different implementations. Without the canonical form, we would need a transformation of data between the client format and the server format inbound and again outbound. For four services, this yields eight transformations, and for two clients, this doubles to sixteen transformations.
Using the canonical format gives us two transformations for each client, inbound and outbound to the canonical form. With two clients, this gives us four transformations. To this, we add the server transformations to and from the canonical form, of which there are two per server, giving us eight transformations. This gives us a total of twelve transformations that must be coded up rather than sixteen if we were using native-to-native transformation.
The benefits of the canonical form are most clearly seen when we deploy a new client. Without the canonical form, we would need to develop eight transformations to allow the client to work with the four different possible service implementations. With the canonical form, we only need two transformations, to and from the canonical form.
Let's look at how we implement the canonical form in Oracle Service Bus.
Mapping service interfaces
In order to take advantage of the canonical form in our service interfaces, we must have an abstract service interface that provides the functionality we need without being specific to any particular service implementation. Once we have this, we can then use it as the canonical service form.
We set up the initial project in the same way we did in the previous section on virtualizing service endpoints. The proxy should provide the canonical interface, while the business service provides the native service interface. Because the proxy and business services are not the same interface, we need to do some more work in the route configuration.
We need to map the canonical form of the address list interface onto the native service form of the interface. In the example, we are mapping our canonical interface to the interface provided by a web-based address solution from the Harte-Hanks Global Address (http://www.qudox.com). To do this, we create a new Service Bus project and add the Harte-Hanks WSDL (http://webservices.globaladdress.net/globaladdress.asmx?WSDL). We use this to define the business service. We also add the canonical interface WSDL that we have defined and create a new proxy with this interface. We then need to map the proxy service onto the Harte-Hanks service by editing the message flow associated with the proxy, as we did in the previous section.
Our mapping needs to do two things as follows:
- Map the method name on the interface to the correct method in the business service
- Map the parameters in the canonical request onto the parameters needed in the business service request
For each method on the canonical interface, we must map it onto a method in the physical interface. We do this by selecting the appropriate method from the business service operation drop-down box. We need to do this because the methods provided in the external service do not match the method names in our canonical service. In the following example, we have mapped onto the SearchAddress method.
Having selected an operation, we now need to transform the input data from the format provided by the canonical interface into the format required by the external service. We need to map the request and response messages if it is a two-way method or just the request message for one-way method. The actual mapping may be done either by XQuery or XSLT. In our example, we will use the XSLT transform.
To perform the transformation, we add a Messaging Processing action to our message flow, which in this case is a Replace operation. The variable body always holds the message in the Service Bus flow. This receives the message through the proxy interface and is also used to deliver the message to the business service interface. This behavior differs from BPEL and most programming languages, where we typically have separate variables for the input and output messages. We need to transform this message from the proxy input canonical format to the business service native output format.
Be aware that there are really two flows associated with the proxy service. The request flow is used to receive the inbound message and perform any processing before invoking the target business service. The response flow takes the response from the business service and performs any necessary processing before replying to the invoker of the proxy service.
On selecting replace, we can fill in the details in the Request Actions dialog. The message is held in the body variable, and so we can fill this (body) in as the target variable name. We then need to select which part of the body we want to replace.
Clicking on the XPath link brings up the XPath Expression Editor, where we can enter the portion of the target variable that we wish to replace. In this case, we wish to replace all the elements so we enter ./*, which selects the top level element and all elements beneath it. Clicking on the Save button causes the expression to be saved in the Replace Action dialog.
Having identified the portion of the message we wish to replace (all of it) , we now need to specify what we will replace it with. In this case, we wish to transform the whole input message, so we click on the Expression link and select the XSLT Resources tab. Clicking on the Browse button enables us to choose a previously registered XSLT transformation file. After selecting the file, we need to identify the input to the transformation. In this case, the input message is in the body variable, and so we select all the elements in the body by using the expression $body/*. We then save our transformation expression.
Having provided the source data, the target, and the transformation, we can then save and repeat the whole process for the response message (in this case, converting from native to canonical form).
We can use JDeveloper to build an XSLT transform and then upload it into the Service Bus. A future release will add support for XQuery in JDeveloper, similar to that provided in Oracle Workshop for WebLogic. XSLT is an XML language that describes how to transform one XML document into another. Fortunately, most XSLT can be created using the graphical mapping tool in JDeveloper, and so SOA Suite developers don't have to be experts in XSLT, although it is very useful to know how it works. Note that in our transform, we may need to enhance the message with additional information, for example, all the Global Address methods require a username and password to be provided to allow accounting of the requests to take place. This information has no place in the canonical request format, but must be added in the transform. A sample transform that does just this is shown in the following screenshot:
Note that we use XPath string functions to set the username and password fields. It would be better to set these from the properties or an external file, as we would usually want to use them in a number of calls to the physical service. XPath functions are capable of allowing access to composite properties. We actually only need to set five fields in the request, namely, a country, postcode, username, password, and the maximum number of results to return. All the other fields are not necessary for the service we are using and so are hidden from end users because they do not appear in the canonical form of the service.
Applying canonical form in the Service Bus
When we think about the canonical form and routing, we have several different operations that may need to be performed.
- Conversion to/from the native business service form from/to the canonical proxy form
- Conversion to/from the native client form from/to the canonical proxy form
- Routing between multiple native services, each potentially with its own message format
The following diagram represents these different potential interactions as distinct proxy implementations in the service. To reduce coupling and make maintenance easier, each native service has a corresponding canonical proxy service. This isolates the rest of the system from the actual native formats. This is shown below in the Local-Harte-Hanks-Proxy and Local-LocalAddress-Proxy services that transform the native service to/from the canonical form. This approach allows us to change the native address lookup implementations without impacting anything other than the Local-*-Proxy service.
The Canonical-Address-Proxy has the job of hiding the fact that the address lookup service is actually provided by a number of different service providers, each with their own message formats. By providing this service, we can easily add additional address providers without impacting the clients of the address lookup service.
In addition to the services shown in the diagram, we may have clients that are not written to use the canonical address lookup. In this case, we need to provide a proxy that transforms the native input request to/from the canonical form. This allows us to be isolated from the requirements of the clients of the service. If a client requires its own interface to the address lookup service, we can easily provide that through a proxy without the need to impact the rest of the system, again reducing coupling.
An important optimization
The previous approach provides a very robust way of isolating service consumers and service requestors from the native formats and locations of their partners. However, there must be a concern about the overhead of all these additional proxy services and also about the possibility of a client accessing a native service directly. To avoid these problems, the Service Bus provides a local transport mechanism that can be specified as part of the binding of the proxy service. The local transport provides two things for us:
- It makes services only consumable by other services in the Service Bus, they cannot be accessed externally
- It provides a highly optimized messaging transport between proxy services, providing in-memory speed to avoid unnecessary overhead in service hand-offs between proxy services
These optimizations mean that it is very efficient to use the canonical form, and so the Service Bus not only allows us great flexibility in how we decouple our services from each other, but it also provides a very efficient mechanism for us to implement that decoupling. Note, though, that there is a cost involved in performing XSLT or XQuery transformations. This cost may be viewed as the price of loose coupling.
Physical versus logical interfaces
Best practice for integration projects was to have a canonical form for all messages exchanged between systems. The canonical form was a common format for all messages. If a system wanted to send a message, then it first needed to transform it to the canonical form before it could be forwarded to the receiving system, which would then transform it from the canonical form to its own representation. This same good practice is still valid in a service-oriented world and the Service Bus is the mechanism SOA Suite provides for us to do this.
Tip
Canonical data and canonical interface
The canonical data formats should represent the idealized data format for the data entities in the system. The canonical interfaces should be the idealized service interfaces. Generally, it is a bad idea to use existing service data formats or service interfaces as the canonical form. There is a lot of work being done in various industry-specific bodies to define standardized canonical forms for entities that are exchanged between corporations.
The benefits of a canonical form are as follows:
- Transformations are only necessary to and from canonical form, reducing the number of different transformations required to be created
- Decouples format of data from services, allowing a service to be replaced by one providing the same function but a different format of data
This is illustrated graphically by a system where two different clients make requests for one of the four services, all providing the same function but different implementations. Without the canonical form, we would need a transformation of data between the client format and the server format inbound and again outbound. For four services, this yields eight transformations, and for two clients, this doubles to sixteen transformations.
Using the canonical format gives us two transformations for each client, inbound and outbound to the canonical form. With two clients, this gives us four transformations. To this, we add the server transformations to and from the canonical form, of which there are two per server, giving us eight transformations. This gives us a total of twelve transformations that must be coded up rather than sixteen if we were using native-to-native transformation.
The benefits of the canonical form are most clearly seen when we deploy a new client. Without the canonical form, we would need to develop eight transformations to allow the client to work with the four different possible service implementations. With the canonical form, we only need two transformations, to and from the canonical form.
Let's look at how we implement the canonical form in Oracle Service Bus.
Mapping service interfaces
In order to take advantage of the canonical form in our service interfaces, we must have an abstract service interface that provides the functionality we need without being specific to any particular service implementation. Once we have this, we can then use it as the canonical service form.
We set up the initial project in the same way we did in the previous section on virtualizing service endpoints. The proxy should provide the canonical interface, while the business service provides the native service interface. Because the proxy and business services are not the same interface, we need to do some more work in the route configuration.
We need to map the canonical form of the address list interface onto the native service form of the interface. In the example, we are mapping our canonical interface to the interface provided by a web-based address solution from the Harte-Hanks Global Address (http://www.qudox.com). To do this, we create a new Service Bus project and add the Harte-Hanks WSDL (http://webservices.globaladdress.net/globaladdress.asmx?WSDL). We use this to define the business service. We also add the canonical interface WSDL that we have defined and create a new proxy with this interface. We then need to map the proxy service onto the Harte-Hanks service by editing the message flow associated with the proxy, as we did in the previous section.
Our mapping needs to do two things as follows:
- Map the method name on the interface to the correct method in the business service
- Map the parameters in the canonical request onto the parameters needed in the business service request
For each method on the canonical interface, we must map it onto a method in the physical interface. We do this by selecting the appropriate method from the business service operation drop-down box. We need to do this because the methods provided in the external service do not match the method names in our canonical service. In the following example, we have mapped onto the SearchAddress method.
Having selected an operation, we now need to transform the input data from the format provided by the canonical interface into the format required by the external service. We need to map the request and response messages if it is a two-way method or just the request message for one-way method. The actual mapping may be done either by XQuery or XSLT. In our example, we will use the XSLT transform.
To perform the transformation, we add a Messaging Processing action to our message flow, which in this case is a Replace operation. The variable body always holds the message in the Service Bus flow. This receives the message through the proxy interface and is also used to deliver the message to the business service interface. This behavior differs from BPEL and most programming languages, where we typically have separate variables for the input and output messages. We need to transform this message from the proxy input canonical format to the business service native output format.
Be aware that there are really two flows associated with the proxy service. The request flow is used to receive the inbound message and perform any processing before invoking the target business service. The response flow takes the response from the business service and performs any necessary processing before replying to the invoker of the proxy service.
On selecting replace, we can fill in the details in the Request Actions dialog. The message is held in the body variable, and so we can fill this (body) in as the target variable name. We then need to select which part of the body we want to replace.
Clicking on the XPath link brings up the XPath Expression Editor, where we can enter the portion of the target variable that we wish to replace. In this case, we wish to replace all the elements so we enter ./*, which selects the top level element and all elements beneath it. Clicking on the Save button causes the expression to be saved in the Replace Action dialog.
Having identified the portion of the message we wish to replace (all of it) , we now need to specify what we will replace it with. In this case, we wish to transform the whole input message, so we click on the Expression link and select the XSLT Resources tab. Clicking on the Browse button enables us to choose a previously registered XSLT transformation file. After selecting the file, we need to identify the input to the transformation. In this case, the input message is in the body variable, and so we select all the elements in the body by using the expression $body/*. We then save our transformation expression.
Having provided the source data, the target, and the transformation, we can then save and repeat the whole process for the response message (in this case, converting from native to canonical form).
We can use JDeveloper to build an XSLT transform and then upload it into the Service Bus. A future release will add support for XQuery in JDeveloper, similar to that provided in Oracle Workshop for WebLogic. XSLT is an XML language that describes how to transform one XML document into another. Fortunately, most XSLT can be created using the graphical mapping tool in JDeveloper, and so SOA Suite developers don't have to be experts in XSLT, although it is very useful to know how it works. Note that in our transform, we may need to enhance the message with additional information, for example, all the Global Address methods require a username and password to be provided to allow accounting of the requests to take place. This information has no place in the canonical request format, but must be added in the transform. A sample transform that does just this is shown in the following screenshot:
Note that we use XPath string functions to set the username and password fields. It would be better to set these from the properties or an external file, as we would usually want to use them in a number of calls to the physical service. XPath functions are capable of allowing access to composite properties. We actually only need to set five fields in the request, namely, a country, postcode, username, password, and the maximum number of results to return. All the other fields are not necessary for the service we are using and so are hidden from end users because they do not appear in the canonical form of the service.
Applying canonical form in the Service Bus
When we think about the canonical form and routing, we have several different operations that may need to be performed.
- Conversion to/from the native business service form from/to the canonical proxy form
- Conversion to/from the native client form from/to the canonical proxy form
- Routing between multiple native services, each potentially with its own message format
The following diagram represents these different potential interactions as distinct proxy implementations in the service. To reduce coupling and make maintenance easier, each native service has a corresponding canonical proxy service. This isolates the rest of the system from the actual native formats. This is shown below in the Local-Harte-Hanks-Proxy and Local-LocalAddress-Proxy services that transform the native service to/from the canonical form. This approach allows us to change the native address lookup implementations without impacting anything other than the Local-*-Proxy service.
The Canonical-Address-Proxy has the job of hiding the fact that the address lookup service is actually provided by a number of different service providers, each with their own message formats. By providing this service, we can easily add additional address providers without impacting the clients of the address lookup service.
In addition to the services shown in the diagram, we may have clients that are not written to use the canonical address lookup. In this case, we need to provide a proxy that transforms the native input request to/from the canonical form. This allows us to be isolated from the requirements of the clients of the service. If a client requires its own interface to the address lookup service, we can easily provide that through a proxy without the need to impact the rest of the system, again reducing coupling.
An important optimization
The previous approach provides a very robust way of isolating service consumers and service requestors from the native formats and locations of their partners. However, there must be a concern about the overhead of all these additional proxy services and also about the possibility of a client accessing a native service directly. To avoid these problems, the Service Bus provides a local transport mechanism that can be specified as part of the binding of the proxy service. The local transport provides two things for us:
- It makes services only consumable by other services in the Service Bus, they cannot be accessed externally
- It provides a highly optimized messaging transport between proxy services, providing in-memory speed to avoid unnecessary overhead in service hand-offs between proxy services
These optimizations mean that it is very efficient to use the canonical form, and so the Service Bus not only allows us great flexibility in how we decouple our services from each other, but it also provides a very efficient mechanism for us to implement that decoupling. Note, though, that there is a cost involved in performing XSLT or XQuery transformations. This cost may be viewed as the price of loose coupling.
Mapping service interfaces
In order to take advantage of the canonical form in our service interfaces, we must have an abstract service interface that provides the functionality we need without being specific to any particular service implementation. Once we have this, we can then use it as the canonical service form.
We set up the initial project in the same way we did in the previous section on virtualizing service endpoints. The proxy should provide the canonical interface, while the business service provides the native service interface. Because the proxy and business services are not the same interface, we need to do some more work in the route configuration.
We need to map the canonical form of the address list interface onto the native service form of the interface. In the example, we are mapping our canonical interface to the interface provided by a web-based address solution from the Harte-Hanks Global Address (http://www.qudox.com). To do this, we create a new Service Bus project and add the Harte-Hanks WSDL (http://webservices.globaladdress.net/globaladdress.asmx?WSDL). We use this to define the business service. We also add the canonical interface WSDL that we have defined and create a new proxy with this interface. We then need to map the proxy service onto the Harte-Hanks service by editing the message flow associated with the proxy, as we did in the previous section.
Our mapping needs to do two things as follows:
- Map the method name on the interface to the correct method in the business service
- Map the parameters in the canonical request onto the parameters needed in the business service request
For each method on the canonical interface, we must map it onto a method in the physical interface. We do this by selecting the appropriate method from the business service operation drop-down box. We need to do this because the methods provided in the external service do not match the method names in our canonical service. In the following example, we have mapped onto the SearchAddress method.
Having selected an operation, we now need to transform the input data from the format provided by the canonical interface into the format required by the external service. We need to map the request and response messages if it is a two-way method or just the request message for one-way method. The actual mapping may be done either by XQuery or XSLT. In our example, we will use the XSLT transform.
To perform the transformation, we add a Messaging Processing action to our message flow, which in this case is a Replace operation. The variable body always holds the message in the Service Bus flow. This receives the message through the proxy interface and is also used to deliver the message to the business service interface. This behavior differs from BPEL and most programming languages, where we typically have separate variables for the input and output messages. We need to transform this message from the proxy input canonical format to the business service native output format.
Be aware that there are really two flows associated with the proxy service. The request flow is used to receive the inbound message and perform any processing before invoking the target business service. The response flow takes the response from the business service and performs any necessary processing before replying to the invoker of the proxy service.
On selecting replace, we can fill in the details in the Request Actions dialog. The message is held in the body variable, and so we can fill this (body) in as the target variable name. We then need to select which part of the body we want to replace.
Clicking on the XPath link brings up the XPath Expression Editor, where we can enter the portion of the target variable that we wish to replace. In this case, we wish to replace all the elements so we enter ./*, which selects the top level element and all elements beneath it. Clicking on the Save button causes the expression to be saved in the Replace Action dialog.
Having identified the portion of the message we wish to replace (all of it) , we now need to specify what we will replace it with. In this case, we wish to transform the whole input message, so we click on the Expression link and select the XSLT Resources tab. Clicking on the Browse button enables us to choose a previously registered XSLT transformation file. After selecting the file, we need to identify the input to the transformation. In this case, the input message is in the body variable, and so we select all the elements in the body by using the expression $body/*. We then save our transformation expression.
Having provided the source data, the target, and the transformation, we can then save and repeat the whole process for the response message (in this case, converting from native to canonical form).
We can use JDeveloper to build an XSLT transform and then upload it into the Service Bus. A future release will add support for XQuery in JDeveloper, similar to that provided in Oracle Workshop for WebLogic. XSLT is an XML language that describes how to transform one XML document into another. Fortunately, most XSLT can be created using the graphical mapping tool in JDeveloper, and so SOA Suite developers don't have to be experts in XSLT, although it is very useful to know how it works. Note that in our transform, we may need to enhance the message with additional information, for example, all the Global Address methods require a username and password to be provided to allow accounting of the requests to take place. This information has no place in the canonical request format, but must be added in the transform. A sample transform that does just this is shown in the following screenshot:
Note that we use XPath string functions to set the username and password fields. It would be better to set these from the properties or an external file, as we would usually want to use them in a number of calls to the physical service. XPath functions are capable of allowing access to composite properties. We actually only need to set five fields in the request, namely, a country, postcode, username, password, and the maximum number of results to return. All the other fields are not necessary for the service we are using and so are hidden from end users because they do not appear in the canonical form of the service.
Applying canonical form in the Service Bus
When we think about the canonical form and routing, we have several different operations that may need to be performed.
- Conversion to/from the native business service form from/to the canonical proxy form
- Conversion to/from the native client form from/to the canonical proxy form
- Routing between multiple native services, each potentially with its own message format
The following diagram represents these different potential interactions as distinct proxy implementations in the service. To reduce coupling and make maintenance easier, each native service has a corresponding canonical proxy service. This isolates the rest of the system from the actual native formats. This is shown below in the Local-Harte-Hanks-Proxy and Local-LocalAddress-Proxy services that transform the native service to/from the canonical form. This approach allows us to change the native address lookup implementations without impacting anything other than the Local-*-Proxy service.
The Canonical-Address-Proxy has the job of hiding the fact that the address lookup service is actually provided by a number of different service providers, each with their own message formats. By providing this service, we can easily add additional address providers without impacting the clients of the address lookup service.
In addition to the services shown in the diagram, we may have clients that are not written to use the canonical address lookup. In this case, we need to provide a proxy that transforms the native input request to/from the canonical form. This allows us to be isolated from the requirements of the clients of the service. If a client requires its own interface to the address lookup service, we can easily provide that through a proxy without the need to impact the rest of the system, again reducing coupling.
An important optimization
The previous approach provides a very robust way of isolating service consumers and service requestors from the native formats and locations of their partners. However, there must be a concern about the overhead of all these additional proxy services and also about the possibility of a client accessing a native service directly. To avoid these problems, the Service Bus provides a local transport mechanism that can be specified as part of the binding of the proxy service. The local transport provides two things for us:
- It makes services only consumable by other services in the Service Bus, they cannot be accessed externally
- It provides a highly optimized messaging transport between proxy services, providing in-memory speed to avoid unnecessary overhead in service hand-offs between proxy services
These optimizations mean that it is very efficient to use the canonical form, and so the Service Bus not only allows us great flexibility in how we decouple our services from each other, but it also provides a very efficient mechanism for us to implement that decoupling. Note, though, that there is a cost involved in performing XSLT or XQuery transformations. This cost may be viewed as the price of loose coupling.
Applying canonical form in the Service Bus
When we think about the canonical form and routing, we have several different operations that may need to be performed.
- Conversion to/from the native business service form from/to the canonical proxy form
- Conversion to/from the native client form from/to the canonical proxy form
- Routing between multiple native services, each potentially with its own message format
The following diagram represents these different potential interactions as distinct proxy implementations in the service. To reduce coupling and make maintenance easier, each native service has a corresponding canonical proxy service. This isolates the rest of the system from the actual native formats. This is shown below in the Local-Harte-Hanks-Proxy and Local-LocalAddress-Proxy services that transform the native service to/from the canonical form. This approach allows us to change the native address lookup implementations without impacting anything other than the Local-*-Proxy service.
The Canonical-Address-Proxy has the job of hiding the fact that the address lookup service is actually provided by a number of different service providers, each with their own message formats. By providing this service, we can easily add additional address providers without impacting the clients of the address lookup service.
In addition to the services shown in the diagram, we may have clients that are not written to use the canonical address lookup. In this case, we need to provide a proxy that transforms the native input request to/from the canonical form. This allows us to be isolated from the requirements of the clients of the service. If a client requires its own interface to the address lookup service, we can easily provide that through a proxy without the need to impact the rest of the system, again reducing coupling.
An important optimization
The previous approach provides a very robust way of isolating service consumers and service requestors from the native formats and locations of their partners. However, there must be a concern about the overhead of all these additional proxy services and also about the possibility of a client accessing a native service directly. To avoid these problems, the Service Bus provides a local transport mechanism that can be specified as part of the binding of the proxy service. The local transport provides two things for us:
- It makes services only consumable by other services in the Service Bus, they cannot be accessed externally
- It provides a highly optimized messaging transport between proxy services, providing in-memory speed to avoid unnecessary overhead in service hand-offs between proxy services
These optimizations mean that it is very efficient to use the canonical form, and so the Service Bus not only allows us great flexibility in how we decouple our services from each other, but it also provides a very efficient mechanism for us to implement that decoupling. Note, though, that there is a cost involved in performing XSLT or XQuery transformations. This cost may be viewed as the price of loose coupling.
An important optimization
The previous approach provides a very robust way of isolating service consumers and service requestors from the native formats and locations of their partners. However, there must be a concern about the overhead of all these additional proxy services and also about the possibility of a client accessing a native service directly. To avoid these problems, the Service Bus provides a local transport mechanism that can be specified as part of the binding of the proxy service. The local transport provides two things for us:
- It makes services only consumable by other services in the Service Bus, they cannot be accessed externally
- It provides a highly optimized messaging transport between proxy services, providing in-memory speed to avoid unnecessary overhead in service hand-offs between proxy services
These optimizations mean that it is very efficient to use the canonical form, and so the Service Bus not only allows us great flexibility in how we decouple our services from each other, but it also provides a very efficient mechanism for us to implement that decoupling. Note, though, that there is a cost involved in performing XSLT or XQuery transformations. This cost may be viewed as the price of loose coupling.
Using the Mediator for virtualization
As discussed earlier, we can also use the Mediator for virtualization within an SCA Assembly. The Mediator should be used to ensure that interface into and out of SCA Assemblies use canonical form. We can also use XSL transforms in Mediator in a similar fashion to Service Bus to provide mappings between one data format and another.
To do this, we would select the canonical format WSDL as the input to our composite and wire this to the Mediator in the same way as we did in Chapter 2, Writing your First Composite. We can then double-click on the Mediator to open it and add a transformation to convert the messages to and from the canonical form.
If necessary, we may need to expand the routing rule to show the details. For the input message, we have the option of filtering the message, meaning that we can choose what to call, based on the contents of the input message. If no filter expression is provided, then all messages will be delivered to a single target.
The Validate Semantic field allows us to check that the input message is of the correct format. This requires a schematron file and is covered in Chapter 13, Building Validation into Services.
The Assign Values field allows us to set values using either the input message or message properties. This is particularly useful when using adapters, as some of the data required may be provided in adapter headers such as the input filename. This may also be used to set adapter header properties, if invoking an adapter.
The Transform Using field allows us to select an XSL stylesheet to transform the input (in this case, the canonical format) to the internal format. Clicking the icon brings up the Request Transformation Map dialog:
Here we can either select an existing XSL or create a new one based on the input and output formats.
The XSL editor provides a graphical drag-and-drop mechanism for creating XSL stylesheets. Alternatively, it is possible to select the Source tab and input XSL commands directly. Note that many XSL commands are not supported by the graphical editor, and so it is best to do as much as possible in the graphical editor before switching to the source mode.
Summary
In this chapter, we have explored how we can use the Oracle Service Bus and the Mediator in the SOA Suite to reduce the degree of coupling. By reducing coupling, or the dependencies between services, our architectures become more resilient to change. In particular, we looked at how to use the Service Bus to reduce coupling by abstracting endpoint interface locations and formats. Crucial to this is the concept of canonical or common data formats that reduce the amount of data transformation that is required, particularly in bringing new services into our architecture. Finally, we considered how this abstraction can go as far as hiding the fact that we are using multiple services' concurrently by allowing us to make routing decisions at runtime.
All these features are there to help us build service-oriented architectures that are resilient to change and can easily absorb new functionality and services.