Skip to content

Commit

Permalink
Introduce document_way implementation
Browse files Browse the repository at this point in the history
Intended as a step from rails_way to event-sourced aggregate.
  • Loading branch information
mostlyobvious committed Nov 20, 2023
1 parent 7a183be commit 1fad4b0
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 0 deletions.
13 changes: 13 additions & 0 deletions examples/document_way/.mutant.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
integration:
name: minitest
includes:
- lib
requires:
- project_management
matcher:
subjects:
- ProjectManagement*
ignore:
- ProjectManagement::Issue#apply_on_state
- ProjectManagement::Issue#apply
- ProjectManagement::Test*
11 changes: 11 additions & 0 deletions examples/document_way/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
source "https://rubygems.org"

gem "ruby_event_store"
gem "arkency-command_bus"
gem "minitest"
gem "mutant"
gem "mutant-minitest"
gem "mutant-license",
source: "https://oss:[email protected]"
gem "activerecord"
gem "sqlite3"
81 changes: 81 additions & 0 deletions examples/document_way/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
GEM
remote: https://oss:[email protected]/
specs:
mutant-license (0.1.1.2.1627430819213747598431630701693729869473.6)

GEM
remote: https://rubygems.org/
specs:
activemodel (7.1.1)
activesupport (= 7.1.1)
activerecord (7.1.1)
activemodel (= 7.1.1)
activesupport (= 7.1.1)
timeout (>= 0.4.0)
activesupport (7.1.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
arkency-command_bus (0.4.1)
concurrent-ruby
ast (2.4.2)
base64 (0.2.0)
bigdecimal (3.1.4)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
diff-lcs (1.5.0)
drb (2.2.0)
ruby2_keywords
i18n (1.14.1)
concurrent-ruby (~> 1.0)
minitest (5.20.0)
mutant (0.11.24)
diff-lcs (~> 1.3)
parser (~> 3.2.2, >= 3.2.2.4)
regexp_parser (~> 2.8.2)
sorbet-runtime (~> 0.5.0)
unparser (~> 0.6.9)
mutant-minitest (0.11.24)
minitest (~> 5.11)
mutant (= 0.11.24)
mutex_m (0.2.0)
parser (3.2.2.4)
ast (~> 2.4.1)
racc
racc (1.7.1)
regexp_parser (2.8.2)
ruby2_keywords (0.0.5)
ruby_event_store (2.12.1)
concurrent-ruby (~> 1.0, >= 1.1.6)
sorbet-runtime (0.5.11089)
sqlite3 (1.6.8-arm64-darwin)
sqlite3 (1.6.8-x86_64-linux)
timeout (0.4.1)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unparser (0.6.9)
diff-lcs (~> 1.3)
parser (>= 3.2.2.4)

PLATFORMS
arm64-darwin-22
x86_64-linux

DEPENDENCIES
activerecord
arkency-command_bus
minitest
mutant
mutant-license!
mutant-minitest
ruby_event_store
sqlite3

BUNDLED WITH
2.4.21
10 changes: 10 additions & 0 deletions examples/document_way/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
install:
@bundle install

test:
@bundle exec ruby -Ilib -rproject_management test/issue_test.rb

mutate:
@bundle exec mutant run

.PHONY: install test mutate
19 changes: 19 additions & 0 deletions examples/document_way/lib/project_management.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require "active_record"

require_relative "../../../shared/lib/project_management"
require_relative "project_management/command_handler"
require_relative "project_management/issue"

module ProjectManagement
class Configuration
def call(event_store, command_bus)
handler = CommandHandler.new(event_store)
command_bus.register(CreateIssue, handler.public_method(:create))
command_bus.register(ReopenIssue, handler.public_method(:reopen))
command_bus.register(ResolveIssue, handler.public_method(:resolve))
command_bus.register(CloseIssue, handler.public_method(:close))
command_bus.register(StartIssueProgress, handler.public_method(:start))
command_bus.register(StopIssueProgress, handler.public_method(:stop))
end
end
end
32 changes: 32 additions & 0 deletions examples/document_way/lib/project_management/command_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module ProjectManagement
class CommandHandler
def initialize(event_store)
@event_store = event_store
end

def create(cmd) = with_aggregate(cmd.id) { |issue| issue.open }
def resolve(cmd) = with_aggregate(cmd.id) { |issue| issue.resolve }
def close(cmd) = with_aggregate(cmd.id) { |issue| issue.close }
def reopen(cmd) = with_aggregate(cmd.id) { |issue| issue.reopen }
def start(cmd) = with_aggregate(cmd.id) { |issue| issue.start }
def stop(cmd) = with_aggregate(cmd.id) { |issue| issue.stop }

private

def stream_name(id) = "Issue$#{id}"

def with_transaction(&) = ActiveRecord::Base.transaction(&)

def with_aggregate(id)
repository = Issue::Repository.new(id)
issue = Issue.new(repository.load)

with_transaction do
@event_store.publish(yield(issue), stream_name: stream_name(id))
repository.store(issue.state)
end
rescue Issue::InvalidTransition
raise Error
end
end
end
67 changes: 67 additions & 0 deletions examples/document_way/lib/project_management/issue.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module ProjectManagement
class Issue
State = Data.define(:id, :status)

class Repository
class Record < ActiveRecord::Base
self.table_name = :issues
end
private_constant :Record

def initialize(id) = @id = id

def store(state) = Record.where(uuid: @id).update(status: state.status)

def load
record = Record.find_or_create_by(uuid: @id)
State.new(id: record.uuid, status: record.status)
end
end

InvalidTransition = Class.new(StandardError)

attr_reader :state

def initialize(state)
@state = state
end

def open
fail if @state.status
@state = @state.with(status: "open")
IssueOpened.new(data: { issue_id: @state.id })
end

def resolve
fail unless %w[open in_progress reopened].include? @state.status
@state = @state.with(status: "resolved")
IssueResolved.new(data: { issue_id: @state.id })
end

def close
fail unless %w[open in_progress resolved reopened].include? @state.status
@state = @state.with(status: "closed")
IssueClosed.new(data: { issue_id: @state.id })
end

def reopen
fail unless %w[resolved closed].include? @state.status
@state = @state.with(status: "reopened")
IssueReopened.new(data: { issue_id: @state.id })
end

def start
fail unless %w[open reopened].include? @state.status
@state = @state.with(status: "in_progress")
IssueProgressStarted.new(data: { issue_id: @state.id })
end

def stop
fail unless %w[in_progress].include? @state.status
@state = @state.with(status: "open")
IssueProgressStopped.new(data: { issue_id: @state.id })
end

def fail = raise InvalidTransition
end
end
40 changes: 40 additions & 0 deletions examples/document_way/test/issue_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "minitest/autorun"
require "minitest/mock"
require "mutant/minitest/coverage"
require "arkency/command_bus"
require "ruby_event_store"

require_relative "../lib/project_management"

module ProjectManagement
class IssueTest < Minitest::Test
include Test.with(
command_bus: -> { Arkency::CommandBus.new },
event_store: -> { RubyEventStore::Client.new },
configuration: Configuration.new
)

cover "ProjectManagement::Issue*"

def test_passed_expected_version
skip "this test sucks"
end

def setup
ActiveRecord::Base.establish_connection(
adapter: "sqlite3",
database: ":memory:"
)

ActiveRecord::Schema.verbose = false
ActiveRecord::Schema.define do
create_table :issues, force: true do |t|
t.string :uuid
t.string :status
end

add_index :issues, :uuid, unique: true
end
end
end
end

0 comments on commit 1fad4b0

Please sign in to comment.