Skip to content

Commit

Permalink
✨ feat: Calendar | write slices: StartDay and FinishDay (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
MateuszNaKodach authored Sep 26, 2024
1 parent f59b82a commit 9041591
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 1 deletion.
28 changes: 28 additions & 0 deletions heroesofddd_rails_application/lib/heroes/calendar/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "building_blocks/infrastructure/event_sourcing_application_service"
require_relative "write/start_day/command_start_day"
require_relative "write/finish_day/command_finish_day"
require_relative "write/calendar"

module Heroes
module Calendar
class Configuration
def call(event_store, command_bus, event_registry)
application_service = ::BuildingBlocks::Infrastructure::EventSourcingApplicationService.new(
Calendar,
event_store,
event_registry
) { |_, metadata| "Game::$#{metadata[:game_id]}::Calendar" }

command_bus.register(
StartDay,
StartDayCommandHandler.new(application_service, event_registry)
)

command_bus.register(
FinishDay,
FinishDayCommandHandler.new(application_service, event_registry)
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Heroes
module Calendar
module Calendar
State = Data.define(:current_day, :current_week, :current_month)

class << self
def decide(command, state)
case command
when StartDay
handle_start_day(command, state)
when FinishDay
handle_finish_day(command, state)
else
raise "Unknown command"
end
end

def evolve(state, event)
case event
when DayStarted
State.new(
current_day: event.day,
current_week: event.week,
current_month: event.month
)
when DayFinished
state
else
raise "Unknown event"
end
end

def initial_state
State.new(current_day: 0, current_week: 0, current_month: 0)
end

private

def handle_start_day(command, state)
validate_start_day(command, state)
[ DayStarted.new(month: command.month, week: command.week, day: command.day) ]
end

def handle_finish_day(command, state)
validate_finish_day(command, state)
[ DayFinished.new(month: command.month, week: command.week, day: command.day) ]
end

def validate_start_day(command, state)
raise CannotSkipDays unless next_day?(command, state)
end

def validate_finish_day(command, state)
raise CanOnlyFinishCurrentDay unless current_day?(command, state)
end

def next_day?(command, state)
return true if state.current_day == 0 # First day

next_day = state.current_day + 1
next_week = state.current_week
next_month = state.current_month

if next_day > 7
next_day = 1
next_week += 1
end

if next_week > 4
next_week = 1
next_month += 1
end

command.day == next_day && command.week == next_week && command.month == next_month
end

def current_day?(command, state)
command.day == state.current_day &&
command.week == state.current_week &&
command.month == state.current_month
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require_relative "../calendar"
require_relative "./event_day_finished"

module Heroes
module Calendar
FinishDay = Data.define(:month, :week, :day)

class FinishDayCommandHandler
def initialize(application_service, event_registry)
@application_service = application_service
event_registry.map_event_type(
Heroes::Calendar::DayFinished,
EventStore::Heroes::Calendar::DayFinished,
EventStore::Heroes::Calendar::DayFinished.method(:from_domain),
EventStore::Heroes::Calendar::DayFinished.method(:to_domain)
)
end

def call(command)
@application_service.call(command)
end
end
end
end

module EventStore
module Heroes
module Calendar
DayFinished = Class.new(RubyEventStore::Event) do
def self.from_domain(domain_event)
new(data: {
month: domain_event.month,
week: domain_event.week,
day: domain_event.day
})
end

def self.to_domain(store_event)
data = store_event.data.deep_symbolize_keys
::Heroes::Calendar::DayFinished.new(
month: data[:month],
week: data[:week],
day: data[:day]
)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Heroes
module Calendar
DayFinished = Data.define(:month, :week, :day)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Heroes
module Calendar
CanOnlyFinishCurrentDay = Class.new(StandardError)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require_relative "../calendar"
require_relative "./event_day_started"

module Heroes
module Calendar
StartDay = Data.define(:month, :week, :day)

class StartDayCommandHandler
def initialize(application_service, event_registry)
@application_service = application_service
event_registry.map_event_type(
Heroes::Calendar::DayStarted,
EventStore::Heroes::Calendar::DayStarted,
EventStore::Heroes::Calendar::DayStarted.method(:from_domain),
EventStore::Heroes::Calendar::DayStarted.method(:to_domain)
)
end

def call(command)
@application_service.call(command)
end
end
end
end

module EventStore
module Heroes
module Calendar
DayStarted = Class.new(RubyEventStore::Event) do
def self.from_domain(domain_event)
new(data: {
month: domain_event.month,
week: domain_event.week,
day: domain_event.day
})
end

def self.to_domain(store_event)
data = store_event.data.deep_symbolize_keys
::Heroes::Calendar::DayStarted.new(
month: data[:month],
week: data[:week],
day: data[:day]
)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Heroes
module Calendar
DayStarted = Data.define(:month, :week, :day)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Heroes
module Calendar
CannotSkipDays = Class.new(StandardError)
end
end
4 changes: 3 additions & 1 deletion heroesofddd_rails_application/lib/heroes/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "heroes/creature_recruitment/configuration"
require "heroes/astrologers/configuration"
require "heroes/calendar/configuration"

module Heroes
class Configuration
Expand All @@ -10,7 +11,8 @@ def call(event_store, command_bus, query_bus, event_mapper)
def configure_modules(event_store, command_bus, event_mapper)
[
Heroes::CreatureRecruitment::Configuration.new,
Heroes::Astrologers::Configuration.new
Heroes::Astrologers::Configuration.new,
Heroes::Calendar::Configuration.new
].each { |c| c.call(event_store, command_bus, event_mapper) }
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require "real_event_store_integration_test_case"
require "heroes/calendar/write/start_day/command_start_day"
require "heroes/calendar/write/finish_day/rule_can_only_finish_the_current_day"
require "heroes/calendar/write/finish_day/command_finish_day"
require "building_blocks/application/app_context"

module Heroes
module Calendar
class FinishDayApplicationTest < RealEventStoreIntegrationTestCase
def setup
super
@game_id = SecureRandom.uuid
@stream_name = "Game::$#{@game_id}::Calendar"
@app_context = ::BuildingBlocks::Application::AppContext.for_game(@game_id)
end

def test_given_day_started_when_finish_current_day_then_day_finished
# given
given_domain_event(@stream_name, DayStarted.new(month: 1, week: 1, day: 1))

# when
finish_day = FinishDay.new(month: 1, week: 1, day: 1)
execute_command(finish_day)

# then
expected_event = DayFinished.new(month: 1, week: 1, day: 1)
then_domain_event(@stream_name, expected_event)
end

def test_given_day_started_when_finish_non_current_day_then_fails
# given
given_domain_event(@stream_name, DayStarted.new(month: 1, week: 1, day: 1))

# when/then
finish_day = FinishDay.new(month: 1, week: 1, day: 2)
assert_raises(CanOnlyFinishCurrentDay) do
execute_command(finish_day)
end
end

def default_app_context
@app_context
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
require "real_event_store_integration_test_case"
require "heroes/calendar/write/start_day/command_start_day"
require "heroes/calendar/write/start_day/rule_cannot_skip_days"
require "heroes/calendar/write/start_day/event_day_started"
require "heroes/calendar/write/finish_day/command_finish_day"
require "heroes/calendar/write/finish_day/event_day_finished"
require "building_blocks/application/app_context"

module Heroes
module Calendar
class StartDayApplicationTest < RealEventStoreIntegrationTestCase
def setup
super
@game_id = SecureRandom.uuid
@stream_name = "Game::$#{@game_id}::Calendar"
@app_context = ::BuildingBlocks::Application::AppContext.for_game(@game_id)
end

def test_given_no_previous_day_when_start_day_then_day_started
# when
start_day = StartDay.new(month: 1, week: 1, day: 1)
execute_command(start_day)

# then
expected_event = DayStarted.new(month: 1, week: 1, day: 1)
then_domain_event(@stream_name, expected_event)
end

def test_given_previous_day_finished_when_start_next_day_then_next_day_started
# given
given_domain_event(@stream_name, DayStarted.new(month: 1, week: 1, day: 1))
given_domain_event(@stream_name, DayFinished.new(month: 1, week: 1, day: 1))

# when
start_day = StartDay.new(month: 1, week: 1, day: 2)
execute_command(start_day)

# then
expected_event = DayStarted.new(month: 1, week: 1, day: 2)
then_domain_event(@stream_name, expected_event)
end

def test_given_previous_day_finished_when_start_day_skipping_one_then_fails
# given
given_domain_event(@stream_name, DayStarted.new(month: 1, week: 1, day: 1))
given_domain_event(@stream_name, DayFinished.new(month: 1, week: 1, day: 1))

# when/then
start_day = StartDay.new(month: 1, week: 1, day: 3)
assert_raises(CannotSkipDays) do
execute_command(start_day)
end
end

def test_given_last_day_of_week_finished_when_start_first_day_of_next_week_then_new_week_started
# given
(1..7).each do |day|
given_domain_event(@stream_name, DayStarted.new(month: 1, week: 1, day: day))
given_domain_event(@stream_name, DayFinished.new(month: 1, week: 1, day: day))
end

# when
start_day = StartDay.new(month: 1, week: 2, day: 1)
execute_command(start_day)

# then
expected_event = DayStarted.new(month: 1, week: 2, day: 1)
then_domain_event(@stream_name, expected_event)
end

def test_given_last_day_of_month_finished_when_start_first_day_of_next_month_then_new_month_started
# given
(1..4).each do |week|
(1..7).each do |day|
given_domain_event(@stream_name, DayStarted.new(month: 1, week: week, day: day))
given_domain_event(@stream_name, DayFinished.new(month: 1, week: week, day: day))
end
end

# when
start_day = StartDay.new(month: 2, week: 1, day: 1)
execute_command(start_day)

# then
expected_event = DayStarted.new(month: 2, week: 1, day: 1)
then_domain_event(@stream_name, expected_event)
end

def default_app_context
@app_context
end
end
end
end

0 comments on commit 9041591

Please sign in to comment.