11.1 Foundation: Components and COMWare


11.1 Foundation: Components and COMWare

Business application fortresses are usually built on two related foundation technologies. The first is distributed components. This is the same distributed-component technology that I discussed in Chapter 5 (Synchronous Drawbridges), and it maps exactly to the technology used by homogeneous synchronous drawbridges . The second foundation technology is a technology I refer to as component -oriented middleware ( COMWare ).

COMWare was first introduced long before .NET by Microsoft under the name Microsoft Transaction Server (MTS), then it was adopted by Sun to be J2EE's EJB technology, and then finally it was released once again by Microsoft under the name COM+. COMWare is a layer above components, so let me start by reviewing how components work.

As I discussed in Chapter 5 (Synchronous Drawbridges), a component is a blob of software defined by an interface and distributed over a network. Clients make requests on these components through intermediaries called surrogates . These surrogates handle the details of the communication, allowing the client programmer and the component programmer to focus on their little application worlds .

Two processes communicate with each other. The client process is the one in which the client, or caller, resides. The component process is the one in which the hardworking instances of the component reside.

In a standard component system the client code is responsible for managing the life cycle of the component instances. The client controls when the component instance is created, how long the component instance is used, and when the component instance is finally destroyed. Invisible to the client is the fact that surrogates are created, used, and destroyed along with component instances. Figure 11.1 illustrates the life cycle of a component.

Figure 11.1. Component Life Cycle

When components are being used in the context of a business application fortress, the client and the component instance are running inside the same fortress (although probably not in the same process). The client is one of two things: either the guard or another component compatriot.

In fact, the guard itself may well also be a component. The guard's client is the guard process, the one whose responsibility is to constantly loop and check the drawbridge for messages. When the process finds a message, it sends the message to its guard component, which does the usual guardy stuffnamely, approving the messages and passing them on to the appropriate fortress workers. Figure 11.2 shows how the guard process, the guard, the various components, and the surrogates are related.

Figure 11.2. The Entities within the Business Application Fortress

Component-oriented middleware provides another layer of functionality beyond the distributed capabilities of components. COMWare works by intercepting the method request as it passes between the two surrogates, as shown in Figure 11.3.

Figure 11.3. COMWare Architecture

This point at which the COMWare system intercepts the method request is what I call the point of interception . In a normal component system, one without COMWare, the method request moves directly from one surrogate to the other. In COMWare, the interception point provides an opportunity for the infrastructure to add value to the method delivery in the form of a series of value-added algorithms.

COMWare was originally developed within the context of three- tier , rich clientoriented systems. It was heavily influenced by the technology known as transaction processing monitors (TPMs). Some of the original goals of COMWare are no longer relevant to a fortress architecture, but some are still important. In this chapter I will discuss COMWare technologies from a software fortress perspective.

Of the various functions of COMWare, three stand out as relevant to software fortresses:

  1. Role-based security

  2. State management

  3. Transaction boundary management

The first of these, role-based security, I discussed already in Chapter 7 (Guards and Walls). It is an important function, but I feel that I have given it due coverage. In this chapter I will discuss only state management and transaction boundary management. In turns out that these two topics are closely related.

11.1.1 State Management

State management is managing the component state. The component state consists of any data needed by the component to process its request. If the component is a savings account in a banking operation, the component state might include information such as the account ID, current balance, and account password.

In theory, state information can be stored in three different places. It can be stored in the memory associated with a specific component instance (similar to how state is stored in object-oriented systems). It can be stored in the data strongbox (which, for a business application fortress, is invariably a database). Last and, it turns out, least, it can be stored "someplace else."

It is a rare business application fortress developer who does not attempt to come up with a "someplace else" to store state. The temptation is almost irresistible. Caches in memory are a particularly tempting "someplace else" because they perform so well. Do not succumb to this temptation . It is very difficult to create a "someplace else" and get it right.

In this book I will explore only the component instance memory and the strongbox as places to store component state. Both sites are used, but at different times. The real issue of state management is how to manage the movement of state between instance memory and the data strongbox.

Plans for state migration are an important part of any business application fortress design. If you don't migrate state often enough, you end up with poor reliability, scalability, and integrity. If you migrate too often, you end up with poor performance. Fortunately, COMWare provides some technology and, perhaps more importantly, a model for dealing with state migration. It all revolves around transaction boundary management, which is the second of the three functions we look to COMWare to provide.

I'll take a break, then, to look at transaction boundary management, and then return in Section 11.1.3 to the issue of state management.

11.1.2 Transaction Boundary Management

Oddly enough, COMWare has very little to do with transactions. It has a lot to do with something closely related to transactionsnamely, transaction boundary management. I discussed how transactions (including distributed transactions) work in Chapter 3 (Transactions). Briefly, there are two types of transactions: tightly coupled and loosely coupled. I will discuss only tightly coupled transactions in this chapter, so I will call them just transactions.

There are also single-resource and multiple-resource transactions. As I discussed in Chapter 3, whereas single-resource transactions can be coordinated by the transactional resource itself, multiple-resource transactions require the help of a distributed transaction coordinator (DTC). From the perspective of transaction boundary management, it doesn't much matter who the coordinator is, so I will generically refer to whoever is coordinating the transaction as the transaction coordinator .

If the transaction coordinator is to successfully group together a bunch of database updates into one all-or-nothing package, she must know which updates are to be included in that package. Where does she get this vital piece of information? You tell her. How do you tell her? With BeginTransaction and Commit calls. The transaction coordinator assumes that all updates between BeginTransaction and Commit are part of the same transaction package. Therefore, BeginTransaction and Commit form what I call the transaction boundaries .

Although programmers have been using BeginTransaction and Commit calls forever, this approach turns out not to be the best way to define transaction boundaries. At least, it's not the best way for components. And since components are the guts of the business application fortress, it is also not a good way to define transaction boundaries for business application fortresses.

The problem is that components often find themselves used in varying situations, with varying transaction semantics. Take, for example, a banking component called SavingsAccount that supports a method called withdrawFunds. The withdrawFunds method will have to do a bunch of database updates that need to be transactionally protected. In the traditional approach, this code would be written as follows :

 BeginTransaction: Do the first database update Do the second database update; ... Commit; 

But what if the Accounts business application fortress were designed as shown in Figure 11.4. What problems might this pseudocode cause?

Figure 11.4. Accounts Business Application Fortress

As Figure 11.4 shows, there are two ways the SavingsAccount component might be used. One is as part of an account transfer. The other is as part of a simple withdrawal. When the withdrawal is a simple withdrawal, the pseudocode works fine. But when the withdrawal is part of the account transfer, a problem arises. Rather than have a money transfer that is transactionally protected, we have, in effect, not one tightly coupled transaction, but two totally independent transactions. One of these transactions withdraws. The other deposits. The two hard-coded transaction boundaries (BeginTransaction and Commit) force this independence.

Because the two transactions are independent, there is no way to be sure that money won't be withdrawn, and then not deposited, or vice versa. This isn't a good way to do money transfers. It is a bad situation when only half of a money transfer occurs!

The problem is that hard-coded transaction boundaries are not very flexible. Every time we do a withdrawal, we hit BeginTransaction which starts a new transaction boundary. To solve this problem, we need a more fluid notion of transaction boundaries. In the banking system, for example, we would like the transaction boundaries to surround the withdrawFunds method when we're doing a simple withdrawal and to surround the transferFunds method when we're doing a money transfer.

COMWare systems give us this greater flexibility. COMWare allows us to define which methods require transaction and then itself creates the appropriate transaction boundaries that will give us the desired transaction semantics.

The exact algorithm differs depending on the transaction requirements of each method, but I'll illustrate it with the most common setting: requires-transaction. This setting means that the workload inside the method does require transaction protection but does not require its own transaction boundary. In other words, if this method (say, withdrawFunds) is called from within another method (say, transferFunds) that has its own transaction, then this method (withdrawFunds) will piggyback off of the other method's transaction (the one belonging to transferFunds). On the other hand, if the method (withdrawFunds) is not being called from within a method that has a transaction in play, then a transaction needs to be started.

I call this algorithm automatic transaction boundary management . Automatic transaction boundary management occurs where all COMWare algorithms occur, at the point of interception, between the two surrogates.

At the incoming point of interception, the COMWare system checks if there is a transaction in progress. If there is, it piggybacks off of that transaction. If there isn't, it starts a new transaction.

At the outgoing point of interception (the point at which the method is about to return to the calling surrogate), the COMWare system checks whether this method, at the incoming point of interception, started a new transaction or just piggybacked off of an existing transaction. One of these two things must have happened . If the incoming point of interception decided to piggyback, then the outgoing point of interception does nothing. If the incoming point of interception decided to start a new transaction, then the outgoing point of interception ends the transaction.

Assuming that both the transferFunds and the withdrawFunds methods are defined as requires-transaction, the following sequence occurs during a transfer operation:

  1. Somebody outside the fortress requests a transfer.

  2. The guard accepts the request and invokes the transferFunds method.

  3. COMWare intercepts the request.

  4. COMWare notices that transferFunds is one of those methods that requires a transaction.

  5. COMWare notices that no transaction is in process.

  6. COMWare starts a new transaction.

  7. The transferFunds method is invoked.

  8. The transferFunds method does its work within the context of the new transaction.

  9. The transferFunds method invokes withdrawFunds.

  10. COMWare intercepts the request.

  11. COMWare notices that withdrawFunds is one of those methods that requires a transaction.

  12. COMWare notices that there is already a transaction in progress, so it piggybacks off of that transaction.

  13. The withdrawFunds method is invoked, and it does its work within the same transaction as the one in which transferFunds did its work.

  14. The withdrawFunds method concludes.

  15. COMWare intercepts the return.

  16. COMWare asks if the transaction in which withdrawFunds did its work was started by withdrawFunds. It determines that withdrawFunds was working within a higher-level transaction (that of transferFunds, not that that matters to COMWare).

  17. The withdrawFunds method returns to its caller, which is the remaining part of the transferFunds method.

  18. The transferFunds method returns to its caller, the guard.

  19. The return is intercepted by COMWare.

  20. COMWare asks if the transaction in which transferFunds did its work was started by transferFunds. It determines that transferFunds was the starting point for the transaction. It therefore concludes the transaction.

The net result is that transferFunds and withdrawFunds do their work within the same tightly coupled transaction. If you work through the case where withdrawFunds is called from within a simple withdrawal operation (no transfer), you will find that the same algorithm ensures that the withdrawFunds workload is done within its own tightly coupled transaction. So the exact same withdrawal code works, regardless of the context in which it runs.

Automatic transaction boundary management is one of the nice features of COMWare. To make use of it, though, you must design your business application components appropriately. The key to the design is understanding that the transaction boundary management algorithm, like all other COMWare algorithms, works at the point of interception.

The point of interception, remember, occurs between the two surrogates, which means that it occurs at component-level method invocations. The implication , then, is that automatic transaction boundary management can occur only when a method is invoked, and that for the algorithm to work, the component-level methods must be designed so that each is a potentially self-contained transaction.

11.1.3 State Management Revisited

What does it mean to design your component-level methods to be self-contained transactions? It means three things. First, none of the transaction workload spills outside that method. Second, the method's workload does not include more than one transaction. And third, the component's state is managed according to the unforgiving rule of transactional integrity.

The idea that the method's workload is exactly one transaction can be summarized as the golden rule of a business application component. This rule states that the method is the transaction. Those of you who are familiar with my writing on three-tier systems (before my conversion to software fortresses) will recognize the similarity between the golden rule of a business application component and the golden rule of the middle tier.

The similarity between these two "golden rules" is no coincidence . The rules governing the business application fortress are very similar to the rules governing the middle tier in three-tier architectures.

Although the middle tier and the business application fortress are similar, they are not identical. For example, some capabilities of COMWare that are critical to middle-tier systems are not relevant to business application fortresses. Instance management is one such example. In addition, the approach to scalability in middle-tier systems, which focus primarily on homogeneous rich clients, is much different from the approach in business application fortresses, which focus primarily on heterogeneous asynchronous gateways.

Let's get back to state management. As I said, we need to make sure that the state is migrated , not too much, not too little, but "just right." Well, now I am in a better position to tell you what just right means. It means "just right according to the rule of transactional integrity."

The rule of transactional integrity governs the migration of data between the data strongbox and the business application component. It governs both directions of data migration: from the component to the data strongbox, and from the data strongbox to the component. Even though I am discussing the rule of transactional integrity, don't forget the other rule, the golden rule of a business application componentthat the method is the transactionwhich will also soon play a major role.

The first part of the rule of transactional integrity governs the migration of data from the strongbox to the component. This part of the rule states that any strongbox data needed by a component's transaction must be acquired from the strongbox within that component's transaction. Because the method and the transaction are identical (the golden rule), the rule for transactional integrity in effect tells us that all strongbox data needed by the method must be acquired within the method. It can't be acquired earlier and cached. It can't be remembered from earlier method invocations. Any data that is not acquired during the current method (or a lower-level method) is not trusted.

The second part of the rule for transactional integrity governs the migration of data from the component back to the strongbox. This part of the rule states that any strongbox data that was changed by the current transaction must be flushed back to the strongbox before the transaction commits . Again, within the context of the golden rule, this tells us that any data changed from within this method must be flushed before the method completes.

The penalty for ignoring the rule of transactional integrity is severe. If you base your business logic on data that was not read in sometime after the start of the transaction, that data may be stale (i.e., no longer the same as the data in the database). This situation occurs when another user has changed the database data since you last read it. If you don't store your changed data back to the database before your transaction completes, then when you do finally store it, you may overwrite somebody else's data.

You can see that the algorithm used by COMWare to manage transaction boundaries has two profound design implications. The first implication is that it forces us to design each of our component-level methods as if they were self-contained transactions, even though we know that the actual transaction boundary may be bubbled to a higher level by the COMWare system at runtime.

The second implication is that state migration must be managed from within the component method. All reads must occur after the method begins. All flushes must occur before the method terminates. Why must state migration occur within the method? Because the rule of transactional integrity tells us how state must migrate vis- -vis transaction boundaries, and the golden rule tells us that the transaction boundaries are the same as the method boundaries.

One last word of warning: All methods are not equal. In particular, not all methods are component-level methods. In fact, the vast majority of methods in the world are not component-level methods; they are object-level methods. I discussed the difference between components and objects in Chapter 1 (Introduction).

Do not confuse object-level methods with component-level methods. If you apply the design principles I have discussed in this chapter to object-level methods, you will have big problems. On the other hand, if you don't apply these design principles to component-level methods, you will also have big problems. Be sure you understand the difference between these two fundamentally different concepts.



Software Fortresses. Modeling Enterprise Architectures
Software Fortresses: Modeling Enterprise Architectures
ISBN: 0321166086
EAN: 2147483647
Year: 2003
Pages: 114

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net