@GeneratedValue for aggregate id

105 views
Skip to first unread message

Sam Kruglov

unread,
May 24, 2019, 8:02:35 AM5/24/19
to Axon Framework Users
As from documentation, one doesn't have to include aggregate id in the first creation command, but in reality it fails with an exception:
AxonServerCommandDispatchException: The command [PlaceOrderCommand] does not contain a routing key

I already considered this topic.

All my code is written in Kotlin.

Definition:
class PlaceOrderCommand
data class OrderPlacedEvent(@TargetAggregateIdentifier val id: Long, val order: OrderWriteModel)

@Entity
@Table(name = "orders")
class OrderWriteModel {

@Id
@GeneratedValue
var id: Long? = null
private set
}

interface OrderCommandRepo : org.springframework.data.repository.CrudRepository<OrderWriteModel, Long>

Dispatching:
commandGateway.send<Long>(PlaceOrderCommand())

Handling:
@Aggregate
class OrderAggregate {

@AggregateIdentifier
var id: Long? = null

@AggregateMember
var order: OrderWriteModel? = null

constructor()

@CommandHandler
constructor(placeOrderCommand: PlaceOrderCommand, repo: OrderCommandRepo) {
val savedOrder = repo.save(OrderWriteModel())
AggregateLifecycle.apply(OrderPlacedEvent(savedOrder.id!!, savedOrder))
}

@EventSourcingHandler
fun orderPlaced(orderPlacedEvent: OrderPlacedEvent) {
id = orderPlacedEvent.id
order = orderPlacedEvent.order
}
}

Saving an order to the write database before applying an event makes me a bit nervous, am I doing it wrong? 
I could ask the database for the next sequence value in controller and then I would only save the order inside event handler but I feel like it's better when Hibernate does it for me, isn't it?

Sam Kruglov

unread,
May 25, 2019, 2:50:41 AM5/25/19
to Axon Framework Users
With Kotlin's "no-args" and "jpa" plugins, I was able to create no args constructors for @Entity and @Aggregate implicitly. Also, for now, I feel it's best to call the database sequence manually.

Definition:
@Entity
@Table(name = "orders")
data class OrderWriteModel(@Id val id: Long)

interface OrderCommandRepo : CrudRepository<OrderWriteModel, Long> {

@Query(value = "select nextval('hibernate_sequence')", nativeQuery = true)
fun getNextId(): Long
}

Dispatching:

fun placeOrder(): Long {
val id = orderCommandRepo.getNextId()
return commandGateway.send<Long>(PlaceOrderCommand(id))
}

Handling:

@Aggregate
class OrderAggregate {

@AggregateIdentifier
var id: Long? = null

@AggregateMember
    lateinit var order: OrderWriteModel

@Suppress("ConvertSecondaryConstructorToPrimary")
@CommandHandler
constructor(placeOrderCommand: PlaceOrderCommand) {
AggregateLifecycle.apply(OrderPlacedEvent(placeOrderCommand.id))
}

@EventSourcingHandler
fun orderPlaced(orderPlacedEvent: OrderPlacedEvent, repo: OrderCommandRepo) {
id = orderPlacedEvent.id
order = repo.save(OrderWriteModel(orderPlacedEvent.id))
}
}

Please, let me know if you have a better idea

Robert Malczewski

unread,
May 27, 2019, 2:54:02 AM5/27/19
to Axon Framework Users
Hello, it seems like you are mixing two ideas here.

There are two types of aggregates, state stored and event sourced, it is not good practice to use the repository within an aggregate especially within event sourced aggregate inside it's EventSourcing handler.

First, you are going to 'save' the OrderWriteModel each time you handle (any) command on that aggregate, preety waste of resources.
Second, what if your event sourcing fails due to the save of the write model?

When you really really need to depend on some external repository, then imho you should model your aggregate around some service that could provide every data you need in your domain model, and then you could use that domain service inside aggregate.

(Keep in mind im not sure about my claims since im new to these things so i might be completely wrong)

Allard Buijze

unread,
May 27, 2019, 4:23:51 AM5/27/19
to Axon Framework Users
Hi Sam,

Robert is right on this one. Remember that the aggregate you are creating _is_ your wite model. So having another (state stored) write model inside an event sourced aggregate is most likely something that overcomplicates your design.

Also, updating this information from an EventSourcingHandler will have this repo.save() method invoked every time the Aggregate is loaded. 

Try modeling all behavior into your aggregate. If you store information for retrieval elsewhere, you probably need a read model for that.

Cheers,

Allard Buijze
CTO

T: +31 6 34 73 99 89


--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/axonframework/07082a01-8c2d-413b-aeed-a97e59d02647%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Message has been deleted

Sam Kruglov

unread,
May 27, 2019, 8:29:54 AM5/27/19
to Axon Framework Users
Thank you!

I see saving after replaying events is very inefficient and also Axon has GenericJpaRepository already under the hood.

Here is what I did:

data class PlaceOrderCommand(@TargetAggregateIdentifier val id: Long)
data class OrderPlacedEvent(@TargetAggregateIdentifier val id: Long)

@Entity
@Table(name = "orders")
@Aggregate
class OrderAggregate {

@Id
val id: Long

@Suppress("ConvertSecondaryConstructorToPrimary")
@CommandHandler
constructor(placeOrderCommand: PlaceOrderCommand) {
        id = placeOrderCommand.id
AggregateLifecycle.apply(OrderPlacedEvent(placeOrderCommand.id))
}
}

interface OrderCommandRepo : org.springframework.data.repository.Repository<OrderAggregate, Long> {
@Query(value = "select nextval('hibernate_sequence')", nativeQuery = true)
fun getNextId(): Long
}

fun placeOrder(): Mono<Long> {
val id = orderCommandRepo.getNextId()
return commandGateway.send<Long>(PlaceOrderCommand(id)).toMono()
}

It looks a lot better! Going back to my original questions: is there any way I can use @GeneratedValue on the aggregate identifier?

Steven van Beelen

unread,
Jun 13, 2019, 8:18:34 AM6/13/19
to Axon Framework Users
Hi Sam,

Regarding your initial question on the `does not contain a routing key` exception you encountered, you must have noticed this trace originated from the `AnnotationRoutingStrategy`.
This is configured for you by default, and by default, will expect an @RoutingKey annotated field in the command message.
Note that the @TargetAggregateIdentifier annotation is meta-annotated with @RoutingKey.

You can configure the AnnotationRoutingStrategy to take different approach to the none-existence of a routing key in your command message.
This can be done by creating a AnnotationRoutingStrategy with another UnresolvedRoutingKeyPolicy.
The default UnresolvedRoutingKeyPolicy is ERROR, exactly why you're seeing this message.
Consider using something like static or random in this scenario.

Additionally, I think it's justified to clarify this in the documentation.
The notion is still correct, granted you're not utilizing a distributed solution for the command bus (as in that scenario, there is no need for a Routing Strategy), but this should be made clear there.
Do you mind be more specific where the documentation states an Aggregate Identifier isn't required for the Aggregate-Creation-Command, so that we may adjust this bit?

Looking forward to your response, and I of course hope this helps you out Sam!

Cheers,
Steven

--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.

Sam Kruglov

unread,
Jun 13, 2019, 2:30:29 PM6/13/19
to Axon Framework Users
Thank you, Steven, for your response!

This is useful information! 
The part is here https://docs.axoniq.io/reference-guide/implementing-domain-logic/command-handling/aggregate "Commands that create an Aggregate instance do not need to identify the target aggregate identifier, as there is no Aggregate in existence yet. It is nonetheless recommended for consistency to annotate the Aggregate Identifier on them as well."

Sam Kruglov

unread,
Jun 14, 2019, 9:55:30 AM6/14/19
to Axon Framework Users
It is also mentioned in the "Note" on the same page:
When the @CommandHandler annotation is placed on an aggregate's constructor, the respective command will create a new instance of that aggregate and add it to the repository. Those commands do not require to target a specific aggregate instance. Therefore, those commands do not require any @TargetAggregateIdentifier or @TargetAggregateVersion annotations, nor will a custom CommandTargetResolver be invoked for these commands.

Steven van Beelen

unread,
Jun 27, 2019, 4:12:28 AM6/27/19
to Axon Framework Users
Hi Sam,

Thanks for specifying the section which was unclear on this.
I've just pushed a change to the reference guides for Axon 4.0 and 4.1 be a little more specific on the Aggregate Creation Command's necessity for a routing key, or the configuration of a different Routing Strategy.

Cheers,
Steven

On Fri, Jun 14, 2019 at 3:55 PM Sam Kruglov <samkr...@gmail.com> wrote:
It is also mentioned in the "Note" on the same page:
When the @CommandHandler annotation is placed on an aggregate's constructor, the respective command will create a new instance of that aggregate and add it to the repository. Those commands do not require to target a specific aggregate instance. Therefore, those commands do not require any @TargetAggregateIdentifier or @TargetAggregateVersion annotations, nor will a custom CommandTargetResolver be invoked for these commands.

--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages