aggro 0.0.1 → 0.0.2

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 (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
@@ -0,0 +1,35 @@
1
+ require 'aggro/nanomsg_transport/request'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Public: Client for making requests against a nanomsg server.
6
+ class Client
7
+ def initialize(endpoint)
8
+ ObjectSpace.define_finalizer self, method(:close_socket)
9
+
10
+ @endpoint = endpoint
11
+ end
12
+
13
+ def post(message)
14
+ request_socket.send_msg message
15
+
16
+ request_socket.recv_msg
17
+ end
18
+
19
+ def close_socket
20
+ request_socket.terminate if @open
21
+ @request_socket = nil
22
+ @open = false
23
+ end
24
+
25
+ private
26
+
27
+ def request_socket
28
+ @request_socket ||= begin
29
+ @open = true
30
+ Request.new(@endpoint)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,98 @@
1
+ require 'ffi'
2
+ require 'nn-core'
3
+ require 'aggro/nanomsg_transport/socket_error'
4
+
5
+ module Aggro
6
+ module NanomsgTransport
7
+ # Private: Base class for nanomsg socket wrappers.
8
+ class Connection
9
+ attr_reader :socket
10
+
11
+ def initialize(endpoint)
12
+ @endpoint = endpoint
13
+ @rcv_buffer = FFI::MemoryPointer.new(:pointer)
14
+
15
+ allocate_socket
16
+ setup_socket
17
+ set_endpoint
18
+ end
19
+
20
+ def setup_socket
21
+ set_socket_option NNCore::NN_LINGER, 100
22
+ set_socket_option NNCore::NN_SNDBUF, 131_072
23
+ set_socket_option NNCore::NN_RCVBUF, 131_072
24
+ end
25
+
26
+ def send_msg(stringable)
27
+ msg = stringable.to_s
28
+ nbytes = NNCore::LibNanomsg.nn_send(@socket, msg, msg.bytesize, 0)
29
+ assert(nbytes)
30
+
31
+ nbytes
32
+ end
33
+
34
+ def recv_fd
35
+ get_socket_option NNCore::NN_RCVFD
36
+ end
37
+
38
+ def recv_msg
39
+ nbytes = NNCore::LibNanomsg.nn_recv(@socket, @rcv_buffer,
40
+ NNCore::NN_MSG, 0)
41
+
42
+ assert(nbytes)
43
+
44
+ str = @rcv_buffer.read_pointer
45
+ response = str.read_string(nbytes)
46
+ NNCore::LibNanomsg.nn_freemsg str
47
+
48
+ response
49
+ rescue SocketError => e
50
+ raise e unless e.errno == NNCore::EAGAIN
51
+
52
+ nil
53
+ end
54
+
55
+ def terminate
56
+ assert NNCore::LibNanomsg.nn_close(@socket)
57
+ end
58
+
59
+ protected
60
+
61
+ def assert(rc)
62
+ fail SocketError.new NNCore::LibNanomsg.nn_errno unless rc >= 0
63
+ end
64
+
65
+ def get_socket_option(setting, level = NNCore::NN_SOL_SOCKET)
66
+ result = FFI::MemoryPointer.new(:int32)
67
+ size = FFI::MemoryPointer.new(:size_t)
68
+ size.write_int result.size
69
+
70
+ rc = NNCore::LibNanomsg.nn_getsockopt(@socket, level, setting,
71
+ result, size)
72
+
73
+ assert(rc)
74
+
75
+ result.read_int
76
+ end
77
+
78
+ def prepare_socket_option(value)
79
+ if value.is_a? String
80
+ [value, value.bytesize]
81
+ else
82
+ option = FFI::MemoryPointer.new(:int32)
83
+ option.write_int(value)
84
+
85
+ [option, 4]
86
+ end
87
+ end
88
+
89
+ def set_socket_option(setting, value, level = NNCore::NN_SOL_SOCKET)
90
+ option, option_length = prepare_socket_option(value)
91
+
92
+ rc = NNCore::LibNanomsg.nn_setsockopt(@socket, level, setting,
93
+ option, option_length)
94
+ assert(rc)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg PUB node.
6
+ class Publish < Connection
7
+ def allocate_socket
8
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP, NNCore::NN_PUB)
9
+ assert @socket
10
+ end
11
+
12
+ def set_endpoint
13
+ assert NNCore::LibNanomsg.nn_bind(@socket, @endpoint)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ require 'aggro/nanomsg_transport/publish'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Public: Handles publishing messages on a given endpoint.
6
+ class Publisher
7
+ def initialize(endpoint)
8
+ ObjectSpace.define_finalizer self, method(:close_socket)
9
+
10
+ @endpoint = endpoint
11
+ end
12
+
13
+ def close_socket
14
+ pub_socket.terminate if @open
15
+ @pub_socket = nil
16
+ @open = false
17
+ end
18
+
19
+ def open_socket
20
+ return @pub_socket if @open
21
+
22
+ @open = true
23
+ @pub_socket = Publish.new(@endpoint)
24
+ end
25
+
26
+ def publish(message)
27
+ pub_socket.send_msg message
28
+ end
29
+
30
+ private
31
+
32
+ def pub_socket
33
+ @pub_socket || open_socket
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg XREP node.
6
+ class RawReply < Connection
7
+ def allocate_socket
8
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP_RAW,
9
+ NNCore::NN_REP)
10
+ assert @socket
11
+ end
12
+
13
+ def set_endpoint
14
+ assert NNCore::LibNanomsg.nn_bind(@socket, @endpoint)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg XREQ node.
6
+ class RawRequest < Connection
7
+ def allocate_socket
8
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP_RAW,
9
+ NNCore::NN_REQ)
10
+ assert @socket
11
+ end
12
+
13
+ def set_endpoint
14
+ assert NNCore::LibNanomsg.nn_bind(@socket, @endpoint)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg REP node.
6
+ class Reply < Connection
7
+ def allocate_socket
8
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP, NNCore::NN_REP)
9
+ assert @socket
10
+ end
11
+
12
+ def set_endpoint
13
+ assert NNCore::LibNanomsg.nn_connect(@socket, @endpoint)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg REQ node.
6
+ class Request < Connection
7
+ def allocate_socket
8
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP, NNCore::NN_REQ)
9
+ assert @socket
10
+ end
11
+
12
+ def set_endpoint
13
+ assert NNCore::LibNanomsg.nn_connect(@socket, @endpoint)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,84 @@
1
+ require 'nio'
2
+
3
+ require 'aggro/nanomsg_transport/raw_reply'
4
+ require 'aggro/nanomsg_transport/raw_request'
5
+ require 'aggro/nanomsg_transport/reply'
6
+
7
+ module Aggro
8
+ module NanomsgTransport
9
+ # Public: Server to handle messages from nanomsg clients.
10
+ class Server
11
+ DEFAULT_WORKER_COUNT = 16
12
+
13
+ class ServerAlreadyRunning < RuntimeError; end
14
+
15
+ def initialize(endpoint, callable = nil, &block)
16
+ fail ArgumentError unless callable || block_given?
17
+
18
+ @callable = block_given? ? block : callable
19
+ @endpoint = endpoint
20
+ @selectors = DEFAULT_WORKER_COUNT.times.map { NIO::Selector.new }
21
+ @inproc_endpoint = "inproc://aggro-server-#{SecureRandom.hex}"
22
+
23
+ ObjectSpace.define_finalizer self, method(:stop)
24
+ end
25
+
26
+ def start
27
+ fail ServerAlreadyRunning if @running
28
+
29
+ @running = true
30
+ start_master
31
+ DEFAULT_WORKER_COUNT.times { |i| start_worker i }
32
+
33
+ sleep 0.01 while @selectors.any?(&:empty?)
34
+
35
+ self
36
+ end
37
+
38
+ def stop
39
+ return self unless @running
40
+
41
+ @running = false
42
+ @selectors.each(&:wakeup)
43
+
44
+ sleep 0.01 until @selectors.any?(&:empty?)
45
+
46
+ @raw_reply.terminate
47
+ @raw_request.terminate
48
+
49
+ self
50
+ end
51
+
52
+ private
53
+
54
+ def handle_request(socket)
55
+ message = socket.recv_msg
56
+ socket.send_msg @callable.call(message) if @running
57
+ end
58
+
59
+ def start_master
60
+ @master_thread = Concurrent::SingleThreadExecutor.new.post do
61
+ @raw_reply = RawReply.new(@endpoint)
62
+ @raw_request = RawRequest.new(@inproc_endpoint)
63
+
64
+ NNCore::LibNanomsg.nn_device @raw_request.socket, @raw_reply.socket
65
+ end
66
+ end
67
+
68
+ def start_worker(i)
69
+ Concurrent::SingleThreadExecutor.new.post do
70
+ reply = Reply.new(@inproc_endpoint)
71
+ io = IO.new(reply.recv_fd, 'rb', autoclose: false)
72
+
73
+ @selectors[i].register io, :r
74
+
75
+ @selectors[i].select { handle_request(reply) } while @running
76
+
77
+ @selectors[i].deregister io
78
+ io.close
79
+ reply.terminate
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,20 @@
1
+ module Aggro
2
+ module NanomsgTransport
3
+ # Public: An error calling the nanomsg API.
4
+ class SocketError < RuntimeError
5
+ attr_reader :errno
6
+
7
+ def initialize(errno)
8
+ @errno = errno
9
+ end
10
+
11
+ def error_message
12
+ NNCore::LibNanomsg.nn_strerror(errno)
13
+ end
14
+
15
+ def to_s
16
+ "Last nanomsg API call failed with '#{error_message}'"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'aggro/nanomsg_transport/connection'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Private: Wrapper for a nanomsg SUB node.
6
+ class Subscribe < Connection
7
+ def add_subscription(topic)
8
+ set_socket_option NNCore::NN_SUB_SUBSCRIBE, topic, NNCore::NN_SUB
9
+ end
10
+
11
+ def allocate_socket
12
+ @socket = NNCore::LibNanomsg.nn_socket(NNCore::AF_SP, NNCore::NN_SUB)
13
+ assert @socket
14
+ end
15
+
16
+ def set_endpoint
17
+ assert NNCore::LibNanomsg.nn_connect(@socket, @endpoint)
18
+ end
19
+
20
+ def setup_socket
21
+ super
22
+
23
+ set_socket_option NNCore::NN_RCVTIMEO, 100
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ require 'aggro/nanomsg_transport/subscribe'
2
+
3
+ module Aggro
4
+ module NanomsgTransport
5
+ # Public: Handles subscribing to messages on a given endpoint.
6
+ class Subscriber
7
+ class SubscriberAlreadyRunning < RuntimeError; end
8
+
9
+ def initialize(endpoint, callable = nil, &block)
10
+ fail ArgumentError unless callable || block_given?
11
+
12
+ @callable = block_given? ? block : callable
13
+ @endpoint = endpoint
14
+ @mutex = Mutex.new
15
+ @selector = NIO::Selector.new
16
+
17
+ ObjectSpace.define_finalizer self, method(:stop)
18
+ end
19
+
20
+ def add_subscription(topic)
21
+ start unless @running
22
+
23
+ @mutex.synchronize do
24
+ sub_socket.add_subscription(topic)
25
+ end
26
+
27
+ self
28
+ end
29
+
30
+ def start
31
+ @mutex.synchronize do
32
+ return self if @running
33
+
34
+ @running = true
35
+ start_on_thread
36
+
37
+ sleep 0.01 while @selector.empty?
38
+ end
39
+
40
+ self
41
+ end
42
+
43
+ def stop
44
+ @mutex.synchronize do
45
+ return self unless @running
46
+
47
+ @running = false
48
+ @selector.wakeup
49
+
50
+ sleep 0.01 until @selector.empty?
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ private
57
+
58
+ def handle_message
59
+ message = sub_socket.recv_msg
60
+ @callable.call(message) if message
61
+ end
62
+
63
+ def sub_socket
64
+ @sub_socket ||= Subscribe.new(@endpoint)
65
+ end
66
+
67
+ def start_on_thread
68
+ Concurrent::SingleThreadExecutor.new.post do
69
+ io = IO.new(sub_socket.recv_fd, 'rb', autoclose: false)
70
+ @selector.register io, :r
71
+
72
+ @selector.select { handle_message } while @running
73
+
74
+ @selector.deregister io
75
+ io.close
76
+ sub_socket.terminate
77
+ @sub_socket = nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end