How to implement query handlers and read models?

Hi all,

I’m experimenting with query handler and wondering if I got the intended use right. As I understood it, the query handler takes the query as input parameter and delivers the query result as return value. My understanding currently is that queries operate on read models, which are independent parts from the query handler, right? So something like this:

Client -> query bus -> query handler -> read model

with the read model connected as an event processor to the event bus:

event bus -> event processor -> read model

Currently I implement this as two components. The read model as first component having event handlers and exposing methods for accessing the read model. The second component being the query handler using the read model (via dependency injection) to create the result data.

I’m asking the question because the query handlers then look like very thin components just delegating to read models. Or should this be in one component, where the read model besides event handlers also is having query handlers? And if so, how is this wired up at application start.

Kind regards,

Frank.

Hi Frank,

I’m including some sample code below that may help clarify.

When you have a read model / projection, it will have capabilities to answer the queries it was designed to answer, and to update itself based on events that occur. The methods representing the first capability would be annotated with @QueryHandler, making them easily available for other components through the QueryBus. The methods representing the second capability would be annotated with @EventHandler, so that Axon will know to invoke these methods when the events occur.

In simple cases, it makes perfect sense to combine both capabilities in a single class / Component. This is what I’ve done in the example below. But, since all state is in the backend database and not in the component itself, you could also split this out into two components, one with query handlers and one with event handlers. That might make sense I think if the model is more complex. But it’s largely a matter of style, technically it’s free to choose.

Regards,
Frans

@Component
public class CardSummaryProjection {

    private final static Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final EntityManager entityManager;

    public CardSummaryProjection(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @EventHandler
    public void on(IssuedEvt evt, @Timestamp Instant instant) {
        log.debug("projecting {}", evt);
        entityManager.persist(new CardSummary(evt.getId(), evt.getAmount(), instant, evt.getAmount()));
    }

    @EventHandler
    public void on(RedeemedEvt evt) {
        log.debug("projecting {}", evt);
        CardSummary summary = entityManager.find(CardSummary.class, evt.getId());
        summary.setRemainingValue(summary.getRemainingValue() - evt.getAmount());
    }

    @QueryHandler
    public FindCardSummariesResponse handle(FindCardSummariesQuery query) {
        log.debug("handling {}", query);
        Query jpaQuery = entityManager.createQuery("SELECT c FROM CardSummary c ORDER BY [c.id](http://c.id)",
                CardSummary.class);
        jpaQuery.setFirstResult(query.getOffset());
        jpaQuery.setMaxResults(query.getLimit());
        FindCardSummariesResponse response = new FindCardSummariesResponse(jpaQuery.getResultList());
        log.debug("returning {}", response);
        return response;
    }

    @QueryHandler
    public CountCardSummariesResponse handle(CountCardSummariesQuery query) {
        log.debug("handling {}", query);
        Query jpaQuery = entityManager.createQuery("SELECT COUNT(c) FROM CardSummary c",
                Long.class);
        CountCardSummariesResponse response = new CountCardSummariesResponse(
                ((Long)jpaQuery.getSingleResult()).intValue());
        log.debug("returning {}", response);
        return response;
    }
}