5

When I want to call a constructor on aggregate root class Task, I am interested how can I check if a passed taskType exists in TaskType repository (enforced later in DB on ORM level).

I want to enforce this relation so only predefined task types can be actually used to create a Task.

Is that something that is done in constructor, and validated in business logi of a domain model, or this type of validation is done solely in DB (expecting DB exception if such a relation does not exists)

class Task
{

     public Task()
     {
     }
     private int GUID {get;set;}
     public TaskType taskType {get;set;}

}

public TaskType
{
     public int ID {get;set;}
     public string TaskCode {get;set;}
}

Code is used only as an example and it could contain some irregularities.

2
  • 1
    In your system where are TaskTypes coming from? Are they not coming from a repository? In which case, you know it exists there. Are they coming from a service? Then do you not have some certainty the service has provided a reliable task type? Commented Jun 15, 2016 at 12:51
  • Tasktype could come from api or api -> application layer. If they are retrieved via app. layer would that be considered breaking domain logic? Commented Jun 15, 2016 at 14:14

1 Answer 1

5

Most of the time, it doesn't need to know; aggregates with correctly designed boundaries don't need to know about outside state to maintain its invariant (that's basically the definition of an aggregate boundary).

When an aggregate needs to know about state outside it's own boundary, the usual answer is a DomainService. The aggregate invokes a query on the service, and the service reports the answer.

Creation patterns are weird, though -- the entity that you are asking to maintain the invariant doesn't exist yet. You can handle that case in one of two ways.

The approach I prefer is to use a common state to initialize the aggregate, and then invoking a method on it to give this entity a unique identity. This fits the general pattern.

Alternatively, you can delegate the creation of the aggregate to a factory. Fundamentally, this is the same idea, with the transaction boundary drawn in a different place. The basic execution (query a domain service to check the last known status of your external state).

I want to enforce this relation so only predefined task types can be actually used to create a Task.

If TaskType is a value, then you could also solve this when translating the CreateTask message. Presumably that message has some bit of state that identifies the type, so you do a lookup on that state prior to creating the aggregate. This is a basic input validation pattern.

Alternatively, you could dispatch the create command initially to the TaskType, which would copy itself into the command to create the task. Conceptually, that makes a lot more sense if TaskType is an entity, rather than a value, but either way works. See Don't Create Aggregate Roots for some reflections on this approach.

If TaskType is something that can "go away", then there are real limits to how much the domain can do to ensure your rule. In many cases, it turns out that it isn't critical to the business that the rule be followed exactly (ie - the inconsistency is rare, easy to identify, cheap to remedy); in which case remediation instead of validation may be more practical.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.