Skip to content

Commit be017c6

Browse files
✨ feat: events sequencing policy based on gameId (#12)
1 parent f0bd796 commit be017c6

9 files changed

Lines changed: 178 additions & 37 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.dddheroes.heroesofddd;
2+
3+
import com.dddheroes.heroesofddd.shared.GameMetaData;
4+
import org.axonframework.eventhandling.EventMessage;
5+
import org.axonframework.eventhandling.async.SequencingPolicy;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
@Configuration
10+
public class GameConfiguration {
11+
12+
@Bean
13+
public SequencingPolicy<EventMessage<?>> gameIdSequencingPolicy() {
14+
return e -> e.getMetaData().get(GameMetaData.KEY);
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.dddheroes.heroesofddd.astrologers.automation.whenweeksymbolproclaimedthenincreasedwellingavailablecreatures;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.Id;
5+
import jakarta.persistence.Index;
6+
import jakarta.persistence.Table;
7+
8+
import java.util.Objects;
9+
10+
@Entity
11+
@Table(
12+
name = "read_model_built_dwelling",
13+
indexes = @Index(name = "idx_game_id", columnList = "gameId")
14+
)
15+
public class BuiltDwellingReadModel {
16+
17+
private String gameId;
18+
19+
@Id
20+
private String dwellingId;
21+
22+
private String creatureId;
23+
24+
public BuiltDwellingReadModel(String gameId, String dwellingId, String creatureId) {
25+
this.gameId = gameId;
26+
this.dwellingId = dwellingId;
27+
this.creatureId = creatureId;
28+
}
29+
30+
public String getGameId() {
31+
return gameId;
32+
}
33+
34+
public String getDwellingId() {
35+
return dwellingId;
36+
}
37+
38+
public String getCreatureId() {
39+
return creatureId;
40+
}
41+
42+
protected BuiltDwellingReadModel() {
43+
// Required by JPA
44+
}
45+
46+
@Override
47+
public boolean equals(Object o) {
48+
if (this == o) {
49+
return true;
50+
}
51+
if (o == null || getClass() != o.getClass()) {
52+
return false;
53+
}
54+
BuiltDwellingReadModel that = (BuiltDwellingReadModel) o;
55+
return Objects.equals(dwellingId, that.dwellingId);
56+
}
57+
58+
@Override
59+
public int hashCode() {
60+
return Objects.hashCode(dwellingId);
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dddheroes.heroesofddd.astrologers.automation.whenweeksymbolproclaimedthenincreasedwellingavailablecreatures;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
5+
6+
import java.util.List;
7+
8+
@Repository
9+
public interface BuiltDwellingReadModelRepository extends JpaRepository<BuiltDwellingReadModel, String> {
10+
11+
List<BuiltDwellingReadModel> findAllByGameId(String gameId);
12+
}
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,59 @@
11
package com.dddheroes.heroesofddd.astrologers.automation.whenweeksymbolproclaimedthenincreasedwellingavailablecreatures;
22

33
import com.dddheroes.heroesofddd.astrologers.write.proclaimweeksymbol.WeekSymbolProclaimed;
4-
import com.dddheroes.heroesofddd.creaturerecruitment.read.DwellingReadModel;
5-
import com.dddheroes.heroesofddd.creaturerecruitment.read.getalldwellings.GetAllDwellings;
4+
import com.dddheroes.heroesofddd.creaturerecruitment.write.builddwelling.DwellingBuilt;
65
import com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures.IncreaseAvailableCreatures;
76
import com.dddheroes.heroesofddd.shared.GameMetaData;
87
import org.axonframework.commandhandling.gateway.CommandGateway;
98
import org.axonframework.config.ProcessingGroup;
109
import org.axonframework.eventhandling.DisallowReplay;
1110
import org.axonframework.eventhandling.EventHandler;
1211
import org.axonframework.messaging.annotation.MetaDataValue;
13-
import org.axonframework.queryhandling.QueryGateway;
1412
import org.springframework.stereotype.Component;
1513

16-
@ProcessingGroup("Automation_WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreatures_Processor")
14+
@ProcessingGroup("ReadModel_Dwelling")
1715
@DisallowReplay
1816
@Component
1917
class WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesProcessor {
2018

21-
private final QueryGateway queryGateway;
2219
private final CommandGateway commandGateway;
2320

21+
// it may be easier to use live model, but AF4 do not allow me to read events just till some position
22+
private final BuiltDwellingReadModelRepository repository;
23+
2424
WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesProcessor(
25-
QueryGateway queryGateway,
26-
CommandGateway commandGateway
25+
CommandGateway commandGateway,
26+
BuiltDwellingReadModelRepository repository
2727
) {
28-
this.queryGateway = queryGateway;
2928
this.commandGateway = commandGateway;
29+
this.repository = repository;
3030
}
3131

3232
@EventHandler
3333
void react(WeekSymbolProclaimed event, @MetaDataValue(GameMetaData.KEY) String gameId) {
34-
// todo: separate dwelling per game. Now we read all of them
35-
// I want be consistent here. With DBC it'd be nice to query all types and by tags like game.
36-
// use EventStore, @SequenceNumber long sequenceNumber
37-
3834
var creature = event.weekOf();
3935
var increaseBy = event.growth();
40-
var toProcess = queryGateway.query(GetAllDwellings.query(gameId), GetAllDwellings.Result.class);
41-
toProcess.thenAccept(r -> r.dwellings()
42-
.stream().filter(dwelling -> dwelling.getCreatureId().equals(creature))
43-
.forEach(dwelling -> increaseAvailableCreatures(dwelling, increaseBy)));
36+
repository.findAllByGameId(gameId).stream()
37+
.filter(dwelling -> dwelling.getCreatureId().equals(creature))
38+
.forEach(dwelling -> increaseAvailableCreatures(dwelling, increaseBy));
4439
}
4540

46-
private void increaseAvailableCreatures(DwellingReadModel dwelling, Integer increaseBy) {
41+
private void increaseAvailableCreatures(BuiltDwellingReadModel dwelling, Integer increaseBy) {
4742
var command = IncreaseAvailableCreatures.command(
4843
dwelling.getDwellingId(),
4944
dwelling.getCreatureId(),
5045
increaseBy
5146
);
5247
commandGateway.sendAndWait(command, GameMetaData.withId(dwelling.getGameId()));
5348
}
49+
50+
@EventHandler
51+
void on(DwellingBuilt event, @MetaDataValue(GameMetaData.KEY) String gameId) {
52+
var state = new BuiltDwellingReadModel(
53+
gameId,
54+
event.dwellingId(),
55+
event.creatureId()
56+
);
57+
repository.save(state);
58+
}
5459
}

src/main/java/com/dddheroes/heroesofddd/astrologers/write/Astrologers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void decide(ProclaimWeekSymbol command) {
3636
@EventSourcingHandler
3737
void evolve(WeekSymbolProclaimed event) {
3838
this.astrologersId = new AstrologersId(event.astrologersId());
39-
this.week = new MonthWeek(event.month(), event.day());
39+
this.week = new MonthWeek(event.month(), event.week());
4040
}
4141

4242
Astrologers() {

src/main/java/com/dddheroes/heroesofddd/astrologers/write/proclaimweeksymbol/WeekSymbolProclaimed.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
public record WeekSymbolProclaimed(
99
String astrologersId,
1010
Integer month,
11-
Integer day,
11+
Integer week,
1212
String weekOf,
1313
Integer growth
1414
) implements AstrologersEvent {

src/main/java/com/dddheroes/heroesofddd/creaturerecruitment/read/DwellingReadModel.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
import java.util.Objects;
1313

1414
@Entity
15-
@Table(name = "read_model_dwelling", indexes = @Index(name = "idx_game_id", columnList = "gameId"))
15+
@Table(
16+
name = "read_model_dwelling",
17+
indexes = @Index(name = "idx_game_id", columnList = "gameId")
18+
)
1619
public class DwellingReadModel {
1720

1821
private String gameId;

src/main/resources/application.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,22 @@ axon:
2525
processors:
2626
Automation_WhenCreatureRecruitedThenAddToArmy_Processor:
2727
mode: pooled
28+
sequencing-policy: gameIdSequencingPolicy
2829
dlq:
2930
enabled: true
3031
Automation_WhenWeekStartedThenProclaimWeekSymbol_Processor:
3132
mode: pooled
33+
sequencing-policy: gameIdSequencingPolicy
3234
dlq:
3335
enabled: true
3436
Automation_WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreatures_Processor:
3537
mode: pooled
38+
sequencing-policy: gameIdSequencingPolicy
3639
dlq:
3740
enabled: true
3841
ReadModel_Dwelling:
3942
mode: pooled
43+
sequencing-policy: gameIdSequencingPolicy
4044
Read_GetAllDwellings_QueryCache:
41-
mode: subscribing
45+
mode: subscribing
46+
sequencing-policy: gameIdSequencingPolicy

src/test/java/com/dddheroes/heroesofddd/astrologers/automation/whenweeksymbolproclaimedthenincreasedwellingavailablecreatures/WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesTest.java

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.dddheroes.heroesofddd.TestcontainersConfiguration;
44
import com.dddheroes.heroesofddd.astrologers.write.AstrologersEvent;
55
import com.dddheroes.heroesofddd.astrologers.write.AstrologersId;
6-
import com.dddheroes.heroesofddd.astrologers.write.proclaimweeksymbol.ProclaimWeekSymbol;
76
import com.dddheroes.heroesofddd.astrologers.write.proclaimweeksymbol.WeekSymbolProclaimed;
87
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingEvent;
98
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingId;
@@ -25,10 +24,7 @@
2524
import org.springframework.context.annotation.Import;
2625
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
2726

28-
import java.util.UUID;
29-
3027
import static com.dddheroes.heroesofddd.utils.AwaitilityUtils.awaitUntilAsserted;
31-
import static org.mockito.ArgumentMatchers.any;
3228
import static org.mockito.Mockito.*;
3329

3430
@Import(TestcontainersConfiguration.class)
@@ -44,41 +40,74 @@ class WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesTest {
4440
private CommandGateway commandGateway;
4541

4642
@Test
47-
void test() {
43+
void whenWeekSymbolProclaimed_thenIncreaseDwellingsAvailableCreaturesIfSymbolSameAsSymbol() {
4844
// given
49-
var angelDwellingId1 = givenDwellingBuiltEvent("angel");
50-
var angelDwellingId2 = givenDwellingBuiltEvent("angel");
51-
var titanDwellingId = givenDwellingBuiltEvent("titan");
45+
var angelDwellingId1 = dwellingBuiltEvent("angel");
46+
var angelDwellingId2 = dwellingBuiltEvent("angel");
47+
var titanDwellingId = dwellingBuiltEvent("titan");
5248

5349
// when
5450
var astrologersId = AstrologersId.random();
55-
whenAstrologersEvents(
51+
astrologersEvents(
5652
astrologersId.raw(),
5753
new WeekSymbolProclaimed(astrologersId.raw(), 1, 1, "angel", 3)
5854
);
5955

6056
// then
6157
var expectedCommand1 = IncreaseAvailableCreatures.command(angelDwellingId1, "angel", 3);
62-
awaitUntilAsserted(() -> verify(commandGateway, times(1)).sendAndWait(expectedCommand1, GameMetaData.withId(GAME_ID)));
58+
assertCommandExecuted(expectedCommand1);
6359

6460
var expectedCommand2 = IncreaseAvailableCreatures.command(angelDwellingId2, "angel", 3);
65-
awaitUntilAsserted(() -> verify(commandGateway, times(1)).sendAndWait(expectedCommand2, GameMetaData.withId(GAME_ID)));
61+
assertCommandExecuted(expectedCommand2);
6662

6763
var notExpectedCommand = IncreaseAvailableCreatures.command(titanDwellingId, "titan", 3);
68-
awaitUntilAsserted(() -> verify(commandGateway, never()).sendAndWait(notExpectedCommand, GameMetaData.withId(GAME_ID)));
64+
assertCommandNotExecuted(notExpectedCommand);
6965
}
7066

71-
private String givenDwellingBuiltEvent(String creatureId) {
67+
@Test
68+
void whenWeekSymbolProclaimed_thenIncreaseAllDwellingsBuiltBeforeTheProclamation() {
69+
// given
70+
var astrologersId = AstrologersId.random();
71+
var angelDwellingId1 = dwellingBuiltEvent("angel");
72+
astrologersEvents(
73+
astrologersId.raw(),
74+
new WeekSymbolProclaimed(astrologersId.raw(), 1, 1, "angel", 1)
75+
);
76+
var angelDwellingId2 = dwellingBuiltEvent("angel");
77+
78+
// when
79+
astrologersEvents(
80+
astrologersId.raw(),
81+
new WeekSymbolProclaimed(astrologersId.raw(), 1, 2, "angel", 2)
82+
);
83+
84+
// then
85+
// week 1 - only 1 dwelling built
86+
var week1ExpectedCommand1 = IncreaseAvailableCreatures.command(angelDwellingId1, "angel", 1);
87+
assertCommandExecuted(week1ExpectedCommand1);
88+
var week1NotExpectedCommand1 = IncreaseAvailableCreatures.command(angelDwellingId2, "angel", 1);
89+
assertCommandNotExecuted(week1NotExpectedCommand1);
90+
91+
// week 2 - 2 dwellings built
92+
var week2ExpectedCommand1 = IncreaseAvailableCreatures.command(angelDwellingId1, "angel", 2);
93+
assertCommandExecuted(week2ExpectedCommand1);
94+
var week2ExpectedCommand2 = IncreaseAvailableCreatures.command(angelDwellingId2, "angel", 2);
95+
assertCommandExecuted(week2ExpectedCommand2);
96+
}
97+
98+
99+
private String dwellingBuiltEvent(String creatureId) {
72100
var dwellingId = DwellingId.random();
73101
var costPerTroop = Cost.resources(ResourceType.GOLD, Amount.of(1000));
74102
var event = DwellingBuilt.event(dwellingId, CreatureId.of(creatureId), costPerTroop);
75103
eventGateway.publish(dwellingDomainEvent(dwellingId.raw(), 0, event));
76104
return dwellingId.raw();
77105
}
78106

79-
private void whenAstrologersEvents(String gameId, AstrologersEvent... events) {
80-
for (int i = 0; i < events.length; i++) {
81-
eventGateway.publish(astrologersDomainEvent(gameId, i, events[i]));
107+
private void astrologersEvents(String gameId, WeekSymbolProclaimed... events) {
108+
for (var event : events) {
109+
var aggregateSequence = event.week() - 1;
110+
eventGateway.publish(astrologersDomainEvent(gameId, aggregateSequence, event));
82111
}
83112
}
84113

@@ -118,4 +147,13 @@ private static DomainEventMessage<?> givenDomainEvent(
118147
payload
119148
).andMetaData(GameMetaData.withId(GAME_ID));
120149
}
150+
151+
private void assertCommandExecuted(IncreaseAvailableCreatures expectedCommand1) {
152+
awaitUntilAsserted(() -> verify(commandGateway, times(1))
153+
.sendAndWait(expectedCommand1, GameMetaData.withId(GAME_ID)));
154+
}
155+
156+
private void assertCommandNotExecuted(IncreaseAvailableCreatures notExpectedCommand) {
157+
verify(commandGateway, never()).sendAndWait(notExpectedCommand, GameMetaData.withId(GAME_ID));
158+
}
121159
}

0 commit comments

Comments
 (0)