"It is better to trust in the LORD Than to put confidence in man." -- Psalm 118:8
Ok folks. First, a couple disclaimers:
- I first heard of this kind of approach from JP back during his Richmond boot-camp tour. :) Having said that, I'm fully expecting folks (including JP) to shoot holes in the implementation of this example. I'm merely providing it to better explain the pieces involved.
- A lot of the patterns and layering you will see in the example are intentionally overdone for the purposes of showing the mechanics.
Now that we have that little tidbit out of the way. This post is intended to be read in 2 parts.
First, get the code man...
You can grab the entire source code (either from my google code repository or in this convenient little zip file). Feel free to browse the code for a bit, but come back to this post so I can show an example of adding a new, simple feature using this code base.
Some quick notes regarding the solution:
- If you open a console to the root directory of the solution you can run "build test" to run all the unit tests and see them succeed (hopefully, :P)
- I was lazy in this example and didn't include my usual "build run" target which will automatically compile, test and fire open a browser to run the application without opening VS. So you'll have to open up VS and do the old skool Ctrl+F5 (sorry).
Second, let's add a quick little feature
Right now the monitor search results show brand, size and model. Let's say the customer would like to also see whether or not the monitor is in stock from the search results. Let's see what that would take.
First off, you may have noticed that I'm using the very handy SmartGridComponent from Ayende (now included in the Castle Contrib project). Here's how it's used in this example:
<%
component smartGrid, { @source:searchResults, @displayId:false } :
section more:
%>
<td>${Html.LinkTo("Details", "monitor", "view", item.Id)}</td>
<%
end
end
%>
As you can see, I don't actually have to specify which columns should be shown in the grid, since it's "smart" enough to figure it out from the object it is bound with. This means we don't even have to modify our view to add this feature!
(To learn more about using this component, check out the new Castle project wiki page on it).
So we know that the object that is used to display our search results is MonitorSearchResultDTO. Instead of just adding an IsInStock property to it now, let's drive it out via TDD like good little boys and girls. :D
If we glance at our Monitor domain object, we see that a Monitor already has the ability to check if its in stock or not (whether it should or not is a whole 'nother discussion which is not the point of this post). Well, sounds like all we have to change is our mapper implementation. Let's open up our MonitorToMonitorSearchResultDTOMapperTest fixture.
Mapper tests are, for the most part, not all that exciting. A lot of value matching and that's about it. So let's set up our 2 stubbed monitors to return values for their IsInStock() methods.
// ...
SetupResult.For(mockDell24InchUltraSharp.IsInStock()).Return(true);
// ...
SetupResult.For(mockApple30InchCinemaDisplay.IsInStock()).Return(false);
Now let's add our asserts to make sure the mapper is doing its job of setting the appropriate values on the DTO. (Yes, I realize relying upon indexers in this case is just plain ugly, but you'll forgive me this time, won't ya? :)
// ...
Assert.IsTrue(listOfSearchResults[0].IsInStock);
// ...
Assert.IsFalse(listOfSearchResults[1].IsInStock);
You'll probably notice we don't have an IsInStock property on our MonitorSearchResultDTO yet. Simply use ReSharper to add it now.
(You'll probably want to update the constructor for this DTO in order to keep it immutable, since there really isn't a reason it should ever need to change once created.)
Now if your run our test suite using "build test" from the console, you'll probably get a compilation error. That's because we need to actually make the necessary changes to get our test to pass. So we just need to update our mapper to use the modified constructor on our DTO.
return new MonitorSearchResultDTO(monitor.Id, monitor.Brand, monitor.Model, monitor.Size, monitor.IsInStock());
So, re-run the tests using "build test" and we should be green. Now just build the app and refresh the browser. You should see that a new column appears in the search results named "Is In Stock".
(Note: This feature actually only takes about 30 seconds to add. Needless to say it took much longer to actually write about it and explain it here.)
One last problem though. It's using "true/false" as the values which makes for a pretty terrible user experience.
Need Something To Do?
So, dear reader. Here's a petty little task for you if you're interested. How would you change the search results "Is In Stock" column to show "Y/N" instead of "true/false"? (Bonus points for leveraging built-in MonoRail features in the solution)
In Conclusion
Let the flaming begin. :) Seriously though, I hope this example at least has some value and maybe gives a small glimpse into one way to use a presentation model to maintain a nice separation between your presentation and domain. I'd really like to see other folks post some examples as well.