Created
July 7, 2015 19:07
-
-
Save codeliner/b173485940aa64b59b79 to your computer and use it in GitHub Desktop.
This example illustrates modelling small aggregates instead of a large cluster: See discussion: https://groups.google.com/forum/#!topic/dddinphp/WBKuPAT0OS8
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* This example illustrates modelling the following use cases: | |
* | |
* - A user can write and publish an article, with the title, description.etc. | |
* - X users can then respond to said article by posting comments, | |
* - and responding to one another. | |
* - it is very common for an Article to be 'locked' when it becomes older. | |
* | |
* Note: The example is an incomplete draft. | |
* - Persistence layer, | |
* - the complete set of value objects, | |
* - some methods | |
* - most of the assertions | |
* - and domain events | |
* are missing. | |
* | |
*/ | |
class User | |
{ | |
/** | |
* @var UserId $userId | |
*/ | |
private $userId; | |
/** | |
* @param ArticleId $articleId is generated somewhere on the outside | |
* @param string $title | |
* @param string $description | |
* @return \Article | |
*/ | |
public function publishArticle($articleId, $title, $description) | |
{ | |
return new Article($articleId, $title, $description, $this); | |
} | |
} | |
class Article | |
{ | |
/** | |
* @var ArticleId | |
*/ | |
private $articleId; | |
/** | |
* @var UserId | |
*/ | |
private $author; | |
/** | |
* @var string | |
*/ | |
private $title; | |
/** | |
* @var string | |
*/ | |
private $description; | |
/** | |
* @var \DateTimeImmutable | |
*/ | |
private $publishedAt; | |
/** | |
* @param ArticleId $articleId | |
* @param string $title | |
* @param string $description | |
* @param UserId $author | |
*/ | |
public function __construct(ArticleId $articleId, $title, $description, UserId $author) | |
{ | |
$this->articleId = $articleId; | |
$this->title = $this; | |
$this->description = $description; | |
$this->author = $author; | |
$this->publishedAt = new \DateTimeImmutable(); | |
} | |
/** | |
* @param UserId $userId | |
* @param CommentId $commentId | |
* @param string $text | |
* @return Comment | |
*/ | |
public function addUserResponse(UserId $userId, CommentId $commentId, $text) | |
{ | |
$this->assertNotLocked(); | |
return Comment::asArticleResponse($commentId, $userId, $text); | |
} | |
/** | |
* In the example we assume that an article gets locked automatically when it is older than 4 weeks | |
* @throws Exception\Article | |
*/ | |
private function assertNotLocked() | |
{ | |
$fourWeeks = new \DateInterval('P28D'); | |
$now = new \DateTimeImmutable(); | |
$fourWeeksAgo = $now->sub($fourWeeks); | |
if ($this->publishedAt < $fourWeeksAgo) { | |
throw Exception\Article::isLocked($this); | |
} | |
} | |
} | |
class Comment | |
{ | |
/** | |
* @var CommentId | |
*/ | |
private $commentId; | |
/** | |
* @var UserId | |
*/ | |
private $author; | |
/** | |
* @var ArticleId | |
*/ | |
private $referencedArticle; | |
/** | |
* @var text | |
*/ | |
private $text; | |
/** | |
* @var null|CommentId | |
*/ | |
private $respondedComment; | |
/** | |
* @param CommentId $commentId | |
* @param UserId $author | |
* @param ArticleId $referencedArticle | |
* @param string $text | |
* @return Comment | |
*/ | |
public static function asArticleResponse(CommentId $commentId, UserId $author, ArticleId $referencedArticle, $text) | |
{ | |
return new self($commentId, $author, $referencedArticle, $text); | |
} | |
/** | |
* @param CommentId $commentId | |
* @param UserId $author | |
* @param ArticleId $referencedArticle | |
* @param string $text | |
* @param CommentId $respondedComment | |
* @return Comment | |
*/ | |
public static function asCommentResponse(CommentId $commentId, UserId $author, ArticleId $referencedArticle, $text, CommentId $respondedComment) | |
{ | |
return new self($commentId, $author, $text, $referencedArticle, $respondedComment); | |
} | |
/** | |
* @param CommentId $commentId | |
* @param UserId $author | |
* @param ArticleId $referencedArticle | |
* @param string $text | |
* @param null|CommentId $respondedComment | |
*/ | |
private function __construct(CommentId $commentId, UserId $author, ArticleId $referencedArticle, $text, CommentId $respondedComment = null) | |
{ | |
$this->commentId = $commentId; | |
$this->author = $author; | |
$this->referencedArticle = $referencedArticle; | |
$this->text = $text; | |
$this->respondedComment = $respondedComment; | |
} | |
/** | |
* @param UserId $userId | |
* @param CommentId $commentId | |
* @param string $text | |
* @return Comment | |
*/ | |
public function addUserResponse(UserId $userId, CommentId $commentId, $text) | |
{ | |
return Comment::asCommentResponse($commentId, $userId, $this->referencedArticle, $text, $this->commentId); | |
} | |
} | |
/** | |
* Action 1: A user can write and publish an article, with the title, description.etc. | |
*/ | |
class PublishArticleWrittenByUserHandler | |
{ | |
private $userCollection; | |
private $articleCollection; | |
public function handle(PublishArticleWrittenByUser $command) | |
{ | |
/** @var $user User */ | |
$user = $this->userCollection->get($command->authorId()); | |
if ($user === null) { | |
throw Exception\User::notFound($command->authorId()); | |
} | |
$article = $user->publishArticle( | |
$command->articleId(), | |
$command->title(), | |
$command->description() | |
); | |
$this->articleCollection->add($article); | |
} | |
} | |
/** | |
* Action 2 and Assertion "locked" Article as part of Article::addUserResponse: | |
* X users can then respond to said article by posting comments | |
*/ | |
class RespondToArticleHandler | |
{ | |
private $articleCollection; | |
private $commentColleciton; | |
public function handle(RespondToArticle $command) | |
{ | |
/** @var $article Article */ | |
$article = $this->articleCollection->get($command->articleId()); | |
if ($article === null) { | |
throw Exception\Article::notFound($command->articleId()); | |
} | |
$newComment = $article->addUserResponse( | |
$command->commentAuthorId(), | |
$command->newCommentId(), | |
$command->commentText() | |
); | |
$this->commentColleciton->add($newComment); | |
} | |
} | |
/** | |
* Action 3: and responding to one another | |
*/ | |
class RespondToCommentHandler | |
{ | |
private $commentCollection; | |
public function handle(RespondToComment $command) | |
{ | |
/** @var $respondedComment Comment */ | |
$respondedComment = $this->commentCollection->get($command->respondedCommentId()); | |
if ($respondedComment === null) { | |
throw Exception\Comment::notFound($command->respondedCommentId()); | |
} | |
$response = $respondedComment->addUserResponse( | |
$command->commentAuthorId(), | |
$command->newCommentId(), | |
$command->commentText() | |
); | |
$this->commentCollection->add($response); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment