Click here to Skip to main content
Click here to Skip to main content

Implementing A Data Model and Business Layer that Supports State Management

, 29 Aug 2007
Rate this:
Please Sign up or sign in to vote.
This article describes a technique to develop business layer logical entities that have in-built state management capabilities. It focuses on how to re-use components to make development of new business logic and data modelling layers easier.

Introduction

There are many of us out there who routinely develop applications involving a 3-tier architecture. By splitting the solution into 3 logical layers - presentation, business logic and data access, we better our development efforts and increase maintainability.

This article focuses on a design approach to the main layers - the business logic and data modelling layers, and tries to formulate a mechanism that allows re-usability and extensibility as much as possible.

While the core idea is to develop a model that supports in-built state management, first we need to become familiar with some terminology we will use in this article to get ahead.

Domain and Model Objects

With regards to an application, the domain refers to the business entities that are involved. For example, in a banking application, the domain objects include Customer, Account and Transaction. Domain objects can contain domain objects; a simple example being Organization containing Employees. Or Customers having more than one Account.

The model of an application refers simply as to the way in which domain objects are described. For example, for the domain object Customer, the model object will consist of Name, AccountNumber, Branch and Occupation.

Note that for every domain object, we should have at least one model object.

The domain objects reside in the business logic layer. The model objects act as a sort of bridge between the business logic layer and the actual data access layer. Put in a graphical form, we have the following:

Screenshot - Layers.jpg

State Management

All entities in a domain undergo some sort of state transition. Initially they have to be created, later on fetched, modified and saved and at some point in time, deleted.

Screenshot - States.jpg

This article focuses on providing a set of classes and a design approach which aims to ease state management of domain objects. By encapsulating the functionality required to create, save, load, update and delete domain objects, we arrive at a solution that allows one to focus on the business logic involved. And moreover, as the technique is re-usable, this speeds up development.

To get an idea of what is in store, consider the Customer example. And the following code snippet:

// create a new customer domain object
Customer newCustomer = new Customer("Jack", "457454876", "SimpleBank"); 
newCustomer.Save(); // persists the data to the underlying data store 
		  //(e.g.: insert a new record in the customer table)
...
newCustomer.Details.Name = "Jack Fuller"; // access the model object, 
				     // modify the domain object details
newCustomer.Save(); // updates the information in the underlying data store; 
		  // note that Save() determines what to do at each stage
newCustomer.Save(); // second call does nothing as the domain object 
		  // hasn't changed after the last save
...
newCustomer.Delete(); // deletes the customer from the underlying data store

Note how easy it is to handle creation, modification and deletion of the domain object Customer. The internal implementation takes care of state, and because of this, while the first Save() method leads to creation of the entity, the second call leads to updation. The third call to Save does nothing as no modification has been made.

Using the Code

Implementing a Sample

The set of classes that are part of the framework allow us to extend them to suit our application's purpose. Let us first see the method to adopt when extending these classes for general use. We will proceed with a simple example of managing an Employee. By following how the Employee model and domain objects are realized, it should be easy for you to start using the base classes for your own needs.

The Employee application is straightforward. We need a mechanism to maintain Employee data, and each Employee will have a Name. To realize this, we have the Employee domain object, and the EmployeeModel model object.

All the layers are implemented as class libraries. (The sample solution file is organized in this manner, and will be used as a reference for this article; you are advised to open the solution file and go through it side by side when you read this article). The main application layers are:

  • Citrus.Core.Test.Domain - The domain layer (Employee goes here)
  • Citrus.Core.Test.Model - The model layer (EmployeeModel goes here)
  • Citrus.Core.Test.Data - The data access layer
  • Citrus.Core.Test - The application's presentation layer
  • Citrus.Core - The core classes for the framework

Defining the Model

In our application, every employee will have a name. The EmployeeModel class for this will look like below:

public class EmployeeModel : ModelObject
{
    /// <summary>
    /// The name field will be monitored for change
    /// Therefore, wrap it up using the Wrap class
    /// </summary>
    private Wrap<string> _name = new Wrap<string>();
    /// <summary>
    /// The name of the employee
    /// </summary>
    public string Name
    {
        get { return _name.Value; }
        set { _name.Value = value; }
    }
    /// <summary>
    /// Create a new employee model object
    /// 
    /// The way the constructor is written follows a pattern.
    /// The model objects need have only one form of a constructor
    /// A public one that accepts the domain container object
    /// The constructor must first call the base constructor
    /// passing this container, and then assign the NotifyContainer
    /// event handler for the ValueUpdated events of all required
    /// fields that are to be monitored for a change
    /// </summary>
    /// <param name="container">The container for this model</param>
    public EmployeeModel(DomainObject container) : base(container)
    {
        _name.ValueUpdated += new Wrap<string>.ValueUpdateEventHandler(NotifyContainer);
    }
}

Let's go about this line by line. The first thing to note is that EmployeeModel extends the ModelObject class. The ModelObject class should be the base class for any data model layer entity. As mentioned earlier, every model entity will be contained in a domain entity (EmployeeModel in Employee). The containing domain entity is called the container for the model entity. The ModelObject class provides in-built functionality for notifying its container whenever it gets updated.

Every Employee has a Name, and this is implemented as a property. Since we want changes that are made to Name monitored, the property encapsulates a Wrap<> field _name. The Wrap<> class helps in monitoring value updated being performed on a contained object.

    /// <summary>
    /// The name field will be monitored for change
    /// Therefore, wrap it up using the Wrap class
    /// </summary>
    private Wrap<string> _name = new Wrap<string>();
    /// <summary>
    /// The name of the employee
    /// </summary>
    public string Name
    {
        get { return _name.Value; }
        set { _name.Value = value; }
    }

The next thing in line is the constructor for the model object. Notice how it is written. Any model layer object must follow this pattern to correctly function. The constructor accepts a DomainObject container, and first calls the ModelObject's constructor to do its necessary setup. After that, any property (example Name) that needs to be monitored for changes needs to be wired up to the ModelObject's NotifyContainer method. In our example, therefore, we have _name's ValueUpdated event hooked up.

public EmployeeModel(DomainObject container) : base(container)
{
_name.ValueUpdated += new Wrap<string>.ValueUpdateEventHandler(NotifyContainer);
} 

That's it. Our EmployeeModel object is now ready to be used by the domain object, Employee, which we define next.

Defining the Domain

The implementation of the Employee domain object is a bit more involving, and requires a few basic concepts to be understood first.

The first concept is that there are four basic operations that can be performed on domain objects:

  • Create a new domain object - Create a new employee
  • Load an existing domain object - Load an existing employee
  • Update an existing domain object - Change the name of an employee
  • Delete an existing domain object - Delete an employee

Therefore, any domain object has to support these operations, and this interface is defined by IDomainObject:

/// <summary>
/// The IDomainObject contract to be supported by
/// all DomainObject classes
/// </summary>
public interface IDomainObject
{
    /// <summary>
    /// Creates the entity in the underlying data store
    /// </summary>
    void CreateEntity();
    /// <summary>
    /// Loads the existing entity from the underlying data store
    /// </summary>
    void LoadEntity(ModelObject source);
    /// <summary>
    /// Updates the current entity to the underlying data store
    /// </summary>
    void UpdateEntity();
    /// <summary>
    /// Deletes the existing entity from the underlying data store
    /// </summary>
    void DeleteEntity();
}

Any domain object that is defined must implement the IDomainObject interface explicitly. Implementing it explicitly is important as we do not want these methods to be accessible directly by clients.

The next concept, one that has been already mentioned earlier, is that every domain object will have a model object. Therefore, the Employee domain object will contain the EmployeeModel model object.

The last concept is understanding the way in which new domain objects are constructed. In code, when you create a new domain object instance, it could either be for a new domain object (one that is not already present in the data store), or it could be to represent a domain object that is already present in the data store. There are several mechanisms to implement domain objects considering these two scenarios, and this article will describe one possibility.

Now with all concepts in our head, let us see the Employee class in its entirety.

/// <summary>
/// The Employee class resides in the business logic layer
/// </summary>
public class Employee : DomainObject, IDomainObject
{
    /// <summary>
    /// The contained model object that describes the 
    /// employee entity
    /// </summary>
    private EmployeeModel _details = null;
    /// <summary>
    /// The details of the employee
    /// </summary>
    public EmployeeModel Details
    {
        get { return _details; }
        set { _details = value; }
    }
    /// <summary>
    /// Creates a new employee
    /// 
    /// The new entity constructor follows a pattern:
    /// It requires nothing as input parameters, and
    /// calls the base class constructor indicating a new
    /// object creation.
    /// </summary>
    public Employee() : base(DomainObjectState.New)
    {
        // The following line is required to set up
        // the contained model object properly
        _details = new EmployeeModel(this); 
    }
    /// <summary>
    /// Loads an existing employee
    /// 
    /// The new entity constructor follows a pattern:
    /// It requires nothing as input parameters, and
    /// calls the base class constructor indicating a clean
    /// object is being created.
    /// </summary>
    public Employee(string name) : base(DomainObjectState.Clean)
    {
        // DataFields acts as a bridge so that ModelObjects
        // can call their Containers load object passing data to
        // them
        DataFields["Name"] = name; // In this example, name acts as the primary key
        // When the domain object's state is clean, constructing
        // a model that loads the details will force it to call
        // the LoadEntity() method
        _details = new EmployeeModel(this);
    }
    #region IDomainObject Members
    void IDomainObject.CreateEntity()
    {
        // Create a new employee
        EmployeeData.Create(Details);            
    }
    void IDomainObject.LoadEntity(ModelObject EmployeeDetails)
    {
        // Load an existing employee
        EmployeeData.Load(EmployeeDetails, DataFields);
    }
    void IDomainObject.UpdateEntity()
    {
        // Update the changes made to the employee
        EmployeeData.Update(Details);
    }
    void IDomainObject.DeleteEntity()
    {
        // Delete the employee
        EmployeeData.Delete(Details);
    }
    #endregion
}

As before, a line-by-line analysis warrants an opportunity here. The first thing to note is that Employee extends DomainObject and explicitly implements the IDomainObject interface. Next, it contains the EmployeeModel model object Details. Lastly, it has two constructors, one for creating new domain objects, another for loading existing ones.

Let us have a closer look at the constructors:

    public Employee() : base(DomainObjectState.New)
    {
        // The following line is required to set up
        // the contained model object properly
        _details = new EmployeeModel(this); 
    }

The first constructor is relatively simple. It first calls the base class constructor passing the New DomainObjectState to indicate that a new domain object is being created. Inside the constructor, the EmployeeModel field _details is initialized, setting Employee as the domain container for the model.

The second constructor is a bit more interesting. To load up an existing Employee from the data store, we need some key values (e.g.: the primary keys) that define that employee. In our simple case, it's the Name field. Therefore, we have the name as a required parameter to load the existing Employee object.

    public Employee(string name) : base(DomainObjectState.Clean)
    {
        // DataFields acts as a bridge so that ModelObjects
        // can call their Containers load object passing data to
        // them
        DataFields["Name"] = name; // In this example, name acts as the primary key
        // When the domain object's state is clean, constructing
        // a model that loads the details will force it to call
        // the LoadEntity() method
        _details = new EmployeeModel(this);
    }

The first difference between this constructor and the last one is the Clean DomainObjectState that is passed to the base constructor. This indicates that this domain object corresponds to an existing object. Now, we need a mechanism to load the data corresponding to this Employee onto the EmployeeModel object. If you look at the constructor code, you may not find any mechanism that fills out the EmployeeModel with the existing employee data. The only thing noticeable is that we create a new EmployeeModel object and leave it at that. However, internally, the ModelObject constructor that is eventually invoked figures out that the Employee domain object is in Clean state, and so calls the LoadEntity() IDomainObject method. Within the LoadEntity() method you explicitly implement in Employee you can write the necessary initialization code for the model object. The LoadEntity() method is called with a reference to the model entity that was just created. (This is required because if a domain object contains multiple model objects, there needs to be a mechanism to identify which model object called the LoadEntity() method.)

The calls go along from Employee.Employee() -> EmployeeModel.EmployeeModel() -> Employee.LoadEntity() via the IDomainObject interface.

That's it. We have now completed the domain layer and the data model layer. For the purpose of this article, the data access layer is simple enough in that it just prints to the Console what needs to be done at every stage. The data access layer will not be discussed.

Finally, some application of what we have done so far. A simple program shows how to use the classes:

    class Program
    {
        static void Main(string[] args)
        {
            Employee newEmployee = new Employee();
            Employee newEmployee2 = new Employee("Jack Filler");
            newEmployee.Details.Name = "Jack";
            newEmployee.Save(); // creates the employee (insert into data store)
            newEmployee.Details.Name = "Jack12";
            newEmployee.Save(); // updates the employee
            newEmployee.Save(); // nothing happens, as employee details up-to-date
            newEmployee = newEmployee2;
            newEmployee.Save(); // nothing happens as employee is clean
            Console.ReadKey();
        }
    }

With that, we come to the end of how to extend the base classes for developing our custom layers. As for exactly how the base classes are implemented is left as an exercise to the reader. The following are present in the base class library Citrus.Core.

  • DomainObjectState - An enumeration listing all the possible states a domain model object can have
  • IDomainObject - An interface every DomainObject must implement
  • DomainObject - The base class for every domain object that is to be abstracted in the business logic layer
  • ModelObject - The base class for every data model object that is to be abstracted
  • Wrap<> - The generic utility class that helps in monitoring updates being made to a wrapped object

Hope that gets you started.

Points of Interest

Advancing Ahead: Domain Object Containers

Domain objects can become very complex at times. There could be scenarios in which domain objects contain other domain objects. To facilitate for these scenarios, the DomainObject class includes another DomainObject called the Container object. This can be used to implement IsAChildOf, HasAParent, IsASiblingOf, etc. relationships. The following flags help in determining the behaviour when the Container object is used:

  • NotifyContainerOnStateChange - Should the object notify its container when its state changes
  • MarkContainerOnStateChange - Should the object mark its container as dirty when its own state changes
  • MarkSelfAsDirtyOnContainerChange - Should the object mark itself as dirty when the container changes
  • MarkSelfAsDirtyOnContainerStateChange - Should the object mark itself as dirty when the container's state changes

Describing these scenarios is out of the scope of this article, but is relatively easy to implement if required.

Wrap Me Up!

The Wrap<> class that is described here came out of an earlier experiment of mine to develop a generic data access layer revolving around the concepts of attribute based programming. The Wrap<> class is extremely useful in various scenarios ranging from implementing a CommandHistory classes, internal triggers, monitors, etc.

History

  • 29 AUG 2007
    • [+] Initial release

Credits

The ideas described here are not entirely my own. I have seen many colleagues implement all sorts of techniques for business, data model and data access layers, most of them which revolve around similar concepts described here. I just went ahead, collected bits and pieces of ideas and mashed them up together. There is still more to be done, and better approaches may exist; I hope that what I have done becomes useful to you in one way or another. If you have suggestions to improve upon and extend these classes, do let me know.

Enjoy!

History

  • 29th August, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Benzi K. Ahamed
Web Developer
United Kingdom United Kingdom
I work as a Technology Lead for an IT services company based in India.
 
Passions include programming methodologies, compiler theory, cartooning and calligraphy.

Comments and Discussions

 
Rantawesome Pinmembermiller999925-Dec-08 17:14 
GeneralRe: awesome PinmemberBenzi K. Ahamed27-Dec-08 18:11 
GeneralNice... PinmemberChamadness4-Sep-07 15:17 
GeneralRe: Nice... PinmemberBenzi K. Ahamed5-Sep-07 10:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 29 Aug 2007
Article Copyright 2007 by Benzi K. Ahamed
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid