aggro 0.0.3 → 0.0.4

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -5
  3. data/aggro.gemspec +1 -3
  4. data/lib/aggro.rb +25 -7
  5. data/lib/aggro/aggregate.rb +33 -6
  6. data/lib/aggro/attribute_dsl.rb +8 -0
  7. data/lib/aggro/client.rb +4 -0
  8. data/lib/aggro/cluster_config.rb +5 -0
  9. data/lib/aggro/concurrent_actor.rb +1 -1
  10. data/lib/aggro/event_bus.rb +5 -2
  11. data/lib/aggro/event_serializer.rb +2 -2
  12. data/lib/aggro/file_store.rb +2 -2
  13. data/lib/aggro/file_store/reader.rb +11 -3
  14. data/lib/aggro/file_store/writer.rb +2 -2
  15. data/lib/aggro/handler/query.rb +19 -7
  16. data/lib/aggro/logging.rb +16 -0
  17. data/lib/aggro/marshal_stream.rb +123 -0
  18. data/lib/aggro/message/events.rb +6 -2
  19. data/lib/aggro/message/server_error.rb +20 -0
  20. data/lib/aggro/node.rb +3 -1
  21. data/lib/aggro/projection.rb +1 -0
  22. data/lib/aggro/server.rb +1 -5
  23. data/lib/aggro/subscription.rb +16 -2
  24. data/lib/aggro/transform/boolean.rb +14 -2
  25. data/lib/aggro/transform/date.rb +22 -0
  26. data/lib/aggro/transform/string.rb +2 -2
  27. data/lib/aggro/transform/time.rb +22 -0
  28. data/lib/aggro/version.rb +1 -1
  29. data/lib/aggro/zeromq_transport.rb +44 -0
  30. data/lib/aggro/{nanomsg_transport → zeromq_transport}/client.rb +11 -9
  31. data/lib/aggro/{nanomsg_transport → zeromq_transport}/publisher.rb +11 -8
  32. data/lib/aggro/zeromq_transport/server.rb +92 -0
  33. data/lib/aggro/{nanomsg_transport → zeromq_transport}/subscriber.rb +23 -24
  34. data/spec/lib/aggro/event_serializer_spec.rb +1 -1
  35. data/spec/lib/aggro/file_store/reader_spec.rb +2 -1
  36. data/spec/lib/aggro/file_store/writer_spec.rb +10 -7
  37. data/spec/lib/aggro/local_node_spec.rb +2 -2
  38. data/spec/lib/aggro/marshal_stream_spec.rb +17 -0
  39. data/spec/lib/aggro/message/events_spec.rb +4 -3
  40. data/spec/lib/aggro/node_spec.rb +3 -3
  41. data/spec/lib/aggro/subscription_spec.rb +4 -2
  42. data/spec/lib/aggro/{nanomsg_transport_spec.rb → zeromq_transport_spec.rb} +11 -7
  43. data/spec/spec_helper.rb +8 -1
  44. metadata +17 -50
  45. data/lib/aggro/abstract_store.rb +0 -12
  46. data/lib/aggro/nanomsg_transport/connection.rb +0 -98
  47. data/lib/aggro/nanomsg_transport/publish.rb +0 -17
  48. data/lib/aggro/nanomsg_transport/raw_reply.rb +0 -18
  49. data/lib/aggro/nanomsg_transport/raw_request.rb +0 -18
  50. data/lib/aggro/nanomsg_transport/reply.rb +0 -17
  51. data/lib/aggro/nanomsg_transport/request.rb +0 -17
  52. data/lib/aggro/nanomsg_transport/server.rb +0 -84
  53. data/lib/aggro/nanomsg_transport/socket_error.rb +0 -20
  54. data/lib/aggro/nanomsg_transport/subscribe.rb +0 -27
  55. data/spec/lib/aggro/abstract_store_spec.rb +0 -15
  56. data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a2dbb5a149c0626edec59aacb503a28aa93c152
4
- data.tar.gz: f7285648aa894911f91f15734278542ff0a1fcd8
3
+ metadata.gz: 87f15442c120f8553416f7cbb2b18e345a1dd0a3
4
+ data.tar.gz: 7963f8f2fbec1eea359339daa103b52eef7e450d
5
5
  SHA512:
6
- metadata.gz: 99b6e4dec758672a0bce1be028c1d9c212a2a8cf1569485b8bed67dc09a26343e541b02618056dfc28f8afe790f44306a6e10951215ac95f555165a326efd796
7
- data.tar.gz: 3d87f27e547b8475e7f4055d8438513e02830bff4d8aaf1ebdab4533f1680758ca625f45fd2f2db0ee5af3eea048b7aef200bcdd88b3d074ad56d50c159572b9
6
+ metadata.gz: 00dddfea09997a76c17f5ed118b52be604dd4e9b412f3cfc1cacc58656a3a7b87bb2aad41eeb64ee64a2b2583e307816987a85af2bf7d4a3385f54574fe71999
7
+ data.tar.gz: c99308f889a0bf8906515dbbbf36025cb322d1cabf27a6b4e58666a780bda2d784055dc57feb48da00da6e06e1136eeef814059b8508005b9999679996e9f378
@@ -1,15 +1,11 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.2.0
4
- - jruby-19mode
5
4
  - ruby-head
6
5
  - jruby-head
7
6
  matrix:
8
7
  allow_failures:
9
- - rvm: jruby-19mode
10
8
  - rvm: jruby-head
11
9
  - rvm: ruby-head
12
10
  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}
11
+ before_install: sudo apt-get install libzmq3-dev
@@ -29,7 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.add_runtime_dependency 'concurrent-ruby'
30
30
  spec.add_runtime_dependency 'consistent-hashing'
31
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'
32
+ spec.add_runtime_dependency 'zeromqrb'
35
33
  end
@@ -6,9 +6,9 @@ require 'concurrent'
6
6
  require 'consistent_hashing'
7
7
  require 'invokr'
8
8
  require 'fileutils'
9
- require 'msgpack'
10
- require 'object-stream'
9
+ require 'uri'
11
10
  require 'yaml'
11
+ require 'zero_mq'
12
12
 
13
13
  # Private: Define methods to protect handlers from code reloading.
14
14
  module Aggro
@@ -31,9 +31,9 @@ module Aggro
31
31
  end
32
32
  end
33
33
 
34
- require 'aggro/abstract_store'
35
34
  require 'aggro/attribute_dsl'
36
35
  require 'aggro/event_dsl'
36
+ require 'aggro/logging'
37
37
 
38
38
  require 'aggro/message/ask'
39
39
  require 'aggro/message/command'
@@ -47,6 +47,7 @@ require 'aggro/message/ok'
47
47
  require 'aggro/message/publisher_endpoint_inquiry'
48
48
  require 'aggro/message/query'
49
49
  require 'aggro/message/result'
50
+ require 'aggro/message/server_error'
50
51
  require 'aggro/message/start_saga'
51
52
  require 'aggro/message/unhandled_operation'
52
53
  require 'aggro/message/unknown_operation'
@@ -58,12 +59,14 @@ require 'aggro/handler/query'
58
59
  require 'aggro/handler/start_saga'
59
60
 
60
61
  require 'aggro/transform/boolean'
62
+ require 'aggro/transform/date'
61
63
  require 'aggro/transform/email'
62
64
  require 'aggro/transform/id'
63
65
  require 'aggro/transform/integer'
64
66
  require 'aggro/transform/money'
65
67
  require 'aggro/transform/noop'
66
68
  require 'aggro/transform/string'
69
+ require 'aggro/transform/time'
67
70
  require 'aggro/transform/time_interval'
68
71
 
69
72
  require 'aggro/aggregate'
@@ -81,9 +84,9 @@ require 'aggro/event_serializer'
81
84
  require 'aggro/file_store'
82
85
  require 'aggro/local_node'
83
86
  require 'aggro/locator'
87
+ require 'aggro/marshal_stream'
84
88
  require 'aggro/message_parser'
85
89
  require 'aggro/message_router'
86
- require 'aggro/nanomsg_transport'
87
90
  require 'aggro/node'
88
91
  require 'aggro/node_list'
89
92
  require 'aggro/projection'
@@ -94,6 +97,7 @@ require 'aggro/saga_status'
94
97
  require 'aggro/server'
95
98
  require 'aggro/subscriber'
96
99
  require 'aggro/subscription'
100
+ require 'aggro/zeromq_transport'
97
101
 
98
102
  # Public: Module for namespacing and configuration methods.
99
103
  module Aggro
@@ -112,6 +116,7 @@ module Aggro
112
116
 
113
117
  class << self
114
118
  attr_writer :data_dir
119
+ attr_writer :logger
115
120
  attr_writer :port
116
121
  attr_writer :publisher_port
117
122
  attr_writer :transport
@@ -159,6 +164,10 @@ module Aggro
159
164
  end
160
165
  end
161
166
 
167
+ def logger
168
+ @logger ||= -> (_level, _progname, _message = nil, &_block) {}
169
+ end
170
+
162
171
  def node_list
163
172
  @node_list ||= begin
164
173
  NodeList.new.tap do |node_list|
@@ -182,14 +191,23 @@ module Aggro
182
191
  @event_bus.shutdown if @event_bus
183
192
  @event_bus = nil
184
193
  @local_node = nil
185
- @node_list = nil
194
+ reset_clients && @node_list = nil
186
195
  @port = nil
187
- @publisher = nil
188
196
  @publisher_port = nil
197
+ @server.stop if @server
189
198
  @server = nil
190
199
  @store = nil
191
200
  end
192
201
 
202
+ def reset_clients
203
+ return unless @node_list
204
+
205
+ @node_list.nodes
206
+ .select { |node| node.is_a? Node }
207
+ .map(&:client)
208
+ .each(&:disconnect!)
209
+ end
210
+
193
211
  def server
194
212
  return unless cluster_config.server_node?
195
213
 
@@ -201,6 +219,6 @@ module Aggro
201
219
  end
202
220
 
203
221
  def transport
204
- @transport ||= NanomsgTransport
222
+ @transport ||= ZeroMQTransport
205
223
  end
206
224
  end
@@ -3,16 +3,15 @@ module Aggro
3
3
  module Aggregate
4
4
  extend ActiveSupport::Concern
5
5
  include EventDSL
6
+ include Logging
6
7
 
7
8
  def initialize(id)
8
9
  @id = id
9
10
 
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
11
+ start_projections
12
+ restore_from_event_stream
14
13
 
15
- Aggro.event_bus.subscribe(id, self)
14
+ log INFO, 'Restored to memory'
16
15
  end
17
16
 
18
17
  private
@@ -24,6 +23,10 @@ module Aggro
24
23
 
25
24
  handler = self.class.handler_for_command(command.class)
26
25
  instance_exec command, &handler
26
+ rescue => e
27
+ log ERROR, "Couldn't apply command\n#{e}\n#{e.backtrace.join "\n"}"
28
+
29
+ raise e
27
30
  ensure
28
31
  @_context = nil
29
32
  end
@@ -34,15 +37,35 @@ module Aggro
34
37
  @event_caller ||= EventProxy.new(self, @id)
35
38
  end
36
39
 
40
+ def log(level, message, &block)
41
+ super level, "#{self.class}/#{@id}", message, &block
42
+ end
43
+
44
+ def restore_from_event_stream
45
+ Aggro.event_bus.subscribe(@id, self)
46
+ rescue => e
47
+ log FATAL, "Couldn't restore from events\n#{e}\n#{e.backtrace.join "\n"}"
48
+ end
49
+
37
50
  def run_query(query)
38
51
  return unless self.class.responds_to? query
39
52
 
40
53
  handler = self.class.handler_for_query(query.class)
41
54
  instance_exec query, &handler
42
- rescue RuntimeError => e
55
+ rescue => e
56
+ log ERROR, "Couldn't complete query\n#{e}\n#{e.backtrace.join "\n"}"
43
57
  QueryError.new(e)
44
58
  end
45
59
 
60
+ def start_projections
61
+ @projections = self.class.projections.reduce({}) do |h, (name, klass)|
62
+ class_eval { define_method(name) { @projections[name] } }
63
+ h.merge name => klass.new(@id)
64
+ end
65
+ rescue => e
66
+ log FATAL, "Couldn't start projections\n#{e}\n#{e.backtrace.join "\n"}"
67
+ end
68
+
46
69
  class_methods do
47
70
  def allows(command_class, &block)
48
71
  command_handlers[command_class] = block if block
@@ -53,10 +76,14 @@ module Aggro
53
76
  end
54
77
 
55
78
  def create(id = SecureRandom.uuid)
79
+ fail ArgumentError unless id && id.length == 36
80
+
56
81
  find(id).create
57
82
  end
58
83
 
59
84
  def find(id)
85
+ fail ArgumentError unless id && id.length == 36
86
+
60
87
  AggregateRef.new id, name
61
88
  end
62
89
 
@@ -43,6 +43,10 @@ module Aggro
43
43
  create_attrs name, Transform::Boolean
44
44
  end
45
45
 
46
+ def date(name)
47
+ create_attrs name, Transform::Date
48
+ end
49
+
46
50
  def email(name)
47
51
  create_attrs name, Transform::Email
48
52
  end
@@ -72,6 +76,10 @@ module Aggro
72
76
  create_attrs name, Transform::String
73
77
  end
74
78
 
79
+ def time(name)
80
+ create_attrs name, Transform::Time
81
+ end
82
+
75
83
  def time_interval(name)
76
84
  require 'time-interval'
77
85
 
@@ -5,6 +5,10 @@ module Aggro
5
5
  @transport_client = Aggro.transport.client(endpoint)
6
6
  end
7
7
 
8
+ def disconnect!
9
+ @transport_client.close_socket
10
+ end
11
+
8
12
  def post(message)
9
13
  MessageParser.parse @transport_client.post message
10
14
  end
@@ -23,6 +23,11 @@ module Aggro
23
23
  @nodes ||= {}.freeze
24
24
  end
25
25
 
26
+ def server_node=(value)
27
+ @is_server_node = value
28
+ persist_config
29
+ end
30
+
26
31
  def server_node?
27
32
  @is_server_node == true
28
33
  end
@@ -1,6 +1,6 @@
1
1
  module Aggro
2
2
  # Private: Wraps a given target in an concurrent actor.
3
- class ConcurrentActor < Concurrent::Actor::Context
3
+ class ConcurrentActor < Concurrent::Actor::RestartingContext
4
4
  def initialize(target)
5
5
  @target = target
6
6
  end
@@ -1,6 +1,8 @@
1
1
  module Aggro
2
2
  # Public: Publishes events to any subscribed listeners.
3
3
  class EventBus
4
+ attr_reader :remote_publishers
5
+
4
6
  def initialize
5
7
  @remote_publishers = {}
6
8
  end
@@ -11,6 +13,7 @@ module Aggro
11
13
  return unless subscriptions.key? topic
12
14
 
13
15
  subscriptions[topic].each do |subscription|
16
+ sleep 0.01 until subscription.caught_up
14
17
  subscription.handle_event event
15
18
  end
16
19
  end
@@ -39,8 +42,6 @@ module Aggro
39
42
 
40
43
  private
41
44
 
42
- attr_reader :remote_publishers
43
-
44
45
  def catchup_local(topic, subscription)
45
46
  Aggro.store.read([topic]).first.events.each do |event|
46
47
  subscription.handle_event event
@@ -66,6 +67,8 @@ module Aggro
66
67
  else
67
68
  catchup_remote(topic, subscription, node)
68
69
  end
70
+
71
+ subscription.notify_subscription_caught_up
69
72
  end
70
73
 
71
74
  def handle_events(topic, events)
@@ -4,11 +4,11 @@ module Aggro
4
4
  module_function
5
5
 
6
6
  def deserialize(serialized)
7
- Marshal.load serialized
7
+ Event.new(serialized[0], Time.parse(serialized[1]), serialized[2])
8
8
  end
9
9
 
10
10
  def serialize(deserialized)
11
- Marshal.dump deserialized
11
+ [deserialized.name, deserialized.occured_at.iso8601, deserialized.details]
12
12
  end
13
13
  end
14
14
  end
@@ -3,7 +3,7 @@ require 'aggro/file_store/writer'
3
3
 
4
4
  module Aggro
5
5
  # Public: Stores and retrieves events by serializing them to flat files.
6
- class FileStore < AbstractStore
6
+ class FileStore
7
7
  INDEX_DIRECTORY = 'indexes'.freeze
8
8
  EVENT_DIRECTORY = 'events'.freeze
9
9
  REGISTRY_FILE = 'registry'.freeze
@@ -84,7 +84,7 @@ module Aggro
84
84
 
85
85
  def initialize_registry
86
86
  File.open(@registry_file) do |file|
87
- ObjectStream.new(file, type: 'marshal').each do |id, type|
87
+ MarshalStream.new(file).each do |id, type|
88
88
  registry[id] = type
89
89
  end
90
90
  end
@@ -1,5 +1,5 @@
1
1
  module Aggro
2
- class FileStore < AbstractStore
2
+ class FileStore
3
3
  # Private: Deserialized events from an IO object.
4
4
  class Reader
5
5
  def initialize(data_io, index_io)
@@ -8,13 +8,21 @@ module Aggro
8
8
  end
9
9
 
10
10
  def read
11
- ObjectStream.new(@data_io, type: 'marshal')
11
+ Enumerator.new do |yielder|
12
+ stream.each do |raw_event|
13
+ yielder << EventSerializer.deserialize(raw_event)
14
+ end
15
+ end
12
16
  end
13
17
 
14
18
  private
15
19
 
16
20
  def index
17
- @index ||= ObjectStream.new(@index_io, type: 'marshal')
21
+ @index ||= MarshalStream.new @index_io
22
+ end
23
+
24
+ def stream
25
+ @stream ||= MarshalStream.new @data_io
18
26
  end
19
27
  end
20
28
  end
@@ -1,5 +1,5 @@
1
1
  module Aggro
2
- class FileStore < AbstractStore
2
+ class FileStore
3
3
  # Private: Serializes events to an IO object.
4
4
  class Writer
5
5
  def initialize(data_io, index_io)
@@ -9,7 +9,7 @@ module Aggro
9
9
 
10
10
  def write(events)
11
11
  events.each do |event|
12
- @data_io.write EventSerializer.serialize(event)
12
+ @data_io.write Marshal.dump EventSerializer.serialize(event)
13
13
  write_to_index @data_io.pos
14
14
  end
15
15
 
@@ -37,15 +37,15 @@ module Aggro
37
37
  end
38
38
 
39
39
  def handle_known
40
- if channel.handles_query?(query)
41
- result = channel.run_query(query)
42
-
43
- Message::Result.new result.value(5)
40
+ if channel
41
+ if channel.handles_query?(query)
42
+ handle_supported
43
+ else
44
+ Message::UnhandledOperation.new
45
+ end
44
46
  else
45
- Message::UnhandledOperation.new
47
+ Message::InvalidTarget.new
46
48
  end
47
- rescue NoMethodError
48
- Message::InvalidTarget.new
49
49
  end
50
50
 
51
51
  def handle_local
@@ -55,6 +55,18 @@ module Aggro
55
55
  def handle_unknown
56
56
  Message::UnknownOperation.new
57
57
  end
58
+
59
+ def handle_supported
60
+ result = channel.run_query(query)
61
+
62
+ result.wait(5)
63
+
64
+ if result.fulfilled?
65
+ Message::Result.new result.value
66
+ else
67
+ Message::Result.new Aggro::QueryError.new('Query timed out')
68
+ end
69
+ end
58
70
  end
59
71
  end
60
72
  end