Monday, 12 October 2009

Domain / UI speration

On the current project the decision was made to use domain objects directly in the user interface. It seemed to be a good idea at first, it made a lot of code unnecessary, but there where some things that were not solved nicely.


An option that we have in .NET is databinding. this means that we can bind properties to controls and let the control update it self when a specific property is changed. There are also collections that can be bound to objects and that can react to changes from their items.

In our domain we basically have items which have a description and a price. Each item can have subitems (but this isn't necessary) and when there are subitems, the price of the item is the total of the subitems.
The way the domain was set up was that the total price was calculated as soon as the list was changed. We responded to the ListChanged event, calculated the total and set the Price property.

This worked fine, the User interface was getting changed and everybody was happy, but we didn't have any persistence at that moment.


When I was implementing/configuring the persistence for our domain, I came across a few problems. First of all the BindingList<> object we used for the subitems was not supported by the framework we use (NHibernate), so we had a choice to make: Implement a custom collection or add extra properties for the BindingList (one exposing the BindingList<>, an other exposing a normal IList<> which could be handled by NHibernate). We went for the custom collection. this went fine for a lot of things, except for the price totals.
When an Item is loaded from the database, a few things happen:
  • an Item object is created
  • a list with for subitems is created
  • eventhandlers are coupled to the list
  • NHibernate sets every property that is mapped, including the list
The last step was deadly for our domain logic. at the moment the list of subitems is set, the event handlers still refer to the old list. This means that when a subitem changes or subitems are added/removed the event handler will NOT pick this up and the total price will not be changed.


The solution is pretty straight forward, but is a change of thinking. Instead of setting the total price, ask for the total price. So we do "SubItems.Sum(x => x.Price)" in the getter of Item.Price. This way we eliminate the need of a BindingList and event handlers and we can save and load everything.
This gives us another problem. A grid with SubItems will not automatically refresh. This can be solved by add a special Item to the view, ItemView or ItemPresenter or ItemPresentation or whatever, this class should have the logic to drive the User Interface and tell the user interface to change when something important happend. Because this class needs domain logic I did make a subclass for this and I map all the values from one class to the other. (in other cases I just change/use the base class directly, so instead of using a subclass it is a gateway)

This seems to be a lot of extra work, and it is, but I think it is worth the trouble and there are a lot of libraries that can ease the pain. (such as automapper which can map two objects to each other by copying all the values of the properties from one object to the other)
Adding these View classes will make the actual UI code easier to read (and write)

I will try to investigate more about UI patterns, and hope to write down more.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.