MSDN Home >  MSDN Library > 

Engine-Collection-Class, a Design Pattern for Building Reusable Enterprise Components

Mike McClure
Microsoft Corporation

Leo Romano
Sierra System Group Inc.

Thanks to contributor Peter Joyce
Sierra Systems Group Inc.

August 2000

Summary: Details an approach to designing reusable enterprise components. This article focuses on the Engine-Collection-Class (ECC) design pattern, a flexible model for creating reusable enterprise components for distributed/tiered applications that work with both traditional "rich" clients and scripting clients. (36 printed pages)

Download Vs6samples.exe.

Contents

Introduction
Prerequisites
Overview
Developing the Solution
Engine-Collection-Class Design Overview
Implementation Note
Conclusion
References

Introduction

This article details an approach to designing reusable enterprise components. It focuses on a proven design pattern that has been implemented and refined over multiple projects and customers.

The Engine-Collection-Class (ECC) design pattern is a flexible model for creating reusable enterprise components for distributed/tiered applications that work with both traditional "rich" clients and the ever-popular scripting clients. With its simplified interfaces and intuitive coding style, the ECC design pattern reduces development time and allows for consistent programming across multiple developers. Companies and developers can capitalize upon its extensibility when making future enhancements, as well as its maintainability when supporting an application.

Since the core of the design pattern is based on COM and other popular design patterns, the implementation of the ECC can be done in either an object-oriented or component-oriented language. This article focuses on a Microsoft Visual Basic implementation of the ECC design pattern.

Prerequisites

This article serves mainly as an Engine-Collection-Class primer. It does not detail some of the more advanced features of the ECC design pattern.

The follow prerequisites are recommended to make effective use the ECC design pattern:

  • Basic understanding of n-tier architecture and design, including knowledge of Microsoft Windows DNA and the functionality of each tier.
  • Basic understanding of COM and how Visual Basic implements it.
  • Basic understanding of object-oriented concepts.
  • Basic understanding of component design, including the purpose of objects, the concept of loose coupling, and separation of interface from implementation.
  • Basic understanding of database design—knowing about Entity Relationship Diagrams (ERDs) and how to use them in database design.
  • An agreement that many system designs are very flexible but that because a variety of factors decisions are made that cause an inflexible implementation of the design. The ECC design pattern takes this into consideration and employs a flexible implementation so you can reduce the cost of those decisions.
    Note   See the References section for a list of books and articles that address these prerequisites further.

Overview

Have you ever completed a project that you believed was reusable and extensible only to find out later that your system must be rewritten to take advantage of the latest technology changes? Have you ever started a project and halfway through realized that using XML or the latest SOAP protocol for interoperability would greatly improve the future system only to find that this would break every component you've written and run costs into the stratosphere? If you answered "yes," you're not alone.

In today's competitive market, companies need to make fast-paced, long-term decisions with minimal information. The wrong decision can affect every part of your company and set it back months or even years. Both business executives and technical developers wonder if there is a way to alleviate development risks. This article introduces a design pattern for building reusable enterprise components. The ECC is a simple and intuitive design pattern that was developed with one thing in mind: change. Businesses change, rules change, technology changes, and people change.

However, just because things change doesn't mean that you have to bear the full impact of that change. From a business perspective, the ECC design pattern helps lessen the impact of:

  • Missed requirements—the difference between what is communicated and what is understood.
  • Staff shortages—there may be a shortage of people, but throwing more people at a problem can also have diminishing returns.
  • Higher learning curves—knowing a programming language is not enough to maintain or extend existing systems.
  • Smaller time frames—things in the Internet world happen fast and the window of opportunity closes very quickly.
  • Additional costs—project overrun costs and schedule revisions caused by changes in project scope have adverse effects.

From a technical perspective, ECC is a three-tiered design pattern for developing n-tier business components by allowing the creation, storage, and attributes of a component to change with minimal impact to the solution. The ECC reduces the technical impact of changes by using:

  • Standard and consistent interfaces to components, which promotes ease of use.
  • Common object-orient programming (OOP)/object-oriented design (OOD) heuristics familiar to developers, such as design patterns and encapsulation.
  • Simplified interfaces that allow for component use in both traditional "rich" clients and scripting clients.
  • Intuitive creation of components that make development quicker and easier.

Front-end planning with an extensible architecture is critical for next-generation applications and businesses. By using the ECC design pattern in today's projects, you can develop more robust and flexible systems that drive tomorrow's businesses.

Developing the Solution

The following core elements were taken into consideration in developing a reusable design pattern solution:

  • n-tier distributed design (tiered insulation)
  • Minimizing calls over the network (round-tripping)
  • Flexible architecture (adaptability)
  • Maintainability and extensibility versus pure performance
  • Simple and consistent interface
  • Encapsulating and centralizing external dependencies (component independence)
  • Modeling of real world entities and their relationships (leverage)

n-tier distributed design (tier insulation)   The application is broken into layers to restrict the cascading effect of changes in one part of the application from affecting another part. Although it requires more front-end design time, tiered insulation reduces operational costs compared to the alternative.

Minimizing calls over the network   With Web-based solutions (location transparency), applications must be designed to do as much work as possible at either end of the chain and send the least amount of information across the wire. This concept can be extended to include the reduction of calls over process boundaries, thread boundaries, and context boundaries.

Flexible architecture   Inflexible applications come from inflexible implementations. In striving to envision goals for the design pattern, one of the most important questions was, "What happens if this changes?". The objective was to provide maximizing extensibility, reusability, maintainability, and independence (data source, data location, language). In the end, the goal was to develop a design pattern that would help reduce investment costs, manage scope creep, minimize the impact of changes, and increase reusability and maintainability without increasing complexity of use.

Maintainability and extensibility versus performance   Maintainability and extensibility usually come at the price of performance but this is offset when enhancements to the system cost very little. When a choice must be made between maintainability and extensibility versus performance, maintainability and extensibility are always the best choice. The implementer of the design pattern ultimately has the ability to forgo one option for the other.

Simple and consistent interface   Building simple and consistent heuristics for calling objects in the system helps reduce user-training costs and reduces the learning curve and knowledge transfer to other developers.

Encapsulating and centralizing external dependencies   Components can usually inherit services from external sources such as the operating system. Most Windows components (or applications) depend upon the operating system or some other service (such as MTS or COM+) to provide security access. But if this is not possible, or the costs of using inherited services outweighs the gain, centralizing external dependencies in the components is valuable because it allows a single point of control. Consequently, if these dependencies change, the programmer will not have to determine which modules or code segments will need to change.

Modeling real world entities and their relationships (RDBMS, Use Cases, OOP)   Many existing methodologies describe real world entities and the processes with which they interact. Some examples include:

  • ERDs, flow charts, and database design
  • Use cases (model entities and their scenarios)
  • Object-oriented programming (OOP) loosely coupling objects to one another or to their interface from the implementation

The ECC design pattern uses the proven heuristics from these methodologies to build a reusable design.

The ECC design pattern also follows Windows DNA models for building extensible, robust, distributed systems and combines them with industry-standard design techniques such as OO design, design patterns, and database design. Knowledge of these techniques is very common among people working with applications, so the transition from one phase to another becomes straightforward and reliable.

These core elements are addressed in this discussion about the ECC design pattern. However, it is important to remember that this is not the ultimate design pattern for every distributed application. Rather, it is one choice among many, and implementation issues must be considered before adopting it for use.

Engine-Collection-Class Design Overview

The Engine-Collection-Class design pattern requires a slightly different mindset than architects are generally accustomed to. They tend to approach a project with a procedural design—what processes must I complete to fulfill the requirements or to manipulate the data? With the ECC design pattern, those processes are secondary and, if the model is correctly designed, will fall logically into the objects themselves.

Figure 1 reveals the symbiotic relationship between the Engine, Collection, and Class. From a logical design perspective, the Class is actually the key reason that Engine and Collection exist. Class relates to a real world entity with its attributes (such as first name and last name). Engine and Collection provide the means for managing and extending its behavior. For an entity to be modeled, all three must be created.

Figure 1. The ECC design pattern relationship diagram

The ECC provides a three-tiered design for business objects by separating the creation, storage, and attributes of an entity.

The ECC design pattern uses object-oriented techniques such as encapsulation and some very popular design patterns such as the Object Factory, and the Opaque Object Adapter.

Note   For more information about these patterns read Visual Basic Design Patterns, which is listed in the References section.

In general, the Engine implements a design pattern known as the Object Factory, which defines an interface for creating other objects. In this case, the Engine creates both the Collection and the Class objects. Designing the Engine in this fashion provides the following benefits:

  • Controls and enforces the creation of the objects through a single interface
  • Provides the ability to check and enforce security on object creation
  • Provides run-time benefits in multithreaded processes by enforcing the creation of the object in the same COM apartment
  • Provides loose coupling between a Client and the Collection and Class objects, which allows the addition of new functionality without impacting the client
  • Enforces creation of the object in a known valid state

The Collection implements a pattern known as the Opaque Object Adapter, which allows one object to encapsulate and use another object's interface and functionality privately. This technique allows Visual Basic to simulate implementation inheritance even though it currently supports only interface inheritance. As shown in Figure 2, the Collection contains a reference to the ConcreteInternalStorage. This is known as composition. The ConcreteInternalStorage object is used to store Class objects, and depending on the amount of functionality provided, it can be used to provide extended functionality to the Collection. Arrays, Visual Basic's Collection object, the Dictionary object, and ADO Recordsets are all examples of ConcreteInternalStorage objects. Implementing the Collection like this provides the following benefits:

  • Implementation inheritance, even though Visual Basic currently doesn't support it
  • Enhanced functionality by changing the ConcreteInternalStorage object
  • Loose coupling between the Client and the Collection's internal storage object, which allows the objects to change with minimal or no impact to the Client
  • The ability to manipulate Class objects (such as calculating Sum, Count)

Class implements the notation of encapsulation, which means the internal implementation details are hidden from the client using it. This provides the benefit of allowing the implementation details to change without affecting the client.

Figure 2 reveals that the objects in the ECC design pattern are concrete classes. This means that the Visual Basic Implements keyword is not used and that there are no abstract classes (classes with just interfaces and no implementation). The main reason for this is that the ECC design pattern was geared toward supporting both traditional "rich" clients and the ever-popular scripting clients using the same components and without extra coding. This design decision helps reduce the time to market for projects and reduces costs because different components don't have to be created or maintained for internal (or external) applications.

Figure 2. The Engine-Collection-Class model

The Engine-Collection-Class model is implemented with the Object Factory design pattern and the Opaque Object Adapter pattern. The diagram shows the base methods that each of the objects can implement.

Design Benefits

The ECC design pattern is similar to a three-tiered design for business objects where the separation of an entity's creation, storage, and attributes provides enormous flexibility and benefits such as call interception, enforcement of business rules, data validation, derived attributes, simplified interfaces, and enforcement of object creation.

An effective ECC design is driven by the database (data source) design, which in turn is driven by the business requirements. In general, a data source schema mimics the relationship and storage of real world entities and a lot of thought goes into developing a data source or ERD. With the ECC design pattern, the Classes map to database tables, views, stored procedures, or other structured storage for a starting point to inherit the base business rules. Using this strategy leverages the existing work and knowledge and makes the design of the system easier to explain to others. This improves communication and feedback and reduces knowledge transfer issues. Object-Oriented techniques such as Use Cases work well for getting the scenarios to tie the pieces together, and tools such as Microsoft Visual Modeler and Microsoft Visio can be used to document and generate the objects.

As the data source is enhanced or modified, ECC can be extended via new properties or methods. This can be achieved by properly versioning the published interfaces in COM.

Note   You cannot change, modify, or remove the existing public interfaces without breaking COM compatibility. ECC does not solve problems associated with modifications to the system; rather, it helps to facilitate change more easily.

Wrapping the data source schema properties with the Classes provides a consistent layer of abstraction for accessing and manipulating the data. But Classes are not limited to the data source schema properties. Derived attributes can be defined to allow for the additional functionality. These attributes could physically exist in another database (data source such as LDAP or the Microsoft Active Directory Service), or they could be custom defined (IsNew, IsDirty), or they could be aggregate functions (Sum, Count). In the end, this layer of abstraction means that the details are hidden and free to change and provides a point of call interception. Call interception is the ability to access a request and handle whatever mediation is required to adjust the request for either the caller or the callee. For example, in Visual Basic when you call the New operator, Visual Basic will create the object the first time it is referenced. The developer doesn't need to do anything for this feature; however, nor can you tap into the call to change it. The type of call interception that developers can capture is at a higher level (property calls, method calls, interface calls, and events). This gives the developer the ability to respond to requests. This is a critical feature because ECC uses this call interception to enforce business rules. For example, a business rule might be that the password field must be at least four characters and no greater then 10 characters long. The Password property becomes a hook to check and enforce this business rule, which in the end maintains data integrity of the underlying data store without making a call across the wire. Another benefit is that the rule is contained within a single place—one that offers better manageability if it were to change.

The last, but certainly not the least, benefit of the ECC design pattern is the ability to provide a simplified interface. Interfaces are like a binding contract in that they define what a component can do. But unlike a contract where the details are in one large document, the ECC design pattern divides the functionality into separate objects with fewer interfaces and specific roles. Therefore, each object can support functionality that is different from its own but consistent throughout the application for the role of that object. For example, Engine and Collection will support the same base functionality, although they perform functions differently from one another. The Engine is used to create Collection and Class objects, and the Collection is used to store and manipulate Class objects. Therefore, instead of creating one super-object that supports both interfaces, separating the objects allows for a more simplified interface that is easier to understand. Furthermore, this allows each of the objects to encapsulate specific roles and functionality, as well as allowing each to evolve separately without affecting the other. In this way, developers achieve a flexible application design without sacrificing code simplicity or consistency. Furthermore, a developer who is familiar with this model can begin at various points in the development process without a huge loss of productivity due to a steep learning curve.

What follows is a detailed explanation of each of these objects and their meaning in the context of the ECC model.

Class Design

The Class design provides the blueprint for mapping a real world entity to a business component and is the main reason the Engine and Collection objects exist. The responsibilities of the Class are:

  • Enforcing business rules and maintaining data integrity at the attribute level
  • Hiding implementation details of the Class attributes
  • Connecting other objects together to mimic complex relationships (optional)

If a Class exists in your model, you must have an Engine and a Collection object. In the case where a Class does not exist, no corresponding Collection will exist and, although the Engine may exist, it will be used as a service or utility object.

Because business rules are project-specific, enforcement of them is implementation-specific. The main point is that your design architecture should permit the flexibility to define the business rule(s) in one place but enforce them in multiple places. The Class is responsible for enforcing the following:

  • Validity of the data—maintains data integrity
  • Format of the data—allows custom formatting conditions, which could include data transformation
  • Security on data access—checks the user's security to make sure they can update this field or perform this action (provides role-based security)

Unlike the Engine and Collection, the methods and properties of the Class are based on the data schema. Each Class can mirror the entities in the application. The mapping of Class properties to data properties is implementation-specific. For example, you could have a single Class that references three physical tables/views or three separate Classes. Each design needs to analyze the purpose of the object and determine which model is best suited for the implementation. Some things to consider when determining the implementation design are method boundaries, transaction boundaries, security, and data location (SQL Server, COMTI, and so forth). Figure 3 shows the ERD for a sample implementation. In this example, there is a Class for each of the entities: UserInfo, Account, Bill. Furthermore, if the entities are similar in structure, a single Class can be used to expose multiple entities. In the sample implementation, the IOBPLookup Class implements this functionality for the ActivityType, BillType, Status, and UserType. These tables mainly store read-only information that is used to populate controls or down-drop lists. One benefit of grouping the entities into a set of Classes is to share functionality. For example, if the IOBPLookup Class implemented a caching mechanism, the other components could use its functionality. This reduces the amount of code to be developed, tested, and maintained.

Figure 3. Online Bill database diagram

The sample implementation includes some additional business-specific fields:

  • RecordTimestamp   for concurrency and locking of the record
  • DeleteFlag   marks a record for deletion, deletion happens via an external process: manual or automated
  • CreateDateTime   stores the date/time the record was created for auditing purposes
  • UpdateDateTime   stores the date/time the record was last updated for auditing purposes

The entities in this model are accessed via stored procedures, one for each function: Read, Insert, Update, and Delete. The sample implementation uses a single stored procedure for functions, but with ECC you can access multiple stored procedures to make data changes, which would be implemented at the Collection. They are based on a macro template that is used inside ERwin to auto-generate the entities (see the macro template "database\onlinebills.er1" in the code sample associated with this article). You can use normal SQL statements, but for true flexibility stored procedures are encouraged. If you do decide to use SQL statements, a general Select, Insert, Update, and Delete statement should be built. Note, however, that there are some limitations to using SQL statements. For example:

  • Inserts can require multiple round-trips depending on the generation of the row identifier and use of a locking mechanism.
  • Updates can require multiple round-trips based on the use of columns like UpdateDateTime and RecordTimestamp.

Figure 4 depicts a sample Class based on the UserInfo entity and the additional properties that should be included for ECC. Visual Modeler was used to generate the initial diagram derived from the database table. Note that the object is prefixed c to denote a Class.

Figure 4. Basic Class diagram for the UserInfo entity

Each attribute in the database has two properties: Let() or Set(), and Get(). Each property can be marked as Public, Protected (Friend), or Private. Figure 4 shows that UserNumber property is public readable (Get) and protected from writes (Let). The UserNumber property is the primary key for the Class and consequently should not be updated by an external entity because it is auto-generated from the database (an Identity column, for example). By using this technique, the Engine can initialize the Class properties but still provide a mechanism where the value cannot be tampered with or changed. The Class is then self-contained, which is helpful because it can then be persisted and recreated.

The base recommendations for the Class interface follow:

  • DirtyBoolean, Friend Let(), Public Get()—is set when an attribute is changed at the record level. Also used by the Collection methods to detect if the state of the Class has changed. This would not be used on a read-only class.
  • ClassStorageBoolean, Friend Let(), Friend Get()—is used to bypass the internal business rule checks contained at the attribute level. Also used by the Engine to initially set the individual properties or the Collection to retrieve values in Update().
  • IsNewBoolean, Friend Let(), Public Get()—is set when a new Class is added to the Collection. This is used by the Collection to determine if this record does not currently exist in the data store. This would not be used when inserts are not allowed. This is similar to EditMode in ADO.
  • SecurityTokenGUID (or some unique identifier), Friend Let(), Friend Get()—is used to store a user's security token for field level restriction. If no field level validation is required this property is optional.
    Note   This is an advanced ECC concept that is not covered in this document but is included in the implementation.

Other interfaces that could be added to the Class include:

  • Properties for each field item with appropriate Let(), Get() or Set() methods and declarations (Public, Protected/Friend, Private).
  • Derived properties or methods that return values based on an attribute(s) in the Class. For example, if the Class is Student, you could write a method that calculates grade point averages.
  • On-Demand Loading property or methods to load extra information when needed. Usually this information is expensive to retrieve in terms of resources and time, so you only want to get it when it is needed.
  • Hierarchy property, which is used to build hierarchies or structures in the design. For example, the UserInfo Class has a one-to-many relationship with the Account Class. A method could be added to allow direct access to the Account information through UserInfo Class. This approach can be used with the On-Demand Loading property.
    Note   This is an advanced ECC concept that is not covered in this document but is included in the implementation.

In the sample implementation each public interface property is defined using the Variant data type for flexibility. Though the properties can be set to other intrinsic data types, the Variant provides the ability for scripting clients to pass items by reference. In Visual Basic Clients, Variant data types can be type cast to a specific data type, which will remove the performance issues. The only problem that still would exist is that the ambiguous type description of a parameter as a Variant can be anything. Documenting the interfaces and publishing this information for other developers to use overcomes this. Another benefit is the ability to change the data type without redefining the interface. This allows the interface to adapt better to changing business requirements.

The values for each property are stored in private variables. These can be type cast to the appropriate data types to enforce type checking of the values. Doing so allows the appropriate errors to be raised to inform the user of the invalid data types used. Storing the values in the private variables in their true storage state is also encouraged. This can cause problems when the database can store NULL and the data type cannot, for example Integer. To solve this problem, the sample implementation uses Variant for most of the private variables to handle both a valid data value and the NULL condition.

This process is repeated for each entity that needs to be created for the application.

Class Implementation

The Get property is responsible for returning the data to the caller, possibly with special formatting conditions applied. A typical Get statement follows:

Public Property Get UserID() As Variant
    On Error GoTo ErrorHandler

    If Me.ClassStorage Then
        'Used to obtain the raw data (unformatted)
        UserID = mvarUserID
    Else
        'No special formatting currently required
        UserID = mvarUserID
    End If
    <>
End Property

The preceding If..Else statement, or some variation of it, should appear in every Get property. The basic rules are:

  • Check to see if business rule checks should be bypassed (ClassStorage)
  • Check security or other class level rules (not implemented in this example)
  • Format or prepare the data for the consumer

As you may have noticed, the If..Else statement evaluates to the same condition, because the Get property code structure is laid out in advance; should the case arise where different functionality is required, it can be easily implemented. This property structure is intuitive for code-generation programs like Visio and Visual Modeler. Providing templates for these tools significantly increases productivity and reduces project timelines.

The Let property is responsible for checking the data type, enforcing business rules, and setting a local (private) variable to the value passed in. Each property has an associated local variable with the same name as the property, prefixed by the letter m (for member variable) and a type (Hungarian notation). The local copy of the ID property would be: mvarUserID. A typical Let property follows:

Public Property Let UserID(ByVal vData As Variant)
    Dim errNum as long
    Dim errSrc as string

    On Error GoTo ErrorHandler

    If Me.ClassStorage Then
        'Used to change the raw data (unformatted)
        mvarUserID = vData
    ElseIf Me.DeleteFlag Then
        Err.Raise ERR_USERINFODELETEFLAGSET, "CUserInfo.UserID LET", _
            "Can't change the value because the record is marked " & _
            "for deletion."
        GoTo CleanExit   'Note, this provides a safety net
    Else
        'Disable error handling to capture type mismatches
        On error Resume Next
        mvarUserID = vData
        if err.number <> 0 then
            'Use the base error plus applications flags
            errNum = Err.Number Or OBPExBusinessRule Or OBPUserError
            errSrc = Err.Source
            Err.Clear    'reset the error state
            On Error GoTo ErrorHandler 
            Err.Raise errNum, errSrc, "Please enter a valid value"
        Else
            On Error Goto ErrorHandler
        end if
        'Check business conditions here
        Me.Dirty = True
    End If
    <>
End Property

The first If..Else line above, or some variation of it, should appear in every Let property to make sure that the data received is of a valid data type for this property, and to make sure that the caller actually passed some value in. The basic rules are:

  • Check to see if business rule checks should be bypassed (ClassStorage).
  • Check security or other class level rules (like the delete condition).
  • Check to see if the variable is uninitialized, if necessary.
  • Check the data type.
  • Check business rules (like length).
  • Format or prepare the data for the internal value (not shown in this example).
  • Set the Dirty condition (not used for read-only).

The first step is to determine if the business rule checks should be bypassed. Note that the ClassStorage property of the Class is turned off after it is fully populated. Thus, subsequent calls to the Class will check and enforce the business rules. This reduces the overhead of loading the Collection and Class and allows access to the raw data.

Once the business rules are enforced, the next step is to check if the Class has been marked for deletion. If so, there is no point in updating this property's value. This example raises an error on this condition.

The last step is to enforce the business rule. You could add a check to enforce that the UserID must be greater then n characters in length. (In reality, the minimum length value should be read from the system parameter table.) If the data passes the tests, it then assigns the local copy the value.

In this example, another property variable called Dirty is also set. Dirty indicates whether the data has changed. Notice that the member variable mvarDirty is not used to set the value directly; instead, the Let method for the Dirty property sets it. This ensures that the business rules are enforced even when the property is set from within the class itself.

Note   There is some overhead involved with setting variables through the Let methods, so it is acceptable to manipulate the private variables directly in some cases. But be careful because this makes debugging and maintenance of the component harder.

In the example above, an error called Goto CleanExit is raised. This should never be executed but should be used to show program flow.

Our UserInfo Class also contains a method called Account. This demonstrates the ECC design pattern's implementation of both On-Demand loading (the ability to load objects when they are needed) and Hierarchies (the ability to connect different ECC components together). Since the UserInfo and Account entity have a relationship (one-to-many), this implementation allows the developer to retrieve Account objects based on an existing UserInfo object. It therefore permits simple and intuitive code such as:

Set objCAccount = objUserInfo.Account.Item(1)

In the following example, the Account method will return a Collection (ColAccount), which contains Account Classes (CAccount). Late binding is used and gives better flexibility during the development cycle. Late binding can be avoided by generating TLB files for the components. The Account objects, respectively CAccount and ColAccount, reside in a different DLLs from the UserInfo objects and are marked as PublicNotCreatable, so you must call the Account Engine to create and retrieve these objects:

Public Property Get Account() As Object
    On Error GoTo ErrorHandler
    
    Dim engAccount As Object
    Dim ErrorNum As Long
    
    If mcolAccount Is Nothing Then
        If Not SafeCreateObject(engAccount, OBP_ACCOUNT_ENG, _
            ErrorNum) Then
            Err.Raise ERR_ACCOUNTSAFECREATEFAILED, _
                  "IOBPUserInfoEng.Account Property", "Unable to " & _
                  "create " & OBP_ACCOUNT_ENG & ". Return Code: " & _
                  ErrorNum
            GoTo CleanExit
        End If
        'Only get the non-deleted records (default condition).
        Set mcolAccount = engAccount.Search(SecurityToken := _
            SecurityToken, UserNumber := Me.UserNumber)
    End If
    Set Account = mcolAccount
    <>
End Property

Look at this a little closer: First, check the internal variable to see if there is a current reference to an Account Collection. If there is, return the Collection; otherwise, call the Search method on the Account Engine to return the Account(s) associated with the current UserNumber. This means that for each CUserInfo Class in the ColUserInfo Collection there will be a specific Account or Accounts for that individual UserNumber—in other words, a hierarchy. Furthermore, the Account Collection did not exist in the CUserInfo prior to the call to the Account method, therefore on-demand loading is achieved. Since the internal call to the Search method is only done once, a parameter such as Refresh could be added to the interface of the object. This would allow for dynamically reloading the contents without destroying and recreating CUserInfo. This demonstrates the reusability built into ECC and the value of loose coupling objects in the hierarchy. It also allows implementation details to change with minimal impact. The consumer would never know that this is occurring under the hood. A sample consumer's line of code follows:

strAccountNumber = ColUserInfo.Item(1).Account.Item(1).Number

It's important to understand exactly what happens in this line. First, it assumes you have created a UserInfo Collection called ColUserInfo containing a user with at least one account. The first step, ColUserInfo.Item(1), calls the Item method and returns the CUserInfo Class corresponding to that user. The next step, .Account, creates an Account Engine that creates an Account Collection, populates it with a CAccount Class (or Classes), and then returns the Collection. The next step, .Item(1), calls the Item method of the Account Collection that returns the first CAccount Class. The final step, .Number, calls the Number Property Get method and returns a string with the account number, which in turn is set to the strAccountNumber variable.

That's a lot of work to get one property, and to access every property of that particular object would mean creating a Collection and Class for each property accessed. In this case, it would be better to write the code as:

      Dim ColAccounts as colAccount
      
      Set ColAccounts = UserInfo.Item(1).Account
      strAccountCode = ColAcounts.Item(1).Number
      strPhone = ColAccounts.Item(1).CustPhone
      strName = ColAccounts.Item(1).CustName

With this method, overhead of several calls to the Account method on the UserInfo is avoided. A For..Each construct could be used also. When accessing only one account, you can write:

      Dim objAccount as CAccount

      Set objAccount = UserInfo.Item(1).Account.Item(1)
      strAccountCode = objAccount.Number
      strPhone = objAccount.Phone
      strName = objAccount.CustName

This is perfectly valid and reduces the overhead of multiple calls to the Items() method for each account property. It should also give you the best performance.

Collection Design

The Collection is a container for Class objects and is a wrapper for an internal storage scheme. The Collection/Class model should be familiar to most object-oriented developers and Visual Basic developers in particular. In the ECC model, the key Collection responsibilities are:

  • Provide a container for storing Classes
  • Separate the interface from the implementation details of the container
  • Provide services to manipulate the Classes in the container
  • Persist the Class(es) information
  • Connect other objects to mimic complex relationships or hierarchies (optional)

The internal storage for the Collection can be implemented in many different ways: an array, a linked list, a Recordset, XML, or another common data storage model. The important concept here is that the actual storage is hidden from the high-level developer, who will always access the data in the same manner regardless of its storage. As the Class, the interface to the Collection is separated from the implementation, thus reducing the impact of implementation changes.

A sample Collection that stores the CUserInfo Class and the additional properties/methods that are recommended by the ECC design pattern follows. Visual Modeler was used to generate the initial diagram.

Note   The object is prefixed with col to represent a Collection.

Having a separate Collection for each Class allows for better flexibility because it allows the interface to be customized appropriately for the Class. An example of this is Add(), which requires specific UserInfo parameters to be passed in.

Figure 5. Basic collection diagram for the UserInfo Entity

The base recommendations for the Collection interface, which provides Create, Read, Update, and Delete (CRUD) functionality, follow:

  • AddLong, Public Function()—is used to add a new Class to the Collection based on defined parameters. For example, this implementation requires that new UserInfo Classes require four parameters before a new Class will be created. This returns the Index of the new object.
    Note   Add() method should put new Classes at the end of the Collection, otherwise the existing indexes will be invalid. It can also check to see if the item ahead exists (implementation specific).
  • CountLong, Public Get()—is used to return the number of Classes in the Collection.
  • DeleteBoolean, Public Function()—is used to delete one or many persisted Classes from an external data source.
  • Item——Class Object, Public Get() is used to retrieve the item in the Collection based upon the Index offset. This could be expanded to support additional index types or values.
  • LoadBoolean, Friend Function()—is used by the Engine to load the initial state of the Collection and Class. This is very similar to an object constructor. This method should be protected to avoid security and data integrity problems.
  • RemoveBoolean, Public Function ()—is used to remove a Class from the Collection based on an index key (memory only).
  • SecurityTokenVariant, Friend Let(), Friend Get()—is used to store the user's security token. This needs to be protected for security reasons.
  • UpdateBoolean, Public Function()—is used to persist one or many Classes to the data source. This function both inserts and updates depending on the Dirty and IsNew values.

Some additional implementation-specific properties/methods follow:

  • ClearPublic Sub()—removes items from the Collection (memory only). This is good for memory management and special features like Undo.
  • MarkForDeleteBoolean, Public Function()—is used to mark an item for deletion (DeleteFlag). This does not go across the wire until the Update method is called. This method is handy when you want to mark multiple items in the Collection for deletion.
  • NewEnumIUnknown, Public Function(), Iterator— is a method used for the Collection. This can be used by For..Each syntax in Visual Basic. This is an example of how to use the internal storage objects functionality.
    Note   This function needs to be supported by the internal storage, otherwise additional custom code is required.
  • StoreByte Array, Public Function()—returns the internal storage of the Collection in a data stream form.
    Note   This is an advanced ECC concept that is not covered in this document but is included in the implementation.
  • UnDeleteBoolean, Public Function()—unmarks an item for deletion (the opposite of MarkForDelete).

Most of the methods are self-contained in the module, but Delete and Update methods actually make external calls to the data access layer. Some of their key functions include:

  • Packs the Class information into some transport structure (variant(), recordset).
  • Determines which command needs to be executed (stored procedure, interface, insert, update, delete).
  • Sends the Class information to an external source (Data Access Layer, ADO, COMTI).
  • Unpacks the results into the internal storage.

Other functionalities that can be added to the Collection include:

  • Derived properties or methods that return values based on the Classes in the Collection. For example, if the Collection is filled with Student Classes, you could write methods that calculate the average grade of the students in the Collection.
  • On-Demand Loading properties or methods to load extra information when needed. Usually this information is expensive to retrieve in terms of resources and time, so you only want to get it when it is needed.

This process is repeated for each Collection that needs to be created for the application.

Collection Implementation

In most implementations, the Collection is a wrapper for a Visual Basic collection, which resides in the private portion of the Collection. In most cases, the Collection will contain the following methods: Item(), Count(), Load(), Add(), Delete(), and Update().

A discussion of each method follows.

Item():

Public Property Get Item(ByVal Index As Variant) As CUserInfo
    On Error GoTo ErrorHandler
    Dim mintCodeID As Integer
    
    Set Item = Nothing
    If mCol Is Nothing Then
        GoTo CleanExit
    End If
    If mCol.Count = 0 Then
        GoTo CleanExit
    End If
    If Trim(Index & "") = "" Or Index <= 0 Then
        GoTo CleanExit
    Else
        'Cast the index to a Integer otherwise sets won't work
        mintCodeID = CInt(Index)
        Set Item = mCol.Item(mintCodeID)
    End If
    <>
End Property

Item() returns a CUserInfo that is stored inside the Collection. It first applies the default condition checks: empty, range, and type, then it sets a reference to the Class.

This function can vary depending on the internal storage model that is used. For most of the sample implementation, the Visual Basic collection is used but the sample code does include a sample of a different storage model.

Note   This is an advanced ECC concept that is not covered in this document but is included in the implementation.

Count():

Public Property Get Count() As Variant
    On Error GoTo ErrorHandler

    If mCol Is Nothing Then
        Count = 0
    Else
        Count = mCol.Count
    End If
    <>
End Property

Count() returns either zero or the number of the internally stored Classes.

Note   If a Variant Array were used for the internal storage mechanism, a function such as Ubound could be used to determine the number of items.

Load():

Friend Function Load(ByVal SecurityToken As Variant, ByVal _
    FilledStorage As Variant) As Variant
    
    On Error GoTo ErrorHandler

    Load = False
    mvarSecurityToken = SecurityToken
    Set mCol = FilledStorage
    Load = True
    <>
End Function

The Load() method assumes the Engine has already filled internal storage with the necessary data. It then just assigns the data to a local variable. Allowing the Engine to fill the internal storage gives better flexibility since it knows the original request. It also stores a copy of the SecurityToken for later requests that might need it for processing.

Add():

Public Function Add(ByVal UserID As Variant, ByVal UserTypeID As _
      Variant, ByVal Password As Variant, ByVal Name As Variant) _
      As Variant
    On Error GoTo ErrorHandler
    Dim NewTemp As CUserInfo
    
    'Set default return value
    Add = -1
    'Check parameter values
    If UserID = "" Or UserTypeID = "" Or Password = "" _
            Or Name = "" Then
        Err.Raise ERR_USERINFOFAILEDADDCHECK, _
            "ColUserInfo.Add PROC", "You must specify values"
        GoTo CleanExit
    End If
    If mCol Is Nothing Then
        Set mCol = New Collection
    End If    
    Set NewTemp = New CUserInfo    
    With NewTemp
        'Set the Required Fields
        .ClassStorage = True
        .UserNumber = 0
        .UserID = UserID
        .UserTypeID = UserTypeID
        .Name = Name
        <>
        .Password = Password
        .SecurityToken = SecurityToken
        .ClassStorage = False
    End With        
    'Add the item to the End of the Collection
    If mCol.Count <> 0 Then
        mCol.Add NewTemp, , , mCol.Count
    Else
        mCol.Add NewTemp
    End If    
    Add = mCol.Count
    <>
End Function

The Add() method first checks the parameters that are passed for conformance to the business rules. It then creates a user Class (CUser), assigns the default values to the properties, and adds the CUser to the internal storage. It also creates the internal collection member if that has not been created. The function returns the item number of the Class in the Collection so that the caller may access the item immediately.

Delete():

Note   Remember that this method goes across the wire to an external source.
Public Function Delete(Optional ByVal Index As Variant) As Variant
    On Error GoTo ErrorHandler

    Dim ErrorNum As Long
    Dim oDALEng As IOBPDA.IOBPConnection
    Dim oCUserInfo As CUserInfo
    Dim vParam() As Variant
    Dim LowerLimit As Long
    Dim UpperLimit As Long
    Dim inx As Long
    Dim SPName As String
    
    Delete = False
        
    If Me.Count = 0 Then    'Nothing to Update
        Update = True
        GoTo CleanExit
    End If
    'If Index is supplied then Delete only the supplied record
    If Not IsMissing(Index) Then
        If Index < 1 Or Index > Me.Count Then
             Err.Raise ERR_USERINFOINDEXOUTOFRANGE, _
                  "ColUserInfo.Delete PROC", "Index out of range"
             GoTo CleanExit
        Else
            LowerLimit = Index
            UpperLimit = Index
        End If
    Else
        LowerLimit = 1
        UpperLimit = Me.Count
    End If
    If Not SafeCreateObject(oDALEng, OBP_DA_CONNECTION, ErrorNum) Then
        Err.Raise ERR_USERINFOSAFECREATEFAILED, _
            "ColUserInfo.Delete PROC", "Unable to create " & _
            OBP_DA_CONNECTION & ". Return Code was: " & ErrorNum
        GoTo CleanExit
    End If
    For inx = UpperLimit To LowerLimit Step -1
        Set oCUserInfo = Me.Item(inx)
        If Not oCUserInfo.IsNew Then
            'Delete from DB If Not New
            ReDim vParam(PARMUBOUND, 1)
            With oCUserInfo
                .ClassStorage = True
                'Fill Parameter Array
                vParam(PARMNAME, 0) = PARMNAMESP_USERINFOUSERNUMBER
                vParam(PARMTYPE, 0) = PARMTYPESP_USERINFOUSERNUMBER
                vParam(PARMLENGTH, 0) = 4
                vParam(PARMDIR, 0) = adInput
                vParam(PARMVALUE, 0) = .UserNumber
            
                <>

                .ClassStorage = False
            End With
            If Not oDALEng.Execute(SecurityToken, SP_D_USERINFO, _
                  vParam) Then
                Err.Raise ERR_USERINFODALDELETEFAILED, _
                        "ColUserInfo.Delete PROC", _
                        "Delete Failed. SPName was: " & SP_D_USERINFO
                GoTo CleanExit
            End If
        End If
        Set oCUserInfo = Nothing
        'Remove from Collection
        mCol.Remove (inx)
    Next    
    Delete = True
    <>
End Function

The Delete() method deletes one or many items from the data storage, which is usually a database. If the item is not new (IsNew = False), then it will be deleted from the database. This is because records that have their IsNew property set to true don't exist in the database. The method accomplishes the delete by packaging up the primary key and the record timestamp; this information is passed to the data access object, which will delete the record. The item is then removed from the Collection to make sure that the client does not access data that doesn't exist in the database. This is useful for "rich" clients where state is maintained.

Note   The Delete method may cause the ItemIndex to change for the Collection, particularly if it is stored in consumer code. It is advisable to refresh the references after Delete() to make sure they point to the correct item in the Collection.

Update():

Note   Remember that this method also goes across the wire.
Public Function Update(Optional ByVal Index As Variant) As Variant
    On Error GoTo ErrorHandler

    Dim ErrorNum As Long
    Dim oDALEng As IOBPDA.IOBPConnection
    Dim rs As ADOR.Recordset
    Dim oCUserInfo As CUserInfo
    Dim vParam() As Variant
    Dim LowerLimit As Long
    Dim UpperLimit As Long
    Dim inx As Long
    Dim SPName As String
    
    Update = False
        
    If Me.Count = 0 Then    'Nothing to Update
        Update = True
        GoTo CleanExit
    End If
    'If Index is supplied then update only the supplied record
    If Not IsMissing(Index) Then
        If Index < 1 Or Index > Me.Count Then
             Err.Raise ERR_USERINFOINDEXOUTOFRANGE, _
             "ColUserInfo.Update PROC", "Index out of range"
             GoTo CleanExit
        Else
            LowerLimit = Index
            UpperLimit = Index
        End If
    Else
        LowerLimit = 1
        UpperLimit = Me.Count
    End If
    If Not SafeCreateObject(oDALEng, OBP_DA_CONNECTION, ErrorNum) Then
        Err.Raise ERR_USERINFOSAFECREATEFAILED, _
            "ColUserInfo.Update PROC", "Unable to create " & _
            OBP_DA_CONNECTION & ". Return Code was: " & ErrorNum
        GoTo CleanExit
    End If
    For inx = LowerLimit To UpperLimit
        Set oCUserInfo = Me.Item(inx)
        If oCUserInfo.Dirty Then
            ReDim vParam(PARMUBOUND, 16)
            With oCUserInfo
                .ClassStorage = True
                'Fill Parameter Array
                vParam(PARMNAME, 0) = PARMNAMESP_USERINFOUSERNUMBER
                vParam(PARMTYPE, 0) = PARMTYPESP_USERINFOUSERNUMBER
                vParam(PARMLENGTH, 0) = 0
                vParam(PARMDIR, 0) = adInput
                vParam(PARMVALUE, 0) = .UserNumber
                <>
                .ClassStorage = False
            End With
            'Check to see if updating existing record to database
            If oCUserInfo.IsNew = False Then
                'Update the record
                SPName = SP_U_USERINFO
            Else
                'Inserting the record
                SPName = SP_I_USERINFO
            End If           
            If Not oDALEng.Execute(SecurityToken, SPName, vParam, _
                    rs) Then
                Err.Raise ERR_USERINFODALUPDATEFAILED, _
                    "ColUserInfo.Update PROC", _
                    "Update to Database Failed. SPName was: " & _
                    SPName
                GoTo CleanExit
            ElseIf (Not rs Is Nothing) Then
                'Set to True if nothing returned in DB
                If rs.RecordCount > 1 Then
                    Err.Raise ERR_RETURNTOOMANYRECORDS, _
                    "ColUserInfo.Update PROC", _
                    "Update to Database Failed. Returned more " & _
                    "than one record. SPName was: " & SPName
                    GoTo CleanExit
                Else
                    'Update collection with returned data
                    With oCUserInfo
                        .ClassStorage = 
                        .UserNumber = rs.Fields(FN_USERINFOUSERNUMBER)
                        .UserID = rs.Fields(FN_USERINFOUSERID)
                        .UserTypeID = rs.Fields(FN_USERINFOUSERTYPEID)
                        <>
                        .IsNew = False
                        .SecurityToken = SecurityToken
                        .ClassStorage = False
                        .Dirty = False
                    End With
                End If
            End If
        End If 'Dirty
        Set oCUserInfo = Nothing
    Next inx
    Update = True
    <>
End Function

Update() is similar to Delete() in that it can update one or many items. Another key feature is the ability to choose between insert and update. The IsNew property is again used, but in this case, true means that the method should perform an insert and false means it should perform an update. The information for the Class is packed and sent to the Data Access object where it is either inserted or updated. Once updated or inserted remotely, the internal Class is updated to reflect the changes that might have changed on the database. For example, the Recordtimestamp will change or be created, and therefore the Class needs to reflect the new value.

For the other functions, see the code sample associated with this article.

Engine Design

The Engine is a layer above the Collection and the Class and exists as a single point of entry to them. The Engine becomes an insulating layer for both security and control of object creation. Its key responsibilities follow:

  • Acts as a surrogate for Collection and Class creation
  • Checks security prior to object creation
  • Packages Classes into a Collection and return them to the consumer
  • Provides a standard interface for accessing the Collection and Class
  • Provides business specific processing

The Engine is the public interface for a particular object and is the only public creatable class in the ECC model. This has a lot of benefits. By being the only publicly creatable object, the Engine can stand alone and implement generic business functionality. In this capacity, the Engine is called a service and has no corresponding Collection and Class. An example of a service object would be one that just does business processing and returns a value. The implementation of the IOBPSecurity shows how service object works.

In a more powerful use of the Engine, it is combined with a Collection and Class. In this case, the Engine is the main interface to the other objects and can control how they are created. As Figure 2 depicts, the Engine is somewhat coupled to the Collection and Class, since it must know how to retrieve the Class information, pack it, and then place into the Collection. However, in terms of implementation details, the Engine is loosely coupled to these objects. Some of the key reasons for designing the Engine in this manner are:

  • To enforce security on the object creation. Nothing can create a Collection or Class without the Engine validating that person (or process, or server, and so forth). Validation can be done by external components; therefore the security of the application can change freely.
  • To separate the interface of accessing objects from their implementation so the implementation for the Collection and Class can change without affecting the consumer (or consumers) that call the Engine. The interface also provides a standard way for calling the objects.
  • To hide (or encapsulate) object creation. The Engine can implement the creation request for the Collection and Class in many different ways. In each scenario, the consumer is not affected and the developer has more flexibility.

One of the key roles for the Engine is to provide a way to package and return Classes. This functionality is implemented in one or many methods that start with the Get prefix. The Engine doesn't return the individual Classes. It places them into a Collection and then returns the Collection. Therefore, for an Engine to work in the ECC model, it must provide a way to return Classes.

Following is a sample Engine that will create CUserInfo and ColUserInfo and then discuss the additional recommended properties/methods that should be included for the ECC design pattern. Visual Modeler was used to generate the initial diagram.

Note   The ECC design pattern uses a consistent naming convention for objects. At the Engine level it is a combination of a prefix and suffix around the object name. The prefix has two parts: an "I" for interface, and "OBP" for the application specific code or acronym. The suffix "Eng" is for Engine. This naming convention serves two purposes: objects can be found much easier in the registry, and the objects are grouped together in the Visual Basic IDE References Window.

Figure 6: Basic engine diagram for the UserInfo Entity

In the example, the key method is called GetUserInfo. The syntax is usually Get<Class name>. This allows an Engine to return one or many Collection and Classes. Another way to implement this is by using more specific methods, for example, GetUsersByEmail. It is quite intuitive that this method will return Users for a particular e-mail address. The method chosen in the sample implementation uses a more generic Search method that takes different optional parameters on which to find and return different users. The implementation details are left up to you.

Other business specific functionalities can be added to the UserInfo Engine. For example, an EnrollUser function can be provided as a means to enroll a user into the system. As mentioned before, here is where the processes that the Entity must implement are incorporated into the ECC design. The processes seem to intuitively go where they belong—with the objects that exhibit this functionality. Furthermore, the Engine is a stateless object; therefore, it should not have properties.

The base method that should be in this implementation follows:

  • Get<ClassName> is responsible for creating the Classes, filling them with information, placing them into a Collection, and then returning the Collection. There should be at least one method of this type. This method typically retrieves items by their primary key. The default behavior for this method (or an Engine method that returns a Collection) when the record cannot be found is to return a Collection that contains no Classes (col<ClassName>.Count = 0). The reason for returning an empty Collection is to allow the consumer to be able to call col<ClassName>.Add() instead of returning to the Engine and calling the New() method. This approach allows the consumer to check the count and then immediately call the Add() method and the returned Collection. The other default behavior is an error condition; this happens when something fails. When this occurs two things should be done: first, set the return reference to nothing and then raise an error to the consumer (caller).

Additional methods include:

  • Search<ClassName> is very similar to Get<ClassName>, but this could be on any field. It is advisable to define data parameters as optional to support growth. The underlying code will determine if the consumer passed enough data to do an appropriate search.
  • New<ClassName> creates an empty Collection for adding Classes. This is good for data entry where an initial round-trip to the data source is not needed.
  • Restore<ClassName> is the opposite of Store method on the Collection. It allows the consumer to restore, or re-hydrate, the persisted state of the Collection and Class(es).
    Note   This is an advanced ECC concept that is not covered in this document but is included in the implementation.

Other functionality that could be added to the Engine includes:

  • Service-based methods, an example of this is GetUserType. This will return the UserType based on the UserNumber.

Engine Implementation

The Engine is in charge of creating a Collection and packing it with one or more Classes. In this capacity, the Engine is known as a class factory, because it is responsible for the creation of other objects. In the ECC design pattern, the Engine is a very specific factory that can create a specific Class. The sample code below demonstrates how a client would code and use the Engine to return a UserInfo Collection (ColUserInfo):

    Dim UserEngine As UserEng
    Dim Users As ColUserInfo
    Dim User as CUserInfo
    Dim strUserNumber as String

    Set UserEngine = New IOBPUserInfoEng
    strUserNumber = 10   
    Set Users = UserEngine.GetUserInfo("Token", strUserNumber)
    Set UserEngine = Nothing
    Set User = Users.Item(Users.Count)
    MsgBox User.Name & "'s E-Mail address is: " & User.EmailAddress

As you can see, the client application only needs to know the user number to access the information about the user and does not need to know the underlining details, such as the field name of the requested data or how to manipulate the internal storage (a recordset or XML DOM object, for example). If the field name or the internal storage were to change, that change would occur in the Engine rather than in the client applications that use that Class.

Notice that the Engine returns a Collection of users, and once this is completed the Engine is finished and can be deleted (set to nothing). Now you can use the properties of the items in the Collection. The implementation for the GetUserInfo method in the user Engine appears as follows:

Public Function GetUserInfo(ByVal SecurityToken As Variant, _
        Optional ByVal UserNumber As Variant, _
        Optional ByVal DeletedRecords As DeleteRecordType = _
        NondeletedRecords) As ColUserInfo
    
    On Error GoTo ErrorHandler

    Dim ErrorNum As Long
    Dim oDALEng As IOBPDA.IOBPConnection
    Dim rsUserInfo As ADOR.Recordset
    Dim oColUserInfo As ColUserInfo
    Dim oCUserInfo As CUserInfo
    Dim vParameters() As Variant
    Dim col As Collection
    Dim inx As Long

    Set GetUserInfo = Nothing
    'Check security token first
    If Trim(SecurityToken) = "" Then
        Err.Raise ERR_USERINFOINVALIDSECURITYTOKEN, _
            "IOBPUserinfoEng.GetUserInfo PROC", _
            "Invalid security token. Security Token cannot be empty."
        GoTo CleanExit
    End If
    ReDim vParameters(PARMUBOUND, 0)
    If IsMissing(UserNumber) Then
        UserNumber = Null
    ElseIf Trim(UserNumber) = "" Then
        UserNumber = Null
    End If
    'Start packaging the parameters
    vParameters(PARMNAME, 0) = PARMNAMESP_USERINFOUSERNUMBER    'Name
    vParameters(PARMTYPE, 0) = PARMTYPESP_USERINFOUSERNUMBER     'Type
    vParameters(PARMLENGTH, 0) = 0    'Size
    vParameters(PARMDIR, 0) = adInput     'Direction
    vParameters(PARMVALUE, 0) = UserNumber     'Value
    If DeletedRecords <> AllRecords Then
        If Not IsEmptyArray(vParameters) Then
            inx = UBound(vParameters, 2) + 1
            ReDim Preserve vParameters(PARMUBOUND, inx)
        Else
            inx = 0
            ReDim vParameters(PARMUBOUND, inx)
        End If
        vParameters(PARMNAME, inx) = PARMNAMESP_USERINFODELETEFLAG
        vParameters(PARMTYPE, inx) = PARMTYPESP_USERINFODELETEFLAG
        vParameters(PARMLENGTH, inx) = 1    'Size
        vParameters(PARMDIR, inx) = adInput     'Direction
        vParameters(PARMVALUE, inx) = DeletedRecords     'Value
    End If
    'Create the Data Access Layer Object
    If Not SafeCreateObject(oDALEng, OBP_DA_CONNECTION, ErrorNum) Then
        Err.Raise ERR_USERINFOSAFECREATEFAILED, _
            "IOBPUserInfoEng.Get PROC", "Unable to create " & _
            OBP_DA_CONNECTION & ". Return Code was: " & ErrorNum
        GoTo CleanExit
    End If
    'Execute the SQL to retrieve the records
    If Not IsEmptyArray(vParameters) Then
        If Not oDALEng.Execute(SecurityToken, SP_S_USERINFO, _
                  vParameters, rsUserInfo) Then
            Err.Raise ERR_USERINFODALCALLFAILED, _
                  "IOBPUserInfoEng.Get PROC", _
                  "Call to Database Failed. SPName was: " & SP_S_BILL
            GoTo CleanExit
        End If
    Else
        If Not oDALEng.Execute(SecurityToken, SP_S_USERINFO, , _
                  rsUserInfo) Then
            Err.Raise ERR_USERINFODALCALLFAILED, _
                  "IOBPUserInfoEng.Get PROC", _
                  "Call to Database Failed. SPName was: " & SP_S_BILL
            GoTo CleanExit
        End If
    End If
    Set oDALEng = Nothing
    If (Not rsUserInfo Is Nothing) Then
        'Start packaging the data into Class objects
        Set oColUserInfo = New ColUserInfo
        Set col = New Collection
        Do Until rsUserInfo.EOF
            Set oCUserInfo = New CUserInfo
            With oCUserInfo
                .ClassStorage = True
                .UserNumber = rsUserInfo.Fields(FN_USERINFOUSERNUMBER)
                .UserID = rsUserInfo.Fields(FN_USERINFOUSERID)
                .UserTypeID = rsUserInfo.Fields(FN_USERINFOUSERTYPEID)
                <>
                .SecurityToken = SecurityToken
                .IsNew = False
                .ClassStorage = False
                .Dirty = False
            End With
            col.Add oCUserInfo 
            rsUserInfo.MoveNext
            Set oCUserInfo = Nothing
        Loop
        'Place information into the Collection object  
        If Not oColUserInfo.Load(SecurityToken, col) Then
            Err.Raise ERR_USERINFOLOADFAILED, _
                  "IOBPUserInfoEng.GetUserInfo PROC", _
                  "Load failed for private collection"
            GoTo CleanExit
        End If
        'Success
        Set GetUserInfo = oColUserInfo
    End If
    <>
End Function

The GetUserInfo method is a prime example of how the Get Engine methods should be defined. These are the basic recommendations:

  • Check security, either to ensure the caller has the permission to call this method or to enforce role based security.
  • Package up the passed parameters or other required fields.
  • Call the data source (our implementation uses a Data Access Layer object) to retrieve the data.
  • Unpack the data into Classes.
  • Fill the Collection and return it to the caller.

Implementation Note

The ECC model is most effective when the business components are running in the same process space as the client. The extensive use of object properties is the main reason for this. Each call to a property requires a round-trip from the client to the object. If these round trips cross process boundaries, the overhead becomes quite significant. In a Web-based solution, this means that the objects run in the same process space as Web site or a close as possible. In this case, the Web site is best configured to run in a separate memory space. This provides fault isolation, which allows the Web server to restart the process if it should go down.

The authors of this article have had great success on multiple projects and clients using this approach. We found that the greatest strength of the ECC design pattern is the template-like approach it encourages. As leaders of projects, we were able to teach developers the pattern and then quickly review the code for exceptions to it. Since it was a pattern proven through previous refinements of the implementation, it allowed us to spend more time on reviewing client-specific requirements. Moreover, most of the code became a cut-and-paste operation; once you create the first business component (template), the other components can use it as a baseline. In general, debugging and designing of the objects became more common across all the components, allowing developers to more easily interchange responsibilities. Changes occurred many times during the construction phase of the projects, ranging from UI to database. We were able to implement many of the changes with minimal impact to the system. These are some examples of the changes that occurred:

  • Data Transport Format   The original design used ADO recordsets as the data transport format. A design change required the application to move the data transport to XML. If we had used an approach that returned the recordset back to the ASP, we would have had to touch every piece of code in the application. ECC gave the developers a layer of isolation between the data transport and the ASP code. This layer reduced the impact significantly because the number of Active Server Pages was ten-fold greater than the components.
  • Security   The original design used entity-level security, with traditional CRUD functionality. Security was implemented at the Engine and Collection level. A design change required that the design needed column level security permissions. Since ECC encourages properties that map to columns, we were quickly able to implement this requirement. To do so, we had to store the SecurityToken at the Class level (via the Engine or Collection methods) and check the user's permission when calling the appropriate property method.
  • Encryption   The original design used Store/Restore methods for persistence of the components. The client was concerned that the Byte() was in readable format when stored in the database. Since we did not use the built-in IPersist methods, we were to able add encrypt/decrypt to the appropriate methods.
    Note   You can only use IPersist in Visual Basic if the Class is public creatable; the Collection and Class methods are not.
  • Caching   The original design suffered from performance issues, specifically multiple round-trips to the database to display a page. The page in question had multiple drop-down lists, each a separate round-trip, which propagated with multiple users. To help performance, we incorporated caching in the appropriate components. We also modified the Engine to first check the cache before making a round trip to the database.

These are just a few examples that demonstrate the flexibility of the ECC design pattern.

Conclusion

The Engine-Collection-Class design pattern provides an implementation-independent application framework. The architecture consists of the Engine, which controls the creation of Classes; the Collection, which stores the Classes; and the Class, which represents a real-world entity. This objectification provides clarity in object creation as well as a consistent interface for business rule validation, data retrieval, and data manipulation. In addition, it provides a loose coupled architecture, where the client and the components are free to change without affecting one another.

Unlike a purely service-based approach, ECC models an entity's data first, and then the processes. This means that a single component can be reused for many different portions of the business logic and that the business rules will be encapsulated in one component.

References

Books

Eddon, Guy, and Henry Eddon. Programming Components with Microsoft Visual Basic 6.0, 2nd Edition. Redmond, WA: Microsoft Press, 1998.

Kirtland, Mary. Designing Component Based Applications. Redmond, WA: Microsoft Press, 1999.

Pattison, Ted. Programming Distributed Applications with COM and Microsoft Visual Basic 6.0. Redmond, WA: Microsoft Press, 1998.

Platt, David S. Understanding COM+. Redmond, WA: Microsoft Press, 1999.

Stamatakis, William. Visual Basic Design Patterns. Redmond, WA: Microsoft Press, 2000.

Articles

Handle Logons in Windows NT and Windows 2000 with Your Own Logon Session Broker

The COM+ Security Model Gets You Out of the Security Programming Business

Top Windows DNA Performance Mistakes and How to Prevent Them

Advanced Basics: Transaction Programming

  Contact Us   |   E-Mail this Page   |   MSDN Flash Newsletter
  © 2003 Microsoft Corporation. All rights reserved.     Terms of Use    Privacy Statement     Accessibility