A new bee here.
Axon 3.0.5 with all defaults with spring-boot.
Greatly impressed by everything just worxs. Thank you guys.
I might have came to a design choice here, so kindly enlighten me if the issue is not a technical one.
At high level I am trying to frame a question like :
"How to create different projections of same Domain Entity only using Event Sourced Aggregates "
Minimal sample for technical details:
A) TargetProfileAggregate is where my “write commands” C in CQRS is targeted, all good here.
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileAggregate {
@AggregateIdentifier
private Long id;
private List<String> msisdn = new ArrayList<>();
B) TargetProfileViewAggregate is projection (Q is CQRS) of the above aggregate handing primary “read commands by id”
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileViewAggregate {
@AggregateIdentifier
private Long id;
private List<String> msisdn = new ArrayList<>();
C) TargetProfileViewByMSISDNAggregate is another projection I want to build from same events.
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileViewByMSISDNAggregate {
@AggregateIdentifier
private String msisdn;
private List<Long> id = new ArrayList<>();
D) The issue is “TargetProfileViewByMSISDNAggregate” has @AggregateIdentifier as it has to serve commands having different @TargetAggregateIdentifier then so called “primary key used for write”
public class GetTargetProfileViewByMSISDNCommand {
@TargetAggregateIdentifier
@NotBlank
String msisdn;
Full source code:
Currently “TargetProfileViewByMSISDNAggregate” does not get built on Command “GetTargetProfileViewByMSISDNCommand”, So what is the “right” way to build such aggregates?
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileAggregate {
@AggregateIdentifier
private Long id;
private String name;
private List<String> msisdn = new ArrayList<>();
private HashMap<Long, String> contents = new LinkedHashMap<>();
@Autowired
private BlobStore contentStore;
@CommandHandler
public TargetProfileAggregate(TargetProfileCreateCommand command) {
log.info("Handling {}[{}]", command.getClass().getSimpleName(), command.getId());
apply(TargetProfileCreatedEvent.builder().id(command.getId()).auditEntry(command.getAuditEntry())
.name(command.getName()).build());
}
@CommandHandler
public int on(AddMsisdnForTargetProfileCommand command) {
log.info("Handling {}[{}]", command.getClass().getSimpleName(), command.getId());
if (msisdn.contains(command.getMsisdn())) {
throw new DataIntegrityViolationException("Can't associate duplicate msisdn " + command.getMsisdn());
} else {
apply(AddMsisdnForTargetProfileEvent.builder().id(command.getId())
.auditEntry(command.getAuditEntry()).msidn(command.getMsisdn()).build());
return msisdn.size() - 1; //always gets added at last index.
}
}
@CommandHandler
public long on(AddContentForTargetProfileCommand command) throws IOException {
log.info("Handling {}[{}]", command.getClass().getSimpleName(), command.getId());
File file = new File(command.getFileName());
FileCopyUtils.copy(command.getFileContent(), file);
long fileId = contentStore.save(file);
apply(AddContentForTargetProfileEvent.builder().id(command.getId())
.auditEntry(command.getAuditEntry()).fileId(fileId).fileName(command.getFileName()).build());
return fileId;
}
@EventSourcingHandler
public void on(TargetProfileCreatedEvent event) {
this.id = event.getId();
this.name = event.getName();
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
@EventSourcingHandler
public void on(AddMsisdnForTargetProfileEvent event) {
msisdn.add(event.getMsidn());
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
@EventSourcingHandler
public void on(AddContentForTargetProfileEvent event) {
contents.put(event.getFileId(), event.getFileName());
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
}
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileViewAggregate {
@AggregateIdentifier
private Long id;
private long version;
private String name;
private List<String> msisdn = new ArrayList<>();
@CommandHandler
public TargetProfileViewResponse on(GetTargetProfileViewCommand command) {
log.info("Handling {}[{}]", command.getClass().getSimpleName(), command.getId());
return TargetProfileViewResponse.builder().id(id).name(name).version(version)
.msisdn(msisdn.toArray(new String[msisdn.size()])).build();
}
@EventSourcingHandler
public void on(TargetProfileCreatedEvent event, @SequenceNumber Long version) {
this.id = event.getId();
this.version = version;
this.name = event.getName();
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
@EventSourcingHandler
public void on(AddMsisdnForTargetProfileEvent event) {
msisdn.add(event.getMsidn());
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
}
@Aggregate
@Slf4j
@NoArgsConstructor
public class TargetProfileViewByMSISDNAggregate {
@AggregateIdentifier
private String msisdn;
private List<Long> id = new ArrayList<>();
@CommandHandler
public List<TargetProfileViewResponse> on(GetTargetProfileViewByMSISDNCommand command) {
log.info("Handling {}[{}]", command.getClass().getSimpleName(), command);
List<TargetProfileViewResponse> targets = new ArrayList<>();
id.forEach(targetId -> targets.add(TargetProfileViewResponse.builder().id(targetId)
.msisdn(new String[]{msisdn}).build()));
return targets;
}
@EventSourcingHandler
public void on(AddMsisdnForTargetProfileEvent event) {
msisdn = event.getMsidn();
id.add(event.getId());
log.info("Applied {}[{}]", event.getClass().getSimpleName(), event.getId());
}
}
I am missing something obvious ? Completely wrong design direction ? Requesting to share your opinions on how to do it using EventSourcedAggregate.