.gif)
Designing the Components of an Application or Service
December 2002
Summary: This chapter discusses the different kinds of components found on a distributed .NET application or service, and describes best practices for designing them.
Chapter 1 described how an application or service is composed of multiple components, each performing a different kind of task. Every software solution contains similar kinds of components, regardless of the specific business need it addresses. For example, most applications contain components that access data, encapsulate business rules, handle user interaction, and so on. Identifying the kinds of components commonly found in distributed software solutions will help you build a blueprint for an application or service design.
This is Chapter 2 of Application Architecture for .NET: Designing Applications and Services. Start here to get the full picture.
Chapter Contents
This chapter contains the following sections:
Component Types
An examination of most business solutions based on a layered component model reveals several common component types. Figure 2.1 shows these component types in one comprehensive illustration.
Note The term component is used in the sense of a piece or part of the overall solution. This includes compiled software components, such as Microsoft .NET assemblies, and other software artifacts such as Web pages and Microsoft® BizTalk® Server Orchestration schedules.
Although the list of component types shown in Figure 2.1 is not exhaustive, it represents the common kinds of software components found in most distributed solutions. These component types are described in depth throughout the remainder of this chapter.
.gif)
Figure 2.1. Component types in the retail sample scenario
The component types identified in the sample scenario design are:
- User interface (UI) components. Most solutions need to provide a way for users to interact with the application. In the retail application example, a Web site lets customers view products and submit orders, and an application based on the Microsoft Windows® operating system lets sales representatives enter order data for customers who have telephoned the company. User interfaces are implemented using Windows Forms, Microsoft ASP.NET pages, controls, or any other technology you use to render and format data for users and to acquire and validate data coming in from them.
- User process components. In many cases, a user interaction with the system follows a predictable process. For example, in the retail application you could implement a procedure for viewing product data that has the user select a category from a list of available product categories and then select an individual product in the chosen category to view its details. Similarly, when the user makes a purchase, the interaction follows a predictable process of gathering data from the user, in which the user first supplies details of the products to be purchased, then provides payment details, and then enters delivery details. To help synchronize and orchestrate these user interactions, it can be useful to drive the process using separate user process components. This way the process flow and state management logic is not hard-coded in the user interface elements themselves, and the same basic user interaction "engine" can be reused by multiple user interfaces.
- Business workflows. After the required data is collected by a user process, the data can be used to perform a business process. For example, after the product, payment, and delivery details are submitted to the retail application, the process of taking payment and arranging delivery can begin. Many business processes involve multiple steps that must be performed in the correct order and orchestrated. For example, the retail system would need to calculate the total value of the order, validate the credit card details, process the credit card payment, and arrange delivery of the goods. This process could take an indeterminate amount of time to complete, so the required tasks and the data required to perform them would have to be managed. Business workflows define and coordinate long-running, multi-step business processes, and they can be implemented using business process management tools such as BizTalk Server Orchestration.
- Business components. Regardless of whether a business process consists of a single step or an orchestrated workflow, your application will probably require components that implement business rules and perform business tasks. For example, in the retail application, you would need to implement the functionality that calculates the total price of the goods ordered and adds the appropriate delivery charge. Business components implement the business logic of the application.
- Service agents. When a business component needs to use functionality provided in an external service, you may need to provide some code to manage the semantics of communicating with that particular service. For example, the business components of the retail application described earlier could use a service agent to manage communication with the credit card authorization service, and use a second service agent to handle conversations with the courier service. Service agents isolate the idiosyncrasies of calling diverse services from your application, and can provide additional services, such as basic mapping between the format of the data exposed by the service and the format your application requires.
- Service interfaces. To expose business logic as a service, you must create service interfaces that support the communication contracts (message-based communication, formats, protocols, security, exceptions, and so on) its different consumers require. For example, the credit card authorization service must expose a service interface that describes the functionality offered by the service and the required communication semantics for calling it. Service interfaces are sometimes referred to as business facades.
- Data access logic components. Most applications and services will need to access a data store at some point during a business process. For example, the retail application needs to retrieve product data from a database to display product details to the user, and it needs to insert order details into the database when a user places an order. It makes sense to abstract the logic necessary to access data in a separate layer of data access logic components. Doing so centralizes data access functionality and makes it easier to configure and maintain.
- Business entity components: Most applications require data to be passed between components. For example, in the retail application a list of products must be passed from the data access logic components to the user interface components so that the product list can be displayed to the users. The data is used to represent real-world business entities, such as products or orders. The business entities that are used internally in the application are usually data structures, such as DataSets, DataReaders, or Extensible Markup Language (XML) streams, but they can also be implemented using custom object-oriented classes that represent the real-world entities your application has to work with, such as a product or an order.
- Components for security, operational management, and communication: Your application will probably also use components to perform exception management, to authorize users to perform certain tasks, and to communicate with other services and applications. These components are discussed in detail in Chapter 3, "Security, Operational Management, and Communications Policies."
General Design Recommendations for Applications and Services
When designing an application or service, you should consider the following recommendations:
- Identify the kinds of components you will need in your application. Some applications do not require certain components. For example, smaller applications that don't need to integrate with other services may not need business workflows or service agents. Similarly, applications that have only one user interface with a small number of elements may not require user process components.
- Design all components of a particular type to be as consistent as possible, using one design model or a small set of design models. This helps to preserve the predictability and maintainability of the design and implementation for all teams. In some cases, it may be hard to maintain a logical design due to technical environments (for example, if you are developing both ASP.NET- and Windows-based user interfaces); however, you should strive for consistency within each environment. In some cases, you can use a base class for all components that follow a similar pattern, such as data access logic components.
- Understand how components communicate with each other before choosing physical distribution boundaries. Keep coupling low and cohesion high by choosing coarse-grained, rather than chatty, interfaces for remote communication.
- Keep the format used for data exchange consistent within the application or service. If you must mix data representation formats, keep the number of formats low. For example, you may return data in a DataReader from data access logic components to do fast rendering of data in Microsoft ASP.NET, but use DataSets for consumption in business processes. However, be aware that mixing XML strings, DataSets, serialized objects, DataReaders, and other formats in the same application will make the application more difficult to develop, extend, and maintain.
- Keep code that enforces policies (such as security, operational management, and communication restrictions) abstracted as much as possible from the application business logic. Try to rely on attributes, platform application programming interfaces (APIs), or utility components that provide "single line of code" access to functionality related to the policies, such as publishing exceptions, authorizing users, and so on.
- Determine at the outset what kind of layering you want to enforce. In a strict layering system, components in layer A cannot call components in layer C; they always call components in layer B. In a more relaxed layering system, components in a layer can call components in other layers that are not immediately below it. In all cases, try to avoid upstream calls and dependencies, in which layer C invokes layer B. You may choose to implement a relaxed layering to prevent cascading effects throughout all layers whenever a layer close to the bottom changes, or to prevent having components that do nothing but forward calls to layers underneath.
Designing Presentation Layers
The presentation layer contains the components that are required to enable user interaction with the application. The most simple presentation layers contain user interface components, such as Windows Forms or ASP.NET Web Forms. For more complex user interactions, you can design user process components to orchestrate the user interface elements and control the user interaction. User process components are especially useful when the user interaction follows a predictable flow of steps, such as when a wizard is used to accomplish a task. Figure 2.2 shows the component types in the presentation layer.
.gif)
Figure 2.2. Presentation layer
In the case of the retail application, two user interfaces are required: one for the e-commerce Web site that the customers use, and another for the Windows Forms–based applications that the sales representatives use. Both types of users will perform similar tasks through these user interfaces. For example, both user interfaces must provide the ability to view the available products, add products to a shopping basket, and specify payment details as part of a checkout process. This process can be abstracted in a separate user process component to make the application easier to maintain.
Designing User Interface Components
You can implement user interfaces in many ways. For example, the retail application requires a Web-based user interface and a Windows-based user interface. Other kinds of user interfaces include voice rendering, document-based programs, mobile client applications, and so on. User interface components manage interaction with the user. They display data to the user, acquire data from the user, and interpret events that the user raises to act on business data, change the state of the user interface, or help the user progress in his task.
User interfaces usually consist of a number of elements on a page or form that display data and accept user input. For example, a Windows-based application could contain a DataGrid control displaying a list of product categories, and a command button control used to indicate that the user wants to view the products in the selected category. When a user interacts with a user interface element, an event is raised that calls code in a controller function. The controller function, in turn, calls business components, data access logic components, or user process components to implement the desired action and retrieve any necessary data to be displayed. The controller function then updates the user interface elements appropriately. Figure 2.3 shows the design of a user interface.
.gif)
Figure 2.3. User interface design
User Interface Component Functionality
User interface components must display data to users, acquire and validate data from user input, and interpret user gestures that indicate the user wants to perform an operation on the data. Additionally, the user interface should filter the available actions to let users perform only the operations that are appropriate at a certain point in time.
User interface components:
- Do not initiate, participate in, or vote on transactions.
- Have a reference to a current user process component if they need to display its data or act on its state.
- Can encapsulate both view functionality and a controller.
When accepting user input, user interface components:
- Acquire data from users and assist in its entry by providing visual cues (such as tool tips), validation, and the appropriate controls for the task.
- Capture events from the user and call controller functions to tell the user interface components to change the way they display data, either by initiating an action on the current user process, or by changing the data of the current user process.
- Restrict the types of input a user can enter. For example, a Quantity field may limit user entries to numerical values.
- Perform data entry validation, for example by restricting the range of values that can be entered in a particular field, or by ensuring that mandatory data is entered.
- Perform simple mapping and transformations of the information provided by the user controls to values needed by the underlying components to do their work (for example, a user interface component may display a product name but pass the product ID to underlying components).
- Interpret user gestures (such as a drag-and-drop operation or button clicks) and call a controller function.
- May use a utility component for caching. In ASP.NET, you can specify caching on the output of a user interface component to avoid re-rendering it every time. If your application contains visual elements representing reference data that changes infrequently and is not used in transactional contexts, and these elements are shared across large numbers of users, you should cache them. You should cache visual elements that are shared across large numbers of users, representing reference data that changes infrequently and that is not used in transactional contexts.
- May use a utility component for paging. It is common, particularly in Web applications, to show long lists of data as paged sets. It is common to have a "helper" component that will keep track of the current page the user is on and thus invoke the data access logic component "paged query" functions with the appropriate values for page size and current page. Paging can occur without interaction of the user process component.
When rendering data, user interface components:
- Acquire and render data from business components or data access logic components in the application.
- Perform formatting of values (such as formatting dates appropriately).
- Perform any localization work on the rendered data (for example, using resource strings to display column headers in a grid in the appropriate language for the user's locale).
- Typically render data that pertains to a business entity. These entities are usually obtained from the user process component, but may also be obtained from the data components. UI components may render data by data-binding their display to the correct attributes and collections of the entity components, if the entity is already available. If you are managing entity data as DataSets, this is very simple to do. If you have implemented custom entity objects, you may need to implement some extra code to facilitate the data binding.
- Provide the user with status information, for example by indicating when an application is working in "disconnected" or "connected" mode.
- May customize the appearance of the application based on user preferences or the kind of client device used.
- May use a utility component to provide undo functionality. Many applications need to let a user undo certain operations. This is usually performed by keeping a fixed-length stack of "old value-new value" data for specific data items or whole entities. When the operation has involved a business process, you should not expose the compensation as a simple undo function, but as an explicit operation.
- May use a utility component to provide clipboard functionality. In many Windows-based applications, it is useful to provide clipboard capabilities for more than just scalar values—for example, you may want to let your users copy and paste a full customer object. Such functionality is usually implemented by placing XML strings in the Clipboard in Windows, or by having a global object that keeps the data in memory if the clipboard is application-specific.
Windows Desktop User Interfaces
Windows user interfaces are used when you have to provide disconnected or offline capabilities or rich user interaction, or even integration with the user interfaces of other applications. Windows user interfaces can take advantage of a wide range of state management and persistence options and can access local processing power. There are three main families of standalone user interfaces: "full-blown" Windows-based applications, Windows-based applications that include embedded HTML, and application plug-ins that can be used within a host application's user interface:
- "Full-blown" desktop/tablet PC user interfaces built with Windows Forms
Building a Windows-based application involves building an application with Windows Forms and controls where your application provides all or most of the data rendering functionality. This gives you a great deal of control over the user experience and total control over the look and feel of the application. However, it ties you to a client platform, and the application needs to be deployed to the users (even if the application is deployed by downloading it over an HTTP connection.
- Embedded HTML
You can choose to implement the entire user interface using Windows Forms, or you can use additional embedded HTML in your Windows-based applications. Embedded HTML allows for greater run-time flexibility (because the HTML may be loaded from external resources or even a database in connected scenarios) and user customization. However, you must carefully consider how to prevent malicious script from being introduced in the HTML, and additional coding is required to load the HTML, display it, and hook up the events from the control with your application functions.
- Application plug-ins
Your use cases may suggest that the user interface of your application could be better implemented as a plug-in for other applications, such as Microsoft Office, AutoCAD, Customer Relationship Management (CRM) solutions, engineering tools, and so on. In this case, you can leverage all of the data acquisition and display logic of the host application and provide only the code to gather the data and work with your business logic.
Most modern applications support plug-ins as either Component Object Model (COM) or .NET objects supporting a specified interface, or as embedded development environments (such as the Microsoft Visual Basic® development system, which is widely supported in most common Windows-based applications) that can, in turn, invoke custom objects. Some embedded environments (including Visual Basic) even provide a forms engine that enables you add to the user interface experience beyond that provided by the host application. For more information about using Visual Basic in host applications, see "Microsoft Visual Basic for Applications and Windows DNA 2000" on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndna/html/vba4dna.asp).
For information about working with .NET from Microsoft Office, see "Microsoft Office and .NET Interoperability" on MSDN (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnofftalk/html/office11012001.asp).
When creating a Windows Forms-based application, consider the following recommendations:
Internet Browser User Interfaces
The retail application described in this guide requires a Web-based user interface to allow customers to place orders through the Internet. Web-based user interfaces allow for standards-based user interfaces across many devices and platforms. You develop Web-based user interfaces for .NET-based applications with ASP.NET. ASP.NET provides a rich environment where you can create complex Web-based interfaces with support for important features such as:
- A consistent development environment that is also used for creating the other components of the application.
- User interface data binding.
- Component-based user interfaces with controls.
- Access to the integrated .NET security model.
- Rich caching and state management options.
- Availability, performance, and scalability of Web processing.
When you need to implement an application for a browser, ASP.NET provides the functionality needed to publish a Web page-based user interface. Consider the following design recommendations for ASP.NET user interfaces:
- Implement a custom error page, and a global exception handler in Global.asax. This provides you with a catch-all exception function that prevents the user from seeing unfriendly pages in case of a problem.
- ASP.NET has a rich validation framework that optimizes the task of making sure that data entered by the user conforms to certain criteria. However, the client validation performed at the browser relies on JavaScript being enabled on the client, so you should validate data on your controller functions as well, just in case a user has a browser with no JavaScript support (or with JavaScript disabled). If your user process has a Validate control function, call it before transitioning to other pages to perform point-in-time validation.
- If you are creating Web user controls, expose only the public properties and methods that you actually need. This improves maintainability.
- Use the ASP.NET view state to store page specific state, and keep session and application state for data with a wider scope. This approach makes it easier to maintain and improves scalability.
- Your controller functions should invoke the actions on a user process component to guide the user through the current task rather than redirecting the user to the page directly. The user process component may call the Redirect function to have the server display a different page. To do so, you must reference the System.Web namespace from your user process components. (Note that this means your user process component will not be reusable from Windows-based applications, so you may decide to implement Redirect calls in a different class.)
- Implement your controller functions as separate functions in your ASP.NET pages or in .NET classes that will be deployed with your Web pages. Writing business logic in ASP.NET-provided event handlers reduces the maintainability of the site, because you may need to invoke the same function from other events in the future. Doing so also requires greater skill on the part of developers writing UI-only code.
For example, suppose the retail site Web site contains a page on which a command button can be clicked to add a product to the user's shopping basket. The ASP.NET markup for the control might look like the following line of code.
<asp:Button id="addItem" OnClick="addItem_Click"/>
As you can see from this code, the button's OnClick event is handled by a function named addItem_Click. However, the event handler should not contain the code to perform the required action (in this case, add an item to the basket), but rather it should call another general function, as shown in the following code.
private void addItem_Click(object sender, System.EventArgs e)
{
AddItemToBasket(selectedProduct, selectedQuantity)
}
public void AddItemToBasket(int ProductID, int Quantity)
{
// code to add the item to the basket
}
This additional layer of abstraction ensures that the code required to perform controller tasks can be reused by multiple user interface elements.
For general information about ASP.NET, see ASP.NET on MSDN and the official ASP.NET site (http://asp.net).
In many applications, it is important to provide an extensible framework where multiple panes with different purposes are displayed. In Web-based applications, you also need to provide a home page or root user interface where tasks and information relevant to the user are displayed in a context- and device-sensitive way. Microsoft provides the following resources to help you implement Web-based portals:
Mobile Device User Interfaces
Mobile devices such as handheld PCs, Wireless Application Protocol (WAP) phones, and iMode devices are becoming increasingly popular, and building user interfaces for a mobile form factor presents its own unique challenges.
In general, a user interface for a mobile device needs to be able to display information on a much smaller screen than other common applications, and it must offer acceptable usability for the devices being targeted. Because user interaction can be awkward on many mobile devices, particularly mobile phones, you should design your mobile user interfaces with minimal data input requirements. A common strategy is to combine the use of mobile devices with a full-sized Web- or Windows-based application and allow users to preregister data through the desktop-based client, and then select it when using the mobile client. For example, an e-commerce application may allow users to register credit card details through the Web site, so that a preregistered credit card can be selected from a list when orders are placed from a mobile device (thus avoiding the requirement to enter full credit card details using a mobile telephone keypad or personal digital assistant [PDA] stylus).
Web User Interfaces
A wide range of mobile devices support Internet browsing. Some use micro browsers that support a subset of HTML 3.2, some require data to be sent in Wireless Markup Language (WML), and some support other standards such as Compact HTML (cHTML). You can use the Microsoft Mobile Internet Toolkit to create ASP.NET-based Web applications that send the appropriate markup standard to each client based on the device type as identified in the request header. Doing so allows you to create a single Web application that targets a multitude of different mobile clients including Pocket PC, WAP phones, iMode phones, and others.
As with other kinds of user interface, you should try to minimize the possibility of users entering invalid data in a mobile Web page. The Mobile Internet Toolkit includes client-side validation controls such as the CompareValidator, CustomValidator, RegularExpressionValidator, and RequiredFieldValidator controls, which can be used with multiple client device types. You can also use the properties of input fields such as Textbox controls to limit the kind of input accepted (for example by accepting only numeric input). However, you should always allow for client devices that may not support client-side validation, and perform additional checks after the data has been posted to the server.
For more information about the Mobile Internet Toolkit, see the Microsoft Mobile Internet Toolkit (http://www.microsoft.com/downloads/details.aspx?FamilyID=ae597f21-b8e4-416e-a28f-b124f41f9768&DisplayLang;=en).
Smart Device User Interfaces
The Pocket PC is a feature-rich device based on the Windows CE operating system on which you can develop both disconnected and connected (usually through wireless) user interfaces. The Pocket PC platform includes handheld PDA devices and smart phones, which combine PDA and phone features.
Microsoft provides the .NET Compact Framework for Pocket PC and other Windows CE platforms. The compact framework contains a subset of the full .NET Framework and allows the development of rich .NET–based applications for mobile devices. Developers can use the Smart Device Extensions for Visual Studio .NET to create applications that target the .NET Compact Framework.
As with regular Windows-based user interfaces, you should provide exception handling in your mobile device to inform the user when an operation fails, and allow the user to retry or cancel it as appropriate.
No input validation controls are provided in the Smart Device Extensions for Microsoft Visual Studio® .NET, so you must implement your own client-side validation logic to ensure that all data entry is valid.
For more resources for Pocket PC platform development and the .NET Compact Framework, see the "Pocket PC Development and the .NET Compact Framework" page on MSDN (http://msdn.microsoft.com/en-us/library/w3keyz9t.aspx).
Another mobile form factor for rich clients that you may want to consider is the Tablet PC. Tablet PCs are Windows XP–based portable devices that support user interaction through a "pen and ink" metaphor in which the user "draws" and "writes" on the screen. Since the Tablet PC is based on Windows XP, the full .NET Framework can be leveraged. An additional API for handling "pen and ink" interactions is also available. For more information about designing applications for the Tablet PC, see Tablet PC Platform on MSDN (http://msdn.microsoft.com/en-us/library/ms840454.aspx).
Document-based User Interfaces
Rather than build a custom Windows-based desktop application to facilitate user interaction, you might find that it makes more sense in some circumstances to allow users to interact with the system through documents created in common productivity tools such as Microsoft Word or Microsoft Excel. Documents are a common metaphor for working with data. In some applications, you may benefit from having users enter or view data in document form in the tools they commonly use. Consider the following document-based solutions:
- Reporting data. Your application (Windows- or Web-based) may provide the user with a feature that lets him or her see data in a document of the appropriate type—for example, showing invoice data as a Word document, or a price list as an Excel spreadsheet.
- Gathering data. You could let sales representatives enter purchase information for telephone customers in Excel spreadsheets to create a customer order document, and then submit the document to your business process.
There are two common ways to integrate a document experience in your applications, each broken down into two common scenarios: gathering data from users and reporting data to users.
Working with Documents from the Outside
You can work with documents "from the outside," treating them as an entity. In this scenario, your code operates on a document that has no specific awareness of the application. This approach has the advantage that the document file may be preserved beyond a specific session. This model is useful when you have "freeform" areas in the document that your application doesn't need to deal with but you may need to preserve. For example, you can choose this model to allow users to enter information in a document on a mobile device and take advantage of the Pocket PC ActiveSync capabilities to synchronize data between the document on the mobile device and a document kept on the server. In this design model, your user interface will perform the following functions:
- Gathering data. A user can enter information in a document, starting with a blank document, or most typically, starting with a predefined template that has specific fields.
The user then submits the document to a Windows-based application or uploads it to a Web-based application. The application scans the document's data and fields through the document's object model, and then performs the necessary actions.
At this point, you may decide either to preserve the document after processing or to dispose of it. Typically, documents are preserved to maintain a tracking history or to save additional data that the user has entered in freeform areas.
- Reporting data. In this case, a Windows- or Web-based user interface provides a way to generate a document that shows some data, such as a sales invoice. The reporting code will usually take data from the ongoing user process, business process, and/or data access logic components and either call macros on the document application to inject the data and format it, or save a document with the correct file format and then return it to the user. You can return the document by saving it to disk and providing a link to it (you would need to save the document in a central store in load-balanced Web farms) or by including it as part of the response.
When returning documents in Web-based applications, you have to decide whether to display the document in the browser for the user to view, or to present the user with an option to save the document to disk. This is usually controlled by setting the correct MIME type on the response of an ASP.NET page. In Web environments, you need to follow file naming conventions carefully to prevent concurrent users from overwriting each other's files.
Working with Documents from the Inside
When you want to provide an integrated user experience within the document, you can embed the application logic in the document itself. In this design model, your user interface performs the following functions:
- Gathering data. Users can enter data in documents with predefined forms, and then specific macros can be invoked on the template to gather the right data and invoke your business or user process components. This approach provides a more integrated user experience, because the user can just click a custom button or menu option in the host application to perform the work, rather than having to submit the entire document.
- Reporting data. You can implement custom menu entries and buttons in your documents that gather some data from the server and display it. You can also choose to use smart tags in your documents to provide rich inline integration functionality across all Microsoft Office productivity tools. For example, you can provide a smart tag that lets users display full customer contact information from the CRM database whenever a sales representative types in a customer name in the document.
Regardless of whether you work with a document from the inside or from the outside, you should provide validation logic to ensure that all user input is valid. You can achieve this in part by limiting the data types of fields, but in most cases you will need to implement custom functionality to check user input, and display error messages when invalid data is detected. Microsoft Office–based documents can include custom macros to provide this functionality.
For information about how to integrate a purely Office-based UI with your business processes, see "Microsoft Office XP Resource Kit for BizTalk Server" (http://www.microsoft.com/downloads/details.aspx?FamilyID=C4C7CB91-7095-4935-B615-4CFB739704E7&displaylang;=en).
For more information about working with Office and .NET, see MSDN. The following two articles will help you get started with Office and .NET-based application development:
You can manage document-based workflows by taking advantage of the services provided by Microsoft SharePoint Portal™. This product can manage the user process and provides rich metadata and search capabilities.
Accessing Data Access Logic Components from the User Interface
Some applications' user interfaces need to render data that is readily available as queries exposed by data access logic components. Regardless of whether your user interface components invoke data access logic components directly, you should not mix data access logic with business processing logic.
Accessing data access logic components directly from your user interface may seem to contradict the layering concept. However, it is useful in this case to adopt the perspective of your application as one homogenous service—you call it, and it's up to it to decide what internal components are best suited to respond to a request.
You should allow direct data access logic component access to user interface components when:
- You are willing to tightly couple data access methods and schemas with user interface semantics. This coupling requires joint maintenance of user interface changes and schema changes.
- Your physical deployment places data access logic components and user interface components together, allowing you to get data in streaming formats (such as DataReaders) from data access logic components that can be bound directly to the output of ASP.NET user interfaces for performance. If you deploy data access and business process logic on different servers, you cannot take advantage of this capability. From an operational perspective, allowing direct access to the data access logic components to take advantage of streaming capabilities means that you will need to provide access to the database from where the data access logic components are deployed—possibly including access through firewall ports. For more information, see Chapter 4, "Physical Deployment and Operational Requirements."
Designing User Process Components
A user interaction with your application may follow a predictable process; for example, the retail application may require users to enter product details, view the total price, enter payment details, and finally enter delivery address information. This process involves displaying and accepting input from a number of user interface elements, and the state for the process (which products have been ordered, the credit card details, and so on) must be maintained between each transition from one step in the process to another. To help coordinate the user process and handle the state management required when displaying multiple user interface pages or forms, you can create user process components.
Note Implementing a user interaction with user process components is not a trivial task. Before committing to this approach, you should carefully evaluate whether or not your application requires the level of orchestration and abstraction provided by user process components.
User process components are typically implemented as .NET classes that expose methods that can be called by user interfaces. Each method encapsulates the logic necessary to perform a specific action in the user process. The user interface creates an instance of the user process component and uses it to transition through the steps of the process. The names of the particular forms or ASP.NET pages to be displayed for each step in the process can be hard-coded in the user process component (thus tightly binding it to specific user interface implementations), or they can be retrieved from a metadata store such as a configuration file (making it easier to reuse the user process component from multiple user interface implementations). Designing user process components to be used from multiple user interfaces will result in a more complex implementation in order to isolate device-specific issues, but can help you distribute the user interface development work between multiple teams, each using the same user process component.
User process components coordinate the display of user interface elements. They are abstracted from the data rendering and acquisition functionality provided in the user interface components. You should design them with globalization in mind, to allow for localization to be implemented in the user interface. For example, you should endeavor to use culture-neutral data formats and use Unicode string formats internally to make it easier to consume the user process components from a localized user interface.
The following code shows how a user process component for a checkout process might look.
public class PurchaseUserProcess
{
public PurchaseUserProcess()
{
// create a guid to track this activity
userActivityID = System.Guid.NewGuid();
}
private int customerID;
private DataSet orderData;
private DataSet paymentData;
private Guid userActivityID;
public bool webUI; // flag to indicate that the client UI is a Web
// site (or not)
public void ShowOrder()
{
if(webUI)
{
//Code to display the Order Details page
System.Web.HttpContext.Current.Response.Redirect
("http://www.myserver.com/OrderDetails.aspx");
}
else // must be a Windows UI
{
//code to display the Order Details window.
OrderDetails = new OrderDetailsForm();
OrderDetails.Show();
}
}
public void EnterPaymentDetails()
{
// code to display the Payment Details page or window goes here
}
public void PlaceOrder()
{
// code to place the order goes here
ShowConfirmation();
}
public void ShowConfirmation()
{
// code to display the confirmation page or window goes here
}
public void Finish()
{
//code to go back to the main page or window goes here
}
public void SaveToDataBase()
{
//code to save your order and payment info in the private variables
//to a database goes here
}
public void ResumeCheckout(System.Guid ProcessID)
{
// code to reload the process state from the database goes here
}
public void Validate()
{
//you would place code here to make sure the process
//instance variables have the right information for the current step
}
}
Separating the user interaction functionality into user interface and user process components provides the following advantages:
- Long-running user interaction state is more easily persisted, allowing a user interaction to be abandoned and resumed, possibly even using a different user interface. For example, a customer could add some items to a shopping cart using the Web-based user interface, and then call a sales representative later to complete the order.
- The same user process can be reused by multiple user interfaces. For example, in the retail application, the same user process could be used to add a product to a shopping basket from both the Web-based user interface and the Windows Forms-based application.
An unstructured approach to designing user interface logic may result in undesirable situations as the size of the application grows or new requirements are introduced. If you need to add a specific user interface for a given device, you may need to redesign the data flow and control logic.
Partitioning the user interaction flow from the activities of rendering data and gathering data from the user can increase your application's maintainability and provide a clean design to which you can easily add seemingly complex features such as support for offline work. Figure 2.4 shows how the user interface and user process can be abstracted from one another.
.gif)
Figure 2.4. User interfaces and user process components
User process components help you to resolve the following user interface design issues:
- Handling concurrent user activities. Some applications may allow users to perform multiple tasks at the same time by making more than one user interface element available. For example, a Windows-based application may display multiple forms, or a Web application may open a second browser window.
User process components simplify the state management of multiple ongoing processes by encapsulating all the state needed for the process in a single component. You can map each user interface element to a particular instance of the user process by incorporating a custom process identifier into your design.
- Using multiple panes for one activity. If multiple windows or panes are used in a particular user activity, it is important to keep them synchronized. In a Web application, a user interface usually displays a set of elements in a same page (which may include frames) for a given user activity. However, in rich client applications, you may actually have many non-modal windows affecting just one particular process. For example, you may have a product category selector window floating in your application that lets you specify a particular category, the products in which will be displayed in another window.
User process components help you to implement this kind of user interface by centralizing the state for all windows in a single location. You can further simplify synchronization across multiple user interface elements by using data bindable formats for state data.
- Isolating long-running user activities from business-related state. Some user processes can be paused and resumed later. The intermediate state of the user process should generally be stored separately from the application's business data. For example, a user could specify some of the information required to place an order, and then resume the checkout process at a later time. The pending order data should be persisted separately from the data relating to completed orders, allowing you to perform business operations on completed order data (for example, counting the number of orders placed in the current month) without having to implement complex filtering rules to avoid operating on incomplete orders.
User activities, just like business processes, may have a "timeout" specified, when the activity has to be cancelled and the right compensatory actions should be taken on the business process.
You can design your user process components to be serializable, or to store their state separately from the application's business data.
Separating a User Process from the User Interface
To separate a user process from the user interface:
- Identify the business process or processes that the user interface process will help to accomplish. Identify how the user sees this as a task (you can usually do this by consulting the sequence diagrams that you created as part of your requirements analysis).
- Identify the data needed by the business processes. The user process will need to be able to submit this data when necessary.
- Identify additional state you will need to maintain throughout the user activity to assist rendering and data capture in the user interface.
- Design the visual flow of the user process and the way that each user interface element receives or gives control flow.
You will also need to implement code to map a particular user interface session to the related user process:
- ASP.NET pages will have to obtain the current user process by getting a reference from the Session object, or by rehydrating the process from another storage medium, such as a database. You will need this reference in event handlers for the controls on your Web page.
- Your windows or controls need to keep a reference to the current user process component. You can keep this reference in a member variable. You should not keep it in a global variable, though, because if you do, composing user interfaces will become very complicated as your application user interface grows.
User Process Component Functionality
User process components:
- Provide a simple way to combine user interface elements into user interaction flows without requiring you to redevelop data flow and control logic.
- Separate the conceptual user interaction flow from the implementation or device where it occurs.
- Encapsulate how exceptions may affect the user process flow.
- Keep track of the current state of the user interaction.
- Should not start or participate in transactions. They keep internal data related to application business logic and their internal state, persisting the data as required.
- Maintain internal business-related state, usually holding on to one or more business entities that are affected by the user interaction. You can keep multiple entities in private variables or in an internal array or appropriate collection type. In the case of an ASP.NET-based application, you may also choose to keep references to this data in the Session object, but doing so limits the useful lifetime of the user process.
- May provide a "save and continue later" feature by which a particular user interaction may be restarted in another session. You can implement this functionality by saving the internal state of the user process component in some persistent form and providing the user with a way to continue a particular activity later. You can create a custom task manager utility component to control the current activation state of the process. The user process state can be stored in one of a number of places:
- If the user process can be continued from other devices or computers, you will need to store it centrally in a location such as a database.
- If you are running in a disconnected environment, the user process state will need to be stored locally on the user device.
- If your user interface process is running in a Web farm, you will need to store any required state on a central server location, so that it can be continued from any server in the farm.
- May initialize internal state by calling a business process component or data access logic components.
- Typically will not be implemented as Enterprise Services components. The only reason to do so would be to use the Enterprise Services role-based authorization capabilities.
- Can be started by a custom utility component that manages the menus in your application.
User Process Component Interface Design
The interface of your user process components can expose the following kinds of functionality, as shown in Figure 2.5.
.gif)
Figure 2.5. User process component design
- User process "actions" (1). These are the interface of actions that typically trigger a change in the state of the user process. Actions are implemented in user process component methods, as demonstrated by the ShowOrder, EnterPaymentDetails, PlaceOrder, and Finish methods in the code sample discussed earlier. You should try to encapsulate calls to business components in these action methods (6).
- State access methods (2). You can access the business-specific and business-agnostic state of the user process by using fine-grained get and set properties that expose one value, or by exposing the set of business entities that the user process deals with (5). For example, in the code sample discussed earlier, the user process state can be retrieved through public DataSet properties.
- State change events (3). These events are fired whenever the business-related state or business-agnostic state of the user process changes. Sometimes you will need to implement these change notifications yourself. In other cases, you may be storing your data through a mechanism that already does this intrinsically (for example, a DataSet fires events whenever its data changes).
- Control functions that let you start, pause, restart, and cancel a particular user process (4). These functions should be kept separate, but can be intermixed with the user process actions. For example, the code sample discussed earlier contains SaveToDataBase and ResumeCheckout methods. Control methods could load required reference data for the UI (such as the information needed to fill a combo box) from data access logic components (7) or delegate this work to the user interface component (forms, controls, ASP.NET pages) that needs the data.
General Recommendations for User Process Components
When designing user process components, consider the following recommendations:
- Decide whether you need to manage user processes as components that are separate from your user interface implementation. Separate user processes are most needed in applications with a high number of user interface dialog boxes, or in applications in which the user processes may be subject to customization and may benefit from a plug-in approach.
- Choose where to store the state of the user process:
- If the process is running in a connected fashion, store interim state for long-running processes in a central SQL Server database; in disconnected scenarios, store it in local XML files, isolated storage, or local Microsoft SQL Server™ 2000 Desktop Engine (MSDE) databases. On Pocket PC devices, you can store state in a SQL Server CE database.
- If the process is not long-running and does not need to be recovered in case of a problem, you should persist the state in memory. For user interfaces built for rich clients, you may want to keep the state in memory. For Web applications, you may choose to store the user process state in the Session object of ASP.NET. If you are running in a Web farm, you should store the session in a central state server or a SQL Server database. ASP.NET will clean up SQL Server-stored session to prevent the buildup of stale data.
- Design your user process components so that they are serializable. This will help you implement any persistence scheme.
- Include exception handling in user process components, and propagate exceptions to the user interface. Exceptions that are thrown by the user process components should be caught by user interface components and published as described in Chapter 3: Security, Operational Management, and Communications Policies."
Network Connectivity and Offline Applications
In many cases, your application will require support for offline operations when network connectivity is unavailable. For example, many mobile applications, including rich clients for Pocket PC or Table PC devices, must be able to function when the user is disconnected from the corporate network. Offline applications must rely on local data and user process state to perform their work. When designing offline applications, follow the general guidelines in the following discussion.
The online and offline status should be displayed to the user. This is usually done in status bars or title bars or with visual cues around user interface elements that require a connection to the server.
The development of most of the application user interface should be reusable, with little or no modification needed to support offline scenarios. While offline, your application will not have:
- Access to online data returned by data access logic components.
- The ability to invoke business processes synchronously. As a result, the application will not know whether the call succeeded or be able to use any returned data.
If your application does not implement a fully message-based interface to your servers but relies on synchronously acquiring data and knowing the results of business processes (as most of today's applications do), you should do the following to provide the illusion of connectivity:
- Implement a local cache for read-only reference data that relates to the user's activities. You can then implement an offline data access logic component that implements exactly the same queries as your server-side data access logic components but accesses the local storage. You can implement the local cache as a desktop MSDE database. This enables you to reuse the design and implementation of your main SQL Server schemas and stored procedures. However, MSDE affects the global state of the computer it is installed on, and you may have trouble accessing it from applications configured for semi-trust. In many scenarios, using MSDE may be overkill for your state persistence requirements, and storing data in an XML file or persisted dataset may be a better solution.
- Implement an offline business component that has the same interface as your business components, but takes the submitted data and places it in a store-and-forward, reliable messaging system such as Message Queuing. This offline component may then return nothing or a preset value to its caller.
- Implement UI functionality that provides a way to inspect the business action "outbox" and possibly delete messages in it. If Message Queuing is used to queue offline messages, you will need to set the correct permissions on the queue to do this from your application.
- Design your application's transactions to accommodate message-based UI interactions. You will have to take extra care to manage optimistic locking and the results of transactions based on stale data. A common technique for performing updates is to submit both the old and new data, and to let the related business process or data access logic component eventually resolve any conflicts. For business processes, the submission may include critical reference data that the business logic uses to decide whether or not to let the data through. For example, you can include product prices alongside product IDs and quantities when submitting an order. For a more detailed discussion of optimistic locking, see "Designing Data Tier Components and Passing Data Through Tiers" on MSDN (http://msdn.microsoft.com/library/?url=/library/en-us/dnbda/html/BOAGag.asp?frame=true).
- Let the user persist the state of the application's user processes to disk and resume them later.
The advent of mobile devices based on IP networking, wireless security standard evolution, the 802.11 standard, IPv6, the Tablet PC, and other technologies will make wireless networks more popular. The issue with wireless networks is that with today's technology, they cannot guarantee connectivity with high confidence in all areas. For example, building structure, nearby machinery, and other factors may cause permanent and transient "dark zones" in the network. If you are designing an application for use in a wireless environment, consider designing it as a message-based, offline application, to prevent an experience full of exceptions and retries. For example, you could design an application so that an offline user can enter data through the same user interface as when connected, and the data can be stored in a local database or queued and synchronized later, when the user reconnects. SQL Server supports replication, which can be used to automate the synchronization of data in a loosely coupled fashion, allowing data to be downloaded to the offline device while connected, modified while disconnected, and resynchronized when reconnected. Microsoft Message Queuing allows data to be encapsulated in a message and queued on the disconnected device for submission to a server-side queue when connected. Components of the server will then read the message from the queue and process it. Using local queues or SQL Server replication to handle communication of user input to the server can help mitigate connectivity issues, even when the application is nominally connected. Where a more tightly coupled approach is required, you should use transactions and custom logging to ensure data integrity.
When data synchronization occurs between a disconnected (or loosely coupled) application and a server, you must take into account the following security considerations:
- Message Queuing provides its own authorization model, based on Windows authentication. If your application relies on custom, application-managed authentication, your client-side components will need to sign the documents that are submitted to the server.
- The client cannot be impersonated on the server if data is submitted through a queue.
- If SQL Server replication is used, you may need to specify an account with permission to access the SQL Server databases on the server. When replicating from SQL Server CE on a mobile device, a secure connection to the Internet Information Services (IIS) site containing the SQL Server CE Server Agent must be established. For more information about configuring SQL Server replication, see the documentation supplied with SQL Server and SQL Server CE.
- If network communication takes place over an HTTP connection, you may want to use Secure Sockets Layer (SSL) to secure the channel.
Notification to Users and Business Process-to-User Communication
Your application may be required to notify users about specific events. As the communication capabilities of the Internet grow, you will have more options for notifying users. Common technologies currently include e-mail, instant messaging, cell phone messaging, paging, and so on.
Instant notification may involve many possible notification technologies and the use of presence services to detect the appropriate way to contact a user. Microsoft Patterns & Practices has released a reference architecture that covers this scenario. It is available on MSDN at http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID;=7f544f57-20e2-4a05-a574-e3a675b5d15e.
Designing Business Layers
The core of your application is the business functionality it provides. An application performs a business process that consists of one or more tasks. In the simplest cases, each task can be encapsulated in a method of a.NET component, and called synchronously or asynchronously. For more complex business processes that require multiple steps and long running transactions, the application needs to have some way of orchestrating the business tasks and storing state until the process has completed. In these scenarios, you can use BizTalk Server Orchestration to define the workflow for the business process. The BizTalk Server schedule that implements the workflow can then use BizTalk Server messaging functionality or call your .NET business components to perform each task as it is required.
You can design the logic in your business layers to be used directly by presentation components or to be encapsulated as a service and called through a service interface, which coordinates the asynchronous conversation with the service's callers and invokes the BizTalk Server workflow or business components. The core of the business logic is sometimes also referred to as domain logic. Your business components may also make requests of external services, in which case you may need to implement service agents to manage the conversation required for the particular business task performed by each service you need to use.
Figure 2.6 shows the business layers of an application.
.gif)
Figure 2.6. Business component layers
Business Components and Workflows
When implementing business functionality, you have to decide if you need to orchestrate the business process or if a set of business components will be sufficient.
You should use business workflows (implemented with BizTalk Orchestration) to:
- Manage a process that involves multiple steps and long-running transactions.
- Expose an interface that implements a business process enabling your application to engage in a conversation or contract with other services.
- Take advantage of the broad range of adaptors and connectors for multiple technologies that are available for BizTalk Server.
You can implement the business process using only business components when:
- You do not need to maintain conversation state beyond the business activity, and the business functionality can be implemented as a single atomic transaction.
- You need to encapsulate functionality and logic that can be reused from many business processes.
- The business logic that needs to be implemented is computationally intensive or needs fine-grained control of data structures and APIs.
- You need to have fine-grained control over data and flow of logic.
In the retail example, the process of placing an order involves multiple steps (authorizing the credit card, processing payment, arranging delivery, and so on), and these steps need to be performed in a particular sequence. The most appropriate design approach for this kind of business process is to create business components to encapsulate each individual step in the process and to orchestrate those components using a business workflow.
Designing Business Components
Business components can be the root of atomic transactions. They implement business rules in diverse patterns and accept and return simple or complex data structures. Your business components should expose functionality in a way that is agnostic to the data stores and services needed to perform the work, and should be composed in meaningful and transactionally consistent ways.
Business logic will usually evolve and grow, providing higher-level operations and logic that encapsulates pre-existing logic. In many cases, you will need to compose pre-existing business functionality in order to perform the required business logic. When composing business logic, you must take special care when transactions are involved.
If your business process will be invoking other business processes in the context of an atomic transaction, all the invoked business processes must ensure their operations participate in the existing transaction so that their operations will roll back if the calling business logic aborts. It should be safe to retry any atomic operation if it fails without fear of making data inconsistent. You can think of a transaction boundary as a retry boundary. Transactions across servers running Windows can be managed using Distributed Transaction Coordinator (DTC), which is used by .NET Enterprise Services (COM+). To manage distributed transactions in heterogeneous environments, you can use COM Transaction Integrator (COMTI) and Host Integration Server 2000. For more information about COMTI and Host Integration Server, see http://www.microsoft.com/hiserver.
If you cannot implement atomic transactions, you will need to provide compensating methods and processes. Note that a compensating action does not necessarily roll back all application data to the previous state, but rather restores the business data to a consistent state. For example, if you are a supplier, you may expose a B2B shopping interface to partners. A compensating action for canceling an order being processed may involve charging an order cancellation fee. For long-running transactions and processes, the compensating action may be different at different states in the workflow, so you need to design these for appropriate stages in the process.
For information about handling transactions and isolation level issues, see "Transactions" in ".NET Data Access Architecture Guide" on MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/daag.asp).
The following list summarizes the recommendations for designing business components:
- Rely on message-based communication as much as possible.
- Ensure that processes exposed through service interfaces are idempotent, meaning that your application or service will not reach an inconsistent state if the same message is received twice.
- Choose transaction boundaries carefully so that retries and composition are possible. This applies to both atomic and long-running transactions. You should also consider using retries for message-based systems, especially when exposing your application functionality as a service.
- Business components should be able to run as much as possible in the context of any service user—not necessarily impersonating a specific application user. This lets you invoke them with mechanisms that do not transmit or delegate user identity.
- Choose and keep a consistent data format (such as XML, DataSet, and so on) for input parameters and return values.
- Set transaction isolation levels appropriately. For information about handling transactions and isolation level issues, see "Transactions" in ".NET Data Access Architecture Guide" on MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/daag.asp).
Implementing Business Components with .NET
You can create components that encapsulate your business logic using the .NET Framework. Your managed code can take advantage of Enterprise Services (COM+) for distributed transactions and other services commonly needed in distributed applications.
Your business components:
- Are invoked by the user process layer, service interfaces, and other business processes, typically with some business data to operate on, expressed as a complex data structure (a document).
- Are the root of transactions, and therefore must vote in the transactions they participate in.
- Should validate input and output.
- May expose compensating operations for the business processes they provide.
- May call data access logic components to retrieve and/or update application data.
- May call external services through service agents.
- May call other business components and initiate business workflows.
- May raise an exception to the caller if something goes wrong when dealing with atomic transactions.
- May use the features of Enterprise Services for initiating and voting on heterogeneous transactions. You need to consider the fact that different transaction options can have a great impact on performance. However, transaction management is not an adjustment mechanism or variable for improving application performance. For performance comparisons of different transaction approaches, see "Performance Comparison: Transaction Control" on MSDN (http://msdn.microsoft.com/library/en-us/Dnbda/html/Bdadotnetarch13.asp). Your transactional settings can be:
- Required. Use this option for components that may be the root of a transaction, or that will participate in existing transactions.
- Supported. Use this option for components that do not necessarily require a transaction, but that you want to participate in an existing transaction if one exists.
- RequiresNew. Use this option when you want the component to start a new transaction that is independent of existing transactions.
- NotSupported. Use this option when you do not want the component to participate in transactions.
Note Using the RequiresNew and NotSupported options will affect transaction composability, so you need to be aware of the impact of retrying a parent transaction.
Business components are called by the following consumers:
- Service interfaces
- User process components
- Business workflows
- Other business components
Figure 2.7 shows a typical business component interacting with data access logic components, service interfaces, service agents, and other business components.
.gif)
Figure 2.7. Business components
Note the following points in Figure 2.7:
- Business components can be invoked by components in the presentation layers (typically user process components) or by business workflows (not shown).
- Business components can also be invoked by service interfaces (for example, an XML Web service or a Message Queuing listener function.
- Business components call data access logic components to retrieve and update data, and they can also invoke other business components.
- Business components can also invoke service agents. You need to take extra care in designing compensation logic in case the service you are accessing is unavailable or takes a long time to return a response.
Note The arrows in Figure 2.7 represent control flow, not data flow.
When to Use Enterprise Services for Your Business Components
Enterprise Services (COM+) is the obvious choice for a host environment for your business components. Enterprise Services provide your components with role-based security, heterogeneous transaction control, object pooling, and message-based interfaces for your components by means of Queued Components (among other things). You may choose not to use Enterprise Services in an application, but for anything more than simple operations against a single data source, you will need its services, and taking advantage of the model provided by Enterprise Services early on provides a smoother growth path for your system.
You should decide at the very beginning of the design process whether or not to use Enterprise Services when implementing your business components, because it will be more difficult to add or remove Enterprise Services features from your component design and code after it is built.
When implementing components with Enterprise Services, you need to be aware of the following design characteristics:
- Remoting channel restriction. Only HTTP and DCOM-RPC channels are supported. For more information, see Designing the Communications Policy in Chapter 3, "Security, Operational Management, and Communications Policies."
- Strong-named components: You need to sign these components and all components they use in turn.
- Deployment. Your components will either be self registering (in which case they will require administrative rights at run time), or you will need to perform a special deployment step. However, most server-side components require extra deployment steps anyway (to register Event Log sources, create Message Queuing queues, and so on).
- Security. You will need to choose whether to use the Enterprise Services role model, which is based on Windows authentication, or to just use .NET-based security.
For more information about Enterprise Services, see "Understanding Enterprise Services (COM+) in.NET" on MSDN (http://msdn.microsoft.com/library/en-us/dndotnet/html/entserv.asp).
Commonly Used Patterns for Business Components
Regardless of whether your business components are hosted in Enterprise Services, there are many common patterns for implementing business tasks in your code. Commonly used patterns include:
- Pipeline pattern. Actions and queries are executed on a component in a sequential manner.
A pipeline is a definition of steps that are executed to perform a business function. All steps are executed sequentially. Each step may involve reading or writing to data confirming the "pipeline state," and may or may not access an external service. When invoking an asynchronous service as part of a step, a pipeline can wait until a response is returned (if a response is expected), or proceed to the next step in the pipeline if the response is not required in order to continue processing.
Use the pipeline pattern when:
- You can specify the sequence of a known set of steps.
- You do not need to wait for an asynchronous response from each step.
- You want all downstream components to be able to inspect and act on data that comes from upstream (but not vice versa).
Advantages of the pipeline pattern include:
- It is simple to understand and implement.
- It enforces sequential processing.
- It is easy to wrap in an atomic transaction.
Disadvantages of the pipeline pattern include:
- The pattern may be too simplistic, especially for service orchestration in which you need to branch the execution of the business logic in complex ways.
- It does not handle conditional constructs, loops, and other flow control logic well. Adding one step affects the performance of every execution of the pipeline.
The pipeline pattern is used extensively in applications based on Microsoft Commerce Server. For more information about how pipelines are used with Commerce Server, see "Pipeline Programming Concepts" in the Commerce Server 2000 SDK documentation on MSDN (http://msdn.microsoft.com/library/en-us/comsrv2k/htm/cs_sp_pipelineobj_woce.asp).
- Event pattern. Events are fired under particular business conditions, and code is written to respond to those events.
You use the event pattern when you want to have many activities happen but all receive the same starting data and cannot communicate with each other. Activities may execute in parallel or sequentially. Different implementations of the event may or may not run, depending on specific filtering information. If the implementations are set to run sequentially, order cannot be guaranteed.
Use the event pattern when:
- You want to be able to manage independent and isolated implementations of a specific 'function' independently.
- Responses from one implementation do not affect the way another implementation works.
- All implementations are write only or fire-and-forget, where the output of the business process is defined by none of the implementations, or by just one specific business implementation.
Advantages of the event pattern include:
- Maintainability is improved by keeping unrelated business process independent.
- It encourages parallel processing, which may result in performance benefits.
- It is easy to wrap in an atomic transaction.
- It is agnostic to whether implementations run asynchronously or synchronously because no reply is expected.
Disadvantages of the event pattern include:
- It does not let you build complex responses for the business function.
- A component cannot use the data or status of another component in the event pattern to perform its work.
Enterprise Services provides the Events service, which provides a good starting point implementation of the event pattern. For more information about Enterprise Services Events, see "COM+ Events" in the COM+ SDK documentation on MSDN (http://msdn.microsoft.com/library/en-us/cossdk/htm/pgservices_events_2y9f.asp).
Implementing Business Workflows with BizTalk Server
When your business processes require multiple steps or long-running transactions, you need to manage the workflow, handling conversation state and exchanging messages with diverse services as required. BizTalk Server includes orchestration services that help meet these challenges.
You can design your business processes using BizTalk Server Orchestration services, and create XLANG schedules that implement your business functionality. XLANG schedules are created graphically using BizTalk Server Orchestration Designer and can use BizTalk Messaging Services, .NET components, COM components, Message Queuing, or script to perform business tasks. XLANG schedules can be used to implement long-running transactions, and they automatically persist their state in a SQL Server database.
You can use BizTalk Server Orchestration to implement most kinds of business functionality. However, it is particularly suitable when your business process involves long-running workflow processes in which business documents are exchanged between multiple services. Documents can be submitted to BizTalk Server programmatically, or they can be delivered to a monitored file system folder or message queue known as a receive function. Receive functions ensure that the delivered documents match the specification defined for expected business documents, and if so, they consume the document and submit it to the appropriate business process channel in BizTalk Server. From this point of view, a receive function can be thought of as a simple form of service interface.
For an in-depth example that shows how to implement a business process using BizTalk Server Orchestration and Visual Studio .NET, see "Building a Scalable Business Process Automation Engine Using BizTalk Server 2002 and Visual Studio .NET" on MSDN (http://msdn.microsoft.com/library/en-us/dnbiz2k2/html/BizTalkVSautoeng.asp).
When your business process involves interactions with existing systems, such as mainframe applications, BizTalk Server can use adapters to integrate with them. For more information about integrating BizTalk Server with existing systems, see "Legacy File Integration Using Microsoft BizTalk Server 2000" on MSDN (http://msdn.microsoft.com/library/en-us/dnbiz/html/legacyfileint.asp).
BizTalk Server Orchestration Implementation
Figure 2.8 shows how an orchestrated business process interacts with service interfaces, service agents, and business components.
.gif)
Figure 2.8. An orchestrated business process
Note the following points in Figure 2.8:
- Business workflows can be invoked from other services or from the presentation components (usually from user process components) using the service interface.
- A business workflow invokes other services through a service agent, or directly through the service interfaces. Every outgoing message does not necessarily need to match an incoming message. You can implement service interfaces and service agents in code, or if only simple operations are required, you can use the message transformation and functoid features of BizTalk Server.
- Business workflows invoke business components. The business workflow or the components that it invokes can initiate atomic transactions.
- Business workflows invoke data access logic components to perform data-related activities.
- When designing business workflows, you must consider long response times, or method invocations with no reply at all. BizTalk Server automatically allows for long running conversations with external services.
BizTalk Server Orchestration schedules are created graphically using the BizTalk Server Orchestration Designer. Figure 2.9 shows how an orchestration flow in the previous figure would look as rendered by Microsoft Visio® drawing and diagramming software. Notice how similar the conceptual diagram in Figure 2.9 looks to the flow a business analyst and developer needs to work with.
.gif)
Figure 2.9. An orchestration flow in BizTalk Server Orchestration Designer
The drawing is then compiled into an XLANG schedule, which is an XML format file containing the instructions necessary for BizTalk Server to perform the tasks in the business process.
After it is compiled, the schedule can be initiated in one of the following ways:
- A BizTalk Server message can be submitted to BizTalk Server programmatically or through a file system or Message Queuing receive function.
- A schedule can be started programmatically from COM-based code using the sked moniker.
For more information about BizTalk Server Orchestration, read BizTalk Server: The Complete Reference by David Lowe et al (published by Osborne/McGraw Hill) and "Designing BizTalk Orchestrations" in the BizTalk Server 2000 documentation (http://msdn.microsoft.com/library/en-us/biztalks/htm/lat_sched_intro_xiju.asp).
For information about adapters for BizTalk:
http://www.microsoft.com/biztalk/evaluation/adapters/adapterslist.asp
The BizTalk Server Adapter's Developer Guide can be found at:
http://www.microsoft.com/biztalk/techinfo/whitepapers/2000/wp_adapterdevelopersguide.asp
Designing a Service Interface
If you are exposing business functionality as a service, you need to provide an entry point for your clients to call that abstracts the internal implementation. You may also need to expose similar functionality to different callers with different authentication requirements and service level agreement (SLA) commitments. You can provide an entry point to your service by creating a service interface.
A service interface is a software entity typically implemented as a facade that handles mapping and transformation services to allow communication with a service, and enforces a process and a policy for communication. A service interface exposes methods, which may be called individually or in a specific sequence to form a conversation that implements a business task. For example, the credit card service in the retail application scenario might provide a method named AuthorizeCard that verifies credit card details, and a second method named ProcessPayment that transfers funds from the cardholder's account to the retailer. These steps would be performed in the appropriate sequence to process an order payment
The necessary communication format, data schema, security requirements, and process are determined as part of a contract, which is published by the service. This contract provides the information clients need to locate and communicate with the service interface.
When designing service interfaces, consider the following:
- Think of a service interface as a trust boundary for your application.
- If your service interfaces are exposed to external organizations and consumers, or made publicly available, you should design them in such a way that changes to your internal implementation will not require a change to the service interface.
- The same business logic in your service may need to be consumed in different ways by different clients, so you may need to publish multiple service interfaces for the same functionality.
- Different service interfaces may define different communication channels, message formats, authentication mechanisms, performance service level agreements, and transactional capabilities. Common service level agreements are defined in time to respond to a certain request with a certain amount of information.
You can implement service interfaces in different ways, depending on how you want to expose the functionality of your application or service:
- To expose your business logic as an XML Web service, you can use ASP.NET Web service pages or expose some components through .NET remoting using SOAP and HTTP.
- To expose your service's functionality to clients sending Message Queuing messages, you can use Message Queuing Triggers or Enterprise Services Queued Components, or you can write your own 'message receiving' services.
For more information, see Designing the Communications Policy in Chapter 3, "Security, Operational Management, and Communications Policies."
Service Interface Characteristics
Consider the following design characteristics of service interfaces:
- Sometimes the .NET infrastructure will let you use a transparent service interface (for example, you can expose Enterprise Services objects as Web services in Windows Server 2003), and sometimes you may need to add specific artifacts to your application, such as XML Web services, BizTalk Orchestration workflows, or messaging ports. Consider the impact of using transparent service interfaces, because they may not provide the abstraction necessary to facilitate changes to the business functionality at a later date without affecting the service interface. Implementing facades has its development cost, but will help you to isolate changes and to make your application more maintainable.
- Service interfaces can implement caching, mapping, and simple format and schema transformations; however, these facades should not implement business logic.
- The service interface may involve a transactional transport (for example, Message Queuing) or a non-transactional transport (for example, XML Web services over HTTP). This will affect your error and transaction management strategy.
- You should design service interfaces for maximum interoperability with other platforms and services, relying whenever possible on industry standards for communications, security, and formats, standard or simple message formats (for example, simple XML schemas for XML Web services), and non-platform specific authentication mechanisms.
- Sometimes the service interface will have a security identity of its own, and will authenticate the incoming messages but will not be able to impersonate them. You should consider using this approach when calling business components that are deployed on a different server from the service interface.
Using Business Facades with Service Interfaces
The channel or communication mechanism you use to expose your business logic as a service may have an associated way of implementing the service interface code. For example, if you choose to build Web services, most of your service interface logic will reside in the Web service itself, namely the asmx.cs files. You could also expose your service through Message Queuing, in which case you could use Queued Components from Enterprise Services, custom listeners, or Message Queuing Triggers to "fire up" the component that acts as service interface.
If you are planning to build a system that may be invoked through different mechanisms, you should add a facade between the business logic and the service interface. By implementing this facade, you can consolidate in one place your policy-related code (such as authorization, auditing, validations, and so on) so it can be reused across multiple service interfaces that deal with diverse channels. This facade provides extra maintainability because it isolates changes in the communication mechanisms from the implementation of the business components. The service interface code then only deals with the specifics of the communication mechanism or channel (for example, examining Web service SOAP headers or getting information from Message Queuing messages) and sets the proper context for invoking the business facade component. Figure 2.10 shows a business facade used in this manner.
.gif)
Figure 2.10. Using a business facade with service interfaces
Figure 2.10 shows an example of how a business facade is used with the service interfaces of a system. IIS and ASP.NET receive an HTTP call (1) and invoke a Web service interface named MyWebService.asmx (2). This service interface inspects some SOAP message headers, and sets the correct principal object based on the authentication of the Web service. It then invokes a business facade component (3) that validates, authorizes, and audits the call. The facade then invokes a business component that performs the business work (4). Later the system is required to support Message Queuing, so a custom listener is built (5) that picks up messages and invokes a service interface component named MyMSMQWorker (6). This service interface component extracts data off the Message Queuing message properties (such as Body, Label, and so on) and also sets the correct principal object on the thread based on the Message Queuing message signature. It then invokes the business facade. By factoring the code of the business facade out of the service interface, the application was able to add a communication mechanism with much less effort.
Transaction Management in Service Interfaces
Your service interface will need to deal with a channel that provides transactional capabilities (such as Message Queuing) or one that doesn't (such as XML Web services). It is very important that you design your transaction boundaries so that operations can be retried in face of an error. To do so, make sure that all the resources you use are transactional, mark your root component as "requires transaction," and mark all sub components as either "requires transaction" or "supports transactions."
With transactional messaging mechanisms, the service interface starts the transaction first and then picks up the message. If the transaction rolls back, the message is automatically "unreceived" and is placed back in the queue for a retry. When using Message Queuing, Enterprise Services Queued Components, or Message Queuing Triggers, you can define a message queue-and-receive operation as transactional to achieve this automatically.
If you are using a messaging mechanism that is not transactional (such as XML Web services), you need to call the root of the transaction from the code in the service interface. In the case of a failure, you can design the service interface code to retry the operation or return to the caller an appropriate exception or preset data representing a failure.
Representing Data and Passing It Through Tiers
When your data access logic components return data, they can do so in a number of formats. These formats can vary from the data-centric (for example, an XML string) to the more object oriented (for example, a custom component that encapsulates an instance of a business entity). Common formats for returning data are:
- XML
- DataReader
- DataSet
- Typed DataSet
- Custom object with properties that map to data fields, and methods that perform data modifications through data access logic components.
For more information about the choices of data formats available in your application design, see "Designing Data Tier Components and Passing Data Through Tiers" on MSDN (http://msdn.microsoft.com/library/?url=/library/en-us/dnbda/html/BOAGag.asp?frame=true).
The data format you choose to use depends on how you want to work with the data. It is recommended that you avoid designs requiring you to transfer data in a custom object-oriented format, because doing so requires custom serialization implementation and can create a performance overhead. Generally, you should use a more data-centric format, such as a DataSet, to pass the data from the data access logic components to the business layers, and then use it to hydrate a custom business entity if you want to work with the data in an object-oriented fashion. In many cases, though, it will be simpler just to work with the business data in a DataSet.
Representing Data with Custom Business Entity Components
In most cases, you should work with data directly by using ADO.NET datasets or XML documents. This allows you to pass structured data between the layers of your application without having to write any custom code. However, if you want to encapsulate all the details about working with a particular format, or you want to add behaviors to your data, you may need to develop custom components. This gives you tight control over what other application components can do with the data, allows you to abstract internal formats from the data schema that the application uses, and enables you to add behavior to your data. This guide refers to the components you use to represent data as business entities.
For example, the ordering process discussed earlier in this guide could use an Order object, which has an associated Customer object, and a collection of LineItem objects. These components form part of the business layers of your application, and can be consumed by other business components or by presentation components.
Entity components contain snapshot data. They are effectively a local cache of information, so the data can only be guaranteed to be consistent if it is read in the context of an active transaction. You should not map one business entity to each database table; typically a business entity will have a schema that is a denormalization of underlying schemas. Note that the entity may represent data that has been aggregated from many sources.
Because the component stores data values and exposes them through its properties, it provides stateful programmatic access to the business data and related functionality. You should avoid designing your business entity components in such a way that the data store is accessed each time a property changes and should instead provide an Update method that propagates all local changes back to the database. Business entity components should not access the database directly, but should use data access logic components to perform data-related work as their methods are called. Business entities should not initiate any kind of transactions, and should not use data access APIs—they are just a representation of data, potentially with behavior. Because they may be called from business components as well as user interfaces, they should flow transactions transparently and should not vote on any ongoing transaction.
You may want to design your business entity components to be serializable, allowing you to persist current state (for example, to store on a local disk if working offline, or into a Message Queuing message).
Business entity components simplify the transition between object-oriented programming and document-based development models. Object-oriented design is common in stateful environments such as user interface design, whereas business functionality and transactions can typically be expressed more clearly in terms of document exchanges.
Note Custom business entity components are not a mandatory part of all applications. Many solutions (especially ASP.NET-based applications and business components) do not use custom representations of business entities, but instead use DataSets or XML documents because they provide all the required information and the development model is more task- and document- based as opposed to object-oriented.
Business Entity Component Interface Design
Business entity components expose:
- Property accessors (get and set functions) for attributes of the entity.
- Collection accessors for sub collections of related data. (The collections don't necessarily yield collections of business entities, so you can design your service entity to expose DataSets or DataTables directly and not be concerned about object model traversal.)
- Control functions and properties commonly used in entity management, for example, Load, Save, IsDirty, and Validate.
- Methods to access metadata for the entity, which can be useful in improving maintainability of the user interface.
- Events to signal changes in the underlying data.
- Methods to perform business tasks or get data for complex queries. These methods may act on the local data only (for example, Order.GetTotalCost) or on the business components and processes (for example, Order.Place).
- Methods and interfaces needed for data binding.
Consumers of business entity components include:
- User interaction components for rich clients. These components may bind to the data in business entities or the data exposed by any queries the component may expose. UI controller functions may also set and get properties of business entities for data input and display.
- User process components. User process components may hold one or more business entities as part of their internal business-specific state.
- Business components. Business processes may pass a business entity as a parameter to a data access logic component method (for example, an Order object could be passed to an InsertOrder method in a data access logic component). Alternatively, business components could also use business entities to access data behavior (for example by calling a Place method on the Order object, which in turn passes the order data to a data access logic component), but this approach is more uncommon than passing the business entity directly to a data access logic component method because it mixes a functional, document-oriented model with an object-based model.
Recommendations for Business Entity Design
These recommendations will help you implement the right mechanism to represent your data:
- Carefully consider whether you need custom entity coding or whether other data representations work for your requirements. Coding custom entities is a complex task that increases in development cost with the number of features it provides. Typically, custom entities are implemented for applications that need to expose a custom macro or a developer-friendly scripting object model for customization.
- Implement business entities by deriving them from a base class that provides boilerplate functionality and encapsulates common tasks.
- Rely on keeping internal datasets or XML documents for complex data instead of internal collections, structs, and so on.
- Implement a common set of interfaces across your business entities that expose common sets of functionality:
- Control methods and properties, such as Save, Load, Delete, IsDirty, and Validate.
- Metadata methods, such as getAttributesMetadata, getChildDatasetsMetadata, and getRelatedEntitiesMetadata. This is especially useful for user interface design.
- Isolate validation rules as metadata, for example by exposing XML Schema Definition Language (XSD) schemas. Make sure, however, that external callers cannot tamper with these validation rules.
- Business entities should validate the data they encapsulate through the enforcement of continuous and point-in-time validation rules.
- Implement an implicit enforcement of relationships between entities based on the data schema and the business rules around the data. For example, an Order object could have a maximum number of LineItem references.
- Design business entities to rely on data access logic components for database interaction. Doing so allows you to implement all your data access policies and related business logic in one place. If your business entities access SQL Server databases directly, it will mean that applications deployed to clients that use the business entities will need SQL connectivity and logon permissions.
For detailed design recommendations and sample code that will assist you when developing your business entity components, see "Designing Data Tier Components and Passing Data Through Tiers" on MSDN (http://msdn.microsoft.com/library/?url=/library/en-us/dnbda/html/BOAGag.asp?frame=true).
Designing Data Layers
Almost all applications and services need to store and access some kind of data. For example, the retail application discussed in this guide needs to store product, customer, and order data.
When working with data, you need to determine:
- The data store you are using.
- The design of the components used to access the data store.
- The format of the data passed between components, and the programming model it requires.
Your application or service may have one or more data sources, and these data sources may be of different types. The logic used to access data in a data source will be encapsulated in data access logic components, which provide methods for querying and updating data. The data your application logic needs to work is related to real-world entities that play a part in your business. In some scenarios, you may have custom components representing these entities, while in others you may choose to work with data by using ADO.NET datasets or XML documents directly.
Figure 2.11 shows how the logical data layer of an application consists of one or more data stores, and depicts a layer of data access logic components that are used to retrieve and manipulate the data in those data stores.
.gif)
Figure 2.11. Data components
Most applications use a relational database as the primary data store for application data. Other choices include the Microsoft Exchange Server Web store, legacy databases, the file system, or document management services.
When your application retrieves data from the database, it may do so using a data format such as a DataSet or DataReader. The data will then be transferred across the layers and tiers of the application and finally will be operated on by one of your components. You may want to use different data formats for retrieving, passing, and operating on the data; for example, you might use the data in a DataSet to populate properties in a custom entity object. However, you should strive to keep the formats consistent, because it will probably improve the performance and maintainability of the application to have only a limited set of formats, avoiding the need for extra translation layers and the need to learn different APIs.
The following sections discuss the choice of data stores, the design of data access logic components, and the choices available for representing data.
Data Stores
Common types of stores include:
- Relational databases. Relational databases such as SQL Server databases provide high volume, transactional, high performance data management with security, operations, and data transformation capabilities. Relational databases also host complex data logic instructions and functions in the form of stored procedures that can be used as an efficient environment for data-intensive business processes. SQL Server also provides a desktop and palm-held device version that lets you use transparent implementations for data access logic components. Database design is beyond the scope of this guide. For relational database design information, see "Database Design Considerations" in the SQL Server 2000 SDK.
- Messaging databases. You can store data in the Exchange Server Web store. This is useful especially if your application is groupware-, workgroup-, or messaging-centric and you don't want to rely on other data stores that may need to be managed separately. However, messaging data stores typically have lower performance, scalability, availability, and management capabilities than fully fledged relational database management systems (RDBMS), and it is therefore relatively uncommon for applications to use the data store provided in a messaging product.
- File system. You may decide to store your data in your own files in the file system. These files could be in your own format or in an XML format with a schema defined for the purposes of the application.
There are many other stores (such as XML databases, online analytical processing services, data warehousing databases, and so on) but they are beyond the scope of this guide.
Data Access Logic Components
Regardless of the data store you choose, your application or service will use data access logic components to access the data. These components abstract the semantics of the underlying data store and data access technology (such as ADO.NET), and provide a simple programmatic interface for retrieving and performing operations on data.
Data access logic components usually implement a stateless design pattern that separates the business processing from the data access logic. Each data access logic component typically provides methods to perform Create, Read, Update, and Delete (CRUD) operations relating to a specific business entity in the application (for example, order). These methods may be used by the business processes. Specific queries can be used by your user interface to render reference data (such as a list of valid credit card types).
When your application contains multiple data access logic components, it can be useful to use a generic data access helper component to manage database connections, execute commands, cache parameters, and so on. The data access logic components provide the logic required to access specific business data, while the generic data access helper utility component centralizes data access API development and data connection configuration, and helps to reduce code duplication. A well designed data access helper component should have no negative impact on performance, and provides a central place for data access tuning and optimization. Microsoft provides the Data Access Application Block for .NET (http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp), which can be used as a generic data access helper utility component in your applications when using SQL Server databases.
Figure 2.12 shows the use of data access logic components to access data.
.gif)
Figure 2.12. Data access logic components
Note the following points in Figure 2.12:
- Data access logic components expose methods for inserting, deleting, updating, and retrieving data. This includes the provision of paging functionality when retrieving large quantities of data.
- You can use a data access helper component to centralize connection management and all code that deals with a specific data source.
- You should implement your queries and data operations as stored procedures (if supported by the data source) to enhance performance and maintainability.
Note Data access logic components are recommended for all applications that need to access business data (such as products, orders, and so on). However, other products and technologies may use databases to store their own operational data, without the need for custom data access logic components.
Data access logic components provide simple access to database functionality (queries and data operations), returning both simple and complex data structures. They hide invocation and format idiosyncrasies of the data store from the business components and user interfaces that consume them. Implementing your data access logic in data access logic components allows you to encapsulate all the data access logic for the entire application in a single, central location, making the application easier to maintain or extend.
You should design each data access logic component to deal with only one data store. (This means that these components do not query and aggregate data from many sources; this is done by the business components.)
When using heterogeneous transactions, your data access logic components should participate in them, but they should never be the root of the transaction. It is more appropriate to have a business component as the root of a transaction in which one or more data access logic components are used to perform database updates.
Data Access Logic Component Functionality
When called, data access logic components typically do the following:
- Perform simple mappings and transformations of input and output arguments. This abstracts your business logic from database specific schemas and stored procedure signatures.
- Access data from only one data source. This improves maintainability by moving all data aggregation functionality to the business components, where data can be aggregated according to the specific business operation being performed.
- Act on a main table and perform operations on related tables as well. (Data access logic components should not necessarily encapsulate operations on just one table in an underlying data source.) This enhances the maintainability of the application.
Optionally, they may perform the following work:
- Use a custom utility component to manage and encapsulate optimistic locking schemes.
- Use a custom utility component to implement a data caching strategy for non-transactional query results.
- Implement dynamic data routing for very large scale systems that provide scalability by distributing data across multiple database servers.
Data access logic components should not:
- Invoke other data access logic components. Avoiding a design in which data access logic components invoke other data access logic components helps keep the path to data predictable, thus improving application maintainability.
- Initiate heterogeneous transactions. Since each data access logic component deals with only a single data source, there will be no scenario in which a data access logic component is the root for a heterogonous transaction. In some cases, however, a data access logic component may control a transaction that involves multiple updates in a single data source.
- Maintain state between method calls.
Data Access Logic Component Interface Design
Data access logic components commonly need to provide an interface to the following consumers:
- Business components and workflows. Data access logic components need to provide I/O of disconnected business documents and/or scalars in stateless, functional style methods, such as GetOrderHeader().
- User interface components. The user interaction components may use data access logic components for I/O of disconnected business documents for rendering data in rich clients and disconnected client scenarios, or for streaming output (for example, obtaining a DataReader) for ASP.NET and clients that benefit from stream rendering. You should consider using data access logic components directly from the user interface if you want to take advantage of the faster performance this design offers and you have no need for additional business logic between the user interface and data source.
Data access logic components may connect to the database directly using a data access API such as ADO.NET, or in more complex applications you may choose to provide an additional data access helper component that abstracts the complexities of accessing the database. In either case, you should strive to use stored procedures to perform the actual data retrieval or modification when using a relational database.
The methods exposed by a data access logic component may perform the following kinds of tasks:
- Common functionality that relates to managing "entities" such as CRUD functions.
- Queries that may involve getting data from many tables for read-only purposes. The data may be returned as paged or non-paged depending on your requirements, and the results may be streamed or non-streamed depending on whether the caller can benefit from it.
- Actions that will update data and potentially also return data.
- Returning metadata related to entity schema, query parameters, and resultset schemas.
- Paging for user interfaces that require subsets of data, such as when scrolling through an extensive product list.
Input parameters to data access logic component methods will typically include scalar values and business documents represented by XML strings or DataSets. Return values may be scalars, DataSets, DataReaders, XML strings, or some other data format. For specific design and implementation guidance in choosing a data format for your objects, see "Designing Data Tier Components and Passing Data Through Tiers" on MSDN (http://msdn.microsoft.com/library/?url=/library/en-us/dnbda/html/BOAGag.asp?frame=true).
Data Access Logic Component Example
The following C# code shows a partial skeleton outline of a simple data access logic component that could be used for accessing order data. This code is not intended to be a template for your code, but to illustrate some of the concepts from the discussion.
public class OrderData
{
private string conn_string;
public OrderData()
{
// acquire the connection string from a secure or encrypted
// location and assign to conn_string
}
public DataSet RetrieveOrders()
{
// Code to retrieve a DataSet containing Orders data
}
public OrderDataSet RetrieveOrder(Guid OrderId)
{
// Code to return a typed DataSet named OrderDataSet
// representing a specific order.
// (OrderDataSet will have a schema that has been defined in Visual Studio)
}
public void UpdateOrder(DataSet updatedOrder)
{
// code to update the database based on the properties
// of the Order data sent in as a parameter of type dataset
}
}
Recommendations for Data Access Logic Component Design
When designing data access logic components, you should consider the following general recommendations:
- Return only the data you need. This improves performance and enhances scalability.
- Use stored procedures to abstract data access from the underlying data schema. However, be careful not to overuse stored procedures, because doing so will severely impact the maintainability of your application in terms of code maintenance and reuse. A symptom of overusing stored procedures is having large trees of stored procedures that call each other. You should avoid using stored procedures to implement control flow, manipulate individual values (for example, perform string manipulation), or to implement any other functionality that is difficult to implement in Transact-SQL.
- Rely on RDBMS functionality for data-intensive work. Follow the principle, "Move the processing to the data, not the data to the processing." You should balance using stored procedures against the maintainability and reusability of your data logic.
- Implement a standard or expected set of stored procedures giving commonly used functionality, such as insert, read, update, and find functions. Doing so will save you time when you develop your business components. If you are proactive about implementing this functionality, you will be able to make the implementations consistent and enforce internal standards. If your design seems to be repeatable, you can even use code generators to build basic boilerplate stored procedures and data access logic component logic.
- Expose the expected functionality that is common across all your data access logic components in a separately defined interface or base class.
- Design consistent interfaces for different clients:
- Your business components can be implemented in many ways, including the use of custom .NET code, BizTalk Orchestration rules, or a third-party business rule engine. The design of the interface for your data access logic components should be compatible with the implementation requirements of your current and potential business components to avoid having additional interfaces, facades, or mapping layers between both.
- ASP.NET-based user interfaces will benefit in terms of performance from rendering data exposed as DataReaders. DataReaders are best for read-only, forward-only operations in which processing for each row is fast. If your data access logic components are deployed together with your user interface, you should expose large query results intended for rendering in data access logic component functions that return DataReaders. If you plan to operate on the data for a longer period of time, you can improve scalability by relying on a disconnected DataSet instead of a DataReader.
- Have the data access logic components expose metadata (for example, schema and column titles) for the data and operations it deals with. Doing so can help make applications more flexible at run-time, especially when rendering data in user interfaces.
- Do not necessarily build one data access logic component per table. You should design your data access logic components to represent a slightly higher level of abstraction and denormalization that is consumable from your business processes. It is uncommon to expose a relationship table as such; instead, you should expose the relationship functionality as data operations on the related data access logic components. For example, in a database where a many-to-many relationship between books and authors is facilitated by a TitleAuthor table, you would not create a data access logic component for TitleAuthor, but rather provide an AddBook method to an Author data access logic component or an AddAuthor method to a Book data access logic component. Semantically, you can add a book to an author or add an author to a book, but you cannot "insert authorship."
- If you store encrypted data, the data access logic components should perform the decryption (unless you want the encrypted data to go all the way to the client).
- If you are hosting your business components in Enterprise Services (COM+), you should build data access logic components as serviced components and deploy them in Enterprise Services as a library application. This allows them to participate and explicitly vote in Enterprise Services transactions and use role-based authorization. Data access logic components don't need to be hosted in Enterprise Services if you are not using any of the services or if they will be loaded in the same AppDomain as an Enterprise Services caller. For more information about using Enterprise Services, see "Business Components and Workflows" earlier in this chapter.
- Enable transactions only when you need them. Do not mark all data access logic components as Require Transactions, because this taxes resources and is unnecessary for read operations performed by the user interface. Instead, you should mark them as Supports Transactions by adding the following attribute:
[Transaction (TransactionOption.Supported)]
- Consider tuning isolation levels for queries of data. If you are building an application with high throughput requirements, special data operations may be performed at lower isolation levels than the rest of the transaction. Combining isolation levels can have a negative impact on data consistency, so you need to carefully analyze this option on a case-by-case basis. Transaction isolation levels should usually be set only at the transaction root (that is, the business process components). For more information, see Designing Business Layers earlier in this chapter.
- Use data access helper components. For benefits of this approach and details, see Designing Data Access Helper Components in this chapter.
For more information about designing data access logic components, see ".NET Data Access Architecture Guide" (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/daag.asp). Microsoft also provides the Data Access Application Block (http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp), a tested, high-performance data helper component that you can use in your application.
Designing Data Access Helper Components
When an application requires large numbers of data access logic components to access the same data source, you may find that you need to implement similar generic data access code in each data access logic component. This duplication of logic can lead to maintainability issues and makes it difficult to troubleshoot data access problems. Centralizing generic data access functionality in a data access helper component can produce a cleaner, more manageable design. Data access helper components provide an easy invocation model to the underlying data source. You can consider data access helper components to be generic, caller-side facades into the data source. They are typically agnostic to the application business logic being performed. Usually you will only have one or two helper components for a given data source. Each one may implement different sets of technical functionality for accessing the service. For example, one data access helper component to a database may let you invoke stored procedures, while another one may allow you to stream large amounts of data out.
If you are designing your application to be agnostic to the data source type (for example, to be able to switch from an Oracle database to a SQL Server database), you can do so by having two simple data access helper components that expose a similar interface. Note, however, that changing the data source should warrant extra testing of your application and that "no touch" data source transparency is a dubious goal for most applications, possibly with the exception of "shrink wrapped" applications developed by ISVs.
The goal of using a data access helper component is to:
- Abstract the data access API programming model from the data-related business logic encapsulated in the data access logic components, thus reducing and simplifying the code in the data access logic components.
- Isolate connection management semantics.
- Isolate data source location (through connection string management).
- Isolate data source authentication.
- Isolate transaction enlistment (ADO.NET does this automatically when used to access data in a SQL Server database or when using ODBC or OLEDB).
- Centralize data access logic for easier maintenance, minimizing the need for data source-specific coding skills throughout the development team and making it easier to troubleshoot data access issues.
- Isolate data access API versioning dependencies from your data access logic components.
- Provide a single point of interception for data access monitoring and testing.
- Use code access and user-based or role-based authorization to restrict access to the whole data source.
- Translate non-.NET exceptions that may be returned by the data source into exceptions that your application can handle in traditional ways.
To see an example of a data access helper component, including source code and documentation, download the Data Access Application Block for .NET from MSDN (http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp).
Accessing Multiple Data Sources
If you access an Oracle database or other data sources, you may prefer to abstract as much as possible the API with which you access them from your data access logic components. Microsoft has provided Oracle and OLE DB implementations of the Data Access Application Block and has stress-tested them in the context of the Nile performance benchmark. These implementations are available for download on MSDN by following the links in this article: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/manprooracperf.asp .
Achieving RDBMS transparency is a complex design goal, and using data access helpers can help to mitigate some of the development, troubleshooting, and maintenance efforts. However, you will still need to test your application with each data source due to the different ways in which relational database management systems handle stored procedures, cursors, and other database artifacts.
If you are envisioning that your application may be deployed in different environments with different relational database management systems, you may want to implement your data access helpers with a common interface and provide the actual component that does the data access for a particular data source in a factory pattern. You can change the source code supplied for the Application Blocks for .NET mentioned earlier to accommodate these specific requirements.
Integrating with Services
If your business process involves external services, you will need to handle the semantics of communicating with each service you need to call. Specifically, you will need to use the correct communication API to call the service and perform any necessary translation between the data formats used by the service and those used by your business process. If the service contract consists of a long-running conversation, you will also need to keep intermediate state while waiting for a response.
You should use a service agent component that encapsulates the logic necessary to encapsulate these tasks and to initiate and manage a messaging-based conversation for each service your application must consume. You can think of service agents as data access logic components for services other than data stores, or as proxies or emissaries to other services. Some service publishers may provide callers with a ready-built service agent, while in other cases you may need to develop your own.
The goal of using a service agent is to:
- Encapsulate access to one service.
- Isolate the business process implementation from the service implementation in terms of data format or schema changes.
- Provide input and output data formats that are compatible with the business components calling the service.
Service agents may also perform the following common type of tasks if required:
- Perform basic validation of the data exchanged with the service.
- Cache data for common queries.
- Authorize access to the service, providing a granular way to check security before accessing the service from the calling application's perspective. Typically, the service will authenticate and authorize requests as well.
- Set the right security context or provide the right credentials to the service for authentication. For example, to set the credentials for an XML Web service you are invoking, you can use the HTTPCredentialCache.
- Make sure the right portions of the message are encrypted or that a secure channel can be established if necessary.
- Provide monitoring information that allows interaction with the service to be instrumented. This allows you to determine whether your partners are complying with their service level agreements (SLAs).
Managing Asynchronous Conversations with Services
In some cases, you will need to integrate your application with other services, both sending and receiving asynchronous calls. In this case, your service interfaces will be receiving calls from the outside services, and you will be making calls into those services from your service agents. If these message exchanges are implemented in an asynchronous way, you may need to keep track of the conversation a certain set of message exchanges belong to. You should use one of these two options to keep track of the conversation state:
- Use the business data in the messages to identify the conversation. For example, you could use an order ID number in all messages to identify the order you are processing in a particular message exchange. This is the most straightforward way of correlating messages.
- Provide an infrastructure component or utility that generates GUIDs or IDs for specific conversations and attaches them to messages. Your service agents and service interfaces will need access to this information to understand how to interpret a particular asynchronous call. You will also need a persistent database to track the state and ID of each conversation. This requires extra development, and the context of the message is lost if the message needs to be interpreted outside the service. However, it may be convenient to use your own correlation IDs if you want to maintain that information private.
For more information about this topic, see Chapter 3, "Security, Operational Management, and Communications Policies."
What's Next?
This chapter described recommendations for designing the different kinds of components that are common in distributed applications and services. Chapter 3, "Security, Operational Management, and Communications Policies" discusses the impact of organizational policies on the design of your application or service.
Feedback and Support
Questions? Comments? Suggestions? To give feedback on this guide, please send an e-mail message to devfdbck@microsoft.com.
.gif)