How to update Multiple Aggregates via single Event

877 views
Skip to first unread message

Dhruv Using Axon 3.0.5 with SpringBoot with JPA

unread,
Aug 10, 2017, 1:48:23 PM8/10/17
to Axon Framework Users
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?

1.

@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());
}
}

2.


@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());
}


}

3.

@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.

Allard Buijze

unread,
Aug 10, 2017, 3:27:31 PM8/10/17
to Axon Framework Users
Hi Dhruv,

in fact, there is something wrong with the design ;-). Projections aren't built with Aggregates, but using "plain" beans with @EventHandler annotated methods. Unlike Aggregates, these Event Handlers receive all events and can update a query model in the database. Aggregates are the implementation of the C part of CQRS.

Check out the webinar I did with Pivotal to see how to do this: https://www.youtube.com/watch?v=Jp-rW-XOYzA

Hope this help.
Cheers,

Allard

Op do 10 aug. 2017 om 19:48 schreef Dhruv Using Axon 3.0.5 with SpringBoot with JPA <yourfri...@gmail.com>:
--
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.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages