aggro 0.0.1 → 0.0.2

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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/.travis.yml +15 -0
  4. data/Gemfile +9 -0
  5. data/README.md +5 -1
  6. data/Rakefile +10 -0
  7. data/aggro.gemspec +8 -1
  8. data/lib/aggro.rb +191 -7
  9. data/lib/aggro/abstract_store.rb +12 -0
  10. data/lib/aggro/aggregate.rb +98 -0
  11. data/lib/aggro/aggregate_ref.rb +68 -6
  12. data/lib/aggro/attribute_dsl.rb +96 -0
  13. data/lib/aggro/binding_dsl.rb +45 -0
  14. data/lib/aggro/block_helper.rb +14 -0
  15. data/lib/aggro/channel.rb +37 -0
  16. data/lib/aggro/client.rb +12 -0
  17. data/lib/aggro/cluster_config.rb +57 -0
  18. data/lib/aggro/command.rb +16 -0
  19. data/lib/aggro/concurrent_actor.rb +26 -0
  20. data/lib/aggro/event_bus.rb +94 -0
  21. data/lib/aggro/event_dsl.rb +53 -0
  22. data/lib/aggro/event_proxy.rb +23 -0
  23. data/lib/aggro/event_serializer.rb +14 -0
  24. data/lib/aggro/file_store.rb +97 -0
  25. data/lib/aggro/file_store/reader.rb +21 -0
  26. data/lib/aggro/file_store/writer.rb +27 -0
  27. data/lib/aggro/handler/command.rb +60 -0
  28. data/lib/aggro/handler/create_aggregate.rb +42 -0
  29. data/lib/aggro/handler/get_events.rb +30 -0
  30. data/lib/aggro/handler/query.rb +60 -0
  31. data/lib/aggro/handler/start_saga.rb +56 -0
  32. data/lib/aggro/local_node.rb +28 -0
  33. data/lib/aggro/locator.rb +32 -0
  34. data/lib/aggro/message/ask.rb +16 -0
  35. data/lib/aggro/message/command.rb +36 -0
  36. data/lib/aggro/message/create_aggregate.rb +16 -0
  37. data/lib/aggro/message/endpoint.rb +16 -0
  38. data/lib/aggro/message/events.rb +24 -0
  39. data/lib/aggro/message/get_events.rb +16 -0
  40. data/lib/aggro/message/heartbeat.rb +16 -0
  41. data/lib/aggro/message/invalid_target.rb +20 -0
  42. data/lib/aggro/message/ok.rb +20 -0
  43. data/lib/aggro/message/publisher_endpoint_inquiry.rb +16 -0
  44. data/lib/aggro/message/query.rb +36 -0
  45. data/lib/aggro/message/result.rb +16 -0
  46. data/lib/aggro/message/start_saga.rb +28 -0
  47. data/lib/aggro/message/unhandled_operation.rb +20 -0
  48. data/lib/aggro/message/unknown_operation.rb +20 -0
  49. data/lib/aggro/message_parser.rb +10 -0
  50. data/lib/aggro/message_router.rb +26 -0
  51. data/lib/aggro/nanomsg_transport.rb +31 -0
  52. data/lib/aggro/nanomsg_transport/client.rb +35 -0
  53. data/lib/aggro/nanomsg_transport/connection.rb +98 -0
  54. data/lib/aggro/nanomsg_transport/publish.rb +17 -0
  55. data/lib/aggro/nanomsg_transport/publisher.rb +37 -0
  56. data/lib/aggro/nanomsg_transport/raw_reply.rb +18 -0
  57. data/lib/aggro/nanomsg_transport/raw_request.rb +18 -0
  58. data/lib/aggro/nanomsg_transport/reply.rb +17 -0
  59. data/lib/aggro/nanomsg_transport/request.rb +17 -0
  60. data/lib/aggro/nanomsg_transport/server.rb +84 -0
  61. data/lib/aggro/nanomsg_transport/socket_error.rb +20 -0
  62. data/lib/aggro/nanomsg_transport/subscribe.rb +27 -0
  63. data/lib/aggro/nanomsg_transport/subscriber.rb +82 -0
  64. data/lib/aggro/node.rb +29 -0
  65. data/lib/aggro/node_list.rb +39 -0
  66. data/lib/aggro/projection.rb +13 -0
  67. data/lib/aggro/query.rb +11 -0
  68. data/lib/aggro/saga.rb +94 -0
  69. data/lib/aggro/saga_runner.rb +87 -0
  70. data/lib/aggro/saga_runner/start_saga.rb +12 -0
  71. data/lib/aggro/saga_status.rb +29 -0
  72. data/lib/aggro/server.rb +88 -0
  73. data/lib/aggro/subscriber.rb +48 -0
  74. data/lib/aggro/subscription.rb +41 -0
  75. data/lib/aggro/transform/boolean.rb +16 -0
  76. data/lib/aggro/transform/email.rb +26 -0
  77. data/lib/aggro/transform/id.rb +34 -0
  78. data/lib/aggro/transform/integer.rb +22 -0
  79. data/lib/aggro/transform/money.rb +22 -0
  80. data/lib/aggro/transform/noop.rb +16 -0
  81. data/lib/aggro/transform/string.rb +16 -0
  82. data/lib/aggro/transform/time_interval.rb +24 -0
  83. data/lib/aggro/version.rb +1 -1
  84. data/spec/lib/aggro/abstract_store_spec.rb +15 -0
  85. data/spec/lib/aggro/aggregate_ref_spec.rb +63 -12
  86. data/spec/lib/aggro/aggregate_spec.rb +207 -0
  87. data/spec/lib/aggro/channel_spec.rb +87 -0
  88. data/spec/lib/aggro/client_spec.rb +26 -0
  89. data/spec/lib/aggro/cluster_config_spec.rb +33 -0
  90. data/spec/lib/aggro/command_spec.rb +52 -0
  91. data/spec/lib/aggro/concurrent_actor_spec.rb +44 -0
  92. data/spec/lib/aggro/event_bus_spec.rb +20 -0
  93. data/spec/lib/aggro/event_serializer_spec.rb +28 -0
  94. data/spec/lib/aggro/file_store/reader_spec.rb +32 -0
  95. data/spec/lib/aggro/file_store/writer_spec.rb +67 -0
  96. data/spec/lib/aggro/file_store_spec.rb +51 -0
  97. data/spec/lib/aggro/handler/command_spec.rb +78 -0
  98. data/spec/lib/aggro/handler/create_aggregate_spec.rb +64 -0
  99. data/spec/lib/aggro/handler/get_events_handler_spec.rb +45 -0
  100. data/spec/lib/aggro/handler/query_spec.rb +78 -0
  101. data/spec/lib/aggro/handler/start_saga_spec.rb +64 -0
  102. data/spec/lib/aggro/local_node_spec.rb +52 -0
  103. data/spec/lib/aggro/locator_spec.rb +61 -0
  104. data/spec/lib/aggro/message/ask_spec.rb +23 -0
  105. data/spec/lib/aggro/message/command_spec.rb +50 -0
  106. data/spec/lib/aggro/message/create_aggregate_spec.rb +28 -0
  107. data/spec/lib/aggro/message/endpoint_spec.rb +23 -0
  108. data/spec/lib/aggro/message/events_spec.rb +37 -0
  109. data/spec/lib/aggro/message/get_events_spec.rb +33 -0
  110. data/spec/lib/aggro/message/heartbeat_spec.rb +23 -0
  111. data/spec/lib/aggro/message/invalid_target_spec.rb +28 -0
  112. data/spec/lib/aggro/message/ok_spec.rb +27 -0
  113. data/spec/lib/aggro/message/publisher_endpoint_inquiry_spec.rb +23 -0
  114. data/spec/lib/aggro/message/query_spec.rb +50 -0
  115. data/spec/lib/aggro/message/start_saga_spec.rb +37 -0
  116. data/spec/lib/aggro/message/unhandled_operation_spec.rb +28 -0
  117. data/spec/lib/aggro/message/unknown_operation_spec.rb +28 -0
  118. data/spec/lib/aggro/message_parser_spec.rb +16 -0
  119. data/spec/lib/aggro/message_router_spec.rb +35 -0
  120. data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +21 -0
  121. data/spec/lib/aggro/nanomsg_transport_spec.rb +37 -0
  122. data/spec/lib/aggro/node_list_spec.rb +38 -0
  123. data/spec/lib/aggro/node_spec.rb +44 -0
  124. data/spec/lib/aggro/projection_spec.rb +22 -0
  125. data/spec/lib/aggro/query_spec.rb +47 -0
  126. data/spec/lib/aggro/saga_runner_spec.rb +84 -0
  127. data/spec/lib/aggro/saga_spec.rb +126 -0
  128. data/spec/lib/aggro/saga_status_spec.rb +56 -0
  129. data/spec/lib/aggro/server_spec.rb +118 -0
  130. data/spec/lib/aggro/subscriber_spec.rb +59 -0
  131. data/spec/lib/aggro/subscription_spec.rb +50 -0
  132. data/spec/lib/aggro/transform/boolean_spec.rb +23 -0
  133. data/spec/lib/aggro/transform/email_spec.rb +13 -0
  134. data/spec/lib/aggro/transform/id_spec.rb +70 -0
  135. data/spec/lib/aggro/transform/integer_spec.rb +30 -0
  136. data/spec/lib/aggro/transform/money_spec.rb +34 -0
  137. data/spec/lib/aggro/transform/string_spec.rb +15 -0
  138. data/spec/lib/aggro/transform/time_interval_spec.rb +29 -0
  139. data/spec/lib/aggro_spec.rb +63 -19
  140. data/spec/spec_helper.rb +21 -2
  141. metadata +283 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0390645ca46508f02d985623d99edef035ab1dbe
4
- data.tar.gz: d4835e34b8fbf89d108a9b349b603ce6f677de9d
3
+ metadata.gz: f0a86dcceeca3a9a1fd14a21861d052541ad178c
4
+ data.tar.gz: e9bd46d0f4ea83a97e355df1b8ddc4dd9ddc2022
5
5
  SHA512:
6
- metadata.gz: 20423f5017bd38745a381efa55e7e863269e9e568ea47456587a0977e2cd96c8edd147e97001dccaa0ea70a4a6c3f54e270c59a346b905130037b9a9b72ca20e
7
- data.tar.gz: ca72c456d6292cc417f2629449efed271189327b06da282be82b10cb269322d613029e6e0882f0ec5d3884e1acf36d7bef1da6e23ce3481929b7778978db8414
6
+ metadata.gz: ff83f579f3a4aae8abc6fda0b3f4f428b8b74266b11532c7ec4073d5299dde185f12c478a5a90b89c920befa29bd5e65d1d801fba30b5abd367ca4eb3d930fa7
7
+ data.tar.gz: f907f007f9cdb1b2a4fe7adb529cf2f37ee913a921c27f6337c7bfd2c599dcda4c122d08b883bda8197ac14065087f372fc696a79f4bc624515d61f6c80a92cc
@@ -3,6 +3,14 @@ AllCops:
3
3
  - aggro.gemspec
4
4
  - lib/aggro/version.rb
5
5
 
6
+ Documentation:
7
+ Exclude:
8
+ - spec/**/*
9
+
6
10
  Eval:
7
11
  Exclude:
8
12
  - spec/spec_helper.rb
13
+
14
+ RaiseArgs:
15
+ Exclude:
16
+ - lib/aggro/nanomsg_transport/connection.rb
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ - jruby-19mode
5
+ - ruby-head
6
+ - jruby-head
7
+ matrix:
8
+ allow_failures:
9
+ - rvm: jruby-19mode
10
+ - rvm: jruby-head
11
+ - rvm: ruby-head
12
+ script: bundle exec rspec
13
+ before_install:
14
+ - git clone git://github.com/nanomsg/nanomsg.git
15
+ - cd nanomsg && ./autogen.sh && ./configure && sudo make install && sudo ldconfig && cd ${TRAVIS_BUILD_DIR}
data/Gemfile CHANGED
@@ -2,3 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in aggro.gemspec
4
4
  gemspec
5
+
6
+ gem 'money'
7
+ gem 'monetize'
8
+
9
+ gem 'time-interval'
10
+
11
+ group :ci do
12
+ gem 'coveralls', require: false
13
+ end
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Aggro
2
2
 
3
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/SebastianEdwards/aggro.svg?branch=master)](https://travis-ci.org/SebastianEdwards/aggro)
4
+ [![Code Climate](https://codeclimate.com/github/SebastianEdwards/aggro/badges/gpa.svg)](https://codeclimate.com/github/SebastianEdwards/aggro)
5
+ [![Coverage Status](https://coveralls.io/repos/SebastianEdwards/aggro/badge.svg)](https://coveralls.io/r/SebastianEdwards/aggro)
6
+
7
+ Distributed in-memory event-store.
4
8
 
5
9
  ## Installation
6
10
 
data/Rakefile CHANGED
@@ -1 +1,11 @@
1
1
  require 'bundler/gem_tasks'
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+ rescue LoadError
10
+ puts 'no rspec available'
11
+ end
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Aggro::VERSION
9
9
  spec.authors = ['Sebastian Edwards']
10
10
  spec.email = ['me@sebastianedwards.co.nz']
11
- spec.summary = 'Distributed in-memory aggregates.'
11
+ spec.summary = 'Distributed in-memory event-store.'
12
12
  spec.description = ''
13
13
  spec.homepage = ''
14
14
  spec.license = 'MIT'
@@ -24,5 +24,12 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'guard-rspec'
25
25
  spec.add_development_dependency 'rubocop'
26
26
 
27
+ spec.add_runtime_dependency 'activemodel'
28
+ spec.add_runtime_dependency 'activesupport'
29
+ spec.add_runtime_dependency 'concurrent-ruby'
27
30
  spec.add_runtime_dependency 'consistent-hashing'
31
+ spec.add_runtime_dependency 'invokr'
32
+ spec.add_runtime_dependency 'nio4r'
33
+ spec.add_runtime_dependency 'nn-core'
34
+ spec.add_runtime_dependency 'object-stream'
28
35
  end
@@ -1,22 +1,206 @@
1
1
  require 'aggro/version'
2
2
 
3
+ require 'active_model'
4
+ require 'active_support/core_ext/hash/keys'
5
+ require 'concurrent'
3
6
  require 'consistent_hashing'
7
+ require 'invokr'
8
+ require 'fileutils'
9
+ require 'msgpack'
10
+ require 'object-stream'
11
+ require 'yaml'
4
12
 
13
+ # Private: Define methods to protect handlers from code reloading.
14
+ module Aggro
15
+ module_function
16
+
17
+ def class_attributes
18
+ @class_attributes ||= Hash.new { |hash, key| hash[key] = {} }
19
+ end
20
+
21
+ def command_handlers
22
+ @command_handlers ||= Hash.new { |hash, key| hash[key] = {} }
23
+ end
24
+
25
+ def query_handlers
26
+ @query_handlers ||= Hash.new { |hash, key| hash[key] = {} }
27
+ end
28
+
29
+ def step_handlers
30
+ @step_handlers ||= Hash.new { |hash, key| hash[key] = {} }
31
+ end
32
+ end
33
+
34
+ require 'aggro/abstract_store'
35
+ require 'aggro/attribute_dsl'
36
+ require 'aggro/event_dsl'
37
+
38
+ require 'aggro/message/ask'
39
+ require 'aggro/message/command'
40
+ require 'aggro/message/create_aggregate'
41
+ require 'aggro/message/endpoint'
42
+ require 'aggro/message/events'
43
+ require 'aggro/message/get_events'
44
+ require 'aggro/message/heartbeat'
45
+ require 'aggro/message/invalid_target'
46
+ require 'aggro/message/ok'
47
+ require 'aggro/message/publisher_endpoint_inquiry'
48
+ require 'aggro/message/query'
49
+ require 'aggro/message/result'
50
+ require 'aggro/message/start_saga'
51
+ require 'aggro/message/unhandled_operation'
52
+ require 'aggro/message/unknown_operation'
53
+
54
+ require 'aggro/handler/command'
55
+ require 'aggro/handler/create_aggregate'
56
+ require 'aggro/handler/get_events'
57
+ require 'aggro/handler/query'
58
+ require 'aggro/handler/start_saga'
59
+
60
+ require 'aggro/transform/boolean'
61
+ require 'aggro/transform/email'
62
+ require 'aggro/transform/id'
63
+ require 'aggro/transform/integer'
64
+ require 'aggro/transform/money'
65
+ require 'aggro/transform/noop'
66
+ require 'aggro/transform/string'
67
+ require 'aggro/transform/time_interval'
68
+
69
+ require 'aggro/aggregate'
5
70
  require 'aggro/aggregate_ref'
71
+ require 'aggro/binding_dsl'
72
+ require 'aggro/block_helper'
73
+ require 'aggro/channel'
74
+ require 'aggro/client'
75
+ require 'aggro/cluster_config'
76
+ require 'aggro/command'
77
+ require 'aggro/concurrent_actor'
78
+ require 'aggro/event_bus'
79
+ require 'aggro/event_proxy'
80
+ require 'aggro/event_serializer'
81
+ require 'aggro/file_store'
82
+ require 'aggro/local_node'
83
+ require 'aggro/locator'
84
+ require 'aggro/message_parser'
85
+ require 'aggro/message_router'
86
+ require 'aggro/nanomsg_transport'
87
+ require 'aggro/node'
88
+ require 'aggro/node_list'
89
+ require 'aggro/projection'
90
+ require 'aggro/query'
91
+ require 'aggro/saga'
92
+ require 'aggro/saga_runner'
93
+ require 'aggro/saga_status'
94
+ require 'aggro/server'
95
+ require 'aggro/subscriber'
96
+ require 'aggro/subscription'
6
97
 
7
98
  # Public: Module for namespacing and configuration methods.
8
99
  module Aggro
9
- def self.initialize_hash_ring(servers = servers_from_env)
10
- ConsistentHashing::Ring.new.tap do |ring|
11
- servers.each { |server| ring.add server }
100
+ ClientNode = Struct.new(:id)
101
+ Event = Struct.new(:name, :occured_at, :details)
102
+ EventArgument = Struct.new(:data, :type)
103
+ EventStream = Struct.new(:id, :type, :events)
104
+ QueryError = Struct.new(:cause)
105
+
106
+ MESSAGE_TYPES = Message
107
+ .constants
108
+ .map { |sym| Message.const_get sym }
109
+ .select { |m| m.const_defined? 'TYPE_CODE' }
110
+ .each_with_object({}) { |m, h| h.merge! m::TYPE_CODE => m }
111
+ .freeze
112
+
113
+ class << self
114
+ attr_writer :data_dir
115
+ attr_writer :port
116
+ attr_writer :publisher_port
117
+ attr_writer :transport
118
+ end
119
+
120
+ module_function
121
+
122
+ def channels
123
+ if cluster_config.server_node?
124
+ @channels ||= begin
125
+ Aggro.store.registry.reduce({}) do |channels, (id, type)|
126
+ channels.merge id => Channel.new(id, type)
127
+ end
128
+ end
129
+ else
130
+ @channels ||= {}
131
+ end
132
+ end
133
+
134
+ def cluster_config
135
+ @cluster_config ||= ClusterConfig.new cluster_config_path
136
+ end
137
+
138
+ def cluster_config_path
139
+ [data_dir, 'cluster.yml'].join('/')
140
+ end
141
+
142
+ def data_dir
143
+ @data_dir ||= begin
144
+ ENV.fetch('AGGRO_DIR') { './tmp/aggro' }.tap do |dir|
145
+ FileUtils.mkdir_p dir
146
+ end
147
+ end
148
+ end
149
+
150
+ def event_bus
151
+ @event_bus ||= EventBus.new
152
+ end
153
+
154
+ def local_node
155
+ if cluster_config.server_node?
156
+ @local_node ||= LocalNode.new(cluster_config.node_name)
157
+ else
158
+ @local_node ||= ClientNode.new(SecureRandom.uuid)
159
+ end
160
+ end
161
+
162
+ def node_list
163
+ @node_list ||= begin
164
+ NodeList.new.tap do |node_list|
165
+ nodes = cluster_config.nodes
166
+ nodes.each { |name, server| node_list.add Node.new(name, server) }
167
+ node_list.add local_node if cluster_config.server_node?
168
+ end
12
169
  end
13
170
  end
14
171
 
15
- def self.hash_ring
16
- @hash_ring ||= initialize_hash_ring
172
+ def port
173
+ @port ||= ENV.fetch('PORT') { 5000 }.to_i
174
+ end
175
+
176
+ def publisher_port
177
+ @publisher_port ||= ENV.fetch('PUBLISHER_PORT') { 6000 }.to_i
178
+ end
179
+
180
+ def reset
181
+ @cluster_config = nil
182
+ @event_bus.shutdown if @event_bus
183
+ @event_bus = nil
184
+ @local_node = nil
185
+ @node_list = nil
186
+ @port = nil
187
+ @publisher = nil
188
+ @publisher_port = nil
189
+ @server = nil
190
+ @store = nil
191
+ end
192
+
193
+ def server
194
+ return unless cluster_config.server_node?
195
+
196
+ @server ||= Server.new(local_node.endpoint, local_node.publisher_endpoint)
197
+ end
198
+
199
+ def store
200
+ @store ||= FileStore.new(data_dir)
17
201
  end
18
202
 
19
- def self.servers_from_env
20
- ENV['AGGRO_SERVERS'] ? ENV['AGGRO_SERVERS'].split(',') : []
203
+ def transport
204
+ @transport ||= NanomsgTransport
21
205
  end
22
206
  end
@@ -0,0 +1,12 @@
1
+ module Aggro
2
+ # Private: Abstract class for an event store.
3
+ class AbstractStore
4
+ def read(_refs)
5
+ fail NotImplementedError
6
+ end
7
+
8
+ def write(_event_streams)
9
+ fail NotImplementedError
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,98 @@
1
+ module Aggro
2
+ # Public: Mixin to turn a PORO into an Aggro aggregate.
3
+ module Aggregate
4
+ extend ActiveSupport::Concern
5
+ include EventDSL
6
+
7
+ def initialize(id)
8
+ @id = id
9
+
10
+ @projections = self.class.projections.reduce({}) do |h, (name, klass)|
11
+ class_eval { define_method(name) { @projections[name] } }
12
+ h.merge name => klass.new(id)
13
+ end
14
+
15
+ Aggro.event_bus.subscribe(id, self)
16
+ end
17
+
18
+ private
19
+
20
+ def apply_command(command)
21
+ return unless self.class.allows? command
22
+
23
+ @_context = command.attributes
24
+
25
+ handler = self.class.handler_for_command(command.class)
26
+ instance_exec command, &handler
27
+ ensure
28
+ @_context = nil
29
+ end
30
+
31
+ def did
32
+ fail 'Must be called within a command handler' unless @_context
33
+
34
+ @event_caller ||= EventProxy.new(self, @id)
35
+ end
36
+
37
+ def run_query(query)
38
+ return unless self.class.responds_to? query
39
+
40
+ handler = self.class.handler_for_query(query.class)
41
+ instance_exec query, &handler
42
+ rescue RuntimeError => e
43
+ QueryError.new(e)
44
+ end
45
+
46
+ class_methods do
47
+ def allows(command_class, &block)
48
+ command_handlers[command_class] = block if block
49
+ end
50
+
51
+ def allows?(command)
52
+ command_handlers.keys.include? command.class
53
+ end
54
+
55
+ def create(id = SecureRandom.uuid)
56
+ find(id).create
57
+ end
58
+
59
+ def find(id)
60
+ AggregateRef.new id, name
61
+ end
62
+
63
+ def handler_for_command(command_class)
64
+ command_handlers[command_class]
65
+ end
66
+
67
+ def handler_for_query(query_class)
68
+ query_handlers[query_class]
69
+ end
70
+
71
+ def projection(projection_name, via:)
72
+ projections[projection_name] = via
73
+ end
74
+
75
+ def projections
76
+ @projections ||= {}
77
+ end
78
+
79
+ def responds_to(query_class, &block)
80
+ query_handlers[query_class] = block if block
81
+ end
82
+
83
+ def responds_to?(query)
84
+ query_handlers.keys.include? query.class
85
+ end
86
+
87
+ private
88
+
89
+ def command_handlers
90
+ Aggro.command_handlers[name]
91
+ end
92
+
93
+ def query_handlers
94
+ Aggro.query_handlers[name]
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,14 +1,76 @@
1
1
  module Aggro
2
2
  # Public: Reference to an Aggregate which may be local or remote.
3
- class AggregateRef
4
- attr_reader :id
3
+ class AggregateRef < Struct.new(:id, :type)
4
+ def command(command)
5
+ response = send_command(command)
5
6
 
6
- def initialize(id)
7
- @id = id
7
+ if response.is_a? Message::InvalidTarget
8
+ create
9
+ response = send_command(command)
10
+ end
11
+
12
+ fail 'Could not send command' unless response.is_a? Message::OK
13
+
14
+ self
15
+ end
16
+
17
+ def create
18
+ response = client.post build_create_message
19
+
20
+ fail 'Could not create aggregate' unless response.is_a? Message::OK
21
+
22
+ self
23
+ end
24
+
25
+ def query(query)
26
+ response = send_query(query)
27
+
28
+ if response.is_a? Message::InvalidTarget
29
+ create
30
+ response = send_query(query)
31
+ end
32
+
33
+ handle_query_response response
34
+ end
35
+
36
+ private
37
+
38
+ def build_command_message(command)
39
+ Message::Command.new(Aggro.local_node.id, id, command.to_details)
40
+ end
41
+
42
+ def build_create_message
43
+ Message::CreateAggregate.new(Aggro.local_node.id, id, type)
44
+ end
45
+
46
+ def build_query_message(query)
47
+ Message::Query.new(Aggro.local_node.id, id, query.to_details)
48
+ end
49
+
50
+ def client
51
+ locator.primary_node.client
52
+ end
53
+
54
+ def handle_query_response(message)
55
+ fail 'Could not execute query' unless message.is_a? Message::Result
56
+
57
+ if message.result.is_a? Aggro::QueryError
58
+ fail message.result.cause
59
+ else
60
+ message.result
61
+ end
62
+ end
63
+
64
+ def locator
65
+ @locator ||= Locator.new(id)
66
+ end
67
+
68
+ def send_command(command)
69
+ client.post build_command_message(command)
8
70
  end
9
71
 
10
- def server
11
- @server ||= Aggro.hash_ring.node_for(id)
72
+ def send_query(query)
73
+ client.post build_query_message(query)
12
74
  end
13
75
  end
14
76
  end