Skip to content

Commit

Permalink
Added cache implementation for queries
Browse files Browse the repository at this point in the history
  • Loading branch information
tg666 committed Feb 5, 2024
1 parent 2967bd2 commit 5a28646
Show file tree
Hide file tree
Showing 13 changed files with 316 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"kubawerlos/php-cs-fixer-custom-fixers": "^3.13",
"latte/latte": "^3.0",
"nette/application": "^3.1",
"nette/caching": "^3.2",
"nette/mail": "^3.1",
"nette/security": "^3.1",
"nette/tester": "^2.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
infrastructure.cache:
autowired: SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheInterface
type: SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheInterface
factory: @extension.infrastructure.cache.nette

infrastructure.cache.nette:
autowired: no
factory: SixtyEightPublishers\ArchitectureBundle\Infrastructure\NetteCache\NetteCache
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ middleware:
- Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware(query_bus)
- Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware
- SixtyEightPublishers\ArchitectureBundle\Bridge\Symfony\Messenger\Middleware\OriginalExceptionMiddleware
- SixtyEightPublishers\ArchitectureBundle\Bridge\Symfony\Messenger\Middleware\QueryCacheMiddleware
panel: %debugMode%
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Bridge\Symfony\Messenger\Middleware;

use Psr\Log\LoggerInterface;
use SixtyEightPublishers\ArchitectureBundle\Bridge\Symfony\Messenger\Stamp\RefreshCacheStamp;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheInterface;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\UnableToReadCacheException;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\UnableToWriteCacheException;
use SixtyEightPublishers\ArchitectureBundle\ReadModel\Query\CachableQueryInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;

final class QueryCacheMiddleware implements MiddlewareInterface
{
public function __construct(
private readonly CacheInterface $cache,
private readonly ?LoggerInterface $logger = null,
) {}

public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
$message = $envelope->getMessage();

if (!($message instanceof CachableQueryInterface)) {
return $stack->next()->handle(
envelope: $envelope,
stack: $stack,
);
}

$refreshCache = $envelope->last(RefreshCacheStamp::class) !== null;
$cacheMetadata = $message->createCacheMetadata();

try {
$item = !$refreshCache ? $this->cache->getItem(
key: $cacheMetadata->key,
) : null;
} catch (UnableToReadCacheException $e) {
if (null === $this->logger) {
throw $e;
}

$this->logger->error(
message: $e->getMessage(),
context: [
'exception' => $e,
],
);

$item = null;
}

if (null !== $item) {
return $item;
}

$item = $stack->next()->handle(
envelope: $envelope,
stack: $stack,
);

try {
$this->cache->saveItem(
metadata: $cacheMetadata,
item: $item,
);
} catch (UnableToWriteCacheException $e) {
if (null === $this->logger) {
throw $e;
}

$this->logger->error(
message: $e->getMessage(),
context: [
'exception' => $e,
],
);
}

return $item;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Bridge\Symfony\Messenger\Stamp;

use Symfony\Component\Messenger\Stamp\StampInterface;

final class RefreshCacheStamp implements StampInterface
{
}
30 changes: 30 additions & 0 deletions src/ArchitectureBundle/Infrastructure/Cache/CacheInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache;

interface CacheInterface
{
/**
* @throws UnableToReadCacheException
*/
public function getItem(string $key): mixed;

/**
* @throws UnableToWriteCacheException
*/
public function saveItem(CacheMetadata $metadata, mixed $item): void;

/**
* @throws UnableToDeleteCacheException
*/
public function deleteItem(string $key): void;

/**
* @param array<int, string>|null $tags
*
* @throws UnableToDeleteCacheException
*/
public function clean(?array $tags = null): void;
}
19 changes: 19 additions & 0 deletions src/ArchitectureBundle/Infrastructure/Cache/CacheMetadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache;

use DateTimeImmutable;

final class CacheMetadata
{
/**
* @param array<int, string> $tags
*/
public function __construct(
public readonly string $key,
public readonly int|DateTimeImmutable|null $expiration,
public readonly array $tags = [],
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache;

use RuntimeException;

final class UnableToDeleteCacheException extends RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache;

use RuntimeException;

final class UnableToReadCacheException extends RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache;

use RuntimeException;

final class UnableToWriteCacheException extends RuntimeException
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function pingConnection(): void

try {
$connection->executeQuery($connection->getDatabasePlatform()->getDummySelectSQL());
} catch (DBALException $e) {
} catch (DbalException $e) {
$connection->close();
$connection->connect();
}
Expand Down
113 changes: 113 additions & 0 deletions src/ArchitectureBundle/Infrastructure/NetteCache/NetteCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\Infrastructure\NetteCache;

use DateTimeImmutable;
use Nette\Caching\Cache;
use Nette\Caching\Storage;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheInterface;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheMetadata;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\UnableToDeleteCacheException;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\UnableToReadCacheException;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\UnableToWriteCacheException;
use Throwable;
use function count;
use function is_int;

final class NetteCache implements CacheInterface
{
private readonly Cache $cache;

public function __construct(
Storage $storage,
) {
$this->cache = new Cache(
storage: $storage,
namespace: self::class,
);
}

public function getItem(string $key): mixed
{
try {
return $this->cache->load(
key: $key,
);
} catch (Throwable $e) {
throw new UnableToReadCacheException(
message: $e->getMessage(),
code: $e->getCode(),
previous: $e,
);
}
}

public function saveItem(CacheMetadata $metadata, mixed $item): void
{
$dependencies = [];

if ($metadata->expiration instanceof DateTimeImmutable) {
$dependencies[Cache::Expire] = $metadata->expiration->format('U.u');
} elseif (is_int($metadata->expiration)) {
$dependencies[Cache::Expire] = $metadata->expiration + time();
}

if (0 < count($metadata->tags)) {
$dependencies[Cache::Tags] = $metadata->tags;
}

try {
$this->cache->save(
key: $metadata->key,
data: $item,
dependencies: $dependencies,
);
} catch (Throwable $e) {
throw new UnableToWriteCacheException(
message: $e->getMessage(),
code: $e->getCode(),
previous: $e,
);
}
}

public function deleteItem(string $key): void
{
try {
$this->cache->remove(
key: $key,
);
} catch (Throwable $e) {
throw new UnableToDeleteCacheException(
message: $e->getMessage(),
code: $e->getCode(),
previous: $e,
);
}
}

public function clean(?array $tags = null): void
{
$conditions = null !== $tags
? [
Cache::Tags => $tags,
]
: [
Cache::All => true,
];

try {
$this->cache->clean(
conditions: $conditions,
);
} catch (Throwable $e) {
throw new UnableToDeleteCacheException(
message: $e->getMessage(),
code: $e->getCode(),
previous: $e,
);
}
}
}
12 changes: 12 additions & 0 deletions src/ArchitectureBundle/ReadModel/Query/CachableQueryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\ArchitectureBundle\ReadModel\Query;

use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Cache\CacheMetadata;

interface CachableQueryInterface extends QueryInterface
{
public function createCacheMetadata(): CacheMetadata;
}

0 comments on commit 5a28646

Please sign in to comment.