[Chapter 8] Improvements

This commit is contained in:
Carlos Buenosvinos
2022-03-22 06:58:03 +01:00
parent 92306c8a12
commit defe686f2a
14 changed files with 369 additions and 8 deletions

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Command;
use Cheeper\Chapter7\Application\Command;
use Cheeper\Chapter7\Application\MessageTrait;
final class NotifyToAuthorAboutNewFollowerCommand implements Command
{
use MessageTrait;
private function __construct(
private string $fromAuthorId,
private string $toAuthorId
) {
$this->stampAsNewMessage();
}
public function fromAuthorId(): string
{
return $this->fromAuthorId;
}
public function toAuthorId(): string
{
return $this->toAuthorId;
}
public function followId(): string
{
return $this->followId;
}
public static function fromArray(array $array): self
{
return new self(
$array['from_author_id'] ?? '',
$array['to_author_id'] ?? '',
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Command;
use Cheeper\AllChapters\DomainModel\Author\AuthorDoesNotExist;
use Cheeper\AllChapters\DomainModel\Author\AuthorId;
use Cheeper\Chapter7\DomainModel\Author\Author;
use Cheeper\Chapter7\DomainModel\Author\AuthorRepository;
use Cheeper\Chapter7\DomainModel\Notifier\Notifier;
final class NotifyToAuthorAboutNewFollowerCommandHandler
{
public function __construct(
private AuthorRepository $authors,
private Notifier $notifier
) {
}
public function __invoke(NotifyToAuthorAboutNewFollowerCommand $command): void
{
$authorId = AuthorId::fromString($command->toAuthorId());
$author = $this->authors->ofId($authorId);
$this->checkAuthorExist($author, $authorId);
$this->notifier->notify($author);
}
private function checkAuthorExist(?Author $author, AuthorId $authorId): void
{
if (null === $author) {
throw AuthorDoesNotExist::withAuthorIdOf($authorId);
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Command;
use Cheeper\Chapter7\Application\Command;
use Cheeper\Chapter7\Application\MessageTrait;
final class WelcomeCommand implements Command
{
use MessageTrait;
private function __construct(
private string $authorId,
) {
$this->stampAsNewMessage();
}
public static function ofAuthorId(string $authorId): self
{
return new self($authorId);
}
public function authorId(): string
{
return $this->authorId;
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Command;
use Cheeper\AllChapters\DomainModel\Author\AuthorDoesNotExist;
use Cheeper\AllChapters\DomainModel\Author\AuthorId;
use Cheeper\Chapter7\DomainModel\Author\Author;
use Cheeper\Chapter7\DomainModel\Author\AuthorRepository;
use Cheeper\Chapter7\DomainModel\Notifier\Notifier;
final class WelcomeCommandHandler
{
public function __construct(
private AuthorRepository $authors,
private Notifier $notifier
) {
}
public function __invoke(WelcomeCommand $command): void
{
$authorId = AuthorId::fromString($command->authorId());
$author = $this->authors->ofId($authorId);
$this->checkAuthorExist($author, $authorId);
$this->notifier->notify($author);
}
private function checkAuthorExist(?Author $author, AuthorId $authorId): void
{
if (null === $author) {
throw AuthorDoesNotExist::withAuthorIdOf($authorId);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Event\AuthorFollowed;
use Cheeper\Chapter7\Application\Author\Projection\IncrementCountFollowersProjection;
use Cheeper\Chapter7\Application\ProjectionBus;
use Cheeper\Chapter7\DomainModel\Follow\AuthorFollowed;
//snippet snippet
final class WhenAuthorFollowedThenIncrementFollowersProjectionEventHandler
{
public function __construct(
private ProjectionBus $projectionBus
) {
}
public function __invoke(AuthorFollowed $event): void
{
$this->projectionBus->project(
IncrementCountFollowersProjection::ofAuthor(
$event->toAuthorId()
)
);
}
}
//end-snippet

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Event\AuthorFollowed;
use Cheeper\Chapter7\Application\Author\Command\NotifyToAuthorAboutNewFollowerCommand;
use Cheeper\Chapter7\Application\CommandBus;
use Cheeper\Chapter7\DomainModel\Follow\AuthorFollowed;
//snippet snippet
final class WhenAuthorFollowedThenNotifyAuthorEventHandler
{
public function __construct(
private CommandBus $commandBus
) {
}
public function __invoke(AuthorFollowed $event): void
{
$this->commandBus->handle(
NotifyToAuthorAboutNewFollowerCommand::fromArray([
'from_author_id' => $event->fromAuthorId(),
'to_author_id' => $event->toAuthorId(),
])
);
}
}
//end-snippet

View File

@@ -19,8 +19,19 @@ final class AuthorFollowedEventHandler
public function __invoke(AuthorFollowed $event): void
{
$this->projectionHandler->__invoke(
CountFollowersProjection::ofAuthor($event->toAuthorId())
CountFollowersProjection::ofAuthor(
$event->toAuthorId()
)
);
// Other actions, like welcoming the new author,
// can be added here. Alternatively, a more
// scalable design is to create one Event Handler
// for each of the needed actions to happen in
// reaction. All of those listening to the same
// AuthorFollowed Domain Event.
// @see: WhenAuthorFollowedThenCreateFollowersCounterProjectionEventHandler
// @see: WhenAuthorFollowedThenWelcomeAuthorEventHandler
}
}
//end-snippet
//end-snippet

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Event\NewAuthorSigned;
use Cheeper\Chapter7\Application\Author\Projection\CountFollowersProjection;
use Cheeper\Chapter7\Application\Author\Projection\CountFollowersProjectionHandlerInterface;
use Cheeper\Chapter7\DomainModel\Author\NewAuthorSigned;
//snippet author-followed-event-handler
final class WhenNewAuthorSignedThenCreateEmptyFollowersCounterProjectionEventHandler
{
public function __construct(
private CountFollowersProjectionHandlerInterface $projectionHandler
) {
}
public function __invoke(NewAuthorSigned $event): void
{
$this->projectionHandler->__invoke(
CountFollowersProjection::ofAuthor($event->authorId())
);
}
}
//end-snippet

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Event\AuthorFollowed;
use Cheeper\Chapter7\Application\Author\Command\WelcomeCommand;
use Cheeper\Chapter7\Application\CommandBus;
use Cheeper\Chapter7\DomainModel\Author\NewAuthorSigned;
//snippet snippet
final class WhenNewAuthorSignedThenWelcomeAuthorEventHandler
{
public function __construct(
private CommandBus $commandBus
) {
}
public function __invoke(NewAuthorSigned $event): void
{
$this->commandBus->handle(
WelcomeCommand::ofAuthorId($event->authorId())
);
}
}
//end-snippet

View File

@@ -24,6 +24,15 @@ final class NewAuthorSignedEventHandler
$event->authorUsername()
)
);
// Other actions, like welcoming the new author,
// can be added here. Alternatively, a more
// scalable design is to create one Event Handler
// for each of the needed actions to happen in
// reaction. All of those listening to the same
// NewAuthorSigned Domain Event.
// @see: WhenNewAuthorSignedThenCreateEmptyFollowersCounterProjectionEventHandler
// @see: WhenNewAuthorSignedThenWelcomeAuthorEventHandler
}
}
//end-snippet

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\Application\Author\Projection;
use Cheeper\Chapter7\Application\MessageTrait;
use Cheeper\Chapter7\Application\Projection;
//snippet snippet
final class IncrementCountFollowersProjection implements Projection
{
use MessageTrait;
public static function ofAuthor(string $authorId): self
{
return new self($authorId);
}
private function __construct(
private string $authorId
) {
$this->stampAsNewMessage();
}
public function authorId(): string
{
return $this->authorId;
}
}
//end-snippet

View File

@@ -2,21 +2,19 @@
declare(strict_types=1);
namespace Cheeper\Chapter8\Application\Author\Projection;
namespace Cheeper\Chapter7\Application\Author\Projection;
use Cheeper\Chapter7\Application\Author\Projection\CountFollowersProjection;
use Cheeper\Chapter7\Application\Author\Projection\CreateFollowersCounterProjectionHandler;
use Redis;
//snippet projector-count-followers
final class CountFollowersProjectionHandler
//snippet snippet
final class IncrementCountFollowersProjectionHandler
{
public function __construct(
private Redis $redis
) {
}
public function __invoke(CountFollowersProjection $projection): void
public function __invoke(IncrementCountFollowersProjection $projection): void
{
$this->redis->hIncrBy(
sprintf(

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter7\DomainModel\Notifier;
use Cheeper\Chapter7\DomainModel\Author\Author;
interface Notifier
{
public function notify(Author $author): void;
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Cheeper\Chapter8\Application\Author\Event;
use Cheeper\Chapter6\Application\ProjectionBus;
use Cheeper\Chapter7\Application\Author\Command\NotifyToAuthorAboutNewFollowerCommand;
use Cheeper\Chapter7\Application\Author\Projection\IncrementCountFollowersProjection;
use Cheeper\Chapter7\Application\CommandBus;
use Cheeper\Chapter7\DomainModel\Follow\AuthorFollowed;
//snippet snippet
final class AuthorFollowedEventHandler
{
public function __construct(
private ProjectionBus $projectionBus,
private CommandBus $commandBus,
) {
}
public function __invoke(AuthorFollowed $event): void
{
$this->projectionBus->project(
IncrementCountFollowersProjection::ofAuthor(
$event->toAuthorId()
)
);
// Other actions, like notifying the author
// about the new follower can be added here.
// Alternatively, a more scalable design is
// to create one Event Handler for each of
// the needed actions to happen in reaction.
// All of those listening to the same
// AuthorFollowed Domain Event.
// @see: WhenAuthorFollowedThenIncrementFollowersProjectionEventHandler
// @see: WhenAuthorFollowedThenWelcomeAuthorEventHandler
$this->commandBus->handle(
NotifyToAuthorAboutNewFollowerCommand::fromArray([
'from_author_id' => $event->fromAuthorId(),
'to_author_id' => $event->toAuthorId(),
])
);
}
}
//end-snippet