Using an Axon Saga in a Spring app

In a previous post I briefly mentioned the wonderful Axon library and promised to provide a closer look at its features. Here is a long overdue closer look on Sagas.

Sagas

Axon has the notion of a Saga,  a complex and long-lasting business transaction. Take, for example, the following situation: you have a user and this user failed to login into your system 5 times. Since this is a suspicious activity, your app must lock the user access for a specific time, say an hour. You can implement that in many ways if you are not using a CQRS approach:

Non-CQRS Solution:

You can have a User entity, kinda like that:

@Entity
@Data
public class User {
@Id private UUID userId;
private boolean locked;
private Long lockExpiresAt;
}
view raw gistfile1.java hosted with ❤ by GitHub

Then, when your user’s login failed N times, you’d set the locked boolean to true, and the lockExpiresAt to whatever time you want your lock to expire, say, in an hour. Then, whenever the user attempts to login, you can display to him that his account is locked and the lock would expire in N seconds. Then when the lock expires, you have two possibilities:

  • On a user attempt to login, check if the lock has expired by comparing the current date with the expire date of the lock, and if so, unlock the user.
  • Have a scheduler that checks from time to time for users that have locks that should expire, and unlock them. This, of course, is not ideal, and involves writing code.

The Axon solution

Axon provides a far more elegant way to solve such problems: Sagas. A saga would allow when the user’s account is locked, to immediately schedule a UserUnlockedCommand after N time, that, unless interrupted, will unlock the user’s account. Then, we can get rid of the lockedAt Long in our domain model and we can manage the unlocking of the User at the same place where we actually lock it. Here is a code for a Saga that does that:

public class UserLockSaga extends AbstractAnnotatedSaga {
private static final long serialVersionUID = 1L;
public static final Long UNLOCK_AFTER_MILLIS = new Long(DateTimeConstants.MILLIS_PER_HOUR);
@Autowired @Setter private transient CommandGateway commandGateway;
@Autowired @Setter private transient EventScheduler eventScheduler;
@Autowired @Setter private transient UserQueryRepository userQueryRepository;
private ScheduleToken userUnlockToken;
@StartSaga
@SagaEventHandler(associationProperty = "userId")
public void handle(UserLockedEvent event, @Timestamp DateTime eventTime) {
if (userUnlockToken != null) {
eventScheduler.cancelSchedule(userUnlockToken);
}
this.userUnlockToken = eventScheduler.schedule(getUnlockTime(eventTime), new UserUnlockSagaEvent(event.getUserId()));
}
@SagaEventHandler(associationProperty = "userId")
public void handle(final UserUnlockSagaEvent unlockSagaEvent) {
commandGateway.sendAndWait(new UserUnlockCommand(unlockSagaEvent.getUserId()));
end();
}
private AxonUserReference getAxonUserReference(UUID userId) {
UserDto user = userQueryRepository.findOne(userId);
return new AxonUserReference(userId, user.getUsername());
}
private DateTime getUnlockTime(DateTime eventTime) {
return new DateTime(eventTime.getMillis() + UNLOCK_AFTER_MILLIS);
}
}
view raw gistfile1.java hosted with ❤ by GitHub

Let’s break it down, We have a @SagaEventHandler(associationProperty = “userId”which signals the start of a Saga. Then we use the eventScheduler to schedule an event. That event, UserUnlockSagaEvent, in my case is an event that is only related to this Saga, going to be handled exclusively by it, which is then going to use the commandGateway to schedule an event to the aggregate (User) to unlock it, and then end the Saga.

That is all there is to it. If you guys have any questions how to configure the Saga, the event scheduler, and other Axon/Spring related stuff, I’d be happy to answer within a reasonable amount of time.

EDIT:

Cause of demand, I created a small app that shows an example of a very simple Spring config and very simple domain model with the usage of Sagas:

https://github.com/lc-nyovchev/axon-simple/

In future, if you have any questions, guys, don’t hesitate to ask, I will always try to respond when I have time.

9 thoughts on “Using an Axon Saga in a Spring app

  1. Hi! Could you give an example for a minimal Spring/Mongo setup to make your example work? Preferrably using a configuration not using the axon namespace but a “pure” bean/annotation configuration? Thank you very much, Daniel

    1. Yes, it is done automatically if the transaction manager for the currently active transaction is the one managing the transaction to the event/saga store. However, if there is also another transaction and there is another transaction manager active, you d have to manually have pre-joined the transactions to roll them both back.

    1. No, I am not affiliated with Allard or Trifork in any way if that is what you mean, apart from living in the same city. Also, you might have confused thoughtworks which are the people behind xstream with the guys that are behind axon.

  2. Where you have put your saga component, into query or command side? To retrieve an user reference, you used a repository inside saga, is that okay?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.