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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e4989c09bf4471795f9770ae4c20bd9703198f557f1df1ec75e21bbbc390e51
4
- data.tar.gz: 52fa236f7c4dca8c782e3671e0509ebb0e7965cd94453e4a16600cd814660f20
3
+ metadata.gz: b341b5c19a83f7f568b2cb9ad1b4f2caf14ec394d061c08509f0bc2940efd4c5
4
+ data.tar.gz: 36e9b955c5511f7ba03a0f6be0b682d79a1f3da410d77e2dfdced632f780a030
5
5
  SHA512:
6
- metadata.gz: 44864e6199ee8c8d983af7c0c6335f66dec1d55f373d30d265fee97ca88aa541c0f2fa12baa52ee9b67a6543dde8d27ddf06453498da16f6649db17e6ece9e5d
7
- data.tar.gz: fac80ac86674542a77946912b8d58b0f0176c033958e694ab334a99f9daece138b5ccd1012cfbebbae6a0224f4ed54af7fa4f9b8e7d6d8a382624f9c5c48bf7c
6
+ metadata.gz: fbeaffa2e91b48af5c9304e2fda103cfb104b8830971599a1168c4ea5392bd478449e2092f920725f1db7e824b85c372de16215fdcdc1225d636149f4a663b8e
7
+ data.tar.gz: 7e6e32fac315907ad1a60f547fd9e9c012d59498892d1390a5af2afc6b31ec686c36dbde3324261ff2489dae86d7d8e7df5d41db805be1c7c69c544ad480eefe
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dry-facts (0.1.0)
4
+ dry-facts (0.2.0)
5
5
  dry-equalizer (>= 0.0.11)
6
6
  dry-events (>= 0.1.0)
7
7
  dry-struct
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
- TODO: Write usage instructions here
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
- Final solution is multifaceted:
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
- composition - all of those are hard topics.
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
- - [ ] Lifecycle and maintenance tasks (snapshots (rolling, deployment) and versioning)
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
- - [ ] Hash instance
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 generation
102
- - [x] Skeleton without generation
103
- - [ ] Type generation
104
- - [ ] Mutation generation
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
- - [ ] From hash
107
- - [ ] Naive generation
108
- - [ ] Customizable & helpers
116
+ - [ ] Mutation generation
117
+ - [ ] Schema from hash?
109
118
  * CI
110
119
  * JRuby tests
111
120
  * OpalRuby tests
@@ -8,8 +8,8 @@ module Dry
8
8
  @event_handlers = Hash.new
9
9
 
10
10
  attr_reader :events
11
- attr_reader :id
12
- attr_reader :uuid
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
- @uuid = @id = if @events.first
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 "This event is not mine!"
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
- self.send "#{key}=", event.data[key]
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
- get_events_by_key(
12
- key: :aggregate_id,
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
- # persist_event_and_return(event)
18
- def persist_event_and_return(event)
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 fact found") : it }
32
- .yield_self {|it| (1 < it.length) ? fail("Too may facts") : it }
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
- # get_events_by_key(key: 'aggregate-id', value: 42)
38
- def get_events_by_key(key:, value:)
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| value == f[key]}
69
+ .find_all {|f| [types].flatten.include? f[:metadata][:type][:name]}
41
70
  .map {|it| deserialize_event(it)}
42
71
  end
43
72
 
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Facts
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
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.2.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-20 00:00:00.000000000 Z
11
+ date: 2018-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: require_all