zondag 3 september 2006

Collections in Business Entities

A while ago, I've been thinking on what would be the best approach to work with collections inside business entities.

As an example, I'll refer back to the domain classes I'm using in another post of me.
There, I have a class Order, and an OrderLine class. The Order class contains a collection of OrderLines.
In C# 2.0, this could look like this:

public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();
}
The problem here, is that I want to have 'controlled access' to the OrderLines collection in the Order class.
What I mean is, that a consumer of these classes should not be able to add an OrderLine to the Order directly, since some additional action(s) need to be executed.



More precisely, if an OrderLine is added to the Order, I want to set a member of the OrderLine, so that the OrderLine knows to which Order it belongs.
Therefore, I create an AddOrderLine method in the Order class:
public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
this.orderLines.Add (ol);
}
A consumer of the code should always use the AddOrderLine method if he wants to add an OrderLine to the Order.
You can enforce this easily by not making the orderLines member public; easy enough.
However, at one time, the consumer of your code (or you :) ) will want to iterate through the OrderLines of the Order, or he will want to know how many OrderLines an Order contains.



Now, you'll have several options to achieve this:

One solution is to expose the orderLines collection to the public. In other words: create a public property which exposes the collection to the outside:

public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();

public List<OrderLine> OrderLines
{
get
{
return orderLines;
}
}
}

Although this is a solution, I do not like it.
Now, we're unable to force the user to use the AddOrderLine method. Since the OrderLines collection is now publicly exposed, it is now possible to just use the Add method of the List to add OrderLines to the Order.
The documentation of the classes could of course mention that you should always use the AddOrderLine method, but there's no real hard constraint here. (If the classes are used inappropriatly, the program could of course crash, so then there is a constraint after all. ;) However, then the programmer using those classes will maybe lose a lot of time to detect the error he made.

Another solution is to keep the OrderLines collection private, and to add some extra members to the Order class. In this way, uncontrolled access to the OrderLines is not possible.
However, there are offcourse disadvantages to this approach as well. First of all, you'll have to write some tedious code that just delegates the functionality to the Collection class, like this:

public class Order
{
private List<OrderLine> orderLines = ...

public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
orderLines.Add (ol);
}

public int NumberOfOrderLines
{
get
{
return orderLines.Count;
}
}
}
Unnecessary to say that this is just a boring task.
Then, to be able to iterate through the OrderLines of an Order, you could write code like this:
public class Order
{
private List<OrderLine> orderLines = ...

...

public OrderLine[] GetOrderLines()
{
return orderLines.ToArray();
}
}
Actually, I think this is plain ugly.

I always have to make a choice between these 2 approaches, where each one has his disadvantages. After doing this too much, I wanted a better solution for this problem, and after some reading and experimenting, I finally found one. It is in fact such a simple solution that I can't imagine why I haven't been using this one much earlier...

It's just nothing more then this:
Keep the collection private, create the necessary methods to provide the necessary controlled access to the collection (for instance the AddOrderLine method, and create a public property which returns a read-only instance of the collection, so that you can iterate through the collection, change existing instances of objects within the collection (at least, this is only true if you have reference types in the collection; you will not be allowed to modify value types on a ReadOnlyCollection, get the number of objects that are in the collection, ...

In code, it looks like this:

class Order
{
private List<OrderLine> orderLines =
new List<OrderLine>();

public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}

public ReadOnlyCollection<OrderLine> OrderLines
{
get
{
return orderLines.AsReadOnly();
}
}

}
This is the C# 2.0 version.
Now, it is impossible to add OrderLines in an uncontrolled way, but it is possible to iterate the OrderLines that are in the collection, and modify existing OrderLine objects that are already in the collection (since it are instances of a reference type; if OrderLine was a struct, it would not be possible to modify them.

In C# 1.x, it looks like this:
class Order
{
private IList orderLines = new ArrayList();

public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}

public IList OrderLines
{
get
{
return ArrayList.ReadOnly (orderLines);
}
}
}
Here, I return a readonly copy of the ArrayList. It is still possible to iterate and change the items that are in the collection, but it is impossible to Add an OrderLine to the Order in an uncontrolled fashion like this:
Order o = new Order();
OrderLine ol = new OrderLine();
o.OrderLines.Add (ol);

In this case, a System.NotSupportedException will be thrown.
It would of course be nicer to not have an Add and Remove method on the read-only ArrayList property, like we've achieved in the C# 2.0 version (the ReadOnlyCollection class does not have Add and Remove methods).
In .NET 1.x we can achieve that by letting the property return an ICollection instead of an IList, however, then we're not able to use an indexer to retrieve an OrderLine like this:
OrderLine ol = theOrder.OrderLines[i] as OrderLine;
Therefore, I prefer to return an IList instead of an ICollection

zondag 27 augustus 2006

1 month ago....

... I was still in Spain.
Surely, the weather was much better there then it is now here in Belgium and every evening, we could enjoy a beautiful sunset:


woensdag 23 augustus 2006

WinFX == .NET 3.0 ?

Scott Bellware makes an excellent point here, when he says that Microsoft should not rename WinFX to .NET 3.0.
I can only agree with him that it will be confusing to rename WinFx to .NET 3.0, since WinFx is another product.
WinFX is just an API, while .NET is a platform, so, why giving an API the name of a platform ?

Anyway, I'm not going to repeat all of it here, so, if you also think that Microsoft will be making a huge mistake here, you should sign the petition.

zaterdag 19 augustus 2006

DDD: A Quickstart - Part III

Introduction

This is the 3rd post of a serie of posts on Domain Driven Design and Test Driven Development.
In this article, I'll continue from where the previous article ended. This means that I'll continue with the implementation of the Domain Model that was defined in the first article of this serie.

In the previous article, we tackled the functionality of Customers placing Orders, and Gold Customers receiving a discount.
In this article, I'll focus on the behaviour for Bad Paying Customers and the creation of Invoices.



Bad Paying Customers

Now that we can determine whether a Customer is a 'Gold' Customer or not, we should build the functionality to determine whether a Customer is a bad payer or not.
Just like I've done for the 'Gold Customer' case, I'd like to kick off with writing a small unit-test.

A Customer is tagged as a bad paying Customer when 1/3 of his invoices are overdue/payed too late. The consequence of being a bad paying Customer is, that such a Customer cannot place Orders whose OrderTotal exceeds 250 euro. The test looks like this:

public void TestBadPayingCustomerPlacesOrder()
{
Customer c = custRep.GetCustomer (3);

// We 'know' that Customer 3 is a bad paying Customer.
// To check this, we can check the
// Status of the Customer.
Assert.AreEqual (CustomerStatus.BadPayer,
c.Status,
"This should be a bad paying Customer.");

// Get the Articles that we'll need.
Article art1 = artRep.GetArticle (1);
Article art2 = artRep.GetArticle (2);

// Check the prices of the articles, so that we
// do not make any false assumptiosn here.
Assert.AreEqual (15M, art1.Price,
"The 1st Article should have a unit-price of 15eur.");
Assert.AreEqual (12.5M, art2.Price,
"The 2nd Article should have a unit-price of 12.5eur.");

// Create the Order...
Order o = c.CreateNewOrder();

// and add the order-lines.
OrderLine ol1 = o.CreateNewOrderLine();
ol1.SetArticle (art1);
ol1.NumberOfItems = 10;

OrderLineAddQueryResult result = o.CanOrderLineBeAdded (ol1);

Assert.AreEqual (OrderLineAddQueryResult.Yes,
result,
"The 1st OrderLine should pose no problem.");

// At this time, the OrderTotal should be 150eur.
Assert.AreEqual (150M,
o.TotalAmountToPay,
"The OrderTotal should be 150eur.");

// Create a 2nd OrderLine which will cause
// the Order-limit to be exceeded.
OrderLine ol2 = o.CreateNewOrderLine();
ol2.SetArticle (art2);
ol2.NumberOfItems = 10;

result = o.CanOrderLineBeAdded (ol2);

Assert.AreEqual (
OrderLineAddQueryResult.NoBecauseOrderLimitExceeded,
result,
"Bad Paying Customers are not allowed to place orders > 250eur.");
}

Before we can make this test succeed, we must be able to compile it. This means that we'll have to extend the OrderLineAddQueryResult enumeration with the NoBecauseOrderLimitExceeded value. This is a simple task, and just a matter of adding this value to the enumeration type. This is in fact so simple, that I'm not going to elaborate on that here.
Once this is done, the test compiles, but it fails. This is due to the fact that we haven't implemented any functionality yet to determine whether a Customer is a bad paying Customer or not, so lets do that right away.
A Bad Paying Customer is a Customer that has at least one out of three overdue invoices. To be able to check this, we need to introduce a new entity: the Invoice.
Invoices are made for Orders that are shipped. An invoice contains a date when the Invoice has been created, and a due-date. Offcourse, it also contains the list of Articles that have been ordered and where the Customer has to pay for, and, it contains the amount that the Customer has to pay.
Since Invoices are created from Orders that have been shipped, we can start with this unit-test:

[Test]
public void CreateInvoice()
{
Order o = orderRepository.GetOrder (2);

Invoice inv = o.CreateInvoice();
}

This is rather simple! But simple is good.

As we can learn from the test, we'll need an Invoice entity, and we'll need to write a CreateInvoice method in the Order class.
Let's start off with the new entity. The Invoice class contains these members:

public class Invoice
{
private int id;
private DateTime invoiceDate;
private DateTime dueDate;
private Customer owningCustomer;
private InvoiceStatus status;
private bool isOverdue;
private DateTime datePayed;
private decimal subTotal;
private decimal discount;
private IList invoiceLines = new ArrayList();

public int Id
{
get
{
return id;
}
}

public DateTime InvoiceDate
{
get
{
return invoiceDate;
}
}

public DateTime DueDate
{
get
{
return dueDate;
}
set
{
dueDate = value;
}
}

public Customer OwningCustomer
{
get
{
return owningCustomer;
}
}

public InvoiceStatus Status
{
get
{
return status;
}
}

public bool IsOverdue
{
get
{
return isOverdue;
}
internal set
{
isOverdue = value;
}
}

public DateTime DatePayed
{
get
{
return datePayed;
}
}

public decimal SubTotal
{
get
{
return subTotal;
}
}

public decimal Discount
{
get
{
return discount;
}
}
}

An Invoice should not be created directly by the user of our Domain classes, so it's not necessary (and not favorable) to have a public constructor in this class. Therefore, I've decided to create an internal constructor. An Invoice is also created for one Order, so the constructor can take the Order for which the invoice is created, as an argument. Then, the constructor and it's implementation looks like this:

public class Invoice
{
...

internal Invoice( Order o, DateTime invoiceDate, DateTime dueDate )
{
id = -1;
this.invoiceDate = invoiceDate;
this.dueDate = dueDate;
owningCustomer = o.OwningCustomer;
status = InvoiceStatus.Open;
isOverdue = false;
subTotal = o.OrderTotal;
discount = o.Discount;

foreach( OrderLine ol in o.OrderLines )
{
AddInvoiceLine (ol.ArticleId,
ol.ArticleName,
ol.ArticlePrice,
ol.NumberOfItems);
}
}
}

The AddInvoiceLine method will add an InvoiceLine object to the invoiceLines collection for each OrderLine object that is contained by the Order:

private void AddInvoiceLine( int articleId, 
string articleName,
decimal articlePrice,
int numberOfItems )
{
invoiceLines.Add (
new InvoiceLine (this, articleId,
articleName, articlePrice, numberOfItems));
}

An InvoiceLine object contains information about the article for which the Customer has to pay, and, offcourse, it contains a pointer to the Invoice to which it belongs.

The CreateInvoice method should also be implemented. This is quite simple; in this method, we'll just use the internal constructor of the Invoice class, and we can also check if the Order has been shipped, since it is not allowed to create Invoices for Orders that haven't been shipped.

public class Order
{
...
public Invoice CreateInvoice()
{
if( this.Status != OrderStatus.Shipped )
{
throw new ApplicationException ("The order hasn't been shipped yet.");
}

return new Invoice (this, DateTime.Now, DateTime.Now.AddDays (30));
}
}

As you can see, the dueDate for the Invoice is calculated in this method. By default, the Customer has 30 days time to pay his invoice.
Since this logic is quite simple, it can safely be placed inside the CreateInvoice method. If the calculation of the due date would becomes more complicated, we could opt to factor this logic into a specification object.


Once the InvoiceLine class is implemented and the CreateInvoice method has been written, the tests compile. However, the first test still fails, because we still have no functionality written to determine if a Customer is a bad payer.
To do this, we can extend the Status property of the Customer class.

public CustomerStatus Status
{
get
{
CustomerStatus result = CustomerStatus.Normal;

// First get the amount of orders this
// customer has made in the last 3 months.
decimal orderTotal = DomainSettings.Instance.
RepositoryFactoryObj.
CreateOrderRepository().
GetOrderTotalForCustomerSinceDate (this,
(DateTime.Now.AddMonths (-3));

if( orderTotal > DomainSettings.GoldAmountTreshold )
{
result = CustomerStatus.Gold;
}

// Check if the Customer is a bad Payer,
// if the Customer is a Gold Customer AND a bad payer,
// the Status should be 'Bad Payer'.

// Get the number of invoices for this customer,
// and get the number of overdue invoices.
IInvoiceRepository invRep =
DomainSettings.Instance.
RepositoryFactoryObj.
CreateInvoiceRepository();

int numberOfOverdueInvoices =
invRep.GetNumberOfOverdueInvoicesForCustomer (this);

if( numberOfOverdueInvoices > 0 )
{
int totalInvoices = invRep.GetNumberOfInvoicesForCustomer (this);

if( numberOfOverdueInvoices / totalInvoices > 0.333 )
{
result = CustomerStatus.BadPayer;
}
}

return result;
}
}

In order to get this code to compile, the IInvoiceRepository interface and an implementation of it have to be created:

public interface IInvoiceRepository
{
int GetNumberOfOverdueInvoicesForCustomer( Customer c );
int GetNumberOfInvoicesForCustomer( Customer c );
}

I'll also create an InvoiceMemoryStoreRepository for the test-cases in the project that contains the unit-tests, and I'll extend the IRepositoryFactory interface (and the classes that implement this interface) with a method that creates the correct IInvoiceRepository.
Once that is done, the CanOrderLineBeAdded member method of the Order class needs to be extended, so that we check if a Customer is allowed to add the OrderLine to it's Order. This means that we'll have to adapt the Order class a bit.
First of all, I'd like to change the isGoldCustomer boolean member that acted as some kind of a helper variable, in order that we do not have to check the Status property of the OwningCustomer (because checking this status can be considered as expensive).
I want to do that because it is not sufficient any more to know if the 'owning customer' of the order, is a Gold Customer or not; now, I want to know whether the customer is a Normal Customer, a Gold Customer or a Bad Payer. Therefore, I removed the isGoldCustomer member variable, and changed it to a customerStatusHelper member, that is of type CustomerStatus.
I've put the code that has to keep track of the CustomerStatus into a separate method that looks like this:

private void DetermineCustomerStatus()
{
customerStatusHelper = owningCustomer.Status;
customerStatusDetermined = true;
}

Then, the CalculateDiscount() member method of the Order class has to be changed as well:

private void CalculateDiscount()
{
if( customerStatusDetermined == false )
{
DetermineCustomerStatus();
}

if( customerStatusHelper == CustomerStatus.Gold )
{
_discount = OrderTotal * 5 / 100;
}
else
{
_discount = 0M;
}
}

Although this is a rather small refactoring, thanks to our unit-test, we can be assured that our code is still correct.

Now, we can add a test to the CanOrderLineBeAdded member method to check whether the 'OrderLimit' is exceeded, when a Customer who's a Bad Payer, makes an Order:

public OrderLineAddQueryResult CanOrderLineBeAdded( OrderLine ol )
{
OrderLineAddQueryResult result = OrderLineAddQueryResult.Yes;

if( customerStatusDetermined == false )
{
DetermineCustomerStatus();
}

if( customerStatusHelper == CustomerStatus.BadPayer )
{
if( this.TotalAmountToPay + ol.LineTotal >
DomainSettings.BadPayerOrderLimit )
{
result = OrderLineAddQueryResult.NoBecauseOrderLimitExceeded;
}
}

// the other checks that were shown before, are now not shown any more.
...
...

return result;

}

The GetReasonWhyOrderLineCantBeAdded needs to be extended as well, but I'm not going to discuss that.

Creating Invoices

Until this time, we're still unable to create Invoices for Orders that are shipped. As discussed earlier, creating Invoices from shipped Orders would best be implemented as a Service.
This 'Domain Service' can then be used by a Windows Service, so that this task is done every night. The CreateInvoices service should use an IOrderRepository to get all the Orders that are shipped, and for which no Invoice has been created yet.
For those Orders, the service should create Invoices. Given the text, the service should look like this:

public static class CreateInvoicesService
{
public static void CreateInvoices()
{
IOrderRepository ordersRep =
DomainSettings.RepositoryFactoryObj.CreateOrderRepository();
IInvoiceRepository invoiceRep =
DomainSettings.RepositoryFactoryObj.CreateInvoiceRepository();

IList orders = ordersRep.FindShippedOrdersWithoutInvoice();

foreach( Order o in orders )
{
Invoice inv = o.CreateInvoice();
invoiceRep.Save (inv);
}
}
}

This is pretty straightforward. However, there is something that we haven't considered yet: transaction handling/boundaries.
The Repository itself should not be responsible for starting and committing or rollbacking transactions.
The Repository is not aware of the context of the transaction, so therefore, it is not the task of the repository to start and commit a transaction. Only the client knows when to start and commit a transaction, so it's his responsability. In this case, it's the CreateInvoicesService service that is responsible for starting and committing (or rollbacking) the transaction.
But since we haven't focused on persistence yet, we'll just keep this in the back of our mind.
Now that we're able to create Invoices, we're still not able to determine whether an Invoice is overdue or not. This batch-process should also be implemented as a service.
An IInvoiceRepository should be used that gives us all Invoices that haven't been payed yet.
Then, each Invoice that has been returned can be checked by a Specification class if the Invoice is overdue or not. If the Invoice is overdue, the IsOverdue flag can be set, and the Invoice can be persisted. If the logic to determine whether the Invoice is overdue or not would be complicated, and depend on several other factors (like, for instance the CustomerStatus of the Customer), then this would be a good solution.
In this case however, things are quite simple: an Invoice is overdue if it is past its duedate (however, we could allow a retention period of say 4 days). This means that marking all Invoices that are past their due date as overdue, could be done be just one query. In other words, the MarkOverdueInvoices Service could be as simple as just calling a method on the IInvoiceRepository:

public static class MarkOverdueInvoicesService
{
public static void MarkOverdueInvoices()
{
IInvoiceRepository invRep =
DomainSettings.RepositoryFactoryObj.CreateInvoiceRepository();

invRep.MarkOverdueInvoices();
}
}

So, I think that's about it. I think we've discussed the most important part of the Domain. Now, we can concentrate on the persistance of the objects in a relation database. For more information about test driven development, you can read these guidelines, written by Jeffrey Palermo.

That's it for now. In the next article (if it ever comes, since I still have to start writing...) of this series, I'll try to tackle the persistence functionality using NHibernate.

maandag 7 augustus 2006

New photography toy!

I was planning to blog about this, but I constantly postponed it until I almost forgot about it. And how could I forget this...

After years of using only a film-camera, I've finally decided to go digital. Well, I'm not really telling the truth here, since I already own a digital compact camera since 2003.
Anyway, I finally decided to buy a digital SLR camera. Since I already have a Nikon F55 film SLR, the choice was easy: my digital SLR would also be a Nikon.
After reading some reviews and testing different models, I've made a decision on the model as well: it would be a Nikon D200.

I've ordered the camera in May 2006, and I got it in my hands in the first week of July 2006.

I have the D200 now for about one month, and it is a great camera. I've done about 1000 shots in past month, and although I've already achieved some satisfying results, I feel that I haven't unleashed its full potential yet.
I hope that I'll be able to show the progress I make by regularly posting a photo here on my blog.

Here you see my F55 film camera next to the D200. Quite a difference in size!
Image taken with a compact digital camera



zondag 6 augustus 2006

This is the end....

... of my holiday.
The last day of my holiday is coming to an end. This means that, as from tomorrow, I'll be spending most of my time behind my workstation at work, developing in .NET.

I've had a nice 3 weeks of vacation wich have passed very fast, which is a good sign: it means that I haven't bored myself. :)
My girlfriend and I made a trip to Spain, continued our search for a suitable and affordable piece of terrain where we could build a house. Hopefully, that quest will come to an end soon, because it becomes quite exhausting.

Next to that, I've read some books, and I've continued reading Applying Domain Driven Design and Patterns, and so far, I find it a very good book.

I've also spent some time preparing for a session about Unit Testing and Test Driven Design wich I'm planning to do in the near future at work.
I hope to finish those preparations very soon, so that I can concentrate on the next episode of my blog article regarding Domain Driven Design.

I think I can say that the past 3 weeks were well spent, and I'll be ready to get back to work with a fresh, relaxed mind.

maandag 31 juli 2006

Back in Belgium

I'm back from vacation.

I've spent the past few days in Southern Spain, in the province of Andalucia.
We stayed in a hotel nearby the 'La Barossa' beach, from where we've explored the nearby region a bit.



This picture has been taken in Medina Sidonia. A 'pueblo blanco' (white town), a few km's more inward the country.

dinsdag 18 juli 2006

DDD: A Quickstart - Implementation using Test Driven Development

Introduction


In the first part of this serie, I've focussed on the modelling of the domain-model.
Now, since the entities, aggregates, etc... have been roughly defined in the first part, I think it's time to dive into the code.
Instead of directly writing classes to model the domain, I want to develop the domain layer using the Test Driven Development Mantra.
This means that I should start with writing a test case before I actually write the 'functional' code.
I will develop the domain using C#, so I'll use the NUnit Framework to create the unit tests.

A first use case


It would be helpfull to have some kind of a use case, so that I can start with writing a test for that use case.
I can start off with the use case that can be found in part 1 of this article:
A customer places an order.
By writing a unit test for this scenario first, I'm forced to think about the class interfaces. Writing the test first, will help me in creating a clear, simple and easy to use class interface and class hierarchy since I will design the classes in the way that I want to use them.
The Unit Test for the use case 'Customer places an Order' looks like this:

[TestFixture]
public class OrderTestCase
{
[Test]
public void TestNormalCustomerPlacesOrder()
{
ICustomerRepository custRep =
new CustomerMemoryStoreRepository();
IArticleRepository artRep =
new ArticleMemoryStoreRepository();

// Get a customer from the repository. We should
// make sure that this customer is a normal customer.
Customer c = custRep.GetCustomer (1);

// Create a new order for the Customer.
Order o = c.CreateNewOrder ();

// The Customer orders some articles...
Article art1 = artRep.GetArticle (1);
Article art2 = artRep.GetArticle (2);
OrderLine ol1 = o.CreateNewOrderLine();

ol1.NumberOfItems = 2;
ol1.SetArticle (art1);

o.AddOrderLine (ol1);
OrderLine ol2 = o.CreateNewOrderLine();

ol2.NumberOfItems = 5;
ol2.SetArticle (art2);

o.AddOrderLine (ol2);

// Now, the order-total should be equal
// to ( 2 * price of art1 ) + ( 5 * price of art2 )
Assert.AreEqual ((2 * art1.Price ) + ( 5 * art2.Price ),
o.OrderTotal );

}
}

This first test is pretty simple. It shows how a Customer can place an Order, and it checks if the OrderTotal of the Order is correct.
While this test case is very simple, you can already discover the advantage of Test Driven Development: by writing a Test first, I've already defined the API, the interface of the domain classes. This will make sure that the interface of those classes will be clear and intention revealing.
By writing the test first, you're forced to design the classes in a way where it's easy to use them. I think the above test is quite self-describing and doesn't need any further explanation.

Now, we should make the test pass. However, I know that good TDD practice preaches that you should make sure that the test fails first meaningfully. This is actually a quite important step, because it enables you to make sure that you know that what you're testing is correct. However, to keep this article a little bit more concise, I've allowed myself to skip this step.
At this point the test fails offcourse since we haven't written any functional code yet, so lets start with that right away.

The first thing that you encounter, is the ICustomerRepository and the IArticleRepository interface, along with the CustomerMemoryStoreRepository and ArticleMemoryRepository classes which implement these interfaces.
For this test, it is sufficient that the ICustomerRepository and the IArticleRepository interfaces look like this:


public interface ICustomerRepository
{
Customer GetCustomer( int customerId );
}

public interface IArticleRepository
{
Article GetArticle( int articleId );
}

The CustomerMemoryStoreRepository and the ArticleMemoryStoreRepository class are not a part of the Domain Layer. These classes are 'mock objects' that live in the assembly that contains the test cases. They do not really access a database, they just hold some objects in memory.

Then, let's get along with the Customer class. To be able to make the above test pass, it would be sufficient that the Customer class only has a CreateNewOrder() method at this point. However, we also want to identify a customer (it is an entity), so let's give it a name and an id as well.
This should be the implementation of the Customer class:


public class Customer
{
private int _id;
private string _name;

public Customer() : this(-1, string.Empty)
{
}

public Customer( int id, string name )
{
_id = id;
_name = name;
}

public int Id
{
get
{
return _id;
}
}

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public Order CreateNewOrder()
{
return new Order(this);
}
}

The CreateNewOrder method creates a new Order, and passes the current Customer object as an argument. This is necessary so that we know to which Customer an Order belongs.
Since we have a reference to the Order class in the Customer class, lets continue with writing the Order class.

Since an Order is also an entity, we will want to uniquely identify an Order as well, so we're going to add an Id to this class as well. Next to that, we also want to know when the Order was made, so let's give the Order class an OrderDate member as well.


public class Order
{
private int _id;
private DateTime _orderDate;
private Customer _customer;
private IList _orderLines = new ArrayList();

public Order( Customer c ) : this(-1, c, DateTime.Now)
{
}

public Order( int id, Customer c, DateTime orderDate )
{
_id = id;
_customer = c;
_orderDate = orderDate;
}

public int Id
{
get
{
return _id;
}
}

public DateTime OrderDate
{
get
{
return _orderDate;
}
}

public Customer OwningCustomer
{
get
{
return _customer;
}
}

public IList OrderLines
{
get
{
return _orderLines;
}
}

public decimal OrderTotal
{
get
{
decimal total = 0.0M;

foreach( OrderLine ol in _orderLines )
{
total += ol.NumberOfItems * ol.ArticlePrice;
}

return total;
}
}

public OrderLine CreateNewOrderLine()
{
return new OrderLine (this);
}

public void AddOrderLine( OrderLine ol )
{
// Perform some checks before adding.
if( ol.IsArticleSet == false )
{
throw new ApplicationException ("You must specify an Article.");
}

if( ol.NumberOfItems == 0 )
{
throw new ApplicationException ("You must order at least one item.");
}

_orderLines.Add (ol);
}
}

Note that I've used an IList for the _orderLines member, and not the generic IList<T> type which would provide strong typing. The reason why I did this, is that I'm planning to use NHibernate as an O/R mapper between the domain classes and the relational database, and the version of NHibernate that I have now does not support generics yet.
I think the Order class is pretty straightforward and doesn't need any further explanation. I think the code is pretty self-explaining.


The OrderLine and Article class haven't been created yet, so let's do that right now.


public class OrderLine
{
private int _id;
private int _articleId;
private string _articleName;
private decimal _articlePrice;
private int _numberOfItems;
private Order _owningOrder;
private bool _isArticleSet;

public int Id
{
}

internal bool IsArticleSet
{
get
{
return _isArticleSet;
}
}

public int ArticleId
{
get
{
return _articleId;
}
}

public string ArticleName
{
get
{
return _articleName;
}
}

public decimal ArticlePrice
{
get
{
return _articlePrice;
}
}

public int NumberOfItems
{
get
{
return _numberOfItems;
}
set
{
_numberOfItems = value;
}
}

public Order OwningOrder
{
get
{
return _owningOrder;
}
}

public OrderLine( Order owningOrder )
{
_owningOrder = owningOrder;
id = -1;
_articlePrice = 0.0M;
_articleName = string.Empty;
_numberOfItems = 0;
}


public void SetArticle( Article art )
{
if( art != null )
{
_articleId = art.Id;
_articleName = art.Name;
_articlePrice = art.Price;
_isArticleSet = true;
}
else
{
_articleId = -1;
_articleName = string.Empty;
_articlePrice = 0.0M;
_isArticleSet = false;
}
}
}

I've decided to not put a reference to an Article object in the OrderLine class, because, when an existing Order is retrieved, the price of the Article can already have changed opposed to the price of the Article at the time the Order was made. So, we're not interested in the current price of the Article, but in the price of the Article at the time when the Customer has ordered that Article.
Therefore, I've decided to just put the Article information that is of interest into the OrderLine class, and make that information available through read-only properties. I've created a SetArticle method so that all the required Article information can be set at once, and in this way, we're sure that the Article information in the OrderLine class is correct.
The IsArticleSet flag is marked internal since it is of no use outside our domain-assembly. At this time, only the Order class uses it to check if the OrderLine that's being added has an Article set.

The Article class is very simple:


public class Article
{
private int _id;
private string _name;
private decimal _price;

public int Id
{
get
{
return _id;
}
}

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public decimal Price
{
get
{
return _price;
}
set
{
_price = value;
}
}

public Article() : this(-1, string.Empty, 0.0M)
{
}

public Article( int id, string name, decimal price )
{
_id = id;
_name = name;
_price = price;
}
}

This code now makes sure that the test we've written passes. However, as I've said earlier, if I was to follow the TDD rules strictly, I should write code first that makes the test fail meaningfully. I've chosen to skip this step though, so that I can make this post a little bit shorter.


At this stage, we have the functionality that we need for a Customer to place Orders. However, we're not there yet. The code we have now, does not take 'Gold Customers' and 'Bad Paying Customers' into account.


Test Case for Gold Customers


To implement the functionality which gives Gold Customers a discount, I'll start of with writing a Unit Test again. This test is pretty similar as the previous one, but you should note that the Repositories are now promoted to be member variables of the class that contains the test-methods.

This is how the test looks like:


[Test]
public void TestGoldCustomerPlacesOrder()
{
// 2 is a gold-customer..
Customer c = custRep.GetCustomer (2);

// To be sure of it, test it...
Assert.IsTrue (c.Status == CustomerStatus.Gold,
"We should have a gold customer.");

Order o = c.CreateNewOrder ();

Article art1 = artRep.GetArticle (1);
Article art2 = artRep.GetArticle (2);

OrderLine ol1 = o.CreateNewOrderLine ();

ol1.SetArticle (art1);
ol1.NumberOfItems = 10;

o.AddOrderLine (ol1);

OrderLine ol2 = o.CreateNewOrderLine ();

ol2.SetArticle (art2);
ol2.NumberOfItems = 7;

o.AddOrderLine (ol2);

// The expected OrderTotal is the 'regular'
// OrderTotal minus a 5% discount.
decimal expected = ( 10 * art1.Price ) + ( 7 * art2.Price );
expected -= ( expected * 5 ) / 100;

Assert.AreEqual (expected, o.TotalAmountToPay);
}

In the above test, I retrieve the Customer with Id 2 out of the Repository. I assume that this is a Gold Customer, and, to be sure, the Status property of the Customer is tested.
Note that I haven't defined this property in the first version of the Customer class since there was no need for that property in the first unit test. This means that I'll have to extend the Customer class with a Status property.
This Status property will return an enumeration-type which indicates whether this Customer is a 'Normal', 'Gold' or 'Bad Paying' Customer. This also implies that I'll have to create that enumeration type.


Let's start of with creating the enumeration type, and adding the Status property to the Customer class. The enumeration is very simple:


public enum CustomerStatus
{
Normal,
Gold,
BadPayer
}

Determining the correct Status of a Customer requires a bit more work. First of all, we need to determine whether the Customer is a 'Gold Customer'. We'll have to do this by getting the total amount of money that a Customer has spent on Orders in the last 3 months. Since the Customer class does not contain a collection of the Orders that he made, we'll have to ask a Repository what that amount is for a specific Customer.
If this amount is over 2500 euro, the Customer is a Gold Customer.
To determine whether the Customer is a bad payer, we should do something similar; we'll need to know how many of his invoices have been or are overdue. To be able to know this, we'll also have to call in a repository.
In a first case, I'll concentrate on determining whether a Customer is a Gold Customer or not.

To do this, I'll have to extend the Customer class with the Status property:


public class Customer
{
...

public CustomerStatus Status
{
get
{
CustomerStatus result = CustomerStatus.Normal;

// First get the amount of orders this
// customer has made in the last 3 months.
decimal orderTotal = DomainSettings.Instance.
RepositoryFactoryObj.
CreateOrderRepository().
GetOrderTotalForCustomerSinceDate (this,
(DateTime.Now.AddMonths (-3));

if( orderTotal > DomainSettings.GoldAmountTreshold )
{
result = CustomerStatus.Gold;
}

return result;
}
}

...

}

This code is pretty straightforward I guess. First of all, we assume that the Customer is a Normal Customer. Then, I 'ask' the OrderRepository to give me the total amount for which the given Customer has placed Orders in the last 3 months. If this value is higher then a specific value, we can say that this Customer is a Gold Customer.

To be able to compile and run this code, we'll need some additional classes.
The first new class that springs into view, is the DomainSettings class, which appears to be a singleton.
I've decided to create this class to get easy access to things like repositories, and usefull constants, like the GoldAmountTreshold property. The DomainSettings class looks like this:


public class DomainSettings
{
private static DomainSettings _instance;

public static DomainSettings Instance
{
get
{
if( _instance == null )
{
_instance = new DomainSettings();
}
return _instance;
}
}

public const decimal GoldAmountTreshold = 2500;

private IRepositoryFactory _repositoryFactoryObj;

public IRepositoryFactory RepositoryFactoryObj
{
get
{
if( _repositoryFactoryObj == null )
{
object repType = Configuration.
ConfigurationManager.
AppSettings["repositorytype"];

if( repType != null )
{
switch( repType.ToString().Trim().ToLower() )
{
case "testrepositories" :
// Since I've put the 'test/mock'
// repositories in another assembly,
// along with my unit-tests,
// use reflection to load
// the correct assembly and type.
Assembly asm = Assembly.LoadFrom ("BlogShopTests.dll");
object o = asm.CreateInstance
("BlogShopTests.RepositoryMocks.TestRepositoryFactory",
true);
_repositoryFactoryObj = o as IRepositoryFactoryObj;
break;
default :
throw new ConfigurationErrorsException (
"Unknown repository-type: " + repType.ToString());
}
}
else
{
throw new ConfigurationErrorsException (
"Repository type must be defined in the app-settings.");
}
}
return _repositoryFactoryObj;
}
}
}

As you can see, I've implemented the DomainSettings class as a singleton, so that there can only be one instance of that class. The most interesting thing in this class is without any doubt the RepositoryFactoryObj property.
Since the unit-tests I've written so far are using 'mock' repositories instead of the real ones that will be used eventually, I wanted to be able to hide which implementation of the Repository that should be used. This is extremely usefull in the Status property of the Customer class. When running the tests, the 'mock' repositories should be used, and, when running the code 'for real', the 'real' Repository should be used.
To be able to do this, I've provided the DomainSettings class with a property that returns a Factory that will create the correct repository objects.
The concrete type of the factory that must be used, depends on a value in the configuration file.
Since the TestRepositoryFactory and the TestRepositories are in the assembly that contains all the unit-tests, and since I do not want to create a dependency between the assembly that contains the Domain classes, and the assembly that contains the test classes, I use reflection to create the TestRepositoryFactory. The Abstract Factory approach for the creation of the Repositories, allows me to hide which specific factory is to be used. In other words: the user -in this case the Customer class- is left unaware of which specific Factory that's being used and therefore, which specific repository that will be used.


To be able to create and use the abstract factory, an interface is needed which describes the methods that a 'RepositoryFactory' object must have. In our client code, we can then talk to that interface without knowing which specific implementation is being used.
At this moment, it is sufficient if the interface looks like this:


public interface IRepositoryFactory
{
ICustomerRepository CreateCustomerRepository();
IOrderRepository CreateOrderRepository();
IArticleRepository CreateArticleRepository();
}

Once this is done, the implementation of the TestRepositoryFactory is fairly easy; it just instantiates and returns an instance of the correct Repository:


public TestRepositoryFactory : IRepositoryFactory
{
public ICustomerRepository CreateCustomerRepository()
{
return new CustomerMemoryStoreRepository();
}

public IOrderRepository CreateOrderRepository()
{
return new OrderMemoryStoreRepository();
}

public IArticleRepository CreateArticleRepository()
{
return new ArticleMemoryStoreRepository();
}
}

In the DomainSettings class, it is now just a matter of creating the correct IRepositoryFactory object depending on the value that's found in the configuration file.
Once the GetOrderTotalForCustomerSinceDate is implemented, we're able to determine if the Customer is a Gold Customer or not. The implementation of the GetOrderTotalForCustomerSinceDate method of the OrderMemoryStoreRepository could be as simple as just returning true if the given Customer has a specific Id, and otherwise false.


We still need to make our test pass, because at this point, we haven't written any functionality yet to calculate the OrderTotal for Gold Customers. As we learn from the user story, Gold Customers receive a discount of 5% on their OrderTotal.
It is usefull to extend the Order class with a member variable that holds the discount. For Normal Customers, this discount variable will just contain 0.
I think that the best place calculate the discount -if there's one-, is in the AddOrderLine method of the Order class. Each time an OrderLine is added to an Order, the amount of the discount for a Gold Customer changes.
There's also something else were I haven't paid attention to yet: once an Order is confirmed by the Customer, it should not be possible to add or remove OrderLines from it.
It is however possible that a user cancels the complete Order. This pops up another issue: an Order should have an OrderStatus.

Let's start with one thing at a time, and begin with the calculation of the discount.
As I've said earlier, the best place to calculate this discount is in the AddOrderLine member method of the Order class. Thus, we can extend this method so that it looks like this:


public void AddOrderLine( OrderLine ol )
{
// Perform some checks here to see if all necessary stuff is provided.
if( ol.NumberOfItems == 0 )
{
throw new ApplicationException ("You must at least order 1 item.");
}

if( ol.IsArticleSet == false )
{
throw new ApplicationException (
"You must specify the article that must be ordered.");
}

this.OrderLines.Add (ol);

// Calculate the discount.
CalculateDiscount ();
}

In the CalculateDiscount method, we'll just have to check the Status property of the Customer to which the Order belongs to. However, since determining the Status of a Customer is a rather expensive operation (remember that we have to query the database to determine the status) and the fact that the AddOrderLine method is likely to be called more then one time (an Order can contain more then one OrderLine), it would be better to call this Status property only one time. We can do this like this:


private bool customerStatusDetermined = false;
private bool isGoldCustomer = false;

private void CalculateDiscount()
{
if( customerStatusDetermined == false )
{
if( _owningCustomer != null )
{
isGoldCustomer = _owningCustomer.Status == CustomerStatus.Gold;
}
else
{
isGoldCustomer = false;
}
customerStatusDetermined = true;
}

if( isGoldCustomer )
{
_discount = this.OrderTotal *
DomainSettings.Instance.GoldDiscountPercentage;
}
else
{
_discount = 0M;
}
}

I think that this code is pretty easy as well: the CustomerStatus is determined if this hasn't been done yet, and if the Customer is a Gold Customer, we calculate the discount on the Order.
Note that we'll have to add an extra property to the DomainSettings class as well, that contains the percentage of the discount for Gold Customers:


public class DomainSettings
{
...
private decimal _discountPercentageForGoldCustomers = 0.05M;

public decimal GoldDiscountPercentage
{
get
{
return _discountPercentageForGoldCustomers;
}
}
}

You should also note that the Test that has been written to test the 'Gold Customer' case, checks the TotalAmountToPay property of the Order class. This property is quite simple, and just returns the OrderTotal minus the Discount


public class Order
{
...
public decimal TotalAmountToPay
{
get
{
return OrderTotal - Discount;
}
}
...
}

This functionality makes our 2nd test pass. Now, I've already talked about the fact that an Order should have a Status. This means that, when a Customer confirms his Order, the Order should have the status 'Confirmed'. Orders that have been confirmed cannot be changed anymore. This means that it should be impossible to add or remove OrderLines from it. However, a Customer should be able to cancel a confirmed Order. However, once the Order is shipped, it cannot be cancelled anymore.
Knowing all this, we can say that an Order can have 3 states:


  • Confirmed

  • Cancelled

  • Shipped

However, this is not sufficient. When an Order is just created, it cannot have the Confirmed, Cancelled or Shipped status. As long as the Order has not been confirmed yet, it has the status 'Pending'. This means that we have in fact 4 OrderStates:

  • Confirmed

  • Cancelled

  • Shipped

  • Pending

We can now start to create some unit-tests in where we will test this behaviour. First of all, we'll create a unit-test in where we create an Order, add some OrderLines to the Order and confirm the Order. Afterwards, we'll check if we can still add or remove OrderLines from that Order.


[Test]
public void TestConfirmOrder()
{
Customer c = custRep.GetCustomer (1);

Article art1 = artRep.GetArticle (1);
Article art2 = artRep.GetArticle (2);

Order o = c.CreateNewOrder();

OrderLine ol1 = o.CreateNewOrderLine();
ol1.SetArticle (art1);
ol1.NumberOfItems = 5;
o.AddOrderLine (ol1);

o.ConfirmOrder();

Assert.AreEqual (OrderStatus.Confirmed,
o.Status,
"The OrderStatus should be confirmed.");

// Now, the Order is confirmed, so it should not be
// possible to still add or remove
// OrderLines
OrderLine ol2 = o.CreateNewOrderLine ();
ol2.SetArticle (art2);
ol2.NumberOfItems = 10;

Assert.AreEqual (o.CanOrderLineBeAdded (ol2),
OrderLineAddQueryResult.NoBecauseOrderIsConfirmed,
"It should not be possible to add an orderline now.");

// Now, just check what if the Status is correct if we cancel the order.
o.CancelOrder ();

Assert.AreEqual (OrderStatus.Cancelled,
o.Status,
"The OrderStatus should be cancelled.");
}

To be able to compile this test, we'll have to add some methods to our classes.
The first one that we encounter, is the ConfirmOrder method, which sets the status of our Order to Confirmed. This means that we'll have to add a property Status to the Order class and create an enumerated type OrderStatus.
A little bit further, you can also see the CancelOrder method that sets the Status of the Order to Cancelled.
Adding these methods is a piece of cake:


public class Order
{
...
private OrderStatus _status = OrderStatus.Pending;

...

public OrderStatus Status
{
get
{
return _status;
}
}

...

public void ConfirmOrder()
{
if( _status != OrderStatus.Pending )
{
throw new ApplicationException (
"An order can only be confirmed when " +
"it's current status is Pending.");
}
_status = OrderStatus.Confirm;
}

public void CancelOrder()
{
if( _status == OrderStatus.Shipped )
{
throw new ApplicationException (
"An order that is shipped, " +
"cannot be cancelled.");
}
_status = OrderStatus.Cancelled;
}

}

As you can see, these methods contain some simple business-logic: a check is made if the Order can be confirmed, or can be cancelled.
Now, I still need to implement the OrderStatus enumeration:


public enum OrderStatus
{
Pending,
Confirmed,
Cancelled,
Shipped
}

The above code is pretty obvious, but, it is not sufficient yet in order to make the test pass, or even compile. As you can see in the test, another method is needed: CanOrderLineBeAdded. As you can see in the test, this method does not return a simple boolean value, even though the name of the method let's you think that it would. The reason why I choose to not return a boolean here, is very simple: instead of just knowing whether it is possible or not to add an OrderLine to an Order, I also want to know why it would not be possible. Therefore, I've choosen to return an enumeration, which has these possible values:


public enum OrderLineAddQueryResult
{
Yes,
NoBecauseOrderIsConfirmed,
NoBecauseOrderIsCancelled,
NoBecauseOrderIsShipped
}

Then, the CanOrderLineBeAdded member method of the Order class looks like this:


public OrderLineQueryResult CanOrderLineBeAdded( OrderLine ol )
{
OrderLineAddQueryResult result = OrderLineAddQueryResult.Yes;

switch( this.Status )
{
case OrderStatus.Confirmed :
result = OrderLineAddQueryResult.
NoBecauseOrderIsConfirmed;
break;

case OrderStatus.Cancelled :
result = OrderLineAddQueryResult.
NoBecauseOrderIsCancelled;
break;

case OrderStatus.Shipped :
result = OrderLineAddQueryResult.
NoBecauseOrderIsShipped;
break;
}

return result;
}

The purpose of this method is that the user of our class can check if an OrderLine can be added, without exceptions being thrown. This method is usefull to know for instance which UI controls should be enabled/disabled, or what message should be given to the user of the software.
But, the existance of this method does not ensures us that this method shall be used as well, therefore, we should enforce this rule in the AddOrderLine method of the Order class as well.


public void AddOrderLine( OrderLine ol )
{
OrderLineQueryAddResult result = this.CanOrderLineBeAdded (ol);
if( result != OrderLineQueryAddResult.Yes )
{
throw new ApplicationException(
GetReasonWhyOrderLineCantBeAdded (result));
}

if( ol.NumberOfItems == 0 )
{
throw new ApplicationException ("You must at least order 1 item.");
}

if( ol.IsArticleSet == false )
{
throw new ApplicationException (
"You must specify the article that must be ordered.");
}

this.OrderLines.Add (ol);

CalculateDiscount();
}

I will not elaborate here on the GetReasonWhyOrderLineCantBeAdded member method very much. It is sufficient if you know that this is just a private method which returns an error-message, depending on the value of the OrderLineQueryAddResult parameter.
The business rules that are in the AddOrderLine method can offcourse be avoided by simply not using the AddOrderLine method at all, and instead, using the Add method of the OrderLines property which is publicly exposed. Therefore, it should be better if we do not expose the Order collection publicly, but, for now, I'll leave it like it is.

As this is one very long post already, I'll end it right here. I'll keep the rest of the implementation for another article.
Please, feel free to post your comments, critics, questions, remarks.

woensdag 24 mei 2006

Domain Driven Design: A Quickstart (Part 1)

Introduction


Some time ago, I bought the book Domain Driven Design, tackling complexity in the heart of software.
Since reading it, I became very interested in the Domain Driven Design paradigm. For enterprise applications, it would be ideal if you could express the core of the application (the domain layer; the part of the program that contains the business logic) in a good model.
The Object Oriented Programming paradigm provides a good way to express the model in a computer program.

So, although the behaviour can be expressed in an OO fashion, the data needs to be persisted as well. In most cases, a relational database is used to persist the data. Combining OO and RDBMS'es gives us the problem of the Object / Relational mismatch. You can offcourse solve this object-relational impedance mismatch yourself by writing a DAL that nicely maps the classes of your domain model to the tables of your relational database. In most cases, this means that you'll have to write a lot of code. Instead of implementing this functionality yourself, you could also opt for using one of the many existing O/R mapping tools, like NHibernate or LLBLGen.
As Frans Bouma once explained in one of his blogposts, there are different types of O/R mappers. NHibernate fits in another category then LLBLGen; In Frans' categorization, NHibernate fits in the 'Domain Approach', while LLBLGen fits in the Entity approach.
Since I'm interested in the Domain Driven approach, I've taken a look at NHibernate, and, while it's not 100% perfect, it still has a lot of advantages. It releases you from some boring tasks (like mapping - hey, that's why it's called an O/R mapper), and takes care of some more complex tasks (caching, state-tracking, ...).

The idea of this blogpost is to provide a little quickstart in Domain Driven Design and NHibernate, by creating a piece of software for a particular use case.

The Case


My idea was to create a simple application for a shop/manufacturer. A customer can order multiple goods at a time, and, when a customer has ordered for over 2500euro in the past 3 months, this customer is a gold customer.
When the order is shipped, an invoice has to be created for that Order. Gold Customers receive a discount of 5% on their invoice. On the other hand, customers that are known as 'bad payers', cannot place orders that have an order total that exceeds 250 euro. A customer is tagged as a 'bad paying customer', when 1/3rd of his invoices have been overdue.
Let’s say that a customer can make an order by phone, and via the website of the shop.
Pretty simple, no ? :) This is off-course not a real-world example, but it should be sufficient for the purpose of this article.

Modelling the domain


Following the Domain Driven Design principle, a model consists of entities, value objects and services. We can already extract some entities out of the given text:

  • Customer

  • Article

  • Order

  • Invoice


Another entity that is not so obvious, is the OrderLine entity. This one is needed because a Customer can order more then one article at a time, so we need to know which Articles have been ordered, and how many of them are ordered.
For the Invoice entity, it's the same story: there must be an InvoiceLine entity that represents each 'line' on the invoice.
This means that, at this time, our model consists of 6 entities. There are no Value objects and Services defined yet.

The entities that we've defined can be drawn in a first schema:

As you can see, a customer can have 0, 1 or more Orders, an Order contains one or more OrderLines, and every OrderLine must contain exactly one Article.
For each Order, there can be one Invoice.
If this were a database schema, this would be perfect. However, this is an (concise) UML diagram, and the classes in this diagram should not describe how our data must be persisted, but how our application should behave.

Now, there are some things in this ‘design’ that can be improved. If you look at the Customer and Order classes in the schema, you see that a Customer has a collection of Orders. This is in fact correct, but, I wonder if this is necessary to express in our domain-model.
In this case, we’re more interested in knowing to which Customer a specific Order belongs, rather then knowing or getting all the Orders of a specific Customer. To get a list of all the Orders of a specific Customer, we can always add a method in a Repository that gives us the list of Orders for a Customer, instead of giving the Customer class a collection of Orders. (I will come back on the Repository part later). This will simplify things a bit. This also means that, if we have customers that have made a lot of Orders, the Customer Object for that Customer doesn’t have to hold a large collection of Order objects.
For the relationship between the Order and OrderLine class, things are a bit different. I do not think we can give a direction to this relationship, since, we do want to know the OrderLines of an Order, since they are coupled to each other: an Order exists only because of its OrderLines. And for each OrderLine, we do want to know to which Order it belongs. So, this association has to be kept bidirectional.
Then again, the relationship between Order and Invoice, doesn't have to be bidirectional. I do not even know if we should have a 'coded' relationship between these 2 entities, because I don't think that it will often occur that we need to see the invoice that is linked to an order, or, the related order of an invoice. If we do need that, we can always get them by calling a method on the repository. However, I will keep the link between Order and Invoice on the schema, since, they're in a way linked to each other.

This gives us the following schema:



In this schema, you can see the directions of the associations.

The next step, is to define the aggregates in the model. An aggregate ‘clusters’ the entities and value objects that belong together.
In this case, we can define 4 aggregates: Customer, Order, Invoice and Product.
The Customer and Product aggregate only contain 1 entity, while the Order aggregate and the Invoice aggregate contains 2 entities; the Order and the OrderLine entity make up the Order aggregate, and the Order entity is the ‘aggregate root’. The aggregate root is the only object in the aggregate, where other objects that are outside of that aggregate, may have references to.
The Invoice aggregate is very similar: it's made up by the Invoice and the InvoiceLine entity, and the Invoice entity is the aggregate root.

Once we know the aggregates, we can define the repositories for our domain model. A repository is an abstraction which gives us references to our aggregates, and allows us to persist those aggregates. The underlying infrastructure can be a relational database, a file, … but our model doesn’t need to know that. We just have to be able to get aggregates, and save them back, so the repository provides us this abstraction.
We should not create a repository for every class in our model, we should create a repository per aggregate. In our example, it makes no sense to be able to retrieve OrderLine objects, without retrieving the corresponding Order object.
Knowing all this, we can extend our schema:



Here, you can see the 4 repositories (I've added some example operations to it), and the 4 aggregates. I've also drawn the aggregate boundaries of the Order and the Invoice aggregate. Since the other 2 aggregates (Customer and Product) only consist out of 1 entity, it is not necessary to draw their boundaries as well.

There is one thing that we'll need to keep in the back of our mind: we have to be able to create Invoices for Orders that are shipped and that have no Invoice yet. It would be a good idea to create a batch-process that runs every night, and that creates Invoices for Orders that are shippend and have no invoice yet. In other words: this would be ideally implemented as a service.

Now that we have identified the entities, aggregates and repositories that make up our domain model, we could start to put the model into code,
but, I'll keep that for another post that I hope to finish soon. :)

maandag 1 mei 2006

Is Software Development too hard, or too easy ?

I've come across some rather interesting blog-articles, like this one from Scott Bellware and this one from Jeffrey Palermo.

Both articles are a reaction to this article from Rockford Lhotka. Rockford Lhotka says in his article that software development is too hard, that we -developers- have to spend too much time doing 'plumbing work', instead of concentrating on delivering business value.
In a way, he has a point: the main point in writing a business application, is to create an application that solves the business problem, and, since time is money and business is constantly changing, it should be done as quick as possible. Isn't this what we're all striving for ?

However, this should not be done at all costs. I mean, there are RAD tools available that will allow you to 'develop' an application quickly, but, if those tools are used in an inappropriate way, you'll end up with an 'application' that is a hell to maintain and to extend.
I think we've all seen those kind of applications: in a first phase, those app's do what they have to do, but, as requirements change, the code gets messier and gets hard to understand and it gets even harder to implement new functionality or change existing functionality.

This is where the opinion of Jeffrey Palermo comes in: the RAD tools allow you to build an application without requiring to know what's going on under the hood, and they make it possible that somebody who is not trained in software engineering can create an application. However, the quality of that application will most likely be poor.
To put it in his words:

It’s too easy for an unskilled person to throw a screen together and deploy it. It’s too easy for Joe blow to create a database application that pulls over entire tables to the client for modifying one record (but it works – initially). It’s too easy for a newbie to get excited about a new technology and completely screw up an application with web service calls to itself and overdo sending XML to Sql Server 2000. It’s too easy to a database guy to throw tons of business logic in stored procedures, call them from ASP and call it an application (until a skilled programmer looks at it later and has a heart attack).

The problem with RAD tools, is that everybody can now create an application that does what it should do. It allows people that are not trained in software engineering to create an application that does what it has to do, and it's possible that the user of the application doesn't notice that the application is actually a piece of crap. And I believe this happens all too often.

My opinion is that RAD tools can reduce the workload, but they should be used with care.

When a RAD tool is used inappropriatly to create a business application, the chances are big that the developer uses the 'Smart UI' antipattern. This means that all the business logic of the application is put directly into the user interface. This is off course problematic when the requirements change. Since the business functionality is scattered throughout the user interface, the programmer who has to maintain this application will have to delve into the UI to find all the code and the related code that has to be changed. When the application is large, it's easy to forget or overlook something that has to be changed, and it will result in a buggy application.
Or, imagine that you've build a Windows application, and once it's delivered, your customer or boss wants to have a web interface for this application as well. When all business logic is implemented in the user interface, this means that you will not be able to just reuse that code in the web application. The result will most likely be duplicated business logic.

That's why I believe that a RAD tool should be used with care. In my opinion, you should use the RAD features of your development tool to build the User Interface, and that's about it.
Developing software is more then just dragging some components on a form, glueing them together by setting some properties and using wizards to get the data from the database and bind it to some kind of control.
Scott Bellware is right when he says that the RAD functionality of Microsoft's development tools encourages one to create badly designed software. Microsoft shows in demo's how one could create an application very fast, with a very small number of lines of code with their RAD tools. The sad thing is that there are developers attending these demo's who think afterwards that this must be the way to develop applications. And this is not only true for developers attending these sessions. Managers seeing these demo's can think that software development isn't that hard at all, and they also do not understand how it comes that it takes so much time developing an application.
This is not a good way of building software. Those RAD tools and wizards are very good for giving demo-sessions (and selling the tools), but they're defenitly not showing a correct way on how to build software.
What about the maintainability and flexibility of software created in this way ? What about the ability to create unit-tests to test the functionality of the implemented business rules ? It is all impossible with applications that are developed in this way.

For the core of the application -the business functionality- the development team should create a domain model that expresses the business problem that the application must tackle. This means off course
that the initial development cost of the application will be higher, but, this development cost should be seen as an investment. The model will be easier to maintain, extend and to reuse, and, by using Agile development
techniques
, the customer can be involved in the development process. By using small development iterations and having customer input after each iteration, the customer knows that the development of the application is going forward, and he can ring the alarm when he sees that the functionality of the application or the business logic is wrong.

To conclude: RAD tools provide a way to make software development easier, and because of that, one could be tempted to create an application in a quick and dirty way. However, building high-quality software still requires skilled and educated/trained developers. They're not only required to be able to create a good domain model. They also have to be skilled in a way that they know for what they should and shouldn't use RAD tools.