I am trying to design a somewhat standard "coupon code" for a shopping cart. So far, I have a discount object for data entry of the discount data and child data. Now I am working on the use case for the shopping cart. I am thinking about adding a collection of Coupon objects which will contain the complex rules for calculating a discount based on what is in the items collection and the subtotal of the cart (or order).
However, I am struggling because I don't want to hold up from saving the parent if the collection is "invalid". Any invalid coupon should simply calculate a value of 0 discount. Additionally, there should be a message indicating that the coupon is invalid (and why) on each invalid coupon. In this case, the coupon would have been valid upon entering it into the collection, but became invalid since then.
More importantly, I don't want an "invalid" object to be added to the the collection at all - I just want to display a message showing how it is invalid:
So, this all looks good on paper, but how can I build this functionality with CSLA? In the project tracker sample, there is an exception thrown when a "rule" such as one of the above is broken - when a resource is assigned to a project twice. This is fine, but technically speaking being invalid is not an exceptional case and raising an exception is an expensive way to get the message to the UI.
I am thinking about making this into an "Editable Switchable" collection. Then my UI can get an instance of the coupon object before it is added to the collection, check whether it is valid, if not display the invalid message to the user, and if so add it to the collection. This unfortunately is a lot of work for my UI to do, but still seems more elegant than throwing an exception in each of these scenarios; for one, I can show more than one reason why the coupon is invalid. On the other hand, I can't do the "can't be combined" rule without access to the collection - in this one case throwing the exeption seems better.
In addition, I am thinking that going with the built-in rules management in CSLA is the wrong approach in my scenario since I don't want to prevent the user from being able to continue just because they have a coupon that has expired or been used too many times after they had entered it into the collection (for example, they return a month later to buy what is in their shopping cart). I am still using CSLA 1.51 and there is no provision for rule severity I can tap into, which would of course be the best way.
Any ideas about how to handle this scenario better would be appreciated.
The typical way to handle this is to have your list as a child of a business object. The business object will handle the rules that need to be applied at a collection/list level. It could also provide you with the final discount (factoring in all valid coupon items).
Ironically that was the part that was straightfoward - the root object will manage the communication between the two collections, one collection will store the items and the other will store the coupons and will contain the coupon logic.
What I am trying to work around is raising exceptions when adding a coupon code to the coupon collection (because the coupon deosn't exist in the DB, for example). While exceptions can be used for sending messages to the UI, they are not the preferred way because they are expensive resources. Rocky designed his framework so "broken" rules can be displayed on the UI without raising exceptions on the business objects. Unfortunately, this one scenario - adding a new item to a collection - doesn't fit that paradigm.
According to the MSDN documentation, exceptions should be reserved for unexpected scenarios and should be used sparingly because they cost CPU cycles and memory. In my case, "invalid" coupons can occur fairly frequently and therefore are not best suited to use the exception model - they are technically part of the business logic.
However, after considering other possibilities the exception is starting to look like the most straightforward way because it will prevent me from having to make the coupon objects into "switchable" objects so the "invalid" states can be read outside of the collection. The fact of the matter is, some of the "invalid" states depend on the rest of the collection to be determined so creating the object inside of the collection to validate it against the rest of the collection seems to be the path of least resistance. Unfortunately, my UI will need to anticipate a specific type of exception thrown from the collection when a new object is added so the message can be shown on the UI.
If you have any other approaches to this problem, I would appreciate hearing them.
Ok, so I have implemented much of the coupon collection on my order object (but not the shopping cart yet). Technically, this collection manages a many-to-many relationship. Following the ProjectTracker's lead, I put the validation in the DoAssignment method. Here is what it looks like:
Private Sub DoAssignment(ByVal OrderCoupon As
If List.Count = 1 AndAlso CType(List.Item(0), OrderCoupon).CantBeCombined Then Throw New CouponInvalidException("The promotional code already in the order cannot be combined with others"
If List.Count > 0 AndAlso OrderCoupon.CantBeCombined Then Throw New CouponInvalidException("The promotional code cannot be combined with other promotions"
If Contains(OrderCoupon) Then Throw New CouponInvalidException("The promotional code has already been applied to this order"
If Not OrderCoupon.Exists Then Throw New CouponInvalidException("The promotional code appears to be invalid"
If OrderCoupon.IsExpired Then Throw New CouponInvalidException("The promotional code has already expired"
If OrderCoupon.IsUsed Then Throw New CouponInvalidException("The promotional code has been used too many times"
I constructed it similar to "rules" code because technically that is what it is. As you can see, the first 2 rules would be impossible to do without a reference to the collection. Each of these rules prevents the collection from ever being contaminated with rules that conflict with each other. Only the last 2 rules will need to be duplicated in the coupon object because they are the only ones that can change after the collection has been populated.
My UI code will just need to catch a CouponInvalidException and show the error that is thrown. I would prefer to do it without the exceptions, but haven't discovered a reasonable way to create the desired behavior that doesn't include them.
Copyright (c) Marimer LLC