123 lines
4.3 KiB
PHP
123 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Temporal\WebhookDelivery;
|
|
|
|
use Carbon\CarbonInterval;
|
|
use Temporal\Activity\ActivityOptions;
|
|
use Temporal\Common\RetryOptions;
|
|
use Temporal\Workflow;
|
|
|
|
class WebhookDeliveryWorkflow implements WebhookDeliveryWorkflowInterface
|
|
{
|
|
private array $endpointStatuses = [];
|
|
private int $totalDelivered = 0;
|
|
private int $totalFailed = 0;
|
|
private int $totalDeadLettered = 0;
|
|
private int $retryCount = 0;
|
|
|
|
/** @var WebhookDeliveryActivityInterface */
|
|
private $deliveryStub;
|
|
|
|
/** @var WebhookDeliveryActivityInterface */
|
|
private $deadLetterStub;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->deliveryStub = Workflow::newActivityStub(
|
|
WebhookDeliveryActivityInterface::class,
|
|
ActivityOptions::new()
|
|
->withStartToCloseTimeout(CarbonInterval::seconds(30))
|
|
->withRetryOptions(
|
|
RetryOptions::new()
|
|
->withMaximumAttempts(3)
|
|
->withInitialInterval(CarbonInterval::seconds(1))
|
|
->withBackoffCoefficient(3.0)
|
|
)
|
|
);
|
|
|
|
$this->deadLetterStub = Workflow::newActivityStub(
|
|
WebhookDeliveryActivityInterface::class,
|
|
ActivityOptions::new()
|
|
->withStartToCloseTimeout(CarbonInterval::seconds(10))
|
|
->withRetryOptions(
|
|
RetryOptions::new()->withMaximumAttempts(1)
|
|
)
|
|
);
|
|
}
|
|
|
|
public function deliver(array $payload, array $endpoints, array $simulationConfig = []): \Generator
|
|
{
|
|
// Initialize endpoint statuses
|
|
foreach ($endpoints as $endpoint) {
|
|
$this->endpointStatuses[$endpoint] = [
|
|
'status' => 'pending',
|
|
'attempt' => 0,
|
|
'responseTime' => 0,
|
|
];
|
|
}
|
|
|
|
// Fan-out: deliver to all endpoints in parallel
|
|
$promises = [];
|
|
foreach ($endpoints as $endpoint) {
|
|
$this->endpointStatuses[$endpoint]['status'] = 'delivering';
|
|
|
|
$promises[$endpoint] = Workflow::async(function () use ($endpoint, $payload, $simulationConfig) {
|
|
try {
|
|
$result = yield $this->deliveryStub->deliverToEndpoint($endpoint, $payload, $simulationConfig);
|
|
return ['endpoint' => $endpoint, 'success' => true, 'result' => (array) $result];
|
|
} catch (\Throwable $e) {
|
|
return ['endpoint' => $endpoint, 'success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
});
|
|
}
|
|
|
|
// Collect results from all parallel activities
|
|
foreach ($promises as $endpoint => $promise) {
|
|
$outcome = (array) (yield $promise);
|
|
|
|
if ($outcome['success']) {
|
|
$result = $outcome['result'];
|
|
$this->endpointStatuses[$endpoint] = [
|
|
'status' => 'delivered',
|
|
'attempt' => $result['attempt'] ?? 1,
|
|
'responseTime' => $result['responseTime'] ?? 0,
|
|
];
|
|
$this->totalDelivered++;
|
|
|
|
$attempt = $result['attempt'] ?? 1;
|
|
if ($attempt > 1) {
|
|
$this->retryCount += ($attempt - 1);
|
|
}
|
|
} else {
|
|
$this->endpointStatuses[$endpoint]['status'] = 'failed';
|
|
$this->totalFailed++;
|
|
|
|
// Dead-letter failed deliveries
|
|
yield $this->deadLetterStub->deadLetter($endpoint, $payload, $outcome['error'] ?? 'Unknown error');
|
|
$this->endpointStatuses[$endpoint]['status'] = 'dead_lettered';
|
|
$this->totalDeadLettered++;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'status' => 'completed',
|
|
'totalDelivered' => $this->totalDelivered,
|
|
'totalFailed' => $this->totalFailed,
|
|
'totalDeadLettered' => $this->totalDeadLettered,
|
|
'retryCount' => $this->retryCount,
|
|
'endpoints' => $this->endpointStatuses,
|
|
];
|
|
}
|
|
|
|
public function getDeliveryStatus(): array
|
|
{
|
|
return [
|
|
'totalDelivered' => $this->totalDelivered,
|
|
'totalFailed' => $this->totalFailed,
|
|
'totalDeadLettered' => $this->totalDeadLettered,
|
|
'retryCount' => $this->retryCount,
|
|
'endpoints' => $this->endpointStatuses,
|
|
];
|
|
}
|
|
}
|