From 52971d6f764ec6f9b3273719f887a4ba4a99d248 Mon Sep 17 00:00:00 2001 From: Yuri Smirnov Date: Thu, 28 Mar 2024 15:32:20 +0300 Subject: [PATCH] add specs + fixes --- CHANGELOG.md | 7 +++- Gemfile.lock | 5 ++- README.md | 48 +++++++++++++----------- lib/rabbit.rb | 2 +- lib/rabbit/daemon.rb | 13 ++++--- lib/rabbit/version.rb | 2 +- rabbit_messaging.gemspec | 1 + spec/units/rabbit/daemon_spec.rb | 63 ++++++++++++++++++++++++++++++++ 8 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 spec/units/rabbit/daemon_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 58b59e7..1851990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## [1.0.0] - 2024-03-28 +### Added +- Add `ExponentialBackoffHandler` for handling errors in messages +- Optional `queue_suffix` config for read queues + ## [0.14.0] - 2023-02-27 ### Added - Exception notifier is required @@ -19,7 +24,7 @@ All notable changes to this project will be documented in this file. - `Gemfile.lock` added ### Fixed -- `unless Sneakers.logger` was never executed +- `unless Sneakers.logger` was never executed ## [0.11.0] - 2021-05-05 ### Added diff --git a/Gemfile.lock b/Gemfile.lock index b489621..02cb478 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,12 @@ PATH remote: . specs: - rabbit_messaging (0.14.0) + rabbit_messaging (0.15.0) bunny (~> 2.0) lamian rails (>= 5.2) sneakers (~> 2.0) + sneakers_handlers tainbox GEM @@ -268,6 +269,8 @@ GEM rake serverengine (~> 2.0.5) thor + sneakers_handlers (0.0.6) + sneakers sorted_set (1.0.3) rbtree set (~> 1.0) diff --git a/README.md b/README.md index e7a4f0d..3357f45 100644 --- a/README.md +++ b/README.md @@ -33,38 +33,42 @@ require "rabbit_messaging" - `Rabbit.config` provides setters for following options: - * `group_id` (`Symbol`), *required* + - `group_id` (`Symbol`), *required* Shared identifier which used to select api. As usual, it should be same as default project_id (I.e. we have project 'support', which runs only one application in production. So on, it's group_id should be :support) - * `project_id` (`Symbol`), *required* + - `project_id` (`Symbol`), *required* Personal identifier which used to select exact service. As usual, it should be same as default project_id with optional stage_id. (I.e. we have project 'support', in production it's project_id is :support, but in staging it uses :support1 and :support2 ids for corresponding stages) + - `queue_suffix` (`String`) - * `exception_notifier` (`Proc`) + Optional suffix added to the read queue name. For example, in case of `group_id = "grp"`, `project_id = "prj"` and + `queue_suffix = "sfx"`, Rabbit will read from queue named `"grp.prj.sfx"`. + + - `exception_notifier` (`Proc`) You must provide your own notifier like this to notify about exceptions: - + ```ruby config.exception_notifier = proc { |e| MyCoolNotifier.notify!(e) } ``` - * `hooks` (`Hash`) + - `hooks` (`Hash`) :before_fork and :after_fork hooks, used same way as in unicorn / puma / que / etc - * `environment` (one of `:test`, `:development`, `:production`), *default:* `:production` + - `environment` (one of `:test`, `:development`, `:production`), *default:- `:production` Internal environment of gem. - * `:test` environment stubs publishing and does not suppress errors - * `:development` environment auto-creates queues and uses default exchange - * `:production` environment enables handlers caching and gets maximum strictness + - `:test` environment stubs publishing and does not suppress errors + - `:development` environment auto-creates queues and uses default exchange + - `:production` environment enables handlers caching and gets maximum strictness By default gem skips publishing in test and development environments. If you want to change that then manually set `Rabbit.skip_publishing_in` with an array of environments. @@ -73,13 +77,13 @@ require "rabbit_messaging" Rabbit.skip_publishing_in = %i[test] ``` - * `receiving_job_class_callable` (`Proc`) + - `receiving_job_class_callable` (`Proc`) Custom ActiveJob subclass to work with received messages. Receives the following attributes as `kwarg`-arguments: - * `:arguments` - information about message type (`type`), application id (`app_id`), message id (`message_id`); - * `:delivery_info` - information about `exchange`, `routing_key`, etc; - * `:message` - received RabbitMQ message (often in a `string` format); + - `:arguments` - information about message type (`type`), application id (`app_id`), message id (`message_id`); + - `:delivery_info` - information about `exchange`, `routing_key`, etc; + - `:message` - received RabbitMQ message (often in a `string` format); ```ruby { @@ -93,7 +97,7 @@ require "rabbit_messaging" } ``` - * `before_receiving_hooks, after_receiving_hooks` (`Array of Procs`) + - `before_receiving_hooks, after_receiving_hooks` (`Array of Procs`) Before and after hooks with message processing in the middle. Where `before_receiving_hooks` and `after_receiving_hooks` are empty arrays by default. @@ -109,6 +113,7 @@ require "rabbit_messaging" config.after_receiving_hooks.append(proc { |message, arguments| do_stuff_4 }) ``` + --- ### Client @@ -127,16 +132,16 @@ Rabbit.publish( - This code sends messages via basic_publish with following parameters: - * `routing_key`: `"support"` - * `exchange`: `"group_id.project_id.fanout"` (default is `"group_id.poject_id"`) - * `mandatory`: `true` (same as confirm_select) + - `routing_key`: `"support"` + - `exchange`: `"group_id.project_id.fanout"` (default is `"group_id.poject_id"`) + - `mandatory`: `true` (same as confirm_select) It is set to raise error if routing failed - * `persistent`: `true` - * `type`: `"ping"` - * `content_type`: `"application/json"` (always) - * `app_id`: `"group_id.project_id"` + - `persistent`: `true` + - `type`: `"ping"` + - `content_type`: `"application/json"` (always) + - `app_id`: `"group_id.project_id"` - Messages are logged to `/log/rabbit.log` @@ -150,6 +155,7 @@ to teardown and setup external connections between daemon restarts, for example - After the server runs, received messages are handled by `Rabbit::EventHandler` subclasses. Subclasses are selected by following code: + ```ruby "rabbit/handler/#{group_id}/#{event}".camelize.constantize ``` diff --git a/lib/rabbit.rb b/lib/rabbit.rb index 826939a..aac4eb8 100644 --- a/lib/rabbit.rb +++ b/lib/rabbit.rb @@ -18,6 +18,7 @@ class Config attribute :group_id, Symbol attribute :project_id, Symbol + attribute :queue_suffix, String attribute :hooks, default: {} attribute :environment, Symbol, default: :production attribute :queue_name_conversion @@ -26,7 +27,6 @@ class Config attribute :before_receiving_hooks, default: [] attribute :after_receiving_hooks, default: [] attribute :skip_publishing_in, default: %i[test development] - attribute :queue_suffix, String attribute :backoff_handler_max_retries, Integer, default: 6 attribute :receive_logger, default: lambda { diff --git a/lib/rabbit/daemon.rb b/lib/rabbit/daemon.rb index 0364aa2..5003654 100644 --- a/lib/rabbit/daemon.rb +++ b/lib/rabbit/daemon.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "sneakers" +require "sneakers_handlers" require "lamian" require "sneakers/runner" @@ -39,14 +40,16 @@ def run(logger: Sneakers.logger) end def config - Rails.application.config_for("sneakers").symbolize_keys + @config ||= Rails.application.config_for("sneakers").symbolize_keys end def connection - bunny_config = config.delete(:bunny_options).to_h.symbolize_keys - bunny_logger = logger.dup - bunny_logger.level = bunny_config[:log_level] || :info - Bunny.new(**bunny_config, logger: bunny_logger) + @connection ||= begin + bunny_config = config.delete(:bunny_options).to_h.symbolize_keys + bunny_logger = logger.dup + bunny_logger.level = bunny_config.delete(:log_level) || :info + Bunny.new(**bunny_config, logger: bunny_logger) + end end private diff --git a/lib/rabbit/version.rb b/lib/rabbit/version.rb index ed79da5..83c6f43 100644 --- a/lib/rabbit/version.rb +++ b/lib/rabbit/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Rabbit - VERSION = "0.14.0" + VERSION = "0.15.0" end diff --git a/rabbit_messaging.gemspec b/rabbit_messaging.gemspec index 924b39a..a04dcbb 100644 --- a/rabbit_messaging.gemspec +++ b/rabbit_messaging.gemspec @@ -23,5 +23,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "lamian" spec.add_runtime_dependency "rails", ">= 5.2" spec.add_runtime_dependency "sneakers", "~> 2.0" + spec.add_runtime_dependency "sneakers_handlers" spec.add_runtime_dependency "tainbox" end diff --git a/spec/units/rabbit/daemon_spec.rb b/spec/units/rabbit/daemon_spec.rb new file mode 100644 index 0000000..7e6ee81 --- /dev/null +++ b/spec/units/rabbit/daemon_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +RSpec.describe Rabbit::Daemon do + before do + allow(app_double).to receive(:config_for).with("sneakers").and_return(sneakers_config) + allow(runner_double).to receive(:run) + allow(logger_double).to receive(:dup).and_return(sneaker_logger_double) + + allow(Bunny).to receive(:new).and_return(bunny_double) + allow(Rails).to receive(:application).and_return(app_double) + allow(Rails).to receive(:env).and_return("test") + + allow(Sneakers::Runner).to receive(:new).and_return(runner_double) + end + + let(:app_double) { double(:rails_app) } + let(:worker_double) { double(:receiving_worker) } + let(:runner_double) { double(:sneakers_runner) } + let(:sneaker_logger_double) { double(:sneaker_logger) } + let(:logger_double) { double(:logger) } + let(:bunny_double) { double(:bunny) } + + let(:sneakers_config) do + { + foo: 1, + bunny_options: { + bar: 2, + log_level: "warn", + } + } + end + + specify do + expect(Sneakers).to receive(:configure).with( + connection: bunny_double, + env: "test", + exchange_type: :direct, + exchange: "test_group_id.test_project_id", + hooks: {}, + supervisor: true, + daemonize: false, + exit_on_detach: true, + log: logger_double, + foo: 1, + ) + + expect(Bunny).to receive(:new).with(logger: sneaker_logger_double, bar: 2) + + expect(Rabbit::Receiving::Worker).to receive(:from_queue).with( + "test_group_id.test_project_id", + handler: SneakersHandlers::ExponentialBackoffHandler, + max_retries: 6, + arguments: { + "x-dead-letter-exchange" => "test_group_id.test_project_id.dlx", + "x-dead-letter-routing-key" => "test_group_id.test_project_id.dlx", + }, + ) + + expect(sneaker_logger_double).to receive(:level=).with("warn") + expect(runner_double).to receive(:run) + Rabbit::Daemon.run(logger: logger_double) + end +end