Init
This commit is contained in:
44
app/Temporal/WebhookDelivery/WebhookDeliveryActivity.php
Normal file
44
app/Temporal/WebhookDelivery/WebhookDeliveryActivity.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
122
app/Temporal/WebhookDelivery/WebhookDeliveryWorkflow.php
Normal file
122
app/Temporal/WebhookDelivery/WebhookDeliveryWorkflow.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user