Search code examples
design-patternsnhibernatedomain-driven-designbusiness-logicaggregateroot

Aggregate root creating child entities with new GUIDS


I generally use nHibernate to generate the unique ID's for my entities... but I am thinking about generating them in code? Consider the following example: (If I am doing something else wrong please point it out as I am new to DDD):

These are all the classes that would belong in the same assembly i.e. my domain model.

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; 
    }
}

My Service Layer would then look something like this:

//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);
    }
}

Now this all works well and good. However the problem is what if I wanted to return the ID of the newly created ChildEntity to the presentation layer? It's not currently possible. I would have to return the whole object graph. The only alternative I can think of is to make the following changes to my code:

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; 
    }
}

Is this wrong? or is it perfectly ok? Would be good if someone could clarify!


Solution

  • This was my solution in the end. When save is executed the ID will be populated for the child object. Since I have a reference to the Child object I can then return the ID. Being new to DDD I wasn't aware that you could pass around child entities... but after doing some further reading I stumbled on the following rule from Eric Evans:

    The root Entity can hand reference to the internal ENTITIES to other objects, but those objects can only use them transiently, and may not hold onto the reference.

    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; 
        }
    }