Introduction
We recently began a new project of a distributed application. My main part consists of building a WPF Gui, and to get the job done, I need to convert the dumb DTOs received from my colleagues WCF services to clever business objects and vice versa. Now the challenge is to implement BO to DTO conversion in both directions as painless as possible. By painless I think about myself and the pain I would feel when I needed to write conversion code for every pair of BO/DTO.
This is why I tried to implement a generic adapter handling the BO/DTO conversion. Feel free to tell me if I succeeded and especially if you have any ideas about how to improve the underlying conventions and implementation.
Some conventions about the architecture
First some info about architecture:
- Based on the use cases, the server team designes the WCF services and as a part of this the DTOs being returned by the services and passed in as service call arguments.
- A DTO is a dump container of properties.
- Although being dumb, every DTO implements an interface which defines all the properties the DTO implements. This interface is called the data contract.
- On the client side, every BO mirroring a DTO implements the same data contract interface.
Differentiation from the prototype pattern
The basic idea to create a new instance B by copying property values of an existing instance A is also common to the creational prototype pattern. But the prototype pattern always returns a new instance of the same type, meaning that the new instance B will be of the same type as instance A. This is not what we want, we need conversion. Furthermore, the prototype pattern requires you to implement a clone-like-method for returning a new instance with copied property values for each type. Once more, this is not what we want because of to much implementation effort required.
Introducing and using the adapter
The adapter class exposes three methods only and looks like this:
Now let us assume one of our use cases has something to do with holidays. We therefore build a DTO and a BO holiday class both sharing the same data contract interface which looks like this:
At first glance, the HolidayBo doesn't look much more intelligent as the HolidayDo, but thanks to inheritance it gains some special abilities like validation. You may also notice that there is no connection between the holiday related classes and the adapter so far. To enable the adapter to convert a Bo to a Do and vice versa, we need to register both the Bo and the Do like this:
Dim testAdapter As New Adapter
testAdapter.Register(GetType(IHolidayContract), GetType(HolidayDo), GetType(HolidayBo))
testAdapter.Register(GetType(IHolidayContract), GetType(HolidayBo), GetType(HolidayDo))
The first argument in the register method designs the data contract. The data contract defines all the properties whose values will be copied when converting a dto to a bo and vice versa. The second paramter designs the source type which will be converted to the target type as defined by the third parameter.
This way the adapter gets all the info it needs to do the conversion. The conversion it-self is done by calling the convert method:
Dim myBo As New HolidayBo With {.HolidayDate = New Date(2011, 12, 24), .Comment = "Christmas"}
Dim myDo = DirectCast(testAdapter.Convert(myBo), HolidayDo)
Because the convert method returns an object we need to cast the result to the appropriate data type. The interesting thing is that the newly created myDo object's properties now have the same values as the orginal myBo object. As you might guess, the adapter uses reflection to iterate through the properties defined by the data contract and copies those.
Now you might wonder what happens if the property to copy is a not a simple value type. The answer is: it depends. If the property type to copy is a registered DTO or BO, no copy will be made, but instead it will also be converted following the same rules as before. If the property to copy is a none registered type, a simple copy will be made, meaning the DTO and BO now refer to the same object. If this is a problem, I haven't found it so far, because by convention every complex type will be designed as a DTO/BO pair implementing the shared data contract. Doing so you only need to register the complex type on the adapter as well and it will be converted.
Somewhat challenging was how to handle collection properties containing DTOs or BOs. In fact, the adapter so far supports collections implementing one of these interfaces:
IList(of T)
, where item of type T
may be converted.
IDictionary(of Key, Value)
, where key or value may be converted.
If the adapter's conversion method encounters a collection implementing one of these interfaces, it will convert the contained items as well. This means that items in an ObservableCollection will be converted, because the ObservableCollection implements IList(of T).
Take a look at the picture below as a summary of the conversion guidelines just mentioned:
Additionally, thanks to the miracles of recursion, the conversion of property items does not stop after the first level as you might think when looking at the image above but is unlimited as long as the property of an object being converted is a registered DTO/BO it-self. If the adapter encounters a none registered object, the conversion chain is broken. Again, have a look at this:
You might wonder if just copying a DTO instead of converting it to a BO as shown above will result in an invalid cast exception. Generally, this is not the case, because the collections containing BOs/DTOs are of type "data contract", not of type BO/DTO. This is a must, because the data contract needs to be implemented by both BO/DTO.
If all this sounds confusing, why not download the code project and run the (n-)unit tests to see the adapter in action. The adapter's class code it-self is also quite manageable, being less than 200 lines so far. It makes heavy use of reflection. So far, I have no performance issues and everything went fine, but as always I am afraid that future will prove me to be wrong again.
Lessons learned I: Open source project Automapper
Thanks for everybody indicating that there already exists a solution to be problem I tried to address called Automapper. I gave it a try and it passed all existing unit test of mine and offers much more functionality. So I recommend that, if you are in need of an object oriented mapper, take this one.
Lessons learned II: Common interface for DTO and BO sucks
Implementing a common interface for both DTO and BO sounded good to me at first. Until I realized that when a DTO/BO property holds another DTO/BO (single or collection) you need to implement this property with the type defined in the interface. Not only does this lead to more annoying conversion, but it also disturbs gui databinding. For example, when binding a collection of an interface type to a WPF datagrid, the datagrid looses it's ability to add a new row to the grid. This is because WPF does not know about the concrete object type it has to initialize when adding a new item to the collection. All it knows is the interface. Automapper does not need interfaces, because it works with conventions, which is a good way, I think.