AF5: CommandHandlerInterceptor on event sourced entity not called

I’m migrating a Spring Boot app written with Kotlin from AF 4.13 to AF 5.3.1. I’m trying to recreate the behaviour of throwing an exception when the event-sourced entity (formerly aggregate) is marked for deletion. For that, I have added a CommandHandlerInterceptor on the entity like this:

    @CommandHandlerInterceptor
    fun denyCommandsOnRedeemedGiftCard(commandMessage: CommandMessage) {
        if (isRedeemed) {
            throw IllegalStateException("Gift card already redeemed")
        }
    }

When testing this with a unit test, I never see the interceptor being called for any command. What could be going wrong here?

The test looks like this:

class GiftCardTest {
    private lateinit var fixture: AxonTestFixture

    @BeforeEach
    fun setUp() {
        fixture = AxonTestFixture.with(
            EventSourcingConfigurer
                .create()
                .registerEntity(
                    EventSourcedEntityModule.autodetected(String::class.java, GiftCard::class.java),
                )
        )
    }

    @AfterEach
    fun tearDown() {
        fixture.stop()
    }

    @Test
    fun denyCommandsOnRedeemedGiftCard() {
        val giftCardCode = UUID.randomUUID().toString()

        fixture
            .given()
            .command(IssueGiftCard(giftCardCode, 50))
            .events(GiftCardRedeemedEvent(giftCardCode))
            .`when`()
            .command(RedeemGiftCard(giftCardCode))
            .then()
            .exception(IllegalStateException::class.java)
    }
}

Hi @pstek!

Thanks for your message. Looks like you have found a gap between AF4 and AF5: a @CommandHandlerInterceptor declared inside the entity (the AF4 aggregate style) is currently not invoked on the command path.

To close this gap will take a little time and may land in a future release. We need to discuss that internally. Let me get back to you on Monday.

Do you have a lot of places like that or is it for a single aggregate?
Because the simplest workaround I see right now, if you need to access entity state (if not interceptors which are out of the entity should be invoked as expected) to make the decision is to put it in each @CommandHandler.

Another possibility is that you can move your CommandHandlers out of the entity class using Stateful Command Handlers
In a long term is the approach I recommend, because it allows using the DCB to have commands that form it’s own consistency boundary or modify multiple entities transactionally. Then you should be able to use @CommandHandlerInterceptor along with @InjectEntity.

class GiftCardCommandHandler {

    @CommandHandlerInterceptor
    fun check(
        command: CommandMessage,
        @Nullable @InjectEntity state: GiftCard
    ) {
        // Runs for every command handled by this component.
    }

    @CommandHandler
    fun handle(
        command: RedeemCard,
        @Nullable @InjectEntity state: GiftCard,
        appender: EventAppender
    ) {
        appender.append(CardRedeemed(command.id))
    }
}

Mateusz

Slight one-up here from what @MateuszNowak shared with you, @pstek.
This is a gap we, sadly, did not spot ourselves when reintegrating interceptor support into Axon Framework 5.

As such, I’ve put some effort into fixing this misser ASAP.
The PR for that is out just now and can be found here.
The referred to PR is for 5.4.0 (to be released end of September / start of October), but will receive a back-port to 5.3.2 once reviewed, approved, and merged.

I’d expect 5.3.2 to be released somewhere this week.

Sorry for the inconvenience here, @pstek! :folded_hands: