Web Services and Sessions
by Sergey BeryozkinJuly 22, 2003
Introduction
|
Related Reading
Web Services Essentials |
However, when building a stateful service, it is often tempting, given some experience with CORBA or DCOM, to design it the way the underlying application has been designed. With traditional distributed technologies, complex systems are often designed with finer granularity, as session-oriented, where a single service object, acting as a factory, returns new or existing session objects to clients.
In general, a service-oriented approach (simple interactions, complex messages) may be better suited to building stateful web services, especially in the bigger B2B world, where integration is normally achieved through an exchange of XML documents. Coarse-grained services, with their API expressed in terms of the document exchange, are likely to be more suitable for creating loosely coupled, scalable and easily composable systems.
Yet there still exists a certain class of applications which might be better exposed in a traditional session-oriented manner. Sometimes a cleaner design can be achieved by assigning orthogonal sets of functionality to separate services, and using thus simpler XML messages as a result. Such web services are fine-grained. They may not be designed for a general large-scale consumption, but, rather, for cooperation with some specific external clients. The integration scope of such services can be limited in many cases to a single enterprise, even to a single department or a small project. However, this route inevitably leads to state, distributed resources management, and scalability problems, which are likely to be much more serious than the same ones associated with traditional distributed programming.
These problems don't mean you should necessarily avoid a fine-grained web services design. If you believe that for a particular use case a fine grained design can result in a better interface, and that a reasonable compromise with respect to those problems can be achieved, then such a route should at least be explored.
It is likely we'll see some standardization efforts in this area of state and resource management in the near future. Meanwhile, this article will look at ways of building stateful web services. In particular we highlight different ways of defining service references and identifying individual sessions.
How We Did It: URLs Can Help
We originally developed a COM-based device management software framework for controlling video-processing systems, which our company produces. A client can request a connection to a particular device from a service factory object, which returns either a new device session object or an existing one.
By the time we had to write a front-end to this system I happened to come across my first SOAP article, A Young Person's Guide to SOAP by Don Box. After some consideration we decided that a client application should talk to devices using SOAP over HTTP, rather than DCOM. This decision was based not only on the eternal programmers' desire to try a hot, new technology, but also in the hope that it would be easier to integrate our system with different third-party software applications.
We designed our first production-based web service the same way the encapsulated system was designed, by having a manager service to return a device key from a local map of device sessions in response to a connection request and passing it as part of a Request URL to a device session service. We also tried to describe our service in WSDL as soon as it appeared.
<definitions ...>
<!-- not all messages are shown -->
<message name="connectRequest">
<!- parts omitted for brevity -->
</message>
<message name="connectResponse">
<part name="deviceKey" type="xsd:long"/>
</message>
<!-- port types, not all operations are shown -->
<portType name="ICommManagerPortType">
<operation name="connect">
<input message="tns:connectRequest"/>
<output message="tns:connectResponse"/>
</operation>
</portType>
<portType name="ICommSessionPortType">
<!-- session-specific operations are not shown -->
</portType>
<!-- bindings are omitted for brevity -->
<service name="ZManagerService">
<port binding="zm:ICommManagerBinding" name="ICommManagerPort">
<soap:address location="http://www.zandar.com/devices"/>
</port>
</service>
<service name="ZSessionService">
<port binding="zm:ICommSessionBinding" name="ICommSessionPort">
<soap:address location="http://www.zandar.com/devices"/>
</port>
</service>
</definitions>
What is visible in the above fragment is that there's no way to specify
in WSDL 1.1 that an instance of a particular abstract port type (such as
ICommManagerPort) returns or accepts a reference to an
instance of another port type (such as ICommSessionPort) as
part of some message.
As a result, an extra pressure falls on a client side programmer's shoulders because a proxy builder does not know how these port types relate to each other. Here's some client code in C#.
ZManagerService zmanagerService = new ZManagerService();
// connect to a device
long deviceKey = zmanagerService.connect();
ZSessionService zSession = new ZSessionService();
// explicitly modify zSession endpoint address
zSession.Url = zmanagerService.Url + "?DeviceKey=" + deviceKey;
// talk to device
SystemProps sysProps = zSession.getSystemProps();
// explicitly disconnect
zSession.disconnect(deviceKey);
This design was strongly influenced by our COM experience. If we were
to build the service today, we would do it differently. We'd use a device
name rather than an opaque number to help the service in identifying the
session. We would consider creating a wrapper around our COM objects and
exposing its interface instead, passing a device name as part of the
application data, hiding completely the way our native implementation is
organized. One reservation about this approach is that orthogonal
operations, such as connect() and send() would
likely have to be put into a single interface, as it may not be possible
for a service to connect automatically as part of the first control
request.
Should a service (port) reference be described in WSDL or in another specification layered on top of WSDL? It seems that a WSDL document is a good place for describing references which allow for simple unconstrained interactions between an application client and a service, and hence do not require complex runtime support. This would make the above code fragment look like this instead.
ZSessionService zSession = zmanagerService.connect();
SystemProps sysProps = zSession.getSystemProps();
However, more complex interactions and dependencies between services can be described by a higher-level language, such as Business Process Execution Language for Web Services (BPEL4WS).
Passing Session Keys in Messages
When building fine-grained session-oriented web services, one of the issues which needs to be addressed is how to identify an individual session.
One approach is to explicitly pass instance keys as part of request messages to a session service. Consider the following two interfaces :
interface IAccount {
void credit (long amount);
}
interface IBankManager {
IAccount openAccount();
}
A corresponding WSDL description may look like this:
<definitions ...>
<!-- non-standard SessionKey type is defined in the types section -->
<message name="openAccountRequest"/>
<message name="openAccountResponse">
<part name="sessionKey" type="xsd1:SessionKey"/>
</message>
<message name="creditRequest">
<part name="sessionKey" type="xsd1:SessionKey"/>
<part name="amount" type="xsd:long"/>
</message>
<portType name="IBankManager">
<operation name="openAccount">
<input message="tns:openAccountRequest"/>
<output message="tns:openAccountResponse"/>
</operation>
</portType>
<portType name="IAccount">
<operation name="credit">
<input message="tns:creditRequest"/>
<!-- output may also be present -->
</operation>
</portType>
</definitions>
SessionKey is returned in the response to an
openAccount request and passed as part of a credit
operation. Its format varies between toolkits, for example, it can be an
integer or a GUID. A proxy automatically adds a key to a request, and as
such it's only visible on the wire, but not in the client code, which can
now look like this :
IAccount account = bankManager.openAccount();
account.credit(1000);
This approach requires the same toolkit (and often the same language) be used on the client and server sides, because instance tokens as well as mechanisms for mapping them to message fields are not standardized.
