0

I understand that objects external to an aggregate shouldn't hold references to entities and value objects belonging to the aggregate. My question if this also applies to the Application Service that exposes the use cases to the client code.

For example, if I have an Order aggregate, and Order Line is a value object inside the aggregate, what would be a more recommended approach:

Option 1: referencing child objects from Application Service

class OrderApplicationService
  # code ommited

  def removeOrderLine(order_id, order_line_product_id)
    order = OrdersRepository.find(order_id)
    order_line = order.find_line_by_product(order_line_product_id)

    order.remove_line(order_line)

    OrdersRepository.save(order)
  end

Option 2: using primitive values instead of child objects

class OrderApplicationService
  # code ommited

  def removeOrderLine(order_id, order_line_product_id)
    order = OrdersRepository.find(order_id)      

    order.remove_line_by_product(order_line_product_id)

    OrdersRepository.save(order)
  end

end

2 Answers 2

1

This specific fragment demonstrates an anemic domain model

order_line = order.find_line_by_product(order_line_product_id)

There's no particular reason that the application service should be participating in the business process. Trying to query the aggregate root in this way indicates you are heading down the wrong path.

The most important idea is that the domain model is responsible for interpreting the command; the responsibility of the application (the OrderApplicationService) is just to provide the correct data to the model.

Beyond that, you want the language -- especially within the model, to match the domain language as closely as you can manage.

So this variation is close...

def removeOrderLine(order_id, order_line_product_id)
  order = OrdersRepository.find(order_id)

  order.remove_line(order_line_product_id)

  OrdersRepository.save(order)
end

But either of these might be better, depending on what an order looks like in your domain

"We remove lines from an order"

def removeOrderLine(order_id, line_id)
  order = OrdersRepository.find(order_id)

  order.remove_line(line_id)

  OrdersRepository.save(order)
end

"We remove products from an order"

def removeOrderLine(order_id, product_id)
  order = OrdersRepository.find(order_id)

  order.remove_product(product_id)

  OrdersRepository.save(order)
end
2
  • For operating with value objects inside the aggregate (no id), would it be reasonable to provide the identifying attributes for removal? For example, order.remove_line(product_id) is meant to remove the order line that has a reference to a particular product.
    – FedericoG
    Jun 23, 2017 at 20:22
  • You can use also the line index. In fact you use whatever the Aggregate provides but not the line item itself. Jun 24, 2017 at 6:56
0

If you are using "order_line_product_id" which looks like a global identity than it seems to me that your application need to use OrderLine directly and not only via the Order model. If you define Order as just an Entity and not aggregate your life will be simpler and you could create an OrderLine repository and do something like this: OrderLineRepository.delete(order_line_product_id).

If you still want to use the aggregate way than lose the "order_line_product_id" identity and use something like order.remove_line(line_number) notice that line number is only unique per order. So if you want to remove the first line of the order (any order) you call this order.remove_line(1) or the last line order.remove_line(order.lines_count). In that case the unique primary key in your order_lines table should be made from two columns order_id and line_number

Your Answer

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

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