Factory Methods in the Aggregate Root are also a good place for invariants.
In a Domain Model with Forum and Post Entities, where Post is an aggregated part of the Aggregate Root Forum, publishing a Post could look something like this:
class Forum
{
// ...
public function publishPost(PostId $postId, $content)
{
$post = new Post($this->id, $postId, $content);
DomainEventPublisher::instance()->publish(
new PostPublished($postId)
);
return $post;
}
}
After talking with a Domain Expert, we came to the conclusion that a Post shouldn't be published when the Forum is closed. This is an invariant, and we could force it directly on Post creation, thereby preventing an inconsistent Domain state:
class Forum
{
// ...
public function publishPost(PostId $postId, $content)
{
if ($this->isClosed()) {
throw new ForumClosedException();
}
$post = new Post($this->id, $postId, $content);
DomainEventPublisher::instance()->publish(
new PostPublished($postId)
);
return $post;
}
}