Support for Finite State Machines

A common use case that occurs is when an aggregate changes from one status to another. The concept of a finite state machine is often used to aid in this: as actions are taken on the aggregate, it transforms into different states. And at each state, the aggregate has a certain set of attributes, and can only perform certain actions. Take the example of an order. A common flow is as follows

  1. An order is created
  2. Items are added to the order
  3. The order is placed
  4. The order is prepared
  5. The order is shipped
  6. The order is delivered

A finite state machine could be used in this example to model the different steps in the process, for instance, an OrderStarted state for step 1-2, an OrderPrepping for step 3-4, and an OrderAwaitingDelivery for steps 5-6. The usefulness of this organization is in how each state is allowed to perform. For instance, items cannot be added to an order that is in the OrderAwaitingDelivery state.

Axon does not support this use case at the present time. From what I have seen, this is typically handled by having a status flag on the aggregate that indicates what state it is in. This results in a very large aggregate, handling a large number of commands, and performing a large number of checks to see if it is in the right state to handle these commands.

It would be greatly beneficial to have this capability in Axon. In my research, I came across this example. However, the example works by creating new aggregates as it advances to the next state. This is not a finite state machine in its truest form. For it to be truly finite state, the aggregate must itself transform into different states. It must involve a single aggregate, rather than creating separate ones for each step in the process.

I would be interested to hear the teams take on this. I understand there are some barriers to getting this to work, but I think it would allow for much cleaner organization of aggregates.

That’s an interesting idea! I can see how explicitly supporting states and transitions can be helpful.

But as always, the devil is in the details. Especially with event-sourced aggregates, it’ll easily become an unmanageable mess. I guess that is why @Ivan_Dugalic went for different aggregates. That way, you can map events to states. With a single event-sourced aggregate, you’d have to re-transition to the current states on every load. I’m curious if the same can be achieved with some sort of “smart” snapshots :thinking:. In theory, it should be possible to make a snapshot on every transition so the next time the aggregate is loaded, it does not have to deal with old events.

It would be interesting to know how you’d like to see such functionality from a usage perspective. Isolate states in different (sub)classes? Mark (annotate) some command/event handlers as the ones triggering a transition? Provide some transition map on registration? I’m trying to leave aside what is possible and imagine what the perfect user/developer experience should this be implemented.

Thanks for your response!

I can share a few ideas that I have. I am “borrowing” some ideas from a framework that our company developed in-house. It goes without saying that I have no idea if this would be feasible within Axon.

A key factor in the framework that we developed is how it handles event sourcing events. Essentially, it is setup so that event handlers return the state of the aggregate, rather than just being void functions. Changes that do not produce a new state transition look fairly similar to Axon - variables are set within the function to match the incoming event. The only difference is that, at the end of the function, this is returned. But, when an event is handled that causes the aggregate to move to a new state, the new subclass is instantiated and returned from within the event handler.

In terms of what a “state” looks like, I envision the different states as being extensions of the same base state class. So in the example previous, OrderStarted, OrderPrepping, etc would all be extensions of an OrderState class. Maybe this OrderState class even has some default behavior, and each subclass can either inherit or override this.

I hope that’s useful!

What you describe is similar to what @Ivan_Dugalic has in the example you point out. IIUC, the main difference is that you dynamically replace the aggregate implementation rather than create a new aggregate.

I am curious if you could implement this approach with Multi-Entity Aggregates. Say you have something like

public class Order {
    @AggregateIdentifier private String id;
    @AggregateMember private OrderState orderState;
   
    ...
    
    public class OrderPlaced implements OrderState {
      ...
    }

    public class OrderPrepared implements OrderState {
      ...
    }

    ...
    

Theoretically, you could have an OrderState implementation per state with relevant handlers. A transition would be updating the orderState field in the aggregate root with another implementation. I haven’t tried that, and there may be some issues I currently don’t see. But it should be relatively easy to do such an experiment.

Another thing to try is Aggregate Polymorphism. The documentation is scarce, but it does look a lot like what you describe.

Please share your findings if you have the time to experiment with those approaches. I understand that both are workarounds, but maybe one of them can serve as a base for implementing actual finite-state machine support in the future.

Your first example is actually the workaround I am currently experimenting with. It seems to be working as intended. There are a few issues I see with this approach though:

Firstly, this approach doesn’t accurately represent the model. If I have an OrderState within my aggregate, which is itself an Order… then what is my OrderState representing? If we want to preserve terminology, the aggregate object itself should be a “state”, and shouldn’t own a state.

Secondly, it doesn’t allow for command handler overriding. Say a command handler exists in Order. If, for instance, OrderPlaced needs to implement different behavior for this handler, that logic would have to exist within Order, and be guarded with a type check:

if (this instanceof OrderPlaced) {
   // behave this way
}
else if (this instanceof OrderPrepping) {
   // behave that way
}

One of the major benefits of finite state behavior is the idea that an object is only concerned with how it behaves, and not about how other states might behave. So having to extract that logic out to a different class, in my mind, breaks this principal.

That issue would also apply for aggregate polymorphism. I see this in the documentation:

Having creational command handlers of the same command name on different aggregates in the same hierarchy is forbidden too, since Axon cannot derive which one to invoke.

With this in mind, an aggregate would not be able to change behavior around a single command depending on what state it is in (unless, again, all of that logic exists within the base class and performs type checks). In my mind, this defeats the kind of state abstraction that finite states is intended to bring.

So to summarize:

  • option 1 works, but breaks some modeling boundaries
  • option 2 does not work, because aggregate types cannot change after being created
  • both options do not give proper ownership of behavior to individual states

Thanks for sharing your findings, Oliver!

I suggested those as workarounds which means it was expected there would be drawbacks and that compromises need to be made. I do acknowledge Axon Framework doesn’t have proper support for finite-state machines.

The interesting question at this point is, can we build proper support up on top of one of those two? I’m afraid we need @Steven_van_Beelen or @allardbz to express their view on that. It certainly does not sound like mission impossible to me. But there are tons of things I don’t know about the internals of the framework, so I can’t reliably judge.

The other aspect is how useful this would be to the community in general. Do you know other people or projects that can benefit from such functionality? It would be of great help to collect some real-world use cases and challenges we could solve by introducing this functionality.

1 Like

Thanks for looping me in here, Milen! Didn’t spot this thread before.

The point of introducing a form of Finite State Machine for Aggregates has come to me several times, actually. One of our internal “advocates” of the subject would be @Ivan_Dugalic.
He shared his opinions on the matter with me some time ago.

In all honesty, I think there’s definitely a time and place for this.
However, making it the go-to approach might be a bit much.
I’d hope we can conceive some concrete step operation within the AggregateLifecycle.

That way, it’s clear to the developer / reader of the code that on “handling event Foo, we alter the overall status of the aggregate A to B.” The most straightforward way how you would achieve this right now is with an actual status field that’s validated on every command.

Doable, but not overly user-friendly.
So, some AggregateLifecycle#transition operation that actually adjusts the instance of the Aggregate to another variant of the Aggregate’s lifecycle would be awesome.
I think that implementing this is, however, not necessarily a straightforward task.

Having said all the above, I’d like to raise a point on prioritization within our team.
Although I think spending time on this is valuable, I need to take a judgment call on giving our move to Axon Framework 5 precedence over this.
Within our move from AF 4 to 5, we’ll touch a lot to comply with more current-day standards of Java. Thus, adjusting the internals, instead of directly introducing new features, like you’ve shared with us, @Opayne93.

So, in short, I think it will take some time before we can start building out a finite state machine, or state transitions support, for Aggregates.

With all that said, I’d hate to lose sight of this idea. So, would you be up for constructing an issue with us, describing what you’re looking for, @Opayne93? I think it’s fair that the issue has your credentials in there, given the effort you’ve put into explaining it. If you’re rather have us construct an issue, that’s fine too, of course.

2 Likes

I would be happy to put something together! I will get on that now.

1 Like