aggro 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|