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
@@ -0,0 +1,16 @@
1
+ require 'logger'
2
+
3
+ module Aggro
4
+ # Private: Mixin for logging concerns.
5
+ module Logging
6
+ include Logger::Severity
7
+
8
+ def log(level, progname, message = nil, &block)
9
+ (@logger || Aggro.logger).call level, progname, message, &block
10
+ rescue => e
11
+ $stderr.puts '`Aggro.logger` failed to log ' \
12
+ "#{[level, progname, message, block].join(' ')}\n" \
13
+ "#{e.message} (#{e.class})\n#{e.backtrace.join "\n"}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,123 @@
1
+ # Private: Wrapper around an IO object to read/write Marshaled objects.
2
+ class MarshalStream
3
+ include Enumerable
4
+
5
+ DEFAULT_MAX_OUTBOX = 10
6
+
7
+ class StreamError < StandardError; end
8
+
9
+ attr_reader :io
10
+ attr_reader :max_outbox
11
+
12
+ def initialize(io, max_outbox: DEFAULT_MAX_OUTBOX)
13
+ @io = io
14
+ @max_outbox = max_outbox
15
+ @inbox = []
16
+ @outbox = []
17
+ end
18
+
19
+ def close
20
+ flush_outbox
21
+ io.close
22
+ end
23
+
24
+ def closed?
25
+ io.closed?
26
+ end
27
+
28
+ def each
29
+ return to_enum unless block_given?
30
+
31
+ read { |obj| yield obj } until eof
32
+ end
33
+
34
+ def eof?
35
+ inbox.empty? && io.eof?
36
+ end
37
+
38
+ alias_method :eof, :eof?
39
+
40
+ def flush_buffer
41
+ self
42
+ end
43
+
44
+ def flush_outbox
45
+ outbox.each { |obj| write_to_stream(obj.is_a? Proc ? obj.call : obj) }
46
+ outbox.clear
47
+
48
+ self
49
+ end
50
+
51
+ def read
52
+ if block_given?
53
+ read_from_inbox { |obj| yield obj }
54
+ read_from_stream { |obj| yield obj }
55
+
56
+ nil
57
+ else
58
+ read_one
59
+ end
60
+ end
61
+
62
+ def read_from_stream
63
+ yield Marshal.load(io)
64
+ rescue IOError, SystemCallError
65
+ raise
66
+ rescue => e
67
+ raise StreamError, "Unreadble stream: #{e}"
68
+ end
69
+
70
+ def read_one
71
+ return inbox.shift unless inbox.empty?
72
+
73
+ result = nil
74
+
75
+ read { |obj| result.nil? ? result = obj : (inbox << obj) } while result.nil?
76
+
77
+ result
78
+ end
79
+
80
+ def to_io
81
+ io
82
+ end
83
+
84
+ def write(*objects)
85
+ write_to_buffer(*objects)
86
+ flush_buffer
87
+ end
88
+
89
+ alias_method :<<, :write
90
+
91
+ def write_to_buffer(*objects)
92
+ flush_outbox
93
+ objects.each { |object| write_to_stream object }
94
+
95
+ self
96
+ end
97
+
98
+ def write_to_outbox(object = nil, &block)
99
+ outbox << (block || object)
100
+
101
+ flush_outbox if outbox.size > max_outbox
102
+
103
+ self
104
+ end
105
+
106
+ def write_to_stream(object)
107
+ Marshal.dump(object, io)
108
+
109
+ self
110
+ end
111
+
112
+ private
113
+
114
+ attr_reader :inbox
115
+ attr_reader :outbox
116
+
117
+ def read_from_inbox
118
+ return if inbox.empty?
119
+
120
+ inbox.each { |obj| yield obj }
121
+ inbox.clear
122
+ end
123
+ end
@@ -9,11 +9,15 @@ module Aggro
9
9
  end
10
10
 
11
11
  def self.parse_events(string)
12
- ObjectStream.new(StringIO.new(string), type: 'marshal')
12
+ Enumerator.new do |yielder|
13
+ MarshalStream.new(StringIO.new(string)).each do |raw_event|
14
+ yielder << EventSerializer.deserialize(raw_event)
15
+ end
16
+ end
13
17
  end
14
18
 
15
19
  def serialize_events
16
- events.map { |event| EventSerializer.serialize event }.join
20
+ events.map { |event| Marshal.dump EventSerializer.serialize event }.join
17
21
  end
18
22
 
19
23
  def to_s
@@ -0,0 +1,20 @@
1
+ module Aggro
2
+ module Message
3
+ # Public: OK message.
4
+ class ServerError
5
+ TYPE_CODE = '00'.freeze
6
+
7
+ def self.parse(_string)
8
+ new
9
+ end
10
+
11
+ def self.new
12
+ @singleton ||= super
13
+ end
14
+
15
+ def to_s
16
+ "#{TYPE_CODE}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -20,7 +20,9 @@ module Aggro
20
20
  response = client.post(message)
21
21
 
22
22
  if response.is_a? Message::Endpoint
23
- response.endpoint
23
+ port = URI.parse(response.endpoint).port
24
+
25
+ URI.parse(endpoint).tap { |uri| uri.port = port }.to_s
24
26
  else
25
27
  fail "Could not discover publisher endpoint for #{id}"
26
28
  end
@@ -7,6 +7,7 @@ module Aggro
7
7
  include EventDSL
8
8
 
9
9
  def initialize(id)
10
+ @id = id
10
11
  Aggro.event_bus.subscribe(id, self)
11
12
  end
12
13
  end
@@ -35,8 +35,8 @@ module Aggro
35
35
  end
36
36
 
37
37
  def stop
38
- @transport_server.stop
39
38
  @transport_publisher.close_socket
39
+ @transport_server.stop
40
40
  end
41
41
 
42
42
  private
@@ -80,9 +80,5 @@ module Aggro
80
80
  end
81
81
  end
82
82
  end
83
-
84
- def publisher
85
- @publisher ||= Publisher.new(local_node.publisher_endpoint)
86
- end
87
83
  end
88
84
  end
@@ -1,6 +1,8 @@
1
1
  module Aggro
2
2
  # Private: Handles invoking events on a subscriber object.
3
3
  class Subscription
4
+ attr_reader :caught_up
5
+
4
6
  def initialize(topic, subscriber, namespace, filters, at_version)
5
7
  @topic = topic
6
8
  @subscriber = subscriber
@@ -21,15 +23,27 @@ module Aggro
21
23
  invoke(event) if handles_event?(event) && matches_filter?(event)
22
24
  end
23
25
 
26
+ def notify_subscription_caught_up
27
+ @caught_up = true
28
+
29
+ return unless @subscriber.handles_event? :caught_up, @namespace
30
+
31
+ @subscriber.send "#{@namespace}_caught_up"
32
+ end
33
+
24
34
  private
25
35
 
36
+ def expand_event(event)
37
+ event.details.merge now: event.occured_at, today: event.occured_at.to_date
38
+ end
39
+
26
40
  def handles_event?(event)
27
41
  @subscriber.handles_event? event.name, @namespace
28
42
  end
29
43
 
30
44
  def invoke(event)
31
- Invokr.invoke method: "#{@namespace}_#{event.name}",
32
- using: event.details, on: @subscriber
45
+ Invokr.invoke method: "#{@namespace}_#{event.name}", on: @subscriber,
46
+ using: expand_event(event)
33
47
  end
34
48
 
35
49
  def matches_filter?(event)
@@ -5,12 +5,24 @@ module Aggro
5
5
  module_function
6
6
 
7
7
  def deserialize(value)
8
- value if value == false || value == true
8
+ value if truthy?(value) || falsey?(value)
9
9
  end
10
10
 
11
11
  def serialize(value)
12
- value if value == false || value == true
12
+ value if truthy?(value) || falsey?(value)
13
13
  end
14
+
15
+ def falsey?(value)
16
+ value == false || value == 'false' || value == '0'
17
+ end
18
+
19
+ private :falsey?
20
+
21
+ def truthy?(value)
22
+ value == true || value == 'true' || value == '1'
23
+ end
24
+
25
+ private :truthy?
14
26
  end
15
27
  end
16
28
  end
@@ -0,0 +1,22 @@
1
+ module Aggro
2
+ module Transform
3
+ # Private: Transforms date representations.
4
+ module Date
5
+ module_function
6
+
7
+ def deserialize(value)
8
+ if value.is_a? ::String
9
+ ::Date.parse(value)
10
+ elsif value.is_a? ::Integer
11
+ ::Date.parse(value.to_s)
12
+ elsif value.is_a? ::Date
13
+ value
14
+ end
15
+ end
16
+
17
+ def serialize(value)
18
+ value.to_s if value.is_a? ::Date
19
+ end
20
+ end
21
+ end
22
+ end
@@ -5,11 +5,11 @@ module Aggro
5
5
  module_function
6
6
 
7
7
  def deserialize(value)
8
- value.to_s
8
+ value.to_s unless value.nil?
9
9
  end
10
10
 
11
11
  def serialize(value)
12
- value.to_s
12
+ value.to_s unless value.nil?
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,22 @@
1
+ module Aggro
2
+ module Transform
3
+ # Private: Transforms time representations.
4
+ module Time
5
+ module_function
6
+
7
+ def deserialize(value)
8
+ if value.is_a? ::String
9
+ ::Time.parse(value)
10
+ elsif value.is_a? ::Integer
11
+ ::Time.parse(value.to_s)
12
+ elsif value.is_a? ::Time
13
+ value
14
+ end
15
+ end
16
+
17
+ def serialize(value)
18
+ value.to_s if value.is_a? ::Time
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Aggro
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
@@ -0,0 +1,44 @@
1
+ require 'aggro/zeromq_transport/client'
2
+ require 'aggro/zeromq_transport/publisher'
3
+ require 'aggro/zeromq_transport/server'
4
+ require 'aggro/zeromq_transport/subscriber'
5
+
6
+ module Aggro
7
+ # Public: Transport layer over nanomsg sockets.
8
+ module ZeroMQTransport
9
+ class << self
10
+ attr_writer :linger
11
+ end
12
+
13
+ module_function
14
+
15
+ def client(endpoint)
16
+ Client.new endpoint
17
+ end
18
+
19
+ def context
20
+ @context ||= ZeroMQ::Context.new
21
+ end
22
+
23
+ def linger
24
+ @linger ||= 1_000
25
+ end
26
+
27
+ def publisher(endpoint)
28
+ Publisher.new endpoint
29
+ end
30
+
31
+ def server(endpoint, callable = nil, &block)
32
+ Server.new endpoint, callable, &block
33
+ end
34
+
35
+ def subscriber(endpoint, callable = nil, &block)
36
+ Subscriber.new endpoint, callable, &block
37
+ end
38
+
39
+ def teardown
40
+ @context.terminate if @context
41
+ @context = nil
42
+ end
43
+ end
44
+ end
@@ -1,23 +1,22 @@
1
- require 'aggro/nanomsg_transport/request'
2
-
3
1
  module Aggro
4
- module NanomsgTransport
2
+ module ZeroMQTransport
5
3
  # Public: Client for making requests against a nanomsg server.
6
4
  class Client
7
5
  def initialize(endpoint)
8
- ObjectSpace.define_finalizer self, method(:close_socket)
9
-
10
6
  @endpoint = endpoint
11
7
  end
12
8
 
13
9
  def post(message)
14
- request_socket.send_msg message
10
+ request_socket.send_string message.to_s
15
11
 
16
- request_socket.recv_msg
12
+ response = ''
13
+ request_socket.recv_string response
14
+
15
+ response
17
16
  end
18
17
 
19
18
  def close_socket
20
- request_socket.terminate if @open
19
+ request_socket.close if @open
21
20
  @request_socket = nil
22
21
  @open = false
23
22
  end
@@ -27,7 +26,10 @@ module Aggro
27
26
  def request_socket
28
27
  @request_socket ||= begin
29
28
  @open = true
30
- Request.new(@endpoint)
29
+ socket = ZeroMQTransport.context.socket(ZMQ::REQ)
30
+ socket.connect @endpoint
31
+
32
+ socket
31
33
  end
32
34
  end
33
35
  end
@@ -1,17 +1,15 @@
1
- require 'aggro/nanomsg_transport/publish'
2
-
3
1
  module Aggro
4
- module NanomsgTransport
2
+ module ZeroMQTransport
5
3
  # Public: Handles publishing messages on a given endpoint.
6
4
  class Publisher
7
5
  def initialize(endpoint)
8
- ObjectSpace.define_finalizer self, method(:close_socket)
9
-
10
6
  @endpoint = endpoint
11
7
  end
12
8
 
13
9
  def close_socket
14
- pub_socket.terminate if @open
10
+ return unless @open && @pub_socket
11
+
12
+ @pub_socket.close if @pub_socket
15
13
  @pub_socket = nil
16
14
  @open = false
17
15
  end
@@ -20,11 +18,16 @@ module Aggro
20
18
  return @pub_socket if @open
21
19
 
22
20
  @open = true
23
- @pub_socket = Publish.new(@endpoint)
21
+
22
+ @pub_socket = ZeroMQTransport.context.socket(ZMQ::PUB)
23
+ @pub_socket.setsockopt ZMQ::LINGER, 1_000
24
+ @pub_socket.bind @endpoint
25
+
26
+ @pub_socket
24
27
  end
25
28
 
26
29
  def publish(message)
27
- pub_socket.send_msg message
30
+ pub_socket.send_string message.to_s
28
31
  end
29
32
 
30
33
  private