Best practices on hooking up web & mobile clients to Axon-powered back end?

Hi all,

I’m leading the backend team for a project leveraging CQRS via Spring Boot & Axon. I’m trying to work with the front end team on the preferred way for them to interact with the back end. There are a few options here, IMHO, and I feel like they should be separate for the write versus read sides.

Write side options:

  • Option W1: REST client → @RestController → Strongly Typed Write-Side Interface → CommandGateway
  • Option W2: Web MQTT client over WebSockets → MQTT broker → MQTT/Axon connector? → CommandGateway
  • Option W3: Mobile MQTT client → MQTT broker → MQTT/Axon connector? → CommandGateway
  • ???

Read side options:

  • Option R1: REST client → @RestController → Strongly Typed Read-Side Interface → QueryGateway
  • Option R2: GraphQL client → GraphQL server → magical configuration or some Axon/GraphQL module? → QueryGateway
  • Option R3: GraphQL client → GraphQL server → Read databases (bypassing Axon’s QueryGateway)

Above, when I say “Strongly Typed … Interface”, I mean the strongly-typed interface I authored for each side.

NB: ignore the Kotlin-specific stuff, like suspend in the following examples. We are awaiting changes to the Kotlin extensions being discussed here. For now, we have our own Kotlin extensions, but you should get the idea from these examples nonetheless.

For example, on the write side, it’s

// irrelevant package & imports omitted
import kotlinx.coroutines.future.await
import org.axonframework.commandhandling.gateway.CommandGateway
import org.springframework.stereotype.Component

@Component
class CqrsAppointmentWritingService(val cgw: CommandGateway) {
    suspend fun bookAppointment(cmd: BookAppointmentCommand) {
        return cgw.send<Unit>(cmd).await()
    }

    suspend fun rescheduleAppointment(cmd: RescheduleAppointmentCommand) {
        return cgw.send<Unit>(cmd).await()
    }

    suspend fun cancelAppointment(cmd: CancelAppointmentCommand) {
        return cgw.send<Unit>(cmd).await()
    }

    suspend fun deleteAppointment(cmd: DeleteAppointmentCommand) {
        return cgw.send<Unit>(cmd).await()
    }
}

This is basically strongly-typed lipstick over the CommandGateway, which is what we’re looking to expose to front end clients in various ways as enumerated above. A couple of open questions for me:

  • Are there any other ways you might expose this interface besides my W1 architecture above?
  • How do I deal with asynchrony when the front end issues a command and is awating some kind of response?

On the read side, same idea: strongly-typed lipstick over QueryGateway.

// irrelevant package & imports omitted
import kotlinx.coroutines.future.await
import org.axonframework.extensions.kotlin.query
import org.axonframework.extensions.kotlin.queryMany
import org.axonframework.queryhandling.QueryGateway
import org.springframework.stereotype.Component

@Component
class CqrsAppointmentReadingService(val qgw: QueryGateway) {
    suspend fun findAppointmentById(q: FindAppointmentByIdQuery): AppointmentDto {
        return qgw.query<AppointmentDto, FindAppointmentByIdQuery>(q).await()
    }

    suspend fun findAppointments(q: FindAppointmentsQuery): List<AppointmentDto> {
        return qgw.queryMany<AppointmentDto, FindAppointmentsQuery>(q).await()
    }

    suspend fun findAgenda(q: FindAgendaQuery): List<AppointmentDto> {
        return qgw.queryMany<AppointmentDto, FindAgendaQuery>(q).await()
    }

    suspend fun findAppointmentSlots(q: FindAppointmentSlotsQuery): List<AppointmentSlotDto> {
        return qgw.queryMany<AppointmentSlotDto, FindAppointmentSlotsQuery>(q).await()
    }
}

Note architecture R3 above, which bypasses QueryGateway completely by bolting on a GraphQL server directly to our read databases. Opinions on that are particularly welcome, as both our front end and backend teams see GraphQL as a superior alternative to REST on the read side.

Thanks in advance for your thoughtful input. :slight_smile:

–matthew

Hi Matthew,

Great question! We have a similar discussions in our company …

First of all there are more options, at least in theory… if you consider the usage of Axon Server, this uses gRPC for communication with the nodes… so in principle, you might use gPRC for direct command ingestion from the client. The protocol is available…

The second question is, what architecture are you using in your frontend. Let assume you are not using Redux/NgRX, then…

For the command side, you might build typed REST endpoints… to save the code for binding in the front end, address this by applying API-first approach (e.g. with OpenApi). For implementation of the backend - have a look at axon-kotlin and axon-reactor extensions… they might help…

The query side is either the same as command side, or GraphQL. I personally would never use graphQL in any particular specific application (rather would use it in generic frameworks and libraries). REST done right is better than any GraphQL. (Artur Ortega on GraphQL - CaSE: Conversations about Software Engineering - Podcast)

If you are using Apollo (client) you might build a generic translator from graphQL query to your Query model. You might also think of providing a generic query interface which is an easier target for GraphQL than strongly-typed Kotlin API.

So my personal preference is strongly typed Kotlin Implementation based on Open-API generated interfaces and generated frontend services.

If you go with GraphQL, the strong-typing in backend is hindering you instead of helping (will result in many when), so try the opposite and try to get to the strong types behind the query gateway… so instead of multiple query handlers, build a generic query parameter transporting the query parameters and build one query handler … or skip it and don’t use query gateway (and live with all problems of graphQL) which might be acceptable if you are not planing any reactive queries or complex query strategies…

Back to your front end architecture… if you are using Redux/NgRX - things become very interesting.
In essence it is CQRS in frontend and CQRS in backend… I don’t have a ready solution for this, but it should be something better than all above….

Sorry for my brain dump.

Let continue the discussion / sharing ideas…

Cheers,

Simon

1 Like

And for the synchronization of command / query, check this answer How to make command to wait until all events triggered against it are completed successfully - #3 by Simon_Zambrovski

1 Like

If you do want to use GraphQL, I have an example, GitHub - gklijs/bank-axon-graphql: Version of the open bank, using Axon instead of Kafka..

1 Like

Thanks for your suggestions, all. We have decided to go with simple REST endpoints on the write side (commands) that delegate to our strongly-typed service interface, which in turn delegates mostly to the CommandGateway, for our MVP.

As for the read side (queries), we’re going with simple REST endpoints that delegate to Spring Data repositories directly. We’ll investigate GraphQL later for the read side, then see about integration GraphQL with subscription queries, then see how we might handle GraphQL mutations so they become calls to the command gateway.

:slight_smile: