I'm rather new to CQRS and DDD and am wondering what would be the best way to implement a voting mechanism in the domain model.
On a product, a user can upvote/downvote. There are some domain rules regarding voting, f.e. you can only vote once, either down or up.
Both vote and product would be an aggregate root. Is this the best approach? It's recommended to keep the aggregates small. Adding the vote to the product aggregate root would overtime make it bloated.
The issue that I'm struggling with is deleting a vote when the vote is an aggregate root. You need to know which vote aggregate needs to be deleted. It's not possible in the command handler to retrieve the vote from the repository with the productId and userId. The aggregateId is stored as a single Guid in the database.
The command would contain these fields
UserId
ProductId
Some possible solutions that I have found:
Use a deterministic GUID based on the userId and productId
Votes are a list of aggregates on a product
Create a voteId and use that to delete the vote.
Store the aggregate as a string and use a combination of productId and userId
What would be the best approach?
Using my limited understanding of your domain, I can conclude that there are two bounded contexts Catalog and Reviews. In this case, you could have a Product aggregate in the Catalog BC and one Product aggregate in the Reviews BC.
The Product aggregate from the Reviews BC would contain a list of all Vote entities for a particular product. A Vote entity would contain all the information needed in order to enforce the vote only once business invariant (like IP address, user ID etc). Both Product aggregates types (from the two BCs) would share the same ID - that's how you keep them synchronized, if you need (for example when a product is removed from the catalog it is marked as non-votable in the reviews BC).
Related
I have an order aggregate with Order as root having multiple OrderLine. OrderLine has "identity reference" to Product aggregate. But having only "identity reference" is not enough. I need value of "taxable" property, last "price" property for calculating price in OrderLine. In fact, in order to calculate price in OrderLine, some data from Product aggregate is needed.
How can this problem is resolved in DDD? Is it valid in DDD approach to have a lite version of Product (ProductLite) to use as read-only DTO?
-- UPDATE (thanks to #Francesc Castells)
// app service to add OrderLine
product = productRepo.Read(productId)
orderItemPrice = priceDomainService.CalculatePrice(product.price, product.tax)
order.AddOrderLine(product.ID, orderItemPrice)
orderRepo.Save(order)
Yes, this is perfectly valid. An aggregate should store all data that it needs to fulfill its purpose. This data doesn't have to always be user input, it can also come from other aggregates. There's abviously a difference between data produced by an aggregate and data consumed by it. For example, your Order consumes product prices, but it cannot change the product price and expect the rest of the system to respect that new price, as it doesn't own it.
In your scenario, I would say that once in the OrderLine, the Price is not part of the Product anymore, but part of the OrderLine itself, which could probably be defined as the price of the product the moment it was ordered or maybe the moment it was put in the shopping cart.
Lots of examples like order and order lines makes sense, like:
Order is an AR that contains OrderLines
Customer is an AR that contains Orders.
Question is, what is the AR that contains Customer?
I guess it can be something like "shop".
So, shop.AddCustomer(customer)...
but, how to get shop?
If it's an AR (entity) it has an id, so shop.GetById(shopId). If I only have one shop, how does this work with persistence?
Should I have a table (shops) with one line?
Shop is an in-memory object with a collection of Customers?
You got that wrong there. Aggregates do not contain other aggregates! They can only reference them by ID.
An aggregate is a group of entities and value objects that are closely related. The aggregate forms a consistency boundary around them. The Aggregate Root is the root entity in that aggregate that is globally addressable. So in your example with Order and OrderLines, Order could indeed be the AR.
Customer on the other hand, would only reference Orders by ID if it is a separate aggregate.
To retrieve an aggregate, you typically use a Repository. You load an aggregate through the repository by specifying the ID of the aggregate, or some other suitable search parameter.
I'm working on a side project to learn and apply DDD within the "Daily Deal' domain. In my purchasing context, i have an invariant where a user can only purchase 'x' amount of deals per deal.
so it seems wasteful for my deal aggregate to load all purchases from all users just to check and see how many times (if any) the user has purchased this deal. I see two ways i could go about this.
Put this logic within a domain service which would allow a pre-condition to already have been met when the Purchase method on the Deal aggregate is invoked.
My repository implementation could always populate the purchases collection of the deal for the purchasing user. hmm...not sure about this one.
any guidance would be great!
I would take the second approach, but with one important change. I would instead create a value object called PurchasedDeal, that consists of just a DealID and Quantity field. The User aggregate could instead load a collection of this more lightweight purchase history object. Performance should be good with this approach, since I'm guessing that the average user will only have a few dozen purchase records.
Also remember that with DDD, you can and probably should have different models per bounded context. So you might design your User aggregate like this in the context of deals/purchasing. However, your User aggregate in another context would look different and not have a purchase history if it's not needed.
I'm facing a typical DDD problem. It must be very basic. I have an order and customer.
A customer can create multiple orders. Customer is the root of its own aggregate. Order is the root of its own aggregate. But when a customer creates an order, we display some portion of the customer information on the order. Should Order aggregate hold reference to customer?
When it holds it then when the Order Repository gets the order, we are able to retrieve some portion of customer information as well for display. But when we involve the order in a transaction, customer also gets into it which is creating problem if the customer is also getting updated at the same time. Please advise guys ! My gut feeling says I MUST not hold reference to customer from order.
Question 2: (NEW)
Can I get and hold a reference to the Customer (from Customer Repository) for a given Order while creating an Order (using Order Factory) and safely save the Order (without updating the Customer inside in anyway, Customer is there only for information/query?) without creating contention if the same Customer is getting modified else where? Lets assume NHibernate as ORM.
A simple answer will be that you hold the ID of the customer or, if needed for your domain some ValueObject with a minimal set of information about the customer ( ID, Name ).
A more complex answer is to think about Bounded Context. See Eric Evans's presentation where he wishes he had put the BC chapter as the first chapter in the book.
The idea is that in your Customer Management Bounded Context, your Customer entity can be the AR of the Customer Aggregate and the Orders can be entities in the Customer Aggregate. In the Billing Bounded Context you can have an Order AR with a Customer entity inside.
I'm still wrapping my head around DDD, and one of the stumbling blocks I've encountered is in how to handle associations between separate aggregates. Say I've got one aggregate encapsulating Customers and another encapsulating Shipments.
For business reasons Shipments are their own aggregates, and yet they need to be explicitly tied to Customers. Should my Customer domain entity have a list of Shipments? If so, how do I populate this list at the repository level - given I'll have a CustomerRepository and a ShipmentRepository (one repo per aggregate)?
I'm saying 'association' rather than 'relationship' because I want to stress that this is a domain decision, not an infrastructure one - I'm designing the system from the model first.
Edit: I know I don't need to model tables directly to objects - that's the reason I'm designing the model first. At this point I don't care about the database at all - just the associations between these two aggregates.
There's no reason your ShipmentRepository can't aggregate customer data into your shipment models. Repositories do not have to have a 1-to-1 mapping with tables.
I have several repositories which combine multiple tables into a single domain model.
I think there's two levels of answering this question. At one level, the question is how do I populate the relationship between customer and shipment. I really like the "fill" semantics where your shipment repository can have a fillOrders( List customers, ....).
The other level is "how do I handle the denormalized domain models that are a part of DDD". And "Customer" is probably the best example of them all, because it simply shows up in such a lot of different contexts; almost all your processes have customer in them and the context of the customer is usually extremely varied. At max half the time you are interested in the "orders". If my understanding of the domain was perfect when starting, I'd never make a customer domain concept. But it's not, so I always end up making the Customer object. I still remember the project where I after 3 years felt that I was able to make the proper "Customer" domain model. I would be looking for the alternate and more detailed concepts that also represent the customer; PotentialCustomer, OrderingCustomer, CustomerWithOrders and probably a few others; sorry the names aren't better. I'll need some more time for that ;)
Shipment has relation many-to-one relationship with Customer.
If your are looking for the shipments of a client, add a query to your shipment repository that takes a client parameter.
In general, I don't create one-to-mane associations between entities when the many side is not limited.