You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/1-essentials/01-routing.md
+33Lines changed: 33 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -678,6 +678,39 @@ final readonly class Auth implements RouteDecorator
678
678
}
679
679
```
680
680
681
+
## Idempotent routes
682
+
683
+
For operations like payment processing or order creation, retrying the same request should not produce duplicate side effects. Tempest provides the {b`Tempest\Idempotency\Attributes\Idempotent`} route decorator to handle this. Clients send an `Idempotency-Key` header; the first request executes normally and caches the response, while subsequent requests with the same key replay the cached response.
684
+
685
+
```php app/OrderController.php
686
+
use Tempest\Router\Post;
687
+
use Tempest\Http\Response;
688
+
use Tempest\Http\GenericResponse;
689
+
use Tempest\Http\Status;
690
+
use Tempest\Idempotency\Attributes\Idempotent;
691
+
692
+
final readonly class OrderController
693
+
{
694
+
#[Post('/orders')]
695
+
#[Idempotent]
696
+
public function create(CreateOrderRequest $request): Response
697
+
{
698
+
$order = $this->orderService->create($request);
699
+
700
+
return new GenericResponse(
701
+
status: Status::CREATED,
702
+
body: ['id' => $order->id],
703
+
);
704
+
}
705
+
}
706
+
```
707
+
708
+
Idempotency is only supported for `POST` and `PATCH` routes. The attribute can be applied at the class level to make all routes in a controller idempotent, and accepts optional TTL parameters. Route-specific settings like key requirement and header name can be configured with the `#[IdempotentRoute]` attribute.
709
+
710
+
:::info
711
+
Read the full [idempotency documentation](../2-features/19-idempotency.md) for details on scope resolvers, configuration, response behavior, and command bus idempotency.
712
+
:::
713
+
681
714
## Responses
682
715
683
716
All requests to a controller action expect a response to be returned to the client. This is done by returning a {b`Tempest\View\View`} or a {b`Tempest\Http\Response`} object.
Copy file name to clipboardExpand all lines: docs/2-features/10-command-bus.md
+64Lines changed: 64 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -115,6 +115,70 @@ In order to _run_ an asynchronous command, you'll have to run the `tempest comma
115
115
116
116
Note that async command handling is still an early feature, and will receive many improvements over time.
117
117
118
+
## Idempotent commands
119
+
120
+
Commands that should not be processed more than once—such as payment processing or invoice imports—can be marked with {b`Tempest\Idempotency\Attributes\Idempotent`}. The attribute can be placed on the command class or on the handler method. Duplicate dispatches with the same payload are silently skipped.
121
+
122
+
```php
123
+
// app/ImportInvoicesCommand.php
124
+
125
+
use Tempest\Idempotency\Attributes\Idempotent;
126
+
127
+
#[Idempotent]
128
+
final readonly class ImportInvoicesCommand
129
+
{
130
+
public function __construct(
131
+
public string $vendorId,
132
+
public string $month,
133
+
) {}
134
+
}
135
+
```
136
+
137
+
Alternatively, the attribute can be placed on the handler method instead:
138
+
139
+
```php
140
+
// app/ImportInvoicesHandler.php
141
+
142
+
use Tempest\CommandBus\CommandHandler;
143
+
use Tempest\Idempotency\Attributes\Idempotent;
144
+
145
+
final class ImportInvoicesHandler
146
+
{
147
+
#[Idempotent]
148
+
#[CommandHandler]
149
+
public function handle(ImportInvoicesCommand $command): void { /* … */ }
150
+
}
151
+
```
152
+
153
+
By default, the deduplication key is derived from the command's properties. Two commands with identical property values are considered duplicates. For explicit control over the key, implement the {b`Tempest\Idempotency\HasIdempotencyKey`} interface:
154
+
155
+
```php
156
+
// app/ProcessPaymentCommand.php
157
+
158
+
use Tempest\Idempotency\Attributes\Idempotent;
159
+
use Tempest\Idempotency\HasIdempotencyKey;
160
+
161
+
#[Idempotent]
162
+
final readonly class ProcessPaymentCommand implements HasIdempotencyKey
163
+
{
164
+
public function __construct(
165
+
public string $paymentId,
166
+
public int $amount,
167
+
) {}
168
+
169
+
public function getIdempotencyKey(): string
170
+
{
171
+
return $this->paymentId;
172
+
}
173
+
}
174
+
```
175
+
176
+
When using explicit keys, the payload fingerprint is still verified. Dispatching the same key with a different payload throws {b`Tempest\Idempotency\Exceptions\IdempotencyKeyWasAlreadyUsed`}.
177
+
178
+
:::info
179
+
Read the full [idempotency documentation](./19-idempotency.md) for details on configuration, TTL overrides, custom stores, and HTTP route idempotency.
180
+
:::
181
+
118
182
## Command bus middleware
119
183
120
184
Whenever commands are dispatched, they are passed to the command bus, which will pass the command along to each of its handlers. Similar to web requests and console commands, this command bus supports middleware. Command bus middleware can be used to, for example, do logging for specific commands, add metadata to commands, or anything else. Command bus middleware are classes that implement the `CommandBusMiddleware` interface, and look like this:
0 commit comments