Integrated Samples and Concepts
This section will cover integrated examples with real code that hopefully you might find useful.
Right now there is an amazing group doing very cool work at Castle. What I often hear is that the barrier to gaining 'expert' level knowledge can be steep, given your average developer still is not 100% comfortable 'working off the trunk'. That is no surprise as often two or three separate projects 'might or might not' even work by themselves. let alone based on a nightly status and given the rather complex dependencies that exist.
So our goal here is to present real solutions that target the developers who have gotten over the initial hump but are trying to get to black belt. Please be aware that a dedicated site also exists for exactly this reason. This site is in no way commercial, it is a resource for those who could benefit. To check it out go here: http://www.domaindotnet.com
.
Lets start at the beginning. For a foundational case we can assume an ASP.NET 3.5 / C# 3.0 Ajax.NET site leveraging the Castle, NHIbernate and Rhino Commons Trunks (that would be NHibernate 2.0.0.Alpha1 (current as of 5/10/2008) - technical post 2.0.0.Alpha1 as that is a tagged item in the repository and we would prefer to work from their current dev state).
Before we dive in let's look at a piece of the Dependencies of the components we'll start with (Dependency Management is a critical skill, and tools like NDepend are worth their weight in gold):
Here is a 'big picture' diagram which covers a subset of the basic dependencies between the critical assemblies:
Figure 1 : Starting with the 'Core'
Initial Decisions
- We will use a derived custom global.asax which will handle our 'session in view' needs as well as provide a clear singleton container for Windsor and the IoC we cannot live without (OK I cannot live without). We could use an HttpModule as well (including the Castle provided solution) but the code is trivial and as you will see we will need more control over what occurs.
- Although Castle's work represents global best practices, we want to design in some degree of flexibility across all reasonable inflection points. This includes ORM Vendor (NHibernate/ActiveRecord/Linq to Entities) as well as even the ASP.NET platform as much as we can.We can only anticipate change and we 'embrace it and look forward to it'.
- As you probably don't care much about Castle unless you are (knowingly or not) 'Domain Driven', this drives a large number of design decisions (that to the experienced in the techniques are more like 'common sense' and/or 'why in the world would you consider an alternative'?
- This includes : Continuous Integration, Heavy Mock Framework Usage (also in the build automation), continuous refactoring / nearly obsessively - because we are lazy. Seem like a paradox? Again I will make the assertion tht the most successful developers are fundamentally lazy (if lazy means trying to avoid tedious non-productive work), well educated and always being pushed internally to learn, and able to think from the perspective of the business domain first, technical domain second (and only to realize the domain requirements).
- Use of 'real' configuration management tools which today basically means Subversion. You must manage complex branch/merge scenarios and other tools just cannot deal. Plus you will find almost all the code you want to leverage and understand will come from existing Subversion repositories.
For our ASP.NET app, let's not use the section handler in web.config and instead store initial Windor settings in Windsor.Config file. Also we'll do programmatic registration of our components in Windsor (and eventually use the incredible Bindsor from Ayende).
Here is the castle.config:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <facilities> <facility id="ar.facility" type="Castle.Facilities.ActiveRecordIntegration.ActiveRecordFacility, Castle.Facilities.ActiveRecordIntegration" isDebug="False" isWeb="True" > <assemblies> <item>agilefactor.domain.coresupport</item> <item>agilefactor.ormadapter</item> </assemblies> <config> <add key="connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="dialect" value="NHibernate.Dialect.MsSql2000Dialect" /> <add key="connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="connection.connection_string" value="Data Source=prem.kster.com;Initial Catalog=Damarr;Persist Security Info=True;User ID=darr;Password=26D" /> </config> </facility> </facilities> </configuration>
So all we are doing is setting the facility for ActiveRecord up, pointing to the assemblies with classes with the [ActiveRecord] attribute, and specifying our NHIbernate stuff.
I will skip completely XML configuration of Windsor/MicroKernal for Components by the way because a) I hate it and b) There are better ways now with Bindsor (or rolling your own which I will show you first).
Before we go there, I want to present one additional diagram which will show the abstract base class for our domain classes (not 100% correct terminology as we will see but who cares for now).
| Tasks: Core Techniques | |||
|---|---|---|---|
I inherit from the Castle infrastructure for the core domain objects. I actually don't 100% like this pattern but it is very 'real-world'. While not absolutely 'pure' in an architectural sense, it is a tradeoff that works.
For example, we all know the 'single resposibility' design goal for classes.
| Critical Knowledge Lack of Cohesion Of Methods (LCOM): The single responsibility principle states that a class should not have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0-1]. The LCOM HS (HS stands for Henderson-Sellers) takes its values in the range [0-2]. A LCOM HS value highest than 1 should be considered alarming. Here are algorithms used by NDepend to compute LCOM metrics:
Where:
The underlying idea behind these formulas can be stated as follow: a class is utterly cohesive if all its methods use all its instance fields, which means that sum(MF)=M*F and then LCOM = 0 and LCOMHS = 0. Recommendations: Types where LCOM > 0.8 and NbFields > 10 and NbMethods >10 might be problematic. However, it is very hard to avoid such non-cohesive types. Types where LCOMHS > 1.0 and NbFields > 10 and NbMethods >10 should be avoided. Note that this constraint is stronger (and thus easier to satisfy) than the constraint types where LCOM > 0.8 and NbFields > 10 and NbMethods >10. |
Well, after looking at all the 'responsibilities' of any Domain Class, it is just too much. Data Access/Update, POCO get/set and mapping to relations, often validation, sometimes logging, etc. etc. But that is for a different day. We'll refactor as we go just like a real project. But remember, your refactoring design and even architecture in an iterative way, and code refactoring is just the side product.
Here is actual code used recently (not perfect, but illustrates some ideas):

The key concept is that as much as possible we refer to the 'generic free' Interface called : IDomainBase - not because generics are bad, but because it makes our life easier as you will see.
We also register our Domain Classes in Windsor, referring to that Interface as the service.
I mistakenly though the ActiveRecordFacility in Windsor did this and as far as I can tell, it does not, it only registers the ISessionFactory and ISessionFactoryHolder as components which 'could' then be injected into components you manage to get into Windsor (anyone? Is this not correct?).
As this is a base class, everything below this is assumed to likely be in a completely different assembly, and those classes would not need to be Generic Types.
public abstract class DomainBase<TType> : ActiveRecordValidationBase<TType>, IDomainBase where TType : DomainBase<TType>, IDomainBase, new() { private readonly ICustomTypeDescriptor _customTypeDescResolver; protected int _Id; private String _instanceId = Guid.NewGuid().ToString(); protected DomainBase() : this(null) { } protected DomainBase(ICustomTypeDescriptor customTypeDescResolver) { _customTypeDescResolver = customTypeDescResolver; } protected ISessionFactoryHolder FactoryHolder { get; set; } private int GetDomainInstanceKey { get { return ((IDomainBase)this).Id; } } #region IDomainBase Members public virtual string ComponentName { get { return _instanceId; } set { if (!String.IsNullOrEmpty(value)) _instanceId = value; } } public void RebindToORM() { if (GetDomainInstanceKey != 0) Refresh(); } public virtual void Dispose() { DomainServices.Release(this); if (Disposed != null) Disposed(this, EventArgs.Empty); } public IDomainBase Initialize(int _int) { ((IDomainBase)this).Id = _int; Refresh(); return this; } public override sealed bool Equals(object obj) { var areEqual = false; // Are they pointing to the same memory location?! if (ReferenceEquals(this, obj)) areEqual = true; else // OK one final check... Do the KEY values match? // Zero is meaningless to us if (GetDomainInstanceKey > 0 && ((IDomainBase)obj).Id == GetDomainInstanceKey) areEqual = true; return areEqual; } public override sealed int GetHashCode() { return _instanceId.GetHashCode(); } public override string ToString() { return GetType().Name + " " + GetDomainInstanceKey + ""; } public void Insert(IDictionary insertColsValues) { Insert(insertColsValues, null); } public void Insert(IDictionary _values, IDomainBase parent) { var _properties = _customTypeDescResolver.GetProperties(); foreach (var keyValue in _values.Keys) { var _prop = _properties.Find((string)keyValue, true); baseColumnInsertUpdate(_prop, _values, keyValue, _properties); } } public void Update(IDictionary keys, IDictionary values, IDictionary oldValues) { var bIsChanged = false; var _properties = _customTypeDescResolver.GetProperties(); foreach (var PropertyName in values.Keys) bIsChanged = VisitPropertyCheckUpdate(values, PropertyName, oldValues, _properties, bIsChanged); if (bIsChanged) UpdateAndFlush(this); } public abstract int Id { get; set; } public virtual event EventHandler Disposed; public virtual ISite Site { get; set; } public virtual bool IsPendingSave() { return GetDomainInstanceKey.Equals(0); } #endregion /// <summary> /// Visits the property check update. /// </summary> /// <param name="values">The values.</param> /// <param name="PropertyName">Name of the property.</param> /// <param name="oldValues">The old values.</param> /// <param name="_properties">The _properties.</param> /// <param name="bIsChanged">if set to <c>true</c> b is changed.</param> /// <returns></returns> private bool VisitPropertyCheckUpdate(IDictionary values, object PropertyName, IDictionary oldValues, PropertyDescriptorCollection _properties, bool bIsChanged) { if (DomainServices.RuntimeTypeAssist.areObjectsDifferent(valuesPropertyName, oldValuesPropertyName)) { var _prop = _properties.Find((String)PropertyName, true); if (_prop != null) { performCorePropertyUpdate(values, PropertyName, _properties, _prop); bIsChanged = true; } else throw new InvalidOperationException("Could not find the property " + ((String)PropertyName + " as a Descriptor when attempting an update")); } return bIsChanged; } /// <summary> /// Performs the core property update. /// </summary> /// <param name="values">The values.</param> /// <param name="PropertyName">Name of the property.</param> /// <param name="_properties">The _properties.</param> /// <param name="_prop">The _prop.</param> private void performCorePropertyUpdate(IDictionary values, object PropertyName, PropertyDescriptorCollection _properties, PropertyDescriptor _prop) { if (!_prop.PropertyType.Equals(values[PropertyName].GetType())) baseColumnInsertUpdate(_prop, values, PropertyName, _properties); else _prop.SetValue(this, values[PropertyName]); } /// <summary> /// Bases the column insert update. REFACTOR THIS NIGHTMARE OF UNREADABLE MUCK /// </summary> /// <param name="_prop">The _prop.</param> /// <param name="values">The values.</param> /// <param name="PropertyName">Name of the property.</param> /// <param name="_properties">The _properties.</param> private void baseColumnInsertUpdate(PropertyDescriptor _prop, IDictionary values, object PropertyName, PropertyDescriptorCollection _properties) { if (prop.Name.Contains("")) DomainServices.Instance.UpdateObjectContainedInstance(_properties, _prop, this, valuesPropertyName); else if (valuesPropertyName != null) if (_prop.Converter.CanConvertFrom(valuesPropertyName.GetType())) _prop.SetValue(this, _prop.Converter.ConvertFrom(valuesPropertyName)); else { // We must deal with a null value into a val type... var resultValue = Convert.ToString(values[PropertyName]); var newval = _prop.Converter.ConvertFromString(resultValue); if (_prop.Converter.CanConvertFrom(newval.GetType())) _prop.SetValue(this, newval); } } }

note: The above should be taken for what it is : An implementation using the ActiveRecord PATTERN. There is nothing that keeps you from decoupling your Entities from the Data Concerns. This is preferable inn most cases. Many use NHibernate for this extra architectural structure and ActiveRecord from Castle for less 'stringent' needs. I have come to a place where there is so much value add from the rest of the Castle stack, and you can essentially leverage best practices using the integration facilities, the above is looking very dated to me now...
Anyway, watch for an update that is more in line with what many people are doing in this area, and by no means should ActiveRecord as a solution be considered a 'lesser' alternative.
Thanks,
Damon Wilder Carr