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, Executions 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 Executions 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 Executions 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 Executions within if the WrapperFactory has been used; and for these sub-Executions there will not be corresponding Commands.

(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 Interactions 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):

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 CommandSubscribers 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 ExecutionSubscribers 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 CommandSubscribers 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 the Command 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 the InteractionContext 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 the ExecutorService will be running the Callable in a separate thread.

      Of course, as a side-effect, this Interaction will be associated with a mostly empty Command.

    • then, it uses CommandExecutorService to actually execute the command, "taking over" the (still mostly empty) Command from this Interaction with the DTO obtained from AsyncCallable.

      This service is also responsible for calling the onReady and onStarted CommandSubscriber callback methods at the appropriate times.

    The InteractionService calls the final onCompleted callback of CommandSubscriber when the Interaction 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 CommandDtos of) all queued CommandLogEntrys, 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:

As described in the previous section, the CommandExecutorService will call the onReady and onStarted callbacks of any CommandSubscribers, 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 the Interaction of the background command. Therefore, call InteractionService.adoc#nextInteraction() to ensure that the final onCompleted 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 calling nextInteraction().

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:

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.