decide.rb 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/decide.md +130 -31
- data/examples/infra.md +15 -0
- data/lib/decider/event_sourcing.rb +25 -0
- data/lib/decider/in_memory.rb +26 -0
- data/lib/decider/state.rb +25 -0
- data/lib/decider/version.rb +1 -1
- data/lib/decider.rb +46 -0
- metadata +50 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef8af0a9aa14ef5a19fce005eba64d4b4504277493434c4091510e49f1378a39
|
4
|
+
data.tar.gz: 4ca62248b625d9bc3926749565c0ac4ef066a7fb46bd3cf0ec605f0c72d3e4b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2326979a7350bf881484052c80cba5147f160a923f41b39c80420da5424e9239b62b6758460efeef81198fb83aec4e026a933a503661cf2ced329bb83dbbd12d
|
7
|
+
data.tar.gz: 07d38ce7613facb15a5e553482cf7b43d05f8b2743c3cac9b26f1fc9ad909cd87e24c03b20fc0258b554a4d9c08fa03123c6f27a36f661d31db054cef52c5221
|
data/examples/decide.md
CHANGED
@@ -1,5 +1,76 @@
|
|
1
1
|
# Decide
|
2
2
|
|
3
|
+
## Match by command
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
Command = Data.define(:value)
|
7
|
+
State = Data.define(:value)
|
8
|
+
|
9
|
+
decider = Decider.define do
|
10
|
+
initial_state State.new(value: 5)
|
11
|
+
|
12
|
+
decide Command do
|
13
|
+
# emit events
|
14
|
+
emit Event.new(
|
15
|
+
value: command.value + state.value
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
decider.decide Command.new(value: 10), decider.initial_state
|
21
|
+
#> [Event.new(value: 15)]
|
22
|
+
```
|
23
|
+
|
24
|
+
## Match by command and state
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
decider = Decider.define do
|
28
|
+
initial_state :turned_off
|
29
|
+
|
30
|
+
decide :turn_on, :turned_off do
|
31
|
+
emit :turned_on
|
32
|
+
end
|
33
|
+
|
34
|
+
decide :turn_off, :turned_on do
|
35
|
+
emit :turned_off
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
decider.decide :turn_on, decider.initial_state
|
40
|
+
#> [:turned_on]
|
41
|
+
decider.decide :turn_off, :turned_on
|
42
|
+
#> [:turned_off]
|
43
|
+
decider.decide :turn_on, :turned_on
|
44
|
+
#> []
|
45
|
+
```
|
46
|
+
|
47
|
+
## Match by pattern matching
|
48
|
+
|
49
|
+
State = Data.define(:status)
|
50
|
+
TurnOn = Data.define
|
51
|
+
TurnOff = Data.define
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
decider = Decider.define do
|
55
|
+
initial_state State.new(status: :turned_off)
|
56
|
+
|
57
|
+
decide proc { [command, state] in [TurnOn, State(status: :turned_off)] } do
|
58
|
+
emit :turned_on
|
59
|
+
end
|
60
|
+
|
61
|
+
decide proc { [command, state] in [TurnOff, State(status: :turned_on)] } do
|
62
|
+
emit :turned_off
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
decider.decide :turn_on, decider.initial_state
|
67
|
+
#> [:turned_on]
|
68
|
+
decider.decide :turn_off, :turned_on
|
69
|
+
#> [:turned_off]
|
70
|
+
decider.decide :turn_on, :turned_on
|
71
|
+
#> []
|
72
|
+
```
|
73
|
+
|
3
74
|
## Handling unknown commands
|
4
75
|
|
5
76
|
```ruby
|
@@ -15,11 +86,18 @@ decider.decide :unknown, decider.initial_state
|
|
15
86
|
#> []
|
16
87
|
```
|
17
88
|
|
18
|
-
|
89
|
+
If you want to raise error, define a catch-all as last one:
|
19
90
|
|
20
91
|
```ruby
|
21
|
-
decider
|
22
|
-
|
92
|
+
decider = Decider.define do
|
93
|
+
initial_state :initial
|
94
|
+
|
95
|
+
decide proc { true } do
|
96
|
+
raise ArgumentError, "Unknown command #{command}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
decider.decide :unknown, decider.initial_state
|
100
|
+
#> Unknown command unknown (ArgumentError)
|
23
101
|
```
|
24
102
|
|
25
103
|
## Commands
|
@@ -30,24 +108,21 @@ Commands can be primitives like symbols:
|
|
30
108
|
decider = Decider.define do
|
31
109
|
initial_state :initial
|
32
110
|
|
33
|
-
decide
|
34
|
-
|
35
|
-
in :initial, :stopped
|
36
|
-
[:started]
|
37
|
-
else
|
38
|
-
[]
|
39
|
-
end
|
111
|
+
decide proc { [command, state] in [:start, :initial | :stopped] } do
|
112
|
+
emit :started
|
40
113
|
end
|
41
114
|
|
42
|
-
decide
|
43
|
-
|
44
|
-
in :started
|
45
|
-
[:stopped]
|
46
|
-
else
|
47
|
-
[]
|
48
|
-
end
|
115
|
+
decide proc { [command, state] in [:stop, :started] } do
|
116
|
+
emit :stopped
|
49
117
|
end
|
50
118
|
end
|
119
|
+
|
120
|
+
decider.decide :start, decider.initial_state
|
121
|
+
#> [:started]
|
122
|
+
decider.decide :stop, :started
|
123
|
+
#> [:stopped]
|
124
|
+
decider.decider :start, :started
|
125
|
+
#> []
|
51
126
|
```
|
52
127
|
|
53
128
|
Or any classes like [Dry::Struct](https://dry-rb.org/gems/dry-struct/) or [Data](https://rubyapi.org/3.3/o/data):
|
@@ -59,23 +134,47 @@ Stop = Data.define
|
|
59
134
|
decider = Decider.define do
|
60
135
|
initial_state :initial
|
61
136
|
|
62
|
-
decide
|
63
|
-
|
64
|
-
in :initial, :stopped
|
65
|
-
[:started, command.value]
|
66
|
-
else
|
67
|
-
[]
|
68
|
-
end
|
137
|
+
decide proc { [command, state] in [Start, :initial | :stopped] } do
|
138
|
+
emit [:started, command.value]
|
69
139
|
end
|
70
140
|
|
71
|
-
decide
|
72
|
-
|
73
|
-
in :started
|
74
|
-
[:stopped]
|
75
|
-
else
|
76
|
-
[]
|
77
|
-
end
|
141
|
+
decide proc { [command, state] in [Stop, [:started, _]] } do
|
142
|
+
emit :stopped
|
78
143
|
end
|
79
144
|
end
|
145
|
+
|
146
|
+
decider.decide Start.new(value: 10), decider.initial_state
|
147
|
+
#> [[:started, 10]]
|
148
|
+
decider.decide Stop.new, [:started, 10]
|
149
|
+
#> [:stopped]
|
80
150
|
```
|
81
151
|
|
152
|
+
## Emitting Events
|
153
|
+
|
154
|
+
Decide can emit 0, 1 or more events:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
decider = Decider.define do
|
158
|
+
initial_state :initial
|
159
|
+
|
160
|
+
decide :none do
|
161
|
+
# noop
|
162
|
+
end
|
163
|
+
|
164
|
+
decide :one do
|
165
|
+
emit :event
|
166
|
+
end
|
167
|
+
|
168
|
+
decide :multiple do
|
169
|
+
emit :one
|
170
|
+
emit :two
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
decider.decide :none, decider.initial_state
|
175
|
+
#> []
|
176
|
+
decider.decide :one, decider.initial_state
|
177
|
+
#> [:event]
|
178
|
+
decider.decide :multiple, decider.initial_state
|
179
|
+
#> [:one, :two]
|
180
|
+
```
|
data/examples/infra.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decider
|
4
|
+
class EventSourcing
|
5
|
+
def initialize(decider:, event_store:)
|
6
|
+
@decider = decider
|
7
|
+
@event_store = event_store
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(command, stream_name:)
|
11
|
+
events = event_store.read.stream(stream_name)
|
12
|
+
state = events.reduce(decider.initial_state, &decider.method(:evolve))
|
13
|
+
|
14
|
+
new_events = decider.decide(command, state)
|
15
|
+
|
16
|
+
event_store.append(new_events, stream_name: stream_name, expected_version: events.count)
|
17
|
+
|
18
|
+
[new_events, events.count + new_events.count]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :decider, :event_store
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent-ruby"
|
4
|
+
|
5
|
+
module Decider
|
6
|
+
class InMemory
|
7
|
+
def initialize(decider)
|
8
|
+
@decider = decider
|
9
|
+
@atom = Concurrent::Atom.new(decider.initial_state)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(command)
|
13
|
+
events = decider.decide(command, state)
|
14
|
+
atom.swap { |state| events.reduce(state, &decider.method(:evolve)) }
|
15
|
+
events
|
16
|
+
end
|
17
|
+
|
18
|
+
def state
|
19
|
+
atom.value
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :decider, :atom
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decider
|
4
|
+
class State
|
5
|
+
def initialize(decider:, repository:)
|
6
|
+
@decider = decider
|
7
|
+
@repository = repository
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(command, key:, etag: nil)
|
11
|
+
state, etag = repository.try_load(key: key, etag: etag)
|
12
|
+
|
13
|
+
events = decider.decide(command, state)
|
14
|
+
new_state = events.reduce(state, &decider.method(:evolve))
|
15
|
+
|
16
|
+
new_etag = repository.save(new_state, key: key, etag: etag)
|
17
|
+
|
18
|
+
[events, new_etag]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :decider, :repository
|
24
|
+
end
|
25
|
+
end
|
data/lib/decider/version.rb
CHANGED
data/lib/decider.rb
CHANGED
@@ -99,6 +99,14 @@ module Decider
|
|
99
99
|
|
100
100
|
context.instance_exec(&terminal)
|
101
101
|
end
|
102
|
+
|
103
|
+
define_method(:dimap_on_state) do |fl:, fr:|
|
104
|
+
Decider.dimap_on_state(self, fl: fl, fr: fr)
|
105
|
+
end
|
106
|
+
|
107
|
+
define_method(:dimap_on_event) do |fl:, fr:|
|
108
|
+
Decider.dimap_on_event(self, fl: fl, fr: fr)
|
109
|
+
end
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
@@ -194,4 +202,42 @@ module Decider
|
|
194
202
|
end
|
195
203
|
end
|
196
204
|
end
|
205
|
+
|
206
|
+
def self.dimap_on_state(decider, fl:, fr:)
|
207
|
+
define do
|
208
|
+
initial_state fr.call(decider.initial_state)
|
209
|
+
|
210
|
+
decide proc { true } do
|
211
|
+
decider.decide(command, fl.call(state)).each(&method(:emit))
|
212
|
+
end
|
213
|
+
|
214
|
+
evolve proc { true } do
|
215
|
+
fr.call(decider.evolve(fl.call(state), event))
|
216
|
+
end
|
217
|
+
|
218
|
+
terminal? do
|
219
|
+
decider.terminal?(fl.call(state))
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.dimap_on_event(decider, fl:, fr:)
|
225
|
+
define do
|
226
|
+
initial_state decider.initial_state
|
227
|
+
|
228
|
+
decide proc { true } do
|
229
|
+
decider.decide(command, state).each do |event|
|
230
|
+
emit fr.call(event)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
evolve proc { true } do
|
235
|
+
decider.evolve(state, fl.call(event))
|
236
|
+
end
|
237
|
+
|
238
|
+
terminal? do
|
239
|
+
decider.terminal?(state)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
197
243
|
end
|
metadata
CHANGED
@@ -1,15 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: decide.rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Dudulski
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
10
|
+
date: 2025-04-12 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: concurrent-ruby
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.3'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '1.3'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: sqlite3
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.5.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 2.5.0
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: ruby_event_store
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.15'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.15'
|
13
54
|
description: Functional Event Sourcing Decider in Ruby
|
14
55
|
email:
|
15
56
|
- jan@dudulski.pl
|
@@ -25,8 +66,12 @@ files:
|
|
25
66
|
- Steepfile
|
26
67
|
- examples/decide.md
|
27
68
|
- examples/evolve.md
|
69
|
+
- examples/infra.md
|
28
70
|
- examples/state.md
|
29
71
|
- lib/decider.rb
|
72
|
+
- lib/decider/event_sourcing.rb
|
73
|
+
- lib/decider/in_memory.rb
|
74
|
+
- lib/decider/state.rb
|
30
75
|
- lib/decider/version.rb
|
31
76
|
- sig/decider.rbs
|
32
77
|
homepage: https://github.com/jandudulski/decide.rb
|
@@ -39,7 +84,6 @@ metadata:
|
|
39
84
|
documentation_uri: https://github.com/jandudulski/decide.rb
|
40
85
|
homepage_uri: https://github.com/jandudulski/decide.rb
|
41
86
|
source_code_uri: https://github.com/jandudulski/decide.rb
|
42
|
-
post_install_message:
|
43
87
|
rdoc_options: []
|
44
88
|
require_paths:
|
45
89
|
- lib
|
@@ -54,8 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
98
|
- !ruby/object:Gem::Version
|
55
99
|
version: '0'
|
56
100
|
requirements: []
|
57
|
-
rubygems_version: 3.
|
58
|
-
signing_key:
|
101
|
+
rubygems_version: 3.6.2
|
59
102
|
specification_version: 4
|
60
103
|
summary: Functional Event Sourcing Decider in Ruby
|
61
104
|
test_files: []
|