Can we create an aggregateIdentifer inside aggregate and setting the identifier as aggregateType+UUID.randomUUID.tostring()?

@Entity
@Table(name = “rewards”)
@Aggregate(snapshotTriggerDefinition = “snapshotTriggerDefinition”)
@Data
@Slf4j
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RewardAggregate{

@Id
@AggregateIdentifier
private String rewardId;
private String name;
private String description;

} This is my aggregate currently, but while setting the primary key i want to set it as Type of the aggregate concated with a randomUUID.
Ex: RewardAggregate||550e8400-e29b-41d4-a716-446655440000 this as identifier. Also i want to create it using an abstract class, where i should be using it for all of my classes

Hi Prasanna,

It’s fine to have the aggregate identifier be a composite as you mention; typically though, the UUID itself is sufficient.

Hi @Marc_Klefter,

I’m able to create a composite aggregate identifier when instantiating a new aggregate. However, when attempting to update the existing instance, it fails to retrieve it properly, resulting in an AggregateNotFoundException.
Could you help me resolve this issue?

Are you providing the full String representation of the composite identifer - e.g. “RewardAggregate||550e8400-e29b-41d4-a716-446655440000” - in the @TargetAggregateIdentifier in your command?

Yeah I provided it as TargetAggregateidentifer @Marc_Klefter

Can you provide the code for the command, aggregate and the sending of the command?

This is my baseAggregate
@Getter
public abstract class BaseAggregate {

@AggregateIdentifier
public String getAggregateIdentifier() {
    return generateAggregateIdentifier();
}

protected String generateAggregateIdentifier() {
    return this.getClass().getSimpleName() + "||" + getIdentifier();
}

protected abstract String getIdentifier();

}
@Entity
@Table(name = “rewards”)
@Aggregate(snapshotTriggerDefinition = “snapshotTriggerDefinition”)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RewardAggregate extends BaseAggregate {

@Id
private String rewardId;
private String name;
private String description;
private String criteriaExpression;
private double points;
private Instant createdAt;
private Instant updatedAt;
private UUID updatedBy;
private UUID serviceId;
private String serviceName;
private String status; //TODO:

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "reward_id")
@AggregateMember
private List<RewardCriteria> rewardCriteria = new ArrayList<>();

@Override
protected String getIdentifier(){
    return rewardId+"||"+serviceId;
}

CommandHandler
public RewardAggregate(CreateRewardCommand command) {
// super(command.getServiceId());

    log.debug("Handling CreateRewardCommand for rewardId: {} with command:{}", this.getRewardId(), command);

// if (command.getRewardId() == null || command.getRewardId().isEmpty()) {
// throw new ServiceCommandException(“Reward ID cannot be null or empty.”, 400);
// }
this.rewardId= UUID.randomUUID().toString();
this.serviceId= command.getServiceId();
try {

        RewardCreatedEvent rewardCreatedEvent = RewardCreatedEvent.builder()
                .rewardId(UUID.randomUUID().toString()) // Use generated ID
                .name(command.getName())
                .description(command.getDescription())
                .criteriaExpression(command.getCriteriaExpression())
                .points(command.getPoints())
                .createdAt(Instant.now())
                .updatedAt(Instant.now())
                .serviceId(command.getServiceId())
                .serviceName(command.getServiceName())
                .updatedBy(command.getUpdatedBy())
                .status("ACTIVE")
                .build();

}
This is my aggregate
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateRewardCommand {

private String name;
private String description;
private String criteriaExpression;
private double points;
private UUID updatedBy;
private UUID serviceId;
private String serviceName;

}
This is my createCommand where it doesn’t contains @TargetAggregateIdentifer since it is to create a new instance.
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UpdateRewardCommand {
@TargetAggregateIdentifier
private String rewardId;
private double rewardPoints;
private String criteriaExpression;
private UUID updatedBy;
}
This is my updateRewardCommand

@Marc_Klefter The issue I encountered is in the code above.

There’s a little bit to untangle here:

  • The (composite) aggregate identifier for your RewardAggregate has the form RewardAggregate||<rewardId>||<serviceId>. But your UpdateRewardCommand has a @TargetAggregateIdentifier of only rewardId. Thus, the identifiers do not match.

  • In the RewardAggregate, it seems you’re generating a UUID for the rewardId and assigning it in the command handler. Then, you’re creating a RewardCreatedEvent and again generating a UUID for the event’s rewardId field.

    Don’t set aggregate state in command handlers - rather set the rewardId in the @EventSourcingHandler for the RewardCreatedEvent.