aggro 0.0.3 → 0.0.4

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