Welcome to the first in a series of articles that will guide you through development of a service that provides persistent object IDs.
A critical requirement in many distributed object systems is the ability to generate unique global identifiers for persistent objects. We will refer to these identifiers as persistent object IDs (POIDs). POIDs provide a long lived object with a global identity that transcends space and time. Example use cases include creating account numbers for bank accounts, and generating identifiers for new customers. For our purposes, POIDs are unique within object type. It can be quite challenging to implement this requirement in a robust, performant manner while servicing many distributed clients.
Our solution will employ standard J2EE and Web technologies, including Enterprise Java Beans (EJB) and Simple Object Access Protocol (SOAP). We will build and test our application using WebSphereTM Studio Application Developer Version 5 and WebSphere Application Server V5.
This first article will discuss overall service requirements, and the client view of the service. Subsequent articles will cover the development of server side components.
For purposes of this exercise, we will strive to keep the requirements straightforward, yet realistic. The service shall guarantee generation of globally unique identifiers for client specified types. The service capacity will support up to 2**31 different persistent object types with up to 2**63 POIDs each. In terms of performance, average request processing time (not including network latencies) shall be less than 0.01 ms, with a minimum effective throughput of 2000 requests/sec using a low end uniprocessor server. The service must support a minimum of 100 concurrent clients, and shall fail-safe through failures of any underlying datastore. That is to say, under no circumstances shall a non-unique POID be dispensed to a client.
Initially, Java clients will use SOAP to access the service, though the design should be flexible enough to easily accommodate other communication protocols, including RMI and CORBA.
A simple two tier database-only approach is not likely to achieve the performance requirements outlined above. In particular, it would be unreasonable to assume that we could afford a database transaction per POID request. And while some systems might support this transaction rate, it would be an unnecessary waste of expensive resources.
One approach to the problem uses a segmented POID address space (see References ). In this scenario, a client such as an AccountFactory, will actually be assigned a block of identifiers by the service. The factory requests a new account POID, and is assigned a range of 100 POIDs. When subsequent account POIDs are required, they will be allocated immediately from this reserved block, eliminating the need for additional remote database transactions.
We want to minimize the complexity exposed to clients of our service. This complexity must be managed on several fronts. The client should not see details of service communication or implementation, as this would limit our flexibility. And the client itself should not have responsibility for managing blocks of POIDs.
We can provide clients an easy to use in-process component, which will be responsible for service location and communication, and POID block management. Figure 1 depicts our initial class diagram design. The component consists of a singleton class named PoIdGenerator, exposing a simple API, and responsible for managing blocks of POIDs. There is a PoId class, a simple wrapper for the POID value, which insulates our APIs from the actual value type (primitive long in this case). PoIdGenerator will hold a reference to a proxy, an implementation of PoIdBroker. The PoIdBroker implementation in turn is responsible for locating and communicating with the underlying service. PoIdBlock is a non public helper class used by PoIdGenerator to manage blocks of POIDs.
Our focus for the remainder of this article is the development of this client side component.
Figure 1. Initial class diagram design
Clients first request an instance of PoIdGenerator, then invoke getPoId() on the instance, passing in a String parameter specifying the type of the persistent object (Figure 2). By convention, this type string corresponds to the value returned by getClass().getName() for the persistent object. Obviously a type namespace policy will have to be established when deploying this service. The PoIdGenerator in turn sends a message to the PoIdBroker to reserve a block of POIDs for this type. The fact that blocks of identifiers are reserved is transparent to the client, but allows most getPoId() requests to be handled as local calls.
Figure 2. Client side sequence
We are now in a position to continue with detailed design and implementation of the client side component. Establishing a service interface early on can enable work to proceed in parallel. In this case, the client side team can work in parallel with the server side development team.
Let's walk through the client side sequence diagram in more detail. Clearly some initialization will need to be performed in the PoIdGenerator. On startup it needs to know which PoIdBroker implementation to load, and where the service is located. This information can be provided by a PropertyManager which is instantiated in the initialization sequence, and loads the properties file. Loading the broker implementation class may result in exceptions. If we follow a straight singleton pattern for PoIdGenerator, this can be problematic. Ideally we would want clients to be aware of any exceptions when the service component is first initialized, but not have to deal with these exceptions on subsequent calls to the service.
One way to handle this is to provide a public static initialize() method that throws a checked exception when initialization fails. A subsequent call to the public static getInstance() will provide access to the singleton instance, but will not throw a checked exception.
What should happen when a client requests a POID? The first request for a particular persistent object type will require PoIdGenerator to call the PoIdBroker to reserve a block for that type. For convenience, we have introduced the non public helper class PoIdBlock (Figure 1). The block maintains the value of the next valid identifier, and will provide the next PoId with the next() method. Over time, the PoIdGenerator will cache these blocks in a HashMap keyed by the type string. Clients will need to handle exceptions when requesting PoIds, since getting a PoId could result in a remote call. Our refinement leads to the detailed class and sequence diagrams in Figures 3 and 4 respectively.
Figure 3. Detailed class diagram
Figure 4. Detailed sequence diagram
That wraps up our first installment on the Persistent Object ID service. At this point, the client API has been defined, and client components supporting a "pluggable" PoidBroker are complete. Next time, we will look at using WebSphere Studio Application Developer to create the backend EJBs that manage POID transactions with a database, and then implement the middle layer, including a PoidBroker proxy that uses a SOAP service to communicate with the back end.
Enterprise-Ready Object IDs
, Scott Ambler, Software Development, December 1999
Tim Biernat has held various research and development positions at General Dynamics and Motorola, and has taught internationally for IBM and Learning Tree International. Tim was trained as an Electrical Engineer at Marquette University, Purdue and the University of Texas, and his professional experience spans 20 years, including research in fault tolerant real-time software, and development of many distributed object business systems. Currently, Tim is President of Software Mentor LLC, a distributed object technology consulting firm based in Milwaukee, Wisconsin, USA. You can contact Tim at firstname.lastname@example.org.