*

JBOF Tutorial Overview

This document consists of four separate tutorials that will take you through developing a simple banking application one step at a time, introducing a different aspect of the Bojangles Programming Model at each step.

The first tutorial concentrates on development of a simple application to quickly take you through the entire development process. In it you will learn how simple it is to use Java, along with four basic classes in the Managed Object Framework (MOFW), a set of interfaces at the heart of the Bojangles programming model. MOFW interfaces are usually paired around an essential application development task, with one interface for the "client" and one for the "server". After introducing Java, this tutorial will focus on Manageable and EDStream interfaces that enable generic bootstrapping (i.e instantiation or activatation) of "root" objects anchoring the application. Next, the Managed and BaseCollection interfaces are introduced; these interfaces enable entire collections of objects to be managed.

The second tutorial builds upon the first, introducing additional MOFW interfaces to enable development of entire frameworks of objects.

1. Developing a simple application

Using Bojangles to develop an application is very similar to the way an automobile is built on an assembly line: one step at a time from reusable components. Also like an assembly line, Bojangles includes tools to automate much of this process. An important difference is that at each step, the resulting Bojangles component is immediately useful (instead of having to wait until the very end when the car is complete to drive it away).

This tutorial will take you through capturing some business logic of a domain into a Java object, then subclass it to make it generically Manageable by a client. The resulting component will be extended so that it is Managed within a BaseCollection that provides various services like identity, persistence and transactions. Distribution is added in the final step, so that business objects can be used by remote client applications. It is added following a similar pattern to that making a Managed object.

There will be a separate subsection for each of the four steps enumerated above followed by a section to summarize the key concepts.

1.1. Java applications and applets

This section is not intended to be a complete tutorial on Java. Instead, it is meant to show how easy it is to implement and test business objects within a pure Java environment (i.e. standalone applications) as well as integrated within a Web environment (i.e. Java applets).

1.1.1. Building a business object

For purposes of understanding the power of Java, we will begin by developing a simple Account "business" object (BO) that has a balance with three methods that manage it, along with a constructor to allow new instances to be created by an application:
package COM.ibm.jaws.tutorial1.example1;

public class Account
{
    protected float balance = 0;

    public
    Account(float initialBalance)
    {
        balance = initialBalance;
    }

    public void
    deposit(float amount)
    {
        balance += amount;
    }

    public void
    withdrawal(float amount)
    {
        balance -= amount;
    }

    public float
    getBalance()
    {
        return balance;
    }
}

The Account BO as shown is rather simple by design. Later versions of this implementation (in the subsequent tutorials) will check for and handle negative balances on the withdrawal method to illustrate some of the advanced features MOFW like relationships and exceptions.

1.1.2. Using BO's in an application

A Java application is simply a special class that can be run directly by the Java virtual machine interpreter (e.g. via java <.applicationClass>.). The only thing "special" about this class is that it has a static (capable of being executed by the interpreter without creating an instance) main() method that encapsulates the application logic.

For this example, we offer an application that tests all the methods of the Account BO above:

 
import COM.ibm.jaws.tutorial1.example1.Account;

public class AccountTest
{
    public static void main(String args[])
    {
    // Get the checking, savings and transfer amount from the command 
    // line arguments
        float cAmount = Float.valueOf(args[0]).floatValue();
        float sAmount = Float.valueOf(args[1]).floatValue();
        float tAmount = Float.valueOf(args[2]).floatValue();

    // Initialize the accounts from the constructor
        Account checking = new Account(cAmount);
        Account savings = new Account(sAmount);

    // Make the transfer
        checking.withdrawal(tAmount);
        savings.deposit(tAmount);

    // Print the results (use subroutine for modularity)
        printBalance("Checking", checking);
        printBalance("Savings", savings);
    }

    public static void
    printBalance(String name, Account account)
    {
        System.out.println(
            name + 
            " account Balance = " +
            account.getBalance()
        );
    }
}

As the code shows, AccountTest initializes two accounts from balances entered on the command line, transfers a third amount specified from the "savings" to the "checking", and then displays the resulting balances with verification that the proper transfer took place.

The two classes (Account and AccountTest can be compiled using the Java compiler and executed with the following result:

javac Account.java

javac AccountTest.java

java AccountTest 10 20 5        
Checking account balance = 5
Savings account balance = 25

One value add of Java illustrated by this example is that there was no need to use a complicated makefile or build a Dynamic Load Library (DLL) in order use the Account BO class within the application.

1.1.3. Using BO's in an applet

Another value add of Java is that it can be used to develop special kinds of applications (called applets) that are executable within the HTML describing a Web page. This feature means that it is relatively easy to add a Graphical User Interface (GUI) to your applications.

Further, the Web browser and Java interpreter work together to download the various applet and business object classes to the client as needed, making Java based applications rather simple to administer.

The primary difference between an applet and an application is in how the applet is invoked (via the init() rather than main() method) and how the applet gets its parameters and displays its results (via special Java supplied library objects). The following code shows a "conversion" of the application shown above into an applet.


Once the applet has been defined and "installed" into the appropriate 
directory, it can be invoked via HTML like the following:
<.html>.
<.head>.
<.title>.SWGTC ATM Applet Demonstration<./title>.
<./head>.
<.body>.
<.center>.
<.h3>.Applet (Automated Teller Machine)<./h3>.

<.applet
name="ATMApplet"
code="COM.ibm.jaws.tutorial1.example1.ATMApplet"
codebase="/"
width=500
height=250>.
<./applet>.

<./center>.
<./body>.
<./html>.

By "browsing" the Web page with a Java enabled browser, the applet can be executed with a GUI rather than via a "batch" command line process.

1.2. Manageable objects and EDStreams

The previous section of this tutorial provides a brief overview of some of the power of Java; it also should serve as an illustration why Java alone is not enough to implement a robust application. The most obvious shortcoming is that both Accounts are created anew each time the test runs (run either test over again to "prove" this: the balances are re-initialized with each execution).

This section of the tutorial shows how the Bojangles programming model enables basic object services to be added without affecting the business logic in any way by transforming the BO into a Manageable object. A Manageable object is one that can enables the client application to initialize the object (via internalizeFromStream()), and export its essential data (through the externalizeToStream() method). Since the client can choose the implementation of the InputEDStream from which the data is imported, or the OutputEDStream into which it is exported, services like persistence, caching, and recovery can be generically (and efficiently) enabled.

The steps to transforming a BO into a Manageable object, and coding applications to use them are straightforward:

  1. Separate the interface from the implementation.
  2. Implement the Manageable interface methods.
  3. Choose EDStream implementations to match application needs.

This section will take you through each of the above topics one at a time.

1.2.1. Separating interfaces from implementations

The main reason to separate the interface from the implementation is to allow client programs to declare and use objects of the interface type so that any implementation supporting the interface can be substituted later, mimimizing changes to the client applications. We will see in later sections of this tutorial that one such set of "clients" are the services in the Managed Object Framework (MOFW) making up the heart of the Bojangles Programming Model.

In any event, separating the interface from the implementation is a relatively simple task that entails:

The following shows the Account class transformed into an interface:

package COM.ibm.jaws.tutorial1.example2;
include COM.ibm.jaws.mofw.Manageable;

public interface Account extends Manageable
{
    public void
    deposit(float amount);
        
    public void
    withdrawal(float amount);

    public float
    getBalance();
}

1.2.2. Implementing the Manageable interface

In order to support generic create operations using dynamically activated classes, the BO needs to include a default constructor (one that has no input parameters), if it does not already have one. Then, to allow the generically created instance to be initialized, the BO implementation needs to include the Manageable interface methods internalizeFromStream() and externalizeToStream().

Implementing the Manageable interface methods entail identifying the essential data of the object (i.e., internal variables that cannot be derived from other sources) that must be written and read as part of the Manageable method implementations. For the AccountImpl, the essential data is simply the balance variable, and (including the default constructor) results in the following "Manageable" version:

package COM.ibm.jaws.tutorial1.example2;

public class AccountImpl 
extends COM.ibm.jaws.tutorial1.example1.Account
implements Account
{
// Constructors added
    public
    AccountImpl(float initialBalance)
    {
            super(initialBalance);
    }

    public
    AccountImpl()
    {
    }

// Manageable method implementations
    public void 
    internalizeFromStream(InputEDStream source)
    {
        balance = source.readFloat();
    }

    public void
    externalizeToStream(OutputEDStream sink)
    {
        sink.writeFloat(balance);
    }
}

1.2.3. Choosing an EDStream implementation

There are two ways to activate (i.e. instantiate) a Manageable object in a client application. One is to use a specific constructor just as we did in the pure Java case. Another is to use the default constructor and the internalizeFromStream() method to load it from an class that implements the InputEDStream interface. When using the "streaming" mechanism, the client application is responsible for choosing an implementation and insuring that it is "loaded" with the essential data of the object(s) to be activated.

The following test case exercises both mechanisms. It also shows how an InputEDStream can be used to simplify various "parsing" tasks, like converting the string command line arguments into the forms expected by the logic of the application.

include COM.ibm.jaws.motk.StringArrayStream;
include COM.ibm.jaws.tutorial1.example2.Account;
include COM.ibm.jaws.tutorial1.example2.AccountImpl;
include COM.ibm.jaws.motk.TransientStream;

public class AccountTest
{
    public static void main(String args[])
    {
    // Get the arguments into a stream to simplify conversion
        StringArrayStream s = new StringArrayStream(args);

    // Activate checking account using the specific constructor
    // Note how the command line argument is directly converted
        Account checking = new AccountImpl(s.readFloat());

    // Activate savings account using default constructor and stream
    // Note: the command line arguments in the stream are "parsed"
    // directly into the object during the internalize
        Account savings = new AccountImpl();
        savings.internalizeFromStream(s);

    // Make the transfer
        float transfer = s.readFloat();
        checking.withdrawal(transfer);
        savings.deposit(transfer);

    // Check and print the balances
        checkBalance("Checking", checking);
        checkBalance("Savings", savings);
    }

    public static void
    checkBalance(String name, Account account)
    {
    // Print balance
        float aBalance = account.getBalance();
        System.out.println(
            name + 
            " account Balance = " +
            aBalance
        );

    // Check balance by externalizing to and reading from a stream
        TransientStream s = new TransientStream();
        account.externalizeToStream(s);
        if (aBalance != s.readFloat())
        {
            System.out.println("\tExternalize NOT OK!");
        }
    }
}

We will leave it as an exercise to the interested reader to modify the test application above to use one of the persistent InputEDStream implementations found in the Bojangles Managed Object Toolkit (MOTK). This extention will doubtless add logic to write the updated essential state using an appropriate OutputEDStream implementation.

1.3. Managed objects and BaseCollections

Using streaming as a "poor man's persistence" is a useful mechanism to bootstrap (i.e. start up) one or two objects that serve as the "anchor" for the rest of the application. However, it is not the mechanism one would choose to activate most business objects, Manageable or otherwise.

For this purpose, MOFW relies on objects called BaseCollections. In general, the purpose of a BaseCollection is to "manage" objects (called Managed objects). It supports methods that allow Managed objects to be created, activated and queried as part of your method implementations and application programs. A beneficial side effect of accessing business objects through a BaseCollection is that it can add services including but not limited to those like identity, persistence, transactions, concurrency, trace, debug, profiling and load balancing.

1.3.1. Choosing a BaseCollection implementation

The wide variety of possible services (called quality of service or more simply QoS) means that the choice of BaseCollection implementation is crucial to the overall application. Each implementation will make certain design trade-offs to achieve the advertised QoS. The "cost" to gain this level of transparency is that there are some basic transformations you must make to your Manageable objects when they are configured into a BaseCollection.

1.3.2. Transforming Manageable objects into Managed ones

The details of making a Managed object will depend on the BaseCollection implementation chosen, but the basic idea is to add logic "before" and possibly "after" invoking methods on the Manageable object subclassed, using the implementation and possibly the interface (developed in the previous step). There are also methods added to manage the Identity and Lifecycle of the object along with any private methods needed to provide the advertised QoS (more on these topics in the next tutorial). It is important to note that some implementations use design patterns that require more than one class to be created.

Bojangles has tools that automate this process for the BaseCollection implementations that we have provided. A later tutorial topic will describe how custom object service implementations can take advantage of our tool set if desired.

1.3.3. Developing applications that use configured BaseCollections

The changes to the test case to exercise this logic are relatively simple. The main differences between it and the version testing the Manageable interface are: a) code to activate the BaseCollection chosen (FilepoBCImpl in this case), and b) code to first try and locate the checking and savings accounts (using resolve()) before creating them (using createFromCopy() or createFromData()). The complete example follows in whch we also added logic to "deposit" the amount from the command line into the accounts if they already exist (instead of using it to initialize them):

include COM.ibm.jaws.motk.StringArrayStream;
include COM.ibm.jaws.tutorial1.example2.Account;
include COM.ibm.jaws.tutorial1.example2.AccountImpl;
include COM.ibm.jaws.motk.FilepoBCImpl;
include COM.ibm.jaws.mofw.BaseCollection;

public class AccountTest
{
    public static void main(String args[])
    {
    // Get the arguments into a stream to simplify conversion
        StringArrayStream s = new StringArrayStream(args);
 
    // Activate the BC holding the accounts
        BaseCollection accounts = new FilepoBCImpl(
            "accounts",
            "COM.ibm.jaws.model.AccountFilepoManaged"
        );

    // Try to find the checking account in the collection first
        Account checking = (Account)accounts.resolve("Checking");
        if (checking == null)
        {
        // Since the checking account didn't exist, create a new one
        // Note: this version exercises the create from copy
        // which initializes from a "Manageable" local copy
            checking = new AccountImpl(cAmount);
            checking = (Account) accounts.createFromCopy(
                "Checking", checking, null
            );
        }
        else
        {
        // Since the checking account already exists, add to it
            checking.deposit(s.readFloat());
        }

    // Try to find the savings account in the collection first
        Account savings = (Account) accounts.resolve("Savings");
        if (savings == null)
        {
        // Since the savings account didn't exist, create a new one
        // Note: this version exercises the create from data
        // which takes the data directly from a stream
            savings = (Account) accounts.createFromData(
                "Savings", s, null
            );
        }
        else
        {
        // Since the savings account already exists, add to it
            savings.deposit(s.readFloat());
        }

    // Make the transfer
        float transfer = s.readFloat();
        checking.withdrawal(transfer);
        savings.deposit(transfer);

    // Check and print the balances
        checkBalance("Checking", checking);
        checkBalance("Savings", savings);
    }

    public static void
    checkBalance(String name, Account account)
    {
    // Print balance
        float aBalance = account.getBalance();
        System.out.println(
            name + 
            " account Balance = " +
            aBalance
        );
    }
}

This example used both versions of the create on the BaseCollection. The savings account was created (if necessary) from directly from data in a stream, while the checking account was created (also only when not found) from a "local" copy. It is important to realize that the latter method is merely a convienient form of the former, since the AccountImpl in this case is only Manageable, and is used to initialize the state of the Managed object created. Any client code using the createFromCopy() programming model should not mistake the local copy for the Managed reference (the same Manageable "copy" can be used to create more than one Managed if desired).

Because the accounts BaseCollection implementation provides persistence, if you run this test case again it will pick up exactly where the previous left off, even if the machine was shut down in the meantime. You can use the following example to "prove" this:

 
java AccountTest 10 20 5 
Checking account balance = 5
Savings account balance = 25

java AccountTest 5 0 10
Checking account balance = 0
Savings account balance = 35

java AccountTest 100 0 0
Checking account balance = 100
Savings account balance = 35

Since a FilepoBCImpl was implemented as a Manageable object, we could have activated it via streaming, as we did for the Account objects in the previous example. We leave the specifics of this exercise to the interested reader; however, we wanted to reinforce the idea that an application will need to use some explicit activation mechanism to bootstrap various "root" objects. We will build on this concept to add distribution to the application, but at this point we have the ability to develop fully functional batch applications where root BaseCollections manage the various business object instances.

1.4. Distributing your applications

Distributing an application involves allocating the objects of the application to specific machines, processes and threads that will be responsible for executing the methods associated with messages targeted to those objects. The effect of allocating the client of an object (which may itself be an object) to a different process is that the target object must "split". That is, it appears to be in (at least) two places at once. This redundancy leads to the notion of "fatness" (or "skinniness") of the client, since the proxy (i.e. "shadow" on the client) can range from a mere shell that delegates everything to the server object to one that replicates both the state and business logic of the server object (i.e. the "master" copy of the object) in the client address space.

1.4.1. Choosing a distribution service implementation

The "end" of the spectrum you choose, along with the choices made previously to manage the object will ultimately determine how "fat" or "skinny" the client is. The trade-offs are between the memory footprint of the client with dependence on the connection to the server; between the ability to balance the processing load and the need to reconcile updates between two or more clients accessing the same server object.

For example, a pure delegating proxy (also called a Stub) only depends on the communication service code being available in the client and an implementation of the server object's interface that handles the delegation (i.e. no real business logic on the client). The number of active proxies has only a minimal effect on the memory footprint (since only the identifier of the server object is maintained locally). Further, since all methods are forwarded onto the server for processing, there is no need to reconcile updates between multiple clients. The primary disadvantage is that every message entails a round trip communication with the server (with minor exceptions in some implementations, like getIdentifier() and isIdentical()).

At the other extreme, a fully replicated proxy requires that the class that implements the business logic is available to the client as well as the state of the business object for each "replica." However, once the replica is downloaded to the client, it may be able to operate in a disconnected mode, decreasing its dependence on a connection to the server object(s) in question. Depending on the implementation, reconciliation may be handled through any number of schemes, such as a locking and notification mechanism (i.e. the Model-View-Controller pattern).

1.4.2. Configuring Stubs and Skeletons

Regardless of the implementation chosen, the basic cookbook for adding distribution is very similar to configuring an object in a BaseCollection. The proxy is the analog of a Managed object on the client side which extends the business object interface such that it communicates with its counterpart server object. The server object is also analogous to a Managed object as it extends the business object so that it can respond to messages sent over a communication protocol.

For this tutorial, we will choose a service implementation that relies on lightweight Stubs (specific to the business object interface) that forward messages through an IIOP based communication service to a Skeleton (specific to the business object implementation) on the server that translates the message back into a Java method invocation.

1.4.3. Developing Server applications

Once the Stubs and Skeletons are configured, we must manage a similar "split" in the BaseCollection logic that manages them. This split shows up in how we activate the root object(s) on the server side separately (and using a different mechanism) from the activation on the client side.

Our example uses the Netscape server to start up a serverlet (we have dubbed our version the Servlet) to minimize the system management overhead associated with the application (with insuring that the server process is up prior to making a connection and sending a message).

Of course, the details of developing the server will vary depending on the implementation chosen, but they will be similar in that the code will need to: a) extend a specific implementation class (like ServerApplet), b) override a well known method that the framework uses to start execution (e.g. run()), c) activate a "root" BaseCollection within that method used to manage objects in the server (we reuse FilepoBCImpl as in the previous example), and d) connect to the distribution mechanism (the ORB in this case). The following shows the complete example:

public class AccountsServlet extends ServerApplet
{
    static BaseCollection accounts = null;

    public void
    run()
    throws Exception
    {
    // Activate the BC holding the accounts as we did in the previous
    // test case
        if (accounts == null)
        {
            accounts = new FilepoBCImpl(
                "accounts",
                "COM.ibm.jaws.model.AccountFilepoManaged"
            );
        }
         
    // Create the server orb with the accounts BC and socket
    // obtained from the Servlet
        ORB orb = (ORB) new ServerORBlet(
            accounts,
            null,
            getClientSocket()
        );

    // Now "run" the ORB which reads messages from the
    // wire (using IIOP) and dispatches them to the activated
    // objects using generated "skeletons".  This method 
    // executes in an "infinite" loop. 
        orb.run();
    }
}

This code could have been even simpler, since the only logic "of consequence" (i.e. that code that cannot be factored out by the implementation class being extended) is the code that activates the BaseCollection. We leave this simplification as an exercise to the interested reader (one possible answer), and now turn to the client side of the distribution equation.

1.4.4. Developing Client applications

The "true" client application code will look very similar to the test cases we have seen before. As you might expect by now, the only real difference will be in how we activate the "root" object (the "distributed" BaseCollection of Accounts in this case):
    // Logic deleted

    // Locate the "root" BaseCollection in the servlet named by the
    // URLToObjectContext parameter.
        orb = new ClientORBlet(this);
        BaseCollection accounts = (BaseCollection)orb.URLToObject(
                getParameter("URLToObjectContext") +
                ";/?COM.ibm.jaws.mofw.BaseCollection"
        );

    // Activate the accounts using "resolve" as before
        checking = (Account) accounts.resolve("Checking");

In a sense, the ORB is acting like a BaseCollection of servers, using the server name passed in to locate the object(s) desired on the server (in this case the "root" BaseCollection). Interested readers may want to code up a version of the ClientORBlet that implements the BaseCollection interface to prove this. At any rate, now we have the ability to develop all kinds of different application styles, from standalone to fully distributed peer-to-peer.

1.5. Tutorial 1 summary

This example show how the Bojangles three-tier architecture enables a high degree of configurability and maintainability via separation of concerns. Once the pure java Account object was developed and tested, it did not change. Next, when the Account interface was separated out and the Manageable interfaces were implemented in AccountImpl, that code did not need to change again either. Finally, after the AccountFilepoManaged and AccountFilepoCached were generated, they did not change when moving from a standalone application without distribution to a client-server model that added AccountStub and AccountSkeleton classes.

In a similar vein, the "business logic" of the client application was able to be coded in such a manner that insulated it from the changes involved in moving from phase to phase. Review the test case code and you will see that the primary differences revolve around a) which objects serve as the "root" (the Account, AccountImpl, FilepoBCImpl, and ClientORBlet), and b) how these root objects are activated (via a specialized class method, a specialized constructor or a default constructor and streaming).

Although we did not provide explicit examples for this, the separation of implementation from interface will provide another "layer of insulation" from change as the application is maintained over time. If only the implementation of the business logic should change without affecting the interface, then none of the objects "downstream" will need to be regenerated. If a change to the implementation should affect only the essential data of the object, then the changes are limited to the associated Manageable class that implements the streaming methods. Of course this may mean that the Managed classes must be regenerated (especially if they are "mapped"), and any "legacy" data be converted, depending on the BaseCollection implementation chosen.

A final point that we want to stress is that at each step of the way, we have a completely functional object that is useful in its own right. Of course, the number of applications will increase as additional functionality is added. For example the pure java Account is useful in completely customized applications or those that can get by with purely transient objects. The Manageable AccountImpl can be used in a standalone application with streaming used as a generic explicit persistence mechanism, usually limited to a few "root" objects in the application. The Managed AccountFilepoManaged and associated FilepoBCImpl provide an implict persistence mechanism for managing large numbers of accounts suitable for a batch or command line application (and serving as the foundation upon which distribution can be added). Lastly, the AccountSkeleton and associated ORB enables the objects to be used remotely by any IIOP compliant client, while the AccountStub and associated ClientORBlet provided such a client suitable for use in a Java applet environment.

Of course, this "application" only involved a single business object class. The next tutorial will build on this example by expanding the "model" to include relationships between the Account, Customer and Bank objects. This example will be sufficient to illustrate how entire frameworks of objects will be implemented within Bojangles, and serve to complete the overview of the MOFW components.

2. Developing a business framework

This tutorial will add to the concepts learned in the previous one by extending the Banking application one relationship at a time. The intent is to show a one-to-one mapping between the staic object modelling concepts of OOA and a specific component of MOFW. Specifically, we will cover various relationship types (contained, referenced and named) and cardinalities (single, optional, and zero-many collections).

2.1. Single (or optional) contained objects.

This section of the tutorial will show three ways to maintain "contained" objects (also called sub objects) using a Customer object that directly contains two Accounts (one checking and one savings).

2.1.1. Explicit streaming of sub object state.

This section shows how explicit streaming of a sub object's state affects the essential state of the containing object with respect to the Manageable methods and any class specific constructors.

2.1.2. Delegation of streaming to the sub object.

This section shows how delegation of streaming to the sub object itself provides a degree of configurability.

2.1.3. Delegation of streaming to the EDStream.

This section shows how delegation of streaming to the EDStream provides a high degree of configurability for sub objects.

2.2. Single (or optional) references to objects.

This section shows how references to objects are maintained, showing various ways to obtain the reference. The Customer will be modified to store references to the Account objects rather than directly manage them. The Account will be modified to refer back to the "authorized" Customer. This will require us to configure a server to manage more than one kind of object.

Since both objects are modified during the course of this part of the tutorial, we will illustrate ways to limit the effect of changes (with respect to re-generating code and re-compiling it).

2.3. Collections of contained objects.

This section shows how BaseCollections can be "embedded" into your object's essential state. In this specific example, we show how a Bank can be have any number of Customer and Account objects. We also show how the BaseCollection itself must either be "contained" or "referenced" as in 2.1 and 2.2, with the same tradeoffs.

2.4. Collections of references to objects.

This section shows how a ReferenceCollection can be similarly embedded into your object's essential state. To illustrate, we will change the Account object so that multiple Customers can be authorized for access.

2.5. Collections of named references to objects.

This section shows how references can be "qualified" by using a NameCollection to provide a user defined alias by modifying the Customer object such that it "embeds" a NameCollection of Account objects qualified with the "type" (i.e. savings or checking, etc.).

As an aside, we will show how a NamedCollection can be used by a BO wishing to provide "dynamic" attributes.

2.6. Tutorial 2 Summary

This tutorial showed how any arbitrary OOA relationship can be directly implemented in terms of MOFW components so that they can be flexibly configured. At this point, you should have the skills necessary to develop some relatively sophisticated business models.

3. Advanced Topics

The purpose of this tutorial is to show how to fully exploit Bojangles to gain complete control over the performance characteristics of the application, yet still maintain the separation of concerns insulating the business logic from the implementation details.

3.1. Changing configurations

This section of the tutorial will show how the basic quality of service can be changed by reconfiguring the BaseCollections in which various objects are stored transparently to the application code or any business logic.

3.2. Throwing exceptions

This section of the tutorial explores how the three types of MOFW exception can be used within your business logic to signal various error conditions to the user of the object.

3.3. Using CommandOn objects

This section of the tutorial examines how CommandOn objects can be used to:

3.4. Wrappering legacy data

This section of the tutorial explores how to use "mapped streams" to wrapper legacy databases. It illustrates the need for business objects to provide an essential data schema that describes the contents of an OutputEDStream subsequent to externalizeToStream() (or an InputEDStream prior to internalizeFromStream()).

3.5. Implementing object services

The purpose of this section is to describe the responsibilities involved in implementing the various object service interfaces, such as will occur when you need to wrapper legacy "middleware" (e.g. proprietary database formats and transfer protocols, among others). It will also include a section on implementing your own servers and servlets to customize the "activation" process.

3.6 Summary

By now, you should have a hands on feel for the flexibility and power that the Bojangles three-tier architecture brings to bear during application development for very little overhead, and without restricting the quality of service associated with an implementation.
Last updated December 17, 1996.
The owner of this document is Geoff Hambrick (geoff@austin.ibm.com)