Skip to content

Instantly share code, notes, and snippets.

@jandudulski
Last active April 12, 2025 16:41
Show Gist options
  • Save jandudulski/5679338a3c277f9c7ddd3861aa4ae754 to your computer and use it in GitHub Desktop.
Save jandudulski/5679338a3c277f9c7ddd3861aa4ae754 to your computer and use it in GitHub Desktop.
Decider lighting talk
# frozen_string_literal: true
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "debug"
gem "sqlite3"
gem "sequel"
gem "decide.rb", "~> 0.5.1", require: "decider"
end
DB = Sequel.sqlite
DB.create_table :issues do
primary_key :id
String :state, null: false
end
Issue = Data.define(:id, :state) do
def to_record
{ id: id, state: state.to_s }
end
def self.from_record(record)
new(id: record[:id], state: record[:state].to_sym)
end
end
OpenIssue = Data.define(:id)
ResolveIssue = Data.define
IssueOpened = Data.define(:id)
IssueResolved = Data.define
IssueDecider = Decider.define do
initial_state :none
decide OpenIssue, :none do
emit IssueOpened.new(id: command.id)
end
decide proc { [command, state] in [ResolveIssue, Issue(state: :open)] } do
emit IssueResolved.new
end
evolve IssueOpened do
Issue.new(id: event.id, state: :open)
end
evolve IssueResolved do
state.with(state: :resolved)
end
end
decider = IssueDecider.dimap_on_state(
fl: ->(record) {
case record
when nil
:none
else
Issue.from_record(record)
end
},
fr: ->(state) {
case state
in :none
nil
else
state.to_record
end
}
)
binding.irb
# open issue
state = decider.initial_state
events = decider.decide(OpenIssue.new(id: 1), state)
state = events.reduce(state) { |s, e| decider.evolve(s, e) }
DB[:issues].insert(state)
binding.irb
# update issue
state = DB[:issues].find(1).first
events = decider.decide(ResolveIssue.new, state)
state = events.reduce(state, &decider.method(:evolve))
DB[:issues].update(state)
# event sourcing
class DomainEvent
def self.inherited(base)
klass = Class.new(RubyEventStore::Event) do
def to_domain_event
base.new(**data)
end
end
const_set("InfraEvent", klass)
end
def to_infra_event
InfraEvent.new(data: to_h)
end
end
decider = decider.dimap_on_event(
fl: ->(infra_event) { infra_event.to_domain_event },
fr: ->(domain_event) { domain_event.to_infra_event }
)
events = event_store.stream("issues$1").read
version = events.size
state = events.reduce(decider.initial_state, &decider.method(:evolve))
events = decider.decide(ResolveIssue.new, state)
event_store.publish(events, stream_name: "issues$1", expected_version: version)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment