|
|
CORBA Server Application Concepts
This topic includes the following sections:
Each of the topics in this section give procedures for and examples of building CORBA server applications that take advantage of various BEA Tuxedo software features. For background information about BEA Tuxedo CORBA server applications and how they work, see Getting Started with BEA Tuxedo CORBA Applications.
The Entities You Create to Build a CORBA Server Application
To build a CORBA server application, you create the following two entities:
There are also a number of files that you work with that are generated by the IDL compiler and that you build into a CORBA server application. These files are listed and described in Steps for Creating a BEA Tuxedo CORBA Server Application.
The sections that follow provide introductory information about these entities. For complete details about how to generate these components, see Steps for Creating a BEA Tuxedo CORBA Server Application.
The Implementation of the CORBA Objects for Your Server Application
Having a clear understanding of what CORBA objects are, and how they are defined, implemented, instantiated, and managed is critical for the person who is designing or creating a CORBA server application.
The CORBA objects for which you have defined interfaces in the Object Management Group Interface Definition Language (OMG IDL) contain the business logic and data for your CORBA server applications. All client application requests involve invoking an operation on a CORBA object. The code you write that implements the operations defined for an interface is called an object implementation. For example, in C++, the object implementation is a C++ class.
This topic includes the following sections:
How Interface Definitions Establish the Operations on a CORBA Object
A CORBA object's interface identifies the operations that can be performed on it. A distinguishing characteristic of CORBA objects is that an object's interface definition is separate from its implementation. The definition for the interface establishes how the operations on the interface must be implemented, including what the valid parameters are that can be passed to and returned from an operation.
An interface definition, which is expressed in OMG IDL, establishes the client/server contract for an application. That is, for a given interface, the server application is bound to do the following:
How the server application implements the operations may change over time. This is acceptable behavior as long as the server application continues to meet the requirement of implementing the defined interface and using the defined parameters. In this way, the client stub is always a reliable proxy for the object implementation on the server machine. This underscores one of the key architectural strengths of CORBA—that you can change how a server application implements an object over time without requiring the client application to be modified or even to be aware that the object implementation has changed.
The interface definition also determines the content of both the client stub and the skeleton in the server application; these two entities, in combination with the ORB and the Portable Object Adapter (POA), ensure that a client request for an operation on an object can be routed to the code in the server application that can satisfy the request.
Once the system designer has specified the interfaces of the business objects in the application, the programmer's job is to implement those interfaces. This book explains how.
For more information about OMG IDL, see Creating CORBA Client Applications.
How You Implement the Operations on a CORBA Object
As stated earlier, the code that implements the operations defined for a CORBA object's interface is called an object implementation. For C++, this code consists of a set of methods, one for each of the operations defined for the interfaces in your application's OMG IDL file. The file containing the set of object implementations for your application is known as an implementation file. The BEA Tuxedo system provides an IDL compiler, which compiles your application's OMG IDL file to produce several files, one being an implementation file, shown in the following figure.
The generated implementation file contains method templates, method declarations, object constructors and destructors, and other data that you can use as a starting place for writing your application's object implementations. For example, in C++, the generated implementation file contains signatures for each interface's methods. You enter the business logic for each method in this file, and then provide this file as input to the command that builds the executable server application file.
How Client Applications Access and Manipulate Your Application's CORBA Objects
Client applications access and manipulate the CORBA objects managed by the server application via object references to those objects. Client applications invoke operations (that is, requests) on an object reference. These requests are sent as messages to the server application, which invokes the appropriate operations on CORBA objects. The fact that these requests are sent to the server application and invoked in the server application is completely transparent to the client; client applications appear simply to be making invocations on the client stub.
Client applications may manipulate a CORBA object only by means of an object reference. One primary design consideration is how to create object references and return them to the client applications that need them in a way that is appropriate for your application.
Typically, object references to CORBA objects are created in the BEA Tuxedo system by factories. A factory is any CORBA object that returns, as one of its operations, a reference to another CORBA object. You implement your application's factories the same way that you implement other CORBA objects defined for your application. You can make your factories widely known to the BEA Tuxedo domain, and to clients connected to the BEA Tuxedo domain, by registering them with the FactoryFinder. Registering a factory is an operation typically performed by the Server object, which is described in the section The Server Object. For more information about designing factories, see the section Generating Object References.
The Content of an Object Reference
From the client application's perspective, an object reference is opaque; it is like a black box that client applications use without having to know what is inside. However, object references contain all the information needed for the BEA Tuxedo system to locate a specific object instance and to locate any state data that is associated with that object.
An object reference contains the following information:
This is the Interface Repository ID of the object's OMG IDL interface.
The OID uniquely identifies the instance of the object to which the reference applies. If the object has data in external storage, the OID also typically includes a key that the server machine can use to locate the object's data.
The group ID identifies the server group to which the object reference is routed when a client application makes a request using that object reference. Generating a nondefault group ID is part of a key BEA Tuxedo feature called factory-based routing, which is described in the section Factory-based Routing.
Note: The combination of the three items in the preceding list uniquely identifies the CORBA object. It is possible for an object with a given interface and OID to be simultaneously active in two different groups, if those two groups both contain the same object implementation. If you need to guarantee that only one object instance of a given interface name and OID is available at any one time in your domain, either: use factory-based routing to ensure that objects with a particular OID are always routed to the same group, or configure your domain so that a given object implementation is in only one group. This assures that if multiple clients have an object reference containing a given interface name and OID, the reference is always routed to the same object instance.
For more information about factory-based routing, see the section Factory-based Routing.
The Lifetime of an Object Reference
Object references created by server applications running in a BEA Tuxedo domain typically have a usable lifespan that extends beyond the life of the server process that creates them. BEA Tuxedo object references can be used by client applications regardless of whether the server processes that originally created them are still running. In this way, object references are not tied to a specific server process.
An object reference created with the TP::create_active_object_reference() operation is valid only for the lifetime of the server process in which it was created. For more information, see the section Preactivating an Object with State.
Passing Object Instances
The ORB cannot marshal an object instance as an object reference. For example, passing a factory reference in the following code fragment generates a CORBA marshal exception in the BEA Tuxedo system:
connection::setFactory(this);
To pass an object instance, you should create a proxy object reference and pass the proxy instead, as in the following example:
CORBA::Object myRef = TP::get_object_reference();
ResultSetFactory factoryRef = ResultSetFactoryHelper::_narrow(myRef);
connection::setFactoryRef(factoryRef);
How You Instantiate a CORBA Object at Run Time
When a server application receives a request for an object that is not mapped in the server machine's memory (that is, the object is not active), the TP Framework invokes the Server::create_servant()operation. The Server::create_servant()operation is implemented in the Server object, which, as mentioned in the section The Implementation of the CORBA Objects for Your Server Application, is a component of a CORBA server application that you create.
The Server::create_servant()operation causes an instance of the CORBA object implementation to be mapped into the server machine's memory. This instance of the object's implementation is called the object's servant. Formally speaking, a servant is an instance of the C++ class that implements an interface defined in the application's OMG IDL file. The servant is generated via the C++ new statement that you write in the Server::create_servant()operation.
After the object's servant has been created, the TP Framework invokes the Tobj_ServantBase::activate_object() operation on the servant. The Tobj_ServantBase::activate_object() operation is a virtual operation that is defined on the Tobj_ServantBase base class, from which all object implementation classes inherit. The TP Framework invokes the Tobj_ServantBase::activate_object() operation to tie the servant to an object ID (OID). (Conversely, when the TP Framework invokes the Tobj_ServantBase::deactivate_object() operation on an object, the servant's association with the OID is broken.)
If your object has data on disk that you want to read into memory when the CORBA object is activated, you can have that data read by defining and implementing the Tobj_ServantBase::activate_object() operation on the object. The Tobj_ServantBase::activate_object() operation can contain the specific read operations required to bring an object's durable state into memory. (There are circumstances in which you may prefer instead to have an object's disk data read into memory by one or more separate operations on the object that you may have coded in the implementation file. For more information, see the section Reading and Writing an Object's Data.) After the invocation of the Tobj_ServantBase::activate_object() operation is complete, the object is said to be active.
This collection of the object's implementation and data compose the run-time, active instance of the CORBA object.
Servant Pooling
Servant pooling provides your CORBA server application the opportunity to keep a servant in memory after the servant's association with a specific OID has been broken. When a client request that can be satisfied with a pooled servant arrives, the TP Framework bypasses the TP::create_servant operation and creates a link between the pooled servant and the OID provided in the client request.
Servant pooling thus provides the CORBA server application with a means to minimize the costs of reinstantiating a servant each time a request arrives for an object that can be satisfied by that servant. For more information about servant pooling and how to use it, see the section Servant Pooling.
Note: Servant pooling was first introduced in release 4.2 of the WebLogic Enterprise product.
The Server Object
The Server object is the other programming code entity that you create for a CORBA server application. The Server object implements operations that execute the following tasks:
You create the Server object from scratch, using a common text editor. You then provide the Server object as input into the server application build command, buildobjserver. For more information about creating the Server object, see Steps for Creating a BEA Tuxedo CORBA Server Application.
Process for Developing CORBA Server Applications
This section presents important background information about the following topics, which have a major influence on how you design and implement CORBA server applications:
It is not essential that you read these topics before proceeding to the next chapter; however, this information is located here because it applies broadly to fundamental design and implementation issues for all CORBA server applications.
Generating Object References
One of the most basic functions of a CORBA server application is providing client applications with object references to the objects they need to execute their business logic. CORBA client applications typically get object references to the initial CORBA objects they use from the following two sources:
Client applications use the Bootstrap object to resolve initial references to a specific set of objects in the BEA Tuxedo domain, such as the FactoryFinder and the SecurityCurrent objects. The Bootstrap object is described in Getting Started with BEA Tuxedo CORBA Applications and Creating CORBA Client Applications.
Factories, however, are designed, implemented and registered by you, and they provide the means by which client applications get references to objects in the CORBA server application, particularly the initial server application object. At its simplest, a factory is a CORBA object that returns an object reference to another CORBA object. The client application typically invokes an operation on a factory to obtain an object reference to a CORBA object of a specific type. Planning and implementing your factories carefully is an important task when developing CORBA server applications.
How Client Applications Find Your Server Application's Factories
Client applications are able to locate via the FactoryFinder the factories managed by your server application. When you develop the Server object, you typically include code that registers with the FactoryFinder any factories managed by the server application. It is via this registration operation that the FactoryFinder keeps track of your server application's factories and can provide object references to them to the client applications that request them. We recommend that you use factories and register them with the FactoryFinder; this model makes it simple for client applications to find the objects in your CORBA server application.
Creating an Active Object Reference
An active object reference is a feature that gives an alternate means through which your server application can generate object references. Active object references are not typically created by factories as described in the previous section, and active object references are meant for preactivating objects with state. The next section discusses object state in more detail.
While an object associated with a conventional object reference is not instantiated until a client application makes an invocation on the object, the object associated with an active object reference is created and activated at the time the active object reference is created. Active object references are especially convenient for specific purposes, such as iterator objects. The section Preactivating an Object with State provides more information about active object references.
Note: The active object reference feature was first introduced in WebLogic Enterprise version 4.2.
Managing Object State
Object state management is a fundamentally important concern of large-scale client/server systems, because it is critical that such systems optimize throughput and response time. The majority of high-throughput applications, such as applications you run in a BEA Tuxedo domain, tend to be stateless, meaning that the system flushes state information from memory after a service or an operation has been fulfilled.
Managing state is an integral part of writing CORBA-based server applications. Typically, it is difficult to manage state in these server applications in a way that scales and performs well. The BEA Tuxedo software provides an easy way to manage state and simultaneously ensure scalability and high performance.
The scalability qualities that you can build into a CORBA server application help the server application function well in an environment that includes hundreds or thousands of client applications, multiple machines, replicated server processes, and a proportionately greater number of objects and client invocations on those objects.
About Object State
In a BEA Tuxedo domain, object state refers specifically to the process state of an object across client invocations on it. The BEA Tuxedo software uses the following definitions of stateless and stateful objects:
Both stateless and stateful objects have data; however, stateful objects may have nonpersistent data in memory that is required to maintain context (state) between operation invocations on those objects. Thus, subsequent invocations on such a stateful object always go to the same servant. Conversely, invocations on a stateless object can be directed by the BEA Tuxedo system to any available server process that can activate the object.
State management also involves how long an object remains active, which has important implications on server performance and the use of machine resources. The duration of an active object is determined by object activation policies that you assign to an object's interface, described in the section that follows.
Object state is transparent to the client application. Client applications implement a conversational model of interaction with distributed objects. As long as a client application has an object reference, it assumes that the object is always available for additional requests, and the object appears to be maintained continuously in memory for the duration of the client application interaction with it.
To achieve optimal application performance, you need to carefully plan how your application's objects manage state. Objects are required to save their state to durable storage, if applicable, before they are deactivated. Objects must also restore their state from durable storage, if applicable, when they are activated. For more information about reading and writing object state information, see the section Reading and Writing an Object's Data.
Note: BEA Tuxedo Release 8.0 provides support for parallel objects, as a performance enhancement. This feature allows you to designate all business objects in a particular application as stateless objects. For complete information, see the section TP Framework in the CORBA Programming Reference.
Object Activation Policies
The BEA Tuxedo software provides three object activation policies that you can assign to an object's interface to determine how long an object remains in memory after it has been invoked by a client request. These policies determine whether the object to which they apply is generally stateless or stateful.
The three policies are listed and described in the following table.
You determine what events cause an object to be deactivated by assigning object activation policies. For more information about how you assign object activation policies to an object's interface, see the section Step 4: Define the In-memory Behavior of Objects.
Application-controlled Deactivation
The BEA Tuxedo software also provides a feature called application-controlled deactivation, which provides a means for an application to deactivate an object during run time. The BEA Tuxedo software provides the TP::deactivateEnable() operation, which a process-bound object can invoke on itself. When invoked, the TP::deactivateEnable() operation causes the object in which it exists to be deactivated upon completion of the current client invocation on that object. An object can invoke this operation only on itself; you cannot invoke this operation on any object but the object in which the invocation is made.
The application-controlled deactivation feature is particularly useful when you want an object to remain in memory for the duration of a limited number of client invocations on it, and you want the client application to be able to tell the object that the client is finished with the object. At this point, the object takes itself out of memory.
Application-controlled deactivation, therefore, allows an object to remain in memory in much the same way that a process-bound object can: the object is activated as a result of a client invocation on it, and it remains in memory after the initial client invocation on it is completed. You can then deactivate the object without having to shut down the server process in which the object exists.
An alternative to application-controlled deactivation is to scope a transaction to maintain a conversation between a client application and an object; however, transactions are inherently more costly, and transactions are generally inappropriate in situations where the duration of the transaction may be indefinite.
A good rule of thumb to use when choosing between application-controlled deactivation and transactions for a conversation is whether there are any disk writing operations involved. If the conversation involves read-only operations, or involves maintaining state only in memory, then application-controlled deactivation is appropriate. If the conversation involves writing data to disk during or at the end of the conversation, transactions may be more appropriate.
Note: If you use application-controlled deactivation to implement a conversational model between a client application and an object managed by the server application, make sure that the object eventually invokes the TP::deactivateEnable() operation. Otherwise, the object remains idle in memory indefinitely. (Note that this can be a risk if the client application crashes before the TP::deactivateEnable() operation is invoked. Transactions, on the other hand, implement a timeout mechanism to prevent the situation in which the object remains idle for an indefinite period. This may be another consideration when choosing between the two conversational models.)
You implement application-controlled deactivation in an object using the following procedure:
Reading and Writing an Object's Data
Many of the CORBA objects managed by the server application may have data that is in external storage. This externally stored data may be regarded as the persistent or durable state of the object. You must address durable state handling at appropriate points in the object implementation for object state management to work correctly.
Because of the wide variety of requirements you may have for your client/server application with regards to reading and writing an object's durable state, the TP Framework cannot automatically handle durable object state on disk. In general, if an object's durable state is modified as a result of one or more client invocations, you must make sure that durable state is saved before the object is deactivated, and you should plan carefully how the object's data is stored or initialized while the object is active.
The sections that follow describe the mechanisms available to you to handle an object's durable state, and give some general advice how to read and write object state under specific circumstances. The specific topics presented include:
How you choose to read and write durable state invariably depends on the specific requirements of your client/server application, especially with regard to how the data is structured. In general, your priority should be to minimize the number of disk operations, especially where a database controlled by an XA resource manager is involved.
Available Mechanisms for Reading and Writing an Object's Durable State
Table 1-1 and Table 1-2 describe the available mechanisms for reading and writing an object's durable state.
Table 1-1 Available Mechanisms for Reading an Object's Durable State
Table 1-2 Available Mechanisms for Writing an Object's Durable State
Note: If you use the Tobj_ServantBase::deactivate_object() operation to write any durable state to disk, any errors that occur while writing to disk are not reported to the client application. Therefore, the only circumstances under which you should write data to disk in this operation is when: the object is transaction-bound (that is, it has the transaction activation policy assigned to it), or you scope the disk write operations within a transaction by invoking the TransactionCurrent object. Any errors encountered while writing to disk during a transaction can be reported back to the client application. For more information about using the Tobj_ServantBase::deactivate_object() operation to write object state to disk, see the section Caveat for State Handling in Tobj_ServantBase::deactivate_object(). Reading State at Object Activation Using the Tobj_ServantBase::activate_object() operation on an object to read durable state may be appropriate when either of the following conditions exist:
The advantages of using the Tobj_ServantBase::activate_object() operation to read durable state include:
Reading State Within Individual Operations on an Object
With all objects, regardless of activation policy, you can read durable state in each operation that needs that data. That is, you handle the reading of durable state outside the Tobj_ServantBase::activate_object()operation. Cases where this approach may be appropriate include the following:
For example, consider an object that represents a customer's investment portfolio. The object contains several discrete records for each investment. If a given operation affects only one investment in the portfolio, it may be more efficient to allow that operation to read the one record than to have a general-purpose Tobj_ServantBase::activate_object() operation that automatically reads in the entire investment portfolio each time the object is invoked.
Stateless Objects and Durable State
In the case of stateless objects—that is, objects defined with the method activation policy—you must ensure the following:
The TP Framework invokes the Tobj_ServantBase::activate_object() operation on an object at activation. If an object has an OID that contains a key to the object's durable state on disk, the Tobj_ServantBase::activate_object() operation provides the only opportunity the object has to retrieve that key from the OID.
If you have a stateless object that you want to be able to participate in a transaction, we generally recommend that if the object writes any durable state to disk that it be done within individual methods on the object. However, if you have a stateless object that is always transactional—that is, a transaction is always scoped when this object is invoked—you have the option to handle the database write operations in the Tobj_ServantBase::deactivate_object() operation, because you have a reliable mechanism in the XA resource manager to commit or roll back database write operations accurately.
Note: Even if your object is method-bound, you may have to take into account the possibility that two server processes are accessing the same disk data at the same time. In this case, you may want to consider a concurrency management technique, the easiest of which is transactions. For more information about transactions and transactional objects, see Chapter 6, "Integrating Transactions into a CORBA Server Application."
Servant Pooling and Stateless Objects
Servant pooling is a particularly useful feature for stateless objects. When your CORBA server application pools servants, you can significantly reduce the costs of instantiating an object each time a client invokes it. As mentioned in the section Servant Pooling, a pooled servant remains in memory after a client invocation on it is complete. If you have an application in which a given object is likely to be invoked repeatedly, pooling the servant means that only the object's data, and not its methods, needs to be read into and out of memory for each client invocation. If the cost associated with reading an object's methods into memory is high, servant pooling can reduce that cost.
For information about how to implement servant pooling, see the section Servant Pooling.
Stateful Objects and Durable State
For stateful objects, you should read and write durable state only at the point where it is needed. This may introduce the following optimizations:
In general, transaction-bound objects must depend on the XA resource manager to handle all database write or rollback operations automatically.
Note: For objects that are involved in a transaction, we do not support having those objects write data to external storage that is not managed by an XA resource manager.
For more information about objects and transactions, see Integrating Transactions into a CORBA Server Application
Servant Pooling and Stateful Objects
Servant pooling does not make sense in the case of process-bound objects; however, depending on your application design, servant pooling may provide a performance improvement for transaction-bound objects.
Your Responsibilities for Object Deactivation
As mentioned in the preceding sections, you implement the Tobj_ServantBase::deactivate_object() operation as a means to write an object's durable state to disk. You should also implement this operation on an object as a means to flush any remaining object data from memory so that the object's servant can be used to activate another instance of that object. You should not assume that an invocation to an object's Tobj_ServantBase::deactivate_object() operation also results in an invocation of that object's destructor.
Avoiding Unnecessary I/O
Be careful not to introduce inefficiencies into the application by doing unnecessary I/O in objects. Situations to be aware of include the following:
A general optimization is to initialize a dirtyState flag on activation and to write data in the Tobj_ServantBase::deactivate_object() operation only if the flag has been changed while the object was active. (Note that this works only if you can be assured that the object is always activated in the same server process.)
Sample Activation Walkthrough
For examples of the sequence of activity that takes place when an object is activated, see Getting Started with BEA Tuxedo CORBA Applications.
Using Design Patterns
It is important to structure the business logic of your application around a well-formed design. The BEA Tuxedo software provides a set of design patterns to address this need. A design pattern is simply a structured solution to a specific design problem. The value of a design pattern lies in its ability to be expressed in a form you can reuse and apply to other design problems.
The BEA Tuxedo design patterns are structured solutions to enterprise-class application design problems. You can use them to design successful large-scale client/server applications.
The design patterns summarized here are a guide to using good design practices in CORBA client and server applications. They are an important and integral part of designing CORBA client and server applications, and the chapters in this book show examples of using these design patterns to implement the University sample applications.
Process-Entity Design Pattern
The Process-Entity design pattern applies to a large segment of enterprise-class client/server applications. This design pattern is referred to as the flyweight pattern in Object-Oriented Design Patterns, Gamma et al., and as the Model-View-Controller in other publications.
In this pattern, the client application creates a long-lived process object that the client application interacts with to make requests. For example, in the University sample applications, this object might be the registrar that handles course browsing operations on behalf of the client application. The courses themselves are database entities and are not made visible to the client application.
The advantages of the Process-Entity design pattern include:
An example of applying the Process-Entity design pattern is described in Designing and Implementing a Basic CORBA Server Application. For complete details on the Process-Entity design pattern, see Technical Articles.
List-Enumerator Design Pattern
The List-Enumerator design pattern also applies to a large segment of enterprise-class client/server applications. The List-Enumerator design pattern leverages a key BEA Tuxedo feature, application-controlled object deactivation, to handle a cache of data that is stored and tracked in memory during several client invocations, and then to flush the data from memory when the data is no longer needed.
An example of applying the List-Enumerator design pattern is described in Designing and Implementing a Basic CORBA Server Application.
Object preactivation, which is an especially useful tool for implementing the List-Enumerator design, is described in the section Preactivating an Object with State.
|
Copyright © 2001 BEA Systems, Inc. All rights reserved.
|