Last active
April 12, 2025 16:41
-
-
Save jandudulski/5679338a3c277f9c7ddd3861aa4ae754 to your computer and use it in GitHub Desktop.
Decider lighting talk
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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