@@ -31,9 +31,12 @@ of the bounded context. They are exposed by your application layer as a driving
3131by the presentation and delivery layer.
3232
3333It 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:
4750namespace App\Http\Controllers\Api\AttendanceReport;
4851
4952use App\Modules\EventManagement\Application\{
50- Ports\Driving\CommandBus ,
53+ Ports\Driving\CommandQueuer ,
5154 UsesCases\Commands\RecalculateSalesAtEvent\RecalculateSalesAtEventCommand,
5255};
5356use 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
8583There 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
9593Or 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
119118namespace 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};
125124use App\Modules\EventManagement\Domain\Events\AttendeeTicketWasCancelled;
126125
127126final 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
144148When 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
182183Define 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
187188use 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
197198namespace 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;
200201use 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.
210211and 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
214215queue 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
219219public commands. I.e.:
220220
221221``` php
222222namespace App\Modules\EventManagement\Application\Ports\Driven\Queue;
223223
224224use CloudCreativity\Modules\Contracts\Application\Ports\Driven\Queue as Port;
225225
226- // queues public commands
226+ // injected into the command queuer for queuing public commands
227227interface Queue extends Port
228228{
229229}
230230
231- // queues internal commands
231+ // used by the application layer to queue internal commands
232232interface InternalQueue extends Port
233233{
234234}
235235```
236236
237237This 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