Strategies and discriminators in NHibernate

I recently posted about enumeration classes, and how I like to use them as a sort of “Enumerations with behavior”.  Not every enumeration should be replaced with a class, but that pattern helps quite a bit when I find a lot of switch statements concerning my enumeration.  Often, these strategies come from data.  For example, I recently had a situation where individual Product instances had different PricingStrategies.  Each PricingStrategy depended on specific rules around Product data, so the Product data owners would decide what PricingStrategy each Product was eligible for.

This was nice, as it seemed like they pretty much flipped a coin on how they wanted to do pricing.  In any case, when the PricingStrategy is data-driven, it leaves a lot of flexibility on the business side to change pricing as they need to, with full confidence that each PricingStrategy type could handle its own pricing rules.

To handle this, we went with something very close to the enumeration classes I described earlier.  First, here’s our Product class:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal UnitPrice { get; set; }
    public int UnitsInStock { get; set; }

    public Category Category { get; set; }
    public PricingStrategy PricingStrategy { get; set; }

    public decimal GetPrice()
    {
        decimal discountAmount = (UnitPrice * PricingStrategy.GetDiscountPercentage(this) / 100m);

        return UnitPrice - discountAmount;
    }
}

Note that the GetPrice uses the PricingStrategy to calculate the final price.  The PricingStrategy (really, a discount strategy in this example) had various means of determining what he discount percentage would be.  It used something of a double-dispatch to calculate this, passing the Product into the PricingStrategy.  Our PricingStrategy class is our old enumeration class:

public abstract class PricingStrategy
{
    private int _id;
    private string _name;
    private string _displayName;

    public static readonly PricingStrategy FullPrice = new FullPriceStrategy(1);
    public static readonly PricingStrategy LowStock = new LowStockDiscountPriceStrategy(2);

    private PricingStrategy() {}

    protected PricingStrategy(int id)
    {
        _id = id;
    }

    public int Id
    {
        get { return _id; }
    }

    public string Name
    {
        get { return _name; }
    }

    public string DisplayName
    {
        get { return _displayName; }
    }

    public override bool Equals(object obj)
    {
        var otherValue = obj as PricingStrategy;

        if (otherValue == null)
        {
            return false;
        }

        return otherValue.Id.Equals(Id);
    }

    public override int GetHashCode()
    {
        return _id.GetHashCode();
    }

    public abstract decimal GetDiscountPercentage(Product product);

    private class FullPriceStrategy : PricingStrategy
    {
        private FullPriceStrategy() { }

        public FullPriceStrategy(int id)
            : base(id)
        {
        }

        public override decimal GetDiscountPercentage(Product product)
        {
            return 0.0m;
        }
    }

    private class LowStockDiscountPriceStrategy : PricingStrategy
    {
        private LowStockDiscountPriceStrategy() { }

        public LowStockDiscountPriceStrategy(int id)
            : base(id)
        {
        }

        public override decimal GetDiscountPercentage(Product product)
        {
            if (product.UnitsInStock < 10)
                return 10m;

            return 0m;
        }
    }
}

It’s rather long, but the basic idea is that the only public type is PricingStrategy.  The implementation types are completely private to anyone outside the abstract PricingStrategy.  Anyone wanting to use a specific PricingStrategy can use the static readonly fields, which again hide the private types.  No one refers to anything but the base PricingStrategy class.

The PricingStrategy values are stored in the database, here is what the tables look like:

In the database, each Product has a foreign key reference to the PricingStrategies table, which has only two entries right now.  Each PricingStrategyID corresponds to the ID used in the static fields defined in the PricingStrategy class.  That way, no matter if a user choose to pull from the database or from the static fields, each Value Object is equal to the other.  The corresponding NHibernate mapping for PricingStrategy would be:

<class name="PricingStrategy" table="PricingStrategies">
    <id name="Id" column="PricingStrategyID" type="int">
        <generator class="assigned" />
    </id>
    <discriminator column="PricingStrategyID" />
    <property name="Name" />

    <subclass discriminator-value="1" extends="PricingStrategy" name="PricingStrategy+FullPriceStrategy" />
    <subclass discriminator-value="2" extends="PricingStrategy" name="PricingStrategy+LowStockDiscountPriceStrategy" />
</class>

Note that each subclass is called out with the <subclass> element, with the corresponding discriminator value.  The specific strategy is specified with the “name” attribute.  At load time, NHibernate will check the discriminator column value and pull up the corresponding subclass that matches that found value.  When the Product table has a “1″ for the PricingStrategyID, NHibernate pulls up the FullPriceStrategy, and so on.

So how might this look in actual client code?  Here are a couple of tests that pull out two different Product entries from the database, each with a different PricingStrategy:

[Test]
public void Products_with_full_price_strategy_should_charge_full_price()
{
    ISession session = GetSession();
    var product = session.Get<Product>(22);

    product.PricingStrategy.ShouldEqual(PricingStrategy.FullPrice);
    product.GetPrice().ShouldEqual(product.UnitPrice);
}

[Test]
public void Products_with_low_stock_price_strategy_should_charge_discount_when_stock_is_low()
{
    ISession session = GetSession();
    var product = session.Get<Product>(8);

    product.UnitsInStock.ShouldBeLessThan(10);
    product.PricingStrategy.ShouldEqual(PricingStrategy.LowStock);
    product.GetPrice().ShouldBeLessThan(product.UnitPrice);
}

The first Product uses the FullPrice strategy (proven by the first assertion).  Its price should simply equal the UnitPrice.  In the second test, that Product uses the LowStock pricing strategy.  That strategy charges a discount when the stock gets low (maybe it’s a blue-light special?).  Its final price should be lower than the original UnitPrice, and this is verified by our unit test above.

When strategies or enumeration classes aren’t completely code-driven, and are data driven as was in this case, NHibernate discriminators provide a nice means of a “strategy factory”.  Each individual strategy instance is supplied with a distinct discriminator value, allowing me to add new strategy/enumeration class implementations as needed.  The specific implementation is data-driven, which makes our product data owners happy.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in NHibernate, Patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://lieser-online.de Stefan Lieser

    Hi Jimmy,
    how do you do the mapping if the enumeration is a value object instead of an entity?

    Cheers,
    Stefan Lieser

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Stefan

    This is a Value Object. Just because it has a primary key (for storage purposes), doesn’t always mean it’s an entity. Evans has some more discussion around this area.

  • http://weblogs.asp.net/jvano joey

    i heart strategy pattern.

  • http://lieser-online.de Stefan Lieser

    @Jimmy

    Lets try to describe better what I meant: if you would not have the PricingStrategies table how would you map your scenario?

    Regards,
    Stefan

  • Dave The Ninja

    @Stefan Lieser

    When I have done this in the past, without the lookup table, I have a CONST ID value within the class itself.

    See Refactoring by fowler, i think the chapter is “replace conditional with polymorphism”

    I would then store the const ID in the, referring to the example above, products table.

    Ninja

  • http://lieser-online.de Stefan Lieser

    @Ninja

    Yes that’s the way we do it (ID on products table). But how do you map this column to the enumeration class? If you use a component mapping you can map the id-column to the id-property of the enumeration class. But then you need a static hashtable in the enumeration class to fill in the other predefined properties that otherwise would come from the lookup table.

    Cheers,
    Stefan Lieser

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Stefan

    The other way to approach this is UserTypes. You create a custom NHibernate-persistable type, and then don’t use discriminators. You’ll have to put the factory logic inside the UserType.

  • http://lieser-online.de Stefan Lieser

    @Jimmy

    Thats a good I idea, I will try that.

    Thanks!
    Stefan

  • Scott Millett

    Hi Jimmy,

    Could you possibly post the corresponding NHibernate mapping for the Product entity?

    Cheers
    Scott

  • http://jimmybogard.lostechies.com Jimmy Bogard

    @Scott

    Can you email me through the “Contact Me” link, I’ll send it right over!

  • dario-g

    Hi
    Can you look at my question at SO?

    I send to you info through the “Contact Me” but with no effect. :(

  • Rupeshmehta

    Hello I have a question  , I will appreciate if you can help me.

    I have following classes with mentioned properties. GLLegalEntity is the base class, in Hibernate I am discriminating 2 subclass on column Dimension ( if Dimension =1 then Cost Center if 2 then Account ) , now I want to Discriminate this subclass as well on certain field , let say TargetLedger , if( TargetLedger=”SAP” then SAPCostCenter , TargetLedger=”Insight” then InsighCostCenter )

    GLLegalEntity
    - Attribute1
    -Attribute2

    CostCenter : GLLegalEntity
    - Attribute3

    SAPCostCenter : CostCenter
    -Attribute4

    InsighCostCenter
    -Attribute5

    Account: GLLegalEntity
    -Attribute6

    Mapping file

                                                                                                                                                     

    I am not sure How to Discriminate sub class. As there is no tag called discrimator in subclass.

    Can you please help me ? appreciate your help.

    • Anonymous

      Hmmm not sure, have you tried asking this question on the NH users mailing list? You’re sure to get someone to answer there.

  • http://hammerproject.com/ matt kocaj

    Hey Jimmy,

    I can’t get this method working for me. The line  is giving me trouble.

    I get the exception: NHibernate.MappingException: persistent class 
    PricingStrategy+FullPriceStrategy not found.

    Any ideas?