Project Example: ASP.NET MVC + SubSonic Architecture
So I've thrown up a couple of posts over the past couple of months regarding our use of SubSonic to provide the DAL in association with some ASP.NET MVC sites and was waylaid in my original intent to throw up a post summarizing the architecture we're using for the effort. Well I've finally gotten around to it. Recall that in his fake it till you need it post, Dave provided a window on our architecture that I'll build upon here.
Example MVC Architecture
In the diagram above, Dave's original concept is shown on the left for reference with my breakout to the right, similarly color coded for consistency.
And later on we'll flip the Castle Windsor switch and hit:
when the system goes live.
The repository layer implementation classes are the only place where facts about the data source and the domain layer are known in the same place. And since these classes have responsibility for data CRUD in the application, unit testing is critical and supported by the nice separation in this architecture once again.
There is acutally a little more to it than that...the call to SubSonic in our live code acutally includes a call to a custom recursive function that converts a SubSonic type to a domain object on the fly.
Example MVC Architecture
In the diagram above, Dave's original concept is shown on the left for reference with my breakout to the right, similarly color coded for consistency.
The UI:
No surprises here. ASP.NET MVC drives the show from the front end with JQuery, Dojo, or both providing the shiny bits in the browser for the users. And since we love our unit tests (and are making strides in one of the projects to be TDD), the separation of concerns/tiers within ASP.NET MVC allows us to unit test our controllers thoroughly.The Domain:
The domain of our architecture is actually evolving. Our first iteration of this architecture worked with both domain objects and POCOs (plain old CLR/C# objects) in the domain layer. I've included the MVC Model in the domain as well because, with some exceptions, it is holding the data required to successfully render a View...an argument can be made that it is UI as well, but that is beyond the scope of this post. As illustrated above, a POCO is simply a data transfer mechanism while the domain object may combine one or more POCOs in new and interesting ways to be rendered by a view. Keeping domain objects and POCOs separated allows us to unit test Model and domain object logic while still letting us "fake" all of the repository and data interaction. Later iterations of this architecture take a more DDD approach, eliminating POCOs entirely due to their tendency to push the schema up the architecture, relying instead on the IRepository to return domain objects to the Controller or Model without the middleman. Either way, there is sufficient abstraction to fake and unit test robustly.The Repository:
This is where Dave left us off in his original post...the ISomethingRepository. Interface based repositories do all kinds of good stuff for the architecture most notably encouraging DI/IOC patterns that allow faking of repository behavior until we actually have a bona fide need to test against a live database. One developer can unit test his Model and Controller against:public class FakeSomethingRepository : ISomethingRepository
public class SqlSomethingRepository : ISomethingRepository
The DAL:
This is where SubSonic takes over. We simply point it at our database, generate a suite of DAL classes, and off we go. In our first iteration, the tendency for SubSonic to like to work against tables proved challenging from a domain driven perspective, but as it turns out we were handed an existing schema that we were bound to anyway. Plenty of Sql Server Views solved most problems. So a typical repository method exercising the SubSonic DAL might look something like: /// <summary>
/// Gets the customer by id performing a deep load of roles.
/// </summary>
/// <param name="customerId">The customer id.</param>
/// <returns></returns>
public UDSH.Domain.Customer GetCustomerById(int customerId)
{
//this passes back a user with all contained collections
Domain.Customer result = null;
//get the user using subsonic query
result = DB.Select().From<Customer>().
Where(Customer.Customer_IdColumn).IsEqualTo(customerId).
ExecuteSingle<Customer>();
if (result != null)
{
//attach roles
result.Roles = GetRolesForUser(result.CustomerId);
}
return result;
}