Context
I'm developing an application using a Domain Driven Design approach. I want to use a design pattern wherever appropriate and apply all SOLID principles.
Scenario
I have an order and I want to allow clients to add order lines to it.
Consider the following pseudo code:
class Order
OrderLines[] (readonly)
AddOrderLine(orderLine)
class OrderLine
Product
Amount
class OrderLineFactory
OrderLine Create(product, amount)
class OrderLineRepository
Save(orderLine)
Now lets add an OrderLine to the Order:
Order.AddOrderLine(OrderLineFactory.Create(product: "Chocolate Cake", amount: 3))
By the way, the OrderLineFactory is there because in my case, there is some relatively complex logic involved with creating order lines. This is one of the main reasons for applying the factory pattern.
Problem
The problem is that I could do this instead...
orderLine = OrderLineFactory.Create(product: "Chocolate Cake", amount: 3)
OrderLineRepository.Save(orderLine)
...and get around the Order.AddOrderLine
method, effectively short-cutting any checks that are performed in that method (e.g. protecting against duplicates or limiting the order amount).
Working towards a solution
An argument could be that there shouldn't be a Save
method on OrderLineRepository
, because an OrderLine isn't an aggregate root. This would (just like I want) render the client incapable of storing the bare OrderLine except from adding it to the Order and saving the Order consequently.
However, having a Save
method on repositories that concern non-root aggregates, allows for saving changes made to them directly, such as:
orderLine = orderLineRepository.Get(31923)
orderLine.Amount = 5
OrderLineRepository.Save(orderLine)
Not being able to save the OrderLine would require me to do something like...
order = OrderRepository.FindByOrderLineId(31923)
order.OrderLines[31923].Amount = 5
OrderRepository.Save(order)
...which isn't all that efficient or practical. I could add a requirement to provide the (actually redundant) Order ID as well but that would put some unnecessary burden on the client.
So lets say I want to stick with my OrderLineRepository.Save
method and still find a way to stop my client from being able to get around my Order.AddOrderLine
method.
All I can think of now, is dropping the OrderLineFactory and creating a domain service that combines the creation and the assignment.
class OrderService
AddOrderLine(order, product, amount)
So we can:
order = orderRepository.Get(123)
OrderService.AddOrderLine(order: order, product: "Chocolate Cake", amount: 3))
orderRepository.Save(order)
I don't really like the service combining multiple concerns here though, those of creation and assignment. That would call for... a factory... again.
Okay, let's consider a private factory then, one that's not available to the client but to the domain service alone. In my case I would get stuck with unit testing and Inversion Of Control. I'd be creating an uninjectable dependency, since my client wouldn't be able to register my non-public factory.
Summary
- I want my client to be able to create order lines through the Order class and in no other way
- I don't want to mangle multiple concerns in a single class
- I want to keep OrderLine creation outside of the Order class
- I'd like to keep using a factory for that
So I'm a little stuck here, trying to find the best way to deal with this. Any suggestions?