JavaOne 2017
JavaOne 2017
Rahul Somasunderam
Wanted Event Sourcing in 2016
Didn’t like anything I saw
Built my own thing - Grooves
I solve problems in Health Care

![]() |
Treat your database like you treat your application logs
Create
Read
Update
Delete
Mark and Jane share a bank account. The account has a balance of $75. Mark goes to an ATM and withdraws $50. At the same time Jane attempts to withdraw $50.
This is what our code looks like
@Transactional
boolean withdraw(String accountNumber, double amount) {
double balance = accountService.getBalance(accountNumber);
if (balance > amount) {
balance -= account;
accountService.setBalance(accountNumber, balance);
return true;
} else {
return false;
}
}By Deutsche Bundespost (Scan by User:Mattes) - Scan (700dpi, Millionen Farben), Public Domain Wikimedia
If you can look at your logs and debug your application, you are already doing that.
Banks defined their business model this way hundreds of years ago.
| | |
| | |
\$S_N\$ | Snapshot at Version N |
\$S_0\$ | empty snapshot |
\$E_1..E_N\$ | events from position 1 through N |
\$f\$ | query |
This is what we would like to do
In Mathematics, it’s called the distributive property
There are special events
Employee
| Department
|
public class Patient implements AggregateType<Long> {
private Long id;
private String uniqueId;
}public abstract class PatientEvent implements
BaseEvent<Long, Patient, Long, PatientEvent> {
private Patient aggregate;
private Long id;
private String createdBy;
private RevertEvent<Long, Patient, Long, PatientEvent> revertedBy;
private Date timestamp;
private Long position;
@Override
@NotNull
public Observable<Patient> getAggregateObservable() {
return aggregate != null ? just(aggregate) : empty();
}
}public class PatientCreated extends PatientEvent {
private String name;
}public class PatientEventReverted
extends PatientEvent
implements RevertEvent<Long, Patient, Long, PatientEvent> {
private Long revertedEventId;
}public class PatientAccount
implements JavaSnapshot<Long, Patient, Long, Long, PatientEvent>,
Serializable {
private Long id;
private Patient aggregate;
private Patient deprecatedBy;
private List<Patient> deprecates = new ArrayList<>();
private Long lastEventPosition;
private Date lastEventTimestamp;
private String name;
private BigDecimal balance = new BigDecimal(0);
private BigDecimal moneyMade = new BigDecimal(0);
public Observable<Patient> getAggregateObservable() {
return just(aggregate);
}
public Observable<Patient> getDeprecatedByObservable() {
return just(deprecatedBy);
}
public Observable<Patient> getDeprecatesObservable() {
return from(deprecates);
}
}public class PatientAccountQuery<...> extends QuerySupport<...> {
...
}Could also be VersionedQuerySupport or TemporalQuerySupport
@Override
public PatientAccount createEmptySnapshot() {
return new PatientAccount();
}
@Override
public Observable<PatientAccount> getSnapshot(
long maxPosition, Patient aggregate) {
...
}
@Override
public Observable<PatientAccount> getSnapshot(
Date maxTimestamp, Patient aggregate) {
...
} @Override
public Observable<PatientEvent> getUncomputedEvents(
Patient aggregate, PatientAccount lastSnapshot, long version) {
}
@Override
public Observable<PatientEvent> getUncomputedEvents(
Patient aggregate, PatientAccount lastSnapshot, Date snapshotTime) {
} @Override
default Observable<EventApplyOutcome> onException(
Exception e, PatientAccount snapshot, PatientEvent event) {
getLog().error("Error computing snapshot", e);
return just(CONTINUE);
}For languages like Java, Groovy
public Observable<EventApplyOutcome> applyPatientCreated(
PatientCreated event, PatientAccount snapshot) {
if (snapshot.getAggregate() == event.getAggregate()) {
snapshot.setName(event.getName());
}
return just(CONTINUE);
}
...For languages with case classes
override fun applyEvent(
event: PatientEvent.Applicable, snapshot: PatientAccount) =
when (event) {
is PatientEvent.Applicable.Created -> {
// Your logic here
just(CONTINUE)
}
is PatientEvent.Applicable.ProcedurePerformed -> {
// Your logic here
just(CONTINUE)
}
is PatientEvent.Applicable.PaymentMade -> {
// Your logic here
just(CONTINUE)
}
}For languages with case classes
sealed class PatientEvent : BaseEvent<..> {
// Properties and methods from equivalent java class
sealed class Applicable : PatientEvent() {
data class Created(val name: String) : Applicable()
data class ProcedurePerformed(
val code: String, val cost: Double) : Applicable()
..
}
data class Reverted(override val revertedEventId: String) :
PatientEvent(), RevertEvent<..>
}…and Groovy.
@Aggregate public class Patient {...}
public abstract class PatientEvent {}
@Event(Patient.class)
public class ProcedurePerformed extends PatientEvent {}
@Event(Patient.class)
public class PaymentMade extends PatientEvent {}
@Query(aggregate=Patient.class, snapshot=PatientAccount.class)
public class PatientAccountQuery {
...
Observable<EventApplyOutcome> applyProcedurePerformed() {...}
Observable<EventApplyOutcome> applyPaymentMade() {...}
}Patient patient = ...
Observable<PatientAccount> account;
// By Date
account = patientAccountQuery.computeSnapshot(patient, new Date());
// By version
account = patientAccountQuery.computeSnapshot(patient, 7L);How many Tour de France General Classification Tour victories did this guy have on 2007-10-01?
EPA/JASPER JUINEN, 22 July 2004. Sourced from vosizneias
|
|
1. Based on what we knew in 2007, How many TdF GC Tour victories did he have on 2007-10-01? | 2. Based on what we know in 2017, How many TdF GC Tour victories did he have on 2007-10-01? |
Ask the right question
Be very careful about what your events look like.
Don’t worry much about what your snapshots look like.
95% of the cost of event sourcing projects is explaining event sourcing
— Scott Bellware (@sbellware) June 11, 2017
When the intent of the user is not clear from the datasource.
The Story of Goldilocks and The Three Bears, Award Publications LTD
Slidedeck | |
Grooves |
Sun 15:00 | CON7610 | Microservices Data Patterns: CQRS and ES |
Mon 16:30 | CON2526 | Reactive Stream Processing with Swarm and Kafka |
Mon 17:30 | CON7474 | ES, Distributed Systems, and CQRS with Java EE |
Tue 13:15 | CON4083 | Async by Default, Synchronous When Necessary |
Wed 14:45 | CON4277 | Three µS Patterns to Tear Down Your Monoliths |