Tim Weaver

A .NET Blog

<November 2008>
SuMoTuWeThFrSa
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456


Navigation

Work

Subscriptions

Post Categories



Monday, January 31, 2005 - Posts

Design Decisions: Paying the Price

Recent events have caused me to go back and review some decisions made a few months back. One of those decisions was around how to solve a problem that was brought to our team.

Now for various reasons we have adopted a model where we have domain objects, service layers (managers) and a datalayer object. All these objects are logically separated, not physically. Now if you read Martin Fowler this will sound almost exactly like his Anemic Domain Model.

The decision I've been pondering isn't about the domain model, however, the choices we made in going with the 3 classes have a profound impact on the problem at hand.

First some sample code. Here is what a typical object would look like following our standard practices. This is purely fictional and I took some liberties to shorten the code.

    6     public class Product

    7     {

    8         private int _ID = NO_PRODUCT_DEFINED;

    9         private string _name;

   10         private const int NO_PRODUCT_DEFINED = -1;

   11         public Product()

   12         {}

   13         public Product(int ID)

   14         {

   15             _ID = ID;

   16         }

   17         public int ID

   18         {

   19             get

   20             {

   21                 return _ID;

   22             }

   23         }

   24         public string Name

   25         {

   26             get

   27             {

   28                 if(_name==null || _name.Length==0)

   29                     return string.Empty;

   30                 else

   31                     return _name;

   32             }

   33  

   34             set{ _name=value;}

   35         }

   36  

   37     }

    5 public class ProductDataLayer

    6     {

    7         protected ProductDataLayer()

    8         {

    9         }

   10         public static Product GetProduct(int ID)

   11         {

   12             return new Product(ID);

   13         }

   14         private static Product LoadProduct(int ID)

   15         {

   16             ///go to the datastore and get the product

   17             ///data and use it to load the entity           

   18             string productName = "Power Drill";

   19             Product product = new Product(ID);

   20             product.Name = productName;

   21             return product;

   22  

   23         }

   24         public static void SellProduct(Product product)

   25         {

   26             int inventory = 0; ///get from the datastore

   27             //check inventory level

   28             if(inventory<1)

   29             {

   30                 ///Custom Inventory Exception

   31                 throw new InventoryException("Sorry, this item is currently out of stock");

   32             }

   33             else

   34             {

   35                 //write updates to the datastore for the sold product

   36             }

   37         }

   38  

   39     }

    5     public class ProductManager

    6     {

    7         public ProductManager()    {}

    8  

    9         public Product GetProduct(int ID)

   10         {

   11             return ProductDataLayer.GetProduct(ID);

   12         }

   13     }

Here is the problem that came up. The team I'm on was approached with a request to find a way to localize the messages in exceptions. Now our first reaction to this was 'why?'. The second reaction was 'find another way'. Of course that didn't resolve the problem this team was trying to solve. The issue at hand was that when an order was saved if there wasn't any inventory the customer needed to be notified. The problem is that because of our class structure you don't "know" if inventory is available until you are inside the static method in the DataLayer. The team didn't want to have to return some sort of result class all the way back up the call stack. They had settled on errors and were happy with that. However they were concerned about how to make the exception messages more localized.

We were concerned about the fact that we were throwing errors in a situation that was anything but exceptional. Not only that, but we are showing exception messages to the UI and had/have visions of connection strings and what have you being shown to customers. Somewhere along the way I think we must have lost our heads because we finally agreed but only with the caveat that only exceptions of a specific type could be caught and shown. Every other exception was not to be shown in the UI. We are going to write an FXCop rule to enforce it.

So now here I am months later still disliking the results and it dawns on me that I'm an idiot. Just to prove how much of an idiot I've recreated the same class with the same ability to communite localized status back to the UI. I do it without throwing an exception and with less code. In the process I'd have to say once again that Martin was right

    5     public delegate void SaleEventHandler(object sender, ProductResultsEventArgs e);

    6     /// <summary>

    7     /// Summary description for SmartProduct.

    8     /// </summary>

    9     public class SmartProduct

   10     {

   11         private string _name;

   12         private const int NO_PRODUCT_DEFINED = -1;

   13         private int _ID= NO_PRODUCT_DEFINED;

   14         protected SmartProduct(int ID)

   15         {

   16             _ID = ID;

   17         }

   18         public int ID

   19         {

   20             get

   21             {

   22                 return _ID;

   23             }

   24         }

   25         public string Name

   26         {

   27             get

   28             {

   29                 if(_name==null || _name.Length==0)

   30                     return string.Empty;

   31                 else

   32                     return _name;

   33             }

   34  

   35             set{ _name=value;}

   36         }

   37         public static SmartProduct GetProduct(int ID)

   38         {

   39             SmartProduct smartProduct  = new SmartProduct(ID);

   40             if(ID!=NO_PRODUCT_DEFINED)

   41             {

   42                 //Get from datastore

   43                 smartProduct.Name = "Smart Power Drill";

   44             }

   45             return smartProduct;

   46         }

   47         public void SellProduct()

   48         {

   49             int inventory = 0; //get from datastore

   50             ProductResultsEventArgs e = new ProductResultsEventArgs();

   51             ///check inventory level

   52             if(inventory<1)

   53             {

   54                 e.Message = "Sorry this product is sold out. Please try another";

   55                 e.SaleComplete = false;

   56  

   57             }

   58             else