Aggregate#execute() - usage guideline in Saga?

Hello,

I have a question regarding the use of Aggregate.execute() method in the Axon-Bank sample application.

There is the following code in the BankAccountCommandHandler class (which handles commands fired by BankTransferManagementSaga):

@CommandHandler
public void handle(DebitSourceBankAccountCommand command) {
    try {
        Aggregate<BankAccount> bankAccountAggregate = repository.load(command.getBankAccountId());
        bankAccountAggregate.execute(bankAccount -> bankAccount
                .debit(command.getAmount(), command.getBankTransferId()));
    } catch (AggregateNotFoundException exception) {
**// ...**
    }
}

The Javadoc of org.axonframework.commandhandling.model.Aggregate#execute method contains the following text:

Note that the use of this method is not recommended as the wrapped aggregate instance is not meant to be exposed. Relying on this method is commonly a sign of design smell.

Question:

Are there better approaches in this situation?

Thanks!

Xiangming

I would also like to understand why execute is not recommend.

Hi,

actually, there is no problem using that method. I think this is a mistaken statement, copied from the invoke() method. The latter does have a warning associated with it, as the model should in general not be designed to expose its state.

I will fix the javadoc accordingly. Thanks for bringing it up.
Cheers

Allard

One thing I’ve noticed that doesn’t make sense to me at the moment.

repository.load(aggregateid).execute { it.updateState() }

fun updateState() {
this.property = true
}

Property value does not persist. Is this a mistake on my end or is this intentional behavior?

Is your aggregate event sourced, or is it state stored?

I’m not sure if I have a solid understanding of your question so for reference here is the sample source code:

Event definition:

@Revision("1.0")
class SyncHostRegisterRequested(name: String,
                                val environments: List<Environment>,
                                val active: Boolean,
                                auditEntry: AuditEntry):
        AuditableAbstractEvent(name, auditEntry)

name is the aggregate id but is not annotated at the moment. (Note name is set to aggreagteIdentifier via AuditableAbstractEvent)

Handling of the event (once more aggregateIdentifier == name)

internal open class SyncHostHandler(private val repository: Repository<SyncHost>, private val eventBus: EventBus) {
    @EventHandler
    @AllowReplay(true)
    fun handle(event: SyncHostRegisterRequested) = try {
        repository.load(event.aggregateIdentifier).execute({ it.validate(event.auditEntry) })
    } catch (exception: AggregateNotFoundException) {
        eventBus.publish(asEventMessage<Any>(SyncHostRegisterRejected(id = event.aggregateIdentifier, reason = "Host Name Not Found", auditEntry = event.auditEntry)))
    } catch (exception: ConcurrencyException) {
        eventBus.publish(asEventMessage<Any>(SyncHostRegisterRejected(id = event.aggregateIdentifier, reason = "Host Name Already Registered", auditEntry = event.auditEntry)))
    }
}

if it.validate() attempts to set a local property it doesn’t persist.

If I make an inference out of your question can I assume the solution is to add @AggregateIdentifier to name in the event (or command)?

While I should make the above a separate topic. I’m having another issue where calling repository.load(aggregateId).execute{ it.function() } . If function() calls apply() to fire a new event. It does not get picked up by @EventSourcingHandler on the aggregate. I don’t understand why this is happening.

Hi Michael,

from the code, I’m unable to see why the aggregate wouldn’t be invoked. Can you share your aggregate code?
Do you use Spring Boot Devtools by any chance? We’ve had reports of issues related to that.

Also, I do notice you’re directly calling a repository from an @EventHandler. That is something you may want to reconsider. It is considered best practice to be explicit about the command you’re trying to execute, and have that command be transported to an Aggregate that defines a command handler for that method.

Cheers,

Allard

I’ve found the issue and I believe I understand why this behavior occurs but it’s a little unintuitive. I can use psuedo code to explain the situation.

repository.load(aggregateId) {
it.doSomething()
}