Understanding UnitOfWork

Hi folks,

I stumbled upon an interesting issue. In an open source library I’m using Axon Framework for dispatching commands to the aggregates. Now this framework is being used for a project and they also use Axon Framework for command dispatch - not only to the aggregates but just to connect parts of the system. Everything I describe works on the same machine (no distribution, just decoupling).

Here is the call stack:

  1. RestController gets invoked
  2. Rest controller creates a command A and sends it via command bus
  3. An annotated CommandHandler receives this command A and does some business logic
  4. As a result, some commands B are collected and sent out on Tx.beforeCommit synchronization using command bus
  5. Commands get received by another annotated command handler for B
  6. Commands B get dispatched to an aggregate
  7. Aggregate tries to emit events (using AggregateLifecycle.apply).

During the last step an Exception is thrown by the EventBus saying: It is not allowed to publish events when the root Unit of Work has already been committed..

I think I understand why it doesn’t work, but I wonder how I can work around this.
In fact - the first delivery of the Command A has nothing to do with dispatch of command B. The application uses command bus just for decoupling of components. A positive side effect is the error propagation, but any other propagation would also do in this case…

Is it possible to create a sub-unit of work to avoid this problem?
What is so specific about the root UoW, and why it is checked in the AbstractCommandBus:


UnitOfWork<?> unitOfWork = CurrentUnitOfWork.get();
Assert.state(!unitOfWork.phase().isAfter(PREPARE_COMMIT),
  () -> "It is not allowed to publish events when the current Unit of Work has already been committed. Please start a new Unit of Work before publishing events.");
Assert.state(!unitOfWork.root().phase().isAfter(PREPARE_COMMIT),
  () -> "It is not allowed to publish events when the root Unit of Work has already been committed.");

I played around trying to bypass the second assert, but whatever I’m doing, any message (command or event) dispatch opens a root UoW, in that I’m calling some business logic and only on Tx-commit (before or after), the commands are generated… So I’ll ever have the root UoW in COMMIT phase which hits the second assertion.

Is there any way to decouple UoW from the Tx? or can I just finish the UoW manually, before sendin new commands?

Any ideas?

Cheers,

Simon

Here is a bit more inside of the actual situation, when sending the event:
Debugging at AbstractEventBus.publish() after UnitOfWork<?> unitOfWork = CurrentUnitOfWork.get(); was called(Axon 4.6.2):

Hey Simon and Jan (and welcome to the forum, Jan!),

To validate, I assume the project uses the default CommandBus from Axon Framework, the SimpleCommandBus.
Is that correct? Or have you perhaps configured another type of CommandBus?

As you may have assumed, the predicament follows from the command handler using the CommandBus to issue a command within the same thread.
Axon Framework uses a ThreadLocal to maintain the UnitOfWork and to nest them.
The nesting is, for example, used to have a distinct UnitOfWork per handled event but also to group all handled events within a batch into one larger UnitOfWork.
It thus allows for transactionality over the entire batch, which you’re running into right now.

I can’t say I’ve tested this, but I assume using the AsychronousCommandBus instead of the SimpleCommandBus should do the trick. As it uses an Executor to invoke the SimpleCommandBus#handle method, you, by default, get different threads per command handling task.

Let me know if that does the trick!

Hm. Interesting… I have to check what other side effects the AsynchronousCommandBus has :slight_smile: But I’ll definitely try this out.

In a mean time I used Camunda Job API to decouple the two instances UnitOfWork. So the first one is writing a Camunda Job and the second on is executed by Camunda Job executed and is used to deliver commands…

Somehow a workaround…

Cheers and thanks,

Simon

1 Like

Good to hear you already found something from the Camunda end that helps, Simon!
Once you have the time to try out the AsynchronousCommandBus, just let us know your experience.
No rush, whatsoever.