Saturday 21 February 2009

Fluent NHibernate semantic model: Visitors

My work-in-progress semantic model based version of Fluent NHibernate makes heavy use of the visitor pattern. Currently, the visitor implementation is there to serve two purposes:

  1. Facilitate the construction of a Hbm* representation of the mapping model. Hbm* refers to the set of classes found in the NHibernate.Cfg.MappingSchema namespace that are generated from the NHibernate mapping schema. My version of Fluent NHibernate communicates with NHibernate by serializing the Hbm* representation to xml.
  2. Enable powerful, user-defined conventions that walk the mapping model and make changes. The NamingConvention class is a very simple example of this – it takes mapping model instances and sets their Name property based on reflected data such as the System.Type or the PropertyInfo.

Lets begin by looking at the implementation of AcceptVisitor for the root of the mapping model – HibernateMapping:

public override void AcceptVisitor(IMappingModelVisitor visitor)
{
    visitor.ProcessHibernateMapping(this);

    foreach (var classMapping in Classes)
        visitor.Visit(classMapping);
}

This is reasonably straightforward. The HibernateMapping tells the visitor to first process a HibernateMapping instance, and passes itself as the argument. Then it tells the visitor to visit each of the child classes. What the visitor does when its told to visit a ClassMapping is its business, but what it is likely to do is call AcceptVisitor on the ClassMapping:

public override void AcceptVisitor(IMappingModelVisitor visitor)
{
    visitor.ProcessClass(this);            

    if (Id != null)
        visitor.Visit(Id);

    if (Discriminator != null)
        visitor.Visit(Discriminator);

    foreach (var subclass in Subclasses)
        visitor.Visit(subclass);

    base.AcceptVisitor(visitor);
}

This is similar to the previous AcceptVisitor implementation, but its worth noting that at the end, it calls base.AcceptVisitor(visitor). This is necessary because ClassMapping inherits from a common base class (JoinedSubclassMapping and SubclassMapping also inherit from this base class). Here is AcceptVisitor on ClassMappingBase:

public override void AcceptVisitor(IMappingModelVisitor visitor)
{
    foreach (var collection in Collections)
        visitor.Visit(collection);

    foreach (var property in Properties)
        visitor.Visit(property);

    foreach (var reference in References)
        visitor.Visit(reference);
}

Of course – all class mappings, regardless of how they fit into an inheritance hierarchy, can have collections, properties and references (many-to-ones). Its probably not necessary to follow this any further. The important point is that as long as the visitor calls AcceptVisitor when it is told to visit something, then it will make its way along the entire mapping model. To make life easier, I’ve implemented a DefaultMappingModelVisitor class that does precisely this. It has a whole bunch of code that all looks very similar to this:

public override void Visit(PropertyMapping propertyMapping)
{
    propertyMapping.AcceptVisitor(this);
}

public override void Visit(ManyToOneMapping manyToOneMapping)
{
    manyToOneMapping.AcceptVisitor(this);
}

public override void Visit(KeyMapping keyMapping)
{
    keyMapping.AcceptVisitor(this);
}
.. Many more Visit methods

Now you might be looking at this and wondering why this is necessary. Why can’t we skip this completely, and just have AcceptVisitor implementations that will call AcceptVisitor directly like this:

public override void AcceptVisitor(IMappingModelVisitor visitor)
{
    visitor.ProcessHibernateMapping(this);

    foreach (var classMapping in Classes)
        classMapping.AcceptVisitor(visitor); <--- JUST DO THIS??
}

The answer is that the proposed change will work when you want one single visitor instance to visit the entire mapping model. While this works fine for conventions (as you will see in a moment), it does not work so well for building the Hbm representation. I’ll get to that in a second, but lets first take a look at the simpler case of the conventions. Here is a gutted version of the NamingConvention:

public class NamingConvention : DefaultMappingModelVisitor
{
    public Func<MemberInfo, string> DetermineNameFromMember = info => info.Name;
    public Func<Type, string> DetermineNameFromType = type => type.AssemblyQualifiedName;

    public override void ProcessOneToMany(OneToManyMapping oneToManyMapping)
    {
        if (!oneToManyMapping.Attributes.IsSpecified(x => x.ClassName))
        {
            if (oneToManyMapping.ChildType == null)
                throw new ConventionException("Cannot apply the naming convention. No type specified.", oneToManyMapping);
            oneToManyMapping.ClassName = DetermineNameFromType(oneToManyMapping.ChildType);
        }
    }
}

I’ve removed the majority of its implementation for the sake of brevity. As it currently stands, it will walk the entire mapping model (because it inherits from the aforementioned DefaultMappingModelVisitor), and when it encounters a OneToManyMapping, it will attempt to set its ClassName based on the ChildType property. Its worth noting that the full implementation of the NamingConvention class actually handles naming of many other mappings types, such as ClassMappings, ManyToManyMappings, etc. This means that this visitor completely handles the concern of setting the name of mapping model elements for the entire mapping model. This point is important, because the next example is different.  As I mentioned before, this visitor implementation would work fine with the previous simplification of having AcceptVisitor directly call AcceptVisitor on the children. Lets now move on to the process of building a Hbm* representation, and examine why the simplification won’t work so well for this case.

I define an interface for classes that build Hbm:

public interface IHbmWriter<T>
{
    object Write(T mappingModel);        
}    

Here is an example implementor, a hbm writer that will handle ColumnMappings:

public class HbmColumnWriter : NullMappingModelVisitor, IHbmWriter<ColumnMapping>
{
    private HbmColumn _hbm;

    public object Write(ColumnMapping mappingModel)
    {
        _hbm = null;
        mappingModel.AcceptVisitor(this);
        return _hbm;
    }

    public override void ProcessColumn(ColumnMapping columnMapping)
    {
        _hbm = new HbmColumn();
        _hbm.name = columnMapping.Name;
        
        if(columnMapping.Attributes.IsSpecified(x => x.IsNotNullable))
        {
            _hbm.notnull = columnMapping.IsNotNullable;
            _hbm.notnullSpecified = true;
        }

        if (columnMapping.Attributes.IsSpecified(x => x.Length))
            _hbm.length = columnMapping.Length.ToString();
        
//etc
    }
}

You’ll notice that this class inherits from NullMappingModelVisitor, which is basically a blank implementation of IMappingModelVisitor. It has all the methods, but none of them do anything. So this visitor ONLY knows how to handle ColumnMappings - if its passed to any other type of mapping, it will do nothing. This is certainly a difference approach to the NamingConvention, which actually knew how to set the name for Classes, OneToMany’s, ManyToMany’s and many other mapping model elements. So why does this difference exist? Basically, the job of generating all the Hbm that NHibernate requires is too big for one class. Creating separate classes for generating the appropriate HBM helps make the job more manageable. These classes can then be composed, like so:

public class HbmIdWriter : NullMappingModelVisitor, IHbmWriter<IdMapping>
{
    private readonly IHbmWriter<ColumnMapping> _columnWriter;
    private readonly IHbmWriter<IdGeneratorMapping> _generatorWriter;

    private HbmId _hbm;

    public HbmIdWriter(IHbmWriter<ColumnMapping> columnWriter, IHbmWriter<IdGeneratorMapping> generatorWriter)
    {
        _columnWriter = columnWriter;
        _generatorWriter = generatorWriter;
    }

    public object Write(IdMapping mappingModel)
    {
        _hbm = null; 
        mappingModel.AcceptVisitor(this);
        return _hbm;
    }

    public override void ProcessId(IdMapping idMapping)
    {
        _hbm = new HbmId();

        if(idMapping.Attributes.IsSpecified(x => x.Name))
            _hbm.name = idMapping.Name;
    }

    public override void Visit(ColumnMapping columnMapping)
    {
        var columnHbm = (HbmColumn) _columnWriter.Write(columnMapping);
        columnHbm.AddTo(ref _hbm.column);
    }

    public override void Visit(IdGeneratorMapping generatorMapping)
    {
        var generatorHbm = (HbmGenerator) _generatorWriter.Write(generatorMapping);
        _hbm.generator = generatorHbm;
    }
}

This hbm writer handles IdMappings. IdMappings include one or more columns and a generator, so this writer composes a IHbmWriter<ColumnMapping> and a IHbmWriter<IdGeneratorMapping>. In this way, the task of creating the hbm representation can be managed by a family of visitors that delegate to each other as required.

Finally now, I can return to the previous point of why all the AcceptVisitor implementations call visitor.Visit(child) rather than child.AcceptVisitor(visitor). The former allows the current visitor to see that a different visitor is passed in when calling child.AcceptVisitor(). You can see some of this happening above, in the override for Visit(ColumnMapping) – it asks the IHbmWriter<ColumnMapping> to write the column. The implementation of that method will call columnMapping.AcceptVisitor(this). Thus, the Fluent NHibernate semantic model has an implementation of the visitor pattern that supports both single visitors that visit the entire graph themselves, and families of visitors that collaborate to get a large job done.

2 comments:

  1. Heya Paul! :) I know I'm late to the postings here, but I'm catching up on everyone's blog.

    I have a quick question regarding the visitor implementation. What happens if you want to change the order in which elements on a class mapping are called? For example, you've got the class visitor calling Id, Discriminator, Subclass, Collection, Property, and then References; in that order. If, I wanted a visitor implementation that needed to process subclasses before processing Id's (say for a joined subclass affecting the parent class mapping somehow), how would I go about doing this?

    Of course, doing something like that puts more responsibility on the visitor, but at an advantage of more fined grained control. I guess both have their pro's and cons.

    Also, as a purely semantic question (no pun intended ;) ), in your example you have methods on the visitor named "ProcessHibernateMapping", "ProcessClassMapping" and "ProcessOneToMany". It looks like you avoided using an overload of "Process" which just took different parameters depending on what's calling it, so I was curious what made you come to that decision sir :)

    ReplyDelete
  2. Hey Hudson, good questions. I posted an answer to the mailing list so that James sees it.

    ReplyDelete