4

Repositories are supposed to work as boundaries for aggregate roots, i.e. IRepository<TAggreagte> would offer CRUD functionality that transactionally saves data to the DB. So far so good.

But what if the aggregate has a composite primary key? In my problem it's an Identity INT column plus a SMALLINT sequence number. (This is the DB design, not my idea!)

The repository examples I have seen either have e.g. void Add(TAggregate aggregate) or bool Add(TAggregate aggregate).

Example where "eventual consistency" is used:

I want to add an aggregate A and I need to call repository A and then insert a dependent aggregate B using repository B, I must know the ID of aggregate A after it was inserted.

That is where I am lost. If you insert A, how would you get its ID, especially if it's a composite key? The only solution I see is to return the entire object again, so:

TAggregate Add(TAggregate a);

Any advice?

John
  • 3,591
  • 8
  • 44
  • 72
  • _must know the ID of aggregate A after it was inserted_ Yes, but that is not different for a composite key, is it? – H H May 07 '15 at 21:08
  • @HenkHolterman it's not any different, but a method can only have one return value, which is a problem if you have a composite key with 2 or more columns. What should the Add() method return? That's essentially my question. – John May 08 '15 at 07:09

3 Answers3

2

Identity is a very tricky topic in DDD.

There are two schools of thought when it comes to the "timing" of identity creation:

  • Identity is generated at the time the instance of that entity class is created, or
  • Identity is created at the time it is persisted (when it is inserted into a relational database, for example).

The second approach can lead to a lot of problems. One which you have already raised. There are some other real issues that arise when only establishing identity at the time of persistence. Consider these two core properties of a domain entity:

  • Entities are distinguished by their identity. Thus, entities cannot exist without identity.
  • Entities are considered equal when their identity is equal.

When creating a new instance of an entity class using the "identity on persistence" approach, you initially have NO identity, and thus violating all of the principles above. Is identity now modeled as somewhat optional in your entities? In my opinion, this approach will lead you down a very dark road.

The only way you can get around these issues effectively is to have identity generated at the time of instantiation. This will solve your problem as well. Identity will be available to you immediately.

This may be tricky for you if your database technology automatically generates IDs.

There are several ways to generate identity at time of entity instantiation.

Generate identity within the entity:

Simplistic example:

public Person : DomainEntity<Guid>
{
    //..
    public Person(string name)
      : base(Guid.New()) // Providing a unique GUID
    {
        Name = name;
    }
}

Client code:

// A new person with identity!
var person = new Person("Eric Evans"); 

This is the preferred approach, but not always possible.

You provide the identity to the entity:

Simplistic example:

public Person : DomainEntity<int>
{
    //..
    public Person(int identity, string name)
      : base(identity) // Providing a unique GUID
    {
        Name = name;
    }
}

Client code:

// A new person with identity!
var person = new Person(IdentityGenerator.Create(), "Eric Evans");

The IdentityGenerator generator may interact with your database to get and reserve the "next" identity (something which is not supported in SQL Server unfortunately).

Where do you stand?

Regarding of whether you have a composite key or not, the question you need to ask is "am I able to generate or provide entity identity at the time of instantiation?"

Dave New
  • 38,496
  • 59
  • 215
  • 394
2

Key is a value object - it's its value that matters, the format is just a technical detail. You could create a TAggregateKey class (PersonKey, OrderItemKey...) to hold the key value

class TAggregateKey
{
    int id {get; set;}
    byte sequence {get; set;}
}

and then use it as follows:

TAggregate a = new Agreggate(value, value, value);
TAggregateKey id = repositoryA.Add(a);    //TAggregateKey Add(TAggregate a)
repositoryB.DoSomething(id, value);      //void DoSomething(TAggregateKey id, int value)
grudolf
  • 1,764
  • 3
  • 22
  • 28
1

But what if the aggregate has a composite primary key? In my problem it's an Identity INT column plus a SMALLINT sequence number. (This is the DB design, not my idea!)

An aggregate never has a composite primary key, because

  1. Aggregate is a group of objects. An Entity i.e the Aggregate Root, has an Id which can be a value object in a format that ensures that Id is global (Hint: Guid). The Id can be a natural id that makes sense for the Domain, such as some generated number or combination of letters and numbers (usually generated by a domain service).
  2. Primary key is a RDBMS concept, a persistence detail. Your Db design should take into consideration the Entity's Id format. If your DB schema was designed first or unrelated to the Domain needs, then you're doing it wrong i.e you're not actually doing Domain Driven Design.

I want to add an aggregate A and I need to call repository A and then insert a dependent aggregate B using repository B, I must know the ID of aggregate A after it was inserted.

The transactional CRUD mindset doesn't work here, despite of the unit of work concept which is a persistence detail but an anti-pattern if used at a higher level. The solution is pretty simple: a service will create and persist Aggregate A then publish a Domain Event which will contain the id (A was created). Another service will act as an event handler and will create/persist Aggregate B. Depending on how it's technically implemented, the eventual consistency will be very short.

For reliable results, a durable service bus should be used.

MikeSW
  • 16,140
  • 3
  • 39
  • 53