Feb
10
Written by:
Mark Whitfeld
Wednesday, February 10, 2010
So, we are starting to develop a project in an Agile, TDD manner and we don’t really want to be concerned with too much other than this object has this property and relates to some other object... because my test that I’m currently writing needs it. I don’t really care how this fits into the whole scheme of things at this stage. The fact that my “Invoice Line” object can only ever exist on an “Invoice” and that the invoice must be associated to a “Customer” in order to be valid doesn’t really concern me at this stage. Although all those other facts are true, they have no relevance to my SUT (system under test)...
For arguments’ sake, let’s say that I am writing a price calculation module that calculates the total of an “Invoice Line”, given the “Product” price, the quantity on the “Invoice Line” and the mark-up allocated to the “Customer” associated to the “Invoice”. In order to set up this test, I don’t really need the real objects for “Product”, “Invoice Line”, “Invoice” and “Customer”, but I do need some sort of intelligent domain model. Currently, none of these objects have anything other than their properties and relationships.
So, what objects do we need in our domain model for this test?
Product: Price
Invoice Line: [Product], [Invoice], Quantity
Invoice: [Customer], [Invoice Lines(1-*)]
Customer: [Invoices(*)], Markup
We could start with writing POCOs (Plain Old Compiler Objects), but there will be quite a bit of code needed in the POCOs in order to support the relationships within the domain. To treat these objects as domain objects there will be some assumptions that I will make before I try to use them. I will document these assumptions as tests:
[Test]
public void Test_Invoice_WhenCustomerSet_ShouldExistInCustomerInvoices()
{
//---------------Set up test pack-------------------
Invoice invoice = new Invoice();
Customer customer = new Customer();
//---------------Assert Precondition----------------
Assert.IsNull(invoice.Customer);
Assert.AreEqual(0, customer.Invoices.Count);
//---------------Execute Test ----------------------
invoice.Customer = customer;
//---------------Test Result -----------------------
Assert.IsNotNull(invoice.Customer);
Assert.AreEqual(1, customer.Invoices.Count);
Assert.IsTrue(customer.Invoices.Contains(invoice));
}
[Test]
public void Test_Cutomer_WhenInvoiceAdded_ShouldSetCustomerOnInvoice()
{
//---------------Set up test pack-------------------
Invoice invoice = new Invoice();
Customer customer = new Customer();
//---------------Assert Precondition----------------
Assert.IsNull(invoice.Customer);
Assert.AreEqual(0, customer.Invoices.Count);
//---------------Execute Test ----------------------
customer.Invoices.Add(invoice);
//---------------Test Result -----------------------
Assert.AreSame(customer, invoice.Customer);
Assert.AreEqual(1, customer.Invoices.Count);
Assert.IsTrue(customer.Invoices.Contains(invoice));
}
As you can see, the main issue is relationships. There is some logic required in order to implement them. So, what we really want is the simplicity of POCOs, with the ability of intelligent Habanero domain objects without the extra effort. So how do we achieve this?
...Enter my idea: Runtime Domain Objects...
What if you were to just create an interface with the properties defined for the various Properties and Relationships of the object and Habanero was to generate the required implementation of the Interface at runtime? That would be perfect. So, the code needed for the domain model I described above would be:
public interface IProduct : IBusinessObject
{
double Price { get; set; }
}
public interface IInvoiceLine : IBusinessObject
{
IProduct Product { get; set; }
IInvoice Invoice { get; set; }
int Quantity { get; set; }
}
public interface IInvoice : IBusinessObject
{
ICustomer Customer { get; set; }
BusinessObjectCollection InvoiceLines { get; }
}
public interface ICustomer : IBusinessObject
{
BusinessObjectCollection Invoices { get; }
double Markup { get; set; }
}
Habanero would then take these interfaces, and use them to retrieve the Class Definition structure from them and then generate a type at runtime that will inherit from this interface (and the BusinessObject base class) and implement all of the necessary methods with the relevant domain model calls. Pretty cool hey! Don’t get too excited just yet. It is just a dream at the moment, but I don’t think it is too far away from becoming a reality. All that is needed is the following:
- Reading of the Class Definitions from interfaces using reflection (this is currently being implemented by Brett Powell, and will probably be included in Habanero quite soon)
- The ability to specify the types to be created for Business Objects at runtime (this could be done through changes to the ClassDefs or through improvement of dependency injection in Habanero – something that has been spoken about quite a bit recently, so i think it won’t be too long until this is implemented)
- Creation of the necessary types at runtime (this is the big one, but I don’t think it is too hard to do. It just needs someone that understands how to do it. Any takers
)
Even though this is pretty cool, I don’t think it would replace the need for creating concrete classes for your domain model. Once the need to write a custom function arises, then you will have to create a concrete or at least an abstract class.
What do you think of this idea? Comments, suggestions and contributions are welcome.
PS. If you would like to see this implemented, please leave a comment requesting it. I will post a comment here when it has been implemented or to notify of progress.