I want to implement a product discount feature within RavenDB.
Product doc:
{
"RegularPrice": 10.00,
"ReferencePrice": 0.0,
"Categories": [
"A",
"B",
"C"
]
}
Now for example I'd like to give a discount for all products in category "A".
So I map category on discount rate in a discounts document like this:
{
"ProductDiscount": {
"A": 20.0
}
}
The question is how do I calculate the ReferencePrice and store or update it when:
A new product is added?
The RegularPrice is updated?
The discounts document is updated?
It seems that I should handle this with a trigger. But I'm not sure!
I'm not sure this model will hold up. How will you associate the product to the discount? They are related by categories, but with this model, it's a many-to-many relationship. Is this what you intended? If so, how will you deal with having multiple discounts that could apply to the same product?
If you were to leave it like this, one strategy you could consider would be multi-map/reduce to calculate the price, and the Indexed Properties Bundle to write the result back to the document. But this is a pretty complicated setup.
You could probably make this easier by having a single discount per category, and defining the discount as part of the category document. Then you could use LoadDocument to pull in the discount for the categories while indexing.
You'll need to experiment with a model that fits your requirements and that you can maintain.
Other ideas:
Yes, you could use a trigger, but that would run server-side as a plugin. It may be difficult to maintain.
You could manually send a scripted patch request any time a category discount is applied. New documents and edits to existing documents would have to be done manually in your own code.
You could leave the calculated price off of the product completely. Whenever loading a product, you'd include the discounts and calculate the price on the fly.
There are others, I'm sure. It really depends on what works in your particular app.
Related
I was reading about DDD and I realize that sometimes an entity might be a VO or VO might be an entity. You can know which one is better depends on the context. I was checking different examples. For example, shopping cart DDD example. Product is an aggregate root, Cart is an aggregate root and items is a entity of Cart, so if you want to add a product to the cart you would do something like this:
$cart->addProduct(id $id, $name, $price)
class Cart
{
private items;
function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
this->items[] = new Item(
new ItemProductId($id->ToString()),
new ItemName($name->ToString()),
new ItemPrice($price->ToString()),
new ItemCartId(this->id->ToString())
);
}
}
There are two reasons why I think it is a VO:
You cannot modify the value's item ( only if the product's
price has been modify there is a event that would modify its price).
The item doesn't have id, it's has a reference of the
product(ItemProductId) and a reference of the cart (ItemCartId)
I was reading about DDD and I realize that sometimes an entity might be a VO or VO might be an entity. You can know which one is better depends on the context.
Usually its pretty clear whats entity and whats an value object. If it contains data that's fixed at the time of assignation, its a value object. For example "order address" on the order aggregate. When the order is placed, the address is set. "Addresses" may be an entity in user aggregate (i.e. a list of his common addresses), but for an order its an value object since its not supposed to change when the user edits or deletes one of his addresses.
cart->addProduct(id $id, $name, $price)
class Cart
{
private items;
function addProduct(ProductId $id, ProductName $name, ProductPrice $price) {
this->items[] = new Item(
new ItemProductId($id->ToString()),
new ItemName($name->ToString()),
new ItemPrice($price->ToString()),
new ItemCartId(this->id->ToString())
);
}
}
That's a pretty bad example. Why would or should the value object be ItemPrice? Does that makes it any special? Why string? A price is usually just a numeric value but also involves a currency, passing it as string kinda beats that.
On top of that, having ItemCartId in the it does
a) leak data persistence knowledge into your domain. The fact, it's contained inside this->items[] already establishes a relationship between the entity (or aggregate) and the value object. ItemCartId as no meaning in the domain, other than that it's required for relational database engines (=persistence knowledge)
There are two reasons why I think it is a VO:
You cannot modify the value's item ( only if the product's price has been modify there is a event that would modify its price).
You sure? Why would a eCommerce business want to have the prices in the card anyways?
Prices are informational only, they could change before the order is placed. Same as availability.
A lot of users put stuff in their cart and check on next day. In that time, the price could change.
No company would want to sell a product for the price when it was put into the shopping cart, if the price increased in the time since it was put in there. That would mean a financial loss.
Prices in the shopping carts are informational, not compulsory. You need know the exact process of the company.
The item doesn't have id, it's has a reference of the product(ItemProductId) and a reference of the cart (ItemCartId)
Again. Why do you think ItemCartId belongs to the Item object? That's leaked persistence knowledge, since its only important for relational database systems.
All you really need in a shopping cart is
* product or article number (not necessary the id, that's typically db knowledge)
* quantity
Nothing else. If you may want to change the user when the price changed and show the old and new price, the take the price (=currency value object, not ItemPrice) to it too as a value to compare to an old state.
Finally and probably most importantly: Consider if the shopping cart is an aggregate at all (or does fit into ddd).
After all, most shopping carts are only a value bag w/o a lot of business logic into it. The real logic (checking the real price, product availability, asking for shipping location, calculation of taxes and shipping costs) happens during the checkout process, not while putting stuff into the cart.
For example you can check out eShops on Containers demo project showing an example shopping service based on microservices and ddd.
Some microservices apply DDD (such as Ordering microservice), where others don't (Catalog microservice or the Basket (cart) Microservice).
Applying DDD doesn't mean everything needs to be done with DDD. If its simple crud based services, then you don't need DDD for these. DDD adds a value where you have complex systems and complex business logic.
A catalog has neither, it just presents data which come from a different system (i.e. ERP which on other side may be built on using DDD).
I don't understand what are you asking exactly, but the code you are providing could be improved.
First of all I suggest you to read the red book by Vaughn Vernon https://www.amazon.co.uk/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577: you can find 3 chapters describing how to define entities, value objects and aggregates, with some rules of thumbs.
One of those advices, is to keep your aggregates as small as possible, in order to improve your performance and keep the code easy to read and maintain. Imagine that you have a Blog aggregate that contains a list of Post entities: if you manage all of them in a single aggregate, when you want to modify the blog Author, for example, you are forced to retrieve all of the blog's post, without any reason, and that means that you are doing a join and slowing down your application. The more your aggregates grows, the slower those queries with their joins.
So, in the case of the Cart, I suggest you to build the cart without any item or product, instead you can add the CartId to the Item. Cart does not know which items it contains, but items know in which cart they are.
About value objects: is a tool that allows you to wrap some validation and business logic inside a class that is represented by its state and not by its id (like entities), so in the case of the cart, if you put two identical bottles of water inside it, how can you know that they are different? Do you need to know that they are different? Are they different if they are physically (or logically different) or are they different if some of their attribute is different?
In my opinion an item or a product, in your case, are entities because they are not measuring anything, and when you put an item twice, you actually have two different items (and you use an id to recognize them).
This is not necessary like this, sometime you can use a value object and sometimes an entity, it depends on your context. A good example to understand that difference is with money:
if you want to measure an amount, for example 10 dollars, probably a value object will work for you, because you don't care if it a bill or another, you just want to measure 10 dollars; in this case if you have 10 dollars, is not important if you have a bill or another, the important thing is that is 10 and not 5
in the case that you need to recognize different physical bills, for any reason (you need to track money for the police), you should use an entity, because any printed bill has a unique serial number, and a 10 dollar bill, in this context, is actually different from another 10 dollar bill
Hope this can help you.
Goodbye!
The shopify store I edit has a 'sale' collection. This consists of products which have been auto-tagged on the condition that the 'compare at price' is higher than the actual price.
I want to create a new 'clearance' section, which would consist of products where the actual price is cheaper than the compare at price by 50% or greater. I can't find a simple way to do this (i.e. by using the inbuilt collection creator).
Can anyone help me out?
Discounts are applied at the checkout, not when browsing a product. That is why the Smart Collection logic has no conditions based on discounts. If I were you I would try and work out how to build a collection "clearance" with conditions that make sense, and then you could create a price rule/discount that would apply to that collection (50% off). It seems like that is the way to go.
I'm building a filter page, with facets etc, which works as it should.
Now the our customer has a request to, basically "Be able to decide which sorting the items comes out in".
Each product is decorated with a Product Display Order, and is in a Product Line.
We got these example Product Display Orders:
1. Featured Item
2. Core Item
3. Spare Part
4. Utility
And these Product Lines:
1. Hammers
2. Saw
3. Wood
and the sorting is like this:
Sorting should firstly be based on Product Display Orders, secondly by product lines, thirdly Alphabetically.
So all products which is a Featured Item is listed first, and all these Featured Items is then sorted by their product line, and if some product are in the same Featured Item and Product Line, then its alphabetically.
The challenge is: I can't just get the sorting of Product Display order items and product lines as a number on the product, i only got a name/id.
We've thought of Boosting based on if the product are in the different categories, but it seems a bit messy.
OR
See if it possible to have some logic in the Sorting.
Sort by productDisplayOrder:
1. featured, 2. core Item ...
Then by ProductLines:
1. Hammers, 2. Saw ...
Then by Name DESC.
Which way is the best way to have this sorting, is it possible to give this logic to elastic, if it is a match and then sort it. Or are we needed to twist the boosts of product?
Hopefully this makes sense for you.
Thanks in advance! :)
Option 1). Quickest/Best performing solution would be to create new/separate integer fields for productDisplayOrder and ProductLine and then use those in your sort criteria as described (after reindexing and validating the the data is indexed as expected).
Option 2) If you want more nuance than described (eg higher scoring matches can 'break through' the ordering ceiling described) then you can explore using a Function Score Query to implement a custom scoring strategy that takes productDisplayOrder and ProductLine into consideration in generating an overall match score.
Option 3). If you can't change the mapping and reindexing your data, you can use Script-Based Sorting to generate sorting values from the currently indexed productDisplayOrder/ProductLine text using a script (eg Groovy). Keep in mind that query performance will be worse than the first two options.
I've got to build a standalone menu button with submenu that contains links to price ranges.
I activated the blocklayered module (not for this task, only for regular left-column filters). So the relative db tables are in place and populated.
I want to make a controller specific for price ranges. So I've got to do the right query and maybe set up the same url vars as the blocklayered module so they wil not conflict.
Would it be too crazy to import blocklayered or blocklayered-ajax in my controller and use part of their functionality? Maybe not good because of object duplication or other issues?
Or maybe, would it be a bad idea to use the blocklayered tables (for example layered_price_index) to help me get filtered products? I'm wandering if it would be a better solution than re-doing all by myself, or if instead it's not good for some reason.
Any idea?
It really depends on which amount (among the ones below) you would like to take into account in your price range filter:
Amount without taxes
Amount including taxes
Amount including discount/promotion
Amount in several currencies or only one currency
Amount for a specific customer group or for everyone
Amount base on any other product price rule
The easy way:
You can build a price range controller easily by yourself, handling only a single currency and prices without taxes and reduction. It will probably be 90% accurate (because of the missing discounts a product might not show up for a certain range).
In that case, you can easily build a query on the ps_product and ps_specific_price tables and SELECT in real-time the right products for a given range.
The proper way:
You want to handle discounts, price rules, specific prices, etc. If you build a real-time query including all these calculations and parameters, it may slow down the server.
Build a product price cache or re-use the one setup by the Block Layered module.
I have a quote that contains items (store in table QuoteItem):
QuoteItemId, QuoteId, ItemId, Quantity etc.
Now, I need to be able to create a group of chosen items in the quote and apply a discount on it.
Well that's simple, I create two more tables:
Group: GroupId, DiscountPercentage
GroupQuoteItem: GroupId, QuoteItemId
Let's say I have 30 items in a quote.
I made a group that contains items 1-20 from the quote and I applied a discount on it.
Now I need to have another group that contains items 10-30, the problem is about those inner 10 items, I need to control whether the discount should apply on the items after the other discount or it should be on the items' base price.
For instance, I am gonna talk about item no. 15 in the quote: QuoteItem.Cost = 100
I applied 1st discount of 10% = 90.
Now I want to apply the second discount, I need to be able to control if the discount should be on the 100 or should be on the 90.
Same is when I have multiple discount groups and when I wanna apply a complex architecture of discounts.
Any assistance will be really appreciated.
I would look into adding a column to the GroupQuoteItem table, GroupQuoteItem.Priority. This column would be used in the query that determines the final price. If you have N discounts with the same, highest priority, they will be stacked atop each other (the order doesn't matter, thanks to associativity of multiplication).
If all of these high-priority discounts are later removed, lower-priority discounts can take their place. This should help you in setting up pretty complex discount structures.
I hope that at least gives you somewhere to start from.
It really depends on your own business rules. Do you want to apply the discounts on the price after discount or on the original price. When you ask questions like this it helps with SAMPLE Data then show us expected results.
This may be one of those rare times in normalization when you want to store data that you could calculate otherwise. So, in QuoteItem, you could have a Cost field and a DiscountedCost field. If they're the same, then you know no discount has been applied, if they are not, then a discount has been applied. By having this field, you would also be able to do comparisons on what the discount is already and whether you want to add the additional discount. In fact, you could also store that number in an ExistingDiscount field.
Why not store a column in the Group table that specifies whether or not the discount can be accumulated with other discounts versus if it must be applied to the base price only? You could name the field something like "ApplyToBasePriceOnly."
Other than that, I agree with JonH that a lot of this logic should be placed in business rules. I think your general database structure looks pretty good.