Active Domain Object

Summary

Purpose

Encapsulates data model and data access details within a relevant domain object. In other words, an Active Domain Object abstracts the semantics of the underlying data store (i.e., SQL Server) and the underlying data access technology (i.e., ADO.NET) and provides a simple programmatic interface for retrieving and operating on data.

Scenario

Assume that data was obtained using a data accessor object. Applications that directly manipulate this data lend themselves to several problems:

The Active Domain Object (or the Data Access Logic Component) addresses these issues by encapsulating a data model and code to manipulate that data model in a set of domain objects. The application then uses the Active Domain Object to access and manipulate data.

The term active refers to domain objects that do more than simply represent data. They expose logical operations to take care of relevant database interactions. Some of these logical operations (which are named using domain terminology and not data access terminology) include:

 

Structure & UML

An ActiveDomainObject defines logical attributes and operations that represent domain concepts. It implements its operations by interacting with a Data Accessor object to access the database. It also handles converting data between its database form and its application form.

An application instantiates an ActiveDomainObject, which initializes itself with data from the database. Once an ActiveDomainObject is initialized, the application can access its properties. The application can then ask the ActiveDomainObject to save its state to the database.

Example

This example define an active domain object that represents a customer:

public class Customer
{
    /* Data members */
    private string               m_strConnection = "";        // Used by the data accessor object to access database
    private System.Data.DataRow  drCustomer      = null;
    private Address              obAddress       = null;

    /* Constructors */
    public Customer()
    {
        // Acquire the connection string from a connection-string factory-object
        m_strConnection = ConnectionStringFactory.GetConnectionString();
    }

    /* Data access methods */
    public System.Data.DataRow GetCustomer( int nID )
    {
        // Use data access helper components to retrieve customer data via a stored procedure
        drCustomer = ...
        
        // Construct an Address object from customer data
       
obAddress = ...
 
        // Return customer information
        return drCustomer;
    }

    public int CreateCustomer( string strName, Address obAddress, string DateTime dtDOB )
    {
        // Use data access helper components to create a new customer entry in the database
        // and return customer ID
    }

    public void DeleteCustomer( int nCustomerID )
    {
        // Use data access helper components to delete customer from the database
    }

    public void UpdateCustomer( DataRow drCustomer )
    {
        // Use data access helper components to update customer in the database
    }

    /* Properties */
    public string     Name         { get { return (string)drCustomer["Name"]; }
    public DateTime   MemberSince  { get { return (DateTime)drCustomer["RegistrationDate"]; }
    public bool       IsActive     { get { return (bool)drCustomer["Active"]; } }
    public Address    Address      { get { return obAddress; } }
}

public class Address
{
    /* Data members */
    private string         strAddress1 = "";
    private string         strAddress2 = "";
    private string         strCity     = "";
    private string         strPostCode = "";
    private string         strCountry  = "";

    /* Constructors */
    public Address (string add1, string add2, string city, string pc, string cntry )
    {
        // Populate data members
    }

    /* Properties */
    public string Address1 { get {return strAddress1; } }
    public string Address1 { get {return strAddress2; } }
    public string City     { get {return strCity; } }
    public string PostCode { get {return strPostCode; } }
    public string Country  { get {return strCountry; } }
}

public class ConnectionStringFactory
{
    public static string GetConnectionString()
    {
        // Retrieve connection string from somewhere - config file, etc.
    }
}

// This Application-code block illustrates how to create and initialize a Customer object
Customer obCustomer = new Customer();
Address obAddress = new Address( ... );
obCustomer.CreateCustomer( "Yazan", obAddress, dtDON ); 

Note how Customer collates address data from drCustomer to form an Address object. Address can be considered as a domain object, but is not active in the sense that it is really part of Customer objects; it is Customer objects that handle the mapping of properties found in Address objects. This also allows the Address object to be reusable by other parts of the application. Also note that ConnectionStringFactory encapsulates connection string management for Customer and other active domain objects.

The following complete example uses CollectionBase to create a specialized collection that only accepts objects of type Customer instead of accepting and exposing contained objects as Object.

// CustomerCollection class that only contain objects of type Employee
public class CustomerCollection : System.Collections.CollectionBase
{
    /* Constructors */
    public CustomerCollection() {}

    /* IList interface implementation */

    // The Add method declaration permits only Customer objects to be added
    public void Add( Customer obCust )
    {
        this.List.Add( obCust );
    }

    // Implement a Remove method
    public void Remove( int nIndex )
    {
        // Check if index is valid
        if ((nIndex < 0) || (nIndex > this.Count - 1))
            throw new ArgumentOutOfRangeException( "nIndex", "Index is not valid");

        this.List.RemoveAt( nIndex );
    }

    // Implement an indexer that returns only Employee objects (and not object of type Object)
    public Customer this[int nIndex]
    {
        get { return (Customer)this.List[nIndex]; }
        set { List[nIndex] = value; }
    }
}

private void CollectionBase_Click(object sender, System.EventArgs e)
{
    // Create some employee obejcts
    Customer obCust1 = new Customer();
    obCust1.CreateCustomer( ... );

    Customer obCust2 = new Customer();
    obCust2.CreateCustomer( ... );

    // Create our specialized collection
    CustomerCollection coll = new CustomerCollection();

    coll.Add( obCust1 );
    coll.Add( obCust2 );
}

Applicability

Use this pattern when:

Strategies / Variants

The main motivation for defining active domain objects is to make application code easier to write and maintain, therefore, it makes sense to tailor domain objects to application concepts.Consider these strategies when designing an active domain object. 

Benefits

Liabilities

Related Patterns