Shows how to use Domain-Driven Design, Event Storming, Event Modeling and Event Sourcing in Heroes of Might & Magic III domain.
👉 See also implementations in: Ruby | Java + Spring + Axon
👉 Let's explore the Heroes of Domain-Driven Design blogpost series
- There you will get familiar with the whole Software Development process: from knowledge crunching with domain experts, designing solution using Event Modeling, to implementation using DDD Building Blocks.
This project probably won't be fully-functional HOMM3 engine implementation, because it's done for educational purposes. If you'd like to talk with me about mentioned development practices fell free to contact on linkedin.com/in/mateusznakodach/.
I'm focused on domain modeling on the backend, but I'm going to implement UI like below in the future.
- Install Java 23 on your machine
./mvnw install -DskipTests
docker compose up
./mvnw spring-boot:run
or./mvnw test
Modules (mostly designed using Bounded Context heuristic) are designed and documented on EventModeling below. Each slice in a module is in certain color which shows the progress:
- green -> completed
- yellow -> implementation in progress
- red -> to do
- grey -> design in progress
List of modules you can see in package com.dddheroes.heroesofddd
.
heroesofddd/
├── armies
├── astrologers
├── calendar
├── creature_recruitment
Each domain-focused module follows Vertical-Slice Architecture of three possible types: write, read and automation following Event Modeling nomenclature.
Slices:
- Write: BuildDwelling -> DwellingBuilt | test
- Write: IncreaseAvailableCreatures -> AvailableCreaturesChanged | test
- Write: RecruitCreature -> CreatureRecruited | test
- Read: (DwellingBuilt, AvailableCreaturesChanged, CreatureRecruited) -> DwellingReadModel projector
- Automation: WhenCreatureRecruitedThenAddToArmy | test
Aggregates:
Slices:
- Write: ProclaimWeekSymbol -> WeekSymbolProclaimed | test
- Automation: DayStarted(where day==1) -> ProclaimWeekSymbol | test
- Automation: (WeekSymbolProclaimed, all game dwellings derived from DwellingBuilt events) -> IncreaseAvailableCreatures for each dwelling in the game where creature == symbol | test
Aggregates:
Slices:
- Write: StartDay -> DayStarted | test
- Write: FinishDay -> DayFinished | test
- Read: [DayStarted -> CurrentDateReadModel] -- todo
Aggregates:
Slices:
- Write: AddCreatureToArmy -> CreatureAddedToArmy | test
- Write: RemoveCreatureFromArmy -> CreatureRemovedFromArmy | test
Aggregates:
The project follows a Screaming Architecture pattern organized around vertical slices that mirror Event Modeling concepts.
The package structure screams the capabilities of the system by making explicit: commands available to users, events that capture what happened, queries for retrieving information, business rules, and system automations. This architecture makes it immediately obvious what the system can do, what rules govern those actions, and how different parts of the system interact through events.
Each module is structured into three distinct types of slices (packages write
, read
, automation
) and there are events (package events
) between them, which are a system backbone - a contract between all other parts:
Contains commands that represent user intentions, defines business rules through aggregates, produces domain events, and enforces invariants (e.g., RecruitCreature command → CreatureRecruited event, with RecruitCreaturesNotExceedAvailableCreatures rule).
Implements queries and read models optimized for specific use cases, with projectors that transform events into queryable state (e.g., GetDwellingById query → DwellingReadModel).
Processes events to trigger subsequent actions, implementing system policies and workflows that connect different modules (e.g., WhenCreatureRecruitedThenAddToArmyProcessor).
Tests using Real postgres Event Store, follows the approach:
- write slice: given(events) -> when(command) -> then(events)
- read slice: given(events) -> then(read model)
- automation: when(event, state?) -> then(command)
Tests are focused on observable behavior which implicitly covers the DDD Aggregates, so the domain model can be refactored without changes in tests.
@BeforeEach
void setUp() { // Axon Framework Test Fixture
fixture = new AggregateTestFixture<>(Dwelling.class);
}
@Test
void givenDwellingWith2Creatures_WhenRecruit2Creatures_ThenRecruited() {
// given
var givenEvents = List.of(
dwellingBuilt(),
availableCreaturesChanged(2)
);
// when
var whenCommand = recruitCreature(2);
// then
var thenEvent = creatureRecruited(2);
fixture.given(givenEvents)
.when(whenCommand)
.expectEvents(thenEvent);
}
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/.