07. Introduction of QueryBus

This commit is contained in:
theUniC
2022-09-05 08:49:17 +02:00
parent 0351c7b04a
commit be8e771617
11 changed files with 124 additions and 6 deletions

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Symfony\Component\Messenger\Stamp
{
/** @template T */
final class HandledStamp implements StampInterface
{
/** @psam-return T */
public function getResult() { }
}
}
namespace Symfony\Component\Messenger
{
trait HandleTrait
{
/**
* @template T
* @psalm-param object|Envelope $message
* @psalm-return T|Envelope<HandledStamp>
*/
private function handle($message) { }
}
}

View File

@@ -12,6 +12,9 @@ framework:
- doctrine_ping_connection
- doctrine_close_connection
- doctrine_transaction
query_bus:
middleware:
- validation
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration

View File

@@ -14,6 +14,8 @@ services:
Cheeper\DomainModel\Follow\FollowRepository: '@Cheeper\Infrastructure\Persistence\DoctrineOrmFollowRepository'
Cheeper\Application\EventBus: '@Cheeper\Infrastructure\Application\SymfonyMessengerEventBus'
Redis: '@snc_redis.default'
Symfony\Component\Messenger\MessageBusInterface $commandBus: '@command_bus'
Symfony\Component\Messenger\MessageBusInterface $queryBus: '@query_bus'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name

View File

@@ -17,6 +17,7 @@
<stubs>
<file name=".psalm-stubs/phpredis.phpstub"/>
<file name=".psalm-stubs/symfony-messenger.phpstub"/>
</stubs>
<plugins>

View File

@@ -6,6 +6,8 @@ namespace App\Controller;
use Cheeper\Application\CountFollowers\CountFollowersQuery;
use Cheeper\Application\CountFollowers\CountFollowersQueryHandler;
use Cheeper\Application\CountFollowers\CountFollowersQueryResponse;
use Cheeper\Application\QueryBus;
use Cheeper\DomainModel\Author\AuthorDoesNotExist;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -19,6 +21,7 @@ final class GetFollowersCountController extends AbstractController
public function __construct(
private readonly CountFollowersQueryHandler $countFollowersQueryHandler,
private readonly QueryBus $queryBus,
) {
}
@@ -50,15 +53,16 @@ final class GetFollowersCountController extends AbstractController
$this->assertValidUuid($authorId);
try {
$count = ($this->countFollowersQueryHandler)(
$response = $this->queryBus->askFor(
new CountFollowersQuery($authorId)
);
assert($response instanceof CountFollowersQueryResponse);
} catch (AuthorDoesNotExist $e) {
throw $this->createNotFoundException($e->getMessage());
}
return $this->json([
'count' => $count,
'count' => $response->totalNumberOfFollowers,
]);
}
}

View File

@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace Cheeper\Application\CountFollowers;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class CountFollowersQueryHandler
{
public function __construct(
@@ -11,10 +14,12 @@ final class CountFollowersQueryHandler
) {
}
public function __invoke(CountFollowersQuery $query): int
public function __invoke(CountFollowersQuery $query): CountFollowersQueryResponse
{
$totalFollowers = $this->redis->get("followers_of:" . $query->authorId);
return false === $totalFollowers ? 0 : (int)$totalFollowers;
return new CountFollowersQueryResponse(
false === $totalFollowers ? 0 : (int)$totalFollowers
);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Cheeper\Application\CountFollowers;
use Cheeper\Application\QueryResponse;
/** @psalm-immutable */
final class CountFollowersQueryResponse implements QueryResponse
{
public function __construct(
public readonly int $totalNumberOfFollowers
) {
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Cheeper\Application;
/**
* @template TQuery of Query
* @template TQueryResponse of QueryResponse
*/
interface QueryBus
{
/**
* @psalm-param TQuery $query
* @psalm-return TQueryResponse
*/
public function askFor(Query $query): QueryResponse;
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Cheeper\Application;
/** @psalm-immutable */
interface QueryResponse
{
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Cheeper\Infrastructure\Application;
use Cheeper\Application\Query;
use Cheeper\Application\QueryBus;
use Cheeper\Application\QueryResponse;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Exception\LogicException;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\HandledStamp;
final class SymfonyMessengerQueryBus implements QueryBus
{
use HandleTrait;
public function __construct(
MessageBusInterface $queryBus
) {
$this->messageBus = $queryBus;
}
public function askFor(Query $query): QueryResponse
{
$result = $this->handle($query);
assert($result instanceof QueryResponse);
return $result;
}
}

View File

@@ -98,10 +98,10 @@ final class CountFollowersQueryHandlerTest extends TestCase
Iter\apply($events, ($this->authorWasFollowedEventHandler)(...));
$totalNumberOfFollowers = ($this->countFollowersQueryHandler)(
$queryResponse = ($this->countFollowersQueryHandler)(
new CountFollowersQuery($authorId)
);
$this->assertSame(3, $totalNumberOfFollowers);
$this->assertSame(3, $queryResponse->totalNumberOfFollowers);
}
}