Совокупный корень, создающий дочерние объекты с новыми GUID

Обычно я использую nHibernate для создания уникальных идентификаторов для своих сущностей... но я думаю о создании их в коде? Рассмотрим следующий пример: (Если я делаю что-то еще неправильно, укажите на это, так как я новичок в DDD):

Это все классы, которые принадлежат одной сборке, то есть моей модели предметной области.

public interface AggregateRootState
{
    bool CanAddChild(); 
    bool CanModifyChild(); 
    bool CanDeleteChild(); 
}

public class AggregateRoot
{
    private AggregateRootState aggregateRootState; 

    public IList<ChildEntity> ChildEntityList {get; internal set;}

    public bool CanAddChild()
    {
        return aggregateRootState.CanAddChild(); 
    }

    public void AddChild(ChildEntityParameters childEntityParameters)
    {
        if (!CanAddChild())
            throw new NotImplementedException("aggregate root not in correct state."); 

        ChildFactory.CreateChildEntity(childEntityParameters);
    }

    public bool CanModifyChild()
    {
        return aggregateRootState.CanModifyChild(); 
    }

    public void ModifyChild(ChildEntityParameters childEntityParameters)
    {

        if (!CanModifyChild())
            throw new NotImplementedException("aggregate root not in correct state.");

        ChildEntity childEntity = ChildEntityList.First(c => c.Id == childEntityParameters.Id); 
        childEntity.Property1 = childEntityParameters.Property1; 
        childEntity.Property2 = childEntityParameters.Property2; 
    }

    public bool CanDeleteChild()
    {
        return aggregateRootState.CanDeleteChild(); 
    }

    public void DeleteChild(Guid Id)
    {
        if (!CanDeleteChild())
            throw new NotImplementedException("aggregate root not in correct state");

        ChildEntityList.Remove(ChildEntityList.First(c => c.Id == Id));
    }

    public void Validate()
    {
        //code to validate the object and ensure it is in a savable state. 
    }
}

public class ChildEntityParameters
{
    public Guid Id {get; set;}
    public string Property1 {get; set;}
    public string Property2 {get; set;}
}

public class ChildEntity
{
    internal ChildEntity() { }
    public Guid Id {get; set;}
    public string Property1 {get; internal set;}
    public string Property2 {get; internal set;}
}

internal static class ChildFactory
{
    public static void CreateChildEntity(ChildEntityParameters childEntityParameters)
    {
        ChildEntity childEntity = new ChildEntity(); 
        childEntity.Property1 = childEntityParameters.Property1; 
        childEntity.Property2 = childEntityParameters.Property2; 
    }
}

Тогда мой сервисный уровень будет выглядеть примерно так:

//for simplicity I have arguments rather than using the request / response pattern. 
public class ServiceLayer
{
    public void AddChildEntity(Guid aggregateRootId, string string1, string string2)
    {
        AggregateRoot aggregateRoot = aggregateRootRepository.FindBy(aggregateRootId);
        ChildEntityParameters childEntityParameters = new ChildEntityParameters();
        childEntityParameters.Property1 = string1;
        childEntityParameters.Property2 = string2;
        aggregateRoot.AddChild(childEntityParameters); 
        aggregateRoot.Validate(); //will throw exception if there is something wrong. 
        aggregateRootRepository.Save(aggregateRoot);
    }
}

Теперь все это работает хорошо и хорошо. Однако проблема в том, что если я хочу вернуть идентификатор вновь созданного ChildEntity на уровень представления? В настоящее время это невозможно. Мне пришлось бы вернуть весь граф объектов. Единственная альтернатива, о которой я могу думать, - это внести следующие изменения в мой код:

internal static class ChildFactory
{
    public static void CreateChildEntity(ChildEntityParameters childEntityParameters)
    {
        ChildEntity childEntity = new ChildEntity(); 
        **childEntity.Id = Guid.NewGuid();**  
        childEntity.Property1 = childEntityParameters.Property1; 
        childEntity.Property2 = childEntityParameters.Property2; 
    }
}

public class ServiceLayer
{
    public **Guid** AddChildEntity(Guid aggregateRootId, string string1, string string2)
    {
        **Guid Id;** 
        AggregateRoot aggregateRoot = aggregateRootRepository.FindBy(aggregateRootId);
        ChildEntityParameters childEntityParameters = new ChildEntityParameters();
        childEntityParameters.Property1 = string1;
        childEntityParameters.Property2 = string2;
        **Id = aggregateRoot.AddChild(childEntityParameters);** 
        aggregateRoot.Validate(); //will throw exception if there is something wrong. 
        aggregateRootRepository.Save(aggregateRoot);
        return Id; 
    }
}

Это неправильно? или все нормально? Было бы хорошо, если бы кто-нибудь разъяснил!


person Cool Breeze    schedule 20.11.2014    source источник


Ответы (2)


В превосходной книге Вона Вернона говорится, что Я полагаю, подходящая стратегия для создания сущностей состоит в том, что они появляются полностью сформированными с личностью, а не в переходном состоянии. Поэтому я думаю, что вы на правильном пути, гарантируя, что ребенок создает с его идентификатором.

Это также решает сложную небольшую проблему, когда получение хэш-кода временного объекта не всегда возвращает тот же хэш-код для эквивалентного сохраняемого объекта.

person David Osborne    schedule 20.11.2014
comment
Мне придется заглянуть в эту книгу, так как на нее, кажется, довольно много ссылок. Итак, вы предлагаете мне генерировать идентификатор, когда объект создается для всех моих сущностей? включая совокупный корень? После публикации исходного вопроса я провел дополнительное исследование и обнаружил, что если вы укажете идентификатор, то nHibernate не знает, вставлять или обновлять запись? Это просто вопрос изменения конфигурации в nhibernate? - person Cool Breeze; 20.11.2014
comment
Разве вы не можете попросить NH сгенерировать для вас идентификатор через Save()? Или изменить свое сопоставление так, чтобы id = назначено? - person David Osborne; 20.11.2014
comment
Я сейчас в замешательстве. Если я заставлю NH сгенерировать для меня идентификатор с помощью Save(), я вернусь к исходной проблеме, заключающейся в том, что я не могу получить идентификатор вновь созданного дочернего объекта? Так что я думаю, мне придется изменить сопоставление, чтобы id = назначенный. Здесь есть проблемы с этим: stackoverflow.com/questions/5048728/ но я действительно не вижу альтернативы? Неправильное проектное решение в соответствии с: nhforge.org/ doc/nh/en/ - person Cool Breeze; 20.11.2014

Это было моим решением в конце концов. При выполнении сохранения идентификатор будет заполнен для дочернего объекта. Поскольку у меня есть ссылка на дочерний объект, я могу вернуть идентификатор. Будучи новичком в DDD, я не знал, что вы можете передавать дочерние сущности... но после дальнейшего чтения я наткнулся на следующее правило от Эрика Эванса:

Корневая сущность может передать ссылку на внутреннюю сущность другим объектам, но эти объекты могут использовать их только временно и не могут удерживать ссылку.

public class ServiceLayer
{
    public **Guid** AddChildEntity(Guid aggregateRootId, string string1, string string2)
    {
        **ChildObject obj;** 
        AggregateRoot aggregateRoot = aggregateRootRepository.FindBy(aggregateRootId);
        ChildEntityParameters childEntityParameters = new ChildEntityParameters();
        childEntityParameters.Property1 = string1;
        childEntityParameters.Property2 = string2;
        **obj = aggregateRoot.AddChild(childEntityParameters);** 
        aggregateRoot.Validate(); //will throw exception if there is something wrong. 
        aggregateRootRepository.Save(aggregateRoot);
        return obj.Id; 
    }
}
person Cool Breeze    schedule 12.12.2014