[Axon 3.2] Metadata value injection into the Event Handler

Hi,

I’m currently using Axon 3.2 and I’m facing an issue with the injection of the Metadata value in my subscribing event handlers.

I’ve implemented a message dispatch interceptor as following and added it to the Command Bus.

`

public class AuthenticationInterceptor implements MessageDispatchInterceptor<Message<?>> {

  @Override
  public BiFunction<Integer, Message<?>, Message<?>> handle(
      List<Message<?>> messages) {

    return (index, message) -> SecurityContextHolder.getContext().getAuthentication() == null
        ? message : message
        .andMetaData(Collections.singletonMap("username",
            SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString()));
  }
}

`

I tried to debug and I get the metadata value correctly injected in the Command Handler but it’s always null in the Event Handler method with the following signature :

`

@EventHandler
public void on(ProjectImportedEvent event, @MetaDataValue("username") String username,
    @Timestamp Instant timestamp)

`

Am I missing something ?

Thank you for your help,
B.R
Jerome

Quick update :

I checked the domain_event_entry table and it seems that the metadata attached in the Command Dispatch interceptor aren’t persisted and therefore not forwarded to the event handlers.

Investigating further.

Best Regards,
Jerome

Ok it seems I found the (obvious) missing part. As a command message and an event message are two separated messages, you have to explicitely tell Axon that you would like to copy the metadata attached to the former to the latter.

The right component for that seems to be the CorrelationDataProvider, and in my case the SimpleCorrelationDataProvider with the header “username”.

If you are using Spring Boot, the easiest way to configure is as following :
`
@Configuration
public class AxonConfig {

@Bean()
CorrelationDataProvider correlationDataProviders() {
return new SimpleCorrelationDataProvider(“username”);
}

}

`

The bean will be picked and registered by the Axon auto configuration.
This topic is not really addressed in the official documentation ( correct me if I’m wrong), it might be worth adding a chapter about it.

Best Regards,
Jerome

Hi Jérôme,

I’m glad that you managed to resolve the issue. Sorry for the late response, but let me give an explanation regarding message interceptors in AxonFramework. When you registered the MessageInterceptor on command bus, it is applied only to the command messages. During command processing you’re (probably) applying events which are completely different type of messages. If you want to attach a metadata to them, you’d have to register a MessageInterceptor on the event bus. Spring boot autoconfiguration will do that automatically for you.

Cheers,
Milan.

Hi Jerome,

Fair point of you that it’s a missing topic on the reference guide.

I’ll make the necessary steps to add it to the reference guide somewhere in the future.

Cheers,
Steven

Dear Milan and Steven,

Thank you for your feedback, it’s really appreciated.

@Milan : That makes sense, but I guess that when it comes to authentication and authorization, it’s better to use the correlation data provider to convey this information from the command side to the read side, instead of assuming that the SecurityContext will be available from an interceptor attached to the Event Bus.

Cheers,
Jerome

Hi Jérôme,

Regarding authorization and authentication, I completely agree with you!

Cheers,
Milan.

Hi Jérôme,

I’m trying to accomplish the same, but cannot get it to work.
I’ve created a MessageDispatchInterceptor that gets some Authentication info from the spring security context and adds it to the metadata. I see this data on the command message. So that part is working.
I’ve also added a CorrelationDataProvider bean to my config, and it’s being invoked (when I debug and set a breakpoint in the CorrelationDataProvider, the code stops there. But in my CorrelationDataProvider, the metaData of the message is always empty.

Do I need to specify all the metadata fields on my @EventHandler annoted methods? Like you I’d like to “transparently” copy over the metadata of the command message to the events being triggered from it.

Thanks,

Danny

Hi Danny,

In my case, I’m using a SimpleCorrelationDataProvider, whose behavior is to copy the metadata from the CommandMessage to the EventMessage for a preconfigured set of metadata keys.
Therefore, when you declare your CorrelationDataProvider bean, you have to configure it by explicitly specifying all the metadata keys you would like to access from the events messages.
In your case, you are attaching some metadata in a MessageDispatchInterceptor, let’s say credentials for example :

To make it work, you have to :

  • Declare a SimpleCorrelationDataProvider in your Spring configuration and pass “credentials” as a parameter as you would like to copy this metadata from the CommandMessage to the EventMessage.

  • Add the @MetadataValue(“credentials”) String credentials as a parameter in your @EventHandler annotated methods, this value will be automatically injected by Axon.

Did you already follow all these steps ?

Hope it helps,

Best Regards,

Hi, I had a similar issue where I need to inject user information for auditing purpose. This worked for me.

@Configuration
public class AxonAuditConfiguration {

    @Autowired
    public void registerInterceptors(CommandBus commandBus) {
        if (commandBus instanceof SimpleCommandBus) {
            commandBus.registerHandlerInterceptor(new AuthenticationInterceptor());
        }
    }
}

@NoArgsConstructor
public class AuthenticationInterceptor implements MessageHandlerInterceptor<Message<?>> {

    @Override
    public Object handle(UnitOfWork<? extends Message<?>> unitOfWork, InterceptorChain interceptorChain) throws Exception {

            if (SecurityContextHolder.getContext().getAuthentication() != null) {
                unitOfWork.transformMessage(message ->
                    message.andMetaData(Collections.singletonMap("username", 
SecurityContextHolder.getContext().getAuthentication().getPrincipal()().get().toString()))
                );
                unitOfWork.registerCorrelationDataProvider(new SimpleCorrelationDataProvider("username"));
            }
return interceptorChain.proceed();
    }
}