Skip to content
Merged
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,4 @@ void givenDwellingWith2Creatures_WhenRecruit2Creatures_ThenRecruited() {

If you'd like to hire me for Domain-Driven Design and/or Event Sourcing projects I'm available to work with:
Kotlin, Java, C# .NET, Ruby and JavaScript/TypeScript (Node.js or React).
Please reach me out on LinkedIn [linkedin.com/in/mateusznakodach/](https://www.linkedin.com/in/mateusznakodach/).
Please reach me out on LinkedIn [linkedin.com/in/mateusznakodach/](https://www.linkedin.com/in/mateusznakodach/).
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package com.dddheroes.heroesofddd.creaturerecruitment;

import com.dddheroes.heroesofddd.creaturerecruitment.write.DwellingId;
import com.dddheroes.heroesofddd.creaturerecruitment.write.recruitcreature.RecruitCreature;
import com.dddheroes.heroesofddd.resourcespool.application.CommandCostResolver;
import com.dddheroes.heroesofddd.shared.domain.valueobjects.Resources;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.axonframework.eventsourcing.EventCountSnapshotTriggerDefinition;
import org.axonframework.eventsourcing.SnapshotTriggerDefinition;
import org.axonframework.eventsourcing.Snapshotter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
class CreatureRecruitmentConfiguration {

Expand All @@ -23,4 +34,33 @@ public Class<? extends RecruitCreature> supportedCommandType() {
}
};
}

@Bean
SnapshotTriggerDefinition dwellingSnapshotTrigger(Snapshotter snapshotter) {
return new EventCountSnapshotTriggerDefinition(snapshotter, 5);
}

@Bean
public Module dwellingIdSerializationModule() {
return new DwellingIdSerializationModule();
}

private static class DwellingIdSerializationModule extends SimpleModule {

public DwellingIdSerializationModule() {
addSerializer(DwellingId.class, new JsonSerializer<>() {
@Override
public void serialize(DwellingId value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeString(value.raw());
}
});
addDeserializer(DwellingId.class, new JsonDeserializer<>() {
@Override
public DwellingId deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new DwellingId(p.getValueAsString());
}
});
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.modelling.command.CreationPolicy;
import org.axonframework.spring.stereotype.Aggregate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

@Aggregate
@Aggregate(snapshotTriggerDefinition = "dwellingSnapshotTrigger")
public class Dwelling {

private static final Logger logger = LoggerFactory.getLogger(Dwelling.class);

@AggregateIdentifier
private DwellingId dwellingId;
private CreatureId creatureId;
private Resources costPerTroop;
private Amount availableCreatures;
public DwellingId dwellingId; // needs to be public for snapshotting
public CreatureId creatureId;
public Resources costPerTroop;
public Amount availableCreatures;

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

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

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

@CommandHandler
// @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
void decide(RecruitCreature command) {
// if(dwellingId == null){
// throw new DomainRule.ViolatedException("Only not built building can be build");
// }
new RecruitCreaturesNotExceedAvailableCreatures(
creatureId,
availableCreatures,
Expand All @@ -104,11 +107,16 @@ void decide(RecruitCreature command) {

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

Dwelling() {
logger.info("\uD83D\uDC80 Dwelling non-args constructor");
// required by Axon
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.dddheroes.heroesofddd.shared.infrastructure;

import com.dddheroes.heroesofddd.shared.domain.identifiers.ArmyId;
import com.dddheroes.heroesofddd.shared.domain.identifiers.CreatureId;
import com.dddheroes.heroesofddd.shared.domain.identifiers.GameId;
import com.dddheroes.heroesofddd.shared.domain.identifiers.PlayerId;
import com.dddheroes.heroesofddd.shared.domain.valueobjects.Amount;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class SerializationConfiguration {

@Bean
public Module gameIdSerializationModule() {
return new GameIdSerializationModule();
}

@Bean
public Module playerIdSerializationModule() {
return new PlayerIdSerializationModule();
}

@Bean
public Module creatureIdSerializationModule() {
return new CreatureIdSerializationModule();
}

@Bean
public Module armyIdSerializationModule() {
return new ArmyIdSerializationModule();
}

@Bean
public Module amountSerializationModule() {
return new AmountSerializationModule();
}

private static class GameIdSerializationModule extends SimpleModule {

public GameIdSerializationModule() {
addSerializer(GameId.class, new JsonSerializer<>() {
@Override
public void serialize(GameId value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeString(value.raw());
}
});
addDeserializer(GameId.class, new JsonDeserializer<>() {
@Override
public GameId deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new GameId(p.getValueAsString());
}
});
}

}

private static class PlayerIdSerializationModule extends SimpleModule {

public PlayerIdSerializationModule() {
addSerializer(PlayerId.class, new JsonSerializer<>() {
@Override
public void serialize(PlayerId value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeString(value.raw());
}
});
addDeserializer(PlayerId.class, new JsonDeserializer<>() {
@Override
public PlayerId deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new PlayerId(p.getValueAsString());
}
});
}
}

private static class CreatureIdSerializationModule extends SimpleModule {

public CreatureIdSerializationModule() {
addSerializer(CreatureId.class, new JsonSerializer<>() {
@Override
public void serialize(CreatureId value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeString(value.raw());
}
});
addDeserializer(CreatureId.class, new JsonDeserializer<>() {
@Override
public CreatureId deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new CreatureId(p.getValueAsString());
}
});
}

}

private static class ArmyIdSerializationModule extends SimpleModule {

public ArmyIdSerializationModule() {
addSerializer(ArmyId.class, new JsonSerializer<>() {
@Override
public void serialize(ArmyId value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeString(value.raw());
}
});
addDeserializer(ArmyId.class, new JsonDeserializer<>() {
@Override
public ArmyId deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new ArmyId(p.getValueAsString());
}
});
}

}

private static class AmountSerializationModule extends SimpleModule {

public AmountSerializationModule() {
addSerializer(Amount.class, new JsonSerializer<>() {
@Override
public void serialize(Amount value, JsonGenerator gen, SerializerProvider __) throws IOException {
gen.writeNumber(value.raw());
}
});
addDeserializer(Amount.class, new JsonDeserializer<>() {
@Override
public Amount deserialize(JsonParser p, DeserializationContext __) throws IOException {
return new Amount(p.getValueAsInt());
}
});
}

}
}
11 changes: 11 additions & 0 deletions src/main/resources/application-jpa-eventstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
axon:
axonserver:
enabled: false
# eventhandling:
# processors:
# (Optional) You can override processor settings here if needed
# eventstore:
# No need to specify storage-engine if axon-server is disabled and JPA is on classpath
# Axon will auto-configure JpaEventStorageEngine

# Inherit datasource and JPA config from application.yaml
6 changes: 4 additions & 2 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ spring:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
axon:
serializer:
general: jackson
axonserver:
enabled: true
serializer:
general: jackson
events: jackson
messages: jackson
eventhandling:
processors:
Automation_WhenCreatureRecruitedThenAddToArmy_Processor:
Expand Down