Reusable Domain Models strikes back

XAF Team Blog
04 March 2008

EDIT:
Domain Components (DC) is in maintenance mode and we do not recommend its use in new software projects.
 

In my first post about reusable domain models, I was looking for an elegant, intuitive and code-centric way in which to create domain models from existing parts. Since then we've done additional research. And now, I’m ready to share our results. I will describe the approach we're going to implement. Please note that some details might be changed. So, you cannot rely on this article as on a specification.

The main conclusion drawn from our research is that there is no elegant way to construct a domain model from existing parts. By an elegant and code-centric way, I mean the ability to look at domain class code and understand what’s going on. The approach we have chosen is that you provide logic and interfaces, and the framework builds an implementation for you.

Let’s consider one small and well-know domain model – Address. Address usually contains Country, City, State/Province, postcode, street address (or line1, line2). There can be one or several addresses. My requirements include:

  1. I want to be able to apply this model to any domain objects I want (Customer, Company, Contact, Location, Airport, Gas Station – anything).
  2. I want the business logic related to this model to be maintained by its developer. For bug fixes, he would give me only an updated assembly.
  3. I don’t want this model to depend on any ORM specifics.
  4. I don’t want to write lots of code to use this model.

With interfaces, I can make my domain model independent from my implementation. Here are simplistic Address interface definitions (please note that this code is just an illustration):

    public interface IAddress {
        IAddressable Owner { get; set; }
        string City { get; set; }
        string Line1 { get; set; }
        bool IsPrimary { get; set; }
    }

    public interface IAddressable {
        IAddress PrimaryAddress { get; }
        IList Addresses { get; } 
    }

Together with these interfaces, I should supply business logic. Since interfaces cannot contain method definitions, the only thing I can use is some kind of extension methods in the service class:

    [Service(typeof(IAddresable))]
    public static class AddressableService:EntityService {
        public IAddress Get_PrimaryAddress(IAddressable self) {
            if (self.Addresses.Count > 0) {
                return self.Addresses.Where(address => address.IsPrimary );
            } else {
                IAddress addr = SomeObjectFactory.CreateObject();
                addr.Owner = self;
                addr.IsPrimary = true;
                self.Addresses.Add(addr);
                return addr;
            }
        }
    }
    [Service(typeof(IAddress))]
    public class AddressService:EntityService {
        private IAddress self;
        private bool isPrimary;
        private bool lockPrimary = false;

        public AddressService(IAddress self) {
            this.self = self;
        }

        public bool IsPrimary {
            get { return isPrimary; }
            set {
                if (lockPrimary) return;

                try {
                    lockPrimary = true;
                    if (IsPrimary) {
                        foreach (Address addr in self.Owner.Addresses) {
                            if (Address != this) {
                                addr.IsPrimary = false;
                            }
                        }
                    }
                } finally {
                    lockPrimary = false;
                }
            }
        }
    }

As a domain model developer, I don’t need to specify anything else. This code assumes that the implementation of interfaces can persist its properties, service methods are called in the corresponding implementation, and that there is SomeObjectFactory that can create an implementation with a given interface. This service class will be wired into a generated interface implementation using naming conventions and/or attributes.

Even as a developer of a final application (that uses this Address model), I still don’t want to know how to implement a persistent property in the particular ORM I’m using. Or, if I do want to – I want to write it in one place, not in every single property getter and setter. I want to provide something like this:

    public class XPOPersistPropertyImpl:PropertyImplBase {
        private OfType value;

        public PersistPropertyImpl(string name):base(name) {
            this.name = name;
        }
        public  OfType GetValue(XPObject owner) {
            return owner.GetPropertyValue(Name);
        }
        public void SetValue(XPObject owner, OfType newValue) {
            owner.SetPropertyValue(Name, newValue);
        }
    }

So, I can imagine the entire application as a superposition of several domain models (one of them is an addition for the final application itself). Assume that we have a library of domain models (IOrganization, IAddress-IAddressable, IContact, ICustomer-ISale-ISaleItem-IProduct-IAgent, IEmployee-IEmployer):

    public interface ICar : ISaleItem {
        public ICarModel Model { get; set; } //Implements ISaleItem.Product
        public string Color { get; set; }
        public DateTime MadeOn { get; set; }
    }

    public interface ICarModel : IProduct {
        string Name;
    }

    public class MyObjectSpace {
        public static MyObjectSpace() {
            RegisterEntity("Company", IOrganization, ICustomer, IAddressable);
            RegisterEntity("Contact", IContact, ICustomer, IAddressable);
            RegisterEntity("Dealership", IOrganization, IEmployer, IAddressable);
            RegisterEntity("SalesManager", IEmployee, IAgent);
            RegisterEntity("CarModel", ICarModel);
            RegisterEntity("Car", ICar);
            RegisterEntity("Sale", ISale);
            RegisterEntity("Address", IAddress);
        }
    }

Who is going to implement all these interfaces? These implementations will be generated at runtime (we will probably provide other options in the future) using domain service classes, and any additional orthogonal services (like the persistent XPO property’s setter class, mentioned earlier).

An additional benefit of the described approach is that we automatically use a “programming against interfaces” approach, which can give us benefits of aspect-oriented extensions. We can add field-level security, and audit features. The interface can be implemented by some proxy class. So, we can even create an application server with remote objects from the same code. A multi-tiered application is something many of our customers have asked for.

If you only have interfaces, it is very easy to write unit tests for service classes. There are mock frameworks that will automatically generate interface implementations.

There is a well-known metaphor of the Dependency Injection (DI) container that can be used here to handle the model creation, and later the resolution of dependencies. We are going to build our own DI container that is tailored to our specific needs, and at the same time, is pretty generic to be used in other parts of XAF, or in your own use cases (OK, frankly – just to make Rinat Abdullin stop complaining that we don’t use IoC in XAF Smile).

A big problem here is indirection. Since you don’t have direct control over the implementation of your interfaces, it might be hard to diagnose problems. We should make some extra efforts to minimize side effects and make it easier to understand what’s going on.

This is what we will work on after 2008 vol.1 release. I doubt that this technology will be implemented in 2008 vol.2. Our target is 2008 vol. 3. We are likely to release some preview bits in interim.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.