FREE hit counter and Internet traffic statistics from freestats.com

Friday, November 19, 2004

Organizing Domain Logic: Domain Model

The third pattern for representing domain logic in an application is the Domain Model. In this model the application is viewed as a set of interrelated objects. The core feature of these objects (referred to as Business Entities in Microsoft nomenclature) is that, unlike the Table Module approach, each object maps to an entity (not necessarily a database table) in the database. In other words, each primary object represents a single record in the database rather than a set of records and the object couples the object’s data with its behavior (business logic and data validation). Additional objects may represent calculations or other behavior. Ultimately, this approach requires at least the following:

  • Object-relational Mapping. Since this pattern does not rely on the DataSet object or even Typed DataSet objects, a layer of mapping code is required. At present the Framework does not contain built-in support for this layer. Look for support in the Microsoft Business Framework (MBF) technology to be released after the Whidbey release of VS .NET. Techniques to perform this mapping are covered in my article Software Design: Using data source architectural patterns in your .NET applications.
  • ID Generation. Since each Domain Model object maps to an entity in the database, the objects need to store the database identity within the object (the CLR’s object system uniquely identifies each object based on its data). There are several techniques to do so documented by Fowler in his discussion of the Identity Field pattern.
  • Strongly-Typed Collection Classes. Because objects in the Domain Model map intrinsically to an individual entity, representing multiple objects is handled through collections. Framework developers can therefore create strongly-typed collection classes to handle representing multiple domain objects. For more information see my article Take advantage of strongly typed collection classes in .NET.

The end result of a domain model is that the application manages many individual and interrelated objects (using aggregation) during a user’s session. Although this may seem wasteful, the CLR’s efficiency at managing objects makes the Domain Model a valid approach. Such an approach would not have been efficient in the world of COM, however. The Domain Model can also take advantage of inheritance by implementing a Layer Supertype.

To build an object used in a Domain Model a best practice is to follow the Layer SuperType pattern. Using this pattern an architect would develop a base class from which all domain objects would inherit as shown here:

<Serializable()> _

Public MustInherit Class BusinessObjectBase : Implements IComparable
Protected _id As Integer ' could also use GUID or a key class
<xml.serialization.xmlattribute()> _
Public Property Id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
_id = value
End Set
End Property
MustOverride Function Save() As Boolean
MustOverride Function Delete() As Boolean
Public Shadows Function Equals(ByVal o As BusinessObjectBase) As Boolean
If Me.Id = o.Id Then
Return True
Else
Return False
End If
End Function
Public Shared Shadows Function Equals(ByVal o As BusinessObjectBase, _
ByVal o1 As BusinessObjectBase) As Boolean
If o.Id = o1.Id Then
Return True
Else
Return False
End If
End Function
Protected IsDirty As Boolean = False
' Used when the Sort method of the collection is called
Public Function CompareTo(ByVal o As Object) As Integer _
Implements IComparable.CompareTo
Dim b As BusinessObjectBase = CType(o, BusinessObjectBase)
Return Me.Id.CompareTo(b.Id)
End Function
End Class

You’ll notice that this abstract class contains the implementation of the Id property as well as the abstract Save and Delete methods and a protected field to determine if the object has been altered and is in need of removal. In addition, it shadows (implemented with the new keyword in C#) both signatures of the Equals method inherited from System.Object that is used to determine whether two instances of the object are equal. Note that these methods check the Id property but could alternately have been implemented to check against some other criterion or more likely overridden in the derived class and checked against other properties of the object. This class also uses the SerializableAttribute and XmlAttribute classes so that the object can be serialized to XML and its Id property represented as an attribute.

A derived Domain Model object, implemented by the Order class might then look as follows.

<serializable> Public Class Order : Inherits BusinessObjectBase
Private _orderId As Long
Private _cust As Customer
Private _prod As Product
Private _quant As Integer = 1 'default
Private _ship As ShipType = ShipType.Postal 'default
Public Sub New()
End Sub
Public Sub New(ByVal cust As Customer, ByVal prod As Product)
_InitClass(cust, prod, Nothing, Nothing
End Sub
Public Sub New(ByVal cust As Customer, _
ByVal prod As Product, ByVal quantity As Integer)
_InitClass(cust, prod, quantity, Nothing)
End Sub
Public Sub New(ByVal cust As Customer, ByVal prod As Product, _
ByVal quantity As Integer, ByVal ship As ShipType)
_InitClass(cust, prod, quantity, ship)
End Sub
Private Sub _InitClass(ByVal cust As Customer, _
ByVal prod As Product, ByVal quantity As Integer, _
ByVal ship As ShipType)
_cust = cust
_prod = prod
Me.Quantity = quantity
Me.ShipVia = ship
Me.IsDirty = True
' Generate a new or temporary order id: use a system assigned key
' _orderId = key table GUID
End Sub
Public ReadOnly Property Customer() As Customer
Get
Return _cust
End Get
End Property

Public ReadOnly Property Product() As Product
Get
Return _prod
End Get
End Property

Public Property Quantity() As Integer
Get
Return _quant
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New ArgumentOutOfRangeException( _
"Quantity must be greater than 0")
End If
_quant = Value
Me.IsDirty = True
End Set
End Property

Public Property ShipVia() As ShipType
Get
Return _ship
End Get
Set(ByVal Value As ShipType)
_ship = Value
Me.IsDirty = True
End Set
End Property

Public Function CalcShippingCost() As Double
' calculate the shipping cost based on the Customer and Product objects
' store the shipping cost for this order
End Function

Public Function CalcOrderTotal() As Double
' calculate the total cost of the order with tax
' store the cost for this order
End Function

Public Function IsComplete() As Boolean
' Determines whether this order has enough information to save
End Function

Public Overrides Function Save() As Boolean
' Persist the order
Me.IsDirty = False
End Function

Public Overrides Function Delete() As Boolean
' Remove the order
End Function
End Class

The key point to note about this class is that it combines data (the Quantity and ShipVia properties), behavior (the IsComplete, Save, CalcOrderTotal and other methods), and the relationships to other data (the Product and Customer properties that relate to the Product and Customer classes). The end result is a static structure diagram:

The second key point to note about this structure is that each object derived from BusinessObjectBase creates its own Id property in the object’s constructor and the Id property is represented as a Long (64-bit) integer. The strategy shown here is only one among several documented by Fowler as the Identity Field pattern. The considerations for Id generation are as follows:

  • Use of system assigned keys. Here the assumption is made that the key value will be assigned by the system and not use a “natural key”. In practice this proves to be the more durable design since the independence of the keys allows them to remain fixed once assigned. This also disallows the use of compound keys (which if used should be implemented in a separate key class) which are difficult to manage and slow performance of the database.
  • Use of data types. Here a 32-bit integer is used, however, unless uniqueness can be guaranteed in the generation of the ids it might be possible to duplicate keys which would cause problems once the object is persisted to the database. An alternative would include the use of System.GUID, which is (for all intents and purposes) guaranteed to be unique. If you wish to protect yourself against data type changes in the key you could also implement the key in a key class that abstracts the data within the key and performs any comparisons and generation.
  • Generation of the ids. If a data type such as an integer is used, one technique for generating unique ids is to generate them from a key table. The downside of this technique is that it incurs a round trip to the database server.
  • Uniqueness of the ids. In the code shown here it is assumed that each object generates its own key probably through the use of a key table. This then assumes that each object type (Order, Customer, Product) generates keys independently of the others. An alternate approach would be to create system wide unique ids through a key table, in which case the generation of the id could be placed in the base class. If the classes form an inheritance relationship, for example if the BookProduct class inherited from the Product class it would be assumed that the generation of the ids would take place in the Product class so that all of the objects generated by the inheritance hierarchy would be uniquely identified so that they could be stored in the same database table.

To be able to manipulate a collection of Domain objects they can be represented in a collection class. While the Framework includes collection classes in the System.Collections namespace including ArrayList, SortedList, and Dictionary among others, there are advantages to creating a custom strongly-typed collection class including:

  • Strong-typing. As the name implies, a custom collection can be strongly-typed, meaning that only objects of a specific type are allowed to be added to the collection.
  • Custom Manipulation. A strongly-typed collection class also provides the opportunity to add custom behaviors to the class, for example, by providing custom sorting capabilities.

To create the strongly-typed collection you can use the power of implementation inheritance. By deriving a class from CollectionBase, ReadOnlyCollectionBase, or DictionaryBase and then overriding and shadowing various members you not only can enforce the type of objects in the collection but take advantage of functionality that Microsoft has already included in the .NET Framework. For example, Microsoft uses the CollectionBase class as the base class for over 30 of its own strongly-typed collection classes in the .NET Framework. Each of these three classes is marked as abstract (MustInherit in VB) and therefore can only be used in an inheritance relationship.

No comments: