I have an aggregate root Entity
which does some processing when requested. If during that processing a condition is met, then SubEntity
has to be initialized. The problem is that SubEntity
has also a child entity Status
which has to be initialized with a starting value that comes from the DB.
I'm trying to closely follow SOLID and DDD principles, but I'm new at this. The way I have it working now is by using a factory, but it looks wrong to me, because I don't like the idea of having this factory being served by a consumer class (in order to follow DIP), since this is part of the domain logic within that entity.
Am I doing this right? Is this the correct way to design such classes? What alternatives do I have?
public class Entity
{
public virtual SubEntity SubEntity { get; private set; }
public void Process(int someData, ISubEntityFactory subEntityFactory)
{
if (SomeConditionIsMet)
{
SubEntity = subEntityFactory.Create(this);
}
}
}
public class SubEntity
{
public SubEntity(Entity entity, Status status)
{
Entity = entity;
Status = status;
}
public virtual Entity Entity { get; private set; }
public virtual Status Status { get; private set; }
}
public class Status
{
public const int StartingId = 1;
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<SubEntity> SubEntity { get; private set; }
}
public class SubEntityFactory : ISubEntityFactory
{
// property and constructor omitted
public SubEntity Create(Entity entity)
{
var status = UnitOfWork.StatusRepository.GetByID(Status.StartingId);
return new SubEntity(entity, status);
}
}
Based on theDmi's great answer, I decided to receive a initialStatus
variable as a parameter to the Process
method, so my domain is not coupled to the DB. Then I validate initialStatus
to make sure its id
matches Status.StartingId
. After that, I don't even need the factory anymore, and it looks a lot cleaner.
public class Entity
{
public virtual SubEntity SubEntity { get; private set; }
public void Process(int someData, Status initialStatus)
{
ValidateInitialStatus(initialStatus);
if (SomeConditionIsMet)
{
SubEntity = new SubEntity(this, initialStatus);
}
}
private void ValidateInitialStatus(Status initialStatus)
{
if (initialStatus == null)
{
throw new ArgumentNullException("initialStatus");
}
if (initialStatus.Id != Status.StartingId)
{
throw new ArgumentException("Initial status is invalid");
}
}
}
public class SubEntity
{
public SubEntity(Entity entity, Status status)
{
Entity = entity;
Status = status;
}
public virtual Entity Entity { get; private set; }
public virtual Status Status { get; private set; }
}
public class Status
{
public const int StartingId = 1;
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<SubEntity> SubEntity { get; private set; }
}
That UnitOfWork.StatusRepository.GetByID(Status.StartingId)
in your factory seems fishy. Try to avoid coupling factories to repositories (though the other way around is ok, e.g. to use a factory during reconstitution).
A clean solution would be the following:
public class Entity
{
private readonly ISubEntityFactory _subEntityFactory;
public Entity(ISubEntityFactory subEntityFactory) {
_subEntityFactory = subEntityFactory;
}
public void Process(int someData, Status initialStatus)
{
if (SomeConditionIsMet)
{
SubEntity = _subEntityFactory.Create(this, initialStatus);
}
}
}
This has the consequence that the initialStatus
must be retrieved by the calling app service. I guess you wanted to avoid that, but it is a lot cleaner than coupling the domain to the DB (which I'd avoid at all cost).
If you can, redesign the initialStatus
as value object. I don't know if that's possible in your case, but it would make the design more robust.
Also, don't pass subEntityFactory
as parameter. A factory is a special kind of a service, so it should be injected into constructors rather than passed around. This makes the dependency of Entity
on the factory explicit, which is good.
Note that having service dependencies in entities usually leads to the conclusion that it's best to create or reconstitute the entity through a factory. That way, the factory can provide all services required by the specific entity when constructing it. Always remember that object construction is an implementation detail, clients don't need to know about it in order to use an object.