Home MVC Storefront

MVC Storefront, Part 9: The Shopping Cart

In this episode I dive into implementing the Shopping Cart in a basic way so I can run a spike to make sure my pattern can push data nicely back into the DB without problems.

Previously, On The MVC Storefront

 

You can watch Part 9 Here (27 Minutes, 32M)

Download the Code Here

I Ramble
Stuff's getting complicated here and as such it's getting harder to keep "a story" going. It's even harder to tell that story without rambling! You'll notice some rough cuts here - I have to keep the time on these down so I chop things up a lot.

Spike Time
Many people have been asking about CRUD and specifically mentioning that everything will likely fail when I write back to the DB. I will admit I'm not used to seeing mapping code in my project (it gets buried by the tool of choice) but I have to say I don't mind it one bit.

I have complete control over what gets mapped and how, and it's not in my way - so I like it. I still have not addressed concurrency yet - but I will in the future episodes when I tackle inventory.

Technorati Tags:
Justin avatar
Justin says:
Thursday, May 08, 2008

It may just be me but around 13:40 the video stops playing, I can still hear the audio but the rest is either blank or frozen on the ShoppingCartTest screen depending upon how I view it.


Josh Stodola avatar
Josh Stodola says:
Thursday, May 08, 2008

On my machine, at 13:40 it skips to 14:05 (using VLC Media Player)


Rob Conery avatar
Rob Conery says:
Thursday, May 08, 2008

Crap - confirmed. I am recreating the WMV now - will have it up in 45 minutes (it takes a while). Doh!


Josh Stodola avatar
Josh Stodola says:
Thursday, May 08, 2008

It happens (especially with WMV). Just out of curiousity, were you doing anything else on your computer while it was encoding?

(PS: Your comment form needs a "Remember Me" checkbox)


Joe avatar
Joe says:
Thursday, May 08, 2008

Your test "ShoppingCartService_Should_Return_SingleCart_ForTestUser" doesn't actually test what you think it's testing. Since "testuser" doesn't actually exist ("testuser1" does) you are creating a new cart with that test instead of returning the existing one (the behavior is the same "nouser"). Your test should be checking that the cart you are getting back is the exact cart you are looking for.

Also, the behavior of your TestCartRepository and your SqlCartRepository are different. The SqlRepository returns all items in all carts while the TestRepository returns the same (different) 5 items (that all have the same ID).


Rob Conery avatar
Rob Conery says:
Thursday, May 08, 2008

@Joe - thanks, good catch :). I'll fix. RE the behavior diff - I'll tweak that too :)


Ajay avatar
Ajay says:
Thursday, May 08, 2008

Hey Rob,

Video is still frozen at around 14:30 onwards.

Keep-up the good stuff you have been doing.

Thanks.


Ryan avatar
Ryan says:
Thursday, May 08, 2008

I'm not sure if this is the problem that Joe mentioned or not, but the test SqlCartRepository_Can_Add_Product_3() fails after the first run because the test item remains in the SQL database, while the test checks for a count of one.

If you take a count before adding, you can make the test work by changing the assert to Assert.AreEqual(productCountOrginal + 1, productCount), but this leaves the new item in the database on every test run. I'm not sure if this is the desired behavior. Should you clean up after your tests so the database is in the same state every test run?


Stephen avatar
Stephen says:
Thursday, May 08, 2008

One comment, I'm about half way in and you are working with getting the cart repository to create a cart for you.. doesn't this sort of break the model where your application model is the one that is in control of creation?

It seems to me that you should be doing:

var newCart = new ShoppingCart();

cartRepository.AddCart(newCart);

?


Sirrocco avatar
Sirrocco says:
Thursday, May 08, 2008

I'm curious about how you'll manage the ShoppingCart scenario on the Web. Will you load the ShoppingCart on every request ? or will you keep it in Session.

If you keep it in session you might run into trouble with the disconnected objects ? (might be mistaken)

And if you hit the database on every request - again might be a bit of a problem.

Hope you deal with this scenario in Part 10 :D.


Stephen avatar
Stephen says:
Thursday, May 08, 2008

@Sirrocco, going off the pattern, the repository shouldn't be expected to handle the caching here.. since the data in the application model is supposed to be disconnected anyway, the service class that interfaces with the repository should handle any caching of that storage data.. at least, this is the way I see it because the repository here is the component thats interchangeable, and as such, should be expected to be lightweight.. the service class is that which expands the lightweight functionality into more of a complex model..

(which I think was what rob was sort of saying with his hose pipe analogy)


Jimit Ndiaye avatar
Jimit Ndiaye says:
Thursday, May 08, 2008

I second Stephen's comment about putting the ShoppingCart repository in charge of creating carts. It's job should be simply persistence and retrieval. Additionally, you mayn't want to actual persist the cart until required i.e when the user wants to save the cart to complete the purchase later or when checking out (if the cart hadn't been saved previously). Otherwise you could end up a database littered with orphaned carts.

Additionally, creating a new datacontext for each CUD operation seems a little excessive and may bite you in the back later with performance issues. Instantiating a datacontext is an expensive task I'm told. Alternatively you could implement a unit of work pattern in which you pass around an object representing the unit of work (containing a data context in the case of Linq-2-SQL) then only call Commit (or submitchanges) when all pending changes have been registered with the unit of work, which can then be disposed of.

Something along the lines of:

Public Interface IUnitOfWork

Inherits IEntity(Of Guid)

Sub RegisterAdded(ByVal entity As IEntity, ByVal repository As IUnitOfWorkRepository)

Sub RegisterChanged(ByVal entity As IEntity, ByVal repository As IUnitOfWorkRepository)

Sub RegisterRemoved(ByVal entity As IEntity, ByVal repository As IUnitOfWorkRepository)

Sub Commit()

Sub Cancel()

End Interface

What do y'all think?


kYann avatar
kYann says:
Thursday, May 08, 2008

@stephen, i think you're right, the repository shouldn't be the one who create the cart.

@sirrocco, i know for sure that loading the cart on every web request doesn't create some performance issue.

The screencast was very speed compares to the other, and i think an explanation is missing about the cart being loading by the "username".

A cart can be created when you're not authenticated, so in this case you don't have any "username". So what really is the username ? a sessionid ? If so, you should rename it.

Thanks again for those screencast, i think they are very interesting, and i really like the repository pattern that you are using.


sirrocco avatar
sirrocco says:
Thursday, May 08, 2008

Thanks for the input , I wasn't saying that the repository should change in any way - it should remain "simple".

But I'm just having a "weird" experience at the moment , where the client insists on saving the ShoppingCart on every product added/removed(or quantity modified).

I just now looked a bit more at the project and I see that he isn't using the exact mapped entityes, but is creating new ones . So I guess it wouldn't be a problem with detached objects .(hopefully Rob will go into this once the project progresses. )


Benny avatar
Benny says:
Thursday, May 08, 2008

The username on the cart did throw me off too.


Stephen avatar
Stephen says:
Thursday, May 08, 2008

I agree with the username thing on the cart being a bit odd, it seems to be that a cart shouldn't have any understanding of a username.. a cart should have an identity that can be linked to a session, and an order (which may well be related to a username if you have streamlined registration (ie, anon orders force you to simply enter a password since you've supplied an email already)).. as far something like amazon, where it watches a users cart, again the cart shouldn't be the one that see's the user.. just that theres a service that is active for logged in users, that watches cart sessions..


Rob Conery avatar
Rob Conery says:
Thursday, May 08, 2008

@Ryan: >>>I'm not sure if this is the problem that Joe mentioned or not, but the test SqlCartRepository_Can_Add_Product_3() fails after the first run because the test item remains in the SQL database<<<

Yah - this is just a spike and yes I should clean it up - but it's not meant to be an automated test just yet. Probably best to clean it all up :).

@Stephen: >>>doesn't this sort of break the model where your application model is the one that is in control of creation<<<

Excellent point - will refactor this :).

@Jimit: >>>Additionally, creating a new datacontext for each CUD operation seems a little excessive and may bite you in the back later with performance issues<<<

Creating the context isn't all that expensive, but your point is well taken. If I don't do this, however, object change tracking will cause a memory leak ;).

@kYann:>>>A cart can be created when you're not authenticated, so in this case you don't have any "username". So what really is the username ? a sessionid ? If so, you should rename it<<<

The username can be anything, and I don't have a requirement just yet for an anon user :). Oh hey - the client just called and asked for that! In this case I'll use a GUID and assuming at some point I'll recognize them, I can swap the GUID as needed.

@Stephen: >>>it seems to be that a cart shouldn't have any understanding of a username<<<

There are so many different ways of handling carts; the best I've used is no cart at all - just start filling out an order :) and set it to "not checked out" - which is what a cart is anyway.

Your point is taken though. We need to do customer recognition (membership) so the concept of userName here is important - but I like your idea RE Amazon. Can you send me an email with some more info?


pmw avatar
pmw says:
Thursday, May 08, 2008

Rob, can you explain what you mean about the memory leak with object change tracking? Are you referencing some known problem in LinqToSql, or some pattern that is known to cause a memory leak?


Rob Conery avatar
Rob Conery says:
Thursday, May 08, 2008

@pmw: "Memory Leak" was too strong - no there isn't a hole in LinqToSql; rather if I was to leave ObjectTracking on with a Singleton pattern, it would track each instance I made of an object, and eventually I'd run into a problem with it trying to track all of em :).

ObjectTracking is there to handle concurrency issues - so if you pull a record from the DB, it will store it in it's tracking store, and you can change it, tweak it, do what you want and it's always tracked. Only when you use "SubmitChanges" does it go back to the DB with it.

So you can imagine if I have a really busy site and one singleton context. The more carts/items/products etc you create, the more it has to track and will hold all of them in memory.

Probably not a huge issue - but if you leave it long enough, it will be.


Doug Mayer avatar
Doug Mayer says:
Thursday, May 08, 2008

I'm loving the screencasts, but mark up another person who'd like to see some coverage of authentication and authorization in the coming shows. :)


Moses avatar
Moses says:
Friday, May 09, 2008

I realy hope this is a hobby project and not a best practice for other developers, if so then sorry but you really need to learn OOP for real and unittesting etc. There are many good books out there. When i saw your code you broke atleast 5 OOP and code principles.


Robert avatar
Robert says:
Friday, May 09, 2008

@Moses: Could you explain these broken OOP principles?


Fredrik Normén avatar
Fredrik Normén says:
Friday, May 09, 2008

Why do you test your Mock Repositories?

[TestMethod]

public void ShoppingCartRepository_Should_Return_Carts() {

IShoppingCartRepository rep = new TestShoppingCartRepository();

IList<ShoppingCart> carts = rep.GetCarts().ToList();

Assert.IsNotNull(carts);

}

They only give you your predefined objects. So what you basically test is to see if C# will work, just a thought ;)

Your ShoppingCart have an Items property, why not use it to add your products and pass the ShoppingCart to the Service only? Is there any reason why you pass both the ShoppingCart and the Item downs to the Repository and then bind them together, maybe I missed something?

Product product = catalogService.GetProduct(1);

ShoppingCart cart = cartService.GetCart("testuser");

var shoppingCartItem = new ShoppingCartItem(product, 1);

cart.items.Add(shoppingCartItem);

cartService.Save(cart);

My last comment and it’s about this code:

ShoppingCartItem item = cartService.FindItem(cart, p);

You already have the Cart, so why do you pass it to the Service and let it find an Item based on a specific product? Why don’t you use the Cart’s Items property instead (You can get the items from the cart when you already have the cart)? An entity like ShoppingCart holds its state. You can then add logic to the entity that will work against its state; in that case you don’t need to pass it to the Service layer.

ShoppingCartItem item = cart.Items.WithProduct(product).SingleOrDefault();

When looking at your code, it's more functional oriented than object oriented programming, but there is a reason for everything, so you probably have a good reason for that!?


Fredrik Normén avatar
Fredrik Normén says:
Friday, May 09, 2008

@Moses: The interesting thing is when we use TDD, we can end up without using any OOP at all, and the result can be simple old functional programming. It’s the test that drives the design. But I agree with you about the OOP stuff, but there is a reason, and maybe TDD is just that kind of reason..


Moses avatar
Moses says:
Friday, May 09, 2008

OO and OOP are from the begging referred to a real world object mapping to classes. Take this line of code:

ShoppingCartItem item = cartService.FindItem(cart, product);

When you are shopping on a mall with a cart, do you seek in the cart or let something else seek in it for you?

var product = cart.FindItem(product) <-- this is how it should be.

You check the cart for the item. This is OO design with OOP.

What about a car? CarSercvice.StartCar(landrower); Why? The car shall have the responsibility to start. What a bout an elevator?

Foo.GoDown(elevator);

Foo.GoUp(elevator);

This approach is more functional than OOP.

This is more accurate in an OO world.

landrower.Start();

elevator.Up();

elevator.Down();

I also pointed out other principles and can mention some. YAGNI (you aren't gonna need it), KISS (keep it simple stupid), DRY (Don't repeat yourself), SrP (Single responsibility Principe).

When using Robs stuff you need to do lots of thing all over again and got lots of services that aren’t needed etc.. The repositories in his design always need services to work in a shared environment. The main reason of repositories (as the repository pattern are defined) is that they don't make you write redundant code if reused in other systems. In this case, you can't do much with the repositories; you lay too much repository-specific code in services so in fact your services are some kind of spaghetti of repositories rules and services rules. That make the whole design messy and that's when you broke some of the principles for others and yourself. (Can talk about this in hours...)

Regarding that TDD make code be more functional? I don't agree there. TDD is a more outside in design. You start with the outside and then build the inside. You can very well do OO and OOP with TDD. As with the shopping cart.

You test the cart and the carts methods as for the FindItem method. That's the one of the nicest thing with TDD. But then you must know design too, TDD don't make good design it's up to you to made it. And thanks to the refactoring-often-part you simple redesign it to more OO/OOP if you want too and know how too. To design a system need design-skills…

As a known developer once said:

"Any fool can write code that a computer can understand.

Good programmers write code that humans can understand."


Fredrik Normén avatar
Fredrik Normén says:
Friday, May 09, 2008

@Moses:

about "Regarding that TDD make code be more functional.", No, but it can, I never ended up with it though. I read a book once where they used TDD with Refactroing, which ended up with no OO at all, but a small notepad app ;)

I agree with you about the OOP etc. Wrote about it in a comment earlier.


Robert avatar
Robert says:
Friday, May 09, 2008

@Rob Conery (or someone else who know it ;) ) I have a question about your definition of the repository/the data project and the service.

In your GetCategories-Method (in the CatalogService) you wrote this:

IList<Category> rawCategories = _repository .GetCategories().ToList();

After that, you create a parents object (with some linq logic) and created child objects (ParentCategory -> SubCategory).

Why you do this stuff in your Service Layer? I thought the Service layer would just call the repository and the filters - no real logic at all.

Thanks for your help :)


Dietrich avatar
Dietrich says:
Friday, May 09, 2008

@Moses. Your point is well made. But isn't a lot of your argument part of the refactoring process? Thus you're jumping the gun a little bit? Personally during the development period, I find being a OOP purists detrimental. For example, some concepts aren't easily discernible as "objects" and two your objects are still malleable and changing. Trying to be a OOP purists during this phase (at least for me) can lead to a lot of unneeded code and at worst badly conceived and thus written objects.

To me the least amount of code is the most readable code --especially during development.


Moses avatar
Moses says:
Friday, May 09, 2008

@Dietrich

I will use the word Maybe here as an answer and the bad explanation “It depends”

I don't get that problem you mention anymore (or at least not often.). I use SCRUM as methodology and Domain Driven Design with TDD so I got a very clean and wide model for my development procedure.

In a simple point of view: (as is)

I got my storyboards in a nice prioritized order, translate my substantives to classes and verbs as methods, works about 99% of my cases. And then I use TDD for each substantive (my classes) and unit test my verbs (methods). I also mock if I need to split em to more classes and then create those classes. Thanx to short sprints (iterations) I can change my model fast if needed.

And thanx to DDD o got a clean design with classes that have its responsibilities. Like. Entities, Value objects, Services (not service layer classes), Factories, Repositories (as they are specified, not as Rob does  change the name and I got happy  )... And the story tells me very fast what kind of DDD class type it is and therefore I got a nice OO and OOP from the beginning.

It sounds crazy I know, but since I started using DDD and SCRUM I got really effective, with some principles in mind like. YAGNI,KISS,SrP,DbC (design by contract)... Before I used DDD and had my own way to structure my code and I got into the problems you mention very often. Ok this sounds like a Silver bullet and I think it is for me in a design point of view...


Rob Conery avatar
Rob Conery says:
Friday, May 09, 2008

@Fredrik: >>>Why do you test your Mock Repositories?<<<

I want to be sure that they are working like I expect. I know it seems like a silly test, but you wouldn't believe how many times I've inverted an IF and a core routine doesn't work.

>>>Your ShoppingCart have an Items property, why not use it to add your products and pass the ShoppingCart to the Service only<<<

I definitely can, yes.

>>>Is there any reason why you pass both the ShoppingCart and the Item downs to the Repository and then bind them together, maybe I missed something<<<

In this first pass I just want the product added to the cart, so I send both to the service.

>>>so why do you pass it to the Service and let it find an Item based on a specific product<<<

I don't want logic in my cart (my preference here - your idea would work as well).

@Moses: >>>I realy hope this is a hobby project and not a best practice for other developers<<<

I'm only going to ask you ONE more time to raise the level of your comments. If you have some thoughts, share them. I don't appreciate trolling.

>>>When you are shopping on a mall with a cart, do you seek in the cart or let something else seek in it for you?<<<

I could ask you the same: Do you ask the cart to hand you that box of Lucky Charms?

>>>You check the cart for the item. This is OO design with OOP.<<<

If I was being strictly OO here my app wouldn't scale. I have this routine in place as I want the repository to find the item - not the cart.

>>The car shall have the responsibility to start.<<

Disagree. Driver.StartCar(car)? :)

>>>When using Robs stuff you need to do lots of thing all over again and got lots of services that aren’t needed etc<<<

If we're using ActiveRecord then I'd be more free to use Data-aware objects. I'm not - but this has nothing to do with OO.

>>>As a known developer once said:<<<

Keep these to yourself please.

@Robert: >>>Why you do this stuff in your Service Layer?<<<

This is where the business logic lives. Grouping the categories into a tree is an app concern, not a data one so I chose to put it there. This free's up the DB design a little.


Stas avatar
Stas says:
Friday, May 09, 2008

Rob, this is not really related, but I would like to see this, i've also emailed you regarding the problem i have.

Are you aware of LINQ to SQL generated sql statement that is INSERTing record on table with XML data type.

I get this error, and i cannot find any answers at all.

Msg 305, Level 16, State 1, Line 2

The xml data type cannot be compared or sorted, except when using the IS NULL operator.

If anyone has any ides, what is the problem or how to fix it, please respond.

Thanx in advance.


kYann avatar
kYann says:
Friday, May 09, 2008

I think we are having a SOA vs DDD debate here ?

One interesting point is this one :

>>>Your ShoppingCart have an Items property, why not use it to add your products and pass the ShoppingCart to the Service only<<<

So there is two possible way to handle this :

cart.items.Add(shoppingCartItem);

Would be the simpliest way to do it, and certainly the natural way to do it.

But rob choose to do it using a service :

cartService.AddItem(cart, p, 1);

To my point of view, rob's way is the "smarter" one of the two, because if you need to do something more when adding a product into the cart, then it's doesn't create any dependency on the cart with some other stuff (it could be discount stuff for example or maybe his client wants him to decrement the stock when the customer add the product into the cart).

Anyway, i'm not a big fan of SOA because it's a litte bit far from OO design.

I would certainly create an AddItem method on the cart that would throw a static event before and after adding a product, so anyone could do the things they need to do when an item is added.


Fredrik Normén avatar
Fredrik Normén says:
Friday, May 09, 2008

@Rob:

I don’t see why the building of an Entity with its aggregate and value object is business logic. In that case if we use DataSet, we should add all columns, rows, tables in our services (The Fill method which fills the DataSet with a table will have “business logic”), and think of it, everyone that want to get a DataSet need to implement this logic into a service, it will let them repeat them self, so why not just encapsulate it into the “Repository”? For example a Repository of Humans has the responsibility to give us Humans as objects, in this case Entities with all its aggregate and value objects. Aren’t your service like a factory, it takes the arms, legs, heads etc and put it together to a single complete item (It creates the Human). Every service that want to use your “Repository”, need to do the creation of the Human, this will let them repeat them self and they need to know that they have to do chatty calls to the “Repository” to get the different items and then they need to know how to put them together. An aggregate root (entity) is for me a well structured data component that holds data in a structured and logical way, and that is the item that comes from the “data layer”. It’s another way of creating a container of data, like a result set such as DataSet is one way to return data.

BUT! A BIG but.. When returning an Aggregate root, we need to have a way to handle performance issues, for example using LazyLoad, or make sure we create one service for each specific Use Case which can construct the Entities with the data the Use Case only need. I often use Eager Loading with Load Spans, but LazyLoad may occur. In some scenarios I also remove my aggregates from an entity to KISS, but I make sure that the entity doesn’t have a property which is null or empty, even if it has data. This will only confuse the people that want to reuse my code.

@ kYann:

>>To my point of view, rob's way is the "smarter" one of the two, because if you need to do something more when adding a product into the cart, then it's doesn't create any dependency on the cart with some other stuff

Good point, but this can be handled within his Service even if he passes the whole ShopingCart to the Service also, but a call to the service is needed every time the Cart is updated.

cartService.Save(cart);

I build a enterprise B2C app for some years ago where we decided to use that approached, but sometimes people only wanted to put stuff into the cart to get a summary of the price before they wanted to do the purchase. In that case it can be a problem to remove items from the stock when it’s added to the cart.

>>I would certainly create an AddItem method on the cart that would throw a static event before and after adding a product, so anyone could do the things they need to do when an item is added.

This is something I often do, only to make sure others can subscribe to changes made to my model.


Moses avatar
Moses says:
Friday, May 09, 2008

>I'm only going to ask you ONE more time to raise the level >of your comments. If you have some thoughts, share them. I >don't appreciate trolling.

I already have so am others, but you don't listen. And it scares me… So I got upset and was trolling!!!

>I could ask you the same: Do you ask the cart to hand you >that box of Lucky Charms?

Depends on the abstraction level yes. I never ask a thingy to get my thing from my things by a thing. For many reasons.

1... Abstraction level, why add extra classes when not needed? Why let a thing take two things to see if the one thing got the other thing?

2...It gives you more code than needed and breaks the KISS principle.

3...More code gives you more tests, it gives you bigger chance to create bugs.

4... It gives you more classes and a more complex system when not needed if doing it OO style and not functional style as you do in this case.

I only give you my thoughts here; you don’t need to agree with them. But I’m glad you explained that you weren’t OO strict. Because that was my point.

>If I was being strictly OO here my app wouldn't scale. I >have this routine in place as I want the repository to >find the item - not the cart.

I don't see your point here. Why can't it scale when doing OO/OOP? That’s one big benefit OOP really can give you.

>Disagree. Driver.StartCar(car)? :)

And the StartCar method calls the car.Start? Or is the driver the engine too? ;)

>If we're using ActiveRecord then I'd be more free to use Data-aware objects. I'm not - but this has >nothing to do with OO.

I'm confused here, ActiveRecords as Folwer explains it or your own definition of it? Though you have your own definition of repositories so it's kind of hard for me to understand if your ActiveRecord is that ActiveRecord? Ok I'm joking, but you see my point here regarding the use of names for things that’s not, it confuses ppl.

It’s like calling a mouse the keyboard or a White Russian a glass because you need a glass to it if you see my point?


D'Arcy from Winnipeg avatar
D'Arcy from Winnipeg says:
Friday, May 09, 2008

@Fredrick

"For example a Repository of Humans has the responsibility to give us Humans as objects, in this case Entities with all its aggregate and value objects"

No...the repository is responsible for returning data from a data store...the domain objects are responsible for modeling the business domain...and the service is responsible for putting it all together. Having the repository do that would be a big violation of SoC.

"I build a enterprise B2C app for some years ago where we decided to use that approached, but sometimes people only wanted to put stuff into the cart to get a summary of the price before they wanted to do the purchase. In that case it can be a problem to remove items from the stock when it’s added to the cart."

I gotta ask: why would you alter your stock levels when people are putting things in their cart? Technically those items aren't out of stock until they've actually been processed as part of a sale.

@Moses

"It gives you more classes and a more complex system when not needed if doing it OO style and not functional style as you do in this case."

In my experience, purist-type OO applications do NOT reduce class count and do NOT reduce complexity. In fact, they increase: business logic is typically placed in heavy entity objects which become test-nightmares and unruly as more functionality is added.

You also mention that OO/OOP can help scale...this can also be a misnoamer. OO is typically *not* a better scaling/performance choice because of the extra overhead of using objects compared to the more functional style (and I'm not convinced that using service classes to handle logic is necessarily more functional than object oriented).

Your argument seems to be from a more purist OO background, which I can appreciate...the same way I appreciate some PM's and BA's still express praises for Waterfall...but that doesn't mean its where the industry is going. ;)

D


Rob Conery avatar
Rob Conery says:
Friday, May 09, 2008

@Moses: I don't listen? Interesting. And here I thought I was pretty good at that. Your thoughts on OO are very good and believe it or not, I've actually learned a few of them over the last 28 years of my professional life. I'm happy to debate what I'm doing here - but it's sort of taking things down to a pretty basic level to be honest.

Your example of Car.Start() is a perfect example of what I'm talking about. A car doesn't start itself - you do when you turn the key. Is this unfair? Sure - it's reality :).

No system is as simple as what you're suggesting, and it's why we have these architectural discussions - because OO in and of itself is a principle. Implementation of it is the fun part.

Moving on...

Overall the argument whether something is OO or Repository or proper SOA is fine - but please know that the point of this screencast was to spike SQL server. I mentioned a few times that what I was doing would be refactored - and I still plan on that.

RE what puts items where - you have to pick what you're doing and move with it. I don't want that logic in my cart; though it's perfectly fine to have it there.

If I did that - Cart.AddItem() I would need to access the Repository from there - and I don't want that. There is a lot of logic involved in adding an item - Inventory is a valid concern - so are discounts and other things. By putting this in your Cart object you're now making your Model responsible for implementing it.

What I'm trying to build in here is something of a Broker - I have a cart and I want to put something in it Mr. Broker - can you scan handle this for me?

It's a perfectly normal pattern - and isolates your logic in one spot so you're free to use whatever Cart system you like (which includes 3rd party).

Finally - as I said in the screencast - I didn't implement a Cart in the CSK - we used Order and OrderItem and simply set the status to "NotCheckedOut". This allowed the transaction to revolve around the authorization and the changing of the status field (less moving parts). It also allowed for some nice reporting.


Fredrik Normén avatar
Fredrik Normén says:
Saturday, May 10, 2008

@D'Archy:

Can you let me know why it's a violation of SoC to return a logical model of the data? The Infrastructure layer used by the Repository returns the data from a data source, the Repository is a repository over the Entities. Think of a book shelf, you get a complete book from the shelf, not chapters that you need to bind togheter each time you want a book. You can read about the Repository and Entity in Fowlers or Evans book, there are good examples also and you will get a glimpse what I'm talking about when I refer to the Repository.

"I gotta ask: why would you alter your stock levels when people are putting things in their cart? Technically those items aren't out of stock until they've actually been processed as part of a sale."

The Customer of the product wanted the app to work like a store in the real world, where people put items from the shelves into a cart. In that case there is one less item left in the shelf for someone else to pick.

@Rob:

Thanks for your answers. There are absolutely different ways to solve things, for example if we use Agile, YAGNI and KISS, it will end up in different ways.


Robert G avatar
Robert G says:
Saturday, May 10, 2008

Rob, these series have been great. You are an awesome speaker and your thoughts are well organized and really easy to follow. I learned a lot from you.

The only thing I'd request, if possible, are higher resolution videos.


Moses avatar
Moses says:
Saturday, May 10, 2008

@D'arcy

>In my experience, purist-type OO applications do NOT >reduce class count and do NOT reduce complexity. In fact, >they increase: business logic is typically placed in heavy >entity objects which become test-nightmares and unruly as >more functionality is added.

True, it can, but it depends, for example if using ActiveRedords it really got big, I'm not a fan of ActiveRecords I like the repository pattern here (the Folower definition of it.) It reduces lots of code in the more upfront code etc... Entities don't usually have that complicated business rules or logics (I refer to DDD now) other classes with the right reposnsibilities do though.

>You also mention that OO/OOP can help scale...this can >also be a misnoamer. OO is typically *not* a better >scaling/performance choice because of the

But scaling and performance aren’t the same. I can agree with you that OO/OOP can decrease the performance but it does not make it less scalable. Our computers are so fast now so you typically need to code wrong to get bad performance but make code scalable is more a design issue.

@Rob>If I did that - Cart.AddItem() I would need to access the >Repository from there - and I don't want that.

That's true if you use ActiveRecords. But in my case I was referring to the repository pattern and that’s why you don't need active db logics in your cart.

You simple only add items to it and fill it with items; this is what the repository does for you.

E.g a cart uses an List<Items> where you add your items. The repository fills it for you.

Cart carts = cartRepositort.GetCartByUser("Rob");

Then if you need to know the price and total of the items you got very little business rules in the cart entity that loop through your items and calculate their price etc.. (You don’t ask the DB for anything here either though you got your items in the internal List<Item> inside the cart entity.

When you need an item you ask the cart for it, loop the items to see if it exist. That's what I was referring too; sorry I wasn't clear from the beginning.

When saving you send the cart that has it items to the cartRepository SaveCart method. The save method get your filled/changed items and saves them as with the cart info to the data source.

In this case developers use the cart and the item entities in the UI and pass it to the Service layer that then pass it to the repositories for saving it. (Other things might happen on the way though). When the UI want a person’s cart it ask the Service/application layer for it and you return a cart entities with the items. You never need to ask the DB for an item in the cart though it already there. For performance issue if got over 100 000 items you simple use the Lazy Load pattern instead.

Thanks to this you reduce lots of unnecessary db access codes in service layers and repositories etc. You don’t need to access a db to get an item from your cart though you cart already have them.


Mike avatar
Mike says:
Saturday, May 10, 2008

So why not just update the quantity property of a shoppingcart item, and then say that's what you want to save? It seems a lot of work so pass an item (shoppingcartitem) and a property (quantity) explicitly to a method that will then 'sync' the stuff. Why set it in your model and pass it to the method to update?

PS. It's going a bit fast...


Mike avatar
Mike says:
Saturday, May 10, 2008

Ì have never done testing (or TDD) but this is not making sense to me. Often it seems that A) it's testing C# and .NET and B) it's testing the database.

For A) I believe that's already tested. And for B) the bigger the app gets, the longer the tests will take, plus I think it would be better to do it in a transaction and roll everything back when the test is finished.


King avatar
King says:
Sunday, May 11, 2008

I just downloaded the source code for the entire project and opened it in VS 2008 and the two Test Projects couldn't open. It said the project types weren't supported.

I know I'm missing something. Any ideas?


David Alpert avatar
David Alpert says:
Sunday, May 11, 2008

@Rob,

I'm curious about your take on @Mike's suggestion to confine your Sql-touching spike tests inside transactions so that (theoretically) you could run them on your actual data without polluting it. Also, you could then (theoretically) run the test multiple times without changing your data and breaking your tests.

Secondly, i appreciate the quicker speed at which you are moving with these webcasts, however i do have a request - would you please show us a new test before new code?

I don't need to watch you type every line, but seeing code before testing loses the feel of test _Driven_ design. Watching the code unfold is helpful as it let's me follow along with your thought process. I can write code, but i'm hoping that if i watch how you construct your tests, and how the code flows from them, then it might help me do the same.


Leave a Comment


Search Me
Subscribe

Popular Posts
 
My Tweets
  • @codinghorror or just listen to Murray Stree by Sonic Youth with a beer in one hand a cat in another
  • @codinghorror: Listen to Miley Syrus - replace one mind virus with another :)
  • This day brought to you by Sonic Youth.
  • Green lights on my first Linq To SubSonic query :)
  • My patience is gone for today. Wanted to do up cast 10 but my usually thick skin is remarkably thin today.
  About Me



Hi! My name is Rob Conery and I work at Microsoft on the ASP.NET team. I am the Creator of SubSonic and was the Chief Architect of the Commerce Starter Kit (a free, Open Source eCommerce platform for .NET)

I live in Kauai, HI with my family, and when my clients aren't looking, I sometimes write things on my blog (giving away secrets of incalculable value).