Introduction
This article focuses on Domain Driven Design, concentrating on using some principles from this area in combination with an 'attribute-flavored' approach. The contents are partially based on an article of mine that was published in the December 2007 issue of the Dutch Microsoft .NET Magazine.
Background
Domain Driven Design, often abbreviated as DDD, is a topic that can be encountered frequently these days. Some would undoubtedly refer to it as another hype, and as with any new kid on the block it is accompanied by debates as to its merits, as well as flocks of puristic believers. It is not my intention to venture down either road, also due to the fact that my interpretation of Domain Driven Design may be very different from the original intentions as written down by, most notably, Eric Evans and Jimmy Nilsson. I will list some references for further reading at the end of this article.
Some Principles
In DDD, the domain is centralized as the planet around which services revolve as if they were satellites or moons. In other words, the services are not themselves part of the business domain, but they use the domain.
The domain is explicitly intended as the business domain, or knowledge domain if you will, containing domain classes and business rules. Also, the domain is exposed by one or more repositories for retrieval purposes, and by factories for creational (lifetime) purposes.
A domain model should be replaceable by another domain model, ideally leaving the rest of your application intact. This is a principle that is not core to DDD, but one that I would like to adhere to.
The principles listed above are actually all examples of the more generic principle of 'separation of concerns'. That is not something new, but in Domain Driven Design, you could call this the mother of all principles.
The Ubiquitous Language
Given the principles above, you could imply that DDD is more of a 'duh ... that's common sense' approach. And in fact, it is. DDD does not actually add a new technology, nor does it add new language constructs, and as a methodology it is not actually new either. It combines a lot of stuff that is best practice in many minds and places (but, truthfully, not always at the front of our minds, and not always in the right place). It hammers down on the domain-centric mantra: "Business knowledge, my business, your business". That's not probably a mantra you'll find anywhere, as I just made it up, but the essence is keeping business objects, rules, vocabulary and knowledge centralized in one place and disclosable to services and applications that need them. And in doing so, making sure that every actor involved speaks this language. It must be unambiguous, as concise as possible, and fluent on every tongue. This is what Eric Evans refers to as the ubiquitous language.
Common Base, Common Ground
To enable compliance with the principles summed up above, a viable option is to have every domain model class inherit from a common base class. In the solution that accompanies this article, this is not just a choice between base class and interface, but a fundamental architectural provision that also makes it possible to replace a given model by another (the third principle above). Therefore, more than a base class, we have a complete base assembly that the domain model derives from. This 'basement assembly' contains provisions for properties and property collections, relations, and - as will be evident later on - for the custom attributes and validators that are a second foundation for a domain model such as proposed here.
The Code Base
The solution contains three projects:
Codebasement.DomainDrivenDesign.Basement
: The basement assemblyCodebasement.DomainDrivenDesign.Model
: A sample domain modelCodebasement.DomainDrivenDesign.App
: A simple test form
The basement assembly is a mini framework for domain models. It cannot have references to the model (since the model references the basement assembly), therefore it relies on reflection if necessary to obtain information from the model, and for the most part contains generic constructs that can be used in domain model classes. Now, since code speaks more than words often can, let's look at an example of a domain class that is a derivative of BasementObject
.
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using Codebasement.DomainDrivenDesign.Basement.Attributes;
using Codebasement.DomainDrivenDesign.Basement.Basement;
using Codebasement.DomainDrivenDesign.Basement.Converters;
using Codebasement.DomainDrivenDesign.Basement.Enums;
using Codebasement.DomainDrivenDesign.Basement.Utilities;
using Codebasement.DomainDrivenDesign.Model.Editors;
using Codebasement.DomainDrivenDesign.Model.Enums;
using Codebasement.DomainDrivenDesign.Model.ModelClasses;
using Codebasement.DomainDrivenDesign.Model.Validators;
namespace Codebasement.DomainDrivenDesign.Model.ModelClasses
{
[Serializable, DisplayName("Server"), DefaultProperty("HostName")]
[XmlInclude(typeof (Reservation))]
[Relatable(BasementObjectType = typeof (Reservation), Cardinality = 0,
CardinalityType = CardinalityType.CardinalityOrMore)]
[Linkable]
public class Server : BasementObject
{
private const ServerType _defaultServerType = ServerType.WEBSVR;
public Server()
{
ParameterCollection.Add(new Parameter(this, "ServerType"));
ParameterCollection.Add(new Parameter(this, "HostName"));
ParameterCollection.Add(new Parameter(this, "IPAddress"));
}
protected Server(SerializationInfo info, StreamingContext context)
: this()
{
ConstructParameters(info);
}
[Category("Identification")]
[Mandatory]
[Description("The hostname of the server.")]
[RegularExpression(RegexExpressions.HostNameExclusionFormat,
RegexMatchType.MatchExclusions, RegexOptions.Compiled,
RegexExpressions.HostNameExclusionFormatHelp)]
[RefreshProperties(RefreshProperties.All)]
[StringLength(Min = 4, Max = 40)]
public string HostName
{
get { return ParameterCollection["HostName"].Value; }
set
{
if (PropertyValidators.ValidateStringLength(value, 4, 40))
ParameterCollection["HostName"].Value = value;
}
}
[Category("Identification")]
[Description("Internet Protocol Address")]
[RegularExpression(RegexExpressions.IPAddressFormat,
RegexMatchType.MatchFormat, RegexOptions.Compiled,
RegexExpressions.IPAddressFormatHelp)]
public string IPAddress
{
get { return ParameterCollection["IPAddress"].Value; }
set
{
if (PropertyValidators.ValidateIPAddress(value))
ParameterCollection["IPAddress"].Value = value;
}
}
[Category("Server Type"), DisplayName("Main Server Usage")]
[DefaultValue(_defaultServerType)]
[TypeConverter(typeof (EnumDescriptionConverter))]
[Editor(typeof (ServerTypeEditor), typeof (UITypeEditor))]
public ServerType ServerType
{
get
{
string val = ParameterCollection["ServerType"].Value;
return (ServerType)PropertyValidators.ValidateEnum(typeof(ServerType),
val, _defaultServerType);
}
set { ParameterCollection["ServerType"].Value = value.ToString(); }
}
public override string ToString()
{
return (!String.IsNullOrEmpty(HostName)
? HostName
:
base.ToString());
}
}
}
Parameters and Properties
To have a generic way of persisting properties and to enable a uniform manner of validation, the properties of domain model instances are represented as parameters, bundled on the ParameterCollection
of the base class. The internal value type of a parameter is a string
, to allow for anything, but the literals are hidden within the getters and setters of properties and never see daylight other than as meaningful return types only.
Attributes
As you can see, a lot of attributes are used on the domain classes here; standard attributes such as Serializable
, DefaultProperty
, DisplayName
, Description
, DefaultValue
and Category
. Also, type converters and type editors are very useful instruments to extend the expressability of your domain model. I will not go into these standards (i.e. supplied in the .NET Framework) attributes in-depth, there are many articles around that explain their usage and implementation.
Besides standard attributes, there are several custom attributes visible in the code above:
Mandatory
: Whether a property must have a valueStringLength
: Maximum allowed length of a string-value propertyRegularExpression
: Whether a property value must comply with a regular expression (or not comply, i.e. excluded characters)Relatable
: Whether a class instance is relatable to another class instance, and howLinkable
: Whether a class instance can be linked to another class instance (re-used by reference)
Some of these are for validation purposes (and variants can also be found in e.g. the Validation Application Block of the Microsoft Patterns and Practices Enterprise Library). Some are intended to express the characteristics of relations and behavior such as copying, linking, etc.
Validation Pattern
The power of attributes such as illustrated above lies in the very generic way that they can be used, for instance, to apply validation to a domain model such as proposed here. Classes, properties, relations and any other types can be validated uniformly and in a single place, that place being the BasementObject
base class. Below is the code for a Validate
method that builds up a running ValidationResults
collection for any domain class instance (or all at once, namely on a container instance that contains other relatable instances). The template pattern is applied for a protected ValidateCore
method that can be used in domain model classes for specific business rules. Violations of the rules can be validated there and added to the ValidationResults
collection.
public void Validate()
{
if (_validationResults != null)
_validationResults.Clear();
else
_validationResults = new ValidationResultCollection();
AddValidationResults(new ObjectRelationValidator().Validate(this));
AddValidationResults(new ObjectTypeValidator().Validate(this));
AddValidationResults(new ObjectPropertyValidator().Validate(this));
ValidateCore();
}
protected virtual void ValidateCore()
{
}
Serialization and Repositories
The persistence implementation in the accompanying solution is simple and based on straightforward serialization. The XmlInclude
attributes used here are for the standard XmlSerializer
. The additional constructor for serialization is required in our case, since the base class contains a custom implementation for the serialization of the (non-standardly implemented) parameters and relations.
In general, a fire and forget all-in-one serialization pattern will not be sufficient of course, bringing a mapping to a relational model or other storage model into play. An important aspect is to have the persistence governed by one or more repositories, possibly in combination with a provider pattern. Here we have the simple implementation of such a repository for straightforward 'container'-based serialization. This will typically have to be replaced by a more extensive repository that can load and save information to and from the domain model, where the 'containers' are to be replaced by well-chosen aggregate boundaries (e.g. customer with order and order lines as a coherent and transactable 'aggregate').
using Codebasement.DomainDrivenDesign.Basement.Enums;
using Codebasement.DomainDrivenDesign.Basement.Serialization;
using Codebasement.DomainDrivenDesign.Model.ModelClasses;
namespace Codebasement.DomainDrivenDesign.Model.Repository
{
public class ModelRepository
{
private readonly ISerializationProvider _provider;
public ModelRepository(SerializationType serializationType)
{
switch (serializationType)
{
case SerializationType.Binary:
_provider = new BinarySerializationProvider();
break;
case SerializationType.Soap:
_provider = new SoapSerializationProvider();
break;
case SerializationType.Xml:
_provider = new XmlSerializationProvider();
break;
}
}
public void Save(InformationContainer containerObject,
string fileName)
{
_provider.Save(containerObject, fileName);
}
public InformationContainer Load(string fileName)
{
return _provider.Load(typeof(InformationContainer), fileName)
as InformationContainer;
}
}
}
Factories
Repositories for retrieval and persistence, factories for creation. Below is an example basic factory for creating and deleting domain objects. Note here that these two simple methods create or remove any domain object in the model, they are generic in nature. You could also make CreateObject
call itself recursively to create nested relations based solely on the domain model rules for contained relatables.
For a more real-life domain, you would most probably require more specific factory methods that use knowledge about the domain and provide more of a headstart.
public static BasementObject CreateObject(
Type type,
BasementObject parentObject)
{
BasementObject newObject =
Activator.CreateInstance(type) as BasementObject;
if (newObject != null)
{
newObject.ObjectIdentifier = Guid.NewGuid();
newObject.OnBasementObjectCreated(new BasementObjectEventArgs(newObject));
}
if (parentObject != null)
{
if (parentObject.Relatables.Contains(type))
parentObject.Relations.Add(newObject);
else
throw new InvalidOperationException(
String.Format("Type {0} not relatable to object {1}",
type, parentObject));
}
return newObject;
}
public static void RemoveObject
(BasementObject basementObject, BasementObject parentObject)
{
if (!(parentObject != null &
basementObject != null)) return;
parentObject.Relations.Remove(basementObject);
basementObject.OnBasementObjectRemoved(new BasementObjectEventArgs(basementObject));
}
Sample
The following screenshot shows the sample app that uses the domain model. The treeview
is populated using the repository, the factory enables creating and removing instances, and the validation results are visible in the list view.
References
Credits
Credits are due for portions of the BasementObject
class and several of the custom attributes to Mark Achten and Patrick Peters. I also used a Vista-style progress bar from an article here on CodeProject: Vista Style Progress Bar in C#.
Conclusion
Numerous improvements could and should be made on the code base. However, the point here is the application of some domain driven design principles, making elaborate use of standard and custom attributes, and enabling a domain model that derives from a base assembly that facilitates common ground for any domain model. Essential to Domain Driven Design is to place the business domain back in the spotlight, where it belongs.
History
- 6th April, 2008: Initial post