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.