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,23 @@
1
+ module Aggro
2
+ # Private: Used as a proxy to apply and save events to an aggregate.
3
+ class EventProxy
4
+ def initialize(aggregate, id)
5
+ @aggregate = aggregate
6
+ @id = id
7
+ end
8
+
9
+ def method_missing(method_sym, *args)
10
+ details = merge_details_with_command_context(args.pop || {})
11
+ event = Event.new(method_sym, Time.now, details)
12
+
13
+ Aggro.store.write_single @id, event
14
+ Aggro.event_bus.publish @id, event
15
+ end
16
+
17
+ private
18
+
19
+ def merge_details_with_command_context(details)
20
+ @aggregate.instance_variable_get(:@_context).merge(details)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Aggro
2
+ # Public: Converts events to and from serialized data.
3
+ module EventSerializer
4
+ module_function
5
+
6
+ def deserialize(serialized)
7
+ Marshal.load serialized
8
+ end
9
+
10
+ def serialize(deserialized)
11
+ Marshal.dump deserialized
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,97 @@
1
+ require 'aggro/file_store/reader'
2
+ require 'aggro/file_store/writer'
3
+
4
+ module Aggro
5
+ # Public: Stores and retrieves events by serializing them to flat files.
6
+ class FileStore < AbstractStore
7
+ INDEX_DIRECTORY = 'indexes'.freeze
8
+ EVENT_DIRECTORY = 'events'.freeze
9
+ REGISTRY_FILE = 'registry'.freeze
10
+
11
+ def initialize(directory)
12
+ @event_directory = [directory, EVENT_DIRECTORY].join('/')
13
+ @index_directory = [directory, INDEX_DIRECTORY].join('/')
14
+
15
+ FileUtils.mkdir_p @event_directory
16
+ FileUtils.mkdir_p @index_directory
17
+
18
+ @registry_file = [directory, REGISTRY_FILE].join('/')
19
+ initialize_registry if File.exist? @registry_file
20
+ end
21
+
22
+ def all
23
+ read registry.keys
24
+ end
25
+
26
+ def create(id, type)
27
+ File.open(@registry_file, 'ab') do |registry_file|
28
+ registry_file.write Marshal.dump [id, type]
29
+ registry[id] = type
30
+ end
31
+
32
+ self
33
+ end
34
+
35
+ def exists?(id)
36
+ registry[id] == true
37
+ end
38
+
39
+ def read(ids)
40
+ ids.map { |id| id_to_event_stream id }
41
+ end
42
+
43
+ def registry
44
+ @registry ||= {}
45
+ end
46
+
47
+ def write(event_streams)
48
+ event_streams.each do |stream|
49
+ FileStore::Writer.new(
50
+ event_file(stream.id, 'ab'),
51
+ index_file(stream.id, 'ab')
52
+ ).write stream.events
53
+ end
54
+
55
+ self
56
+ end
57
+
58
+ def write_single(id, event)
59
+ FileStore::Writer.new(
60
+ event_file(id, 'ab'),
61
+ index_file(id, 'ab')
62
+ ).write [event]
63
+ end
64
+
65
+ private
66
+
67
+ def event_file(id, flags = 'rb')
68
+ File.new [@event_directory, id].join('/'), flags
69
+ end
70
+
71
+ def id_to_event_stream(id)
72
+ EventStream.new id, type_for_id(id), id_to_reader(id).read
73
+ rescue Errno::ENOENT
74
+ EventStream.new id, type_for_id(id), []
75
+ end
76
+
77
+ def id_to_reader(id)
78
+ FileStore::Reader.new event_file(id), index_file(id)
79
+ end
80
+
81
+ def index_file(id, flags = 'rb')
82
+ File.new [@index_directory, id].join('/'), flags
83
+ end
84
+
85
+ def initialize_registry
86
+ File.open(@registry_file) do |file|
87
+ ObjectStream.new(file, type: 'marshal').each do |id, type|
88
+ registry[id] = type
89
+ end
90
+ end
91
+ end
92
+
93
+ def type_for_id(id)
94
+ registry[id]
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,21 @@
1
+ module Aggro
2
+ class FileStore < AbstractStore
3
+ # Private: Deserialized events from an IO object.
4
+ class Reader
5
+ def initialize(data_io, index_io)
6
+ @data_io = data_io
7
+ @index_io = index_io
8
+ end
9
+
10
+ def read
11
+ ObjectStream.new(@data_io, type: 'marshal')
12
+ end
13
+
14
+ private
15
+
16
+ def index
17
+ @index ||= ObjectStream.new(@index_io, type: 'marshal')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Aggro
2
+ class FileStore < AbstractStore
3
+ # Private: Serializes events to an IO object.
4
+ class Writer
5
+ def initialize(data_io, index_io)
6
+ @data_io = data_io
7
+ @index_io = index_io
8
+ end
9
+
10
+ def write(events)
11
+ events.each do |event|
12
+ @data_io.write EventSerializer.serialize(event)
13
+ write_to_index @data_io.pos
14
+ end
15
+
16
+ @data_io.flush
17
+ @index_io.flush
18
+ end
19
+
20
+ private
21
+
22
+ def write_to_index(offset)
23
+ @index_io.write Marshal.dump(offset)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ module Aggro
2
+ module Handler
3
+ # Private: Handler for incoming command requests.
4
+ class Command < Struct.new(:message, :server)
5
+ def call
6
+ commandee_local? ? handle_local : handle_foreign
7
+ end
8
+
9
+ private
10
+
11
+ def channel
12
+ Aggro.channels[commandee_id]
13
+ end
14
+
15
+ def command
16
+ @command ||= message.to_command
17
+ end
18
+
19
+ def commandee_id
20
+ message.commandee_id
21
+ end
22
+
23
+ def command_known?
24
+ !command.nil?
25
+ end
26
+
27
+ def commandee_local?
28
+ comandee_locator.local?
29
+ end
30
+
31
+ def comandee_locator
32
+ @comandee_locator ||= Locator.new(commandee_id)
33
+ end
34
+
35
+ def handle_foreign
36
+ comandee_locator.primary_node.client.post message
37
+ end
38
+
39
+ def handle_known
40
+ if channel.handles_command?(command)
41
+ channel.forward_command command
42
+
43
+ Message::OK.new
44
+ else
45
+ Message::UnhandledOperation.new
46
+ end
47
+ rescue NoMethodError
48
+ Message::InvalidTarget.new
49
+ end
50
+
51
+ def handle_local
52
+ command_known? ? handle_known : handle_unknown
53
+ end
54
+
55
+ def handle_unknown
56
+ Message::UnknownOperation.new
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ module Aggro
2
+ module Handler
3
+ # Private: Handler for incoming command requests.
4
+ class CreateAggregate < Struct.new(:message, :server)
5
+ def call
6
+ local? ? handle_local : handle_foreign
7
+ end
8
+
9
+ private
10
+
11
+ def add_to_channels
12
+ channel = Channel.new(message.id, message.type)
13
+ Aggro.channels[message.id] = channel
14
+ end
15
+
16
+ def exists_in_channels?
17
+ Aggro.channels.keys.include?(message.id)
18
+ end
19
+
20
+ def handle_local
21
+ unless exists_in_channels?
22
+ Aggro.store.create message.id, message.type
23
+ add_to_channels
24
+ end
25
+
26
+ Message::OK.new
27
+ end
28
+
29
+ def handle_foreign
30
+ Message::Ask.new locator.primary_node.id
31
+ end
32
+
33
+ def local?
34
+ locator.local?
35
+ end
36
+
37
+ def locator
38
+ @locator ||= Locator.new(message.id)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ module Aggro
2
+ module Handler
3
+ # Private: Handler for incoming command requests.
4
+ class GetEvents < Struct.new(:message, :server)
5
+ def call
6
+ local? ? handle_local : handle_foreign
7
+ end
8
+
9
+ private
10
+
11
+ def handle_local
12
+ events = Aggro.store.read([message.id]).first.events
13
+
14
+ Message::Events.new(message.id, events.to_a)
15
+ end
16
+
17
+ def handle_foreign
18
+ Message::Ask.new locator.primary_node.id
19
+ end
20
+
21
+ def local?
22
+ locator.local?
23
+ end
24
+
25
+ def locator
26
+ @locator ||= Locator.new(message.id)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,60 @@
1
+ module Aggro
2
+ module Handler
3
+ # Private: Handler for incoming query requests.
4
+ class Query < Struct.new(:message, :server)
5
+ def call
6
+ queryee_local? ? handle_local : handle_foreign
7
+ end
8
+
9
+ private
10
+
11
+ def channel
12
+ Aggro.channels[queryable_id]
13
+ end
14
+
15
+ def query
16
+ @query ||= message.to_query
17
+ end
18
+
19
+ def queryable_id
20
+ message.queryable_id
21
+ end
22
+
23
+ def query_known?
24
+ !query.nil?
25
+ end
26
+
27
+ def queryee_local?
28
+ comandee_locator.local?
29
+ end
30
+
31
+ def comandee_locator
32
+ @comandee_locator ||= Locator.new(queryable_id)
33
+ end
34
+
35
+ def handle_foreign
36
+ comandee_locator.primary_node.client.post message
37
+ end
38
+
39
+ def handle_known
40
+ if channel.handles_query?(query)
41
+ result = channel.run_query(query)
42
+
43
+ Message::Result.new result.value(5)
44
+ else
45
+ Message::UnhandledOperation.new
46
+ end
47
+ rescue NoMethodError
48
+ Message::InvalidTarget.new
49
+ end
50
+
51
+ def handle_local
52
+ query_known? ? handle_known : handle_unknown
53
+ end
54
+
55
+ def handle_unknown
56
+ Message::UnknownOperation.new
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ module Aggro
2
+ module Handler
3
+ # Private: Handler for incoming command requests.
4
+ class StartSaga < Struct.new(:message, :server)
5
+ def call
6
+ locator.local? ? handle_local : handle_foreign
7
+ end
8
+
9
+ private
10
+
11
+ def create_channel
12
+ channel = Channel.new message.id, 'Aggro::SagaRunner'
13
+
14
+ Aggro.channels[message.id] = channel
15
+ end
16
+
17
+ def create_saga
18
+ Aggro.store.create message.id, 'Aggro::SagaRunner'
19
+ end
20
+
21
+ def locator
22
+ @locator ||= Locator.new(message.id)
23
+ end
24
+
25
+ def handle_foreign
26
+ locator.primary_node.client.post message
27
+ end
28
+
29
+ def handle_known
30
+ create_saga
31
+ create_channel
32
+
33
+ Aggro.channels[message.id].forward_command start_command
34
+
35
+ Message::OK.new
36
+ end
37
+
38
+ def handle_local
39
+ saga_known? ? handle_known : handle_unknown
40
+ end
41
+
42
+ def handle_unknown
43
+ Message::UnknownOperation.new
44
+ end
45
+
46
+ def saga_known?
47
+ ActiveSupport::Inflector.safe_constantize message.name
48
+ end
49
+
50
+ def start_command
51
+ SagaRunner::StartSaga.new name: message.name, details: message.args,
52
+ id: message.id
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ module Aggro
2
+ # Public: Represents the local aggro server node.
3
+ class LocalNode < Struct.new(:id)
4
+ def client
5
+ @client ||= create_loopback_client
6
+ end
7
+
8
+ def endpoint
9
+ "tcp://127.0.0.1:#{Aggro.port}"
10
+ end
11
+
12
+ def publisher_endpoint
13
+ "tcp://127.0.0.1:#{Aggro.publisher_port}"
14
+ end
15
+
16
+ def to_s
17
+ id
18
+ end
19
+
20
+ private
21
+
22
+ def create_loopback_client
23
+ ->(msg) { Aggro.server.handle_message msg }.tap do |proc|
24
+ proc.class_eval { alias_method :post, :call }
25
+ end
26
+ end
27
+ end
28
+ end