aggro 0.0.1 → 0.0.2

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