November 2006 Entries

WLW Drafts Folder - Part Deux (better workaround)

 

As noted in a previous post about WLW, I was wishing WLW would allow you to save your posts and drafts in another location other than the default C:\Documents and Settings\blah blah.  Well thanks to a former co-worker who GTalk'd me today, I was reminded of a great little feature of NTFS called reparse points.  For some reason I had forgotten how useful these little guys can be.

So Sysinternals, recently acquired by Microsoft, has this nifty little utility called junction allowing you to create these reparse points, aka symbolic links.  I'd highly recommend downloading the entire Sysinternals suite while you're there.

With a simple command like the one below, I was able to "fake out" my file system into allowing my d:\joey\documents\blog posts folder and c:\documents and settings\blah blah folder to essentially be the same.  At least as far as windows is concerned.

junction "d:\joey\documents\blog posts" "C:\Documents and Settings\jeb\My Documents\My Weblog Posts"

You can read up more about using reparse points in this PCGuide article and Scott Hanselman has an article relating to how Vista uses and extends this technique for manipulating how files and folders are represented on the file system.

DTOs and Data Contracts and Complexity, Oh My! - Part 1

 

(Chalk this one up to lessons learned...)

I recently made the comment to a co-worker that the more applications I develop, the more layers and complexity I seem to be removing and the more I'm striving for simplicity.  At first glance, this may look like a step backwards to those caught up in BDUF and designing a complex layering scheme for what you think the application is going to need. 

But even if you're trying to emerge the design as you develop, you can still be tempted to prematurely inject layers of complexity that aren't warranted.  As you can probably tell if you read one of my previous posts, we are using DTOs, perhaps when they aren't needed. 

Like on one of my current projects, I started out with a pretty typical architecture (non-distributed):

  • web ui
  • presentation layer
    • using one of my favorite patterns, Model-View-Presenter (right now I lean towards the Passive View flavor of MVP for its increased testability, but I can see how using Supervising Controller might be a good choice for windows forms apps if you actually like databinding...  :)
  • services
  • domain
  • data access layer
    • you know, that layer we all hate writing over and over again (can't wait to actually use NHibernate on a real project)

So the basic gist of it is my presentation layer uses different services to perform operations using my domain objects and my services take care of shoving stuff in and out of my data access layer.

Well for some strange reason I got on a DTO kick and prematurely injected a set of LocalDTOs to hide my domain objects from my presentation layer.  I thought it would be nice to separate how the data is represented in my presentation layer from my actual domain model.  Turns out this flexibility comes at quite a hefty price.  I suppose I should've known this going into it when I found this quote in PoEAA...

"Don't underestimate the cost of [using DTOs].... It's significant, and it's painful - perhaps second only to the cost and pain of object-relational mapping"

Perhaps if I would have read this post by Jon Tirsen and Fowler's response, it would have persuaded me away from my attempt at improperly applying the DTO pattern to my application.  The mapping logic that needs to be written and maintained in order to support this pattern can be tedious and downright frustrating.  However, if you're in a situation where you have to use DTOs, you should check out the BLToolkit which has a nice mapping engine which will definitely ease your pain.

Bottom line is that I decided to remove this layer of complexity from my application by refactoring away from this pattern and boy am I glad I did.  Now I can use my nice rich domain objects in my presentation layer which will really speed up future development and lower maintenance costs for this system.

So, remember, whenever you feel compelled to introduce another layer of abstraction or apply some new cool pattern you just learned about, make sure you're not over-architecting and you're KISS.  :)  (Refactoring is your friend...)

Well looks like I only got to the DTOs portion of my title, so I'll tackle Data Contracts and Accidental Complexity vs. Essential Complexity in subsequent parts.

Happy TDD'ing!

Windows Live Writer - Drafts folder woes/workaround

 

So like a lot of other bloggers, I'm really liking Windows Live Writer (WLW) for posting to my blog.  For some reason I was always too cheap to fork out the $$$ for BlogJet which most say is the leader is this particular area.  So WLW is a pretty nice free blog editor with some very useful plugins

One of the things that does bug me about it though is the fact that, by default, I can't save my drafts in a location other than My Documents\My Weblog Posts\Drafts.  Since I don't use the My Documents folder structure for anything, this does me no good.  I know, I know, just redirect the location of My Documents to the location of my choosing.  Just something about prefixing everything with "My" gives me the VB heeby jeebies.  :)

(Besides, I've set up a portable, synchronized work environment allowing me to use my same portable apps and same documents across different PCs using FolderShare.)

So after much googling without an answer I fired up my handy dandy Reflector to confirm my suspicion that the path is indeed hard-coded based off the Environment.SpecialFolder.Personal folder as shown below...

WLW.PostEditorFile

So my workaround is to simply create a blank .wpost file in my own "drafts" folder, (for me it's at d:\joey\documents\blog posts\drafts) and just create copies of that to use for new blog posts.

DraftsFolder

Notice I've marked the file named "New Post.wpost" with the read-only attribute to keep me from accidentally altering it.  And I've made a copy of it and opened it up in WLW.  The nice thing about this is that when you open a .wpost file directly from Windows Explorer into WLW, when you save, it will save it in that location instead of trying to save it in the default Drafts folder.  Another nice thing is that it will automatically rename the .wpost file to match the title you give the post in WLW

The downside to this approach is WLW won't show your drafts in its sidebar, since that only looks at the default My Weblog Posts\Drafts folder.

So hopefully Microsoft will make the save locations configurable (like every other Office app) as WLW comes out of beta so this kind of nonsense is not necessary.

MVP Refactoring - Extract View Mapper

 

Background

One of the patterns we are using in all of our current projects at our company is the Model-View-Presenter pattern (which I'll refer to as the MVP pattern from this point forward).  This is a great way of breaking out presentation logic into a highly testable and reusable layer of the application. 

Most of our implementations of the MVP pattern make use of DTOs (Data Transfer Objects) as the mechanism to pass data to and from the service layer and the presentation layer.  This allows us to make a clear separation between our presentation layer and domain model which lives behind the services layer.  But enough about that for now...

I won't get into too many specific details of the MVP pattern here, since a lot of folks have already covered that quite nicely.  Rather I'm going to talk about a refactoring I performed in one of our MVP implementations.

The Problem

Here is a typical example of a presenter that takes some data entered by the user about a person and saves it to the system. 

PersonPresenterTests

 

    1     [TestFixture]

    2     public class PersonPresenterTests

    3     {

    4         private Mockery _mockery;

    5 

    6         [SetUp]

    7         public void BeforeTest()

    8         {

    9             _mockery = new Mockery();

   10         }

   11 

   12         [TearDown]

   13         public void AfterTest()

   14         {

   15             _mockery.VerifyAllExpectationsHaveBeenMet();

   16         }

   17 

   18         [Test]

   19         public void ShouldSavePersonDetailsToSystem()

   20         {

   21             IPersonView mockView = _mockery.NewMock<IPersonView>();

   22             IPersonService mockService = _mockery.NewMock<IPersonService>();

   23 

   24             Expect.Once.On(mockView).GetProperty("FirstName").Will(Return.Value("first"));

   25             Expect.Once.On(mockView).GetProperty("MiddleName").Will(Return.Value("middle"));

   26             Expect.Once.On(mockView).GetProperty("LastName").Will(Return.Value("last"));

   27             Expect.Once.On(mockService).Method("SavePerson");

   28 

   29             PersonPresenter presenter = new PersonPresenter(mockView, mockService);

   30             presenter.SaveView();

   31         }

   32 

   33         [Test]

   34         public void ShouldShowErrorMessageWhenExceptionOccursDuringSave()

   35         {

   36             IPersonView mockView = _mockery.NewMock<IPersonView>();

   37             IPersonService mockService = _mockery.NewMock<IPersonService>();

   38 

   39             Expect.Once.On(mockView).GetProperty("FirstName").Will(Return.Value("first"));

   40             Expect.Once.On(mockView).GetProperty("MiddleName").Will(Return.Value("middle"));

   41             Expect.Once.On(mockView).GetProperty("LastName").Will(Return.Value("last"));

   42             Expect.Once.On(mockService).Method("SavePerson").Will(Throw.Exception(new Exception()));

   43             Expect.Once.On(mockView).Method("DisplayError");

   44 

   45             PersonPresenter presenter = new PersonPresenter(mockView, mockService);

   46             presenter.SaveView();

   47         }

   48     }

 

PersonPresenter 

 

    1     public class PersonPresenter

    2     {

    3         private readonly IPersonView _view;

    4         private readonly IPersonService _service;

    5 

    6         public PersonPresenter(IPersonView view, IPersonService service)

    7         {

    8             _view = view;

    9             _service = service;

   10         }

   11 

   12         public void SaveView()

   13         {

   14             try

   15             {

   16                 _service.SavePerson(CreatePersonFromView());

   17             }

   18             catch (Exception e)

   19             {

   20                 _view.DisplayError(string.Format("Error while saving: {0}", e.Message));

   21             }

   22         }

   23 

   24         private PersonDTO CreatePersonFromView()

   25         {

   26             return new PersonDTO(_view.FirstName, _view.MiddleName, _view.LastName);

   27         }

   28     }

 

IPersonView

 

    1     public interface IPersonView

    2     {

    3         string FirstName { get; }

    4         string MiddleName { get; }

    5         string LastName { get; }

    6     }

 

PersonDTO

 

    1     public class PersonDTO

    2     {

    3         private readonly string _firstName;

    4         private readonly string _middleName;

    5         private readonly string _lastName;

    6 

    7         public string FirstName

    8         {

    9             get { return _firstName; }

   10         }

   11 

   12         public string MiddleName

   13         {

   14             get { return _middleName; }

   15         }

   16 

   17         public string LastName

   18         {

   19             get { return _lastName; }

   20         }

   21 

   22         public PersonDTO(string firstName, string middleName, string lastName)

   23         {

   24             _firstName = firstName;

   25             _middleName = middleName;

   26             _lastName = lastName;

   27         }

   28     }

 

IPersonService

 

    1     public interface IPersonService

    2     {

    3         void SavePerson(PersonDTO person);

    4     }

Take notice the private method named CreatePersonFromView in the presenter class which handles creating a new PersonDTO from the properties on the view.  So this is fine as long as the number of properties on your view remain small.  The overhead of having the expectations in your tests to retrieve the property values from the view isn't that big of a deal when you only have 3 of them.

 

But let's say perhaps you have a typical data entry view with 15-20 properties on it.  That would mean for each test in your presenter test fixture that exercised the SaveView method on the presenter, you'd most likely need to duplicate those property getter expectations in each one (or of course extract them out into a private method in your test fixture).  But then the question arises, should the presenter be responsible for mapping the view to a DTO in this case, or should that behavior be delegated to a separate object?

 

In comes Extract View Mapper.  Since Google has no knowledge of it, I'm going to be cocky and say I created this new refactoring...joking of course...  :P  (Knowing full well that a lot of people have probably already done something very similar...)

 

In order to decrease the complexity of the presenter tests and the presenter class itself, you can extract out the logic that maps a view to a DTO behind an interface so it can be mocked in your presenter tests.  It also simplifies your presenter class since all the mapping logic is delegated to a different object.  So enough talk, let's see some code.

(For the sake of brevity, I've left out the some of the skeleton code shown above...)

PersonPresenterTests

 

    1         [Test]

    2         public void ShouldSavePersonDetailsToSystem()

    3         {

    4             IPersonView mockView = _mockery.NewMock<IPersonView>();

    5             IPersonService mockService = _mockery.NewMock<IPersonService>();

    6             IPersonViewMapper mockViewMapper = _mockery.NewMock<IPersonViewMapper>();

    7 

    8             PersonDTO personDTO = new PersonDTO("first", "middle", "last");

    9 

   10             Expect.Once.On(mockViewMapper).Method("MapToDTO").With(mockView).Will(Return.Value(personDTO));

   11             Expect.Once.On(mockService).Method("SavePerson").With(personDTO);

   12 

   13             PersonPresenter presenter = new PersonPresenter(mockView, mockService, mockViewMapper);

   14             presenter.SaveView();

   15         }

   16 

   17         [Test]

   18         public void ShouldShowErrorMessageWhenExceptionOccursDuringSave()

   19         {

   20             IPersonView mockView = _mockery.NewMock<IPersonView>();

   21             IPersonService mockService = _mockery.NewMock<IPersonService>();

   22             IPersonViewMapper mockViewMapper = _mockery.NewMock<IPersonViewMapper>();

   23 

   24             PersonDTO personDTO = new PersonDTO("first", "middle", "last");

   25 

   26             Expect.Once.On(mockViewMapper).Method("MapToDTO").With(mockView).Will(Return.Value(personDTO));

   27             Expect.Once.On(mockService).Method("SavePerson").Will(Throw.Exception(new Exception()));

   28             Expect.Once.On(mockView).Method("DisplayError");

   29 

   30             PersonPresenter presenter = new PersonPresenter(mockView, mockService, mockViewMapper);

   31             presenter.SaveView();

   32         } 

 

PersonPresenter 

    1     public class PersonPresenter

    2     {

    3         private readonly IPersonView _view;

    4         private readonly IPersonService _service;

    5         private readonly IPersonViewMapper _viewMapper;

    6 

    7         public PersonPresenter(IPersonView view, IPersonService service, IPersonViewMapper viewMapper)

    8         {

    9             _view = view;

   10             _service = service;

   11             _viewMapper = viewMapper;

   12         }

   13 

   14         public void SaveView()

   15         {

   16             try

   17             {

   18                 _service.SavePerson(_viewMapper.MapToDTO(_view));

   19             }

   20             catch (Exception e)

   21             {

   22                 _view.DisplayError(string.Format("Error while saving: {0}", e.Message));

   23             }

   24         }

   25     } 

 

IPersonViewMapper

    1     public interface IPersonViewMapper

    2     {

    3         PersonDTO MapToDTO(IPersonView view);

    4     }

So as you can see, our presenter tests and presenter class itself has been simplified to use the new view mapper to handle mapping the values from the view to the DTO.  You can also see that the private method CreatePersonFromView is no longer needed in the PersonPresenter class.  Anytime I can extract out logic from a private method in a class to another new or existing class, it’s a good thing because it allows it to be directly covered by a unit test instead of indirectly covered through a classes public interface.

 

Again, this is probably overkill for a simple example such as the one posted above; it was simply used to demonstrate how the refactoring was performed.  I’m sure there are different and/or better ways to accomplish this, but this is pretty close to how I performed this refactoring in one of our real applications.  The only difference is that in the real application I made a generic IViewMapper<ViewType, DTOType> interface so that the same interfact could be used for all view mappers.

 

Anyways, that’s all for now.  I’d love to hear your feedback on this and how you’ve tackled the problem of test complexity with mock objects using the MVP pattern.