In the July 2010 issue of MSDN Magazine, I began to introduce the process of building smart client applications for lending libraries. I named the project Alexandria and decided to use NHibernate for data access and to use the Rhino service bus to achieve reliable communication with the server.
NHibernate (nhforge.org) is an object-relational mapping (O/RM) framework, while the Rhino service Bus (GITHUB.COM/RHINO-ESB/RHINO-ESB) is built on the Microsoft. NET Framework Open source Service Bus implementation. I happen to be involved in the deep development of both frameworks, which gives me the opportunity to implement projects using the techniques I know, while providing a working paradigm for developers who need to know about NHibernate and Rhino service buses.
In my last article, I introduced the basic building blocks of smart client applications. I designed the communication pattern between the back end and the smart client application and the backend. In addition, I talked a little bit about how to manage transactions and NHibernate sessions, how to use and reply to messages from clients, and how to incorporate everything into the bootstrapper.
In this issue, I'll describe the best practices for sending data between backend and smart client applications, and the pattern of distributed change management. In this process, I'll introduce the rest of the implementation details and provide a complete client for the Alexandria application.
You can download the sample solution from Github.com/ayende/alexandria. The solution consists of three parts: The alexandria.backend contains back-end code, Alexandria.client contains the front-end code, and alexandria.messages the message definition that is shared between the first two.
No single model rule
One of the most common questions that people ask when writing distributed applications is: How do I send my entity to a client application and then apply a changeset on the server side?
If this is your problem, you may be thinking about a pattern that primarily takes the server side as a data repository. If you build such an application, you can choose to use some technology to simplify this task (for example, with WCF RIA Services and WCF Data Services). However, using the type of architecture I have outlined so far has virtually no effect on sending entities over the network. In fact, the Alexandria application uses three different models for the same data, each of which is best suited to the different parts of the application.
The back-end domain model is used for querying and transactional processing and is suitable for use with NHibernate. For further optimization, you can split the query and transactional processing responsibilities. The message model represents messages on the network, including concepts that are very close to the domain entity (bookdto in the sample project is the book's Data clone). In client applications, the view model (similar to the Bookmodel Class) is optimized to bind to XAML and handle user interaction.
Although there are many commonalities between the three models (book, Bookdto, and Bookmodel) at first glance, they actually have different responsibilities, which means that if you try to incorporate all of these responsibilities into one model, you create a cumbersome, bloated, and generic model. By splitting the model by a series of responsibilities, I can make the job easier because I can optimize each model for its own purposes.
From a conceptual standpoint, there are other reasons why you need to create a separate model for each purpose. Objects are a combination of data and behavior, but data can only be sent when you try to send objects over the network. This leads to some interesting questions. Where do you put the business logic that should be run on the back-end server? If this logic is placed in an entity, what happens when the client executes it?
The end result of this architecture is that you are not using real objects. The data object you are using is an object that only holds data, while the business logic resides in a different location in the same way as the process that is running against the object data. This is not supported because it leads to the fragmentation of logic and code and is more difficult to maintain over time. Regardless of how you view this problem, you need to use different models in different parts of your application, unless the backend system is a simple data repository. This naturally leads to a very interesting question: How will you handle the changes?
Commands for Change sets
The actions that I allow users to perform in Alexandria applications include adding books to their queues, reordering books in queues, and completely deleting books from the queue, as shown in Figure 1. These operations need to be reflected both on the front and back.
Figure 1 actions that can be performed on a user's book queue