Interactions, Commands and Executions
Interactions
An Interaction is created for each and every request made to the application; you could think of it as analogous to HttpRequest
.
Thus, an Interaction
is created for every action invocation or property edit, but an Interaction
is also created to render an object, or to render an action prompt, or even to evaluate the set of choices from a supporting choicesNAct()
method.
It is the responsibility of the viewer implementation to ensure that an Interaction
is created early on in the request processing, and torn down later.
An Interaction
can also be created programmatically, using the InteractionService.
One use case is for "headless" execution of background commands, eg as scheduled by a Quartz cron job.
Another important use case is within testing.
The CausewayInteractionHandler JUnit 5 extension takes responsibility for wrapping each test with an Interaction
(taking account any @InteractAs annotation if present).
The CausewayIntegrationTestAbstract is provided as a convenient superclass for integration tests, and is already annotated with the CausewayInteractionHandler
extension.
(Alternatively, CausewayIntegrationGwtAbstract is not annotated with the handler; instead it provides given(…)
, when(…)
and then(…)
methods that each execute the provided Runnable
/Callable
within the context of its own Interaction
.
Integration tests can also simulate multiple interactions (HTTP requests) within a single test using InteractionService#nextInteraction.
When called, any state changes made in the previous Interaction
will be committed, and any references to entities should be either discarded or else be re-retrieved in the next Interaction
.
(In the case of the JDO ORM, any entities from the previous Interaction
will be reset to null when that Interaction
completes).
Executions
Contained within an Interaction
is an Execution.
In many cases there will be only a single such Execution
, and these come in two varieties, representing either an action invocation or a property edit.
However, Execution
s can be nested.
This will occur if the "top-level" action invokes another action (or property edit) using the WrapperFactory service.
Conversely, in many cases an Interaction
will occur where no Execution
s occur within it.
For example, if the interaction is to render an object, or to render an action prompt, or to display a list of choices, then no action would have been invoked nor property edited, and thus its Interaction#getCurrentExecution() will be simply empty.
Commands
Every Interaction
has a corresponding Command, accessible using Interaction#getCommand.
Both the Interaction
and Command
are identified by a UUID (Interaction#getInteractionId() and Command#getInteractionId() respectively); these will have the same value and correlate the Interaction
and Command
with each other.
Whereas an Interaction
(or more precisely, the Execution
s within an Interaction
) represent an action invocation or property edit having occurred, a Command
represents the intention to invoke an action/edit a property.
Think of this in terms of cause and effect: the Command
is the cause, the Execution
is the effect.
However, there is only ever one Command
, and this represents the "top-level" action; there will be a corresponding Execution
but (as explained above) there could in theory be nested Execution
s within if the WrapperFactory has been used; and for these sub-Execution
s there will not be corresponding Command
s.
(From here on, we’ll just use the phrase "invoke an action" as this is the more common case; "edit a property" can be thought of as a very particular type of action, modifying a single property with a single value).
Interactions that do not represent executions
As noted above, in many (in fact the majority of) cases the overarching interaction will not be to invoke an action, but instead will be just torender an object, or to render an action prompt, or to display a list of choices.
In such cases there will be no Execution
.
While every Interaction
does hold a corresponding Command
, this corresponding Command
will remain mostly empty.
In particular, its Command#getCommandDto() will remain null.
Only when the interaction is to actually invoke an action, then the Command
's CommandDto and other properties will be fleshed out.
Publishing
Command Publishing
If the overarching interaction is to actually invoke an action, then the corresponding Command
will be populated and will be published to any registered CommandSubscribers.
Because a Command
represents the intention to invoke an action, the CommandSubscriber#onReady() callback is called first.
The CommandSubscriberForCommandLog subscriber uses this to persist a CommandLogEntry which represents the fact that the action represented by the Command
is to be executed.
Just before the action is executed, the CommandSubscriber#onStarted() callback is called. The CommandSubscriberForCommandLog subscriber uses this to update the persisted CommandLogEntry (its startedAt field etc.
However, recall that there is only ever one Command
, representing the invocation of the top-level command.
The final CommandSubscriber#onCompleted() callback is called only when the overarching Interaction
is being closed.
Execution Publishing
Whenever an Interaction
s Execution
is complete, it will be published to any ExecutionSubscribers.
This happens more or less immediately after the Execution
is complete, in other words it isn’t deferred until the closing of the top-level Interaction
.
Normally there will only be a single Execution
per Interaction
, so this publishing will occur immediately prior to the Command
being completed is published (see previous section).
Summary
To summarise the previous sections: the "usual" way in which commands/executions are published are as the result of a user invoking an action through the viewer (e.g. clicking on the OK button in an action prompt):
-
An Interaction is created as part of the HTTP request processing. This contains a corresponding Command, mostly empty at this point.
-
The Wicket UI code delegates down to an
ObjectAction
; it is this that sets up the state within Command, in particular its CommandDto -
The CommandSubscriber#onReady() callback is called.
-
An Execution is created and held by its owning Interaction
-
The CommandSubscriber#onStarted() callback is called
-
The action (domain logic) itself is called
-
The Execution is updated as completed
-
ExecutionSubscriber's onExecution() callback is called
-
The overarching Interaction is closed; as part of that processing:
-
the associated top-level Command is marked as complete and
-
the CommandSubscriber#onCompleted() callback is called.
-
The above assumes of course that the action has command and publishing enabled. If either are disabled then the corresponding subscribers won’t be called.
Other publishing scenarios to consider
As well as the "usual" way of executing actions (described above), there are a number of other ways in which commands and executions may be published.
Wrapper Factory
When a domain object is wrapped (or more accurately: proxied) using the WrapperFactory, the wrapping proxy delegates to the DomainObjectInvocationHandler
framework class.
This looks up the ObjectAction
from the java.lang.reflect.Method
being invoked on the proxy and calls it.
If this is done in production code, then there will already be an Interaction
with a corresponding Command
, and this will be set up with the action whose body is calling the wrapped object.
Therefore this scenario has no impact on the Command
and CommandSubscriber
s will not be called.
On the other hand, the wrapped action is an Execution
, and so this will result in an execution graph of two levels: the top-level action invoked by the user, and then the nested action invoked via the proxy.
Any ExecutionSubscriber
s will be notified as soon as the nested action has completed.
(Of course, the nesting could be arbitrarily deep).
If the wrapped domain object’s action is called in test code, then things will probably be different.
The integration test itself will most likely have set up a top-level Interaction
(through the CausewayInteractionHandler JUnit 5 extension) with a mostly-empty Command
.
When the wrapped action is invoked, this will in effect be the top-level action for the interaction, and so the CommandDto
will be set up with the details of that wrapped action, and any CommandSubscriber
s will be notified.
Asynchronous Commands
The WrapperFactory
allows wrapped actions to be invoked synchronously or asynchronously.
The former is more common, and is the process described above.
If the wrapper is created for asynchronous invocation (using WrapperFactory#asyncWrap() or similar), then the framework passes an AsyncCallable (a subtype of Callable
) to the configured ExecutorService
.
(The default ExecutorService
is the simple ForkJoinPool
, though this can be replaced if required).
The AsyncCallable
interface is implemented by an internal framework class (AsyncTask
).
It’s worth understanding the data that it holds and its behaviour:
-
in terms of its data, it holds a representation of the action to be invoked as a
CommandDto
.Since this is a "child" command, the
CommandDto
will have a new UUID identifier. This isn’t the UUID of theCommand
of the action that actually called the wrapped action; that is instead saved as the UUID of the "parent" command -
It also holds an InteractionContext that determines the who, when and timezone of the "virtual" user that is executing the action.
This is derived from the AsyncControl passed into
WrapperFactory
combined with theInteractionContext
of the original parent action. -
in terms of its behaviour, it simply delegates back to the WrapperFactory#execute(), in a double-dispatch pattern.
This in turn:
-
uses the InteractionService to create a new
Interaction
, because of course theExecutorService
will be running theCallable
in a separate thread.Of course, as a side-effect, this
Interaction
will be associated with a mostly emptyCommand
. -
then, it uses CommandExecutorService to actually execute the command, "taking over" the (still mostly empty)
Command
from thisInteraction
with the DTO obtained fromAsyncCallable
.This service is also responsible for calling the
onReady
andonStarted
CommandSubscriber callback methods at the appropriate times.
The
InteractionService
calls the finalonCompleted
callback ofCommandSubscriber
when theInteraction
is torn down. -
Typical production usage of wrapped asynchronous actions will result in those actions being invoked in a new Interaction
on a separate thread.
If required, the code that calls the async action can obtain a Future
from the AsyncControl passed into WrapperFactory#asyncWrap or similar.
Or, it may simply "fire-n-forget".
As described above, because the wrapped action is invoked in its own thread/Interaction
, then it will be published to any CommandSubscribers and ExecutionSubscribers.
Typical test production is very similar, though test code is more likely to want to obtain the Future
in order to assert that the wrapped async action was executed correctly.
Background Commands
One consideration when invoking actions asynchronously (as described above) is that there are no hard transactional guarantees.
In other words, if the async action hits a problem and aborts, then the original calling action will not also abort.
Now, that code can of course obtain the Future
from the AsyncControl
, and manually abort if the (eventual) returned value of the Future
is not as expected.
However, there’s little point in using an async action if the calling action is just going to wait on that child async action’s Future
to resolve.
And, if the point of using async actions was to fan out and start multiple async actions in parallel, then there’s no way to rollback all of these actions if any one of them has failed.
An alternative approach is to use the WrapperFactory
async API to persist the commands in some form, and then have a Quartz cron job or similar pick up those queued commands and execute them.
If any of those commands fail, there is at least a record as to how they might have failed.
This is the philosophy behind the BackgroundService, part of the Command Log extension.
Under the covers the BackgroundService
calls WrapperFactory
, but with a custom ExecutorService
(the BackgroundService.PersistCommandExecutorService class) which simply persists the implied command as a CommandLogEntry.
Its executeIn field indicates that the command is to be executed "in the background".
The Command Log extension also provides the RunBackgroundCommandsJob, which is a Quartz Job
implementation.
Conceptually this is similar to the AsyncCallable
described previously, but will run (the CommandDto
s of) all queued CommandLogEntry
s, rather than just a single CommandDto
.
To use the RunBackgroundCommandsJob
, we configure Quartz to run it periodically, eg every 10 seconds.
When run by Quartz, it performs these steps:
-
uses the InteractionService to start a new
Interaction
(because — obviously — the Quartz job runs in a separate thread to the original action that queued up the command) -
runs a query to find any new
CommandLogEntry
s since last time, using CommandLogEntryRepository#findBackgroundAndNotYetStarted(), and extracts out the CommandDto from each -
for each such
CommandDto
:-
starts a new transaction
-
uses the CommandExecutorService to execute the
CommandDto
.
-
As described in the previous section, the CommandExecutorService
will call the onReady
and onStarted
callbacks of any CommandSubscriber
s, while tearing down the overarching Interaction
will call the final onCompleted
callback.
One wrinkle though that the CommandSubscriberForCommandLog subscriber has to cater for is that — when its onReady
callback is called — the CommandLogEntry
will already exist for the command; because (of course) this is what is being used by RunBackgroundCommandsJob
.
Therefore, rather than persist a new CommandLogEntry
, instead callback is a no-op.
The above describes how this all works in production code, with a Quartz scheduler set up to run periodically.
When in test code, however, we won’t want to be running Quartz, so we need to call RunBackgroundCommandsJob
within the test.
There are two options:
-
the best approximation of Quartz (within a test) would be to simply create a new thread and just execute the
RunBackgroundCommandsJob
within it.The test can then wait for this manually spawned background thread to complete.
-
alternatively, the
RunBackgroundCommandsJob
can simply be executed within the thread of the test itself.However, this runs the risk of confusing the
Interaction
of the test with theInteraction
of the background command. Therefore, call InteractionService.adoc#nextInteraction() to ensure that the finalonCompleted
callback for the async command is correctly called at the end. This will also require re-retrieving any entities etc in the test because they will have become detached as the result of callingnextInteraction()
.
Transactions
It’s worth quickly mentioning that sitting between the overarching Interaction
and the Command
, the framework will always be using TransactionService to create a new transaction.
This is done by:
-
the action invocation facet (the
ActionInvocationFacetForDomainEventAbstract
internal framework class)
Ultimately all of the scenarios will use the action invocation facet to invoke the action.
That code will only create a new transaction if one is not already in progress, so there’s no harm in AsyncCallable
or RunBackgroundCommandsJob
in explicitly managing the transactions.
This is particulary important for RunBackgroundCommandsJob
because it intentionally runs each of the queued commands in a separate transaction.