0

I'm learning about DDD and am attempting to model a basic interaction between a user and a shopping cart, and am wondering if the following code is directly aligned with DDD principles, or if there is a cleaner way to do so. I have the following two aggregate roots

class ShoppingCart {
    public id: number
    public productQuantityLimit: number
    public totalProductPriceLimit: number (will be VO in future)
    public products: List<CartItem>

    public addToCart(Product product) {
        if (productQuantityLimit > 0 && totalProductPriceLimit - product.price > 0) {
          productQuantityLimit -=1
          totalProductPriceLimit -= product.price
          this.products.append(new CartItem(product.id, product.price))
        } 
    }
}

class Product {
    public id: number
    public price: number (will be VO in future)
}

And I have an entity which is apart of the ShoppingCart aggregate root

class CartItem {
    public id: number
    public price: number (will be VO in future)
}

This CartItem represents the idea that the item in your cart is tied to your cart, but a Product is a higher order concept. So if the price of a Product changes, we do not violate the ShoppingCart's price limit invariant. In other words, if a user adds an item to their cart and then the price of that item changes, they still get the item for the original price

Now this is all good, but lets say I'm designing a REST API and I have an endpoint which adds an item to a shopping cart. That endpoint would accept a Product's, and a ShoppingCart's id, and if all invariants hold, add the item to the shopping cart. The business logic would then have to look like so:

const product = productRepository.getById(productId)
const shoppingCart = shoppingCartRepository.getById(shoppingCartId)
shoppingCart.addToCart(product)
shoppingCartRepository.save(shoppingCart)

My questions are:

  1. is it OK to inject multiple repositories into an application layer service in order to get an aggregate which is needed as a parameter in another aggregate's method?

  2. Is there a more effective way to model this where the above would not be required?

1
  • As long as you inject the contract instead of the concrete implementation of the repository, yes it's okay. But what i would do slightly differently is have a business usecase class that encapsulates all these repositories and do my thing in there. Commented Jan 5 at 17:15

1 Answer 1

0

is it OK to inject multiple repositories into an application layer service in order to get an aggregate which is needed as a parameter in another aggregate's method?

Is it "OK"? yes. Are there tradeoffs involved? Yes.

If all you are interested in is a copy of the information controlled by the product "aggregate", then it might make sense to use a read-only copy of that information, rather than passing around the "aggregate" with all of its affordances for making changes to that data.

Also, in the general case, you need to think carefully about whether you need to have a (logical) lock on that information, to prevent it from being changed while you are assuming it has a particular value.

In short, if what you really need is an affordance that allows you to exchange a productId for a read only value, then a "repository" (a facade that supports the illusion of a local collection of "aggregates") might not be the best thing to inject into your message handling logic.

Also, you might be missing some domain concepts in your model that makes things easier - i.e. the idea of a "quoted price" or "offer" - a promise to honor a particular price if accepted within a particular time window. That might be easier in so far as (a) it separates the problem of calculating an offer to propose to the customer from the problem of documenting that a customer has (provisionally) accepted that offer and (b) gives you an hook to start collecting information about which offers are accepted and which are declined, so that your sales/marketing teams can revise their strategies based on the data you are collecting.

Sign up to request clarification or add additional context in comments.

1 Comment

In terms of a repository might not being the best thing to inject into my message handling logic, is there something else that you would recommend? I'm about 1/3 through the blue book, and I haven't read of any ways to really collection information from the database other than repositories. Also, in terms of a logical lock, is that an actual lock at the database level? And would that be handled via a query?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.