Monday, December 22, 2008

DDD : Value Types

*A follow on from : http://rhysc.blogspot.com/2008/09/when-to-use-enums-vs-objects.html

This is a brief demo of mapping constructing and using Value type in the domain. To stick with the cliches we will use orders and order status. To give some structure we will lay out some ground business rules

  1. When an order is created it is of the status In Process
  2. it is then Approved
  3. then Shipped
  4. Cancelled backordered need to be in the mix too

Ok, not the most robust order system, but that's not the point.

Lets first look at how the domain logic could be handled with using an Enum.... bring on the switch statement!!!!

Ok so let see if we can edit an order when the status is an enum:

public class Order{
//...
public bool CanEdit()
{
switch(this.OrderStatus)
case: OrderStatus.InProcess
return true;
case: OrderStatus.Approved
return false;
case: OrderStatus.Shipped
return false;
//etc etc
}
//...
}


Ok that is not exactly scalable... the more statuses we get the more case statements we have to add. If we add a status we also have to find every place that there is a switch statement using this enum and add the new status is not a case. think about his for a second... really think about it, how many enum do you have that have functionality tied to them. Right.



No lets look at the same code but we are using "real" objects; Exit the switch and enter the strategy pattern:



public class Order
{//...
public bool CanEdit()
{
return this.OrderStatus.CanEditOrder();
}//...
}



Now obviously there need to be some know-how on this non-enum enum. let have a look at how I have done this is the past.



/// <summary>

    /// Sales Order Status Enumeration


    /// </summary>


    public abstract class SalesOrderStatus


    {


        #region Statuses


        /// <summary>


        /// InProcess


        /// </summary>


        public static SalesOrderStatus InProcess = new InProcessSalesOrderStatus();


        /// <summary>s


        /// Approved


        /// </summary>


        public static SalesOrderStatus Approved = new ApprovedSalesOrderStatus();


        /// <summary>


        /// Backordered


        /// </summary>


        public static SalesOrderStatus Backordered = new BackorderedSalesOrderStatus();


        /// <summary>


        /// Rejected


        /// </summary>


        public static SalesOrderStatus Rejected = new RejectedSalesOrderStatus();


        /// <summary>


        /// Shipped


        /// </summary>


        public static SalesOrderStatus Shipped = new ShippedSalesOrderStatus();


        /// <summary>


        /// Cancelled


        /// </summary>


        public static SalesOrderStatus Cancelled = new CancelledSalesOrderStatus();


        #endregion



        #region Protected members

        /// <summary>


        /// The status description


        /// </summary>


        protected string description;


        #endregion



        #region Properties

        /// <summary>


        /// Gets the description of the order status


        /// </summary>


        /// <value>The description.</value>


        protected virtual string Description


        {


            get { return description; }


        }


        #endregion



        #region Public Methods

        /// <summary>


        /// Determines whether this instance allows the diting of it parent order.


        /// </summary>


        /// <returns>


        ///     <c>true</c> if this instances parent order can be edited; otherwise, <c>false</c>.


        /// </returns>


        public abstract bool CanEditOrder();


        #endregion



        #region Child Statuses

        private class InProcessSalesOrderStatus : SalesOrderStatus


        {


            public InProcessSalesOrderStatus()


            {


                description = "In Process";


            }


            public override bool CanEditOrder()


            {


                return true;


            }


        }


        private class ApprovedSalesOrderStatus : SalesOrderStatus


        {


            public ApprovedSalesOrderStatus()


            {


                description = "Approved";


            }


            public override bool CanEditOrder()


            {


                return false;


            }


        }


        private class BackorderedSalesOrderStatus : SalesOrderStatus


        {


            public BackorderedSalesOrderStatus()


            {


                description = "Back ordered";


            }


            public override bool CanEditOrder()


            {


                return true;


            }


        }


        private class RejectedSalesOrderStatus : SalesOrderStatus


        {


            public RejectedSalesOrderStatus()


            {


                description = "Rejected";


            }


            public override bool CanEditOrder()


            {


                return false;


            }


        }


        private class ShippedSalesOrderStatus : SalesOrderStatus


        {


            public ShippedSalesOrderStatus()


            {


                description = "Shipped";


            }


            public override bool CanEditOrder()


            {


                return false;


            }


        }


        private class CancelledSalesOrderStatus : SalesOrderStatus


        {


            public CancelledSalesOrderStatus()


            {


                description = "Cancelled";


            }


            public override bool CanEditOrder()


            {


                return false;


            }


        }



        #endregion

    }



Note this is especially good for Value object in a DDD sense and can be easily mapped to the database. More benefits include that I do not have to hit the DB to get a status. As they are value object and have no need for an ID (in the the domain), we only map the id in the mapping files. The POCO object know nothing of ids. I can also create lists for drop down binding too if required... with no need to retrieve from the DB.



I have had some people raise the point "but what if we need a change in the DB for a new status?". Well that sounds like new logic to me and should mean reworking the logic and then a recompile anyway, however now we are being very clear with how we handle each enum as the object now possesses its own logic.



If you are using NHibernate the mapping would look like this:



<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">


    <class name="Sample.SalesOrderStatus,Sample" table="Sales.SalesOrderStatus" abstract="true">



        <id column="SalesOrderStatusID" type="Int32" unsaved-value="0">

            <generator class="native"/>


        </id>


        <discriminator column="SalesOrderStatusID" />


        <property column="Description" type="String" name="Description" not-null="true" length="50" />



        <subclass discriminator-value="1" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+InProcessSalesOrderStatus,Sample"/>

        <subclass discriminator-value="2" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+ApprovedSalesOrderStatus,Sample"/>


        <subclass discriminator-value="3" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+BackorderedSalesOrderStatus,Sample"/>


        <subclass discriminator-value="4" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+RejectedSalesOrderStatus,Sample"/>


        <subclass discriminator-value="5" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+ShippedSalesOrderStatus,Sample"/>


        <subclass discriminator-value="6" extends="Sample.SalesOrderStatus,Sample" name="Sample.SalesOrderStatus+CancelledSalesOrderStatus,Sample"/>


    </class>


</hibernate-mapping>



The above SalesOrderStatus abstract class can now have static method on it to do thing you may normally hit the DB for eg to get Lists of Statuses, however now you are confined to the realms of the domain. This makes life easier  IMO as there is less external dependencies.  I have found I use enums very rarely in the domain and usually only have them in the UI for Display object or in DTOs across the wire (eg error codes; as an enum fails back to its underlying universal int type).



Try it out, see if you like it and let me know how it goes.

5 comments:

Lee Campbell said...

Being really picky here but:
You dont need constructors on your private classes. The C# compiler will add them for you. Also note this is ok for internal/private types as it is NOT a breaking change if you later add them (as the only code you could affect is internal code which was going to be recompiled anyway).
Next, protected fields are part of the public API -->public fields. Booo!! I recommend modifying the base class to have an abstract description property with a getter.
The refactor of the code would look like this (minus comments for brevity)

public abstract class SalesOrderStatus
{
#region Statuses
public static SalesOrderStatus InProcess = new InProcessSalesOrderStatus();

public static SalesOrderStatus Approved = new ApprovedSalesOrderStatus();

public static SalesOrderStatus Backordered = new BackorderedSalesOrderStatus();

public static SalesOrderStatus Rejected = new RejectedSalesOrderStatus();

public static SalesOrderStatus Shipped = new ShippedSalesOrderStatus();

public static SalesOrderStatus Cancelled = new CancelledSalesOrderStatus();
#endregion

#region Properties

public abstract string Description { get; }

#endregion

#region Public Methods

public abstract bool CanEditOrder();

#endregion


#region Child Statuses

private class InProcessSalesOrderStatus : SalesOrderStatus
{
//Not on a public interface so not providing a ctor is fine. (ie is not a breaking change to add later)
//public InProcessSalesOrderStatus()
//{
//}

public override string Description
{
get { return "In Process"; }
}

public override bool CanEditOrder()
{
return true;
}
}


private class ApprovedSalesOrderStatus : SalesOrderStatus
{
public override string Description
{
get { return "Approved"; }
}
Public override bool CanEditOrder()
{
return false;
}
}

etc....

RhysC said...

GOOD POINTS.
This is was stripped down version of old code so need to be modified. The protected field should have been protected internal if it was used in the base class, otherwise the abstract property is all good.

I really need a resharper licence for this machine.

Lee Campbell said...

"protected internal" means protected OR internal access is granted. What I think you mean is to make the class internal and the field protected (thus making it available only to sub classes in the same assembly), however now your class is not public! So, correctly making the property abstract and never exposing public modifiable fields* is the correct way forward.

*Dont really have a problem with "public static readonly" or "public const" fields assuming that a const is truly constant.

RhysC said...

Thats funny, there appears to be a misconception on what protected internal is, becuase you are right and most people think its an AND effect.

Lee Campbell said...

Oh and they should probably be public static readonly, and getting really picky the base class could have a private ctor as I assume you dont want anyone else creating their own versions of these status types.