Reusable Domain Models. Interfaces and delegation.

XAF Team Blog
10 October 2006

This is the second post on this topic. We are trying to find a way to create reusable domain model libraries. We did not choose any particular route, I'm just thinking out loud. So here is one way of solving the problem - using interfaces and delegation for implementation.

Let me describe all the problems related to reusable domain models with one simple example:

In my project (code name Saturn), I need to implement tasks system. Tasks can be assigned to engineers and be related to some projects. And all domain classes in my project must be inherited from SaturnBase class. As a developer I don't really want to dig into task-management systems as well as person-related aspects of Engineer. At the same time I want to be able to change the default task behavior when I need to.

I would like to have a library of objects that implements all or most of the task and person management functionality for me.

So, to summarize the requirements:

  1. Task and Person implementations should be inherited from SaturnBase.
  2. Task should have associations with the Engineer and Project classes.
  3. Reusing Task and Person should add the necessary business logic to my classes.
  4. Task management code should be able to work with my task implementation.
  5. Everything should be explicit. If I use Task in my application I should have full control over it.

Now I'm wearing a library developer hat. How can I satisfy these requirements? The easiest one is #4. I have to write some code that deals with tasks and persons and in the same time I cannot work with certain classes. So I should use the ITask and IPerson interfaces:

    public interface IPerson {
        string FirstName { get; set; }
        string LastName { get; set; }
        DateTime BirthDate { get; set; }
        string DisplayName { get; }
    }

    public interface ITask {
        void MarkCompleted();
        string Subject { get; set; }
        string Description { get; set; }
        DateTime DueDate { get; set; }
        DateTime StartDate { get; set; }
        DateTime DateCompleted { get; }
        TaskStatus Status { get; set; }
        Int32 PercentCompleted { get; set; }
    }

With these interfaces I will be able to edit and show Task and Person objects, but I cannot retrieve them or create new. At some point I should know certain implementation class name. This information can be provided to my module instance in the configuration - I see no problem with that.

To satisfy requirement #3 I could inherit Task from some TaskBase that has the necessary logic implemented, but this will break requirement #1. So if I cannot use the inheritence I will try aggregation. I can implement ITask in some helper class and delegate all ITask calls to it:

    public class Task : SaturnBase, ITask {
        public Task(Session session) : base(session) { }

        #region ITask library implementation
        private ITask taskImpl = new TaskImpl();
        public void MarkCompleted() {
            taskImpl.MarkCompleted();
        }
        public string Subject {
            get { return taskImpl.Subject; }
            set { taskImpl.Subject = value; }
        }
        public string Description {
            get { return taskImpl.Description; }
            set { taskImpl.Description = value; }
        }
        public DateTime DueDate {
            get { return taskImpl.DueDate; }
            set { taskImpl.DueDate = value; }
        }
        public DateTime StartDate {
            get { return taskImpl.StartDate; }
            set { taskImpl.StartDate = value; }
        }
        public DateTime DateCompleted {
            get { return taskImpl.DateCompleted; }
        }
        public TaskStatus Status {
            get { return taskImpl.Status; }
            set { taskImpl.Status = value; }
        }
        public int PercentCompleted {
            get { return taskImpl.PercentCompleted; }
            set { taskImpl.PercentCompleted = value; }
        }

        #endregion
    }

The same with IPerson:

    public class Engineer : SaturnBase, IPerson {
        public Engineer(Session session) : base(session) { }

        #region IPerson library implementation
        private IPerson personImpl = new PersonImpl();
        public string FirstName {
            get { return personImpl.FirstName; }
            set { personImpl.FirstName = value; }
        }
        public string LastName {
            get { return personImpl.LastName; }
            set { personImpl.LastName = value; }
        }
        public DateTime BirthDate {
            get { return personImpl.BirthDate; }
            set { personImpl.BirthDate = value; }
        }
        public string DisplayName {
            get { return personImpl.DisplayName; }
        }
        #endregion
     }

This code is a dumb code, so ideally it should be generated automatically. With DXCore and partial classes it is possible to hide it from the active development reducing the cost of maintainence.

Now requirement #2. Task should have an association with Project and Engineer. The easiest solution is to leave this out of the library implementation:

    public class Task : SaturnBase, ITask {
        public Task(Session session) : base(session) { }

        #region ITask library implementation
	...
        #endregion
        [Association("Task-Engineer")] 
        public Engineer AssignedTo {
            get { return GetCollection("Engineer"); }
        }
        [Association("Task-Project")] 
        public Project AssignedTo {
            get { return GetCollection("Project"); }
        }
    }

But many domain models consist of more than one classes. Employment models describe the relationship between organization, organization units, person. Let's assume that our Task model library provides some functionality for other end of task association - I will call it TaskTarget:

    public interface ITask {
	...
        ITaskTarget AssignedTo { get; set; }
        ...
    }
    public interface ITaskTarget {
        IList Tasks { get; }
        bool HaveTasks(); 
    }

Now I see a serious problem with my library ITaskTarget implementation. It is in the nature of associations in persistent object models in ORM systems. In our example the collection of tasks is not something that can be stored in the TaskTargetImpl helper class - in fact this should be a result of sql query that selects all records in the Tasks table (where the actual class is mapped) where the Project foreign key is equal to the current Project key field. In short, it means for me that I cannot have the Tasks collection getter implemented in some helper class - only final persistent objects are able to build the collection for their association.

So - the developer will have to specify the necessary associations in his final classes and let the helper implementation know about it:

    public class TaskTargetImpl : ITaskTarget {
        ITaskTarget owner;
        public TaskTargetImpl(ITaskTarget owner) {
            this.owner = owner;
        }
        public IList Tasks {
            get { return owner.Tasks; }
        }

        public bool HaveTasks() {
            return Tasks.Count > 0;
        }
    }


    public class Engineer : SaturnBase, IPerson, ITaskTarget {
        public Engineer(Session session) : base(session) {
            taskTargetImpl = new TaskTargetImpl(this);
        }
        #region IPerson library implementation
	...
        #region ITaskTarget library implementation
        private TaskTargetImpl taskTargetImpl;
        [NonPersistent]
        IList ITaskTarget.Tasks {
            get { return new ListConverter(this.Tasks); }
        }
        [Association("Task-Engineer")]
        XPCollection Tasks {
            get { return GetCollection("Tasks"); }
        }
        public bool HaveTasks() {
            return taskTargetImpl.HaveTasks();
        }
        #endregion
    }

ListConverter here is a class that can make List from List (where Task is ITask) - the compiler cannot do this automatically. Note that TaskTargetImpl uses a real Tasks collection, not his own, so the owner class can change the default implementation but HaveTasks will still work correctly.

The described approach satisfies all listed requirements, but it does not look easy to me. I'm thinking about some DXCore plugin that can make addding these plumbing structures easier and that checks that all parts of puzzle are in place.

Also one thing that is left out in my samples is attributes. Real Task implementation needs additional attributes to tell XPO field lengths or that some properties are non-persistent or calculated. Since in my library there are no real classes, then the only place where I can write these attributes is the interface definition itself. Something like this:

    public interface ITask {
        void MarkCompleted();
        string Subject { get; set; }
        [Size(SizeAttribute.Unlimited)]
        string Description { get; set; }
        DateTime DueDate { get; set; }
        DateTime StartDate { get; set; }
        [Persistent]
        DateTime DateCompleted { get; }
        [Association("ITask-ITaskTarget")]
        ITaskTarget AssignedTo { get; set; }
        TaskStatus Status { get; set; }
        Int32 PercentCompleted { get; set; }
    }

Implementation generator can copy these attributes to its implementation methods. Or I can ask XPO developer to tweak it to understand attributes for the interfaces that persistent classes implement. Note that the Association attribute cannot be just copied as is, its name should be replaced with a real persistent classes combination.

Another thing I want to mention is that the approach described here is not really polymorphic. In other words, while several classes can implement ITask, there is no way here to work with them at the same time. For example I cannot easily get a collection of objects from the database that implements ITask as one list. To do so, XPO should be modified to be fully interface-aware and be able to generate sql union queries or create some denormalized tables for interfaces. But I think that in these rare cases when the developer needs different ITask implementations in the same list - he will be able to use the inheritence and implement ITask in the base class.

All this analysis hasn't led to a clear conclusion so far. We are still considering the best way to go and we want to invite everybody to a discussion. If you have other ideas, please feel free to let us know about them.

Tags
2 comment(s)
Anonymous
Dan Vanderboom
Hi Roman,

I have some questions about your posting on Reusable Domain Models.  I’m hoping you can clarify a few statements that you made, so that I can fully understand where you’re going.

  "As a developer I don’t really want to dig into task-management systems as well as person-related aspects of Engineer."

What do you mean by digging into task-management systems?  Why wouldn’t you want to be able to access the Person properties of an Engineer if they do in fact inherit from Person?  If I’m creating a report of Engineers and the work they’ve done, I would definitely want to access Person properties like FirstName and LastName.

  "At the same time I want to be able to change the default task behavior when I need to."

Where does the default task behavior come from?  Are you talking about validation/business logic in the Task data class?  How do you want to change Task behavior?  By inheriting from Task to create a new class with the ability to add validation logic in the base class?

It sounds like your goal, and correct me if I’m wrong, is to have a bunch of data classes in a catalog or library, where the classes aren’t related to each other, but whose relationships are defined by the application using them.  In other words, you would have a Task class with all the details of a task, but without the links to Engineers, Projects, Phases, or anything else.  The new application would then glue them all together.

The use of interfaces and private fields holding references to PersonImpl objects, etc., looks confusing in code and like a lot of work.  (I can follow it, but there’s a lot of indirection there for what you’re trying to accomplish.)  If all you want to do is pull in data classes into a new application, and you don’t have to worry about other applications using schemas that overlap with your application (and sharing the same database), it might just be easier to have a DXCore plugin that uses reflection to reverse engineer the data classes in your library.  In other words, if I have Person and Task objects in my Domain Model Library, and I select them, a plugin could read those classes in the assembly they’re defined in, and regenerate the source code for them (like Lutz Roeder’s Reflector does) and spit them out into your application’s data class assembly.  Then you can make those classes inherit directly from the base class you want to, and all of the business logic will be “templated” in for you to extend or change as you wish.  Then you avoid all of the indirectness of having to define the interfaces, implement them, reference an inner implementation object, etc.

Now it’s one thing to supply a library of commonly-used data classes, but the really important data model design aren’t in deciding which basic properties to include, but in determining how those classes are related to each other.  A good data modeler will think in terms of patterns of related classes more than single isolated entities, in the same way that we use GoF patterns to decide how to design and arrange the non-persistent classes in our applications.  Having Person, Address, and Organization data classes predefined for me to easily use is no big deal, but having a pattern in which they are all related is truly useful.  A set of related classes, a subsystem for a data model, is a big enough piece to be really useful.  

Maybe the ultimate answer is that you need both.  If you have a library of separate, isolated data classes, and a second-level library of patterns of data classes with predefined relationships, you then have a great deal of flexibility and power in building a robust data model quickly.  If I’m building a Customer Management System, I can imagine browsing a tool for patterns, finding a Contact Management pattern, and extending it to suit my needs.  I could take that pattern, load or create a new Customer class, modify that Customer class so it inherits from Contact, add validation logic, and be up and running quickly.

Storing data classes in compiled assemblies might not be the best route, either.  In Compact Framework, which is my primary development target, having lots of assemblies is a bad idea due to the memory overhead (they load in 64KB chunks, even if they’re typically much smaller).  I mentioned being able to disassemble them to include their code in your new project, but why not store them somewhere else, something like the template system that CodeRush uses?  This would require the following to make work:

1. A discovery-navigation tool that developers could invoke to explore the library of data classes and data patterns.
2. The ability to add a new data class to the current project, via a template-like mechanism, from the developer’s selection in the discovery-navigation tool.  This should create a new project code file for each data class added.
3. An ability to define new data classes and patterns, and to include them into the discovery-navigation tool.

In the category of “nice to have” but not immediately required:

4. Some verification would be nice, to make sure you don’t add data classes and patterns that won’t compile.
5. Also nice would be the ability to publish types to a community site, to share with other XPO customers, and to download what others have published for use in your own projects.

Keep up the good work in this important area!
10 January, 2007
Anonymous
eXpress App Framework Team

In my first post about reusable domain models , I was looking for an elegant, intuitive and code-centric

4 March, 2008

Please login or register to post comments.