"You'll see two typical types of objects running within a COM+ environment: business objects and data objects. Let's look at business objects first.
"Business objects encapsulate real world business operations. It's important that these operations be specified one place. For example, some objects might encapsulate a particular business rule. You don't want a business rule to be located in multiple places, because if the rule changes, it's hard to update the system. Other objects might be control objects. They basically enforce the sequencing of rules and the make sure that other objects are called to apply specific rules. Often the control objects are the ones that interact directly with the user service layer.
"When you take your initial cut at making a business object, you want to avoid trying to be all things to all the people who might ever want to use this object. Instead, you look at the known clients—the people who want to use this object today—and you provide the functionality that those clients need. Now you might think 'I've got a 'create new invoice' operation and an 'update invoice' operation, and I've got a query operation. It would be nice for my users to be able to delete an invoice or to mark it as paid as well.' In this case, it's probably worthwhile to flesh out the set of operations that are exposed by the object. But you shouldn't dream up potential clients who might do something a little bit differently. You probably want to get one version of the object out for the known client and if another client comes along, you can add a new interface or design a new object that meets the new client's needs.
"Another thing you want to do is make your methods fine-grained, meaning that they should do one thing and do it very well. Methods that do one thing and objects that do a cohesive set of related things are much easier to reuse than methods and objects that do a lot of things. For one thing, there is the weight factor: You're reluctant to load a 'kitchen sink' object into memory to use it. For another thing, a complicated operation may be applicable only to the client that's using it. You're going to get the most reuse if you break things up into separate steps and separate methods.
"You have to think carefully about how you're going to pass state information around in business objects. Ask yourself these questions: 'How do I get information into the object? Am I going to keep the information on the server or do I need to pass it in from the client? Do I need to get the information from a data store or will it come from the user service layer?' The way you talk to the business objects affects the look of the interfaces. If the information is kept on the client, you probably are going to end up with methods that have a lot of parameters to take all the information from the client tier and move it into the business object. The business object will use that information to complete one method; it will do its work, and it'll go away. If, on the other hand, the information is kept in a data store, typically the client presents some key information that the business object uses to retrieve the information from the store or from a transient shared area. The object uses the information to do its work and then returns the result back to the caller.
"In the case of a few high-level business objects, you will probably keep a lot of per-object state information. Typically you do this for internal business objects that are not directly accessed by the user service layer and that you know are going to be used only within the context of one transaction. For example, you might do this when you're building a master record with several detail records—say, an invoice with multiple items on it. When you're finished, you want to hand the invoice off to a data object to store. So one method says create the invoice and then add items to it one by one. As long as everything is done within the context of one transaction, this is a perfectly fine design, and you can keep the state information about that invoice within a particular object. If, on the other hand, you expect the user to pass information about one item at a time over to the business object, you don't want to keep that information in the per-object state, because then you have to keep this business object alive on the server, tying up resources, until the user decides he or she is done. Users have an annoying tendency to wander off before they've completed their work, and this poor business object would be sitting there, waiting to find out when it's done. So then you end up having to design things like timeouts and other things to clean up your resources. It's much easier just to say, 'Here's all the information; now do some work.' That's typically the approach that you'll see when you're going from the user service layer to the business service layer.
"You need to think about how the object methods or services that you want to implement at the business layer will work together. What things use the same resources? What things share responsibilities? What things work together to form one transaction? (These are typically packaged together into a higher-level operation.) If possible, you want to make sure that things that use the same resources are located in the same business object, and things that share the same transactional requirements are either located in the same package or have their transactional setting configured in such a way that they can work together to build that higher-level behavior.
"You also need to look at security requirements. This was a bit harder with MTS than it is with COM+ in Windows 2000, because you could not set security in MTS on a particular method. You could give certain types of users access to a particular component and to individual interfaces, but you couldn't restrict access to a particular method. You can restrict access to methods with COM+, but you will probably still want to group methods that have similar security requirements in a particular interface or a particular component. You should also think about the issue of data protection. Can you pass information around in plain text, or do you need to encrypt it? That's going to impact the interface design for these business objects, particularly any that talk to the outside world.
"Now let's take a look at the other primary type of object, data objects. You want your data objects to do is be responsible for the accuracy and consistency of a particular type of data. This ensures that the responsibility for that particular type of data is in one place. If something goes wrong with that data, you know where to go and look for problems. If you distribute the responsibility for checking the data across multiple objects and then just assume that the data is OK, you can end up with a corrupted data store. Or you have inconsistent information in different places in the data store, which is hard to correct. So the data objects are the last line of defense. They're responsible for making sure that your company's data doesn't get corrupted. Now they may, in turn, use system services to ensure that the data is in a consistent state. For example, you might want to use rules within an SQL Server database to make sure that you have referential integrity, or that deleting data in one place also deletes it in another. But for the most part, you want to make sure that responsibility for ensuring correctness is not at some higher level of the program—that it isn't spread out in your business objects.
"So what do you need to keep in mind about data objects? Well, again, there's the issue of where you're going to put the state. Here's where this whole argument about stateless and stateful objects really gets confusing for a lot of people who are thinking about using MTS or COM+. Typically, a data object is only in operation, or only in memory, during the lifetime of one transaction. Remember, the rule in MTS and COM+ is that you can't share state across transactions. But within a particular transaction, it's perfectly OK. So you might want to have a data object that is stateful, that you can set particular properties of, and that you can call individual methods to build up composite data. Then you would have a save method or something similar to indicate when it was time to write that information out to the store. Or you might use the same approach you would typically use for a business object, where you pass all the information into the data object at one time, it does some processing to ensure that the data's OK, and then just passes it on to the data store. Then the object goes away as soon as the transaction is completed. The key point I'm trying to make here is that you need to choose whether to design your interfaces so that you maintain information from one method call to another within a transaction, or whether to design them so that all the information is passed to the data object on a per-method basis.
"The typical behaviors of data objects are create, read, update and delete. If your objects need to be protected in any way, you need to decide whether you want the update, create, and delete operations in the same interface or the same component as the read operations. The operations that change the database might have different transactional requirements than the operations that just do a query. In both MTS and COM+, you can set the transactional level—the type of transaction support you want—only at the component level. If all the operations are in a particular component, you are going to get the transactional behavior that's specified. In that case, you might want to set your transactional settings to 'supports transaction,' and then make sure that any business objects that are doing queries don't have an enclosing transaction. Then they can do a query without the overhead of doing a full transaction. Any business objects that are doing update operations should have an enclosing transaction that the data objects will fit into. You might also choose to separate things so that you've got a cleaner separation when you're trying to restrict access. Then you can allow certain people to query but not also do updates.
"As with business objects, you should think about which services in your data objects use similar resources. For example, you want to keep services that talk to a similar database or the same database table in one data object. You want to make sure that you're able to share connections to a database. Connections are expensive in using system recourses, and they're limited in number. So it's important that you be able to share access as much as possible. As I mentioned before, transactional behavior is really the key when you're doing data object design. Transactions are expensive, so if you don't need them—if you're updating only one table and you can rely on the underlying data store to do the work, or if you're not doing any updates at all—maybe this particular object doesn't need transaction. On the other hand, if you're updating multiple things, or you can't rely on the containing component to do transactions correctly, you might want to require a transaction. So even though it's really easy to say, 'I want a transaction,' and all the work of setting it up is done for you, you still need to think about where you want transactions. Microsoft isn't getting rid of the need to think about that part of design; we're just making it easier to implement.
"Another thing you have to think about is who is allowed to do various operations on your data object, so that you can split the operations into methods that have particular role-based security applied to them. For even greater security, you might want to split off into separate interfaces, different approaches that you can take, depending on what platforms you need to support and what kind of errors your clients are willing to accept.
"One of the things you might want to watch out for with dividing things up into multiple interfaces on a particular component is that script clients can only access the default IDispatch interface for a particular object. So if you have divided up the behavior of an object into multiple interfaces, the script client might not be able to get to all of the services. That can be good or bad. You must make sure that the services you want everyone to have access to are on the primary interface. With COM+, you can selectively set the security settings on particular methods, even for the dispatch interface, so you can cause them to get a security error when they make a call. But you can't hide the fact that a particular method is there. If you really want to hide a method, you split it off into a separate interface, or maybe even a separate component.
"You need to decide who will use the data objects and whether they will be used directly from the user service layer. Are they going to be used by business objects that you trust? Do you want to have to rely on the behavior of a particular object? If not, you will want to make sure that all the responsibility for the data is encapsulated within the data object. And you also need to think about the language that the clients are written in. If you have clients that are using script languages and can access only IDispatch, you need to make sure that all of your functionality is provided through that default automation interface.
"So just a quick note on some things that you might want to think about for the user service layer. The user service layer usually contains ActiveX controls or automation components, depending on whether you've got a physical display or you're just doing some background processing. They usually run in-process, so they will typically be running within a browser environment, or within the context of a containing Win32 application.
"When you're building an application that relies on client-side components, one of the things that you need to watch out for is that the components may need to be installed over the Internet. That introduces some complications. When you're installing components to run in a browser environment, they need to be safe for encryption and safe for initialization; otherwise some clients may not even be able to get the objects created in their browser. They've got their security settings set so that they don't enable objects to be created within a particular page. By marking your objects as safe, you increase the chances that they'll let the objects be created.
"The other thing to watch out for is that user service layer objects are almost always accessed from script clients, meaning that you have to provide your programmatic interfaces through the default IDispatch. If you want to split things off into separate, well-defined interfaces with tight type definitions, that's fine. But you'll also want to create some sort of façade that provides the information that the script clients need through the default IDispatch interface. Now, there is a raging argument within the COM developer community about whether programmatic interfaces should be dual interfaces. A dual interface gives you both a custom aspect and the IDispatch component. If you have only one programmatic interface, I would say that you might as well make it a dual interface so that late-bound clients can access it through the fastest possible method, which is the custom part of the interface. Meanwhile the script clients can access it through IDispatch. If you have only IDispatch, everybody gets the slow way. If you have only the custom part, your script clients are out of luck. If you have multiple programmatic interfaces, things become kind of complicated, because even though they're all dual interfaces, only one dispatch interface is ever going to be looked at by your script clients. So you might want to decide that the default interface should either be a dual interface or a pure IDispatch interface that script clients can use.
"You also need to decide whether to do property-based or method-based interfaces. Property-based interfaces are perfectly fine at the user service layer, because you're working in-process. When you're going across the network connection, you should typically use a method-based approach, because you want to try to minimize the number of network roundtrips. For example, suppose you set the color, the size, and other attributes of an object. Those properties are then accessed through a script interface, based on IDispatch.