Skip to content

Commit

Permalink
[EPC-9177] Implement removal in batch via single SQL query
Browse files Browse the repository at this point in the history
  • Loading branch information
Can Demiralp committed Feb 12, 2025
1 parent f39b8ca commit 32ff97c
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 101 deletions.
25 changes: 4 additions & 21 deletions Api/Repository/AdyenNotificationRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Adyen Payment Module
*
* Copyright (c) 2024 Adyen N.V.
* Copyright (c) 2025 Adyen N.V.
* This file is open source and available under the MIT license.
* See the LICENSE file for more info.
*
Expand All @@ -12,28 +12,11 @@

namespace Adyen\Payment\Api\Repository;

use Adyen\Payment\Api\Data\NotificationInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\Exception\LocalizedException;

interface AdyenNotificationRepositoryInterface
{
/**
* Retrieve Adyen Notification entities which match a specified criteria.
*
* @param SearchCriteriaInterface $searchCriteria
* @return SearchResultsInterface
*
* @throws LocalizedException
*/
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface;

/**
* Deletes a specified Adyen notification.
*
* @param NotificationInterface $entity The notification ID.
* @return bool
* @param array $entityIds
* @return void
*/
public function delete(NotificationInterface $entity): bool;
public function deleteByIds(array $entityIds): void;
}
51 changes: 24 additions & 27 deletions Cron/Providers/ProcessedWebhooksProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,46 @@

namespace Adyen\Payment\Cron\Providers;

use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Logger\AdyenLogger;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Exception\LocalizedException;
use Adyen\Payment\Model\ResourceModel\Notification\Collection;
use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory;

class ProcessedWebhooksProvider implements WebhooksProviderInterface
{
/**
* @param CollectionFactory $notificationCollectionFactory
* @param Config $configHelper
*/
public function __construct(
private readonly AdyenNotificationRepositoryInterface $adyenNotificationRepository,
private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
private readonly Config $configHelper,
private readonly AdyenLogger $adyenLogger
private readonly CollectionFactory $notificationCollectionFactory,
private readonly Config $configHelper
) { }

/**
* Provides the `entity_id`s of the processed webhooks limited by the removal time
*
* @return array
*/
public function provide(): array
{
$numberOfDays = $this->configHelper->getProcessedWebhookRemovalTime();

$dateFrom = date('Y-m-d H:i:s', time() - $numberOfDays * 24 * 60 * 60);

$searchCriteria = $this->searchCriteriaBuilder
->addFilter('done', 1)
->addFilter('processing', 0)
->addFilter('created_at', $dateFrom, 'lteq')
->setPageSize(self::BATCH_SIZE)
->create();

try {
$items = $this->adyenNotificationRepository->getList($searchCriteria);
return $items->getItems();
} catch (LocalizedException $e) {
$errorMessage = sprintf(
__('An error occurred while providing webhooks older than %s days!'),
$numberOfDays
);

$this->adyenLogger->error($errorMessage);
/** @var Collection $notificationCollection */
$notificationCollection = $this->notificationCollectionFactory->create();
$notificationCollection->getProcessedWebhookIdsByTimeLimit($numberOfDays, self::BATCH_SIZE);

if ($notificationCollection->getSize() > 0) {
return $notificationCollection->getColumnValues('entity_id');
} else {
return [];
}
}

/**
* Returns the provider name
*
* @return string
*/
public function getProviderName(): string
{
return "Adyen processed webhooks provider";
Expand Down
47 changes: 24 additions & 23 deletions Cron/RemoveProcessedWebhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Adyen\Payment\Cron\Providers\WebhooksProviderInterface;
use Adyen\Payment\Helper\Config;
use Adyen\Payment\Logger\AdyenLogger;
use Adyen\Payment\Model\Notification;
use Exception;

class RemoveProcessedWebhooks
Expand Down Expand Up @@ -44,27 +43,17 @@ public function execute(): void
$numberOfItemsRemoved = 0;

foreach ($this->providers as $provider) {
/** @var Notification $notificationToCleanup */
foreach ($provider->provide() as $notificationToCleanup) {
try {
$isSuccessfullyDeleted = $this->adyenNotificationRepository->delete($notificationToCleanup);

if ($isSuccessfullyDeleted) {
$message = __(
'%1: Notification with entity_id %2 has been deleted because it was processed %3 days ago.',
$provider->getProviderName(),
$notificationToCleanup->getEntityId(),
$this->configHelper->getProcessedWebhookRemovalTime()
);
$this->adyenLogger->addAdyenNotification($message);
$webhookIdsToRemove = $provider->provide();
$numberOfWebhooksProvided = count($webhookIdsToRemove);

$numberOfItemsRemoved++;
}
if ($numberOfWebhooksProvided > 0) {
try {
$this->adyenNotificationRepository->deleteByIds($webhookIdsToRemove);
$numberOfItemsRemoved += $numberOfWebhooksProvided;
} catch (Exception $e) {
$message = __(
'%1: An error occurred while deleting the notification with entity_id %2: %3',
'%1: An error occurred while deleting webhooks! %2',
$provider->getProviderName(),
$notificationToCleanup->getEntityId(),
$e->getMessage()
);

Expand All @@ -73,11 +62,23 @@ public function execute(): void
}
}

$successMessage = __(
'%1 processed webhooks have been removed by the RemoveProcessedWebhooks cronjob.',
$numberOfItemsRemoved
);
$this->adyenLogger->addAdyenNotification($successMessage);
if ($numberOfItemsRemoved > 0) {
$successMessage = __(
'%1 processed webhooks have been removed by the RemoveProcessedWebhooks cronjob.',
$numberOfItemsRemoved
);

$this->adyenLogger->addAdyenNotification($successMessage);
} else {
$debugMessage = __(
'There is no webhooks to be removed by RemoveProcessedWebhooks cronjob.',
$numberOfItemsRemoved
);

$this->adyenLogger->addAdyenDebug($debugMessage);
}


} else {
$message = __('Processed webhook removal feature is disabled. The cronjob has been skipped!');
$this->adyenLogger->addAdyenDebug($message);
Expand Down
38 changes: 10 additions & 28 deletions Model/AdyenNotificationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,42 @@
*
* Adyen Payment module (https://www.adyen.com/)
*
* Copyright (c) 2024 Adyen N.V. (https://www.adyen.com/)
* Copyright (c) 2025 Adyen N.V. (https://www.adyen.com/)
* See LICENSE.txt for license details.
*
* Author: Adyen <magento@adyen.com>
*/

namespace Adyen\Payment\Model;

use Adyen\Payment\Api\Data\NotificationInterface;
use Adyen\Payment\Api\Repository\AdyenNotificationRepositoryInterface;
use Adyen\Payment\Model\ResourceModel\Notification\CollectionFactory;
use Magento\Framework\Api\Search\SearchResultFactory;
use Magento\Framework\Api\SearchCriteria\CollectionProcessor;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\ObjectManagerInterface;

class AdyenNotificationRepository implements AdyenNotificationRepositoryInterface
{
/**
* @param SearchResultFactory $searchResultsFactory
* @param CollectionFactory $collectionFactory
* @param CollectionProcessor $collectionProcessor
* @param ObjectManagerInterface $objectManager
* @param string $resourceModel
*/
public function __construct(
private readonly SearchResultFactory $searchResultsFactory,
private readonly CollectionFactory $collectionFactory,
private readonly CollectionProcessor $collectionProcessor,
private readonly ObjectManagerInterface $objectManager,
private readonly string $resourceModel
) { }

/**
* @param SearchCriteriaInterface $searchCriteria
* @return SearchResultsInterface
* Delete multiple entities with the given IDs
*
* @param array $entityIds
* @return void
*/
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
public function deleteByIds(array $entityIds): void
{
$searchResult = $this->searchResultsFactory->create();
$collection = $this->collectionFactory->create();
$this->collectionProcessor->process($searchCriteria, $collection);
$searchResult->setItems($collection->getItems());
$searchResult->setTotalCount($collection->getSize());
if (empty($entityIds)) {
return;
}

return $searchResult;
}

public function delete(NotificationInterface $entity): bool
{
$resource = $this->objectManager->get($this->resourceModel);
$resource->delete($entity);

return true;
$resource->deleteByIds($entityIds);
}
}
28 changes: 27 additions & 1 deletion Model/ResourceModel/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

namespace Adyen\Payment\Model\ResourceModel;

class Notification extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;

class Notification extends AbstractDb
{
/**
* Construct
Expand Down Expand Up @@ -49,4 +52,27 @@ public function getNotification($pspReference, $eventCode, $success, $originalRe

return $this->getConnection()->fetchAll($select);
}

/**
* Deletes the rows corresponding to the given `entity_id`s
*
* @param array $entitiyIds
* @return void
* @throws LocalizedException
*/
public function deleteByIds(array $entitiyIds): void
{
if (empty($entitiyIds)) {
return;
}

$tableName = $this->getMainTable();

$connection = $this->getConnection();
$select = $connection->select()
->from(['notification' => $tableName])
->where('notification.entity_id IN (?)', $entitiyIds);

$connection->query($select->deleteFromSelect('notification'));
}
}
24 changes: 24 additions & 0 deletions Model/ResourceModel/Notification/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,28 @@ public function notificationsToProcessFilter()

return $this;
}

/**
* Returns the `entity_id`s of the processed webhooks with the given time limit.
*
* @param int $processedWebhookRemovalTime
* @param int|null $batchSize
* @return $this
*/
public function getProcessedWebhookIdsByTimeLimit(
int $processedWebhookRemovalTime,
?int $batchSize = null
): Collection {
$dateFrom = date('Y-m-d H:i:s', time() - $processedWebhookRemovalTime * 24 * 60 * 60);

$this->addFieldToFilter('created_at', ['lteq' => $dateFrom]);
$this->addFieldToFilter('done', 1);
$this->addFieldToFilter('processing', 0);

if (isset($batchSize)) {
$this->setPageSize($batchSize);
}

return $this;
}
}
2 changes: 1 addition & 1 deletion etc/crontab.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<schedule>0 0 * * *</schedule>
</job>
<job name="adyen_payment_remove_processed_webhooks" instance="Adyen\Payment\Cron\RemoveProcessedWebhooks" method="execute">
<schedule>0 0 * * *</schedule>
<schedule>*/1 * * * *</schedule>
</job>
</group>
</config>

0 comments on commit 32ff97c

Please sign in to comment.