Hanalei, Hawaii Tuesday, February 09, 2010

MVC Storefront, Part 10: Shopping Cart Refactoring and Membership

In Part 10 I refactor the initial go at the cart, and also implement the beginning of what will be the Authorization system for the site. Previously, On The MVC Storefront Part 1: Architectural Discussion and Overview.

In Part 10 I refactor the initial go at the cart, and also implement the beginning of what will be the Authorization system for the site.

Previously, On The MVC Storefront

You can watch Part 10 here (52M, 26 Minutes)

Get The Code Here


Feedback is Good
I want it, I need it. I want to ask you to please keep your comments as professional as possible - and by that I mean don't assume that I think I know everything :). I'm openly asking for your comments and thoughts so if possible I'd like to veer away from discussions of what I know to more about what you think.

Not everyone agrees, and no one approach is correct.

That said - I'm very interested in your feedback on this segment.

Technorati Tags:


Simon - May 14, 2008 -

Having a few problems with the source from codeplex.

Seems to be missing AssemblyInfo.cs and when i try to run i get a compile error

raised both as issues on codeplex www.codeplex.com/.../View.aspx http://www.codeplex.com/mvcsamples/WorkItem/View.aspx?WorkItemId=879

Rob Conery - May 14, 2008 -

Hi Simon - sorry bout that - bit if a copy/paste error on my part :). The Web.config was set with certain profile props - strange why I didn't get the error... Anyway, source is reloaded.

Chad Smith - May 14, 2008 -

This is an aside to the current screencast (which I'm currently watching).

Whilst looking at the previous screencasts, one thing that stands out is that "category/subcategory" is hardcoded throughout. If my imaginary client comes to me and says they want to go infinately deep in categories like amazon does for example, how then does the system cope? What are your thoughts on this and how nicely this plays with MVC? indeed does implementing this break you out of the nice url methods we've been using?

jim - May 14, 2008 -

Maybe a n00b question, but why create a "Users" table when you already have the aspnet_users and other aspnet_* tables? It seems like you're storing the information twice.

Mike - May 14, 2008 -

Great series so far. I just have a question about your validation. Is there a reason you are throwing exceptions for validation? Exceptions are expensive and is invalid data from the user an exceptional case or is it something we can predict?

Jason Simone - May 14, 2008 -

I like exceptions and think they are often the way to go for anything that is intended to 'break' program execution. When considering their usefulness, perhaps we should set the performance drawbacks aside temporarily and ask if they are a good design? Then we can better determine when they are worthwhile?

It makes a lot more sense to me if all the page has to worry about is the natural state of things and exception handling can fill in the gaps. I'm not decided on loving exceptions for all uses, but I am also not quick to disregard them.

Rob Conery - May 14, 2008 -

@Chad: good question :). The model is set up to handle this relationship just fine as any category can contain 0 or more of another. The DB is set up for this as well, with a self-referencing join setup.

The only place where I get explicit about a one-step association is the Route (as you pointed out) and the service - so if the client changed their mind, we can accomodate pretty nicely by changing 2 things.

This is much more a client question in terms of presentation - how many steps do you want to go down before you see the goods?

@jim:>>>why create a "Users" table when you already have the aspnet_users and other aspnet_* tables? It seems like you're storing the information twice<<<

I mention this in the video a bit, but the idea here is that the aspnet system can be located anywhere. In a hosted scenario, you can run many sites off of one membership DB so I can't plan on the aspnet tables being there (people might remove them in favor of their own system).

Moreover - people might want to plug in their own auth system - such as Active Directory. I don't want to confuse the auth system with the notion of a User.

It's a good question - and you're right, we're storing some of the info twice (like email). But some we're not - like First/Last name.

@Mike: >>>Is there a reason you are throwing exceptions for validation? Exceptions are expensive<<<

In general, if a method can't do what it's supposed to do, throw. Exceptions are only expensive when thrown/caught in a loop - in this case we're halting an operation and notifying the caller that it didn't work - not really that expensive as everything stops at that point.

I hear you on this however and I'm hoping that client-side validation can mitigate throwing exceptions for invalid data.

Christoph - May 14, 2008 -

Great series! I love MVC and this new way of learning it (instead of looking at the source code of a starter kit and then trying to understand what the author of the starter kit thought while he was writing it ...)

Coming from a non "en-Us" culture, I especially like the way you treat localization in your Product catalog. A propos, a small remark here: actually your integration tests fail on a non "en-US" system. The reason is that you implicitly suppose in your tests that "en-US" is the current culture. Here I added the line

// To ensure we use en-US culture for our tests System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");

in the setup method of [TestInitialize] of the files CatalogIntegrationTest.cs and ShoppingCartIntegrationTests.cs

I don't know whether this is the best way of dealing with it (as I said, I am learning!) but it works.

Mike - May 14, 2008 -

I feel that the beginning of this episode was more about refactoring because of what people commented. As always with architecture, some like it this way, others like it that way. However, did the client request this change in architecture on the cart? Would he/she not be better served if the time was spent implementing features (as long as the architecture is working)?

I have some other questions too, hope you don't mind.

Is the .dbml file in Commerce.MVC.Data really SqlServer specific? I ask because it's in a folder called SqlServer and I assume you mean Microsoft SQL Server.

Is the CommerceMVC.bak the database? I can't attach it, did I do something wrong?

Rob Conery - May 14, 2008 -

@Mike: >>>I feel that the beginning of this episode was more about refactoring because of what people commented<<<

It was - but the cart was ripe to be refactored (still is - and I have some changes I will put in) anyway so I did a pass now. That's what this whole thing is about.

>>>However, did the client request this change in architecture on the cart<<<

In general, clients don't dictate architecture - though I'm sure some do. However since I've said the community is the client - the answer would be "yes".

>>>Would he/she not be better served if the time was spent implementing features<<<

Refactoring is part of the process I'm using, so "not really" is the answer here.

>>>Is the .dbml file in Commerce.MVC.Data really SqlServer specific<<<

Yes - it's a LinqToSql file, which is SQL Server-specific.

>>>Is the CommerceMVC.bak the database? I can't attach it, did I do something wrong<<<

It's a backup - you'll need to use the Restore command, not attach.

Erik - May 14, 2008 -

Shopping Cart Item

int _quantity = 0; isn't used.

Couple of questions in understanding the application:

Are carts Orders? Do the remain beyond when they have been processed? Or Do Carts create Orders and that is something that will be refactored once a user need to check status of his "orders/carts".

Will you still have a service class for this (Carts/Orders)? Or will you have your application calling the Repository directly to get a Iqueryable List of Carts. Once you have the requirement to get multiple orders/carts (Think a User viewing his completed Orders or a customer service rep looking through orders)

You could have a Service Class make a Cart look really smart by keeping the repository stored with the cart like you refactored Making your service/Business Logic Class A Static Class and doing 90 % the methods (the one with a first parameter of type cart as extension methods

while still allowing for other Methods i.e.

public static cart Load (this cart, username)

{

\\Load the cart for a particular username using its existing repostitory or a new one if null.

}

public static List <carts> GetUnfilledOrders()

{ return GetCarts().WithCompleted().WithUnfilled().ToList()

}

to reside there as well.

I know, you don't have a requirement for that yet :-), like I said pretty green and trying to get a good feel for why you are making the design decisions you are making.

Erik

Rob Conery - May 14, 2008 -

@Erik: Thanks for the code check :). I'll fix that...

>>>Are carts Orders? Do the remain beyond when they have been processed? Or Do Carts create Orders and that is something that will be refactored once a user need to check status of his "orders/carts".<<<

What i did in the past is to just start an order for a user and set the status to "not checked out" - so there was no notion of a cart in the DB.

In terms of OrderService etc - I think you can see where I'm going :).

Simon - May 15, 2008 -

in SqlUserRepository.cs

what's the point of having an instance of DB when most of the actions create their own temp instances?

and if their is a point of having the instance of DB shouldn't SqlUserRepository implement IDisposable since DB does?

Same question for SqlShoppingCartRepository and SqlCatalogRepository

Rob Conery - May 15, 2008 -

Hi Simon - the idea here is we can increase performace a bit by only having one context created for Read-only operations. In the future I'll go more into this with pre-compiled queries etc. For now I've just turned off object tracking.

The perf tweaking show will be fun :).

George - May 15, 2008 -

I like the localization features of product catalog very much too.

It would be wonderful if you could take this one step further and make this application even more "localization friendly". Some suggestions:

* Use resource files instead of hardcoded "US-en" string name for common terms on the website, such as "My Account", "Log Out", "Home", "Contact Us", "Add to Cart" and etc.

* Write test cases to test the localization features.

by the way, I installed the newly released visual studio sp1 and .net framework 3.5 sp1 beta. I ran the latest code from codeplex and got the following exception

[NullReferenceException: Object reference not set to an instance of an object.]

System.Collections.Generic.Dictionary`2.FindEntry(TKey key) +42

System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value) +16

System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ParameterInfo parameterInfo, IDictionary`2 values) +440

System.Web.Mvc.ControllerActionInvoker.GetParameterValues(MethodInfo methodInfo, IDictionary`2 values) +610

System.Web.Mvc.ControllerActionInvoker.InvokeAction(String actionName, IDictionary`2 values) +247

System.Web.Mvc.Controller.Execute(ControllerContext controllerContext) +330

System.Web.Mvc.Controller.System.Web.Mvc.IController.Execute(ControllerContext controllerContext) +50

System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) +480

System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext) +96

System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext httpContext) +50

System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +181

System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

Anyone else ran into the same issue?

David - May 15, 2008 -

Hi Rob,

Appreciate your work on this series :)

Looking at the SqlUserRepository.RegisterUser(...) method -- there's lots of un-unit tested logic in there. I know you've covered some of this with integration tests, but would love if you could cover some techniques for getting this stuff under unit test, as this is something I sometimes struggle with.

For example, should I wrap and mock the built-in Membership stuff and verify my code calls the API properly? Or use TypeMock to mock Membership directly? Or extract the logic into a new class that I can test directly?

Regards,

David

Fredrik Norm&#233;n - May 15, 2008 -

@Rob:

You want to know what people think about the implementation you have, and I hope mines is welcome too ;)

Is there a reason why you don't add the item to the Cart's item collection, and then call the ShopingCart Repository to persist the cart?

myShoppingCart.AddProduct(myProduct);

This code will only create a ShopingCartItem with the product and add it to the Items collection, nothing more. When creating a model we try to reflect it to the real-world. In a real-world the cart will only hold products; it's like a simple container. Then if you want to persist the cart, you pass down the whole Aggregate Root (The Enitty) "ShippingCart" to the ShopingCartRepository, which have the responsibility to persist the entity. So each time a customer add an item, you ad dit first to the cart and then pass the cart down to the Repository. By using an ORM and enable track changing, it will handle the insert or updates for you. No need to track the changes by yourself.

Your ShoppingCart works almost similar to an ActiveRecord. The benefits this approaches has is the simplicity, easy to build and easy to understand. But can be a problem when the business logic is going to be complex. That is what will lead you to use the Data Mapper pattern instead.

Have you any thoughts about supporting anonymous users? For example if I visit a shop, I can put stuff into the Cart without identifying myself. First when I want to make an order of the Cart, I need to identify myself. In this case when there is a new Customer, my app needs to both create the Customer and then place the Order. This "flow" is something that I add to the Service Layer and I also use UoW. It means that the Service Layer need an instance of the DataContext and pass it down to the CustomerRepository and also the OrderRepository, any thoughts on this?

Have you notice that your model will sort of be designed after the Spikes you have created and the limitation of LINQ to SQL and also dependent to the technology you have used even before the model is created!?

I know that you don't use DDD. So if you don't care about DDD, just ignore this part. But I love DDD I have never built such stable and maintainable apps in my whole life before I start using DDD. Data is often stored in a relation database, but the main view of the data is in terms of the domain object, not in terms of tables etc (That is why your Service layer shouldn't build the Aggregate Root "Entity/domain object" for you, the entity is the data, and re Repository has the responsibility to give you the data, in this case the domain object. By letting the Service Layer create your entity, you will end up with repeating yourself or at least other Services that want to use your Repository need to repeat the same code to create the domain object. If you want to build your domain object, you can instead use the Factory pattern, which of course repository can take benefits of). Core domain logic is kept in the domain model, rather than being spread across the Service layers of the application. The primary focus should be on the domain and the domain logic, complex domain designs should be based on a model.

Fredrik Norm&#233;n - May 15, 2008 -

@Rob:

You could use the Profile feature to save your ShoppingCart. By doing so you can easy store the ShoppingCart for authenticated an anonymous users. But there will of course be problems when you need to make changes to your shopping cart. I used the Profile in one small app to make it simple and in that case I used the Profile instead of the Session. I wanted the information to be left when the user logs in later. But in a large app, I will not use the Profile feature.

About TDD and Mock. This is how I use it, maybe it's totally wrong ;)

I use TDD to create test that will "create" the design of my Repositories, when it comes to add the code to persist my domain objects, I instead create my Mock objects, and at this stage the design of the Repository is already set. Because I have the test for the Repository already created, I can reuse them for my integration test later. I never write test against my Mock object directly, because the test does not have any purpose, because what am I testing?

Rob Conery - May 15, 2008 -

@David: >>>Looking at the SqlUserRepository.RegisterUser(...) method -- there's lots of un-unit tested logic in there.<<<

Can you be more specific? One thing I don't want to do is test the Membership API - that's part of the framework. What other logic are you speaking about? Mocking the Membership API isn't easy - in fact it's just about impossible; but it doesn't matter since as I say, it's part of the framework.

@Fredrik: >>>Is there a reason why you don't add the item to the Cart's item collection, and then call the ShopingCart Repository to persist the cart?<<<

Why yes, I go into this in depth in the screencast :). I understand the approach you're going for, but in order to effectively synch the cart, I needed to write some extra code. At the end of the day - what do I gain?

>>>Your ShoppingCart works almost similar to an ActiveRecord<<<

Not really - ActiveRecord object persist themselves; I'm still telling the Repository what to do.

>>>But can be a problem when the business logic is going to be complex<<<

It's a shopping cart - how much more complex does it need to be?

>>>That is what will lead you to use the Data Mapper pattern instead.<<<

?

>>>Have you any thoughts about supporting anonymous users<<<

Yes - it's forthcoming.

>>>Have you notice that your model will sort of be designed after the Spikes you have created and the limitation of LINQ to SQL and also dependent to the technology you have used even before the model is created!<<<

?? My model came first - cart second. The cart looks nothing like my DB - not sure what you're talking about.

>>>the main view of the data is in terms of the domain object, not in terms of tables etc<<<

This is what I'm doing indeed, though I'm not holding fast to any theory.

>>>You could use the Profile feature to save your ShoppingCart<<<

Yes indeed - this will be part of the next webcast.

>>>By doing so you can easy store the ShoppingCart for authenticated an anonymous users<<<

Reporting?

>>>But there will of course be problems when you need to make changes to your shopping cart<<<

? Why's that ? It's just XML that gets dumped/loaded each time. It doesn't care about what your object looks like. You just can't Report on it - which will be important later on when our customer wants to know something about what people are doing with their carts :).

>>>I never write test against my Mock object directly, because the test does not have any purpose, because what am I testing?<<<

If you're wondering why I'm testing my stubbed TestXRepository (it's not a mock) - it's because I want to be sure that what I do there works.

Fredrik Norm&#233;n - May 15, 2008 -

@Rob:

>>>Your ShoppingCart works almost similar to an ActiveRecord<<<

**Not really - ActiveRecord object persist themselves; I'm still telling the Repository what to do.

What your Cart does is actually persist them self but you moved the persist logic it into another "class", the Repository, but still the Cart will execute the saving. That is why I wrote "almost similar". The ShoppingCart is not a POCO, you have a dependency to the Repository, if the Repository will be changes in the future; you also need to update the ShoppingCart domain object. If you instead pass it down to the Repository, you will not need to care about the dependency etc.

>>>But can be a problem when the business logic is going to be complex<<<

**It's a shopping cart - how much more complex does it need to be?

Well the cart may not be complex; I was more referring to the Active Pattern problem in general. Maybe I wasn't clear enough.

>>>Have you notice that your model will sort of be designed after the Spikes you have created and the limitation of LINQ to SQL and also dependent to the technology you have used even before the model is created!<<<

**?? My model came first - cart second. The cart looks

nothing like my DB - not sure what you're talking about.

If you don't know about the technology you should use, you may have designed it in a different way, or!? I can also be wrong here. But I can promise that you model will look different if you only focus on the domain concept and leave the technology and UI etc. Now you focus on UI and how to persist the data even if your model isn't completed yet. I didn't say anything about the DB, only about the technology used. When using DDD for example, we create the domain model first, when it's done and tested, we start thinking about how to persist the model. So at this case I often end up using nHibernate because LINQ to SQL don't fulfill the requirement. I didn't say that you are doing anything wrong.

>>>By doing so you can easy store the ShoppingCart for authenticated an anonymous users<<<

**Reporting?

I was referring to Profile feature, where it can save data for anonymous user, and also authenticated user. So when a user logs in an event is raised and you can move the anonymous data into the authenticated users profile data.

>>>But there will of course be problems when you need to make changes to your shopping cart<<<

***? Why's that ? It's just XML that gets dumped/loaded each time. It doesn't care about what your object looks like. You just can't Report on it - which will be important later on when our customer wants to know something about what people are doing with their carts :).

Oh, I was more thinking about using BinaySerialization, so never mind.

>>>I never write test against my Mock object directly, because the test does not have any purpose, because what am I testing?<<<

***If you're wondering why I'm testing my stubbed TestXRepository (it's not a mock)

Ok, but when you pass in the TestRepository to the Service when you test your Service, you actually use the TestRepository as a Mock object, right!? If you look at the repository, what you really test is if you can return a List with your own defined objects.

**it's because I want to be sure that what I do there works.

But isn't it obvious that a method can return an objects? If you want to test your Repository in isolation you need to mock the infrastructure service it will use, if you don't you have an integration test. If you mock the infrastructure service, what are you really testing, if the method behaves as expected? If you want to test the correct behavior you need to involve the Infrastructure service, right?

David - May 15, 2008 -

@Rob: >>> Can you be more specific? One thing I don't want to do is test the Membership API... <<<

Firstly, 100% agree about not testing the Membership API. But I would like to test that my code interacts with the API properly (this is obviously a personal preference -- I assume not everyone would find it necessary to test to that level of detail). Secondly, this comment is too long, sorry :-\

I'm looking at the RegisterUser() method here: http://tinyurl.com/4rsh8k

The basic logic as I follow it goes like this:

* Try and create user using Membership API (which is the only Membership API call we use here)

* If successful (MembershipCreateStatus.Success):

- create User object

- try to persist to DB

- if this fails call DeleteUser and throw

* If not successful, expect InvalidOperationException (different message per MembershipCreateStatus)

So from there we could test:

1. Should throw InvalidOperationException if Membership.CreateUser fails

2. Assuming MembershipCreateStatus.Success, call is made to persist User in DB

3. Assuming MembershipCreateStatus.Success and persist User call fails, call DeleteUser and throw.

To do this I guess we'd need to fake the Membership.CreateUser() call to be able to return canned values for our test. And we'd need to fake calls to persist the User in the DB so we can check the User argument and also throw on demand. All this seems like a lot of work, which is why I was hoping to get some hints on how to test logic like this. In the screen cast you mentioned that you initially forgot to re-throw in test case 3, so maybe the extra work would be worthwhile to help make the desired behaviour clear?

My guess is that the answer from most people would be that it's too hard for too little benefit, and we'll just cover with integration tests, which is cool. Or maybe coupling tests that tightly to the implementation is a bad idea anyway? Or maybe the difficulty testing means we could use a neater abstraction?

I'm not sure, but if there was a nice way to test some of this behaviour then I would love to hear it, hence my original question :) (The only ways I can think of are a bit messy -- like moving the Membership.CreateUser() and DB calls to virtual methods and using a test shunt or partial mock, or maybe TypeMock can do it easily?)

I always seem to trip my self up on these kind of questions when unit testing, so maybe I'm just over thinking it. Appreciate any input you can give, even if it's just letting me know that I'm crazy and need serious counselling :-)

Regards,

David

James - May 15, 2008 -

Hi Rob,

I know you are not concentrating your efforts at the moment on "performance" but I couldn't help noticing that, if you load up the /store url the database gets hit over 100 times (using SQL profiler here). I think this is due to the "LasyList" approach. It seems to load image objects for every product in the database regardless of whether they are being used on the page? Is this something you expect to refactor in the future?

Rob Conery - May 15, 2008 -

@Fredrik: >>>If you don't know about the technology you should use, you may have designed it in a different way,<<<

When I started the whole app here, I wasn't (honestly) sure what to use. I knew I wanted to use LINQ/IQueryable so that narrowed things :). When SubSonic 3 comes out - who knows?

>>>you actually use the TestRepository as a Mock object, right!?<<<

It's actually called a "stub". A mock is something different - wherein you fake the presence of a dependency - like HttpContext. In this case I'm not faking it (really) since this is indeed a store. I think of it as a real DB - in memory and one that I create for each test as needed. It saves me the pain of creating a test database.

>>>But isn't it obvious that a method can return an objects<<<

Is it? How many times have you said "it seemed obvious but in turned out that..." :). I do it a lot. The point of the tests is to be sure I've structured it up; these are just some CYA tests - nothing more.

@David: I see your point :). This is a classic integration test and yes, I'll be sure to implement them moving forward.

@James: Yah, that's a little extreme isn't it :). I will be tweaking perf in the coming webcasts for sure.

David Jade - May 15, 2008 -

It seems to me (from watching SQL Profiler) that LazyList doesn't really work. There seems to be two issues here:

First, from looking at stack traces it seems that somewhere in the depths of Linq to SQL, the LazyList members are being touched which causes the inner IQueryable to be executed while constructing the model objects.

Second, it seems that sometimes a query in a repository that generates a model object that contains LazyList members seems to get wildly complex and pre-load a lot of data which should be lazily loaded. I say sometimes since it seems to happen for the first LazyList model member but not subsequent LazyList model members (in fact if you rearrange the model members, it will switch which one gets pre-loaded). This is not necessary related to using SingleOrDefault() either as it happen when returning IQueryable as well.

Calling SqlCatalogRepository.GetCategories().ToList() is a good example of this: The SQL query that is generated first returns each category. Then other queries fire that return each product in those categories, and each review for each of those products. Then the LazyList for Products.Images is touched, which causes the first issue to kick in and send a ton of SQL queries for each product to retrieve the images.

At first I thought this may be an issue since the query are dependent on each other but this doesn't seems to change even when you remove the dependencies between queries. For instance in SqlCatalogRepository if both the Reviews and Images queries are simply modified to not rely on p.ProductID (even though this does not produce the right results), the queries for these are still fired through to SQL when retrieving a product.

I am really curious as to how this might be addressed. It seems to me that the only way is to move the lazy retrieval to your model objects so that they are not part of the compound query, much like Linq's EntitySet does. However this may push the SQL knowledge into your domain objects. Perhaps something can be created along the line of Func<T, TResult> delegates which fires its own query for these lazy model members?

David

Fredrik Norm&#233;n - May 15, 2008 -

@Rob:

>>>It's actually called a "stub". A mock is something different.

Ops, yes, sorry.. Fowler write a article about Mock isn't subs because several developers use Mocks as stubs. Regarding to Test Double definition isn't your TestRep more like a Fake object: Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example)? I haven't looked at your whole implemention; only refer to your answer "I think of it as a real DB - in memory".

Well we stay to the word "stub". "Stub" and mocks aren't used in production environment, so why test them, what purpose does it gives us, what do we really test and why should we test them, what benefits does it gives us? I can see the use of the "stubs" as you say "It saves me the pain of creating a test database.", but why write test directly against your "stubs" to see if it can return "fake" data? To be honest I actually write unit test directly against "stubs" before, but it doesn't gave me any value because the code within the "stub" isn't even used in production. They are useful to test other stuffs, like Services. But to see if a "Stub" returns a specific object or hold an object in memory, what value does the test really gives us?

Rob Conery - May 15, 2008 -

@David - it actually does work (from what I've tried here) - the problem for me is how I'm mapping Products - in one spot I'm using SingleOrDefault() which trips the whole thing (I talk about this in part 8 I believe). I know I need to refactor - it's something I'll do later :).

One way around it is to Explicitly load the products for the categories - I can do this by passing in an overload or setting a property. I can also load things in order, or batch load them instead of one at a time.

The point is - i have a prototype to jam out and I don't want to dismiss perf - but at the same time I know I can fix this later.

@Fredrik: I honestly don't know what you and I are discussing anymore :). I think I answer this question at least 4 times in part 10:

>>>Stub" and mocks aren't used in production environment, so why test them, what purpose does it gives us, what do we really test and why should we test them, what benefits does it gives us<<<

... and I'll go into it here as well :). My TestRepositories (lets take Catalog) are a set of test data. I base all my other tests on this test set. So, as I mention, testing the test repository can be a little strange - sort of like going overboard.

At the same time, if you're expecting to have 50 test Products, each with 1 Category apiece - well isn't it worth at least one test to make sure that assumption is covered?

If you say no - I'm cool with that. I'm going to do it anyway :) as I tend to get sloppy sometimes and invert my numbers :). Can we move on from this?

David Jade - May 16, 2008 -

Hmm, that is not what I am seeing in the Part 10 codebase. When I run this SQL integration test:

SqlCatalogRepository_ShouldReturn_Categories_AsQueryable_WithACountof26

which doesn't touch the SingleOrDefault() function anywhere as far as I can see, I see > 100 SQL queries fire returning all of the data for the category's products, reviews, images, etc...

My question isn't do much about perf; I understand your perf-later argument. My question is at what point does that get balanced with a possible failure of what LazyList was intended to do? Yes this particular test passed as written but a side effect of running it highlights that LazyList might not be working as expected (at least for me). Perhaps a set of tests are missing for LazyList that would have highlighted the issue I am seeing here? Or perhaps I am not understanding how LazyList is intended to be used.

David

Rob Conery - May 16, 2008 -

Hi David - thanks - i was thinking of the cart but in looking at this more - i see what you're talking about. I'll take a look - probably a dumb bug :)

Fredrik Norm&#233;n - May 16, 2008 -

@Rob:

Sorry, I'm so slow.

>>>Can we move on from this

Sure, and thanks for your answer.

Are you going to add search functionality to your shop. For example where customers can search for a product based on different product information, for example "Screencast Microsoft Rob Conery".. where Screencast can be a category, the Microsoft, is the company that creates the product, Rob Conery is part of the screen cast etc, and if so, are you going to use the LINQ to SQL dynamic quires, because I would like to see that in action, never used it by my self yet.

Cyril - May 16, 2008 -

@Rob :

What David Jade said what exactly what I was trying to explain (I think that Perry and Andrew asked the same in previous blog comments).

The "custom" mapping mecanism seems to breaks the essence of IQueryable (deferred execution and optimization of SQL queries in that case).

Robin - May 16, 2008 -

>>When SubSonic 3 comes out - who knows?

Somewhat off topic here, but what is happening with Subsonic 3?

I see Subsonic 2.1 is now RC1, but there's been no news on Subsonic 3 for, well, a long time. I don't mean in terms of a release date just in terms of progress and what we can expect, especially as 2.1 appears to be nearing completion.

Alex Simkin - May 16, 2008 -

I am with Robin.

Any word about SubSonic 3?

martin - May 16, 2008 -

same thing

what about Subsonic 3 and what features can we expect of it ?

Thanks to rob

Chandermani - May 17, 2008 -

Hi Rob,

Amazing stuff !! Gr8 project to learn ASP.Net and LINQ.

One issue that i faced during deploying the solution on my machine was due to non default SQL Server instance name.

Since i had SQLEXPRESS installed. I updated the web.config connectionstring setting, but was still getting sql connection error page.

The problem turned out to be with LINQ to SQL, it was using a connection string embedded in Commerce.MVC.Data Project setting (You can see the "Settings" property page for data project).

This connection string also needs to be modified in case SQL Server is installed as named instance

Hope you highlight this in your later post, as it can same time for people tinkering with your app :)

Dirk Maegh - May 18, 2008 -

Rob,

could you plan on testing your delivered view output as well (the output before it is sent over the wire) ?

Imho view output needs to be tested as well.

I just cannot seem to find any examples on the net about view output testing, only controller and model testing :-(

Alex Simkin - May 18, 2008 -

Rob,

Because you call the system you are building "A Real World Example", when are you going to start to add:

- Logging

- Exception Handling

- Audit trail

- Instrumentation

- Life beat monitoring

- Performance Counters, etc.

It is not to say that you forgot any one of these, it is a qustion about "when in the development cycle we should start to think about these?".

Thank you very much, I am really enjoying your screencasts and your guest stars are wonderful.

Dave Arkley - May 18, 2008 -

Rob,

This is a great series, I was ready to start lerning this stuff the hard way, this series is a great help.

I notice that the Web project has references to both the service and data layers. I want to prevent UI developers from being able to bypass my business logic and update the database through the Linq to Sql generated objects directly. I feel the need to remove the DAL reference forom the UI project. I believe this can't be done with your architecture as it stands as the DAL does the data mapping, I guess this is why the UI layer needs access to the DAL too.

I'd rather pass the Linq to SQL DTOs (the generated ones which map tables) to the BLL and have it map to BLL DTOs and then consume those in the UI layer. I'm struggling to map DAL DTOs to BLL DTOs while the DAL is returning IQueriables<DAL DTO> as I'm having to iterate these and map each entry. Are you aware of a better pattern for this cross layer mapping, or of a way to stop UI layer programmers bypassing my business logic and updating the Linq to SQL DTOs directly?

I have moved the filters from the data layer to the services layer, as I consider the filters to be made up of business rules. For example in the forum code I'm implementing I've created an IsViewable() filter which uses rules to determine if 'now' is beyond a posts's publish date, before its expiry date and that a moderator has OK'd it for pulic consumption. That doesn't feel like data layer stuff to me.

Keep up the good work, roll on episode 11 :)

Robert G - May 22, 2008 -

Rob, great job, as everyone has been saying. I finally caught up with the videos.

I have to chime in with an architecture concern. LINQ kind of turns the traditional separation of concerns between Business layer and Data Access layer on its head, since it basically returns objects straight out of the query. For instance, your business objects (e.g. Product, User, Category, etc... classes) are in the Data layer. The Business layer (e.g. Commerce.MVC.Services) itself is a thin, a la web service, wrapper around the true business objects that are in the Data layer.

As a result, the model is leaking objects all the way to the presentation layer, thus the UI project must reference Commerce.MVC.Data.

I am not saying it's wrong - LINQ maybe a game changer here. Does it make sense to pull the business objects out of the Data layer and place them somewhere in between the .Service and .Data projects?

Final nit to pick: how come ShoppingCartID is a GUID, while all other primary keys are INT IDENTITY. Is there something special about ShoppingCart that it requires a GUID?

Thanks again.

Robert G - May 22, 2008 -

Oops, Dave Arkley already brought up the concern about UI referencing the Data layer. Should have read the comments.

Pepe - June 11, 2008 -

Hello Rob.

Can you explain why you are not using a transaction when storing data in the membership DB and in your DB at the same time?

Cheers.

Gecko