I’m pretty new to CQRS, events sourcing, DDD and Axon. My question is in the intersection of these areas and I am aware that questions I have are caused by the lack of knowledge and experince. Documentation and other resouces I was able to find have some examples but the case I have is a bit more complicated and I don’t know what is the good and canonical way to deal with it.
This is an example from the telecommunication area where we have a device and it may have multiple profiles one of which is active and the profile may be deactivated and another one may be activated.
This process is triggered from the ExternalParty. The activation of the profile involves some complex logic (encapsulated in profile bounded context) that may require other profile deactivation and/or deletion. Notifications about profile(s) lifecycle events may be sent to external systems called NotificationTarget for the purpose of this question. The rules specifying when, how and where these notifications should be sent are rather complicated and are implemented in notifications bounded context.
In the described scenario one more context is envolved - ExternalRequest. It is responsible for maintaining the state of the request from the ExternalParty, handling retries, timeouts etc. For the purpose of this question it is important as it knows when the response with the result about the successful request execution was successfully delivered to the ExternalParty.
One important business requirement is that notifications to NotificationTarget should be sent only if the ExternalParty that triggered the operation successfully received the result of such operation.
It means that events published by the Profile context like ProfileEnabledEvent, ProfileDisabledEvent etc cannot be sent immediately and should be delayed until OperationResultSentToClientEvent is published by the ExternalRequest context. And my problem and the question is how to model this and implement using Axon.
Here is the diagram displaying the command and events flow with the implementation of the notifications context that I initially drafted:
The idea is that the state of the notifications handling for the specific request is stored in RequestNotifications aggregate. It also has all the business rules like if and where to send the notification.
Here I see several issues and I’m not sure that I’m using the correct approach and would like to know what is more canonical way to model and implement this. So the problems:
- aggregate existence check
- race conditions in events processing
- aggregate removal timing
In order to execute step #5 I’m accessing RequestNotifications aggregate using JpaRepository. I’ve found that using the query model does not work as it is updated asynchronously and may show that aggregate does not exist while it has been created already.
[Q1] What’s the canonical way to do such check?
[Q2] Or may there is a way to avoid such conditional commands execution (I have a feeling that I’m doing it wrong)?
There’s a race condition in it, namely ProfileEnabledEvent and OperationResultSentToClientEvent may happen in any order (and that’s what I experience in practice). If OperationResultSentToClientEvent is processed first it does not see any notifications in RequestNotifications or even no RequestNotifications aggregate at all.
One way to overcome this is to make processing of the OperationResultSentToClientEvent in NotificationService also conditional like ProfileEnabledEvent processing. That is it should first check if the RequestNotifications aggregate with the given requestId exists. This way the first event will create the aggregate and aggregate will have a chance to record the fact that event happened.
[Q3] Is it a good way to handle this problem?
[Q4] Is there a better/alternative way to overcome this issue?
Given that processing of the events from profile context like ProfileEnabledEvent may be delayed it is not clear when RequestNotifications aggregate can be safely deleted. I guess this can be done by scheduling a deadline when RequestNotifications aggregate is created.
[Q5] Is this an appropriate solution or there is some good alternative?