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