dry-facts 0.2.0 → 0.3.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.lock +1 -1
- data/README.md +64 -2
- data/ROADMAP.md +24 -15
- data/lib/dry/facts/aggregate.rb +21 -5
- data/lib/dry/facts/event_store.rb +40 -11
- data/lib/dry/facts/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b341b5c19a83f7f568b2cb9ad1b4f2caf14ec394d061c08509f0bc2940efd4c5
|
4
|
+
data.tar.gz: 36e9b955c5511f7ba03a0f6be0b682d79a1f3da410d77e2dfdced632f780a030
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbeaffa2e91b48af5c9304e2fda103cfb104b8830971599a1168c4ea5392bd478449e2092f920725f1db7e824b85c372de16215fdcdc1225d636149f4a663b8e
|
7
|
+
data.tar.gz: 7e6e32fac315907ad1a60f547fd9e9c012d59498892d1390a5af2afc6b31ec686c36dbde3324261ff2489dae86d7d8e7df5d41db805be1c7c69c544ad480eefe
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,6 +3,68 @@
|
|
3
3
|
EventSourcing ruby toolkit, somewhat based on dry-rb primitives.
|
4
4
|
Opinionated. Raw.
|
5
5
|
|
6
|
+
## Goals
|
7
|
+
|
8
|
+
* General EventSourcing toolkit for Ruby
|
9
|
+
* Ability to generate GraphQL interface to Bounded Contexts
|
10
|
+
* Idiomatic human-friendly API/DSL
|
11
|
+
* Explore *data-primitives-first* approach minimize usage of Classes and inheritance
|
12
|
+
|
13
|
+
## Basic concepts
|
14
|
+
|
15
|
+
Example module structure:
|
16
|
+
```ruby
|
17
|
+
# Bounded Context - isolated autonomous module
|
18
|
+
# it governs own state
|
19
|
+
# and encapsulates interactions with outer world
|
20
|
+
module AccountManagementContext
|
21
|
+
# State of the context is derived from persisted facts
|
22
|
+
module Events
|
23
|
+
Corrected
|
24
|
+
SignedUp
|
25
|
+
SignedIn
|
26
|
+
SignedOut
|
27
|
+
PasswordResetLinkRequested
|
28
|
+
PasswordResetLinkSent
|
29
|
+
PasswordUpdated
|
30
|
+
end
|
31
|
+
|
32
|
+
# Commands modify state by persisting events and
|
33
|
+
# encapsulate behaviors
|
34
|
+
module Commands
|
35
|
+
Correct
|
36
|
+
SignUp
|
37
|
+
SignIn
|
38
|
+
SignOut
|
39
|
+
PasswordResetTokenRequest
|
40
|
+
PasswordResetTokenSend
|
41
|
+
PasswordReset
|
42
|
+
end
|
43
|
+
|
44
|
+
# Queries allow read access of context's state
|
45
|
+
# for inner and outer usage
|
46
|
+
# multiple storages of various types may be used
|
47
|
+
module Queries
|
48
|
+
IsEmailAvialable
|
49
|
+
AccountByUUID
|
50
|
+
AccountByEmail
|
51
|
+
AccountByPasswordResetToken
|
52
|
+
AccountByEmailAndEncryptedPassword
|
53
|
+
|
54
|
+
IsValidAuthTokenForAccount
|
55
|
+
IsValidPasswordResetTokenForAccount
|
56
|
+
end
|
57
|
+
|
58
|
+
# Aggregates are optionally post-processed
|
59
|
+
# collections of events, used for representation of data
|
60
|
+
module Aggregates
|
61
|
+
Account
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
|
6
68
|
## Installation
|
7
69
|
|
8
70
|
Add this line to your application's Gemfile:
|
@@ -21,7 +83,7 @@ Or install it yourself as:
|
|
21
83
|
|
22
84
|
## Usage
|
23
85
|
|
24
|
-
|
86
|
+
See example usage in `test/dummy_app`
|
25
87
|
|
26
88
|
## Development
|
27
89
|
|
@@ -30,7 +92,7 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
30
92
|
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
31
93
|
|
32
94
|
## Contributing
|
33
|
-
|
95
|
+
Active development is visible in `develop` branch.
|
34
96
|
Bug reports and pull requests are welcome on GitHub at https://github.com/andriytyurnikov/dry-facts.
|
35
97
|
|
36
98
|
## License
|
data/ROADMAP.md
CHANGED
@@ -7,16 +7,15 @@ pattern.
|
|
7
7
|
Being able to build core behaviors and exposing those through GraphQL to the
|
8
8
|
customers is the basic idea.
|
9
9
|
|
10
|
-
|
11
|
-
GraphQL generation (?),
|
10
|
+
Goal is multidimensional:
|
11
|
+
GraphQL generation from bounded context code (?),
|
12
12
|
persistance,
|
13
13
|
serialization,
|
14
14
|
validations,
|
15
|
-
transactions,
|
16
|
-
|
15
|
+
nested transactions,
|
16
|
+
async execution,
|
17
|
+
command composition
|
17
18
|
|
18
|
-
In order to achieve such a goal
|
19
|
-
I'll try to coordinate'Breadth-first' & 'Depth first' design strategies
|
20
19
|
|
21
20
|
## Top to bottom vision
|
22
21
|
|
@@ -72,7 +71,11 @@ No explicit types ?
|
|
72
71
|
- [ ] Causality
|
73
72
|
- [ ] Correlations
|
74
73
|
- [ ] Transactions
|
75
|
-
- [ ]
|
74
|
+
- [ ] Snapshots
|
75
|
+
- [ ] Projections
|
76
|
+
- [ ] Versioning
|
77
|
+
- [ ] Locking
|
78
|
+
- [ ] Lifecycle and maintenance tasks
|
76
79
|
- [ ] Sequel store
|
77
80
|
- [ ] ActiveRecord store
|
78
81
|
- [ ] Redis store
|
@@ -94,18 +97,24 @@ No explicit types ?
|
|
94
97
|
- [x] Class instance
|
95
98
|
- [x] With events
|
96
99
|
- [x] Data transfer DSL
|
97
|
-
- [ ]
|
100
|
+
- [ ] Stream DSL
|
101
|
+
- [ ] Projection DSL
|
102
|
+
- [ ] Conversion to Hash
|
103
|
+
- [ ] Hash instance?
|
98
104
|
- [ ] With contract?
|
99
105
|
- [ ] Code & test generator
|
100
106
|
- [ ] Docs from comments
|
101
|
-
* GraphQL
|
102
|
-
- [x]
|
103
|
-
- [
|
104
|
-
- [ ]
|
107
|
+
* GraphQL
|
108
|
+
- [x] Data type
|
109
|
+
- [x] Query
|
110
|
+
- [ ] Paginated query
|
111
|
+
- [ ] Cursor paginated query
|
112
|
+
- [x] Mutation
|
113
|
+
- [ ] Unified mutation result (Relay?)
|
114
|
+
- [ ] Data generation
|
105
115
|
- [ ] Query generation
|
106
|
-
- [ ]
|
107
|
-
- [ ]
|
108
|
-
- [ ] Customizable & helpers
|
116
|
+
- [ ] Mutation generation
|
117
|
+
- [ ] Schema from hash?
|
109
118
|
* CI
|
110
119
|
* JRuby tests
|
111
120
|
* OpalRuby tests
|
data/lib/dry/facts/aggregate.rb
CHANGED
@@ -8,8 +8,8 @@ module Dry
|
|
8
8
|
@event_handlers = Hash.new
|
9
9
|
|
10
10
|
attr_reader :events
|
11
|
-
|
12
|
-
|
11
|
+
attr_accessor :id
|
12
|
+
attr_accessor :uuid
|
13
13
|
|
14
14
|
class << self
|
15
15
|
def build_one_from_events events
|
@@ -23,6 +23,10 @@ module Dry
|
|
23
23
|
.map {|_, g_events| build_one_from_events(g_events) }
|
24
24
|
end
|
25
25
|
|
26
|
+
def event_types
|
27
|
+
@event_handlers.keys
|
28
|
+
end
|
29
|
+
|
26
30
|
def event_handlers
|
27
31
|
@event_handlers
|
28
32
|
end
|
@@ -46,12 +50,20 @@ module Dry
|
|
46
50
|
|
47
51
|
def initialize(events)
|
48
52
|
@events = events || []
|
49
|
-
|
53
|
+
first_event_aggregate_id= if @events.first
|
50
54
|
@events.first.aggregate_id
|
51
55
|
end
|
56
|
+
@uuid = @id = first_event_aggregate_id
|
57
|
+
|
52
58
|
@events.each {|e| self.send :_handle_event, e}
|
53
59
|
end
|
54
60
|
|
61
|
+
def to_h
|
62
|
+
{id: self.id, email: self.email}
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :to_hash, :to_h
|
66
|
+
|
55
67
|
private
|
56
68
|
|
57
69
|
def _default_aggregate_metadata
|
@@ -64,7 +76,7 @@ module Dry
|
|
64
76
|
|
65
77
|
def _handle_event event
|
66
78
|
if self.uuid != event.metadata[:aggregate_id]
|
67
|
-
fail "
|
79
|
+
fail "Event don't belong to aggregate instance. Aggregate #{self.inspect}, Event: #{event.inspect}"
|
68
80
|
end
|
69
81
|
handler_name =
|
70
82
|
self
|
@@ -76,7 +88,11 @@ module Dry
|
|
76
88
|
|
77
89
|
def _transfer_data_from_event event
|
78
90
|
event.data.keys.each do |key|
|
79
|
-
|
91
|
+
if :id == key.to_sym
|
92
|
+
self.send "#{key}=", event.aggregate_id
|
93
|
+
else
|
94
|
+
self.send "#{key}=", event.data[key]
|
95
|
+
end
|
80
96
|
end
|
81
97
|
end
|
82
98
|
|
@@ -7,15 +7,32 @@ module Dry
|
|
7
7
|
@serialized_events = []
|
8
8
|
end
|
9
9
|
|
10
|
+
def delete_all
|
11
|
+
@serialized_events = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def aggregate_by_id(klass, id)
|
15
|
+
get_events_by_aggregate_id(id)
|
16
|
+
.yield_self {|events| klass.build_one_from_events(events)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def aggregates_by_ids(klass, ids)
|
20
|
+
get_events_by_aggregate_ids(ids)
|
21
|
+
.yield_self {|events| klass.build_all_from_events(events)}
|
22
|
+
end
|
23
|
+
|
24
|
+
def all_aggregates_of(klass)
|
25
|
+
get_events_by_types(klass.event_types)
|
26
|
+
.yield_self {|events| klass.build_all_from_events(events)}
|
27
|
+
end
|
28
|
+
|
10
29
|
def aggregate_from_event(klass, event)
|
11
|
-
|
12
|
-
|
13
|
-
value: event.to_h[:data][:aggregate_id])
|
14
|
-
.yield_self {|events| klass.new(events)}
|
30
|
+
get_events_by_aggregate_id(event.to_h[:metadata][:aggregate_id])
|
31
|
+
.yield_self {|events| klass.build_one_from_events(events)}
|
15
32
|
end
|
16
33
|
|
17
|
-
#
|
18
|
-
def
|
34
|
+
# commit(event)
|
35
|
+
def commit(event)
|
19
36
|
# validate & fail
|
20
37
|
@serialized_events << event.to_h
|
21
38
|
event
|
@@ -28,16 +45,28 @@ module Dry
|
|
28
45
|
# fail if many events found?
|
29
46
|
@serialized_events
|
30
47
|
.find_all {|f| fact_id == (f && f[:metadata] && f[:metadata][:id])}
|
31
|
-
.yield_self {|it| (0 == it.length) ? fail("No
|
32
|
-
.yield_self {|it| (1 < it.length) ? fail("Too may
|
48
|
+
.yield_self {|it| (0 == it.length) ? fail("No event found") : it }
|
49
|
+
.yield_self {|it| (1 < it.length) ? fail("Too may events") : it }
|
33
50
|
.first
|
34
51
|
.yield_self {|it| deserialize_event(it)}
|
35
52
|
end
|
36
53
|
|
37
|
-
|
38
|
-
|
54
|
+
def get_events_by_aggregate_id(value)
|
55
|
+
@serialized_events
|
56
|
+
.find_all {|e| value == e[:metadata][:aggregate_id]}
|
57
|
+
.map {|it| deserialize_event(it)}
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_events_by_aggregate_ids(value)
|
61
|
+
@serialized_events
|
62
|
+
.find_all {|e| [value].flatten.include? e[:metadata][:aggregate_id]}
|
63
|
+
.map {|it| deserialize_event(it)}
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def get_events_by_types(types)
|
39
68
|
@serialized_events
|
40
|
-
.find_all {|f|
|
69
|
+
.find_all {|f| [types].flatten.include? f[:metadata][:type][:name]}
|
41
70
|
.map {|it| deserialize_event(it)}
|
42
71
|
end
|
43
72
|
|
data/lib/dry/facts/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-facts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andriy Tyurnikov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: require_all
|