Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
    
You really are asking for an atomic createAndSave message. Would it help if you think of that object as a factory that has some saving functionality munged in? –  blueberryfields Feb 25 '12 at 15:41
    
I think that leads to the same problem as creating the OrderService: it combines multiple concerns in one class. I'm really trying to stick with the separation of concerns/single responsibility principle (see en.wikipedia.org/wiki/Single_responsibility_principle). –  Sandor Drieënhuizen Feb 25 '12 at 16:02
add comment

1 Answer

Why not make the order responsible for creating orderlines?

The AddOrderline function could take the Product and Amount parameters and create an Orderline changing an orderline would be by product on an order so you would have a function ChangeOrderLine(string productName, int amount) and DeleteOrderLine(string productName)

Done and done.

share|improve this answer
    
I think you are right for simple cases. In this case, let's just say that creating my order line involves some relatively complex logic (I'll update the question with this). Since (less-simple) object creation is a separate concern, this is a valid reason to use the factory pattern. –  Sandor Drieënhuizen Feb 25 '12 at 16:38
2  
There's no reason the Order can't use a factory, if a factory is required. The responsibility for adding the OrderLine to the Order still lies in the Order object. –  Eric King Feb 25 '12 at 19:37
    
DDD purists often strongly advice against having entities depend on services and I agree with them. Without the dependencies, the domain can stay pure and not become infected with concerns that really belong elsewhere (for example, in domain services or application services such as command handlers). –  Sandor Drieënhuizen Feb 25 '12 at 22:44
    
In my opinion, it is important to keep the domain model only concerned with expressing the domain and defining entity interaction (therefore not being anemic). –  Sandor Drieënhuizen Feb 25 '12 at 22:52
    
Maybe a nitpick, but the factory in this context is not a service. If the only entity that should be charged with creating a new orderline is an order, then that's where the code should be. –  Eric King Feb 25 '12 at 23:29
show 2 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.