Skip to content

Commit 21103a0

Browse files
committed
declarative wakeup of long processes based on DI
1 parent e2459f3 commit 21103a0

4 files changed

Lines changed: 286 additions & 303 deletions

File tree

ddd-src/Application/Process/AwaitEvent.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@
1010
*
1111
* When a step returns Result with an AwaitEvent, the process suspends
1212
* and resumes when a matching event is published.
13+
*
14+
* IMPORTANT: Any event class used with AwaitEvent must be declared in the
15+
* process's DI service tag so the framework can wire up the action hooks.
16+
*
17+
* In your services.yaml:
18+
* ```yaml
19+
* App\Process\OrderFulfillmentProcess:
20+
* tags:
21+
* - name: 'ddd.long_process'
22+
* awaits:
23+
* - App\Events\PaymentReceived
24+
* - App\Events\UserApproved
25+
* ```
26+
*
27+
* If you forget to declare an awaited event, the process will suspend
28+
* but never resume (the action hook won't be registered).
1329
*/
1430
final class AwaitEvent {
1531
/** @var class-string<IIntegrationEvent> */

ddd-src/Application/Process/LongProcess.php

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace TangibleDDD\Application\Process;
44

55
use DateTimeImmutable;
6+
use TangibleDDD\Domain\Events\IIntegrationEvent;
67
use TangibleDDD\Domain\Shared\Aggregate;
78
use TangibleDDD\Domain\Shared\JsonLifecycleValue;
89

@@ -16,7 +17,21 @@
1617
* - commands: execute these commands (they send() themselves)
1718
* - await: suspend until an integration event fires
1819
*
19-
* Example:
20+
* ## DI Registration
21+
*
22+
* Register your process in services.yaml with the 'ddd.long_process' tag.
23+
* If your process uses AwaitEvent, declare the awaited event classes:
24+
*
25+
* ```yaml
26+
* App\Process\GenerateLearningPath:
27+
* tags:
28+
* - name: 'ddd.long_process'
29+
* awaits:
30+
* - App\Events\UserApprovedPath
31+
* ```
32+
*
33+
* ## Example
34+
*
2035
* ```php
2136
* class GenerateLearningPath extends LongProcess {
2237
* public function __construct(
@@ -34,6 +49,13 @@
3449
* await: new AwaitEvent(UserApprovedPath::class, ['user_id' => $this->user_id])
3550
* );
3651
* }
52+
*
53+
* // Next step receives the event via $this->resume_event()
54+
* protected function process_approval(): Result {
55+
* $event = $this->resume_event(UserApprovedPath::class);
56+
* // ... use event data
57+
* return new Result();
58+
* }
3759
* }
3860
* ```
3961
*/
@@ -63,6 +85,17 @@ abstract class LongProcess extends Aggregate {
6385
*/
6486
protected ?ProcessSteps $steps = null;
6587

88+
// ─────────────────────────────────────────────────────────────────────────
89+
// Transient state (NOT persisted - only valid during current execution)
90+
// ─────────────────────────────────────────────────────────────────────────
91+
92+
/**
93+
* Event that triggered resume from suspension.
94+
* Available to the step immediately after an await.
95+
* Cleared after the step executes.
96+
*/
97+
private ?IIntegrationEvent $resume_event = null;
98+
6699
// ─────────────────────────────────────────────────────────────────────────
67100
// Accessors (framework state)
68101
// ─────────────────────────────────────────────────────────────────────────
@@ -99,6 +132,50 @@ public function updated_at(): ?DateTimeImmutable {
99132
return $this->updated_at;
100133
}
101134

135+
// ─────────────────────────────────────────────────────────────────────────
136+
// Transient state accessors
137+
// ─────────────────────────────────────────────────────────────────────────
138+
139+
/**
140+
* Get the event that triggered resume from suspension.
141+
* Only available to the step immediately after an await.
142+
*
143+
* @template T of IIntegrationEvent
144+
* @param class-string<T>|null $expected_class Optional type check
145+
* @return T|IIntegrationEvent|null
146+
*/
147+
public function resume_event(?string $expected_class = null): ?IIntegrationEvent {
148+
if ($this->resume_event === null) {
149+
return null;
150+
}
151+
152+
if ($expected_class !== null && !($this->resume_event instanceof $expected_class)) {
153+
throw new \RuntimeException(sprintf(
154+
'Expected resume event of type %s, got %s',
155+
$expected_class,
156+
get_class($this->resume_event)
157+
));
158+
}
159+
160+
return $this->resume_event;
161+
}
162+
163+
/**
164+
* Set the resume event (called by ProcessRunner).
165+
* @internal
166+
*/
167+
public function set_resume_event(IIntegrationEvent $event): void {
168+
$this->resume_event = $event;
169+
}
170+
171+
/**
172+
* Clear the resume event after step execution.
173+
* @internal
174+
*/
175+
public function clear_resume_event(): void {
176+
$this->resume_event = null;
177+
}
178+
102179
// ─────────────────────────────────────────────────────────────────────────
103180
// Step state accessors (delegates to ProcessSteps, hides internal structure)
104181
// ─────────────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)