Skip to content

Commit 7019f50

Browse files
📸 chore: Creature Recruitment | Add snapshotting to Dwelling aggregatwith 5-event threshold (#25)
The Dwelling aggregate is now configured to use the existing dwellingSnapshotTrigger bean to create snapshots every 5 events. It's just to show how the snapshooting can be configured.
1 parent f344304 commit 7019f50

6 files changed

Lines changed: 212 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,4 @@ void givenDwellingWith2Creatures_WhenRecruit2Creatures_ThenRecruited() {
157157

158158
If you'd like to hire me for Domain-Driven Design and/or Event Sourcing projects I'm available to work with:
159159
Kotlin, Java, C# .NET, Ruby and JavaScript/TypeScript (Node.js or React).
160-
Please reach me out on LinkedIn [linkedin.com/in/mateusznakodach/](https://www.linkedin.com/in/mateusznakodach/).
160+
Please reach me out on LinkedIn [linkedin.com/in/mateusznakodach/](https://www.linkedin.com/in/mateusznakodach/).

src/main/java/com/dddheroes/heroesofddd/creaturerecruitment/CreatureRecruitmentConfiguration.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package com.dddheroes.heroesofddd.creaturerecruitment;
22

3+
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingId;
34
import com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature.RecruitCreature;
45
import com.dddheroes.heroesofddd.resourcespool.application.CommandCostResolver;
56
import com.dddheroes.heroesofddd.shared.domain.valueobjects.Resources;
7+
import com.fasterxml.jackson.core.JsonGenerator;
8+
import com.fasterxml.jackson.core.JsonParser;
9+
import com.fasterxml.jackson.databind.*;
10+
import com.fasterxml.jackson.databind.Module;
11+
import com.fasterxml.jackson.databind.module.SimpleModule;
12+
import org.axonframework.eventsourcing.EventCountSnapshotTriggerDefinition;
13+
import org.axonframework.eventsourcing.SnapshotTriggerDefinition;
14+
import org.axonframework.eventsourcing.Snapshotter;
615
import org.springframework.context.annotation.Bean;
716
import org.springframework.context.annotation.Configuration;
817

18+
import java.io.IOException;
19+
920
@Configuration
1021
class CreatureRecruitmentConfiguration {
1122

@@ -23,4 +34,33 @@ public Class<? extends RecruitCreature> supportedCommandType() {
2334
}
2435
};
2536
}
37+
38+
@Bean
39+
SnapshotTriggerDefinition dwellingSnapshotTrigger(Snapshotter snapshotter) {
40+
return new EventCountSnapshotTriggerDefinition(snapshotter, 5);
41+
}
42+
43+
@Bean
44+
public Module dwellingIdSerializationModule() {
45+
return new DwellingIdSerializationModule();
46+
}
47+
48+
private static class DwellingIdSerializationModule extends SimpleModule {
49+
50+
public DwellingIdSerializationModule() {
51+
addSerializer(DwellingId.class, new JsonSerializer<>() {
52+
@Override
53+
public void serialize(DwellingId value, JsonGenerator gen, SerializerProvider __) throws IOException {
54+
gen.writeString(value.raw());
55+
}
56+
});
57+
addDeserializer(DwellingId.class, new JsonDeserializer<>() {
58+
@Override
59+
public DwellingId deserialize(JsonParser p, DeserializationContext __) throws IOException {
60+
return new DwellingId(p.getValueAsString());
61+
}
62+
});
63+
}
64+
65+
}
2666
}

src/main/java/com/dddheroes/heroesofddd/creaturerecruitment/write/Dwelling.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@
1919
import org.axonframework.modelling.command.AggregateIdentifier;
2020
import org.axonframework.modelling.command.CreationPolicy;
2121
import org.axonframework.spring.stereotype.Aggregate;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2224

2325
import static org.axonframework.modelling.command.AggregateLifecycle.*;
2426

25-
@Aggregate
27+
@Aggregate(snapshotTriggerDefinition = "dwellingSnapshotTrigger")
2628
public class Dwelling {
2729

30+
private static final Logger logger = LoggerFactory.getLogger(Dwelling.class);
31+
2832
@AggregateIdentifier
29-
private DwellingId dwellingId;
30-
private CreatureId creatureId;
31-
private Resources costPerTroop;
32-
private Amount availableCreatures;
33+
public DwellingId dwellingId; // needs to be public for snapshotting
34+
public CreatureId creatureId;
35+
public Resources costPerTroop;
36+
public Amount availableCreatures;
3337

3438
@CommandHandler
3539
@CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING) // performance downside in comparison to constructor
@@ -47,6 +51,7 @@ void decide(BuildDwelling command) {
4751

4852
@EventSourcingHandler
4953
void evolve(DwellingBuilt event) {
54+
logger.info("🏗️ Dwelling built with ID: {}, creature type: {}", event.dwellingId(), event.creatureId());
5055
this.dwellingId = new DwellingId(event.dwellingId());
5156
this.creatureId = new CreatureId(event.creatureId());
5257
this.costPerTroop = Resources.fromRaw(event.costPerTroop());
@@ -69,15 +74,13 @@ void decide(IncreaseAvailableCreatures command) {
6974

7075
@EventSourcingHandler
7176
void evolve(AvailableCreaturesChanged event) {
77+
logger.info("📈 Available creatures changed for dwelling {}: {} creatures now available",
78+
event.dwellingId(), event.changedTo());
7279
this.availableCreatures = new Amount(event.changedTo());
7380
}
7481

7582
@CommandHandler
76-
// @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
7783
void decide(RecruitCreature command) {
78-
// if(dwellingId == null){
79-
// throw new DomainRule.ViolatedException("Only not built building can be build");
80-
// }
8184
new RecruitCreaturesNotExceedAvailableCreatures(
8285
creatureId,
8386
availableCreatures,
@@ -104,11 +107,16 @@ void decide(RecruitCreature command) {
104107

105108
@EventSourcingHandler
106109
void evolve(CreatureRecruited event) {
110+
logger.info("🧙 Recruited {} creatures of type {} from dwelling {} to army {}",
111+
event.quantity(), event.creatureId(), event.dwellingId(), event.toArmy());
107112
// todo: consider if it's OK or RecruitCreature should cause also AvailableCreaturesChanged event
108113
this.availableCreatures = this.availableCreatures.minus(new Amount(event.quantity()));
109114
}
110115

111116
Dwelling() {
117+
logger.info("\uD83D\uDC80 Dwelling non-args constructor");
112118
// required by Axon
113119
}
120+
121+
114122
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.dddheroes.heroesofddd.shared.infrastructure;
2+
3+
import com.dddheroes.heroesofddd.shared.domain.identifiers.ArmyId;
4+
import com.dddheroes.heroesofddd.shared.domain.identifiers.CreatureId;
5+
import com.dddheroes.heroesofddd.shared.domain.identifiers.GameId;
6+
import com.dddheroes.heroesofddd.shared.domain.identifiers.PlayerId;
7+
import com.dddheroes.heroesofddd.shared.domain.valueobjects.Amount;
8+
import com.fasterxml.jackson.core.JsonGenerator;
9+
import com.fasterxml.jackson.core.JsonParser;
10+
import com.fasterxml.jackson.databind.*;
11+
import com.fasterxml.jackson.databind.Module;
12+
import com.fasterxml.jackson.databind.module.SimpleModule;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
16+
import java.io.IOException;
17+
18+
@Configuration
19+
public class SerializationConfiguration {
20+
21+
@Bean
22+
public Module gameIdSerializationModule() {
23+
return new GameIdSerializationModule();
24+
}
25+
26+
@Bean
27+
public Module playerIdSerializationModule() {
28+
return new PlayerIdSerializationModule();
29+
}
30+
31+
@Bean
32+
public Module creatureIdSerializationModule() {
33+
return new CreatureIdSerializationModule();
34+
}
35+
36+
@Bean
37+
public Module armyIdSerializationModule() {
38+
return new ArmyIdSerializationModule();
39+
}
40+
41+
@Bean
42+
public Module amountSerializationModule() {
43+
return new AmountSerializationModule();
44+
}
45+
46+
private static class GameIdSerializationModule extends SimpleModule {
47+
48+
public GameIdSerializationModule() {
49+
addSerializer(GameId.class, new JsonSerializer<>() {
50+
@Override
51+
public void serialize(GameId value, JsonGenerator gen, SerializerProvider __) throws IOException {
52+
gen.writeString(value.raw());
53+
}
54+
});
55+
addDeserializer(GameId.class, new JsonDeserializer<>() {
56+
@Override
57+
public GameId deserialize(JsonParser p, DeserializationContext __) throws IOException {
58+
return new GameId(p.getValueAsString());
59+
}
60+
});
61+
}
62+
63+
}
64+
65+
private static class PlayerIdSerializationModule extends SimpleModule {
66+
67+
public PlayerIdSerializationModule() {
68+
addSerializer(PlayerId.class, new JsonSerializer<>() {
69+
@Override
70+
public void serialize(PlayerId value, JsonGenerator gen, SerializerProvider __) throws IOException {
71+
gen.writeString(value.raw());
72+
}
73+
});
74+
addDeserializer(PlayerId.class, new JsonDeserializer<>() {
75+
@Override
76+
public PlayerId deserialize(JsonParser p, DeserializationContext __) throws IOException {
77+
return new PlayerId(p.getValueAsString());
78+
}
79+
});
80+
}
81+
}
82+
83+
private static class CreatureIdSerializationModule extends SimpleModule {
84+
85+
public CreatureIdSerializationModule() {
86+
addSerializer(CreatureId.class, new JsonSerializer<>() {
87+
@Override
88+
public void serialize(CreatureId value, JsonGenerator gen, SerializerProvider __) throws IOException {
89+
gen.writeString(value.raw());
90+
}
91+
});
92+
addDeserializer(CreatureId.class, new JsonDeserializer<>() {
93+
@Override
94+
public CreatureId deserialize(JsonParser p, DeserializationContext __) throws IOException {
95+
return new CreatureId(p.getValueAsString());
96+
}
97+
});
98+
}
99+
100+
}
101+
102+
private static class ArmyIdSerializationModule extends SimpleModule {
103+
104+
public ArmyIdSerializationModule() {
105+
addSerializer(ArmyId.class, new JsonSerializer<>() {
106+
@Override
107+
public void serialize(ArmyId value, JsonGenerator gen, SerializerProvider __) throws IOException {
108+
gen.writeString(value.raw());
109+
}
110+
});
111+
addDeserializer(ArmyId.class, new JsonDeserializer<>() {
112+
@Override
113+
public ArmyId deserialize(JsonParser p, DeserializationContext __) throws IOException {
114+
return new ArmyId(p.getValueAsString());
115+
}
116+
});
117+
}
118+
119+
}
120+
121+
private static class AmountSerializationModule extends SimpleModule {
122+
123+
public AmountSerializationModule() {
124+
addSerializer(Amount.class, new JsonSerializer<>() {
125+
@Override
126+
public void serialize(Amount value, JsonGenerator gen, SerializerProvider __) throws IOException {
127+
gen.writeNumber(value.raw());
128+
}
129+
});
130+
addDeserializer(Amount.class, new JsonDeserializer<>() {
131+
@Override
132+
public Amount deserialize(JsonParser p, DeserializationContext __) throws IOException {
133+
return new Amount(p.getValueAsInt());
134+
}
135+
});
136+
}
137+
138+
}
139+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
axon:
2+
axonserver:
3+
enabled: false
4+
# eventhandling:
5+
# processors:
6+
# (Optional) You can override processor settings here if needed
7+
# eventstore:
8+
# No need to specify storage-engine if axon-server is disabled and JPA is on classpath
9+
# Axon will auto-configure JpaEventStorageEngine
10+
11+
# Inherit datasource and JPA config from application.yaml

src/main/resources/application.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ spring:
1717
hibernate:
1818
dialect: org.hibernate.dialect.PostgreSQLDialect
1919
axon:
20-
serializer:
21-
general: jackson
2220
axonserver:
2321
enabled: true
22+
serializer:
23+
general: jackson
24+
events: jackson
25+
messages: jackson
2426
eventhandling:
2527
processors:
2628
Automation_WhenCreatureRecruitedThenAddToArmy_Processor:

0 commit comments

Comments
 (0)