dry-facts 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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