Skip to content

Commit b367644

Browse files
committed
Merge branch 'release/3.0.0-rc.1'
2 parents b217aa0 + d790c5e commit b367644

File tree

240 files changed

+678
-541
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

240 files changed

+678
-541
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. This projec
55

66
## Unreleased
77

8+
## [3.0.0-rc.1] - 2025-01-12
9+
10+
### Added
11+
12+
- Commands can now be queued by the presentation and delivery layer via the `CommandQueuer` port. Refer to the command
13+
documentation for details.
14+
15+
### Changed
16+
17+
- **BREAKING** Moved the `Message`, `Command`, `Query`, and `IntegrationEvent` interfaces to the `Toolkit\Messages`
18+
namespace. This is to make it clearer that these are part of the toolkit, not the application or infrastructure
19+
layers. It matches the `Result` and `Error` interfaces that are already in the `Toolkit\Result` namespace. I.e.
20+
now the toolkit contains both the input and output interfaces.
21+
22+
### Removed
23+
24+
- **BREAKING** The `CommandDispatcher` driving port no longer has a `queue()` method. Use the new `CommandQueuer` port
25+
instead.
26+
827
## [2.0.0] - 2024-12-07
928

1029
### Changed

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
"ramsey/uuid": "^4.7"
2828
},
2929
"require-dev": {
30-
"laravel/pint": "^1.15",
31-
"phpstan/phpstan": "^2.0",
32-
"phpunit/phpunit": "^10.4"
30+
"laravel/pint": "^1.19",
31+
"phpstan/phpstan": "^2.1",
32+
"phpunit/phpunit": "^10.5"
3333
},
3434
"autoload": {
3535
"psr-4": {

docs/guide/application/asynchronous-processing.md

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ of the bounded context. They are exposed by your application layer as a driving
3131
by the presentation and delivery layer.
3232

3333
It is reasonable for there to be scenarios where the presentation and delivery layer intends to change the state of the
34-
bounded context via a command, but does not need to wait for the result of that change. Our command bus implementation
35-
allows this to be signalled by exposing a `queue()` method on the command bus. This allows the outside world to
36-
signal a desire to alter the state of the domain asynchronously.
34+
bounded context via a command, but does not need to wait for the result of that change. We provide a _command queuer_
35+
implementation that allows commands to be dispatched in a non-blocking way, allowing the outside world to alter the
36+
state of the domain asynchronously.
37+
38+
See the [Commands chapter](./commands.md#command-queuer) for details on how to define a command queuer port and
39+
implementation.
3740

3841
### Example
3942

@@ -47,7 +50,7 @@ For example, an endpoint that triggers a recalculation of our sales report:
4750
namespace App\Http\Controllers\Api\AttendanceReport;
4851

4952
use App\Modules\EventManagement\Application\{
50-
Ports\Driving\CommandBus,
53+
Ports\Driving\CommandQueuer,
5154
UsesCases\Commands\RecalculateSalesAtEvent\RecalculateSalesAtEventCommand,
5255
};
5356
use CloudCreativity\Modules\Toolkit\Identifiers\IntegerId;
@@ -57,7 +60,7 @@ class ReportRecalculationController extends Controller
5760
{
5861
public function __invoke(
5962
Request $request,
60-
CommandBus $bus,
63+
CommandQueuer $bus,
6164
string $attendeeId,
6265
) {
6366
$validated = $request->validate([
@@ -75,11 +78,6 @@ class ReportRecalculationController extends Controller
7578
}
7679
```
7780

78-
For this to work, you must have a driven port that can queue commands, along with a queue adapter in the infrastructure
79-
layer that implements this port. This is covered by
80-
the [queues chapter in the infrastructure section.](../infrastructure/queues) The queue adapter is then injected into
81-
the command bus. The queue chapter contains an example.
82-
8381
## Internal Queuing
8482

8583
There are many scenarios where it can be advantageous for your bounded context to queue internal work for asynchronous
@@ -94,17 +92,18 @@ processing. Some examples of where your application layer might need to push int
9492

9593
Or anything else that fits with the specific use case of your bounded context!
9694

97-
Our approach is to define this work as _internal_ command messages. These are queued and dispatched by a specific
98-
_internal_ command bus - separating them from the command bus that implements the driving port in the application
99-
layer.
95+
Our approach is to define this work as _internal_ command messages. These are queued by a specific _internal_ queue, and
96+
dispatched by a specific _internal_ command bus. This segregates them from the command bus that implements the command
97+
bus driving port.
10098

101-
This means internal commands are not exposed as use cases of our module - making them an internal implementation detail
102-
of the application layer.
99+
This segregation is important, because it means that internal commands cannot be dispatched by the outside world. And it
100+
means internal commands are not exposed as use cases of our module - making them an internal implementation detail of
101+
the application layer.
103102

104103
:::tip
105-
If you have a command that can be dispatched both by the outside world and internally, you should define this as a
106-
public command. The internal dispatching would use the public command bus rather than the internal command bus to queue
107-
the command.
104+
If you have a command that can be queued by both the outside world and internally, you should define this as a use case
105+
of your bounded context, i.e. a public command. When queuing internally within the application layer, the command can be
106+
pushed directly onto the queue via the queue driven port. I.e. you do not need to go via the public command queuer.
108107
:::
109108

110109
### Example
@@ -118,27 +117,32 @@ We could push this internal work to a queue via a domain event listener:
118117
```php
119118
namespace App\Modules\EventManagement\Application\Internal\DomainEvents\Listeners;
120119

121-
use App\Modules\EventManagement\Application\Ports\Driving\CommandBus\InternalCommandBus;
122-
use App\Modules\EventManagement\Application\Internal\Commands\{
123-
RecalculateSalesAtEvent\RecalculateSalesAtEventCommand,
120+
use App\Modules\EventManagement\Application\Ports\Driven\Queue\{
121+
InternalQueue,
122+
Commands\RecalculateSalesAtEventCommand,
124123
};
125124
use App\Modules\EventManagement\Domain\Events\AttendeeTicketWasCancelled;
126125

127126
final readonly class QueueTicketSalesReportRecalculation
128127
{
129-
public function __construct(private InternalCommandBus $bus)
128+
public function __construct(private InternalQueue $queue)
130129
{
131130
}
132131

133-
public function handle(AttendeeTicketWasCancelled $bus): void
132+
public function handle(AttendeeTicketWasCancelled $event): void
134133
{
135-
$this->bus->queue(new RecalculateSalesAtEventCommand(
134+
$this->queue->queue(new RecalculateSalesAtEventCommand(
136135
$event->eventId,
137136
));
138137
}
139138
}
140139
```
141140

141+
:::tip
142+
Notice that as this is an internal command, the command class is defined in the queue driven port namespace. This is to
143+
ensure that the command is not exposed to the outside world.
144+
:::
145+
142146
### Workflow Orchestration
143147

144148
When you have a complex process that needs to be executed asynchronously, you can define a workflow that orchestrates
@@ -169,20 +173,17 @@ commands that could cancel or retry the workflow.
169173

170174
### Internal Command Bus
171175

172-
If you are implementing internal commands, you will need an internal command bus that is separate from your public
173-
command bus.
174-
175-
Technically, our internal command bus is a driving port of the application layer. This is because when the internal
176-
command is queued by an infrastructure adapter, it has left the application layer. When that adapter pulls it from the
177-
queue for processing, it needs to re-enter the application layer via a driving port.
176+
If you are implementing internal commands, you will need an internal command bus that is separate from your _driving_
177+
port command bus.
178178

179-
However, by defining this as a separate port to our public command bus, we can ensure that internal commands are only
180-
dispatched by the internal command bus.
179+
We deal with this by defining the internal command bus as a _driven_ port. This is technically correct, as commands
180+
cannot be queued unless we have infrastructure to support queuing messages. Therefore, the internal command bus works
181+
nicely as a driven port.
181182

182183
Define the internal command bus as follows:
183184

184185
```php
185-
namespace App\Modules\EventManagement\Application\Ports\Driving;
186+
namespace App\Modules\EventManagement\Application\Ports\Driven\Queue;
186187

187188
use CloudCreativity\Modules\Application\Ports\Driving\CommandBus\CommandDispatcher;
188189

@@ -191,15 +192,15 @@ interface InternalCommandBus extends CommandDispatcher
191192
}
192193
```
193194

194-
And then our port implementation is as follows:
195+
And then our port adapter is as follows:
195196

196197
```php
197198
namespace App\Modules\EventManagement\Application\Bus;
198199

199-
use App\Modules\EventManagement\Application\Ports\Driving\InternalCommandBus;
200+
use App\Modules\EventManagement\Application\Ports\Driven\Queue\InternalCommandBus;
200201
use CloudCreativity\Modules\Application\Bus\CommandDispatcher;
201202

202-
final class InternalCommandBusService extends CommandDispatcher implements
203+
final class InternalCommandBusAdapter extends CommandDispatcher implements
203204
InternalCommandBus
204205
{
205206
}
@@ -210,29 +211,32 @@ See the [commands chapter](./commands) for details on how to create the adapter.
210211
and middleware into the command bus.
211212
:::
212213

213-
To allow this bus to queue commands, it requires a driven port that can queue commands. This means there must also be a
214+
You will also need a queue driven port that allows you to queue these internal commands. This means there must also be a
214215
queue adapter in the infrastructure layer that implements this port. Queue adapters are covered by
215-
the [queues chapter in the infrastructure section.](../infrastructure/queues) The queue adapter is then injected into
216-
the command bus.
216+
the [queues chapter in the infrastructure section.](../infrastructure/queues)
217217

218-
Our approach is to define a port specifically for queuing internal commands - rather than reusing a queue port for
218+
One approach is to define a port specifically for queuing internal commands - rather than reusing the queue port for
219219
public commands. I.e.:
220220

221221
```php
222222
namespace App\Modules\EventManagement\Application\Ports\Driven\Queue;
223223

224224
use CloudCreativity\Modules\Contracts\Application\Ports\Driven\Queue as Port;
225225

226-
// queues public commands
226+
// injected into the command queuer for queuing public commands
227227
interface Queue extends Port
228228
{
229229
}
230230

231-
// queues internal commands
231+
// used by the application layer to queue internal commands
232232
interface InternalQueue extends Port
233233
{
234234
}
235235
```
236236

237237
This separation is useful because it allows each queue adapter to know exactly which command bus - the public or
238-
internal bus - to dispatch the command to when it is pulled from the queue.
238+
internal bus - to dispatch the command to when it is pulled from the queue.
239+
240+
If you prefer, it is acceptable to define a single queue driven port. This simplifies the implementation by having a
241+
single queue that deals with both. However, you might find it gets complicated knowing whether to dispatch queued
242+
commands to either the public or internal command bus.

0 commit comments

Comments
 (0)