1
votes

I'm working on a project where there are, for the sake of this question, two microservices:

  1. An new OrderService (Spring Boot)
  2. A "legacy" Invoice Service (Jersey Web Application)

Additionally, there is a RabbitMQ message broker.

In the OrderService, we've used the Axon framework for event-sourcing and CQRS.

We would now like to use sagas to manage the transactions between the OrderService and InvoiceService.

From what I've read, in order to make this change, we would need to do the following:

  1. Switch from a SimpleCommandBus -> DistributedCommandBus
  2. Change configuration to enable distributed commands
  3. Connect the Microservices either using SpringCloud or JCloud
  4. Add AxonFramework to the legacy InvoiceService project and handle the saga events received.

It is the fourth point where we have trouble: the invoice service is maintained by a separate team which is unwilling to make changes.

In this case, is it feasible to use the message broker instead of the command gateway. For instance, instead of doing something like this:

public class OrderManagementSaga {

    private boolean paid = false;
    private boolean delivered = false;
    @Inject
    private transient CommandGateway commandGateway;

    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCreatedEvent event) {
        // client generated identifiers
        ShippingId shipmentId = createShipmentId();
        InvoiceId invoiceId = createInvoiceId();
        // associate the Saga with these values, before sending the commands
        SagaLifecycle.associateWith("shipmentId", shipmentId);
        SagaLifecycle.associateWith("invoiceId", invoiceId);
        // send the commands
        commandGateway.send(new PrepareShippingCommand(...));
        commandGateway.send(new CreateInvoiceCommand(...));
    }

    @SagaEventHandler(associationProperty = "shipmentId")
    public void handle(ShippingArrivedEvent event) {
        delivered = true;
        if (paid) { SagaLifecycle.end(); }
    }

    @SagaEventHandler(associationProperty = "invoiceId")
    public void handle(InvoicePaidEvent event) {
        paid = true;
        if (delivered) { SagaLifecycle.end(); }
    }

    // ...
}

We would do something like this:

public class OrderManagementSaga {

    private boolean paid = false;
    private boolean delivered = false;
    @Inject
    private transient RabbitTemplate rabbitTemplate;

    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCreatedEvent event) {
        // client generated identifiers
        ShippingId shipmentId = createShipmentId();
        InvoiceId invoiceId = createInvoiceId();
        // associate the Saga with these values, before sending the commands
        SagaLifecycle.associateWith("shipmentId", shipmentId);
        SagaLifecycle.associateWith("invoiceId", invoiceId);
        // send the commands
        rabbitTemplate.convertAndSend(new PrepareShippingCommand(...));
        rabbitTemplate.convertAndSend(new CreateInvoiceCommand(...));
    }

    @SagaEventHandler(associationProperty = "shipmentId")
    public void handle(ShippingArrivedEvent event) {
        delivered = true;
        if (paid) { SagaLifecycle.end(); }
    }

    @SagaEventHandler(associationProperty = "invoiceId")
    public void handle(InvoicePaidEvent event) {
        paid = true;
        if (delivered) { SagaLifecycle.end(); }
    }

    // ...
}

In this case, When we receive a message from the InvoiceService in the exchange, we would publish the corresponding event on the event gateway or using SpringAMQPPublisher.

Questions:

  • Is this a valid approach?
  • Is there a documented way of handling this kind of scenario in Axon? If so, can you please provide a link to the documentation or any sample code?
1

1 Answers

1
votes

First off, and not completely tailored towards your question, you're referring to the Axon Extensions to enable distributed messaging. Although this is indeed an option, know that this will require you to configure several separate solutions dedicated for distributed commands, events and event storage. Using a unified solution for this like Axon Server will ensure that a user does not have to dive in three (or more) different approaches to make it all work. Instead, Axon Server is attached to the Axon Framework application, and it does all the distribution and event storage for you.

That thus means that things like the DistributedCommandBus and SpringAMQPPublisher are unnecessary to fulfill your goal, if you would use Axon Server.


That's a piece of FYI which can simplify your life; by no means a necessity of course. So let's move to your actual question:

Is this a valid approach?

I think it is perfectly fine for a Saga to act as a anti corruption layer in this form. A Saga states that it reacts on events and send operations. Whether those operations are in the form of commands or another third party service is entirely up to you.

Note though that I feel AMQP is more a solution for distributed events (in a broadcast approach) than that it's a means to send commands (to a direct handler). It can be morphed to suit your needs, but I'd regard it as suboptimal for command dispatching as it needs to be adjusted.

Lastly, make sure that your Saga can cope with exception from sending those operations over RabbitMQ. You wouldn't want the Invoice service to fail on that message and having your Order service's Saga think it's on a happy path with your transaction of course.

Concluding though, it's indeed feasible to use another message broker within a Saga.

Is there a documented way of handling this kind of scenario in Axon?

There's no documented Axon way to deal with such a scenario at the moment, as there is no "one size fits all" solution. From a pure Axon approach, using commands would be the way to go. But as you stated, that's not an option within your domain due to an (unwilling?) other team. Hence you would track back to the actual intent of a Saga, without taking Axon into account, which I would summarize in the following:

  • A Saga manages a complex business transaction.
  • Reacts on things happening (events) and sends out operations (commands).
  • The Saga has a notion of time.
  • The Saga maintains state over time to know how to react.

That's my two cents, hope this helps you out!