I've heard/read rumblings over the last few months that "SubSonic is tightly coupled" and therefore you have to "drag it around" with you in your project. I can see why people might think this - ActiveRecord is not the most testable thing in the world :). I've really tried to push SubSonic into the TDD realm and thought it might be a good idea to show how you can structure up a highly testable, decoupled application using SubSonic as your Data Access tool.
The Repository Pattern
This is usually the first choice in Data Access patterns when it comes to writing testable, decoupled code. So I'm going to use that today. If you arent' familiar with it, you may want to read more here. Essentially, the idea here is that you want to abstract your data access/query bits as much as possible from the rest of your application - including your model. I go into this a lot in the MVC Storefront series.
Rather than dive into explanations here, I'll just show the code :).
Solution Setup
I'm using ASP.NET MVC - but you don't have to use this. You can use whatever application setup you like. I'm dividing out the data access and test projects as well, so that I have 4 total in my application:
- ASP.NET MVC Website
- Data Access Project (Class Library)
- Business Logice (Class Library)
- Test Project (using MS Test)
Data Access
Let's assume, for now, that I'm writing tests for everything I do. And my tests tell me that I need to setup a Catalog Repository that talks to my DB (for today it will be Northwind). My tests tell me I need to have a Product so the first thing I do is setup a class to represent a Product:
public class Product { public int ProductID { get; set; } public string ProductName { get; set; } public decimal UnitPrice { get; set; } }
Next, I need to have a method on my Repository that will retrieve products from the DB. For those of you following along with the MVC Storefront, I'm going to use the classic Repository Pattern here, without using IQueryable.
I define an interface that will abstract the implementation of the Repository:
public interface ICatalogRepository { IList<Product> GetProducts(); }
Finally, I need to setup an implementation of this interface, and it will use SubSonic. I reference and setup SubSonic in the Data Access class, and then generate all the good stuff SubSonic will create for me here. Note that I can use ActiveRecord or RepositoryRecord - it doesn't matter, and that's just how I want it. All I'm going to use is the new Query tool - none of the base objects that are created (except for the Repository querying).
After generating the code, my project looks like this:
Note: you'll notice an App.config in the project. This is ONLY to tell SubCommander how/where to run the generation. You don't need to do this (you can send everything in to SubCommander by command line)- I just find it handy.
In the "Generated" folder are all the classes SubSonic generates for you to use and include a full schema look at your DB. This will allow us to query as we need to.
Mapping
If you're not use to using ORM tool (Object-Relational Mappers), you may not know why I want to "duplicate" the classes that SubSonic generates, versus the one I created here in my "Model" folder.
The reason comes down to what's known as "impedance mismatch" - the idea that my DB structure will not follow my application structure (and therefore OO principles). Moreover, if you tie your DB structure to your application, eventually you will end up with some pain points as your DB grows - and to mitigate these you compromise good DB practices. The opposite is true as well - you "bend" your application to allow for easier use of the DB. I'll end this discussion here :).
What I need to do now is implement ICatalogRepository using SubSonic, and map the Northwind Products to my Model. This is easy to do with SubSonic, using "ExecuteTypedList<>":
public class SubSonicCatalogRepository:ICatalogRepository { public IList<Product> GetProducts() { return Northwind.DB.Select("ProductID", "ProductName", "UnitPrice") .From<Northwind.Product>().ExecuteTypedList<Product>(); } }
ExecuteTypedList<> tries to match the names of the returned columns to the names of the properties of the passed-in type. In this example they match exactly - and that's not completely real world. You can get around this by aliasing the columns - in the same way you'd alias a SQL call:
return Northwind.DB.Select("ProductID as 'ID'", "ProductName as 'Name'", "UnitPrice as 'Price'") .From<Northwind.Product>().ExecuteTypedList<Product>();
Note: Using "as" is ANSI SQL, and will work with most DB providers - including MySQL 5.x and above.
You can also Stored Procedures here, or Views - up to you.
The Test
In the Test Application, all you need to do is:
- Reference your Data Access project
- Setup SubSonic's configuration (since this is an execution end point)
I'm looking into hard-coding the connection string info (like Linq To Sql does) so you can omit the config stuff - but I feel weird about that. Would love to hear some feedback...
This is my Test Project:
Note that I don't have any reference to SubSonic here :). I don't need it - it's abstracted nicely away. Now I can write my tests, stubbing out ICatalogRepository with a TestCatalogRepository, and use Dependency Injection to inject the SubSonicCatalogRepository as needed :).
To make sure that SubSonic is playing nicely here, you can write an integration test:
[TestMethod]
public void SubSonic_Should_Return_Product_List() {
ICatalogRepository rep = new SubSonicCatalogRepository();
IList<Product> products = rep.GetProducts();
Assert.AreEqual(77, products.Count);
}
And here's the result:
Yes, I know I shouldn't hard-code Count values, but this is a sample :).
Putting It All Together
The one thing I haven't shown yet is my Business Logic bits. The one thing you don't want to do in your application is instantiate a Repository class directly - that's "coupling" and ties your application to the implementation.
To get around this, you can use Dependency Injection to "inject" the Repository into your business logic class at runtime. If you want to know more about "why" you'd do this, I did a nice Dependency Injection screencast with Jeremy Miller (creator of StructureMap) on the subject.
Normally, your Service classes should implement your business logic so your Repository only concerns itself with getting the data you want. An example of this is that you may grab a list of Products and want to check inventory levels, whether the shopper is eligible for discounts - etc. You'll probably want to keep this type of logic in the application tier:
public class CatalogService { ICatalogRepository _repository; public CatalogService(ICatalogRepository repository) { _repository = repository; } public IList<Product> GetProducts() { List<Product> products = _repository.GetProducts(); //inventory checks //discount setups //cross-sell correlation //bundling... } }
The main thing about this setup is that the CatalogService class is FORCED to take a Repository in the constructor - this is exactly what we want in order to make our code more testable and decoupled.
Normally you might put some Linq To Sql code in here, or perhaps straight-up ADO stuff with an IDataReader. That may be a great call with a smaller app, but with anything that you want to maintain over the next few years, you're better off trying to keep it cleanly separated like this.
An example of why you might want this decoupling is you might decide to move your application to .NET 3.5 in the future and may want to use SubSonic 3.0, or Linq To Sql (or NHib... whatever). It's trivial to swap the parts out if you follow this architectural pattern.
Dependency Injection
It may seem like a bummer to have to instantiate all these things just to get at a list of Products, but that's where DI comes in. If we plug in a DI tool (like StructureMap), we can rely on it to do the associations we need:
ForRequestedType<ICatalogRepository>()
.TheDefaultIsConcreteType<SubSonicCatalogRepository>();
Setting this up is really, really easy and if you're interested in learning more about DI, take a look at my screencast with Jeremy Miller.
I'm very interested to hear your comments. This setup is pretty tried and true, and if you'd like to see more about how to do this type of thing, I'd push you to check out the MVC Storefront. I'm not using SubSonic there (yet) because it doesn't support IQueryable - but the ideas are pretty much the same.
