Skip to content

Commit 0d1675e

Browse files
✨ feat: Armies | Army command model  (#2)
1 parent edf3507 commit 0d1675e

20 files changed

Lines changed: 518 additions & 24 deletions

File tree

.github/workflows/maven.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ jobs:
3535
run: mvn -B package --file pom.xml
3636

3737
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
38-
- name: 📈 Update dependency graph
39-
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
38+
# - name: 📈 Update dependency graph
39+
# uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.dddheroes.heroesofddd.armies.write;
2+
3+
import com.dddheroes.heroesofddd.armies.write.addcreature.AddCreatureToArmy;
4+
import com.dddheroes.heroesofddd.armies.write.addcreature.CreatureAddedToArmy;
5+
import com.dddheroes.heroesofddd.armies.write.addcreature.CanHaveMax7CreatureStacksInArmy;
6+
import com.dddheroes.heroesofddd.armies.write.removecreature.CreatureRemovedFromArmy;
7+
import com.dddheroes.heroesofddd.armies.write.removecreature.CanRemoveOnlyPresentCreatures;
8+
import com.dddheroes.heroesofddd.armies.write.removecreature.RemoveCreatureFromArmy;
9+
import com.dddheroes.heroesofddd.shared.Amount;
10+
import com.dddheroes.heroesofddd.shared.ArmyId;
11+
import com.dddheroes.heroesofddd.shared.CreatureId;
12+
import org.axonframework.commandhandling.CommandHandler;
13+
import org.axonframework.eventsourcing.EventSourcingHandler;
14+
import org.axonframework.modelling.command.AggregateCreationPolicy;
15+
import org.axonframework.modelling.command.AggregateIdentifier;
16+
import org.axonframework.modelling.command.CreationPolicy;
17+
import org.axonframework.spring.stereotype.Aggregate;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
23+
24+
@Aggregate
25+
class Army {
26+
27+
@AggregateIdentifier
28+
private ArmyId armyId;
29+
private final Map<CreatureId, Amount> creatureStacks = new HashMap<>();
30+
31+
@CommandHandler
32+
@CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
33+
void handle(AddCreatureToArmy command) {
34+
new CanHaveMax7CreatureStacksInArmy(command.creatureId(), creatureStacks).verify();
35+
36+
apply(
37+
CreatureAddedToArmy.event(
38+
command.armyId(),
39+
command.creatureId(),
40+
command.quantity()
41+
)
42+
);
43+
}
44+
45+
@EventSourcingHandler
46+
void on(CreatureAddedToArmy event) {
47+
this.armyId = new ArmyId(event.armyId());
48+
creatureStacks.merge(new CreatureId(event.creatureId()), new Amount(event.quantity()), Amount::plus);
49+
}
50+
51+
@CommandHandler
52+
void handle(RemoveCreatureFromArmy command) {
53+
new CanRemoveOnlyPresentCreatures(command.creatureId(), command.quantity(), creatureStacks).verify();
54+
55+
apply(
56+
CreatureRemovedFromArmy.event(
57+
command.armyId(),
58+
command.creatureId(),
59+
command.quantity()
60+
)
61+
);
62+
}
63+
64+
@EventSourcingHandler
65+
void on(CreatureRemovedFromArmy event) {
66+
var creatureId = new CreatureId(event.creatureId());
67+
var currentQuantity = creatureStacks.get(creatureId);
68+
var removedQuantity = new Amount(event.quantity());
69+
if (currentQuantity.equals(removedQuantity)) {
70+
creatureStacks.remove(creatureId);
71+
} else {
72+
creatureStacks.merge(
73+
creatureId,
74+
removedQuantity,
75+
Amount::minus
76+
);
77+
}
78+
}
79+
80+
Army() {
81+
// required by Axon
82+
}
83+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.dddheroes.heroesofddd.armies.write;
2+
3+
import com.dddheroes.heroesofddd.shared.ArmyId;
4+
5+
public interface ArmyCommand {
6+
7+
ArmyId armyId();
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.dddheroes.heroesofddd.armies.write;
2+
3+
public interface ArmyEvent {
4+
5+
String armyId();
6+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.dddheroes.heroesofddd.armies.write.addcreature;
2+
3+
import com.dddheroes.heroesofddd.shared.Amount;
4+
import com.dddheroes.heroesofddd.shared.ArmyId;
5+
import com.dddheroes.heroesofddd.shared.CreatureId;
6+
import org.axonframework.modelling.command.TargetAggregateIdentifier;
7+
8+
public record AddCreatureToArmy(
9+
@TargetAggregateIdentifier
10+
ArmyId armyId,
11+
CreatureId creatureId,
12+
Amount quantity
13+
) {
14+
15+
public AddCreatureToArmy {
16+
if (quantity.raw() <= 0) {
17+
throw new IllegalArgumentException("Quantity must be greater than 0");
18+
}
19+
}
20+
21+
public AddCreatureToArmy command(String armyId, String creatureId, int quantity) {
22+
return new AddCreatureToArmy(new ArmyId(armyId), new CreatureId(creatureId), new Amount(quantity));
23+
}
24+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.dddheroes.heroesofddd.armies.write.addcreature;
2+
3+
import com.dddheroes.heroesofddd.shared.Amount;
4+
import com.dddheroes.heroesofddd.shared.CreatureId;
5+
import com.dddheroes.heroesofddd.shared.DomainRule;
6+
7+
import java.util.Map;
8+
9+
public record CanHaveMax7CreatureStacksInArmy(
10+
CreatureId creatureToAdd,
11+
Map<CreatureId, Amount> creatureStacksBeforeAdd
12+
) implements DomainRule {
13+
14+
private static final int MAX_CREATURE_STACKS = 7;
15+
16+
@Override
17+
public boolean isViolated() {
18+
return creatureStacksBeforeAdd.size() >= MAX_CREATURE_STACKS
19+
&& !creatureStacksBeforeAdd.containsKey(creatureToAdd);
20+
}
21+
22+
@Override
23+
public String message() {
24+
return "Can have max 7 different creature stacks in the army";
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dddheroes.heroesofddd.armies.write.addcreature;
2+
3+
import com.dddheroes.heroesofddd.armies.write.ArmyEvent;
4+
import com.dddheroes.heroesofddd.shared.Amount;
5+
import com.dddheroes.heroesofddd.shared.ArmyId;
6+
import com.dddheroes.heroesofddd.shared.CreatureId;
7+
8+
public record CreatureAddedToArmy(
9+
String armyId,
10+
String creatureId,
11+
Integer quantity
12+
) implements ArmyEvent {
13+
14+
public static CreatureAddedToArmy event(
15+
ArmyId armyId,
16+
CreatureId creatureId,
17+
Amount quantity
18+
) {
19+
return new CreatureAddedToArmy(
20+
armyId.raw(),
21+
creatureId.raw(),
22+
quantity.raw()
23+
);
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dddheroes.heroesofddd.armies.write.removecreature;
2+
3+
import com.dddheroes.heroesofddd.shared.Amount;
4+
import com.dddheroes.heroesofddd.shared.CreatureId;
5+
import com.dddheroes.heroesofddd.shared.DomainRule;
6+
7+
import java.util.Map;
8+
9+
public record CanRemoveOnlyPresentCreatures(
10+
CreatureId creatureToRemove,
11+
Amount quantityToRemove,
12+
Map<CreatureId, Amount> creatureStacksBeforeRemoved
13+
) implements DomainRule {
14+
15+
@Override
16+
public boolean isViolated() {
17+
return creatureStacksBeforeRemoved
18+
.getOrDefault(creatureToRemove, Amount.zero()).raw() < quantityToRemove.raw();
19+
}
20+
21+
@Override
22+
public String message() {
23+
return "Can remove only present creatures";
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dddheroes.heroesofddd.armies.write.removecreature;
2+
3+
import com.dddheroes.heroesofddd.armies.write.ArmyEvent;
4+
import com.dddheroes.heroesofddd.shared.Amount;
5+
import com.dddheroes.heroesofddd.shared.ArmyId;
6+
import com.dddheroes.heroesofddd.shared.CreatureId;
7+
8+
public record CreatureRemovedFromArmy(
9+
String armyId,
10+
String creatureId,
11+
Integer quantity
12+
) implements ArmyEvent {
13+
14+
public static CreatureRemovedFromArmy event(
15+
ArmyId armyId,
16+
CreatureId creatureId,
17+
Amount quantity
18+
) {
19+
return new CreatureRemovedFromArmy(
20+
armyId.raw(),
21+
creatureId.raw(),
22+
quantity.raw()
23+
);
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.dddheroes.heroesofddd.armies.write.removecreature;
2+
3+
import com.dddheroes.heroesofddd.shared.Amount;
4+
import com.dddheroes.heroesofddd.shared.ArmyId;
5+
import com.dddheroes.heroesofddd.shared.CreatureId;
6+
import org.axonframework.modelling.command.TargetAggregateIdentifier;
7+
8+
public record RemoveCreatureFromArmy(
9+
@TargetAggregateIdentifier
10+
ArmyId armyId,
11+
CreatureId creatureId,
12+
Amount quantity
13+
) {
14+
15+
public RemoveCreatureFromArmy {
16+
if (quantity.raw() <= 0) {
17+
throw new IllegalArgumentException("Quantity must be greater than 0");
18+
}
19+
}
20+
21+
public RemoveCreatureFromArmy command(String armyId, String creatureId, int quantity) {
22+
return new RemoveCreatureFromArmy(new ArmyId(armyId), new CreatureId(creatureId), new Amount(quantity));
23+
}
24+
}

0 commit comments

Comments
 (0)