Juan Vega
Juan Vega

Reputation: 1120

How to handle getOrCreateIfAbsent inside a DDD aggregate?

I have a situation in my requirements I would like to see if someone has a strong opinion about it.

The project I'm working on requires that, given a Customer, once the Customer adds a Product to a Cart, if the Cart is not present, the Cart has to be created. At the moment, the aggregate is the Customer and it contains the Cart that contains the Product. Because this system is kind of a side one that supports the real e-commerce project, the creation of the Customer and the Cart is taken for granted once an "AddProductCommand" is received. In that situation, both Customer and Cart have to be created if they are not there already.

My current implementation creates the Customer at the application service if it doesn't exist. Once created, I pass the AddProductRequest using customer.addProduct(addProductRequest). AddProductRequest contains the Cart id and the Product id. The problem comes here. Once in the Customer aggregate, if the Cart is not there, I have to create it. So basically, I don't have an addCart in customer with a Cart entity that I call first to then call addProduct. I need to create the Cart if it's not there from inside the Customer aggregate and then add the Product to it. And to do this, I don't create any factory as I don't want to have a static method that complicates testing and I can't just create a new factory inside the aggregate either. I just create the Cart entity using the new operator in a protected method that I override when testing to verify what I'm doing there.

So my question is, is this a valid approach? In my head, to add a Product to a Cart, the Cart should be added first to the Customer and fail if it's not there. To do it this way I would need to add logic to the application service where first I would ask the Customer if he has a Cart with that id, create it otherwise, and add it to Customer before adding the Product. I could add a domain service that is given this request but then I would need to inject the factory to create the Cart into it, when I've read in several places that a domain service shouldn't be injected any factory, that should be the work of the application service.

I could do it that way but the project will get more complicated in the future as there will be yet another layer where a Product could be added a list of Vouchers having to also create Customer, Cart and Product if they are not there when the AddVoucherCommand is consumed. In this situation, if I don't want to create the entities inside the model, I would need to check at the application/domain service whether each aggregate or entity has the necessary entity inside it, which I don't think is very DDD friendly, or just keep doing what I'm doing at the moment. This is, each aggregate/entity is in charge of create the necessary entity before calling the addXXXX method on it.

Some simplified code to explain what I'm doing at the moment and what I'm going to have to do in the future:

public class CustomerService {
    public void addVoucher(AddVoucherRequest addVoucherRequest) {
        Customer customer = customerRepo.load(customerId);
        customer.addVoucher(addVoucherRequest);
        customerRepo.save(customer);
    }
}

public class Customer() {
    public void addVoucher(AddVoucherRequest addVoucherRequest) {
        Cart cart = getOrCreateIfAbsent(addVoucherRequest.getCartId());
        cart.addVoucher(addProductRequest);
    }
    private Cart getOrCreateIfAbsent(long cartId){
        Optional<Cart> cart = carts.stream().filter(cart -> cart.getId() == cartId).findFirst();
        return cart.orElseGet(() -> {
             Cart newCart = createCart(cartId);
             carts.add(newCart);
             return newCart;
        }
     }
     protected Cart createCart(long cartId) {
         return new Cart(cartId);
     }
}
public class Cart() {
     public void addVoucher(AddVoucherRequest addVoucherRequest) {
         Product product = getOrCreateIfAbsent(addVoucherRequest.getProductId());
         product.addVoucher(addVoucherRequest);
     }
     private void getOrCreateIfAbsent(long productId) {
         Optional<Product> product = products.stream().filter(product -> product.getId() == productId).findFirst();
        return product.orElseGet(() -> {
             Product newProduct = createProduct(productId);
             products.add(newProduct );
             return newProduct ;
        }
     }
     protected Product createProduct(long productId) {
         return new Product(productId);
     }
 }

Any suggestions?

Thanks!

Upvotes: 1

Views: 175

Answers (1)

guillaume31
guillaume31

Reputation: 14064

Choosing to wait for a Customer to order something to create him/her in the new system rather than importing all Customers from the legacy system upfront is a purely technical decision, it has nothing to do with your domain IMO.

Therefore, it shouldn't affect the way you design your domain objects in any way - as I understand it, getOrCreateIfAbsent() should be a behavior in some kind of application service, not in one of your entities.

Upvotes: 2

Related Questions