views:

77

answers:

1

Let's say we have an aggregate root entity of type Order that relates customers and order lines. When I think about an order entity it's more natural to conceptualize it as not being defined without an Id. An order without an Id seems to be better represented as an order request than an order.

To add an order to a repository, I usually see people instantiate the order without the Id and then have the repository complete the object:

class OrderRepository
{
    void Add(Order order)
    {
        // Insert order into db and populate Id of new order
    }
}

What I like about this approach is that you are adding an Order instance to an OrderRepository. That makes a lot of sense. However, the order instance does not have an Id, and at the scope of the consumer of the repository, it still makes no sense to me that an order does not have an Id. I could define an OrderRequest to be an instance of order and add that to the repository, but that feels like deriving an apple from an orange and then adding it to a list of oranges.

Alternatively, I have also seen this approach:

class OrderRepository
{
    Order AddOrder(Customer customer)
        // It might be better to call this CreateOrder
    {
        // Insert record into db and return a new instance of Order
    }
}

What I like about this approach is that an order is undefined without an Id. The repository can create the database record and gather all the required fields before creating and returning an instance of an order. What smells here is the fact that you never actually add an instance of an order to the repository.

Either way works, so my question is: Do I have to live with one of these two interpretations, or is there a best practice to model the insertion?

EDIT: I just found this answer which is similar, but for value objects: http://stackoverflow.com/questions/566854/how-should-i-add-an-object-into-a-collection-maintained-by-aggregate-root. When it comes to a value object there is no confusion, but my question concerns an entity with identiy derived from an external source (Auto-Generated Database Id).

+1  A: 

I would like to start by ruling out the second approach. Not only does it seem counter-intuitive, it also violates several good design principles, such as Command-Query Separation and the Principle of Least Surprise.

The remaining options depend on the Domain Logic. If the Domain Logic dictates that an Order without an ID is meaningless, the ID is a required invariant of Order, and we must model it so:

public class Order
{
    private readonly int id;

    public Order(int id)
    {
        // consider a Guard Clause here if you have constraints on the ID
        this.id = id;
    }
}

Notice that by marking the id field as readonly we have made it an invariant. There is no way we can change it for a given Order instance. This fits perfectly with Domain-Driven Design's Entity pattern.

You can further enforce Domain Logic by putting a Guard Clause into the constructor to prevent the ID from being negative or zero.

By now you are probably wondering how this will possibly work with auto-generated IDs from a database. Well, it doesn't.

There's no good way to ensure that the supplied ID isn't already in use.

That leaves you with two options:

  • Change the ID to a Guid. This allows any caller to supply a unique ID for a new Order. However, this requires you to use Guids as database keys as well.
  • Change the API so that creating a new order doesn't take an Order object, but rather a OrderRequest as you suggested - the OrderRequest could be almost identical to the Order class, minus the ID.

In many cases, creating a new order is a business operation that needs specific modeling in any case, so I see no problem making this distinction. Although Order and OrderRequest may be semantically very similar, they don't even have to be related in the type hierarchy.

I might even go so far as to say that they should not be related, because OrderRequest is a Value Object whereas Order is an Entity.

If this approach is taken, the AddOrder method must return an Order instance (or at least the ID), because otherwise we can't know the ID of the order we just created. This leads us back to CQS violation which is why I tend to prefer Guids for Entity IDs.

Mark Seemann
Great answer! Before I asked the question I was leaning towards changing the API to accept an OrderRequest, and I agree with you that it should be a value object not included in the Order hierarchy.However, now I really like your Guid suggestion. I could define an Order constructor that knows how to instantiate itself as unique, and then add it to the repository. Sounds perfect to me. However I'm uneasy because this solution seems outside normal practice. Have you encountered many projects where you felt like using this solution but were not allowed?
Ed Saito
You may run into people who think that this is a bad idea for several reasons. Some of them are mostly related to Cargo Cult approaches to application architecture, but the resistance can still be real. In general, there's a (small) perf overhead on using Guids as keys instead of ints, but nothing that has stopped me so far. The most important argument against Guids is if the ID is used outside the application, because they are really hard to read aloud over the phone (as one example). Using Guids solves a some problems, but not all. I prefer Guids, but am open to other alternatives as well :)
Mark Seemann