freddy 1.4.2 → 2.0.0

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