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
@@ -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