The conception, birth, and first steps of an application named Charlie

Subscribe: Atom or RSS

The Entity System

by Alister Jones (SomeNewKid)

In the last couple of weblog entries, I explained why the first class to be created for Charlie was a low-level business object named an Entity. If Charlie will sometimes work with a single entity, it will at other times need to work with a collection of entities. For that reason, the second class that I created was an EntityCollection class, which inherited from CollectionBase.

namespace Charlie.Framework.Business
{
    // **********************************************************
    //  EntityCollection Class
    //
    /// <summary>
    /// Represents a collection of Entity objects.
    /// </summary>
    //
    // **********************************************************
    public abstract class EntityCollection : CollectionBase
    {
    }
}

In the context of the architecture, here is the location at which these classes reside:

The definition of an Entity is that it is a “persistable business object.” Persistence means that the business object saves some or all of its data in a data store, so that the object can be loaded again in the future. I have mentioned that Expert C# Business Objects recommends putting the persistence code inside the business objects. This does not seem to be a widely-used practice, as it violates the separation of concerns that a layered architecture is meant to promote. I chose instead to put the persistence code in, you guessed it, the Persistence layer. I elected to call this class an EntityMapper, since that describes precisely what the class does. It takes an Entity object, and then maps its properties to and from the underlying data store (which will usually be a database, but not always).

Now came an important decision. How will another class “get” a business object that is based on this entity system? There are two main ways.

First, the calling class can talk directly to the entity, telling it to load itself and to save itself:

public void UpdatePerson()
{
    Int32 id = 10;
    Person person = Person.LoadById(id);
    person.LastName = "NewlyMarried";
    person.Save();
}

Second, the calling class can talk to an intermediate class that will control access to these entities.

public void UpdatePerson()
{
    Int32 id = 10;
    Person person = PersonManager.LoadPersonById(id);
    person.LastName = "NewlyMarried";
    PersonManager.SavePerson(person);
}

The first option looks simpler, but would lead to the entities taking on the responsibility of knowing how to load and save its own data. An Entity would not do the loading and saving (that has been moved to the EntityMapper), but it still knows how the loading and saving happens (it must know the methods to call in that EntityMapper). Moreover, what happens when we introduce caching to these entities? We don’t want to hit the database every time a commonly-used entity is needed, so caching must be given a high priority. If we take the first approach, an Entity must know not only about the database provider (the EntityMapper), it must also know about how it may be cached. Giving the business objects all of these extra responsibilities makes them very “heavy,” which was precisely what I wanted to avoid. So, I decided to go with the second option, by introducing an EntityManager class that would control access to an Entity or an EntityCollection.

This design has moved the responsibility for persisting entities to the EntityMapper class, and moved the responsibility for controlling access to entities to the EntityManager class. This leaves the Entity free to concentrate on its own responsibility, which is to represent some thing, be it a user, an article, a car, or a star. So far, so good.

The final decision is how the EntityManager class communicates with the EntityMapper class. How does the EntityManager tell the EntityMapper whether to load an Entity based on an ID value, or to load an Entity based on a Name value, or to load an EntityCollection based on some other criteria? Once again, we have two main choices.

The first option is the direct option. The EntityManager can pass an integer, or a string, or a some other basic type to the EntityMapper. Here is an example:

public class EntityManager
{
    public Entity LoadEntityById(Int32 id)
    {
        EntityMapper mapper = new EntityMapper();
        Entity entity = mapper.LoadEntityById(id);
        return entity;
    }
}

public class EntityMapper
{
    public Entity LoadEntityById(Int32 id)
    {
        Entity entity;
        // code to load entity by ID
        return entity;
    }

    public Entity LoadEntityByName(String name)
    {
        Entity entity;
        // code to load entity by Name
        return entity;
    }
}

The alternative is to introduce a formal Criteria class. Rather than passing low-level integers and strings around, we instead pass a high-level Criteria object around.

public class EntityCriteria
{
    public Int32 Id;
    public String Name;
    public Boolean LoadById;
    public Boolean LoadByName;
}

public class EntityManager
{
    public Entity LoadEntityById(Int32 id)
    {
        EntityCriteria criteria = new EntityCriteria();
        criteria.Id = id;
        criteria.LoadById = true;
        EntityMapper mapper = new EntityMapper();
        Entity entity = mapper.LoadEntity(criteria);
        return entity;
    }
}

public class EntityMapper
{
    public Entity LoadEntity(EntityCriteria criteria)
    {
        Entity entity;
        if (criteria.LoadById)
        {
            Int32 id = criteria.Id;
            // code to load entity by ID
        }
        else
        {
            String name = criteria.Name;
            // code to load entity by Name
        }
        return entity;
    }
}

If you looked at the code and did not merely skim passed it, you will have noticed that while the second option introduces a third class to the mix, it simplifies the EntityMapper class. The EntityMapper previously had two separate methods, one to load an entity by its ID value, and another to load an entity by its Name value. Now, the EntityMapper has a single method, which accepts a high-level EntityCriteria object, and decides what to do based on that criteria. Because the introduction of an EntityCriteria class simplified the EntityMapper class, I decided that this is the approach I would take with Charlie:

The idea of having a high-level Criteria class came from Expert C# Business Objects. In that book, however, the criteria is buried inside the business objects themselves. But this means a business object is responsible for representing the user or article or car or star or whatever else, and responsible for maintaining criteria, and responsible for the data access code needed to load and save itself. You will see why I earlier descibed these business objects as being particularly “heavy.”

I also noticed a weblog entry by David Hayden wherein he shows how Community Server takes a similar approach. If this approach is good enough for the clever pumpkins at Telligent, then it’s good enough for this dumb potato. A superficial difference is that Community Server uses the term Query rather than the term Criteria. Personally, I don’t like the term Query, since it smacks of being database-specific. Because Charlie’s persistence logic is up in the Persistence class, it can retrieve data from not just databases but also from flat files and from web services. Hence, I prefer the term Criteria, since it does not presume the use of a database.

That is the process by which I arrived at a five-part Entity System for Charlie.

by Alister Jones | Next up: The Web System

0 comments

----