dry-facts 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +4 -1
- data/README.md +7 -0
- data/ROADMAP.md +33 -20
- data/lib/dry/facts/aggregate.rb +29 -25
- data/lib/dry/facts/command.rb +8 -6
- data/lib/dry/facts/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35e96fd751117a68e6492f8472722648f80a1cd49fa0830cdba2fb4b579b1fce
|
4
|
+
data.tar.gz: 2e2f7b7374ea7e141df10b332f04e836f2475423995ff0518d6b196bb3b18cb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d1ee1eef74e2fdb5379777c8cb91b9c3bc6612543736e9687beb7c812b9210eb7c2722ec47a75c965ff2744efc592b643ff2ea46534ef1bbfdfdb9af7128145
|
7
|
+
data.tar.gz: 7eefafc1aa1bf0ce211b752cd6a26028506aaba9739ce51985cd40828ebf3e755dc1ab4f32194c0d33899127f621249944e76b5831e089eb09f78b85de826172
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dry-facts (0.
|
4
|
+
dry-facts (0.3.0)
|
5
5
|
dry-equalizer (>= 0.0.11)
|
6
6
|
dry-events (>= 0.1.0)
|
7
7
|
dry-struct
|
@@ -66,5 +66,8 @@ DEPENDENCIES
|
|
66
66
|
minitest (>= 5.0)
|
67
67
|
rake (>= 10.0)
|
68
68
|
|
69
|
+
RUBY VERSION
|
70
|
+
ruby 2.5.0p0
|
71
|
+
|
69
72
|
BUNDLED WITH
|
70
73
|
1.16.1
|
data/README.md
CHANGED
@@ -3,6 +3,13 @@
|
|
3
3
|
EventSourcing ruby toolkit, somewhat based on dry-rb primitives.
|
4
4
|
Opinionated. Raw.
|
5
5
|
|
6
|
+
## Rationale
|
7
|
+
|
8
|
+
People dont *know* or *understand* software projects, people navigate themselves through software projects.
|
9
|
+
To effectively navigate through structure and mutable state of big systems - programmers need sub-systems to be isolated.
|
10
|
+
Usable incapsulation - barriers have to be visible and hard to break.
|
11
|
+
Readability matters - declarative readable code wins.
|
12
|
+
|
6
13
|
## Goals
|
7
14
|
|
8
15
|
* General EventSourcing toolkit for Ruby
|
data/ROADMAP.md
CHANGED
@@ -1,33 +1,23 @@
|
|
1
1
|
# Roadmap
|
2
2
|
|
3
|
-
|
4
|
-
rapid application development using EventSourcing as a core architectural
|
5
|
-
pattern.
|
3
|
+
Goal is to build toolkit that would allow rapid application development using EventSourcing as a core architectural pattern.
|
6
4
|
|
7
|
-
Being able to build core behaviors and exposing those through GraphQL to the
|
8
|
-
customers is the basic idea.
|
5
|
+
Being able to build core behaviors and exposing those through GraphQL to the customers is the basic idea.
|
9
6
|
|
10
7
|
Goal is multidimensional:
|
11
|
-
GraphQL
|
12
|
-
|
13
|
-
|
14
|
-
validations,
|
15
|
-
nested transactions,
|
16
|
-
async execution,
|
17
|
-
command composition
|
18
|
-
|
8
|
+
Autogenerated GraphQL schema
|
9
|
+
EventSourcing toolkit
|
10
|
+
Idiomatic, human-friendly DSL
|
19
11
|
|
20
12
|
## Top to bottom vision
|
21
|
-
|
22
|
-
Autogenerated GraphQL used to invoke Commands and Queries, which read from Projections,
|
23
|
-
Aggregates, generate and persist events. Events persisted in EventStore.
|
13
|
+
Autogenerated GraphQL used to invoke Commands and Queries, which read from Projections, Aggregates, generate and persist events. Events persisted in EventStore.
|
24
14
|
|
25
15
|
## Approach
|
26
16
|
|
27
17
|
Minimal prototypes of primitives
|
28
18
|
Minimal prototype of integrated system
|
29
19
|
Minimal prototype of application based on system
|
30
|
-
Refactoring
|
20
|
+
Refactoring-Improvement-Refactoring
|
31
21
|
|
32
22
|
## Challenges
|
33
23
|
|
@@ -35,6 +25,13 @@ Brain split,
|
|
35
25
|
Deliver once,
|
36
26
|
Transactional consistency,
|
37
27
|
Nesting transactions,
|
28
|
+
Persistence,
|
29
|
+
Serialization,
|
30
|
+
Validations,
|
31
|
+
Command composition,
|
32
|
+
Nested transactions,
|
33
|
+
Async execution
|
34
|
+
|
38
35
|
Event/Command/Schema evolution (behavior, naming, structure),
|
39
36
|
Identity (Sequent -- Repository for aggregates.
|
40
37
|
Implements the Unit-Of-Work and Identity-Map patterns to ensure each aggregate is only loaded once per transaction and that you always get the same aggregate instance back.)
|
@@ -46,6 +43,14 @@ No explicit types ?
|
|
46
43
|
|
47
44
|
|
48
45
|
## Checklist
|
46
|
+
* DX
|
47
|
+
- [x] Git Flow
|
48
|
+
- [ ] CI
|
49
|
+
- [ ] Ruby below 2.5.0 (yield_self usage)
|
50
|
+
- [ ] Linter
|
51
|
+
- [ ] Dependency watcher
|
52
|
+
- [ ] Badges
|
53
|
+
|
49
54
|
* Command
|
50
55
|
- [x] Skeleton without any meaningful structure
|
51
56
|
- [x] With inner class input contract
|
@@ -54,6 +59,7 @@ No explicit types ?
|
|
54
59
|
- [x] With explicit method output contract
|
55
60
|
- [x] Without input contract
|
56
61
|
- [x] Without output contract
|
62
|
+
- [ ] Dry-transaction?
|
57
63
|
- [ ] Unified result (Relay?)
|
58
64
|
- [ ] Multistep
|
59
65
|
- [ ] Composition
|
@@ -68,23 +74,27 @@ No explicit types ?
|
|
68
74
|
- [x] In memory store
|
69
75
|
- [ ] Serialization and typecasting?
|
70
76
|
- [x] Aggregates
|
77
|
+
- [ ] Snapshots
|
71
78
|
- [ ] Causality
|
72
79
|
- [ ] Correlations
|
73
80
|
- [ ] Transactions
|
74
|
-
- [ ] Snapshots
|
75
81
|
- [ ] Projections
|
76
82
|
- [ ] Versioning
|
77
83
|
- [ ] Locking
|
84
|
+
- [ ] Blockchain?
|
78
85
|
- [ ] Lifecycle and maintenance tasks
|
79
86
|
- [ ] Sequel store
|
80
87
|
- [ ] ActiveRecord store
|
81
88
|
- [ ] Redis store
|
82
89
|
- [ ] Kafka store
|
90
|
+
- [ ] IPFS store?
|
91
|
+
- [ ] IPDB store?
|
83
92
|
- [ ] Docs from comments
|
84
93
|
* Event
|
85
94
|
- [ ] Hash?
|
86
95
|
- [x] Object?
|
87
|
-
- [
|
96
|
+
- [x] Immutability?
|
97
|
+
- [x] Aggregate DSL
|
88
98
|
- [ ] Correlation DSL
|
89
99
|
- [ ] Causation DSL
|
90
100
|
- [ ] Module?
|
@@ -97,6 +107,8 @@ No explicit types ?
|
|
97
107
|
- [x] Class instance
|
98
108
|
- [x] With events
|
99
109
|
- [x] Data transfer DSL
|
110
|
+
- [x] To hash
|
111
|
+
- [ ] Aggregate from namespaces (*::Events* ?)
|
100
112
|
- [ ] Stream DSL
|
101
113
|
- [ ] Projection DSL
|
102
114
|
- [ ] Conversion to Hash
|
@@ -104,6 +116,8 @@ No explicit types ?
|
|
104
116
|
- [ ] With contract?
|
105
117
|
- [ ] Code & test generator
|
106
118
|
- [ ] Docs from comments
|
119
|
+
* Projections
|
120
|
+
- [ ] Skeleton without any functionality
|
107
121
|
* GraphQL
|
108
122
|
- [x] Data type
|
109
123
|
- [x] Query
|
@@ -115,7 +129,6 @@ No explicit types ?
|
|
115
129
|
- [ ] Query generation
|
116
130
|
- [ ] Mutation generation
|
117
131
|
- [ ] Schema from hash?
|
118
|
-
* CI
|
119
132
|
* JRuby tests
|
120
133
|
* OpalRuby tests
|
121
134
|
|
data/lib/dry/facts/aggregate.rb
CHANGED
@@ -12,40 +12,39 @@ module Dry
|
|
12
12
|
attr_accessor :uuid
|
13
13
|
|
14
14
|
class << self
|
15
|
-
def
|
16
|
-
|
17
|
-
|
15
|
+
def aggregate_data_from_event_types *event_klasses
|
16
|
+
event_klasses.each do |klass|
|
17
|
+
define_event_type_handler klass, :_transfer_data_from_event
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
21
|
def build_all_from_events events
|
21
22
|
events
|
22
|
-
.group_by {|e|
|
23
|
-
.map
|
23
|
+
.group_by {|e| e.aggregate_id }
|
24
|
+
.map {|_, g_events| build_one_from_events(g_events) }
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
|
27
|
+
def build_one_from_events events
|
28
|
+
self
|
29
|
+
.new(events)
|
28
30
|
end
|
29
31
|
|
30
|
-
def
|
31
|
-
@event_handlers
|
32
|
+
def define_event_type_handler klass, method_name
|
33
|
+
@event_handlers ||= {}
|
34
|
+
@event_handlers[klass.name] = method_name
|
32
35
|
end
|
33
36
|
|
34
|
-
def
|
35
|
-
|
37
|
+
def event_handlers
|
38
|
+
@event_handlers
|
36
39
|
end
|
37
40
|
|
38
|
-
def
|
39
|
-
@event_handlers
|
40
|
-
@event_handlers[name] = method_name
|
41
|
+
def event_types
|
42
|
+
@event_handlers.keys
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
-
define_event_handler klass.name, :_transfer_data_from_event
|
46
|
-
end
|
45
|
+
def find_event_handler(key)
|
46
|
+
(@event_handlers)[key]
|
47
47
|
end
|
48
|
-
|
49
48
|
end
|
50
49
|
|
51
50
|
def initialize(events)
|
@@ -59,9 +58,12 @@ module Dry
|
|
59
58
|
end
|
60
59
|
|
61
60
|
def to_h
|
62
|
-
|
61
|
+
self
|
62
|
+
.instance_variables
|
63
|
+
.map {|ivar_name| ivar_name.to_s.gsub(/@/, '')}
|
64
|
+
.yield_self {|ivars| ivars - ['events', 'uuid']}
|
65
|
+
.reduce(Hash.new) {|memo, obj| memo[obj.to_sym] = self.send(obj); memo }
|
63
66
|
end
|
64
|
-
|
65
67
|
alias_method :to_hash, :to_h
|
66
68
|
|
67
69
|
private
|
@@ -70,20 +72,22 @@ module Dry
|
|
70
72
|
uuid = SecureRandom.uuid,
|
71
73
|
{ id: uuid,
|
72
74
|
uuid: uuid,
|
73
|
-
type: { name:
|
74
|
-
version: '0' } }
|
75
|
+
type: { name: self.class.name } }
|
75
76
|
end
|
76
77
|
|
77
78
|
def _handle_event event
|
78
79
|
if self.uuid != event.metadata[:aggregate_id]
|
79
|
-
fail "Event don't belong to aggregate instance.
|
80
|
+
fail "Event don't belong to aggregate instance. \n
|
81
|
+
Aggregate #{self.inspect} \n
|
82
|
+
Event: #{event.inspect}"
|
80
83
|
end
|
84
|
+
|
81
85
|
handler_name =
|
82
86
|
self
|
83
87
|
.class
|
84
88
|
.find_event_handler(event.metadata[:type][:name])
|
85
89
|
|
86
|
-
self.send(handler_name, event)
|
90
|
+
self.send(handler_name, event) if handler_name
|
87
91
|
end
|
88
92
|
|
89
93
|
def _transfer_data_from_event event
|
data/lib/dry/facts/command.rb
CHANGED
@@ -36,14 +36,14 @@ module Dry
|
|
36
36
|
def define_input_contract &block
|
37
37
|
@input_contract =
|
38
38
|
Class
|
39
|
-
.new(Dry::Struct)
|
39
|
+
.new(Dry::Struct::Value)
|
40
40
|
.yield_self { |k| k.instance_eval(&block) if block }
|
41
41
|
end
|
42
42
|
|
43
43
|
def define_output_contract &block
|
44
44
|
@output_contract =
|
45
45
|
Class
|
46
|
-
.new(Dry::Struct)
|
46
|
+
.new(Dry::Struct::Value)
|
47
47
|
.yield_self { |k| k.instance_eval(&block) if block }
|
48
48
|
end
|
49
49
|
|
@@ -54,7 +54,8 @@ module Dry
|
|
54
54
|
self::InputContract
|
55
55
|
else
|
56
56
|
self.input_contract
|
57
|
-
end
|
57
|
+
end
|
58
|
+
.yield_self {|c| _enforce_contract_if_present(c, input) }
|
58
59
|
end
|
59
60
|
|
60
61
|
def _enforce_output_contract_on output
|
@@ -62,16 +63,17 @@ module Dry
|
|
62
63
|
self::OutputContract
|
63
64
|
else
|
64
65
|
self.output_contract
|
65
|
-
end
|
66
|
+
end
|
67
|
+
.yield_self {|c| _enforce_contract_if_present(c, output) }
|
66
68
|
end
|
67
69
|
|
68
70
|
def _execute_with input
|
69
71
|
self.new.call input
|
70
72
|
end
|
71
73
|
|
72
|
-
def _enforce_contract_if_present
|
74
|
+
def _enforce_contract_if_present contract, data
|
73
75
|
if contract
|
74
|
-
contract.new
|
76
|
+
contract.new **data
|
75
77
|
else
|
76
78
|
data
|
77
79
|
end
|
data/lib/dry/facts/version.rb
CHANGED