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.

8 opmerkingen:

Anoniem zei

Thanks, this quickstart is a good start for anyone who want to know how to implement DDD in their project.

I have a few question, the relation between Customer and Order is unidirectional where Order have know the Customer, but when creating the Order u have add create method in Customer which is responsible to create new Order, I think this job will be more suitable if u put in factory maybe OrderFactory

Frederik Gheysels zei

You can think of the CreateNewOrder member method of the Customer class as a factory method.
I've choosen to do it like this, because right now, an Order object is instantiated and set up in a correct state; I mean: in this way, it is fairly easy to assign the Customer to which the Order belongs.
I also found it quite understandable to have this CreateNewOrder method in the Customer class, because it is the Customer that places an Order.

However, I'm not saying that this is the only correct solution; there are always multiple good solutions for a problem.
And indeed, maybe an OrderFactory will be more suitable when we would have different types of Orders. (f.i. an Order that has to be identical (an exact Copy) of another , previous Order for a specific Customer, etc...) In that case, I think I would opt for an OrderFactory.

Anoniem zei

I have a few question and hope can get your opinion on this problem..

I want to create a Question for a selected Topic, but the Topic is a aggregate boundry in Subject.In DDD a outside entity cannot have reference to internal aggregate boundry but only have a reference to root aggregate, how do u think to design this model, how i can have access to Topic?

So i have Question 0-* to 1 Subject and Subject has aggregate Topic.

William Rohrbach zei

Great posting. I thought I would share my solution to the repository creator process. I actually do something quite similar to you, but I am using generics to avoid having to add in the interfaces for the repositories.

public class RepositoryLocator<TRepository>
where TRepository : IRepository
{
public static TRepository GetRepository()
{
Type t = typeof(TRepository);
string name = t.FullName;

System.Configuration.Configuration config = null;
string deploymentTarget = ConfigurationManager.AppSettings["DeploymentTarget"];

if (deploymentTarget == "Web")
{
string deploymentPath = ConfigurationManager.AppSettings["DeploymentPath"];
config = WebConfigurationManager.OpenWebConfiguration(deploymentPath);
}
else
{
config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
}

if (config == null)
throw new ApplicationException("Configuration is not available.");

RepositoryConfiguration repositoryConfig = (RepositoryConfiguration)config.GetSection("lh1Repository");
RepositoryConfigurationElement trepositoryConfig = repositoryConfig.RepositoryList[name];

Type trepositoryType = Type.GetType(trepositoryConfig.Type);
return (TRepository)Activator.CreateInstance(trepositoryType);
}
}

As you can see it is very similar in terms of going to the configuration file to determine the concrete repository implementation to use. But the generics allows me to use this same base with no modifications as new repositories are added. So the client code to get a repository becomes:

ParticipantRepository partRepository = RepositoryLocator<ParticipantRepository>.GetRepository();

Again, a great posting... it's nice to see that I wasn't off my rocker with the approach I was taking.

Frederik Gheysels zei

@anonymous:
I do not quite understand; you have an aggregate within an aggregate ?
Can you show a little scheme of your classes and how they interact with each other ?

@william:
nice solution. However, how does the IRepository interface looks like then ? Does it have just some simple methods like GetById for instance ? (However, if that's the case, what's the return type then ?)

Anoniem zei

@william; you're reinventing the wheel. Why not use lightweight containers for dependency injection (preferable to service locators)? See Spring.NET for a fine example.

Evan zei

first off, i want to say thank you for providing these examples..these are proving very helpful for me..

i had one thought when looking at your code, you may want to refactor the CanOrderLineBeAdded method to a strategy..i'm still very new to DDD, but this was something i've been doing in my first few projects..ie..

inside AddNewLine, do something similar to the following

OrderPolicy Policy = new OrderPolicy(this);
if (Policy.CanAddNewLine()) blah

or you can make the policy object a singleton and shorten the entire thing

it's an easy way to move the Business Policy into a single place (for easy editing)..

it might not make sense if you only have the one method with a business policy rule embedded, but if you find a lot of those attached to your entity or value object, maintenance should be easier..

my thoughts were to have 1 policy object (see strategy pattern) per aggregate root..

Frederik Gheysels zei

It would indeed be better to factor some logic into a separate class, if this logic would be used in other places.

I know the strategy pattern, but, I do not see why you would want to use a strategy here ? At this time, there's only one 'implementation' of this 'algorithm'. A strategy would be usefull if you have multiple implementations; for instance a 'DeleteConfirmedOrder' and a 'DeleteUnConfirmedOrder'.