This commit is contained in:
2026-05-09 01:18:51 +02:00
parent 7116ee4619
commit 959970c150
132 changed files with 21310 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Temporal\WebhookDelivery;
use App\Models\WebhookDelivery;
use App\Temporal\Shared\FaultSimulator;
use Temporal\Activity;
class WebhookDeliveryActivity implements WebhookDeliveryActivityInterface
{
public function deliverToEndpoint(string $endpoint, array $payload, array $simulationConfig = []): array
{
$startTime = hrtime(true);
FaultSimulator::maybeApply($simulationConfig, "deliverToEndpoint:{$endpoint}");
// Simulate HTTP POST latency
usleep(mt_rand(100000, 400000));
$responseTime = (int) ((hrtime(true) - $startTime) / 1_000_000);
return [
'status' => 'delivered',
'statusCode' => 200,
'attempt' => Activity::getInfo()->attempt,
'responseTime' => $responseTime,
];
}
public function deadLetter(string $endpoint, array $payload, string $reason): bool
{
WebhookDelivery::create([
'workflow_id' => Activity::getInfo()->workflowExecution->getID(),
'endpoint' => $endpoint,
'payload' => $payload,
'status' => 'dead_lettered',
'attempts' => 0,
'last_error' => $reason,
'dead_lettered_at' => now(),
]);
return true;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Temporal\WebhookDelivery;
use Temporal\Activity\ActivityInterface;
use Temporal\Activity\ActivityMethod;
#[ActivityInterface]
interface WebhookDeliveryActivityInterface
{
#[ActivityMethod]
public function deliverToEndpoint(string $endpoint, array $payload, array $simulationConfig = []): array;
#[ActivityMethod]
public function deadLetter(string $endpoint, array $payload, string $reason): bool;
}

View File

@@ -0,0 +1,122 @@
<?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,
];
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Temporal\WebhookDelivery;
use Temporal\Workflow\WorkflowInterface;
use Temporal\Workflow\WorkflowMethod;
use Temporal\Workflow\QueryMethod;
#[WorkflowInterface]
interface WebhookDeliveryWorkflowInterface
{
#[WorkflowMethod]
public function deliver(array $payload, array $endpoints, array $simulationConfig = []);
#[QueryMethod]
public function getDeliveryStatus(): array;
}