Skip to content

Commit 108d206

Browse files
✨ feat: Astrologers | automation: WeekSymbolProclaimed -> IncreaseAvailableCreatures (#9)
1 parent ea37338 commit 108d206

7 files changed

Lines changed: 277 additions & 3 deletions

File tree

src/main/java/com/dddheroes/heroesofddd/astrologers/automation/whenweekstartedthenproclaimweeksymbol/WhenWeekStartedThenProclaimWeekSymbolProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import org.axonframework.eventhandling.EventHandler;
1111
import org.springframework.stereotype.Component;
1212

13-
@ProcessingGroup("Automation_WhenWeekStartedThenProclaimWeekSymbolProcessor_Processor")
13+
@ProcessingGroup("Automation_WhenWeekStartedThenProclaimWeekSymbol_Processor")
1414
@DisallowReplay
1515
@Component
1616
class WhenWeekStartedThenProclaimWeekSymbolProcessor {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.dddheroes.heroesofddd.astrologers.automation.whenweeksymbolproclaimedthenincreasedwellingavailablecreatures;
2+
3+
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;
6+
import com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures.IncreaseAvailableCreatures;
7+
import org.axonframework.commandhandling.gateway.CommandGateway;
8+
import org.axonframework.config.ProcessingGroup;
9+
import org.axonframework.eventhandling.DisallowReplay;
10+
import org.axonframework.eventhandling.EventHandler;
11+
import org.axonframework.queryhandling.QueryGateway;
12+
import org.springframework.stereotype.Component;
13+
14+
@ProcessingGroup("Automation_WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreatures_Processor")
15+
@DisallowReplay
16+
@Component
17+
class WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesProcessor {
18+
19+
private final QueryGateway queryGateway;
20+
private final CommandGateway commandGateway;
21+
22+
WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesProcessor(
23+
QueryGateway queryGateway,
24+
CommandGateway commandGateway
25+
) {
26+
this.queryGateway = queryGateway;
27+
this.commandGateway = commandGateway;
28+
}
29+
30+
@EventHandler
31+
void react(WeekSymbolProclaimed event) {
32+
// todo: separate dwelling per game. Now we read all of them
33+
// I want be consistent here. With DBC it'd be nice to query all types and by tags like game.
34+
// use EventStore, @SequenceNumber long sequenceNumber
35+
36+
var creature = event.weekOf();
37+
var increaseBy = event.growth();
38+
var toProcess = queryGateway.query(GetAllDwellings.query(), GetAllDwellings.Result.class);
39+
toProcess.thenAccept(r -> r.dwellings()
40+
.stream().filter(dwelling -> dwelling.getCreatureId().equals(creature))
41+
.forEach(dwelling -> increaseAvailableCreatures(dwelling, increaseBy)));
42+
}
43+
44+
private void increaseAvailableCreatures(DwellingReadModel dwelling, Integer increaseBy) {
45+
var command = IncreaseAvailableCreatures.command(
46+
dwelling.getDwellingId(),
47+
dwelling.getCreatureId(),
48+
increaseBy
49+
);
50+
commandGateway.sendAndWait(command);
51+
}
52+
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.hibernate.type.SqlTypes;
99

1010
import java.util.Map;
11+
import java.util.Objects;
1112

1213
@Entity
1314
@Table(name = "read_model_dwelling")
@@ -24,7 +25,7 @@ public class DwellingReadModel {
2425

2526
private Integer availableCreatures;
2627

27-
DwellingReadModel(String dwellingId,
28+
public DwellingReadModel(String dwellingId,
2829
String creatureId,
2930
Map<String, Integer> costPerTroop,
3031
Integer availableCreatures
@@ -64,4 +65,21 @@ public Integer getAvailableCreatures() {
6465
protected DwellingReadModel() {
6566
// Required by JPA
6667
}
68+
69+
@Override
70+
public boolean equals(Object o) {
71+
if (this == o) {
72+
return true;
73+
}
74+
if (o == null || getClass() != o.getClass()) {
75+
return false;
76+
}
77+
DwellingReadModel that = (DwellingReadModel) o;
78+
return Objects.equals(dwellingId, that.dwellingId);
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
return Objects.hashCode(dwellingId);
84+
}
6785
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.read.getalldwellings;
2+
3+
import com.dddheroes.heroesofddd.creaturerecruitment.read.DwellingReadModel;
4+
5+
import java.util.List;
6+
7+
public record GetAllDwellings() {
8+
9+
public static GetAllDwellings query() {
10+
return new GetAllDwellings();
11+
}
12+
13+
public record Result(List<DwellingReadModel> dwellings) {
14+
15+
}
16+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.read.getalldwellings;
2+
3+
import com.dddheroes.heroesofddd.creaturerecruitment.read.DwellingReadModel;
4+
import com.dddheroes.heroesofddd.creaturerecruitment.read.DwellingReadModelRepository;
5+
import com.dddheroes.heroesofddd.creaturerecruitment.write.builddwelling.DwellingBuilt;
6+
import com.google.common.collect.Streams;
7+
import org.axonframework.config.ProcessingGroup;
8+
import org.axonframework.eventhandling.EventHandler;
9+
import org.axonframework.queryhandling.QueryHandler;
10+
import org.springframework.stereotype.Component;
11+
12+
import java.util.concurrent.ConcurrentLinkedDeque;
13+
14+
@ProcessingGroup("Read_GetAllDwellings_QueryCache")
15+
@Component
16+
class GetAllDwellingsQueryHandler {
17+
18+
private final DwellingReadModelRepository dwellingReadModelRepository;
19+
private final ConcurrentLinkedDeque<DwellingReadModel> cache = new ConcurrentLinkedDeque<>();
20+
21+
GetAllDwellingsQueryHandler(DwellingReadModelRepository dwellingReadModelRepository) {
22+
this.dwellingReadModelRepository = dwellingReadModelRepository;
23+
}
24+
25+
@QueryHandler
26+
GetAllDwellings.Result handle(GetAllDwellings query) {
27+
var dwellings = dwellingReadModelRepository.findAll();
28+
var result = Streams.concat(dwellings.stream(), cache.stream()) // todo: check ordering
29+
.distinct()
30+
.toList();
31+
return new GetAllDwellings.Result(result);
32+
}
33+
34+
@EventHandler
35+
void evolve(DwellingBuilt event) {
36+
while (cache.size() > 20) {
37+
cache.pollFirst();
38+
}
39+
var item = new DwellingReadModel(event.dwellingId(), event.creatureId(), event.costPerTroop(), 0);
40+
if (!cache.contains(item)) { // todo: check concurrency issues
41+
cache.push(item);
42+
}
43+
}
44+
}
45+
46+
// without cache
47+
//@Component
48+
//class GetAllDwellingsQueryHandler {
49+
//
50+
// private final DwellingReadModelRepository dwellingReadModelRepository;
51+
//
52+
// GetAllDwellingsQueryHandler(DwellingReadModelRepository dwellingReadModelRepository) {
53+
// this.dwellingReadModelRepository = dwellingReadModelRepository;
54+
// }
55+
//
56+
// @QueryHandler
57+
// GetAllDwellings.Result handle(GetAllDwellings query) {
58+
// var dwellings = dwellingReadModelRepository.findAll();
59+
// return new GetAllDwellings.Result(dwellings);
60+
// }
61+
//}

src/main/resources/application.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,15 @@ axon:
2727
mode: pooled
2828
dlq:
2929
enabled: true
30+
Automation_WhenWeekStartedThenProclaimWeekSymbol_Processor:
31+
mode: pooled
32+
dlq:
33+
enabled: true
34+
Automation_WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreatures_Processor:
35+
mode: pooled
36+
dlq:
37+
enabled: true
3038
ReadModel_Dwelling:
31-
mode: pooled
39+
mode: pooled
40+
Read_GetAllDwellings_QueryCache:
41+
mode: subscribing
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.dddheroes.heroesofddd.astrologers.automation.whenweeksymbolproclaimedthenincreasedwellingavailablecreatures;
2+
3+
import com.dddheroes.heroesofddd.TestcontainersConfiguration;
4+
import com.dddheroes.heroesofddd.astrologers.write.AstrologersEvent;
5+
import com.dddheroes.heroesofddd.astrologers.write.AstrologersId;
6+
import com.dddheroes.heroesofddd.astrologers.write.proclaimweeksymbol.ProclaimWeekSymbol;
7+
import com.dddheroes.heroesofddd.astrologers.write.proclaimweeksymbol.WeekSymbolProclaimed;
8+
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingEvent;
9+
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingId;
10+
import com.dddheroes.heroesofddd.creaturerecruitment.write.builddwelling.DwellingBuilt;
11+
import com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures.IncreaseAvailableCreatures;
12+
import com.dddheroes.heroesofddd.shared.Amount;
13+
import com.dddheroes.heroesofddd.shared.Cost;
14+
import com.dddheroes.heroesofddd.shared.CreatureId;
15+
import com.dddheroes.heroesofddd.shared.ResourceType;
16+
import org.axonframework.commandhandling.gateway.CommandGateway;
17+
import org.axonframework.eventhandling.DomainEventMessage;
18+
import org.axonframework.eventhandling.GenericDomainEventMessage;
19+
import org.axonframework.eventhandling.gateway.EventGateway;
20+
import org.junit.jupiter.api.*;
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.context.annotation.Import;
24+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
25+
26+
import java.util.UUID;
27+
28+
import static com.dddheroes.heroesofddd.utils.AwaitilityUtils.awaitUntilAsserted;
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.Mockito.*;
31+
32+
@Import(TestcontainersConfiguration.class)
33+
@SpringBootTest
34+
class WhenWeekSymbolProclaimedThenIncreaseDwellingAvailableCreaturesTest {
35+
36+
@Autowired
37+
private EventGateway eventGateway;
38+
39+
@MockitoSpyBean
40+
private CommandGateway commandGateway;
41+
42+
@Test
43+
void test() {
44+
// given
45+
var angelDwellingId1 = givenDwellingBuiltEvent("angel");
46+
var angelDwellingId2 = givenDwellingBuiltEvent("angel");
47+
var titanDwellingId = givenDwellingBuiltEvent("titan");
48+
49+
// when
50+
var astrologersId = AstrologersId.random();
51+
whenAstrologersEvents(
52+
astrologersId.raw(),
53+
new WeekSymbolProclaimed(astrologersId.raw(), 1, 1, "angel", 3)
54+
);
55+
56+
// then
57+
var expectedCommand1 = IncreaseAvailableCreatures.command(angelDwellingId1, "angel", 3);
58+
awaitUntilAsserted(() -> verify(commandGateway, times(1)).sendAndWait(expectedCommand1));
59+
60+
var expectedCommand2 = IncreaseAvailableCreatures.command(angelDwellingId2, "angel", 3);
61+
awaitUntilAsserted(() -> verify(commandGateway, times(1)).sendAndWait(expectedCommand2));
62+
63+
var notExpectedCommand = IncreaseAvailableCreatures.command(titanDwellingId, "titan", 3);
64+
awaitUntilAsserted(() -> verify(commandGateway, never()).sendAndWait(notExpectedCommand));
65+
}
66+
67+
private String givenDwellingBuiltEvent(String creatureId) {
68+
var dwellingId = DwellingId.random();
69+
var costPerTroop = Cost.resources(ResourceType.GOLD, Amount.of(1000));
70+
var event = DwellingBuilt.event(dwellingId, CreatureId.of(creatureId), costPerTroop);
71+
eventGateway.publish(dwellingDomainEvent(dwellingId.raw(), 0, event));
72+
return dwellingId.raw();
73+
}
74+
75+
private void whenAstrologersEvents(String gameId, AstrologersEvent... events) {
76+
for (int i = 0; i < events.length; i++) {
77+
eventGateway.publish(astrologersDomainEvent(gameId, i, events[i]));
78+
}
79+
}
80+
81+
private static DomainEventMessage<?> astrologersDomainEvent(String identifier, int sequenceNumber,
82+
AstrologersEvent payload) {
83+
return givenDomainEvent(
84+
"Astrologers",
85+
identifier,
86+
sequenceNumber,
87+
payload
88+
);
89+
}
90+
91+
private static DomainEventMessage<?> dwellingDomainEvent(
92+
String identifier,
93+
int sequenceNumber,
94+
DwellingEvent payload
95+
) {
96+
return givenDomainEvent(
97+
"Dwelling",
98+
identifier,
99+
sequenceNumber,
100+
payload
101+
);
102+
}
103+
104+
private static DomainEventMessage<?> givenDomainEvent(
105+
String aggregateType,
106+
String identifier,
107+
int sequenceNumber,
108+
Object payload
109+
) {
110+
return new GenericDomainEventMessage<>(
111+
aggregateType,
112+
identifier,
113+
sequenceNumber,
114+
payload
115+
);
116+
}
117+
}

0 commit comments

Comments
 (0)