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 OrderLine
s 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.