I ran RavenDB
from docker. I need to manually create a database.
link:assets/docker-compose.yaml[role=include]
Screenshots from the application run.
See ESRavenProgram
Create account # this leads to AccountCreated event Record call # this leads to PhoneCallCharged event Top up credit # this leads to CreditSatisfiesFreeCallAllowanceOffer and CreditAdded events Press any key to continue... Record call # this leads to PhoneCallCharged event
The exception:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: sqliteeventstore.model.PayAsYouGo.PhoneCallCharged["phoneCall"]->sqliteeventstore.model.PayAsYouGo.PhoneCall["callStart"])
Fixed by registering the module.
objectMapper.registerModule(new JavaTimeModule());
In PhoneNumber
, Jackson tried to deserialize boolean checks e.g. isUKLandlineOrMobile
.
However, it’s not a field.
Adding JsonIgnore
solves the problem.
But it brings a bit of infrastructure into the model.
Jackson needs no-arg constructors, getters and setters. Which makes classes a bit of boilerplate-ish.
Screenshots from the application run.
There are almost the same as for RavenDB
.
I keep them just to demonstrate that the application works.
See EsSqliteProgram
Create account # this leads to AccountCreated event Record call # this leads to PhoneCallCharged event Top up credit # this leads to CreditSatisfiesFreeCallAllowanceOffer and CreditAdded events Press any key to continue... Record call # this leads to PhoneCallCharged event
By default EventStoreDB
is non-blocking. Each call returns CompletableFuture
.
I used "blocking" calls. To achieve that, I added a helper method that waits for completions.
See EventStoreD.await
.
I run EventStoreDB
from docker.
link:assets/docker-compose.yaml[role=include]
In EventStore.getStream
, we calculate amount
.
It can lead to integer overload.
@Override public List<DomainEvent> getStream(String streamName, int fromVersion, int toVersion) {
// if fromVersion = 0, toVersion = Integer.MAX_VALUE
// we have interger overload
var amount = (toVersion - fromVersion) + 1;
}
ReadStreamOptions.get().fromEnd()
leads to the exception below.
java.lang.RuntimeException: java.util.concurrent.ExecutionException: io.grpc.StatusRuntimeException: UNKNOWN: Unexpected ReadStreamResult: Error
ReadStreamOptions.get().fromEnd().backwards()
works.
EventStoreDB
creates ObjectMapper
ad hoc, that’s why I can’t even register a module.
public static <A> EventDataBuilder json(UUID id, String eventType, A eventData) {
try {
JsonMapper mapper = new JsonMapper(); // <--- HERE
return json(id, eventType, mapper.writeValueAsBytes(eventData));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
E.g. I can’t use LocalDateTime
, because this mapper doesn’t have jackson-datatype-jsr310
module.
And I can’t add it.
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: sqliteeventstore.model.PayAsYouGo.PhoneCallCharged["phoneCall"]->sqliteeventstore.model.PayAsYouGo.PhoneCall["callStart"])
I switched LocalDateTime
to String
.
When we save, we serialize data with ObjectMapper
, that "embedded" into EventStoreDB
.
When we load, we deserialize data with own ObjectMapper
. Data is serialized and deserialized by different ObjectMapper
.
It’s not good.
We start from a clean EventStoreDB
.
Then we create a steam and the first event
Top up account leads to two events: CreditSatisfiesFreeCallAllowanceOffer
and CreditAdded
.
The last phone call
I do it on empty EventStoreDB
.
To populate data in EventStoreDB
, run ImportTestData
.
I was getting MoreThanTwoDecimalPlacesInMoneyValueException
while importing data.
I think it’s because numbers in ImportTestData
and because of usage of double
.
To save time, I deleted this check.
FreePhoneCallCosting
extends PhoneCallCosting
.
PhoneCallCosting
was a record
but to make this inheritance I converted it to class
.
Queries fromCategory
didn’t return anything at all.
I found a similar problem on StackOverflow.
I went to: Projections → $by_category
→ Press "Start". It helped.
fromStream('{PayAsYouGoAccount}-{a1af9c2612e248529c5ea73ab196e46d}')
.when({
// initialize the state
$init : function(s,e) {
return {
minutes : 0
}
},
"eventstoredb.model.PayAsYouGo.PhoneCallCharged" : function(s,e) {
var dateOfCall = e.data.phoneCall.callStart;
var june4th = '2014-06-04';
if (dateOfCall.substring(0, 10) === june4th) {
s.minutes += e.data.phoneCall.minutes.number;
}
}
});
fromStream('{PayAsYouGoAccount}-{a9b540ef309d49fd987bb05cb31634a6}')
.when({
// initialize the state
$init : function(s,e) {
return {
june3rd: 0,
june4th: 0,
june5th: 0
}
},
"eventstoredb.model.PayAsYouGo.PhoneCallCharged" : function(s,e) {
const dateOfCall = e.data.phoneCall.callStart;
const june3rd = '2014-06-03';
const june4th = '2014-06-04';
const june5th = '2014-06-05';
if (dateOfCall.substring(0, 10) == june3rd) {
s.june3rd += e.data.phoneCall.minutes.number
}
if (dateOfCall.substring(0, 10) == june4th) {
s.june4th += e.data.phoneCall.minutes.number
}
if (dateOfCall.substring(0, 10) == june5th) {
s.june5th += e.data.phoneCall.minutes.number
}
}
// handle other types of event and update the state accordingly
});
fromCategory('{PayAsYouGoAccount}')
.when({
// initialize the state
$init : function(s,e) {
return {
june3rd: 0,
june4th: 0,
june5th: 0
}
},
"eventstoredb.model.PayAsYouGo.PhoneCallCharged" : function(s,e) {
const dateOfCall = e.data.phoneCall.callStart;
const june3rd = '2014-06-03';
const june4th = '2014-06-04';
const june5th = '2014-06-05';
if (dateOfCall.substring(0, 10) == june3rd) {
s.june3rd += e.data.phoneCall.minutes.number;
}
if (dateOfCall.substring(0, 10) == june4th) {
s.june4th += e.data.phoneCall.minutes.number;
}
if (dateOfCall.substring(0, 10) == june5th) {
s.june5th += e.data.phoneCall.minutes.number;
}
}
// handle other types of event and update the state accordingly
});
Go to Projections → New Projection.
fromCategory('{PayAsYouGoAccount}')
.when({
"eventstoredb.model.PayAsYouGo.CreditAdded": function(s, event) {
linkTo('AllTopUps', event);
}
});
After pressing "Create", you will be redirected to projection details page.
you can also see the projection in streams
we can explore the projection.
And we can query projections
fromStream('$projections-AllTopUps')
.when({
$init : function(state, event) {
return {
count: 0
};
},
$any: function(state, event) {
state.count += 1;
}
});
-
❏ RavenDB: write tests for snapshot logic
-
❏ SQLite: write tests for snapshot logic
-
❏ SQLite: add
UnitOfWork
-
❏ SQLite: add transactions
-
❏ Try
MongoDB
as eventstore -
❏ Try
Kafka
as eventstore -
❏ Try MartenDB as eventstore