freddy 1.7.0 → 2.0.0
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/.github/workflows/ci.yml +31 -0
- data/.rubocop.yml +9 -28
- data/.ruby-gemset +1 -1
- data/.ruby-version +1 -1
- data/Gemfile +3 -4
- data/README.md +5 -10
- data/freddy.gemspec +10 -17
- data/lib/freddy.rb +9 -14
- data/lib/freddy/adapters.rb +3 -28
- data/lib/freddy/adapters/bunny_adapter.rb +19 -2
- data/lib/freddy/consumers.rb +1 -1
- data/lib/freddy/consumers/respond_to_consumer.rb +3 -13
- data/lib/freddy/consumers/response_consumer.rb +2 -4
- data/lib/freddy/consumers/tap_into_consumer.rb +12 -24
- data/lib/freddy/delivery.rb +46 -13
- data/lib/freddy/payload.rb +2 -33
- data/lib/freddy/producers.rb +1 -1
- data/lib/freddy/producers/reply_producer.rb +12 -5
- data/lib/freddy/producers/send_and_forget_producer.rb +5 -11
- data/lib/freddy/producers/send_and_wait_response_producer.rb +19 -33
- data/lib/freddy/request_manager.rb +1 -9
- data/lib/freddy/tracing.rb +37 -0
- data/lib/freddy/version.rb +5 -0
- data/spec/.rubocop.yml +26 -0
- data/spec/freddy/error_response_spec.rb +6 -6
- data/spec/freddy/payload_spec.rb +25 -16
- data/spec/integration/concurrency_spec.rb +8 -12
- data/spec/integration/tracing_spec.rb +15 -32
- data/spec/spec_helper.rb +5 -13
- metadata +28 -15
- data/.npmignore +0 -8
- data/.travis.yml +0 -16
- data/lib/freddy/adapters/march_hare_adapter.rb +0 -64
- data/lib/freddy/trace_carrier.rb +0 -28
- data/spec/freddy/trace_carrier_spec.rb +0 -56
data/lib/freddy/payload.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'oj'
|
5
|
-
rescue LoadError
|
6
|
-
require 'symbolizer'
|
7
|
-
require 'json'
|
8
|
-
end
|
3
|
+
require 'oj'
|
9
4
|
|
10
5
|
class Freddy
|
11
6
|
class Payload
|
@@ -20,7 +15,7 @@ class Freddy
|
|
20
15
|
end
|
21
16
|
|
22
17
|
def self.json_handler
|
23
|
-
@json_handler ||=
|
18
|
+
@json_handler ||= OjAdapter
|
24
19
|
end
|
25
20
|
|
26
21
|
class OjAdapter
|
@@ -35,31 +30,5 @@ class Freddy
|
|
35
30
|
Oj.dump(payload, DUMP_OPTIONS)
|
36
31
|
end
|
37
32
|
end
|
38
|
-
|
39
|
-
class JsonAdapter
|
40
|
-
def self.parse(payload)
|
41
|
-
# MRI has :symbolize_keys, but JRuby does not. Not adding it at the
|
42
|
-
# moment.
|
43
|
-
Symbolizer.symbolize(JSON.parse(payload))
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.dump(payload)
|
47
|
-
JSON.dump(serialize_time_objects(payload))
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.serialize_time_objects(object)
|
51
|
-
if object.is_a?(Hash)
|
52
|
-
object.reduce({}) do |hash, (key, value)|
|
53
|
-
hash.merge(key => serialize_time_objects(value))
|
54
|
-
end
|
55
|
-
elsif object.is_a?(Array)
|
56
|
-
object.map(&method(:serialize_time_objects))
|
57
|
-
elsif object.is_a?(Time) || object.is_a?(Date)
|
58
|
-
object.iso8601
|
59
|
-
else
|
60
|
-
object
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
33
|
end
|
65
34
|
end
|
data/lib/freddy/producers.rb
CHANGED
@@ -10,17 +10,24 @@ class Freddy
|
|
10
10
|
@exchange = channel.default_exchange
|
11
11
|
end
|
12
12
|
|
13
|
-
def produce(
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def produce(routing_key, payload, properties)
|
14
|
+
span = Tracing.span_for_produce(
|
15
|
+
@exchange,
|
16
|
+
routing_key,
|
17
|
+
payload,
|
18
|
+
correlation_id: properties[:correlation_id]
|
19
|
+
)
|
17
20
|
|
18
21
|
properties = properties.merge(
|
19
|
-
routing_key:
|
22
|
+
routing_key: routing_key,
|
20
23
|
content_type: CONTENT_TYPE
|
21
24
|
)
|
25
|
+
Tracing.inject_tracing_information_to_properties!(properties)
|
22
26
|
|
23
27
|
@exchange.publish Payload.dump(payload), properties
|
28
|
+
ensure
|
29
|
+
# We won't wait for a reply. Just finish the span immediately.
|
30
|
+
span.finish
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
@@ -11,19 +11,15 @@ class Freddy
|
|
11
11
|
@topic_exchange = channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
|
12
12
|
end
|
13
13
|
|
14
|
-
def produce(
|
15
|
-
span =
|
16
|
-
tags: {
|
17
|
-
'message_bus.destination' => destination,
|
18
|
-
'component' => 'freddy',
|
19
|
-
'span.kind' => 'producer' # Message Bus
|
20
|
-
})
|
14
|
+
def produce(routing_key, payload, properties)
|
15
|
+
span = Tracing.span_for_produce(@topic_exchange, routing_key, payload)
|
21
16
|
|
22
17
|
properties = properties.merge(
|
23
|
-
routing_key:
|
18
|
+
routing_key: routing_key,
|
24
19
|
content_type: CONTENT_TYPE
|
25
20
|
)
|
26
|
-
|
21
|
+
Tracing.inject_tracing_information_to_properties!(properties)
|
22
|
+
|
27
23
|
json_payload = Payload.dump(payload)
|
28
24
|
|
29
25
|
# Connection adapters handle thread safety for #publish themselves. No
|
@@ -33,8 +29,6 @@ class Freddy
|
|
33
29
|
ensure
|
34
30
|
# We don't know how many listeners there are and we do not know when
|
35
31
|
# this message gets processed. Instead we close the span immediately.
|
36
|
-
# Listeners should use FollowsFrom to add trace information.
|
37
|
-
# https://github.com/opentracing/specification/blob/master/specification.md
|
38
32
|
span.finish
|
39
33
|
end
|
40
34
|
end
|
@@ -12,7 +12,6 @@ class Freddy
|
|
12
12
|
@request_manager = RequestManager.new(@logger)
|
13
13
|
|
14
14
|
@exchange = @channel.default_exchange
|
15
|
-
@topic_exchange = @channel.topic Freddy::FREDDY_TOPIC_EXCHANGE_NAME
|
16
15
|
|
17
16
|
@channel.on_no_route do |correlation_id|
|
18
17
|
@request_manager.no_route(correlation_id)
|
@@ -24,43 +23,37 @@ class Freddy
|
|
24
23
|
@response_consumer.consume(@channel, @response_queue, &method(:handle_response))
|
25
24
|
end
|
26
25
|
|
27
|
-
def produce(
|
26
|
+
def produce(routing_key, payload, timeout_in_seconds:, delete_on_timeout:, **properties)
|
28
27
|
correlation_id = SecureRandom.uuid
|
29
28
|
|
30
|
-
span =
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
'message_bus.response_queue' => @response_queue.name,
|
37
|
-
'message_bus.correlation_id' => correlation_id,
|
38
|
-
'freddy.timeout_in_seconds' => timeout_in_seconds
|
39
|
-
})
|
29
|
+
span = Tracing.span_for_produce(
|
30
|
+
@exchange,
|
31
|
+
routing_key,
|
32
|
+
payload,
|
33
|
+
correlation_id: correlation_id, timeout_in_seconds: timeout_in_seconds
|
34
|
+
)
|
40
35
|
|
41
36
|
container = SyncResponseContainer.new(
|
42
|
-
on_timeout(correlation_id,
|
37
|
+
on_timeout(correlation_id, routing_key, timeout_in_seconds, span)
|
43
38
|
)
|
44
39
|
|
45
40
|
@request_manager.store(correlation_id,
|
46
41
|
callback: container,
|
47
42
|
span: span,
|
48
|
-
destination:
|
43
|
+
destination: routing_key)
|
49
44
|
|
50
45
|
properties[:expiration] = (timeout_in_seconds * 1000).to_i if delete_on_timeout
|
51
46
|
|
52
47
|
properties = properties.merge(
|
53
|
-
routing_key:
|
48
|
+
routing_key: routing_key, content_type: CONTENT_TYPE,
|
54
49
|
correlation_id: correlation_id, reply_to: @response_queue.name,
|
55
50
|
mandatory: true, type: 'request'
|
56
51
|
)
|
57
|
-
|
58
|
-
json_payload = Payload.dump(payload)
|
52
|
+
Tracing.inject_tracing_information_to_properties!(properties)
|
59
53
|
|
60
54
|
# Connection adapters handle thread safety for #publish themselves. No
|
61
|
-
# need to lock
|
62
|
-
@
|
63
|
-
@exchange.publish json_payload, properties.dup
|
55
|
+
# need to lock this.
|
56
|
+
@exchange.publish Payload.dump(payload), properties.dup
|
64
57
|
|
65
58
|
container.wait_for_response(timeout_in_seconds)
|
66
59
|
end
|
@@ -82,28 +75,21 @@ class Freddy
|
|
82
75
|
"with correlation_id #{delivery.correlation_id}"
|
83
76
|
request[:callback].call(delivery.payload, delivery)
|
84
77
|
rescue InvalidRequestError => e
|
85
|
-
request[:span].
|
86
|
-
request[:span].
|
87
|
-
event: 'invalid request',
|
88
|
-
message: e.message,
|
89
|
-
'error.object': e
|
90
|
-
)
|
78
|
+
request[:span].record_exception(e)
|
79
|
+
request[:span].status = OpenTelemetry::Trace::Status.error
|
91
80
|
raise e
|
92
81
|
ensure
|
93
82
|
request[:span].finish
|
94
83
|
end
|
95
84
|
|
96
|
-
def on_timeout(correlation_id,
|
85
|
+
def on_timeout(correlation_id, routing_key, timeout_in_seconds, span)
|
97
86
|
proc do
|
98
|
-
@logger.warn "Request timed out waiting response from #{
|
87
|
+
@logger.warn "Request timed out waiting response from #{routing_key}"\
|
99
88
|
", correlation id #{correlation_id}, timeout #{timeout_in_seconds}s"
|
100
89
|
|
101
90
|
@request_manager.delete(correlation_id)
|
102
|
-
span.
|
103
|
-
span.
|
104
|
-
event: 'timed out',
|
105
|
-
message: "Timed out waiting response from #{destination}"
|
106
|
-
)
|
91
|
+
span.add_event('timeout')
|
92
|
+
span.status = OpenTelemetry::Trace::Status.error("Timed out waiting response from #{routing_key}")
|
107
93
|
span.finish
|
108
94
|
end
|
109
95
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
class Freddy
|
4
4
|
class RequestManager
|
5
5
|
def initialize(logger)
|
6
|
-
@requests =
|
6
|
+
@requests = {}
|
7
7
|
@logger = logger
|
8
8
|
end
|
9
9
|
|
@@ -22,13 +22,5 @@ class Freddy
|
|
22
22
|
def delete(correlation_id)
|
23
23
|
@requests.delete(correlation_id)
|
24
24
|
end
|
25
|
-
|
26
|
-
class ConcurrentHash < Hash
|
27
|
-
# CRuby hash does not need any locks. Only adding when using JRuby.
|
28
|
-
if RUBY_PLATFORM == 'java'
|
29
|
-
require 'jruby/synchronized'
|
30
|
-
include JRuby::Synchronized
|
31
|
-
end
|
32
|
-
end
|
33
25
|
end
|
34
26
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Freddy
|
4
|
+
module Tracing
|
5
|
+
# NOTE: Make sure you finish the span youself.
|
6
|
+
def self.span_for_produce(exchange, routing_key, payload, correlation_id: nil, timeout_in_seconds: nil)
|
7
|
+
destination = exchange.name
|
8
|
+
destination_kind = exchange.type == :direct ? 'queue' : 'topic'
|
9
|
+
|
10
|
+
attributes = {
|
11
|
+
'payload.type' => (payload[:type] || 'unknown').to_s,
|
12
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM => 'rabbitmq',
|
13
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_RABBITMQ_ROUTING_KEY => routing_key,
|
14
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION => destination,
|
15
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_DESTINATION_KIND => destination_kind,
|
16
|
+
OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION => 'send'
|
17
|
+
}
|
18
|
+
|
19
|
+
attributes['freddy.timeout_in_seconds'] = timeout_in_seconds if timeout_in_seconds
|
20
|
+
|
21
|
+
if correlation_id
|
22
|
+
attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_CONVERSATION_ID] = correlation_id
|
23
|
+
end
|
24
|
+
|
25
|
+
Freddy.tracer.start_span(
|
26
|
+
".#{routing_key} send",
|
27
|
+
kind: OpenTelemetry::Trace::SpanKind::PRODUCER,
|
28
|
+
attributes: attributes
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.inject_tracing_information_to_properties!(properties)
|
33
|
+
properties[:headers] ||= {}
|
34
|
+
OpenTelemetry.propagation.inject(properties[:headers])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
inherit_from: ../.rubocop.yml
|
3
|
+
|
4
|
+
RSpec/ExampleLength:
|
5
|
+
Enabled: no
|
6
|
+
|
7
|
+
RSpec/MultipleExpectations:
|
8
|
+
Enabled: no
|
9
|
+
|
10
|
+
RSpec/MessageSpies:
|
11
|
+
Enabled: no
|
12
|
+
|
13
|
+
RSpec/VerifiedDoubles:
|
14
|
+
Enabled: no
|
15
|
+
|
16
|
+
RSpec/InstanceVariable:
|
17
|
+
Enabled: no
|
18
|
+
|
19
|
+
RSpec/NestedGroups:
|
20
|
+
Enabled: no
|
21
|
+
|
22
|
+
RSpec/DescribeClass:
|
23
|
+
Enabled: no
|
24
|
+
|
25
|
+
RSpec/MultipleMemoizedHelpers:
|
26
|
+
Enabled: no
|
@@ -13,10 +13,10 @@ describe Freddy::ErrorResponse do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe '#message' do
|
16
|
-
subject { error.message }
|
16
|
+
subject(:message) { error.message }
|
17
17
|
|
18
18
|
it 'uses error type as a message' do
|
19
|
-
|
19
|
+
expect(message).to eq('SomeError')
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -31,10 +31,10 @@ describe Freddy::ErrorResponse do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
describe '#message' do
|
34
|
-
subject { error.message }
|
34
|
+
subject(:message) { error.message }
|
35
35
|
|
36
36
|
it 'uses error type as a message' do
|
37
|
-
|
37
|
+
expect(message).to eq('SomeError: extra info')
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -49,10 +49,10 @@ describe Freddy::ErrorResponse do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
describe '#message' do
|
52
|
-
subject { error.message }
|
52
|
+
subject(:message) { error.message }
|
53
53
|
|
54
54
|
it 'uses default error message as a message' do
|
55
|
-
|
55
|
+
expect(message).to eq('Use #response to get the error response')
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
data/spec/freddy/payload_spec.rb
CHANGED
@@ -2,19 +2,28 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Freddy::Payload do
|
4
4
|
describe '#dump' do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
5
|
+
context 'with a given Ruby engine' do
|
6
|
+
let(:ts) do
|
7
|
+
RUBY_ENGINE == 'jruby' ? '{"time":"2016-01-04T20:18:00Z"}' : '{"time":"2016-01-04T20:18:00.000000Z"}'
|
8
|
+
end
|
9
|
+
let(:ts_array) do
|
10
|
+
RUBY_ENGINE == 'jruby' ? '{"time":["2016-01-04T20:18:00Z"]}' : '{"time":["2016-01-04T20:18:00.000000Z"]}'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'serializes time objects as iso8601 format strings' do
|
14
|
+
expect(dump(time: Time.utc(2016, 1, 4, 20, 18)))
|
15
|
+
.to eq(ts)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'serializes time objects in an array as iso8601 format strings' do
|
19
|
+
expect(dump(time: [Time.utc(2016, 1, 4, 20, 18)]))
|
20
|
+
.to eq(ts_array)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'serializes time objects in a nested hash as iso8601 format strings' do
|
24
|
+
expect(dump(x: { time: Time.utc(2016, 1, 4, 20, 18) }))
|
25
|
+
.to eq("{\"x\":#{ts}}")
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
29
|
it 'serializes date objects as iso8601 format strings' do
|
@@ -33,17 +42,17 @@ describe Freddy::Payload do
|
|
33
42
|
end
|
34
43
|
|
35
44
|
it 'serializes datetime objects as iso8601 format strings' do
|
36
|
-
expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18)))
|
45
|
+
expect(dump(datetime: DateTime.new(2016, 1, 4, 20, 18)))
|
37
46
|
.to eq('{"datetime":"2016-01-04T20:18:00+00:00"}')
|
38
47
|
end
|
39
48
|
|
40
49
|
it 'serializes datetime objects in an array as iso8601 format strings' do
|
41
|
-
expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)]))
|
50
|
+
expect(dump(datetime: [DateTime.new(2016, 1, 4, 20, 18)]))
|
42
51
|
.to eq('{"datetime":["2016-01-04T20:18:00+00:00"]}')
|
43
52
|
end
|
44
53
|
|
45
54
|
it 'serializes datetime objects in a nested hash as iso8601 format strings' do
|
46
|
-
expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) }))
|
55
|
+
expect(dump(x: { datetime: DateTime.new(2016, 1, 4, 20, 18) }))
|
47
56
|
.to eq('{"x":{"datetime":"2016-01-04T20:18:00+00:00"}}')
|
48
57
|
end
|
49
58
|
|
@@ -10,21 +10,17 @@ describe 'Concurrency' do
|
|
10
10
|
|
11
11
|
it 'supports multiple requests in #respond_to' do
|
12
12
|
freddy1.respond_to 'Concurrency1' do |_payload, msg_handler|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
msg_handler.error(e.response)
|
19
|
-
end
|
13
|
+
freddy1.deliver_with_response 'Concurrency2', msg: 'noop'
|
14
|
+
result2 = freddy1.deliver_with_response 'Concurrency3', msg: 'noop'
|
15
|
+
msg_handler.success(result2)
|
16
|
+
rescue Freddy::InvalidRequestError => e
|
17
|
+
msg_handler.error(e.response)
|
20
18
|
end
|
21
19
|
|
22
20
|
freddy2.respond_to 'Concurrency2' do |_payload, msg_handler|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
msg_handler.error(e.response)
|
27
|
-
end
|
21
|
+
msg_handler.success(from: 'Concurrency2')
|
22
|
+
rescue Freddy::InvalidRequestError => e
|
23
|
+
msg_handler.error(e.response)
|
28
24
|
end
|
29
25
|
|
30
26
|
freddy3.respond_to 'Concurrency3' do |_payload, msg_handler|
|