Presentation Model: "Screen" Store Example

posted @ Tuesday, October 02, 2007 3:36 PM

 

"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.

Comments
Joey Beninghove - 10/2/2007 9:14 PM
# re: Presentation Model: "Screen" Store Example
@Joe,
Yeah, some of it is just personal preference.

One of the reasons I like having a separate project for my controllers is so I can easily compile them separately in my build script, without having to build the web UI project. Although I guess the same could be done using csc directly against a \controllers folder in the web UI project.

As far as the Service project, that is mainly for "application" services, not necessarily "domain" services. I personally don't like the term "service" very much since it has so many different meanings. But that's what most people seem used to. I prefer something like what JP calls "tasks". But either way, that's just a terminology thing.

As far as this just being "demo" code, this structure is actually almost exactly what I'm using on my current e-Commerce MonoRail project and it has worked out very well. The other developers on the team are able to easily understand the solution broken up in this way.

Having said that, I really like to see how other folks structure their solutions and source trees. I'm constantly looking for better ways to simplify things.
Joey Beninghove - 10/10/2007 12:58 PM
# re: Presentation Model: "Screen" Store Example
@Steve,
Yes, all view components should be "view engine independent" AFAIK. I'm sure folks could benefit from an NVelocity sample this component if you could bang it out. ;)
Mike - 10/12/2007 3:06 AM
# re: Presentation Model: "Screen" Store Example
Hey Joey-
I've been going thru the sample in this post. I'm curious if you've considered moving your IMonitorService interface to the controller and invert the dependencies b/w controller and service projects. Seems like this is more DIP and something I am playing around with. It seems like since you are going top-down here that this would fit.
Apart from a few minor differences your structure is similar to what I have come to enjoy. Kinda fun seeing other folks' tricks...thanks alot.
MIKE
Post Comment






Please add 3 and 6 and type the answer here: