How can I create CommandHandlerInterceptor inside of AggregateMember

So I’m trying to intercept every command handler inside of aggregate member to check whether the state of it is suspended or not. I have tried put it inside of aggregate root and it works just fine, but I want to put it inside of aggregate member so I don’t have to filter the target entity and it makes the codes look more nice. I tested with AggregateTestFixture, but the CommandHandlerInterceptor didn’t seems to work as I expected (It won’t trigger interceptor there’s no log showing or anything else). so how can I put interceptor method inside of AggregateMember or did I miss something? the code may look like this. I use Axonframework version 4.5.1

@Aggregate
class KubernetesAggregate {
  // ..state and etc
  @AggregateMember
  val pods = mutableListOf<PodAggregate>()
  
  // this works fine, but I want to put it inside of aggregate member so it looks more nice
  @CommandHandlerInterceptor
  fun handle(command: CommandMessage<*>, interceptorChain: InterceptorChain){
    val suspended = pods.first { ... }.suspended
    logger.info("[Aggregate member] test intercept command")
    if (suspended && command.payload !is UnsuspendPodCommand){
      throw IllegalArgumentException("This pod has been suspended")
    }
    interceptorChain.proceed()
  }
}

class PodAggregate(
  @EntityId
  val podId: String
){
  var suspended = true

  // ... some other commands that should not available if pod is suspended

  @CommandHandler
  fun handle(command: UnsuspendPodCommand){
    logger.info("[Aggregate member] test handle command")
    AggregateLifecycle.apply(...)
  }

  @EventSourcingHandler
  fun on(event: PodUnsuspended){
    suspended = false
  }

  // this won't trigger
  @CommandHandlerInterceptor
  fun handle(command: CommandMessage<*>, interceptorChain: InterceptorChain){
    logger.info("[Aggregate member] test intercept command")
    if (suspended && command.payload !is UnsuspendPodCommand){
      throw IllegalArgumentException("This pod has been suspended")
    }
    interceptorChain.proceed()
  }
}

That’s curious, @Michale_Scofield, as we have a test case that validates the @CommandHandlerInterceptor annotation within an @AggregateMemeber annotated field.
The main difference is that you’re in Kotlin, and the test is written in Java.

You can find it here, by the way.

Although I don’t see anything amiss in your code right now, I do have a suggestion you could try out.
My hunch is it’s to do with how Kotlin’s syntax abstracts away some of the types, although this is a hunch. One thing you can try out is removing the InterceptorChain entirely. The framework will automatically invoke proceed on exiting the method when the InterceptorChain parameter is not used. Given the implementation of your interceptors, this will not change the behavior.

I am recommending this because Axon will not invoke message handlers for which it cannot resolve all the types of parameters. Thus, removing parameters might net us with a working interceptor.

Granted, the odd thing is that the interceptor does work from within your aggregate.

1 Like

Thank you for replying my question, I did remove InterceptorChain parameter and it worked really well. so do I really need interceptorChain because I learn from this post messaging concept and there’s interceptorChain in the code.

so do I really need interceptorChain because I learn from this post messaging concept and there’s interceptorChain in the code.

You would only use the InterceptorChain if you require control over when Axon moves on into the following interceptor in the chain.
This approach could, for example, be applicable if you have logic to invoke before and after the InterceptorChain#proceed method.

Given the implementation you shared, that wasn’t the case, however.
Hence, I suggested trying out the interceptors without the InterceptorChain parameter.

Anyhow, I am glad to hear that it resolved your problem.
That means you can proceed.

I do have a somewhat unrelated question for you, though.
From your snippet, it seems you are building a tool to manage Kubernetes instances through an Axon application. Is that correct? Or is this just a pet project wherein you’re trying out the framework’s functionality, perhaps?

This is just a project for learning purpose. I’m currently learning kuberenetes (CKAD) and also axonframework so I though It would be nice to learn 2 things at the same time then I tried to implement this project. btw I have one more problem, I need the returned result from command, but interceptor returned null although the command was handle successfully. I use commandGateway and send the command via controller like this.

class PodAggregate {
  @CommandHandler
  fun handle(command: SomethingCommand){
    val randomSting = RandomString().nextString()
    // ... process
    // I need to return this random string, but CommandHandlerInterceptor return null before I got it
    return randomSting
  }
  
  @CommandHandleInterceptor
  fun handle(command: CommandMessage<*>){
    if (suspended && command.payload !is UnsuspendPodCommand){
      throw ..
    }
   
  }
}

@Controller
class PodController {
  
  @PostMapping
  fun something(request: somethingDTO) = Mono.just(request)
    .map { dto -> dto.toCommand() }
    // this passed interceptor and return null, but I want randomString from command
    .flatMap { cmd -> Mono.fromFuture(commandGateway.send<String>(cmd)) }
    .toFuture()
}

Ah, this actually signals you do need the InterceptorChain.
The result of InterceptorChain#proceed is the outcome of your @CommandHandler annotated method.

You didn’t have results from your command handlers (which is uncommon anyhow), hence I didn’t expect this scenario.
Note that the only valuable thing to ever return from a command handler is identifiers of constructed entities and potential exception results (if simply throwing an Exception is insufficient).

So, if this randomString you’re returning is either of thus, you’re good.
If it’s used to return a form of state set in the aggregate, then you should revert to a query model instead.