-
MVVM Mediator Deluxe
-
From what I’ve been reading on the implementations of the Mediator pattern for MVVM, I must say I’m a little disappointed. Maybe it’s my background with a certain UI design methodology (for which I must credit Philip Almey, a former colleague) for the last five or six years. Using (hierarchic) state charts to design user interfaces allows you to think about it’s behavior without being hampered by technology nor content or style. I’m happy that this “way of thinking” still stands firm as technology progresses (into Silverlight/WPF that is). Translating such charts into the asynchronous code flows that rule the “new world” is a challenge. One that requires clear separation of concerns, and the MVVM pattern adds a lot of value to solve this challenge. Another piece of the puzzle is the EventAggregator (Prism term) / Mediator / Message Bus / Messenger that provides the communication mechanism between your views and view models. But current implementations seem to focus on using the mediator as a publish/subscribe pattern vehicle and going to great lengths to eliminate memory leaks (which I personally find a little over the top considering that “unsubscribe” should be something explicit).
Looking at the mediator communication mechanism from a messaging perspective, I see a lot of room for improvement. There is no explicit concept of addressability (think WS-Addressing). If I want to send a message to a specific view, I’ll have to juggle with predicates and properties to make it work. Mind you I’m not advocating for tightly coupling views and view models (although some implementations already do this on subscription ;-)). After all, for addressability, one can simply use System.Uri. Along with addressability comes explicit support for the request/response message exchange pattern (alongside the existing publish/subscribe pattern). I’d like to see an API along the following lines:
| //Receive any message of the specified type IDisposable Accept<TRequest>(RequestCallback<TRequest> callback) where TRequest : RequestMessage; //Receive messages of the specified type matching the predicate IDisposable Accept<TRequest>(Expression<Predicate<TRequest>> predicate, RequestCallback<TRequest> callback) where TRequest : RequestMessage; //Reply to a request message void Reply<TReply>(TReply reply) where TReply : ReplyMessage; //Send a request message void Request<TRequest, TReply>(TRequest request, ReplyCallback<TReply> callback) where TRequest : RequestMessage where TReply : ReplyMessage; |
Remark: People who have used WSE3 (yes, really) will know that you can use the message to specify the addressing bits.
I know this API is more rigid (contrary to sprinkling Action<T>) but that’s also its strength. It forces you to think in terms of request/reply messages that flow around. This is how I discovered that sequence diagrams are the new hype all over again to model the communication between views and view models and how they help me understand the message flow. I realize that there are a lot of details and mechanics about the API that I haven’t explained, but this post is just intended to get the juices flowing.
My conclusion: Is it impossible to achieve the above with the current incarnations? No, but it’s cumbersome and too verbose.
-
The "facets-enabled" meta model
-
During the course of my current project at work, I've been trying to come up with a way to describe the meta data of my domain model (which I dubbed the meta model). Because I've taking a rather ObjectSpaces-like approach to object-relational mapping, the domain schema I created seemed like a good place to store the facets of my class members(fields and/or properties). Here's an example of what it looks like:
<?xml version="1.0" encoding="UTF-8"?>
<DomainSchema AssemblyFullName="SomeProject, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
xmlns="http://www.seagile.com/domainschema-1.0/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.seagile.com/domainschema-1.0/
D:\WorkSpace\SomeProject\trunk\Schemas\DomainSchema.xsd">
<Classes>
<Class FullName="Seagile.SomeProject.SomeBaseClass" IdentityMember="ID">
<Members>
<Member Name="ID" Field="_id" Type="System.Guid"/>
</Members>
</Class>
<Class FullName="Seagile.SomeProject.SomeClass" BaseClass="Seagile.SomeProject.SomeBaseClass">
<Members>
<Member Name="Name" Field="_name" Type="System.String">
<Facets>
<MinLength>1</MinLength>
<MaxLength>5</MaxLength>
</Facets>
</Member>
<Member Name="ExternalCode" Property="ExternalCode" Type="System.String"
Required="False" Default="MCQ000">
<Facets>
<FixedLength>6</FixedLength>
<Pattern>^MCQ\d{3}$</Pattern>
</Facets>
</Member>
<RelationshipMember Name="Category" Field="_category" RelationshipType="One"
TargetClass="Seagile.SomeProject.SomeOtherClass"/>
<RelationshipMember Name="Materials" Field="_materials" RelationshipType="Many"
TargetClass="Seagile.SomeProject.Material"/>
</Members>
</Class>
<Class FullName="Seagile.SomeProject.Material" BaseClass="Seagile.SomeProject.SomeBaseClass">
<Members>
<Member Name="Name" Field="_name" Type="System.String"/>
<Member Name="TotalParts" Field="_totalParts" Type="System.Int32">
<Facets>
<MinInclusive>2</MinInclusive>
<MaxExclusive>11</MaxExclusive>
</Facets>
</Member>
<Member Name="BarCode" Field="_barCode" Type="Seagile.SomeOtherProject.BarCode, SomeOtherProject"
TypeConverter="Seagile.SomeOtherProject.Data.BarCodeConverter, SomeOtherProject.Data"/>
</Members>
</Class>
</Classes>
</DomainSchema>
By itself the schema is worth nothing, but in conjunction with a mapping and relational schema it's "easy" for a generic mapping layer to interpret how to retrieve and store objects. Strictly speaking the mapping layer doesn't need the type information and facets. But their presence is very much appreciated by the domain, application and presentation layers. The presentation layer can use the information to constrain the input fields, and both the domain and application layer can feed these facet values to validators (LengthValidator, RegularExpressionValidator, Int32RangeValidator, ...).
The xml representation is both a blessing and a curse. On one side, it's very easy to get up and running (using an xml schema to drive the intellisense and validation). It feels like a modelling tool if the schema is what you start out with. If the domain model is already in place it's a pesky task. Maintenance wise, however, it's a little devil. Having a validator to compare the declarative xml representation against an assembly is not a luxury. Keeping the code and xml in sync is asking for trouble. I think the best way to handle this is (a) to forget this whole idea, (b) have the xml representation generate the code or (c) have the code generate (attribute-based approach) the xml representation.
But lets not forget the problem I'm trying to solve, namely preventing the scattering of the domain's meta data. Where do you store the fact a column is 5 long, how do you constrain the textbox representing that same conceptual value to only take 5 characters to have an overal nice end-user experience, and how about duplicating that value once more in the domain layer so that other "clients" of your code play by the rules? How do you deal with change and know where to look if 5 becomes 10?