Skip to content

Commit 6262dc2

Browse files
✨ feat: Creature Recruitment | automation: CreatureRecruited -> AddCreatureToArmy (#5)
1 parent ef1831c commit 6262dc2

7 files changed

Lines changed: 144 additions & 9 deletions

File tree

src/main/java/com/dddheroes/heroesofddd/armies/write/addcreature/AddCreatureToArmy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public record AddCreatureToArmy(
1818
}
1919
}
2020

21-
public AddCreatureToArmy command(String armyId, String creatureId, int quantity) {
21+
public static AddCreatureToArmy command(String armyId, String creatureId, int quantity) {
2222
return new AddCreatureToArmy(new ArmyId(armyId), new CreatureId(creatureId), new Amount(quantity));
2323
}
2424
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.automation;
2+
3+
import com.dddheroes.heroesofddd.armies.write.addcreature.AddCreatureToArmy;
4+
import com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature.CreatureRecruited;
5+
import org.axonframework.commandhandling.gateway.CommandGateway;
6+
import org.axonframework.config.ProcessingGroup;
7+
import org.axonframework.eventhandling.DisallowReplay;
8+
import org.axonframework.eventhandling.EventHandler;
9+
import org.springframework.stereotype.Component;
10+
11+
@ProcessingGroup("Automation_WhenCreatureRecruitedThenAddToArmy_Processor")
12+
@DisallowReplay
13+
@Component
14+
class WhenCreatureRecruitedThenAddToArmyProcessor {
15+
16+
private final CommandGateway commandGateway;
17+
18+
WhenCreatureRecruitedThenAddToArmyProcessor(CommandGateway commandGateway) {
19+
this.commandGateway = commandGateway;
20+
}
21+
22+
@EventHandler
23+
void on(CreatureRecruited event) {
24+
var command = AddCreatureToArmy.command(
25+
event.toArmy(),
26+
event.creatureId(),
27+
event.quantity()
28+
);
29+
30+
commandGateway.sendAndWait(command);
31+
}
32+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import com.dddheroes.heroesofddd.creaturerecruitment.write.builddwelling.DwellingBuilt;
44
import com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures.AvailableCreaturesChanged;
55
import com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature.CreatureRecruited;
6+
import org.axonframework.config.ProcessingGroup;
67
import org.axonframework.eventhandling.EventHandler;
78
import org.springframework.stereotype.Component;
89

10+
@ProcessingGroup("ReadModel_Dwelling")
911
@Component
1012
class DwellingReadModelProjector {
1113

src/main/resources/application.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ axon:
2121
general: jackson
2222
axonserver:
2323
enabled: true
24+
eventhandling:
25+
processors:
26+
Automation_WhenCreatureRecruitedThenAddToArmy_Processor:
27+
mode: pooled
28+
dlq:
29+
enabled: true
30+
ReadModel_Dwelling:
31+
mode: pooled
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.dddheroes.heroesofddd.creaturerecruitment.automation;
2+
3+
import com.dddheroes.heroesofddd.TestcontainersConfiguration;
4+
import com.dddheroes.heroesofddd.armies.write.addcreature.AddCreatureToArmy;
5+
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingEvent;
6+
import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingId;
7+
import com.dddheroes.heroesofddd.creaturerecruitment.write.builddwelling.DwellingBuilt;
8+
import com.dddheroes.heroesofddd.creaturerecruitment.write.changeavailablecreatures.AvailableCreaturesChanged;
9+
import com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature.CreatureRecruited;
10+
import com.dddheroes.heroesofddd.shared.ArmyId;
11+
import com.dddheroes.heroesofddd.shared.CreatureIds;
12+
import com.dddheroes.heroesofddd.shared.ResourceType;
13+
import org.axonframework.commandhandling.gateway.CommandGateway;
14+
import org.axonframework.eventhandling.DomainEventMessage;
15+
import org.axonframework.eventhandling.GenericDomainEventMessage;
16+
import org.axonframework.eventhandling.gateway.EventGateway;
17+
import org.junit.jupiter.api.*;
18+
import org.springframework.beans.factory.annotation.Autowired;
19+
import org.springframework.boot.test.context.SpringBootTest;
20+
import org.springframework.context.annotation.Import;
21+
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
22+
23+
import java.util.Map;
24+
25+
import static com.dddheroes.heroesofddd.utils.AwaitilityUtils.awaitUntilAsserted;
26+
import static org.mockito.Mockito.*;
27+
28+
@Import(TestcontainersConfiguration.class)
29+
@SpringBootTest
30+
class WhenCreatureRecruitedThenAddToArmyTest {
31+
32+
private static final Map<String, Integer> PHOENIX_COST = Map.of(
33+
ResourceType.GOLD.name(), 2000,
34+
ResourceType.MERCURY.name(), 1
35+
);
36+
37+
@Autowired
38+
private EventGateway eventGateway;
39+
40+
@MockitoSpyBean
41+
private CommandGateway commandGateway;
42+
43+
@Test
44+
void whenCreatureRecruited_ThenAddCreatureToArmy() {
45+
// given
46+
var dwellingId = DwellingId.random().raw();
47+
var creatureId = CreatureIds.phoenix().raw();
48+
var armyId = ArmyId.random().raw();
49+
givenDwellingEvents(
50+
dwellingId,
51+
new DwellingBuilt(dwellingId, creatureId, PHOENIX_COST),
52+
new AvailableCreaturesChanged(dwellingId, creatureId, 3),
53+
new CreatureRecruited(dwellingId, creatureId, armyId, 1, PHOENIX_COST)
54+
);
55+
56+
// when
57+
// processed by the automation
58+
59+
// then
60+
awaitUntilAsserted(() -> verify(commandGateway, times(1))
61+
.sendAndWait(AddCreatureToArmy.command(armyId, creatureId, 1))
62+
);
63+
}
64+
65+
private void givenDwellingEvents(String dwellingId, DwellingEvent... events) {
66+
for (int i = 0; i < events.length; i++) {
67+
eventGateway.publish(dwellingDomainEvent(dwellingId, i, events[i]));
68+
}
69+
}
70+
71+
private DomainEventMessage<?> dwellingDomainEvent(String dwellingId, int sequenceNumber, DwellingEvent payload) {
72+
return new GenericDomainEventMessage<>(
73+
"Dwelling",
74+
dwellingId,
75+
sequenceNumber,
76+
payload
77+
);
78+
}
79+
}

src/test/java/com/dddheroes/heroesofddd/creaturerecruitment/read/getdwellingbyid/GetDwellingByIdTest.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.dddheroes.heroesofddd.shared.ArmyId;
1111
import com.dddheroes.heroesofddd.shared.CreatureIds;
1212
import com.dddheroes.heroesofddd.shared.ResourceType;
13+
import com.dddheroes.heroesofddd.utils.AwaitilityUtils;
1314
import org.awaitility.Awaitility;
1415
import org.axonframework.eventhandling.DomainEventMessage;
1516
import org.axonframework.eventhandling.GenericDomainEventMessage;
@@ -24,13 +25,14 @@
2425
import java.util.HashMap;
2526
import java.util.Map;
2627

28+
import static com.dddheroes.heroesofddd.utils.AwaitilityUtils.*;
2729
import static org.assertj.core.api.Assertions.*;
2830

2931
@Import(TestcontainersConfiguration.class)
3032
@SpringBootTest
3133
class GetDwellingByIdTest {
3234

33-
public static final Map<String, Integer> PHOENIX_COST = Map.of(
35+
private static final Map<String, Integer> PHOENIX_COST = Map.of(
3436
ResourceType.GOLD.name(), 2000,
3537
ResourceType.MERCURY.name(), 1
3638
);
@@ -150,11 +152,4 @@ private DomainEventMessage<?> dwellingDomainEvent(String dwellingId, int sequenc
150152
payload
151153
);
152154
}
153-
154-
private void awaitUntilAsserted(Runnable assertion) {
155-
Awaitility.await()
156-
.pollInSameThread()
157-
.atMost(Duration.ofSeconds(5))
158-
.untilAsserted(assertion::run);
159-
}
160155
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.dddheroes.heroesofddd.utils;
2+
3+
import org.awaitility.Awaitility;
4+
5+
import java.time.Duration;
6+
7+
public class AwaitilityUtils {
8+
9+
public static void awaitUntilAsserted(Runnable assertion) {
10+
Awaitility.await()
11+
.pollInSameThread()
12+
.atMost(Duration.ofSeconds(5))
13+
.untilAsserted(assertion::run);
14+
}
15+
16+
private AwaitilityUtils() {
17+
throw new UnsupportedOperationException("Utility class");
18+
}
19+
}

0 commit comments

Comments
 (0)