Project Peer Review

Greetings,
I’m working on an app with a simple purpose and I would like for someone to look over what I have so far and perhaps provide a little guidance. Please note that the pom file references a parent project which is not included, identifying information has been removed or changed, and that the code may not reflect the following narrative.

So, the idea is that you want to alert someone via phone (using twilio) that they need to check their email (because it needs immediate attention and they can’t be expected to check their inbox every 5 minutes). An external app may create or cancel voice notifications. When a voice notification is created, the important pieces of information include a phone number, the number of attempts, and how long to wait between each attempt. Each phone call has a few possible outcomes: No Answer, Busy, Failed (eg. invalid #), Incomplete (eg. the call was answered, but the callee might have hung up before completing the flow), or Complete (ie. the callee listened to the call and followed the prompts to the end). A Phone Call Scheduler will check every minute for notifications that need to go out and queue a phone call.

  1. Where might the actual call to twilio go? From the scheduler? From an event handler in the aggregate/listener? When using their API to create a call, it returns a callSid which I would think should be available to the PhoneCallQueued event. However, this would make it seem as if I should do it from a command handler. Furthermore, if the command handler is the place, it seems like that might make things harder to test.

  2. I need to use timestamps (eg. How do I know that it’s been 10 minutes since my last attempt?), but in coding up some simple tests, trying to use them (eg. when was the notification created?) seemed a little strange. As if I was supposed to put it into the Created command and then pass it along.

I’m sure I will have more questions, but I just want to start with these.

Thanks!

notification-service.zip (16.9 KB)

Hi Brian,

I’m newbie myself in CQRS/Axon but that are my thoughts about this task.
First of all why do you really need CQRS here? To keep audit logs information?
Personally I think CQRS will not help to solve such task. I mean not audit-log but accomplish/solve such technical task.
So, how I will usually solve such task without CQRS?

  1. Batching approach. I would keep required user calls in table with status and last time when call is happened.
    Use Spring Batch (or another clustered solution if horizontal scaling is needed), query required calls by statuses & current time – delay >= last call time and make a REST call…
    Not sure how it’s not but there was framework for hibernate to do audit logs or do it myself.

The same approach can be apply with CQRS too, you can query aggregates from event store with the same query (if your event store allow to do it) or have another query table for it which will be populated by events.
There is only one problem how to do horizontal scaling and you need to keep in mind that you will have to instantiate all user aggregates in memory which require a call.

  1. Messaging approach. Have some Message Broker with queue where you will publish a message about which user need a call. You will have subscriber(s) which will pick it up and try to make a call.
    If something wrong it will keep changed state at your aggregate (CQRS come on scene here) and push another “delay” message back to queue to repeat it.

Thanks,
Evgeny Kochnev

Also does twilio has some throughput limitation(s)? It will complicate the logic by querying but I think not too much.

Sorry, didn’t answer on your technical questions regarding Axon itself :slight_smile:
Looking forward Allard answers.

Hello Evgeny,
Right now, the app we develop is turning into a monolithic ball-of-mud and I thought that this seemed like a good opportunity to get some experience with Axon since it’s a fairly simple use case. For now, I need to keep track of the call completion, but requirements are subject to expansion in the future. Twilio does not rate limit the queueing, but it will pop them off its stack at a rate of 1/sec. I appreciate the feedback.

Actually, I am aware of one practice/pattern when you are working with messages/batching - always pick it up the latest messages because if something wrong with some of them you will not want to stack all another coming messages in production. For example you will still want to process newest payments even if some old are fail.

In another hand CQRS requires to keep messages order (in many cases) to support eventual consistency.
Also it’s good question about your business case - forget about unresponsive users and proceed with new one or keep trying :slight_smile:

Sorry for offtopic, will create another thread to discuss it.

Hi Brian,

unfortunately, I don’t have time at the moment to go through the code in detail, but here is some tips for guidance:

First of all, take a little step back and look at your system from a higher perspective (so forget Twilio and any external systems involved). Basically, you want your application to call someone. If you want something done, it’s a command. You want that command sent when something happens: ImportantEmailArrived.

So essentially, we’re talking about an Event Handler here that sends command. If it’s not as easy as Event in, Command out, then you might want to look at Axon’s Saga mechanism (e.g. when deadlines, timeouts are involved, or when a certain sequence of events needs to take place).

Now, when going back into the detail mode where we know phone calls are actually executed by an external service, we need to decide whether you want the Saga to invoke some service (preferably a service abstracting the Twilio API) directly, or that you want to use the Command abstraction for this. Using Commands makes it a bit easier to test your Saga. It also allows for easier distribution of the commands to other nodes, should you want to deploy the Twilio connector module separately from the rest of your application.

Regarding the timestamps, each Event in Axon carries the time at which it was published. You can use a parameter on your @EventHandler method to fetch it: “@Timestamp Instant timestamp”. You can then use deadlines (https://docs.axonframework.org/part2/sagas.html#keeping-track-of-deadlines) to set timeouts, etc. Try to avoid using the system clock. The Saga test fixtures allow you to work with time. Essentially, the Fixtures mess around with the clock used by the Events to simulate passing of time. Nothing awkward (from a user’s perspective) here.

Hope this helps.

Cheers,

Allard

PS. Do realise that AxonIQ provides consultancy and support services, should you need in-depth advice.