Refactor point value object and add observability
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Payload\SignalPayload;
|
||||
use App\Entity\Signal;
|
||||
use App\Exception\SubmissionRateLimitedException;
|
||||
use App\Message\SignalCreatedMessage;
|
||||
use App\Repository\SignalRepository;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
||||
|
||||
final class SignalSubmissionService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SignalRepository $signals,
|
||||
#[Autowire(service: 'limiter.signal_submission')]
|
||||
private readonly RateLimiterFactory $submissionLimiter,
|
||||
private readonly PointProximityValidator $proximityValidator,
|
||||
private readonly MessageBusInterface $bus,
|
||||
#[Autowire(service: 'monolog.logger.signals')]
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function submit(string $clientKey, SignalPayload $payload): Signal
|
||||
{
|
||||
$this->enforceRateLimit($clientKey);
|
||||
$signalLocation = $payload->signalLocation;
|
||||
$userLocation = $payload->userLocation;
|
||||
|
||||
$this->proximityValidator->assertWithinRange($userLocation, $signalLocation);
|
||||
|
||||
$this->logger->info('Storing new signal submission.', [
|
||||
'client_key' => $clientKey,
|
||||
'signal_location' => [
|
||||
'lat' => round($signalLocation->getLat(), 5),
|
||||
'lng' => round($signalLocation->getLng(), 5),
|
||||
],
|
||||
'user_location' => [
|
||||
'lat' => round($userLocation->getLat(), 5),
|
||||
'lng' => round($userLocation->getLng(), 5),
|
||||
],
|
||||
]);
|
||||
|
||||
$signal = (new Signal())
|
||||
->setUserKey($clientKey)
|
||||
->setUserLocation($userLocation)
|
||||
->setSignalLocation($signalLocation)
|
||||
->setCreatedAt(new DateTimeImmutable('now', new DateTimeZone('UTC')));
|
||||
|
||||
$this->signals->save($signal);
|
||||
|
||||
$signalId = $signal->getId();
|
||||
if ($signalId !== null) {
|
||||
$this->logger->debug('Dispatching signal created message.', [
|
||||
'signal_id' => $signalId,
|
||||
]);
|
||||
$this->bus->dispatch(new SignalCreatedMessage($signalId));
|
||||
$this->logger->info('Signal stored successfully.', [
|
||||
'signal_id' => $signalId,
|
||||
'client_key' => $clientKey,
|
||||
]);
|
||||
}
|
||||
|
||||
return $signal;
|
||||
}
|
||||
|
||||
private function enforceRateLimit(string $clientKey): void
|
||||
{
|
||||
$rateLimiter = $this->submissionLimiter->create($clientKey);
|
||||
$limit = $rateLimiter->consume(1);
|
||||
|
||||
if (! $limit->isAccepted()) {
|
||||
$retryAfter = $limit->getRetryAfter();
|
||||
$this->logger->warning('Signal submission rejected due to rate limiting.', [
|
||||
'client_key' => $clientKey,
|
||||
'retry_after' => $retryAfter?->format(DATE_ATOM),
|
||||
]);
|
||||
throw new SubmissionRateLimitedException($limit->getRetryAfter());
|
||||
}
|
||||
|
||||
$this->logger->debug('Signal submission passed rate limiting.', [
|
||||
'client_key' => $clientKey,
|
||||
'remaining_tokens' => $limit->getRemainingTokens(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user