7.8 Service Discoverability
The two primary aspects of the Service Discoverability principle are discovery at design-time (which promotes service reuse in a newly developed solution), and discovery at runtime (which involves resolving the appropriate endpoint address for a given service or retrieving other metadata). Even though the information can be physically stored in one place, the way in which the information is accessed in each scenario varies.
Design-Time Discoverability
At design-time, it is important for project teams to be able to effective identify the existence of services that contain logic relevant to the solution they plan to build. This way they can either discover services that they can reuse or confirm that new service logic they plan to build does not already exist. For example, a service is designed to address an Order Management business process for which customer information is required. The service designer must investigate whether a Customer data type already exists, and if so, determine whether it meets the requirements for the Order Management Process service. If the data sent into the newly designed service is missing information, the designer can also check whether an entity service encapsulating all data access to this type of data exists. Additional customer information required can be built or retrieved via a Customer entity service.
During the design of a new service, several types of artifacts must be evaluated for reuse directly on the new service’s interface and within the service via some kind of aggregation. This includes non-functional aspects, which may not be expressed in machine-readable form. Meta information, such as performance and reliability of existing services, can influence whether a service is reusable in a new context.
Code can exist for existing data type definitions. JAXB, for example, defines a mapping between XML schema and Java. Java code can be directly generated from a schema definition and reused wherever that particular data type is used.
All of the relevant information must be available to the designer during design-time, ideally via the development environment directly. This relevant information includes the artifacts themselves, such as the WSDL, XML schema, and Java source code as well as relationships and dependencies between them. These dependencies must be documented thoroughly, as part of the service profile so that a complete metamodel of the service exists once the design is complete.
Since the design of a service is manually performed by a service designer, this information must be accessible and searchable by humans. How this feature is enabled depends on the registry type used and the access mechanisms offered, without depending on the programming language used to implement services or on the runtime platform that the service will run on. The underlying mechanism should offer a way to control access to the information and support versioning of artifacts.
Runtime Discoverability
Runtime service discovery refers to the ability of software programs to programmatically search for services using APIs exposed by the service registry. Doing so allows for the retrieval of the physical location or address of services on the network. Because services may need to be moved from one machine to another or perhaps redundantly deployed on multiple machines, it may be advisable for service addresses not to be hardcoded into the service consumer logic.
For SOAP services using JAX-WS, the location of the target service is included in the <port> element of the WSDL definition by default. In turn, the location of the WSDL file is automatically added to the generated ...Service class and passed to the tooling, or wsimport, that generates the appropriate service consumer artifacts. Pointing to the local WSDL file in the filesystem will result in a service consumer that cannot be moved to a different environment, because functionality depends on that particular location for the WSDL file and the endpoint address contained by the WSDL file.
Using the URL and appending "?wsdl" to the service is a more flexible approach. For example, a service can be located at myhost.acme.com:8080/account/account, in which case the WSDL definition can be retrieved via the URL http://myhost.acme.com:8080/account/account?wsdl. During the installation and deployment of a service, the endpoint information in this WSDL file is updated to reflect the real address of the hosting system.
The address points to a fixed location for the WSDL file despite now residing on the network, meaning the address of any target services should always be resolved separately from the generated service consumer code. JAX-WS provides a way to set a target endpoint address on a proxy instance at runtime that utilizes the javax.xml.ws.BindingProvider interface. Each proxy implements this interface and allows properties to be dynamically set on an exchange between a service consumer and service, with the endpoint address being one of the pre-defined properties. The code in Example 7.44 illustrates how a service consumer sets a new endpoint address on a service proxy before invocation.
Example 7.44
String endpointAddress = ...; //retrieve address somehow OrderForm orderForm = new OrderFormService().getOrderFormPort(); java.util.Map<String, Object> requestContext = (javax.xml. ws.BindingProvider)orderForm).getRequestContext(); requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress); orderForm.addOrderForm("123");
The WSDL location can alternatively be set for a service at runtime instead of the endpoint address, to maintain the address of the service together with the rest of the service contract in its WSDL file and not in multiple locations, as shown in Example 7.45.
Example 7.45
URL wsdlLocation = ...; //retrieve WSDL location OrderFormService orderFormService = new OrderFormService(wsdlLocation, new QName("http://utility. services.novobank.com/", "OrderFormService")); OrderForm orderForm = orderFormService.getOrderFormPort(); orderForm.addOrderForm("123");
In addition to the lookup of a service endpoint address, other artifacts can be used by service consumers at runtime to identify an appropriate service and build the applicable request message. The JAX-WS Dispatch API allows a service request to be completely built at runtime. Theoretically, a service consumer could look up a WSDL definition at runtime, read its portType and XML schema definitions, and build the right messages from scratch. However, building a service request completely at runtime is impractical in a real-life scenario, as the service will likely perform poorly and require plenty of tedious coding.
Non-functional characteristics of a service are other examples of information that a service consumer can discover at runtime. For example, assume that two physical instances of a service exist on both a slower machine and a faster machine. The service consumer can retrieve this information and select the appropriate service to be used, depending on the business context of the call. For instance, a silver customer is routed to the slower machine, whereas a gold customer is routed to the faster machine. The service is still implemented in both invocations, as the differentiation in routing is a non-functional characteristic.
API endpoints are unnecessary in a REST-based system because no RPC-style invocation is involved. Recall that URI-based addressability, like statelessness, is a formal REST constraint. Resources that offer services are the fundamental entities and must be discoverable through URIs before clients can invoke operations on them. The hypermedia constraint, if followed accurately, requires only the URI of the root resource to be provided to the service consumer.
Various operations on the root resource will publish URIs of related resources, which can then be used by the service consumers to reduce the amount of foreknowledge required. However, service consumers would still require knowledge of the semantics associated with the URIs to be able to make meaningful use of them, which makes this approach impractical in real-life application.
Service Registries
Information can be retrieved by a service consumer at runtime with the service registry. As the following methods are inapplicable to REST services, the remainder of this discussion will only apply to SOAP-based Web services. Storing information about WSDL locations and endpoint addresses in a file accessible through a proprietary API at runtime is a retrieval mechanism appropriate for small environments. However, the format in which the information is stored, such as XML or a character-delimited format, must be defined, and the location of this file must always be maintained throughout the enterprise for easy accessibility.
Alternatively, storing the information in a relational database allows for remote access and query using a standard language, such as SQL, although a proprietary relational model for this information must still be invented. Other options include leveraging the JNDI or LDAP to serve the same purpose.
In the early days of Web services, a platform-independent standard describing how to uniformly store, find, and retrieve business and technical information about services was developed as the UDDI registry, which offers a query and publish interface. As a Web service, the UDDI registry allows the API to be described by the WSDL so that any Web service-capable service consumer can access a UDDI registry by generating a proxy from the standard WSDL. For accessing registries at runtime, many IDEs such as Eclipse’s Web Tools Platform have built-in support for existing UDDI registries.