BPM models as Saga orchestrator

This is probably more an application design/BPM or DDD modelling related question. I’m just wondering how other’s would solve this question. I apologize for the very long post :blush:

Assume you have the business process as defined in the BPM workflow in the image below.

This workflow represents a use case of communicating between an account service holding a customer’s balance, an inventory service keeping track of products in stock and an external sales order service containing all details of the customer’s purchase order.

Step 1
All order details are retrieved using an external API which “enriches” the workflow’s process variables with the different products of a purchase order.

Step 2 starting a BPM transaction consisting out of two subprocesses

  1. The user’s account balance is verified to be sufficient for the purchase to complete.
    If sufficient funds are available, the send task sends a command to Axon Server requesting it to withdraw money from the user’s account balance. The command might fail due to the inavailability of the account’s command service. In this case, it retries after a time-out of one minute and keeps on retrying until it eventually succeeds. If the command was successfully sent, the process waits for a
    MoneyWithdrawnEvent to be sent by the Axon Server indicating the account balance has been updated successfully. This event ends the first subprocess.

  2. After successfully updating the account balance, a multi-instance subprocess (one for each product in the purchase order) is started to check if sufficient stock is available. The same strategy as in the previous subprocess is used. If sufficient stock is available, a request to update the stock for the current product is sent to the Axon Server until it succeeds. The server acknowledges this by sending a ProductRemovedEvent which is translated into a SIGNAL toward the BPM engine (more on that later). This signal completed this subprocess exiting the transaction and updating the purchase order’s state correspondingly.

Please note: the escalation task undoing the account balance update whenever a certain product has insufficient quantity in stock.

What is the problem with this approach?
The boundary scope of the inventory (to which the command to update the inventory of a product and the corresponding event to acknowledge this request belong) is not aware of a sales order. In fact it does not have to be since updating the stock of a product can have multiple causes: a purchase of product from the supplier, a customer sales order or a correction towards the current stock. Whenever an event handler receives a ProductRemovedEvent, which it needs to translate to a BPM signal in this case (I guess ideally this should be a message event as well), it does not have any information about the sales order. As a result, there is no way to distinct running workflows between different sales orders from point of inventory updates that need to take place. The only info this event has, it the id of the product and the quantity. Therefore the current model uses a signal introducing to risk to update all similar products between different running processes.

How to solve this?

  1. As a first solution, one could add a saled id attribute to both the inventory command and events to enrich them with the additional information. This kind of violates the boundary context of the inventory service since it does not need this data to operate. Is this considered an acceptable “violation” of the context?
  2. One could enrich the command and the event with the salesId using metadata to store this information. The question then is: how to transfer the command’s metadata to the event’s metadata without touching any of the aggregate’s code.
  3. Other possible solutions I did not think of.

Please also feel free to comment on the BPM model attached in this post as well since I suspect it to violate some general design rules as well…

I deliberately choose not to implement this as a Saga since I wanted to investigate the possibilities of BPM engines to orchestrate different services.

Thanks for your feedback and/or recommendations.

1 Like

Just to add to the previous post.

You could consider leaving out the confirmation caused by the ProductRemovedEvent (referring to the signal in the BPMN flow above). Since I think it is safe to assume whenever a command was successfully sent and processed (no errors thrown by the command handler) the inventory will BECOME eventually consistent. You could decide not to wait for it to actually BE consistent before proceeding the workflow. Nothing at this stage of the flow can result in something which requires you to “undo” the product inventory update.

But the question remains… What if you need information to handle commands/events in BPMN processes which are part of a different boundary context?

First of all avoid adding business parameters to the workflow
Second, use message instead of signal
Third, send correlation keys along with message to identifybthe process instance
Fourth set the sales id as a business key