Serviced Component Woes (Part II)

"Like any COM object, Transaction Server objects can maintain internal state across multiple interactions with a client. An object that has this behavior is stateful. Transaction Server objects can also be stateless, which means the object doesn't hold an intermediate state while waiting for the next call from a client.

When a transaction is either committed or aborted, all the objects involved in the transaction are deactivated, causing them to lose any state they acquired during the course of the transaction. This helps ensure transaction isolation and database consistency; it also frees server resources for use in other transactions.

Completing a transaction enables Transaction Server to deactivate an object and reclaim its resources, thus increasing the scalability of the application. Maintaining state on an object requires the object to remain activated, holding potentially valuable resources, such as database connections. Stateless objects are more efficient and are recommended. "

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsqlsg/html/msdn_sql_mts.asp

So, it makes perfect sense that fields set during a transaction should not stay around. After all, if the transaction aborts for some reason, how do you know which fields are valid and which are not? It is possible that only half of the fields on the object were properly set before an exception was thrown that caused the transaction to abort. However, what happens when the transaction does not modify any fields on the object? Why must I still destroy the entire state of my object? There is no really good reason that I can think of. Consider this contrieved example:

public enum ShipperRegistry { GlobalUDDI, InternalUDDI }
public class ShippingProviderLocator
{
public string FindCheapestShipper(ShipperRegistry registry)
{
// connect to UDDI registry, find shippers implementing the shipping contract,
// get the cheapest one, and return the url of the service.

}
  public string FindMostReliableShipper(ShipperRegistry registry, IRatingProvider p)
{
// connect to UDDI registry, find shippers implementing the shipping contract,
// get the most reliable one, and return the url of the service.

}

}



[Transaction]
public class OrderManager : ServicedComponentWithWSTransactionSupport, IOrderManager
{
  string _shipperUrl;

  public void InitializeForCheapestShipping()
{
ShippingProviderLocator locator = new ShippingProviderLocator();


_shipperUrl = locator.FindCheapestShipper(ShipperRegistry.GlobalUDDI);
}
  public void InitializeForMostReliableShipping()
{
ShippingProviderLocator locator = new ShippingProviderLocator();


_shipperUrl = locator.FindMostReliableShipper(ShipperRegistry.GlobalUDDI, new StandardRatingProvider());
}
  [AutoComplete]
public void Ship(Package p)
{
if(_shipperUrl == null)
{
throw new InvalidOperationException("The shipping manager has not been initialized");
}
      ShippingLogManager logManager = new ShippingLogManager();
ShippingServiceProxy proxy = new ShippingServiceProxy();
proxy.Url = _shipperUrl;
ShippingInformation shippingInfo = proxy.Ship(p);
logManager.LogShipment(shippingInfo);
}
}

Now, maybe some of you COM+ gurus can explain why I would ever want this class to destroy all its fields after the Ship method returns? I can tell you why I wouldn't want to, because the initalization is darn expensive and I don't want to re-run it every time I ship something.

10 Comments

  • The reason the state gets destroyed is that there's no way for COM+ to distinguish volatile state from non-volatile state. Believe it or not, the calls to InitializeForMostReliableShipping and InitializeForCheapestShipping are being executed under the same transaction as the call to Ship. It's just that the call to Ship() completes the transaction and deactivates the object.





    If these are things are expensive to calculate once, presumably they are expensive to calculate for all clients. Why don't you cache the results in a static variable? Those don't get torn down when the object goes away...








  • Stop whining and try reading the article you quote. The answer is staring you in the face.

  • JD, yes, I could store the stuff outside in another object, but this is exactly my point, this state has to be maintained somewhere if I am going to have a decent performing system, why not in the object that is using it? A static variable doesn't work, because each shipping manager can be intialized in a different manner, and two different shipping managers can exist at the same time. So, the common option would be to seperate the two classes and pass in the url location to the object every time you make the call, but this is a needless passing around of data and a waste of code. Again, you are still maintaining state, but give me a good reason as to why I shouldn't be able to do this in my object (other than the "COM+ doesn't know what is volatile" argument, because that is an easily solvable problem).

  • From an implementation perspective, it's not really an easily solvable problem. I will not say it's not solvable, because it is. In this case, it involves teaching the system that the UDDI query had nothing to do with the transaction, somehow. If this weren't a UDDI query, but a database query, then it WOULD have something to do with the transaction, and thus the state couldn't be safely kept around.





    But that's not an argument to get into, really. What you need to realize is that ServiceComponents are not objects like you expect them to be. The design behind COM+ (and thus ServicedComponent) is that the objects represent *processes*, not data. That is why you should not store the data inside of the component.





    It's really a correctness thing. By caching the results of the UDDI query *anywhere*, you are introducing a possiblility of getting an incorrect result. We make such tradeoffs for performance, of course, but you need to realize that they really are performance optimizations, and actually hurt correctness. By keeping your performance optimizations away from your core business logic, you do yourself a favor in the long run.





    (Really, the results of the UDDI query are special. You would agree that, if your component queried the number of rows in some database table, that the results of the query should be discarded at the end of the transaction, wouldn't you?)

  • I see your point, but I think we both agree that it is a solvable problem and that there are valid reasons why you could want some state that isn't involved in the transaction to stick around and that is exactly my point (I just happen to think it wouldn't be too insanely difficult to solve). Yes, it is more of a convinience than anything else, because there are other ways to accomplish the same thing. However, I am always for the route that takes less code to do the same thing. If at some point I needed to refactor the code to provide the UDDI search in another component, that should be up to me to decide. There are also plenty of situations where using COM+ services would be very nice without enforing the "this is supposed to be a process" constriants. For example, an AOP engine that utilized Clemens' AOP ServicedComponents would probably have quite a few ServicedComponents that didn't fit the traditional COM+ programming model.





    I do agree that if the data came from a DB query that was related to the transaction, it would make sense for its data to be destroyed. Still, if it came from an entirely different DB, or from a table that wasn't even remotely related to the tables on which the work was happening, then I would definately like it to stay around. Again, this may be something difficult for COM+ to determine automatically, but that is exactly what metadata is for, giving meaning to otherwise non-descript fields, properties, and classes.





    I guess the bottom line is that I don't like the box. I think we can use a bigger one now. It might still be a box, but maybe a little less cramped :-).

  • I am not a COM+ (or MTS) expert. Therefore, I can't address your specific question. I am curious on a higher level however to your general question and how COM+ compares to other technologies. Is what you are saying true for all Transaction related technologies, as some have commented on? I thought that EJB's did maintain state. If Java can do it, why can't COM+ components?


    Cheers,


    TC

  • That is a very good point TC. EJB's don't have to maintain state, but you do have the option. If you are worried about inconsitencies in the transaction rollback / commit you can do something about it by implementing a synchronization interface.

  • I think you're asking the wrong question. You are the one that told the system to destroy all its state. Why did you do that? And why do you expect the system not to destroy the state when you explicitly (with the transaction and autocomplete attributes) told it to do that? Objects that are expensive to initialize and shareable across clients should be pooled objects, not autocomplete transactional objects. Don't blame the system for your mistakes.

  • No, I want AutoComplete, not AutoDestroy. The problem with the system is that it doesn't allow AutoComplete w/o AutoDestroy. Unlike EJBs, as TC points out.

  • COM+ does provide a partial solution to this problem, if you wish. Object pooling allows you to maintain certain elements of your state when the transaction completes. The feature was designed, in fact, both to amortize the cost of aquiring expensive resources, and to provide constraints on the number of those resources that exist at any one time.





    If you mark your component as pooled, then you can aquire your resources in your constructor, do any work that would require the current transaction in Activate(), and release (or clear) those bits of state in Deactivate(). Note that this does not change the recommended usage pattern from the client's point of view, and the interface described above still won't work (exactly). When the client calls the AutoComplete method, your object will STILL be deactivated and placed back in the pool. When the client calls another method, it will receive a new object out of the pool, and that object may or may not have already had one of the Initialize* methods called on it. Therefore, a client must always be sure to call one of the Initialize* methods before calling Ship.





    But you can still cache your state inside your object, which may be what you want.


Comments have been disabled for this content.