freddy 1.4.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: afcbdc550b2c021be177ce71767e874d88f62266
4
- data.tar.gz: b385e9fc1aa1f12d01e42db629c8e669f56c08f8
2
+ SHA256:
3
+ metadata.gz: 7ae99c2d347a67110923b71ffac1af46c706d177b39b82c3adaae4db3f686a1d
4
+ data.tar.gz: 557210126d3c4cdff8f0946ee5b6882e3334126c3cb0ae900df823f6e762fdd9
5
5
  SHA512:
6
- metadata.gz: b850d5ce01a28ee9f1ed0762ac61ca293a36270d754f2065d5cfcfc4f8ee2f666584a0e82d60ebafc1c734692835bd39e38319b63fdbd2509862a4707db5f13d
7
- data.tar.gz: 47cf5182652976f7966226142845fd3c64077046d5fbeb5b5627c20a4e2a69936b33f26731344f74273a32b778f01420299b5a80ca9147e802a897e72cb8081a
6
+ metadata.gz: a2ee88b1ec7e51523ae6b3851f07b398b9093d90b193712bce172a09764d2a683d0b5209ced2a4a524248d8ffab36074b8a0b65c662ef9844f319424ccb32e93
7
+ data.tar.gz: ea65f5f23e18c768098c1eec7edc299a146f0fc830a0a57b5e9d1f725b5fe6f0b442b017ecd08b80f6242424a6a0958b57c8514cdf015bca07e4d316e88edc56
@@ -0,0 +1,31 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby-version: ['2.7']
15
+ services:
16
+ rabbitmq:
17
+ image: rabbitmq
18
+ ports:
19
+ - 5672:5672
20
+ options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ with:
24
+ submodules: true
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true
30
+ - name: Run lint & tests
31
+ run: bundle exec rake
data/.rubocop.yml CHANGED
@@ -1,39 +1,23 @@
1
- require: rubocop-rspec
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
2
4
 
3
- Metrics/AbcSize:
4
- Enabled: no
5
-
6
- Metrics/BlockLength:
7
- Enabled: no
8
-
9
- Metrics/LineLength:
5
+ Layout/LineLength:
10
6
  Max: 120
11
7
 
12
- Metrics/MethodLength:
13
- Enabled: no
14
-
15
- Style/Documentation:
16
- Enabled: no
17
-
18
- RSpec/ExampleLength:
19
- Enabled: no
20
-
21
- RSpec/MultipleExpectations:
8
+ Lint/EmptyBlock:
22
9
  Enabled: no
23
10
 
24
- RSpec/MessageSpies:
25
- Enabled: no
26
-
27
- RSpec/VerifiedDoubles:
11
+ Metrics/AbcSize:
28
12
  Enabled: no
29
13
 
30
- RSpec/InstanceVariable:
14
+ Metrics/BlockLength:
31
15
  Enabled: no
32
16
 
33
- RSpec/NestedGroups:
17
+ Metrics/MethodLength:
34
18
  Enabled: no
35
19
 
36
- RSpec/DescribeClass:
20
+ Style/Documentation:
37
21
  Enabled: no
38
22
 
39
23
  Style/FrozenStringLiteralComment:
@@ -42,8 +26,5 @@ Style/FrozenStringLiteralComment:
42
26
  Include:
43
27
  - 'lib/**/*'
44
28
 
45
- Performance/TimesMap:
46
- Enabled: no
47
-
48
29
  Naming/FileName:
49
30
  Enabled: no
data/.ruby-gemset CHANGED
@@ -1 +1 @@
1
- sm-messaging
1
+ freddy
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4
1
+ 2.7
data/Gemfile CHANGED
@@ -1,11 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'codeclimate-test-reporter'
4
3
  gem 'hamster', '~> 3.0'
5
- gem 'opentracing_test_tracer', '~> 0.1'
4
+ gem 'opentelemetry-sdk', '~> 1.0.0.rc3'
6
5
  gem 'pry'
7
6
  gem 'rspec'
8
- gem 'rubocop', '~> 0.54.0'
9
- gem 'rubocop-rspec', '~> 1.24.0'
7
+ gem 'rubocop', '~> 1.19'
8
+ gem 'rubocop-rspec', '~> 2.4'
10
9
 
11
10
  gemspec
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Messaging API supporting acknowledgements and request-response
2
2
 
3
- [![Build Status](https://travis-ci.org/salemove/freddy.svg?branch=master)](https://travis-ci.org/salemove/freddy)
4
- [![Code Climate](https://codeclimate.com/github/salemove/freddy/badges/gpa.svg)](https://codeclimate.com/github/salemove/freddy)
5
- [![Test Coverage](https://codeclimate.com/github/salemove/freddy/badges/coverage.svg)](https://codeclimate.com/github/salemove/freddy/coverage)
3
+ [![Build Status](https://github.com/salemove/freddy/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/salemove/freddy/actions/workflows/ci.yml?query=branch%3Amaster)
6
4
 
7
5
  ## Setup
8
6
 
@@ -121,6 +119,16 @@ freddy.tap_into "somebody.*.love"
121
119
 
122
120
  receives messages that are delivered to `somebody.to.love` but doesn't receive messages delivered to `someboy.not.to.love`
123
121
 
122
+ It is also possible to tap using multiple patterns:
123
+
124
+ ```ruby
125
+ freddy.tap_into(['user.created', 'user.deleted'], group: 'user-event') do
126
+ # This processes events from both user.created topic and user.deleted topic.
127
+ # It also groups them into one queue called 'user-event'. This ensures that
128
+ # only one listener within a group process a particular event.
129
+ end
130
+ ```
131
+
124
132
  ## The ResponderHandler
125
133
 
126
134
  When responding to a message or tapping the ResponderHandler is returned.
@@ -137,14 +145,11 @@ responder_handler.shutdown
137
145
 
138
146
  ## Request Tracing
139
147
 
140
- Freddy supports [OpenTracing API|https://github.com/opentracing/opentracing-ruby]. You must set a global tracer which then freddy will use:
141
- ```ruby
142
- OpenTracing.global_tracing = MyTracerImplementation.new(...)
143
- ```
144
-
145
- Current trace can be accessed through a thread-local variable `OpenTracing.active_span`. Calling `deliver` or `deliver_with_response` will pass trace context to down-stream services.
148
+ Freddy supports [OpenTelemetry API|https://github.com/open-telemetry/opentelemetry-ruby].
149
+ The trace information is automatically passed through `deliver`,
150
+ `deliver_with_response`, `respond_to` and `tap_into` calls.
146
151
 
147
- See [opentracing-ruby](https://github.com/opentracing/opentracing-ruby) for more information.
152
+ This is not compatible with `opentelemetry-instrumentation-bunny` library.
148
153
 
149
154
  ## Notes about concurrency
150
155
 
data/freddy.gemspec CHANGED
@@ -1,19 +1,17 @@
1
-
2
1
  lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'freddy/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = if RUBY_PLATFORM == 'java'
7
- 'freddy-jruby'
8
- else
9
- 'freddy'
10
- end
11
- spec.version = '1.4.2'
12
- spec.authors = ['Salemove TechMovers']
6
+ spec.name = 'freddy'
7
+ spec.version = Freddy::VERSION
8
+ spec.authors = ['Glia TechMovers']
13
9
  spec.email = ['techmovers@salemove.com']
14
10
  spec.description = 'Messaging API'
15
11
  spec.summary = 'API for inter-application messaging supporting acknowledgements and request-response'
16
- spec.license = 'Private'
12
+ spec.license = 'MIT'
13
+ spec.homepage = 'https://github.com/salemove/freddy'
14
+ spec.required_ruby_version = '>= 2.7'
17
15
 
18
16
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -23,14 +21,9 @@ Gem::Specification.new do |spec|
23
21
  spec.add_development_dependency 'bundler'
24
22
  spec.add_development_dependency 'rake'
25
23
 
26
- if RUBY_PLATFORM == 'java'
27
- spec.add_dependency 'march_hare', '~> 2.12.0'
28
- spec.add_dependency 'symbolizer'
29
- else
30
- spec.add_dependency 'bunny', '~> 2.11'
31
- spec.add_dependency 'oj', '~> 2.13'
32
- end
33
-
34
- spec.add_dependency 'opentracing', '~> 0.4'
24
+ spec.add_dependency 'bunny', '~> 2.11'
25
+ spec.add_dependency 'oj', '~> 3.6'
26
+ spec.add_dependency 'opentelemetry-api', '~> 1.0.0.rc3'
27
+ spec.add_dependency 'opentelemetry-semantic_conventions', '~> 1.0'
35
28
  spec.add_dependency 'thread', '~> 0.1'
36
29
  end
data/lib/freddy.rb CHANGED
@@ -3,9 +3,11 @@
3
3
  require 'json'
4
4
  require 'thread/pool'
5
5
  require 'securerandom'
6
- require 'opentracing'
6
+ require 'opentelemetry'
7
+ require 'opentelemetry/semantic_conventions'
8
+ require_relative './freddy/version'
7
9
 
8
- Dir[File.dirname(__FILE__) + '/freddy/*.rb'].each(&method(:require))
10
+ Dir["#{File.dirname(__FILE__)}/freddy/*.rb"].sort.each(&method(:require))
9
11
 
10
12
  class Freddy
11
13
  FREDDY_TOPIC_EXCHANGE_NAME = 'freddy-topic'
@@ -26,22 +28,15 @@ class Freddy
26
28
  # @return [Freddy]
27
29
  #
28
30
  # @example
29
- # Freddy.build(Logger.new(STDOUT), user: 'thumper', pass: 'howdy')
30
- def self.build(logger = Logger.new(STDOUT), max_concurrency: DEFAULT_MAX_CONCURRENCY, **config)
31
- OpenTracing.global_tracer ||= OpenTracing::Tracer.new
32
-
31
+ # Freddy.build(Logger.new($stdout), user: 'thumper', pass: 'howdy')
32
+ def self.build(logger = Logger.new($stdout), max_concurrency: DEFAULT_MAX_CONCURRENCY, **config)
33
33
  connection = Adapters.determine.connect(config)
34
34
  new(connection, logger, max_concurrency)
35
35
  end
36
36
 
37
- # @deprecated Use {OpenTracing.active_span} instead
38
- def self.trace
39
- OpenTracing.active_span
40
- end
41
-
42
- # @deprecated Use OpenTracing ScopeManager instead
43
- def self.trace=(trace)
44
- OpenTracing.scope_manager.activate(trace) if OpenTracing.active_span != trace
37
+ # @private
38
+ def self.tracer
39
+ @tracer ||= OpenTelemetry.tracer_provider.tracer('freddy', Freddy::VERSION)
45
40
  end
46
41
 
47
42
  def initialize(connection, logger, max_concurrency)
@@ -111,6 +106,15 @@ class Freddy
111
106
  # @option options [String] :group
112
107
  # only one of the listeners in given group will receive a message. All
113
108
  # listeners will receive a message if the group is not specified.
109
+ # @option options [Boolean] :durable
110
+ # Should the consumer queue be durable? Default is `false`. This option can
111
+ # be used only in combination with option `:group`.
112
+ # @option options [Boolean] :on_exception
113
+ # Defines consumer's behaviour when the callback fails to process a message
114
+ # and raises an exception. Can be one of `:ack`, `:reject` or `:requeue`.
115
+ # `:ack` simply acknowledges the message and re-raises the exception. `:reject`
116
+ # rejects the message without requeueing it. `:requeue` rejects the message with
117
+ # `requeue` flag.
114
118
  #
115
119
  # @yield [message] Yields received message to the block
116
120
  #
@@ -120,12 +124,12 @@ class Freddy
120
124
  # freddy.tap_into 'notifications.*' do |message|
121
125
  # puts "Notification showed #{message.inspect}"
122
126
  # end
123
- def tap_into(pattern, options = {}, &callback)
124
- @logger.debug "Tapping into messages that match #{pattern}"
127
+ def tap_into(pattern_or_patterns, options = {}, &callback)
128
+ @logger.debug "Tapping into messages that match #{pattern_or_patterns}"
125
129
 
126
130
  Consumers::TapIntoConsumer.consume(
127
131
  thread_pool: Thread.pool(@prefetch_buffer_size),
128
- pattern: pattern,
132
+ patterns: Array(pattern_or_patterns),
129
133
  channel: @connection.create_channel(prefetch: @prefetch_buffer_size),
130
134
  options: options,
131
135
  &callback
@@ -1,36 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'adapters/bunny_adapter'
4
+
3
5
  class Freddy
4
6
  module Adapters
5
7
  def self.determine
6
- if RUBY_PLATFORM == 'java'
7
- require_relative 'adapters/march_hare_adapter'
8
- MarchHareAdapter
9
- else
10
- require_relative 'adapters/bunny_adapter'
11
- BunnyAdapter
12
- end
13
- end
14
-
15
- module Shared
16
- class Queue
17
- def initialize(queue)
18
- @queue = queue
19
- end
20
-
21
- def bind(*args)
22
- @queue.bind(*args)
23
- self
24
- end
25
-
26
- def name
27
- @queue.name
28
- end
29
-
30
- def message_count
31
- @queue.message_count
32
- end
33
- end
8
+ BunnyAdapter
34
9
  end
35
10
  end
36
11
  end
@@ -34,7 +34,7 @@ class Freddy
34
34
  @channel = channel
35
35
  end
36
36
 
37
- def_delegators :@channel, :topic, :default_exchange, :consumers, :acknowledge
37
+ def_delegators :@channel, :topic, :default_exchange, :consumers, :acknowledge, :reject
38
38
 
39
39
  def queue(*args)
40
40
  Queue.new(@channel.queue(*args))
@@ -47,12 +47,29 @@ class Freddy
47
47
  end
48
48
  end
49
49
 
50
- class Queue < Shared::Queue
50
+ class Queue
51
+ def initialize(queue)
52
+ @queue = queue
53
+ end
54
+
55
+ def bind(*args)
56
+ @queue.bind(*args)
57
+ self
58
+ end
59
+
60
+ def name
61
+ @queue.name
62
+ end
63
+
64
+ def message_count
65
+ @queue.message_count
66
+ end
67
+
51
68
  def subscribe(manual_ack: false)
52
69
  @queue.subscribe(manual_ack: manual_ack) do |info, properties, payload|
53
70
  parsed_payload = Payload.parse(payload)
54
71
  delivery = Delivery.new(
55
- parsed_payload, properties, info.routing_key, info.delivery_tag
72
+ parsed_payload, properties, info.routing_key, info.delivery_tag, info.exchange
56
73
  )
57
74
  yield(delivery)
58
75
  end
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir[File.dirname(__FILE__) + '/consumers/*.rb'].each(&method(:require))
3
+ Dir["#{File.dirname(__FILE__)}/consumers/*.rb"].sort.each(&method(:require))
@@ -35,21 +35,11 @@ class Freddy
35
35
 
36
36
  def process_message(delivery)
37
37
  @consume_thread_pool.process do
38
- begin
39
- scope = delivery.build_trace("freddy:respond:#{@destination}",
40
- tags: {
41
- 'peer.address' => "#{@destination}:#{delivery.payload[:type]}",
42
- 'component' => 'freddy',
43
- 'span.kind' => 'server', # RPC
44
- 'message_bus.destination' => @destination,
45
- 'message_bus.correlation_id' => delivery.correlation_id
46
- })
47
-
38
+ delivery.in_span do
48
39
  yield(delivery)
49
- ensure
50
- @channel.acknowledge(delivery.tag, false)
51
- scope.close
52
40
  end
41
+ ensure
42
+ @channel.acknowledge(delivery.tag, false)
53
43
  end
54
44
  end
55
45
  end
@@ -7,11 +7,9 @@ class Freddy
7
7
  @logger = logger
8
8
  end
9
9
 
10
- def consume(_channel, queue)
10
+ def consume(_channel, queue, &block)
11
11
  @logger.debug "Consuming messages on #{queue.name}"
12
- queue.subscribe do |delivery|
13
- yield(delivery)
14
- end
12
+ queue.subscribe(&block)
15
13
  end
16
14
  end
17
15
  end
@@ -7,11 +7,13 @@ class Freddy
7
7
  new(*attrs).consume(&block)
8
8
  end
9
9
 
10
- def initialize(thread_pool:, pattern:, channel:, options:)
10
+ def initialize(thread_pool:, patterns:, channel:, options:)
11
11
  @consume_thread_pool = thread_pool
12
- @pattern = pattern
12
+ @patterns = patterns
13
13
  @channel = channel
14
14
  @options = options
15
+
16
+ raise 'Do not use durable queues without specifying a group' if durable? && !group
15
17
  end
16
18
 
17
19
  def consume(&block)
@@ -28,38 +30,52 @@ class Freddy
28
30
 
29
31
  def create_queue
30
32
  topic_exchange = @channel.topic(Freddy::FREDDY_TOPIC_EXCHANGE_NAME)
31
- group = @options.fetch(:group, nil)
32
-
33
- if group
34
- @channel
35
- .queue("groups.#{group}")
36
- .bind(topic_exchange, routing_key: @pattern)
37
- else
38
- @channel
39
- .queue('', exclusive: true)
40
- .bind(topic_exchange, routing_key: @pattern)
33
+
34
+ queue =
35
+ if group
36
+ @channel.queue("groups.#{group}", durable: durable?)
37
+ else
38
+ @channel.queue('', exclusive: true)
39
+ end
40
+
41
+ @patterns.each do |pattern|
42
+ queue.bind(topic_exchange, routing_key: pattern)
41
43
  end
44
+
45
+ queue
42
46
  end
43
47
 
44
48
  def process_message(_queue, delivery)
45
49
  @consume_thread_pool.process do
46
- begin
47
- scope = delivery.build_trace("freddy:observe:#{@pattern}",
48
- tags: {
49
- 'message_bus.destination' => @pattern,
50
- 'message_bus.correlation_id' => delivery.correlation_id,
51
- 'component' => 'freddy',
52
- 'span.kind' => 'consumer' # Message Bus
53
- },
54
- force_follows_from: true)
55
-
50
+ delivery.in_span(force_follows_from: true) do
56
51
  yield delivery.payload, delivery.routing_key
57
- ensure
58
- @channel.acknowledge(delivery.tag, false)
59
- scope.close
52
+ @channel.acknowledge(delivery.tag)
53
+ end
54
+ rescue StandardError
55
+ case on_exception
56
+ when :reject
57
+ @channel.reject(delivery.tag)
58
+ when :requeue
59
+ @channel.reject(delivery.tag, true)
60
+ else
61
+ @channel.acknowledge(delivery.tag)
60
62
  end
63
+
64
+ raise
61
65
  end
62
66
  end
67
+
68
+ def group
69
+ @options.fetch(:group, nil)
70
+ end
71
+
72
+ def durable?
73
+ @options.fetch(:durable, false)
74
+ end
75
+
76
+ def on_exception
77
+ @options.fetch(:on_exception, :ack)
78
+ end
63
79
  end
64
80
  end
65
81
  end