Building The Lounge - Release 1.1 - Refactoring, Models, SubSonic, and Testing

This is the first in a series of posts about building The Lounge Advertising Network. My goal here isn't to try and tell people how to build applications, but rather just to explain how I have refactored/built this application.

First a little backstory. The Lounge was started by Kevin Fricovsky in 2007. In December of 2007 I took over The Lounge and began the process of refactoring it and building upon what Kevin had built. When Kevin turned The Lounge over to me it was built using ASP.NET 2.0 and used SubSonic as the ORM to talk to SQL Server 2005. I also want to make sure it is clear that I did not have any problems with Kevin's code, the Lounge worked perfectly for me from Day 1.

Because I was starting with a production application I had to prioritize technical improvements vs. new features. I wanted to start out with a refactoring and testing release. Release 1.1 only included two tickets:

I am a big fan of having rich models that contain as much business logic as possible. This makes the front-end pages very light and easy to modify, and it makes the DB simple and easy to work with. It also helps me practice TDD and get good test coverage as I can focus my testing time and effort on the Models. So the first step with The Lounge was to refactor as much logic as possible into the Models and to wrap those with tests.

SubSonic makes it fairly easy to do this style of development as long as you structure it correctly. What I did was in my Model project create a subfolder called SubSonic where the SubSonic models are generated. To generate them I use the External Tools trick in Visual Studio which makes it easy to quickly re-generate the subsonic models when I make a change. I don't want to put any logic in these models though since they get blown away when you regenerate. Thankfully SubSonic makes all of these models partial classes, so in the root of my Model project I have my own model objects with their logic. This is actually very Rails/ActiveRecord like since my models just have logic and don't have to have the data properties. Yes I know that my models do not have PI, I am OK with that.

Part of putting as much logic as possible in my model means moving any validation to the model and not keeping it in the code behind or UI. In the past I have used a custom attribute based system (that turned into EVIL) and I have also been enamored with ActiveRecords validations.. but neither was really an option since I didn't want to add anything to the SubSonic models. I ended up writing a simple little ValidationEngine that just gives me some standard ways of checking for conditions and throwing a standard error message, using my little engine I can override the SubSonic Validate() method with something like this:

public override bool Validate()
{

  ValidationEngine valEngine = new ValidationEngine();

  valEngine.CheckRequiredString(Title, "Website Title");
  valEngine.CheckRequiredString(Url, "URL");
  valEngine.CheckValidURL(Url, "URL");

  validationErrors = valEngine.Errors;
  return valEngine.IsValid;
}

Its pretty simple but it keeps all of my validation in the model, in one place, and it makes sure all my error messages are standardized across the models. I try to keep everything in my models, so even simple things like this go in a model so they stay out of the view:

public bool IsPayPal
{
  get
  {
    return PaymentOption == "PayPal";
  }
}

For the unit tests I use MbUnit, at one point I was close to 100% coverage but lately it has crept down a little bit. I feel another hardening release in my future. One part of my refactoring was tweaking how the ads were served, I made a couple changes that I hoped would have a positive impact on performance, but before I did that I wanted to get an idea of how long it took. To do that I wanted to test more than just one ad being served... so I decided to write a test like so:

[Test]
[Repeat(100)
public void GetNextPassTest()
{
  Pass pass = Pass.GetNextPass("127.0.0.1", "DOTNET");
  Assert.IsNotNull(pass);
  Assert.IsTrue(pass.RoomCode == "DOTNET");
}

This uses the nice MbUnit Repeat function to get 100 Passes (a Pass is basically a campaign). I can run this test and see how long it takes, make my changes then run it again. It isn't as precise as using something like ANTS Profiler or dotTrace, but this was a way to quickly run my test and make sure I wasn't negatively impacting performance. I don't remember the exact numbers but I know that I cut the time in half that it took to get 100 passes. (MbUnit also has lets you assert on run times, but I just wanted a metric to keep track of and didn't have a strict goal I was shooting for)

At the end of the release I deployed and didn't really deliver a single new feature, but I got alot of housekeeping out of the way and was ready to move on to the most important feature to my advertisers at the moment.... Reporting.

-James

Comments

#1 JohnR on 7.28.2008 at 9:06 AM

I love it. Are you willing to share the code?

#2 James Avery on 7.28.2008 at 9:18 AM

I thought about sharing the code, but I decided against it because I have future plans where that could be a bad thing. :)

Leave a Comment