decider 0.10.0 → 2.0.0

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.

Potentially problematic release.


This version of decider might be problematic. Click here for more details.

data/README.md DELETED
@@ -1,152 +0,0 @@
1
- # Decider
2
-
3
- This gem provides simple DSL for building Functional Event Sourcing Decider in Ruby. To learn more about the pattern read the original [article by Jérémie Chassaing](https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider).
4
-
5
- Special credits for [Ismael Celis for inspiration](https://ismaelcelis.com/posts/decide-evolve-react-pattern-in-ruby/).
6
-
7
- ## Installation
8
-
9
- ```bash
10
- gem install decider
11
- ```
12
-
13
- or add to Gemfile
14
-
15
- ```ruby
16
- gem "decider"
17
- ```
18
-
19
- ## Usage
20
-
21
- ```ruby
22
- require "decider"
23
-
24
- State = Data.define(:value) do
25
- def max?
26
- value >= MAX
27
- end
28
-
29
- def min?
30
- value <= MIN
31
- end
32
- end
33
-
34
- module Commands
35
- Increase = Data.define
36
- Decrease = Data.define
37
- end
38
-
39
- module Events
40
- ValueIncreased = Data.define
41
- ValueDecreased = Data.define
42
- end
43
-
44
- MIN = 0
45
- MAX = 100
46
-
47
- ValueDecider = Decider.define do
48
- # define intial state
49
- initial_state State.new(value: 0)
50
-
51
- # decide command with state
52
- decide Commands::Increase do
53
- # return collection of events
54
- if state.max?
55
- []
56
- else
57
- [Events::ValueIncreased.new]
58
- end
59
- end
60
-
61
- decide Commands::Decrease do
62
- if state.min?
63
- []
64
- else
65
- [Events::ValueDecreased.new]
66
- end
67
- end
68
-
69
- # evolve state with events
70
- evolve Events::ValueIncreased do
71
- # return new state
72
- state.with(value: state.value + 1)
73
- end
74
-
75
- evolve Events::ValueDecreased do
76
- # state is immutable Data object
77
- state.with(value: state.value - 1)
78
- end
79
-
80
- terminal? do
81
- state <= 0
82
- end
83
- end
84
-
85
- state = ValueDecider.initial_state
86
- events = ValueDecider.decide(Commands::Increase.new, state)
87
- new_state = events.reduce(state) { |state, event| ValueDecider.evolve(state, events)
88
- ```
89
-
90
- You can also compose deciders:
91
-
92
- ```ruby
93
- Left = Data.define(:value)
94
- Right = Data.define(:value)
95
-
96
- left = Decider.define do
97
- initial_state Left.new(value: 0)
98
-
99
- decide Commands::LeftCommand do
100
- [Events::LeftEvent.new(value: command.value)]
101
- end
102
-
103
- evolve Events::LeftEvent do
104
- state.with(value: state.value + 1)
105
- end
106
-
107
- terminal? do
108
- state <= 0
109
- end
110
- end
111
-
112
- right = Decider.define do
113
- initial_state Right.new(value: 0)
114
-
115
- decide Commands::RightCommand do
116
- [Events::RightEvent.new(value: command.value)]
117
- end
118
-
119
- evolve Events::RightEvent do
120
- state.with(value: state.value + 1)
121
- end
122
-
123
- terminal? do
124
- state <= 0
125
- end
126
- end
127
-
128
- Composition = Decider.compose(left, right)
129
-
130
- state = Composition.initial_state
131
- #> #<data Decider::Pair left=#<data Left value=0>, right=#<data Right value=0>>
132
-
133
- events = Composition.decide(Decider::Left.new(Commands::LeftCommand.new(value: 1)), state)
134
- #> [#<Decider::Left value=#<data value=1>]
135
-
136
- state = events.reduce(state, &Composition.method(:evolve))
137
- #> #<data Decider::Pair left=#<data value=1>, right=#<data value=0>>
138
- ```
139
-
140
- ## Development
141
-
142
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
143
-
144
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
145
-
146
- ## Contributing
147
-
148
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/decider.
149
-
150
- ## License
151
-
152
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Steepfile DELETED
@@ -1,26 +0,0 @@
1
- D = Steep::Diagnostic
2
-
3
- target :lib do
4
- signature "sig"
5
-
6
- check "lib"
7
- check "Gemfile"
8
-
9
- # library "pathname" # Standard libraries
10
- # library "strong_json" # Gems
11
-
12
- # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
13
- configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
14
- # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
15
- # configure_code_diagnostics do |hash| # You can setup everything yourself
16
- # hash[D::Ruby::NoMethod] = :information
17
- # end
18
- end
19
-
20
- # target :test do
21
- # signature "sig", "sig-private"
22
-
23
- # check "test"
24
-
25
- # # library "pathname" # Standard libraries
26
- # end
data/examples/decide.md DELETED
@@ -1,180 +0,0 @@
1
- # Decide
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
- #> [#<data Event 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
-
74
- ## Handling unknown commands
75
-
76
- ```ruby
77
- decider = Decider.define do
78
- initial_state :initial
79
- end
80
- ```
81
-
82
- Return empty list of events (nothing changed) by default:
83
-
84
- ```ruby
85
- decider.decide :unknown, decider.initial_state
86
- #> []
87
- ```
88
-
89
- If you want to raise error, define a catch-all as last one:
90
-
91
- ```ruby
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)
101
- ```
102
-
103
- ## Commands
104
-
105
- Commands can be primitives like symbols:
106
-
107
- ```ruby
108
- decider = Decider.define do
109
- initial_state :initial
110
-
111
- decide proc { [command, state] in [:start, :initial | :stopped] } do
112
- emit :started
113
- end
114
-
115
- decide proc { [command, state] in [:stop, :started] } do
116
- emit :stopped
117
- end
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
- #> []
126
- ```
127
-
128
- Or any classes like [Dry::Struct](https://dry-rb.org/gems/dry-struct/) or [Data](https://rubyapi.org/3.3/o/data):
129
-
130
- ```ruby
131
- Start = Data.define(:value)
132
- Stop = Data.define
133
-
134
- decider = Decider.define do
135
- initial_state :initial
136
-
137
- decide proc { [command, state] in [Start, :initial | :stopped] } do
138
- emit [:started, command.value]
139
- end
140
-
141
- decide proc { [command, state] in [Stop, :started] } do
142
- emit :stopped
143
- end
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]
150
- ```
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/evolve.md DELETED
@@ -1,150 +0,0 @@
1
- # Evolve
2
-
3
- ## Match by event
4
-
5
- ```ruby
6
- Event = Data.define(:value)
7
-
8
- decider = Decider.define do
9
- initial_state :initial
10
-
11
- evolve Event do
12
- event.value
13
- end
14
- end
15
-
16
- decider.evolve decider.initial_state, Event.new(value: :changed)
17
- #> :changed
18
- ```
19
-
20
- ## Match by state and event
21
-
22
- ```ruby
23
- decider = Decider.define do
24
- initial_state :turned_off
25
-
26
- evolve :turned_on, :turned_off do
27
- event
28
- end
29
-
30
- evolve :turned_off, :turned_on do
31
- event
32
- end
33
- end
34
-
35
- decider.evolve decider.initial_state, :turned_on
36
- #> :turned_on
37
- decider.evolve :turned_on, :turned_off
38
- #> :turned_off
39
- decider.evolve :turned_on, :unknown
40
- #> :turned_on
41
- ```
42
-
43
- ## Match by pattern matching
44
-
45
- ```ruby
46
- State = Data.define(:value)
47
- Event = Data.define(:value)
48
-
49
- decider = Decider.define do
50
- initial_state State.new(value: :turned_off)
51
-
52
- evolve proc { [state, event] in [State(value: :turned_off), Event(value: :turned_on)] } do
53
- state.with(value: event.value)
54
- end
55
- end
56
-
57
- decider.evolve decider.initial_state, Event.new(value: :turned_on)
58
- #> #<data State value=:turned_on>
59
- decider.evolve decider.initial_state, :unknown
60
- #> #<data State value=:turned_off>
61
- ```
62
-
63
- ## Handling unknown events
64
-
65
- ```ruby
66
- decider = Decider.define do
67
- initial_state :initial
68
- end
69
-
70
- decider.decide decider.initial_state, :initial
71
- #> :initial
72
- ```
73
-
74
- If you want to raise error, define a catch-all as last one:
75
-
76
- ```ruby
77
- decider = Decider.define do
78
- initial_state :initial
79
-
80
- evolve proc { true } do
81
- raise ArgumentError, "Unknown event #{event}"
82
- end
83
- end
84
- decider.evolve decider.initial_state, :unknown
85
- #> Unknown event unknown (ArgumentError)
86
- ```
87
-
88
- ## Events
89
-
90
- Events can be primitives like symbols:
91
-
92
- ```ruby
93
- decider = Decider.define do
94
- initial_state :initial
95
-
96
- evolve :started do
97
- :started
98
- end
99
-
100
- evolve :stopped do
101
- :stopped
102
- end
103
- end
104
- ```
105
-
106
- Or any classes like [Dry::Struct](https://dry-rb.org/gems/dry-struct/) or [Data](https://rubyapi.org/3.3/o/data):
107
-
108
- ```ruby
109
- State = Data.define(:speed)
110
- Started = Data.define(:speed)
111
- Stopped = Data.define
112
-
113
- decider = Decider.define do
114
- initial_state State.new(speed: 0)
115
-
116
- evolve State, Started do
117
- state.with(speed: event.speed)
118
- end
119
-
120
- evolve State, Stopped do
121
- State.new(speed: 0)
122
- end
123
- end
124
- ```
125
-
126
- ## Calculate state
127
-
128
- In most cases you want to take a collection of events and reduce them with `evolve` to calculate the state, like:
129
-
130
- ```ruby
131
- decider = Decider.define do
132
- initial_state 0
133
-
134
- evolve :increased do
135
- state + 1
136
- end
137
- end
138
-
139
- [:increased, :increased].reduce(decider.initial_state) { |state, event| decider.evolve(state, event) }
140
- #> 2
141
- ```
142
-
143
- You can shortcut that with `&`:
144
-
145
- ```ruby
146
- [:increased, :increased].reduce(decider.initial_state, &decider.method(:evolve))
147
- #> 2
148
- [:increased, :increased].reduce(decider.initial_state, &decider.evolve)
149
- #> 2
150
- ```
data/examples/infra.md DELETED
@@ -1,15 +0,0 @@
1
- # Infra
2
-
3
- ## In memory
4
-
5
- ```ruby
6
- require "decider/in_memory"
7
-
8
- GLOBAL = Decider::InMemory.new(MyDecider)
9
-
10
- # returns list of events
11
- GLOBAL.handle(command)
12
-
13
- # returns current state
14
- GLOBAL.state
15
- ```
data/examples/state.md DELETED
@@ -1,55 +0,0 @@
1
- # State examples
2
-
3
- ## Data
4
-
5
- ```ruby
6
- State = Data.define(:value)
7
-
8
- decider = Decider.define do
9
- initial_state State.new(value: 0)
10
-
11
- decide Commands::Command do
12
- emit Events::Event.new(value: command.value)
13
- end
14
-
15
- evolve Events::Event do
16
- state.with(value: state.value + 1)
17
- end
18
- end
19
- ```
20
-
21
- ## Primitive
22
-
23
- ```ruby
24
- decider = Decider.define do
25
- initial_state :turned_off
26
-
27
- decide Commands::TurnOn, :turned_off do
28
- emit Events::TurnedOn.new
29
- end
30
-
31
- decide Commands::TurnOff, :turned_on do
32
- emit Events::TurnedOff.new
33
- end
34
-
35
- evolve Events::TurnedOn do
36
- :turned_on
37
- end
38
- end
39
- ```
40
-
41
- ## List
42
-
43
- ```ruby
44
- decider = Decider.define do
45
- initial_state []
46
-
47
- decide Commands::Command, [] do
48
- emit Events::Event.new(value: command.value)
49
- end
50
-
51
- evolve Events::Event do
52
- state + [event]
53
- end
54
- end
55
- ```
@@ -1,25 +0,0 @@
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
@@ -1,26 +0,0 @@
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