Non-Blocking Query Handlers with SimpleQueryBus & Spring Boot

We are using Axon Framework 4.11 with Spring Boot and no Axon Server (yet), relying on the SimpleQueryBus implementation and sending queries via the QueryGateway. Here we sent two queries and expected them to be processed in parallel, when in fact they were processed sequentually.

A look at its implementation shows it invokes handlers directly in the calling thread (no executor). So even though the QueryGateway calls return CompletableFutures, the query handlers themselves are executed sequentially (blocking) by default if the underlying query handlers block. That could us off-guard at first.

Example

// Two queries in parallel
var f1 = queryGateway.query(new FindExampleQuery(id1), ResponseTypes.optionalInstanceOf(ExampleDto.class));
var f2 = queryGateway.query(new FindOtherQuery(id2), ResponseTypes.optionalInstanceOf(OtherDto.class));
CompletableFuture.allOf(f1, f2).join();

// Blocking query handler
@QueryHandler
public Optional<ExampleDto> on(FindExampleQuery query) {
    return exampleQueryService.fetchExample(query.getId());
}

// Actual query service
@Service
public class ExampleQueryService {
    private final ExampleRepository exampleRepository;

    public TransactionalQueryService(ExampleRepository exampleRepository) {
        this.exampleRepository = exampleRepository;
    }

    @Transactional(readOnly = true)
    public Optional<ExampleDto> fetchExample(String id) {
        return exampleRepository.findById(id)
                                .map(ExampleEntity::toDto);
    }
}

The examples I have seen in the documentation all implement blocking query handlers.

Question

What is the recommended/idiomatic approach when using Spring Boot, SimpleQueryBus, and no reactive programming to enable non-blocking query handlers?

Would it be to use @Async and returned a completed CompletableFuture in the query handlers? Or is there any alternative QueryBus implementation which calls query handlers off-thread? Are there any examples and recommendations which we may have missed?

@Async("queryExecutor")
@QueryHandler
public CompletableFuture<Optional<ExampleDto>> on(FindExampleQuery query) {
    var result = exampleQueryService.fetchExample(query.getId());
    return CompletableFuture.completedFuture(result);
}
1 Like

Hi Frederik,

As you’ve observed and as is mentioned in the documentation:

The SimpleQueryBus does straightforward processing of queries in the thread that dispatches them.

Thus @Async won’t have the desired impact.

You can of course return a CompletableFuture to allow for async processing in the query handler.