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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/.travis.yml +15 -0
- data/Gemfile +9 -0
- data/README.md +5 -1
- data/Rakefile +10 -0
- data/aggro.gemspec +8 -1
- data/lib/aggro.rb +191 -7
- data/lib/aggro/abstract_store.rb +12 -0
- data/lib/aggro/aggregate.rb +98 -0
- data/lib/aggro/aggregate_ref.rb +68 -6
- data/lib/aggro/attribute_dsl.rb +96 -0
- data/lib/aggro/binding_dsl.rb +45 -0
- data/lib/aggro/block_helper.rb +14 -0
- data/lib/aggro/channel.rb +37 -0
- data/lib/aggro/client.rb +12 -0
- data/lib/aggro/cluster_config.rb +57 -0
- data/lib/aggro/command.rb +16 -0
- data/lib/aggro/concurrent_actor.rb +26 -0
- data/lib/aggro/event_bus.rb +94 -0
- data/lib/aggro/event_dsl.rb +53 -0
- data/lib/aggro/event_proxy.rb +23 -0
- data/lib/aggro/event_serializer.rb +14 -0
- data/lib/aggro/file_store.rb +97 -0
- data/lib/aggro/file_store/reader.rb +21 -0
- data/lib/aggro/file_store/writer.rb +27 -0
- data/lib/aggro/handler/command.rb +60 -0
- data/lib/aggro/handler/create_aggregate.rb +42 -0
- data/lib/aggro/handler/get_events.rb +30 -0
- data/lib/aggro/handler/query.rb +60 -0
- data/lib/aggro/handler/start_saga.rb +56 -0
- data/lib/aggro/local_node.rb +28 -0
- data/lib/aggro/locator.rb +32 -0
- data/lib/aggro/message/ask.rb +16 -0
- data/lib/aggro/message/command.rb +36 -0
- data/lib/aggro/message/create_aggregate.rb +16 -0
- data/lib/aggro/message/endpoint.rb +16 -0
- data/lib/aggro/message/events.rb +24 -0
- data/lib/aggro/message/get_events.rb +16 -0
- data/lib/aggro/message/heartbeat.rb +16 -0
- data/lib/aggro/message/invalid_target.rb +20 -0
- data/lib/aggro/message/ok.rb +20 -0
- data/lib/aggro/message/publisher_endpoint_inquiry.rb +16 -0
- data/lib/aggro/message/query.rb +36 -0
- data/lib/aggro/message/result.rb +16 -0
- data/lib/aggro/message/start_saga.rb +28 -0
- data/lib/aggro/message/unhandled_operation.rb +20 -0
- data/lib/aggro/message/unknown_operation.rb +20 -0
- data/lib/aggro/message_parser.rb +10 -0
- data/lib/aggro/message_router.rb +26 -0
- data/lib/aggro/nanomsg_transport.rb +31 -0
- data/lib/aggro/nanomsg_transport/client.rb +35 -0
- data/lib/aggro/nanomsg_transport/connection.rb +98 -0
- data/lib/aggro/nanomsg_transport/publish.rb +17 -0
- data/lib/aggro/nanomsg_transport/publisher.rb +37 -0
- data/lib/aggro/nanomsg_transport/raw_reply.rb +18 -0
- data/lib/aggro/nanomsg_transport/raw_request.rb +18 -0
- data/lib/aggro/nanomsg_transport/reply.rb +17 -0
- data/lib/aggro/nanomsg_transport/request.rb +17 -0
- data/lib/aggro/nanomsg_transport/server.rb +84 -0
- data/lib/aggro/nanomsg_transport/socket_error.rb +20 -0
- data/lib/aggro/nanomsg_transport/subscribe.rb +27 -0
- data/lib/aggro/nanomsg_transport/subscriber.rb +82 -0
- data/lib/aggro/node.rb +29 -0
- data/lib/aggro/node_list.rb +39 -0
- data/lib/aggro/projection.rb +13 -0
- data/lib/aggro/query.rb +11 -0
- data/lib/aggro/saga.rb +94 -0
- data/lib/aggro/saga_runner.rb +87 -0
- data/lib/aggro/saga_runner/start_saga.rb +12 -0
- data/lib/aggro/saga_status.rb +29 -0
- data/lib/aggro/server.rb +88 -0
- data/lib/aggro/subscriber.rb +48 -0
- data/lib/aggro/subscription.rb +41 -0
- data/lib/aggro/transform/boolean.rb +16 -0
- data/lib/aggro/transform/email.rb +26 -0
- data/lib/aggro/transform/id.rb +34 -0
- data/lib/aggro/transform/integer.rb +22 -0
- data/lib/aggro/transform/money.rb +22 -0
- data/lib/aggro/transform/noop.rb +16 -0
- data/lib/aggro/transform/string.rb +16 -0
- data/lib/aggro/transform/time_interval.rb +24 -0
- data/lib/aggro/version.rb +1 -1
- data/spec/lib/aggro/abstract_store_spec.rb +15 -0
- data/spec/lib/aggro/aggregate_ref_spec.rb +63 -12
- data/spec/lib/aggro/aggregate_spec.rb +207 -0
- data/spec/lib/aggro/channel_spec.rb +87 -0
- data/spec/lib/aggro/client_spec.rb +26 -0
- data/spec/lib/aggro/cluster_config_spec.rb +33 -0
- data/spec/lib/aggro/command_spec.rb +52 -0
- data/spec/lib/aggro/concurrent_actor_spec.rb +44 -0
- data/spec/lib/aggro/event_bus_spec.rb +20 -0
- data/spec/lib/aggro/event_serializer_spec.rb +28 -0
- data/spec/lib/aggro/file_store/reader_spec.rb +32 -0
- data/spec/lib/aggro/file_store/writer_spec.rb +67 -0
- data/spec/lib/aggro/file_store_spec.rb +51 -0
- data/spec/lib/aggro/handler/command_spec.rb +78 -0
- data/spec/lib/aggro/handler/create_aggregate_spec.rb +64 -0
- data/spec/lib/aggro/handler/get_events_handler_spec.rb +45 -0
- data/spec/lib/aggro/handler/query_spec.rb +78 -0
- data/spec/lib/aggro/handler/start_saga_spec.rb +64 -0
- data/spec/lib/aggro/local_node_spec.rb +52 -0
- data/spec/lib/aggro/locator_spec.rb +61 -0
- data/spec/lib/aggro/message/ask_spec.rb +23 -0
- data/spec/lib/aggro/message/command_spec.rb +50 -0
- data/spec/lib/aggro/message/create_aggregate_spec.rb +28 -0
- data/spec/lib/aggro/message/endpoint_spec.rb +23 -0
- data/spec/lib/aggro/message/events_spec.rb +37 -0
- data/spec/lib/aggro/message/get_events_spec.rb +33 -0
- data/spec/lib/aggro/message/heartbeat_spec.rb +23 -0
- data/spec/lib/aggro/message/invalid_target_spec.rb +28 -0
- data/spec/lib/aggro/message/ok_spec.rb +27 -0
- data/spec/lib/aggro/message/publisher_endpoint_inquiry_spec.rb +23 -0
- data/spec/lib/aggro/message/query_spec.rb +50 -0
- data/spec/lib/aggro/message/start_saga_spec.rb +37 -0
- data/spec/lib/aggro/message/unhandled_operation_spec.rb +28 -0
- data/spec/lib/aggro/message/unknown_operation_spec.rb +28 -0
- data/spec/lib/aggro/message_parser_spec.rb +16 -0
- data/spec/lib/aggro/message_router_spec.rb +35 -0
- data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +21 -0
- data/spec/lib/aggro/nanomsg_transport_spec.rb +37 -0
- data/spec/lib/aggro/node_list_spec.rb +38 -0
- data/spec/lib/aggro/node_spec.rb +44 -0
- data/spec/lib/aggro/projection_spec.rb +22 -0
- data/spec/lib/aggro/query_spec.rb +47 -0
- data/spec/lib/aggro/saga_runner_spec.rb +84 -0
- data/spec/lib/aggro/saga_spec.rb +126 -0
- data/spec/lib/aggro/saga_status_spec.rb +56 -0
- data/spec/lib/aggro/server_spec.rb +118 -0
- data/spec/lib/aggro/subscriber_spec.rb +59 -0
- data/spec/lib/aggro/subscription_spec.rb +50 -0
- data/spec/lib/aggro/transform/boolean_spec.rb +23 -0
- data/spec/lib/aggro/transform/email_spec.rb +13 -0
- data/spec/lib/aggro/transform/id_spec.rb +70 -0
- data/spec/lib/aggro/transform/integer_spec.rb +30 -0
- data/spec/lib/aggro/transform/money_spec.rb +34 -0
- data/spec/lib/aggro/transform/string_spec.rb +15 -0
- data/spec/lib/aggro/transform/time_interval_spec.rb +29 -0
- data/spec/lib/aggro_spec.rb +63 -19
- data/spec/spec_helper.rb +21 -2
- metadata +283 -3
data/lib/aggro/node.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Represents an aggro server node.
|
3
|
+
class Node < Struct.new(:id, :endpoint)
|
4
|
+
def client
|
5
|
+
@client ||= Aggro::Client.new(endpoint)
|
6
|
+
end
|
7
|
+
|
8
|
+
def publisher_endpoint
|
9
|
+
@publisher_endpoint ||= discover_publisher_endpoint
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
id
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def discover_publisher_endpoint
|
19
|
+
message = Message::PublisherEndpointInquiry.new(Aggro.local_node.id)
|
20
|
+
response = client.post(message)
|
21
|
+
|
22
|
+
if response.is_a? Message::Endpoint
|
23
|
+
response.endpoint
|
24
|
+
else
|
25
|
+
fail "Could not discover publisher endpoint for #{id}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Computes which nodes are responsible for a given aggregate ID.
|
3
|
+
class NodeList
|
4
|
+
DEFAULT_REPLICATION_FACTOR = 3
|
5
|
+
|
6
|
+
attr_reader :state
|
7
|
+
|
8
|
+
def add(node)
|
9
|
+
hash_ring << node
|
10
|
+
|
11
|
+
update_state
|
12
|
+
end
|
13
|
+
|
14
|
+
def nodes_for(id, replication_factor = default_replication_factor)
|
15
|
+
nodes
|
16
|
+
.cycle
|
17
|
+
.take(nodes.index(hash_ring.node_for(id)) + replication_factor)
|
18
|
+
.last(replication_factor)
|
19
|
+
end
|
20
|
+
|
21
|
+
def nodes
|
22
|
+
hash_ring.nodes.sort_by(&:id)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def default_replication_factor
|
28
|
+
[nodes.length, DEFAULT_REPLICATION_FACTOR].min
|
29
|
+
end
|
30
|
+
|
31
|
+
def hash_ring
|
32
|
+
@hash_ring ||= ConsistentHashing::Ring.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_state
|
36
|
+
@state = Digest::MD5.hexdigest(nodes.map(&:to_s).join)[0..16].hex
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/aggro/query.rb
ADDED
data/lib/aggro/saga.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Mixin to turn a PORO into an Aggro saga.
|
3
|
+
module Saga
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include AttributeDSL
|
7
|
+
include BindingDSL
|
8
|
+
include EventDSL
|
9
|
+
|
10
|
+
included do
|
11
|
+
generate_id :causation_id
|
12
|
+
generate_id :correlation_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def bindings
|
16
|
+
@runner.bindings
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_filters
|
20
|
+
{ correlation_id: correlation_id }
|
21
|
+
end
|
22
|
+
|
23
|
+
def saga_id
|
24
|
+
@saga_id ||= SecureRandom.uuid
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
fail 'Saga is not valid' unless valid?
|
29
|
+
|
30
|
+
promise = SagaStatus.new(saga_id)
|
31
|
+
|
32
|
+
message = Message::StartSaga.new Aggro.local_node.id, saga_id, to_details
|
33
|
+
response = primary_node.client.post message
|
34
|
+
|
35
|
+
if response.is_a? Message::OK
|
36
|
+
promise
|
37
|
+
else
|
38
|
+
fail 'Saga could not be started'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def primary_node
|
45
|
+
@primary_node ||= Locator.new(saga_id).primary_node
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_details
|
49
|
+
{ name: model_name.name, args: serialized_attributes }
|
50
|
+
end
|
51
|
+
|
52
|
+
def reject(reason = nil)
|
53
|
+
fail 'Runner not set' unless @runner
|
54
|
+
|
55
|
+
@runner.reject reason
|
56
|
+
end
|
57
|
+
|
58
|
+
def resolve(value = nil)
|
59
|
+
fail 'Runner not set' unless @runner
|
60
|
+
|
61
|
+
@runner.resolve value
|
62
|
+
end
|
63
|
+
|
64
|
+
def transition(step_name, *args)
|
65
|
+
fail 'Runner not set' unless @runner
|
66
|
+
|
67
|
+
@runner.transition step_name, *args
|
68
|
+
end
|
69
|
+
|
70
|
+
class_methods do
|
71
|
+
def handler_for_step(step_name)
|
72
|
+
steps[step_name]
|
73
|
+
end
|
74
|
+
|
75
|
+
def handles_step?(step_name)
|
76
|
+
steps.key? step_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def initial(step_name = nil)
|
80
|
+
step_name ? @initial = step_name : @initial
|
81
|
+
end
|
82
|
+
|
83
|
+
def step(step_name, &block)
|
84
|
+
steps[step_name] = block
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def steps
|
90
|
+
Aggro.step_handlers[name]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'aggro/saga_runner/start_saga'
|
2
|
+
|
3
|
+
module Aggro
|
4
|
+
# Private: Aggregate which runs saga processes.
|
5
|
+
class SagaRunner
|
6
|
+
include Aggregate
|
7
|
+
|
8
|
+
allows StartSaga do |command|
|
9
|
+
@details = command.details
|
10
|
+
|
11
|
+
@klass = ActiveSupport::Inflector.constantize command.name
|
12
|
+
|
13
|
+
@saga = @klass.new(@details).tap do |saga|
|
14
|
+
saga.instance_variable_set(:@saga_id, command.id)
|
15
|
+
saga.instance_variable_set(:@runner, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
did.started state: @klass.initial
|
19
|
+
|
20
|
+
run_step @klass.initial
|
21
|
+
end
|
22
|
+
|
23
|
+
def bindings
|
24
|
+
@bindings ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def cancel_bindings
|
28
|
+
bindings.each(&:cancel)
|
29
|
+
@bindings = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def reject(reason)
|
33
|
+
did.rejected reason: reason
|
34
|
+
|
35
|
+
teardown
|
36
|
+
end
|
37
|
+
|
38
|
+
def resolve(value)
|
39
|
+
did.resolved value: value
|
40
|
+
|
41
|
+
teardown
|
42
|
+
end
|
43
|
+
|
44
|
+
def transition(step_name, *args)
|
45
|
+
cancel_bindings
|
46
|
+
did.transitioned state: step_name, args: args
|
47
|
+
|
48
|
+
run_step step_name, args
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def did
|
54
|
+
@_context = @details
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_step(step_name, args = [])
|
59
|
+
with_thread_ids do
|
60
|
+
handler = @klass.handler_for_step(step_name)
|
61
|
+
|
62
|
+
fail "Step '#{step_name}' does not exist" unless handler
|
63
|
+
|
64
|
+
@saga.send(:instance_exec, *args, &handler)
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
reject e.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def teardown
|
71
|
+
@saga = nil
|
72
|
+
cancel_bindings
|
73
|
+
Aggro.event_bus.subscribe(@id, self)
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_thread_ids
|
77
|
+
old_causation_id = Thread.current[:causation_id]
|
78
|
+
old_correlation_id = Thread.current[:correlation_id]
|
79
|
+
Thread.current[:causation_id] = @details[:causation_id]
|
80
|
+
Thread.current[:correlation_id] = @details[:correlation_id]
|
81
|
+
yield
|
82
|
+
ensure
|
83
|
+
Thread.current[:causation_id] = old_causation_id
|
84
|
+
Thread.current[:correlation_id] = old_correlation_id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Tracks the state of a saga as it processes.
|
3
|
+
class SagaStatus
|
4
|
+
include Projection
|
5
|
+
include Concurrent::Obligation
|
6
|
+
|
7
|
+
def initialize(id)
|
8
|
+
@state = :unscheduled
|
9
|
+
init_obligation
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
events do
|
14
|
+
def started
|
15
|
+
self.state = :pending
|
16
|
+
end
|
17
|
+
|
18
|
+
def rejected(reason)
|
19
|
+
set_state false, nil, reason
|
20
|
+
event.set
|
21
|
+
end
|
22
|
+
|
23
|
+
def resolved(value)
|
24
|
+
set_state true, value, nil
|
25
|
+
event.set
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/aggro/server.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Binds a transport endpoint and handles incoming messages.
|
3
|
+
class Server
|
4
|
+
RAW_HANDLER = :handle_raw
|
5
|
+
|
6
|
+
HANDLERS = {
|
7
|
+
Message::Command => :handle_command,
|
8
|
+
Message::CreateAggregate => :handle_create,
|
9
|
+
Message::GetEvents => :handle_get_events,
|
10
|
+
Message::Heartbeat => :handle_heartbeat,
|
11
|
+
Message::PublisherEndpointInquiry => :handle_publisher_endpoint_inquiry,
|
12
|
+
Message::Query => :handle_query,
|
13
|
+
Message::StartSaga => :handle_start_saga
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(endpoint, publisher_endpoint)
|
17
|
+
@endpoint = endpoint
|
18
|
+
@publisher_endpoint = publisher_endpoint
|
19
|
+
|
20
|
+
@transport_server = Aggro.transport.server endpoint, method(RAW_HANDLER)
|
21
|
+
@transport_publisher = Aggro.transport.publisher publisher_endpoint
|
22
|
+
end
|
23
|
+
|
24
|
+
def bind
|
25
|
+
@transport_server.start
|
26
|
+
@transport_publisher.open_socket
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_message(message)
|
30
|
+
message_router.route message
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish(message)
|
34
|
+
@transport_publisher.publish message
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
@transport_server.stop
|
39
|
+
@transport_publisher.close_socket
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def handle_command(message)
|
45
|
+
Handler::Command.new(message, self).call
|
46
|
+
end
|
47
|
+
|
48
|
+
def handle_create(message)
|
49
|
+
Handler::CreateAggregate.new(message, self).call
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_get_events(message)
|
53
|
+
Handler::GetEvents.new(message, self).call
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_heartbeat(_message)
|
57
|
+
Message::OK.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_publisher_endpoint_inquiry(_message)
|
61
|
+
Message::Endpoint.new @publisher_endpoint
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_query(message)
|
65
|
+
Handler::Query.new(message, self).call
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_raw(raw)
|
69
|
+
handle_message MessageParser.parse raw
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_start_saga(message)
|
73
|
+
Handler::StartSaga.new(message, self).call
|
74
|
+
end
|
75
|
+
|
76
|
+
def message_router
|
77
|
+
@message_router ||= begin
|
78
|
+
MessageRouter.new.tap do |router|
|
79
|
+
HANDLERS.each { |type, sym| router.attach_handler type, method(sym) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def publisher
|
85
|
+
@publisher ||= Publisher.new(local_node.publisher_endpoint)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Public: Subscribes to topics at a given endpoint.
|
3
|
+
class Subscriber
|
4
|
+
RAW_HANDLER = :handle_raw
|
5
|
+
|
6
|
+
def initialize(endpoint, callable = nil, &block)
|
7
|
+
if callable
|
8
|
+
@callback = callable
|
9
|
+
elsif block_given?
|
10
|
+
@callback = block
|
11
|
+
else
|
12
|
+
fail ArgumentError
|
13
|
+
end
|
14
|
+
|
15
|
+
@transport_sub = Aggro.transport.subscriber endpoint, method(RAW_HANDLER)
|
16
|
+
@subscribed_topics = Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def bind
|
20
|
+
@transport_sub.start
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_message(message)
|
24
|
+
@callback.call message.id, message.events if message.is_a? Message::Events
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@transport_sub.stop
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscribe_to_topic(topic)
|
32
|
+
return if @subscribed_topics.include? topic
|
33
|
+
|
34
|
+
@subscribed_topics << topic
|
35
|
+
@transport_sub.add_subscription message_prefix_for_topic(topic)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def handle_raw(raw)
|
41
|
+
handle_message MessageParser.parse raw
|
42
|
+
end
|
43
|
+
|
44
|
+
def message_prefix_for_topic(topic)
|
45
|
+
Message::Events::TYPE_CODE + topic
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|