Skip to content

Shows how to use Domain-Driven Design, Event Storming, Event Modeling and Event Sourcing in Heroes of Might & Magic III domain.

Notifications You must be signed in to change notification settings

MateuszNaKodach/HeroesOfDomainDrivenDesign.EventSourcing.Ruby

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

62 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Heroes of Domain-Driven Design (Ruby)

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've also played around with Rails app frontend using Hotwire.

Heroes3_CreatureRecruitment_ExampleGif

πŸš€ How to run the project locally?

  1. cd heroesofddd_rails_application
  2. docker compose up
  3. bundle install
  4. rails db:drop db:create db:migrate db:seed - (re)creates database and seed with example data
  5. rails server

Example use case:

🧱 Modules

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 lib/heroes directory of the Rails application.

heroes/
β”œβ”€β”€ astrologers
β”œβ”€β”€ calendar
β”œβ”€β”€ creature_recruitment

Each domain-focused module follows Vertical-Slice Architecture of three possible types: write, read and automation following Event Modeling nomenclature. Aggregates are implemented using Decider pattern.

πŸ‘Ύ Creature Recruitment

EventModeling_Module_CreatureRecruitment.png

Slices:

Aggregates:

πŸ§™ Astrologers

EventModeling_Module_Astrologers.png

Slices:

Aggregates:

πŸ“… Calendar

EventModeling_Module_Calendar.png

Slices:

Aggregates:

πŸ€– Working with AI Large Language Models:

If you'd like to use the whole source code as your prompt context generate codebase file by: npx ai-digest --whitespace-removal

Domain Model purity

Domain Events are decoupled from infrastructure RailsEventStore events. Every domain event is registered with corresponding functions which maps from domain to storage structure and vice-versa (as shown below).

WeekSymbolProclaimed = Class.new(RubyEventStore::Event) do
    def self.from_domain(domain_event)
      ::EventStore::Heroes::Astrologers::WeekSymbolProclaimed.new(
        data: {
          month: domain_event.month,
          week: domain_event.week,
          week_of: domain_event.week_of,
          growth: domain_event.growth
        }
      )
    end
    
    def self.to_domain(store_event)
      data = store_event.data.deep_symbolize_keys
      ::Heroes::Astrologers::WeekSymbolProclaimed.new(
        month: data[:month],
        week: data[:week],
        week_of: data[:week_of],
        growth: data[:growth],
      )
    end
end

πŸ›οΈ Screaming Architecture

The project follows a Screaming Architecture pattern organized around vertical slices that mirror Event Modeling concepts.

ScreamingArchitecture

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:

Write Slices

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).

Read Slices

Implements queries and read models optimized for specific use cases, with projectors that transform events into queryable state (e.g., GetDwellingById query β†’ DwellingReadModel).

Automation Slices

Processes events to trigger subsequent actions, implementing system policies and workflows that connect different modules (e.g., WhenCreatureRecruitedThenAddToArmyProcessor).

πŸ§ͺ Testing

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.

Example: write slice

EventModeling_GWT_TestCase_CreatureRecruitment.png

def test_given_dwelling_with_3_creature_when_recruit_2_creature_then_success
  # given
  given_domain_event(@stream_name, DwellingBuilt.new(@dwelling_id, @creature_id, @cost_per_troop))
  given_domain_event(@stream_name, AvailableCreaturesChanged.new(@dwelling_id, @creature_id, 3))

  # when
  recruit_creature = RecruitCreature.new(@dwelling_id, @creature_id, 2)
  execute_command(recruit_creature, @app_context)

  # then
  expected_cost = Heroes::SharedKernel::Resources::Cost.resources([ :GOLD, 6000 ], [ :GEM, 2 ])
  expected_event = CreatureRecruited.new(@dwelling_id, @creature_id, 2, expected_cost)
  then_domain_event(@stream_name, expected_event)
end

πŸ’Ό Hire me

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/.