In this installment, I walk through our Repository implementation and write out the first main set of unit tests. I also structure up the initial service layer methods and interfaces.
Previously, On the MVC Storefront
UPDATE: You can download Part 1 here and download Part 2 here.
Getting Off The Ground
Finally! I get to code something! Our first set of requirements are pretty simple - which is the way we want em since we're being Agile:
I take a pretty good chunk out of the requirements in this session - working on the main setup of the Service layer as well as the Catalog Repository.
I also show you how you can work without a database (and keep working without one) as you test up and build your application from nothing. Ideally when working with TDD, you want to be sure that the your tests which require data are as isolated as possible. In other words - you don't want bad data to fail your tests.
Technorati Tags: aspnetmvc
rob, i know people like the textmate theme in VS, but in a screencast it makes it a bit hard to read unless you use pan/zooming (an automatic feature in camtasia 5...let me know if you need a license)...it's hard to grok when so dark
Thanks Tim :). I've popped the movies to my server and I'm hoping MaximumASP will be nice to me and not charge me too much for the bandwidth :).
You should also release the source code for each part (or perhaps using SCM and tag it for each part). This will make it easy for anyone who wants to follow on your video.
I'm running commits on each release so if you go to the source (which is in the next episode) you can download the corresponding release.
Hey Rob, it's great seeing stuff like this come out of Microsoft. I've got a few nitpicks about the "tdd" being shown. I don't know if that's just a time/size issue or a puristish issue, but there seems to be a lot of corner cutting.
An important part of TDD is the reddest part of the red stage, that is when the code doesn't even compile. Writing the tests before you write your domain or your service methods or anything really aids in design. After a while you start to cut corners as you see the obvious, but I think that as an intro to TDD, as this will be for a lot of people, you should start out a bit more "pure".
I'm sure some will disagree with me on this, but just wanted to throw out my opinion. Other than that stuff is looking great and I can't wait to see where it goes.
Oh, and the whole testing the stub thing just begs for an introduction to a mocking framework, but one step at a time.
Very nice. I love the idea of using IQueryable as the sole method on your repository interface and then pushing the creation of more complex queries to the service layer. An excellent pattern that I will be certain to use in the future.
>>>An important part of TDD is the reddest part of the red stage, that is when the code doesn't even compile<<<
Is that really a test? I mean it seems to me you're just testing to see if C# really is static :). In all seriousness - a test that can't even compile, to me, is just dead space. The failure isn't a failure if the test doesn't exist - and in this case it doesn't because the compiler says it doesn't.
Anyway - I don't think this is cutting corners; you have to work with what you have:).
That said - talk to me, these are my opinions and I don't want to preclude anything. I have a hard time seeing red when the app doesn't compile :).
@Justin: Yah that was some good stuff coming out of the Harman/Rahien peer-coding.
I don't actually fail a compile usually. What I do do is write the method call before the method exists and R# it's creation.
Ask yourself why you need the method on your class under test. Why do you need those fields on your domain entities? What *test* mandates their existence?
In purest form of TDD you don't even create your Class under test until after a test mandates its existence. You don't create a method or a property on a class until a test requires you to do so.
In this form of TDD you're practically guaranteed incredibly high code coverage and very little wasted code especially if you start at a high level (like the story level) and drill down.
I must say that this is not only purist, but also somewhat idealist. It's sometimes difficult to do, both in practice and patience, but it's usually worth it.
Rob,
Great posts... looking forward to the next one!
I just wondered if you were intending to use Subsonic in this application?
Thanks, Simon
Rob,
You are the man.Keep up the good work. I would hope subsonic implementation would be inferred.
Thanks,
Mike
Thanks for the nice tutorial. It's useful especially for people like me, who couldn't find their way to TDD, dependency injection etc. and who still haven't given up :) (I'm writing that after a couple of years of hard trying)
The only thing I don't understand in this sample is that you're creating that hierarchy of categories and subcategories in your service class - shouldn't the repository act as data access layer here? And data access layer is supposed to return complete domain objects (like categories and its relatives), right? Shouldn't the service contain just business logic?
If I used NHibernate, I would hide it behind that repository class. That would already return categories with their subcategories - I wouldn't need to do that in the service class.
Am I missing something? What planet am I on? :)
>> In all seriousness - a test that can't even compile, to me, is just dead space. <<
Or it keeps you in check to make sure you will only write the simplest possible thing that could work for your production code.
Or it makes you think about how the class under test will be used (which helps prevent goldplating of classes)..
I'm not advocating any specific products, but (as another example) in R# land, the non-compiling unit test is also a code generation template. It will look at the non-compiling code and infer what the class should look like (properties, methods, constructors, implemented interfaces, parent classes) based on how it's used in the test. So, using the noncompiling unit test, you can use code generation to get the full skeleton of the production class using only a few keyboard shortcuts..
I'll throw this book out again..
Pattern-Oriented Sofware Archictecture: Vol 1
www.amazon.com/.../0471958697
It has a full discussion of the Pipes and Filters pattern and the implementation variations (do the filters push? do they pull? what's the pipe do? etc)
@Evan: not sure that the book was meant for me. But - IMHO this isn't matter of pipes or filters (distributed architecture). This is a common layered architecture and the service and repository will be in one physical tier.
My concern is the separation of concern between layers (within one tier) in this solution since I haven't seen it yet.
@David
No, I threw out that book simply because the screencast scratched on pipes and filters..
As for the structure of the solution being built, I'd agree in saying that the highest level pattern is (or likely will be) layering (without tiering).
Incidentally (lol), the Layering pattern is also covered in depth in the book.
Once we start seeing the MVC triad show up in his videos, we should start seeing the layered architecture show up.
I think what Rob meant in the video was that the Repository pattern was the overall pattern used for data access. As for pipes in filters, he wasn't referring to a distributed implementation of the pattern (you can implement it as a set of classes in the same process as well).
And, on one other side note, the MVC pattern was also covered in depth in the book (maybe you are starting to see why I recommend it heavily)..lol
@David
Scratch that last bit about the Repository as data access, he appears to be going down the full DDD Repository Pattern route (which is a very nice approach)..
Check out the book "Domain Driven Design" for more info (there's too much detail to leave in blog comments)..
@Rob
Sorry for all the comments..lol..
@aaron: I dig it :). I was talking to Phil about this and he and Steve Harman both (as with others) echoed that this is a tooling issue - I agree with the idea and sentiment here :). I'll mention in the next screencast.
@mikedopp: Currently the plan is to use LinqToSql; however if I do things right, it will be easily swappable with NHibernate's implementation of Linq (linq to nhib) and our future plans with SubSonic.
@Evan - thanks again for the links. Yes you're right the Pipeline model I was talking about is purely for filtering at this moment and currently the plan is DDD/Repository (you can thank Ayende for that) although I'm listening if there are opposing thoughts.
Something looks odd with the CatalogService [but I can only look at the code in media player, so that may be part of the problem :)].
Why are there so many greedy ToList operations hanging around when you could be composing the queries?
Was that a question of style, or did it have some deeper meaning?
In other words, why isn't CatalogService::GetCategories a single query:
var parents = (from c in _repository.GetCategories()
where c.ParentID == 0
select new Category {
// ...
SubCategories = (from subs in ...)
});
These recent MVC screencast posts are chock full of win for too many reasons to list. Do more.
@K Scott: the first "ToList()" that you see is set to fire the query (when we move to LinqToX) to return all the categories.
The second (parent) is the result, which needs to be IList;
RE the single query - i'm not sure I follow the code you posted here, but would be happy to refactor what I have; can you add some detail?
again not exactly a fan of this writing tests for trivial operations. i guess that makes me against tdd, and this whole process in general. oh well.
good luck with the series.
Heh - sorry for using the "web compiler".
This is almost purely a matter of style, but I prefer to preserve the "IQueryableness" of a sequence as long as possible, so consider:
var categories =
from c in _repository.GetCategories()
join sub in _repository.GetCategories()
on c.ID equals sub.ParentID
into children
select new Category {
ID = c.ID,
Name = c.Name,
ParentID = c.ParentID,
Subcategories = children
};
... and you'll still have an IQueryable that you can pipe through some additional filters without having executed any real processing.
@K Scott- I get it... thanks. This is an issue I'll actually be addressing in Part 3: When To Cut off IQueryable. For me, that's at the edge of the Service class - sneding IQueryable out into the application (as a practice) can be a security issue as well as a bomb.
Finally, this example is fine - i tend to like to "chunk out" the IQueryable calls as they make the intent more readable. You add an extra line or two - but I think it's clearer.
Thoughts?
I have to agree with Aaron in that you should REALLY write your test first for a real TDD experience. That helps designing your API and not doing more than what's really needed.
A good book on the subject is "Applying Domain Driven Desing and Patterns" by Jimmy Nilson. (you can go directly to chapter 3 which focuses on TDD).
And as someone else already pointed, audio is really sucky when you are chatting with the other guys, almost inintelligible to me. But I really like the idea on this kind of collaboration. It gives a real sense of comunity. :)
Yup - I understand that tradeoff. Readability is king [evem though beauty is in the eye of the beholder ;)]. Looking forward to hearing about your afternoon.
I found the video useful and helpful. I can understand why you'd want to cut TDD corners at the beginning.
Can't wait till you actually hook it up to a database, but which one are you going to use?
I'm really interested in how you would abstract/decouple a LinqDataContext from it's clients.
I'm not so sure about returning the IQueryable from the repository. It seems to me that it would make it difficult to plug in a different repository since your repository would have to implement the IQueryable, and it does not seem trivial to make one that performs well: msdn2.microsoft.com/.../bb546158.aspx
I know you just built a test repository, but suppose there were thousands (or millions) of categories in your database. Would you want to build an IList of them all to pass down to the Service for it to query? LINQ to SQL makes it easy, but what if you wanted to use MySql or another database? You'd have to have a LINQ to X implementation for it to work. What do you think?
@Lance - good question and I go into this in Part 3 a bit. Phil and I talked a bit about this and I think, for now, requiring an IQueryable interface is OK given that in about 6 months a lot of DAL tools will move that way.
For instance, LinqToNHibernate is on the way, and I'm working on LinqToSubsonic as well.
Good question though :).
Rob, is it weird we use your name in our office like you work with us? If just the first name "Rob" is used in our office everyone knows we are talking about you, its pretty creepy.
We just love your stuff! We are huge Sub Sonic fans and are jumping all over the ASP.NET MVC framework right now and doing some small development projects in it.
We never post, but we are constantly stalking you.
Great job!, Rob. I can´t wait for the rest of WebCast Series. It Was nice to know Ayende, but hear him is a little hard. But the material included is so nice..Good Job !.
Ayendes voice recording was even worse in this one, even with headphones I couldn't really understand it.
Ayendes voice recording was even worse in this one, even with headphones I couldn't really understand it, sorry.
The part I struggle with this how this connects to MVC. Sure, you don't write production code until it is mandated by a test, but what if your MVC site doesn't need the method mandated by the test? In the past, I've started with TDD and ended up with a bunch of methods that are a bad fit for the app I'm developing.
Don't get me wrong. I understand what you're doing and I appreciate how it improves the quality of your plumbing code, but how does MVC take advantage of this better than plain ole web forms? How does MVC make the tested code easier to produce? How do the tests flow out of the MVC app, i.e., how do we know these are the correct tests for the MVC app?
Something's is wrong here ... just see "The connection was reset" in the iframe. Help!
@Neil: Hey, which identity service does this site use? How do I get "my" avatar to appear next to my comments? Thanks.
@flipdoubt: I use myOpenID, (http://www.myopenid.com). Didn't even realise it could pick me up from here!
Cool. So how did you "sign in" to Rob's site so it knows to associate your comments with your OpenId?
@flipdoubt: I have no idea. It must be linked to my email address!