Skip to content

Instantly share code, notes, and snippets.

@codeliner
Created July 7, 2015 19:07
Show Gist options
  • Save codeliner/b173485940aa64b59b79 to your computer and use it in GitHub Desktop.
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
<?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