5

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?

I found this answer which is similar, but for value objects: 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).

Community
  • 1
  • 1
Ed I
  • 7,008
  • 3
  • 41
  • 50
  • I argue that an Order without an Id is not an Order entity at all, which is where your first initial thought comes from. To combat this, you have to remember the state of your objects. Look at 'state' as a pause in time. I.e., a webpage request. During the creation of an order, yes it does not have an Id at first but you aren't done with its state yet - you Add() it in the repository, thereby completing the persisted state of the Order(), which is now complete. – eduncan911 Jan 19 '10 at 21:04
  • I'm not disagreeing with you as of yet, however if you claim that an order without and Id is an entity for a brief pause in time, during that pause it will have no discernable identity. I think, at least semantically, that this is a contradiction with the definition of an entity. – Ed I Jan 19 '10 at 21:17
  • Identity is not as cut and dry as you would think. In my faith, our identities are our unique souls present from our conception. But to the government, our identities can be established through more than one means: birth certificate, passport, so on. There is a point in time where the child has no official identity to the government but the government still sees it as an entity, although one needing identification. Think of an order as a precious little baby and the repository as the government (ints are more user-friendly anyways!) :) – tuespetre May 23 '14 at 04:14

1 Answers1

5

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
  • 225,310
  • 48
  • 427
  • 736
  • 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 I Jan 19 '10 at 21:38
  • 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 Jan 19 '10 at 22:23
  • If Order and OrderRequest are separated it will be a maintenance hell, won't it? I mean, since they reflect each other completely, except for the Id field, they will have to co-evolve all the time. Or am I missing something? – vorou Mar 01 '13 at 08:06
  • This illustration is not so much about maintaining code, it's more about investigating an acceptable way to provide the business layer a means to be identity aware rather than passing the buck down the pipe. – Ed I Oct 15 '13 at 04:50