From f60cb61d4dd98a0f7f9ee1843103fbcb983831f3 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 13:30:29 -0300 Subject: [PATCH 001/108] feat: add worker class to manager the active job --- lib/pipefy_message/worker.rb | 11 +++++++++++ spec/worker_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 lib/pipefy_message/worker.rb create mode 100644 spec/worker_spec.rb diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb new file mode 100644 index 0000000..6e01b87 --- /dev/null +++ b/lib/pipefy_message/worker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module PipefyMessage + module Worker + # ClassMethods + module ClassMethods #ActiveJob + def perform(body, options = {}); end + def pipefymessages_options(opts = {}); end + end + end +end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb new file mode 100644 index 0000000..36a6ea1 --- /dev/null +++ b/spec/worker_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.describe PipefyMessage::Worker do + $result =~ nil + + class TestWorker + include PipefyMessage::Worker + # pipefymessage_options broker: "aws", queue: "default", delay: 1, retry: 1 + + def perform(body) + $result = body + end + end + + describe "#perform" do + it "allow call .perform" do + worker = TestWorker.new + worker.perform("test") + expect($result).to eq "test" + end + end +end From 8ad2bc1292a8480b44175d0d8fc308054ffa09eb Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:40:42 -0300 Subject: [PATCH 002/108] feat: add libraries to gemfile --- Gemfile | 1 + Gemfile.lock | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Gemfile b/Gemfile index ca002f8..0c745a5 100644 --- a/Gemfile +++ b/Gemfile @@ -16,4 +16,5 @@ group :development, :test do gem "pry-doc" gem "rspec", "~> 3.0" gem "rubocop", "~> 1.21" + gem "solargraph" end diff --git a/Gemfile.lock b/Gemfile.lock index 3df6d36..0bce941 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,15 +29,27 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) + backport (1.2.0) + benchmark (0.2.0) coderay (1.1.3) concurrent-ruby (1.1.9) diff-lcs (1.3) + e2mmap (0.1.0) i18n (1.10.0) concurrent-ruby (~> 1.0) + jaro_winkler (1.5.4) jmespath (1.5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) logger (1.3.0) method_source (1.0.0) minitest (5.15.0) + nokogiri (1.13.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.13.3-x86_64-linux) + racc (~> 1.4) parallel (1.21.0) parser (3.0.2.0) ast (~> 2.4.1) @@ -47,9 +59,12 @@ GEM pry-doc (1.3.0) pry (~> 0.11) yard (~> 0.9.11) + racc (1.6.0) rainbow (3.0.0) rake (13.0.6) regexp_parser (2.1.1) + reverse_markdown (2.1.1) + nokogiri rexml (3.2.5) rspec (3.8.0) rspec-core (~> 3.8.0) @@ -76,7 +91,23 @@ GEM rubocop-ast (1.12.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) + solargraph (0.41.2) + backport (~> 1.1) + benchmark + bundler (>= 1.17.2) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + reverse_markdown (>= 1.0.5, < 3) + rubocop (>= 0.52) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + thor (1.2.1) thread_safe (0.3.6) + tilt (2.0.10) tzinfo (1.2.9) thread_safe (~> 0.1) unicode-display_width (2.1.0) @@ -99,6 +130,7 @@ DEPENDENCIES rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.21) + solargraph BUNDLED WITH 2.3.9 From 642d029e31ea275cba92cd6b1fa66626485a1c7a Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:41:04 -0300 Subject: [PATCH 003/108] feat: add config to rubocop --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index e93bd8f..4a34c43 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,6 @@ AllCops: TargetRubyVersion: 2.6 + NewCops: enable Style/StringLiterals: Enabled: true From d9c66b479b2e7bef50d773f3584a7fa46f03ef28 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:41:21 -0300 Subject: [PATCH 004/108] feat: add worker to boot app --- lib/pipefy_message.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 7962474..543b9c6 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -5,6 +5,7 @@ require_relative "pipefy_message/broker/aws/sns/publisher" require_relative "pipefy_message/base_consumer" require_relative "pipefy_message/base_publisher" +require_relative "pipefy_message/worker" require "logger" module PipefyMessage From f9be3ab4e9534acf6b7700469ba22233d6186921 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 17:45:52 -0300 Subject: [PATCH 005/108] feat: add default worker options --- lib/pipefy_message.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 543b9c6..a124437 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -9,6 +9,14 @@ require "logger" module PipefyMessage + + def self.default_worker_options + @default_worker_options ||= { + "broker" => "gcp", + "queue" => "default" + } + end + # Simple Test class to validate the project class Test def initialize From c6b6bba3453e778c372ee24306c6c90a75f77104 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 17:47:16 -0300 Subject: [PATCH 006/108] feat: add self.included to add attr in runtime --- lib/pipefy_message/worker.rb | 16 +++++++++++++--- spec/worker_spec.rb | 9 ++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 6e01b87..5071153 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,11 +1,21 @@ # frozen_string_literal: true module PipefyMessage + # ClassMethods module Worker + def self.included(base) + base.extend(ClassMethods) + end + # ClassMethods - module ClassMethods #ActiveJob - def perform(body, options = {}); end - def pipefymessages_options(opts = {}); end + module ClassMethods + def pipefymessage_options(opts = {}) + options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) + options_hash.each do |k, v| + singleton_class.class_eval { attr_accessor k } + send("#{k}=", v) + end + end end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 36a6ea1..c98e3ce 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -5,7 +5,7 @@ class TestWorker include PipefyMessage::Worker - # pipefymessage_options broker: "aws", queue: "default", delay: 1, retry: 1 + pipefymessage_options broker: "aws" def perform(body) $result = body @@ -19,4 +19,11 @@ def perform(body) expect($result).to eq "test" end end + + describe "#options class" do + it "should set options in class" do + TestWorker.new + expect(TestWorker.broker).to eq "aws" + end + end end From 9597d624ef240b46962577f450b8de894eec2d89 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 15:29:59 -0300 Subject: [PATCH 007/108] feat: add .env.local to dev enviroment --- .env.local | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .env.local diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..c0b9490 --- /dev/null +++ b/.env.local @@ -0,0 +1,4 @@ + export AWS_ACCESS_KEY_ID=foo + export AWS_SECRET_ACCESS_KEY=bar + export AWS_ENDPOINT="http://localhost:4566" + export ENABLE_AWS_CLIENT_CONFIG=true From 52bdc63e21c1ea947500996359258190f3220f88 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:56:54 -0300 Subject: [PATCH 008/108] refact: url of queue Co-authored-by: Duda Ferreira --- lib/pipefy_message.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index a124437..fd0a855 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -6,6 +6,7 @@ require_relative "pipefy_message/base_consumer" require_relative "pipefy_message/base_publisher" require_relative "pipefy_message/worker" +require_relative "pipefy_message/providers/broker" require "logger" module PipefyMessage @@ -13,7 +14,7 @@ module PipefyMessage def self.default_worker_options @default_worker_options ||= { "broker" => "gcp", - "queue" => "default" + "queue" => "http://localhost:4566" } end From 21b9a526855159896986c0f88e8622f6daa1b732 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:57:42 -0300 Subject: [PATCH 009/108] feat: implement a perform_async method --- lib/pipefy_message/worker.rb | 23 ++++++++++++++++++++--- spec/worker_spec.rb | 21 +++++++++++++++------ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 5071153..e0eb55a 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,21 +1,38 @@ # frozen_string_literal: true +require 'singleton' + module PipefyMessage # ClassMethods module Worker def self.included(base) base.extend(ClassMethods) end - + # ClassMethods - module ClassMethods - def pipefymessage_options(opts = {}) + module ClassMethods + def pipefymessage_options(opts = {}) options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end end + + def perform_async() + instance_broker = build_instance_broker + obj = self.new + instance_broker.poller do |message| + obj.perform(message) + end + end + + def build_instance_broker() + map = {"aws" => "PipefyMessage::Providers::AwsBroker"} + require_relative "providers/#{self.broker}_broker" + + map[self.broker].constantize.new(self.queue) + end end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index c98e3ce..38992a5 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -2,28 +2,37 @@ RSpec.describe PipefyMessage::Worker do $result =~ nil + + # class MockBroker < Broker + # def poller + # yield("test") + # end + # end class TestWorker include PipefyMessage::Worker pipefymessage_options broker: "aws" - def perform(body) - $result = body + def perform(message) + $result = message end end describe "#perform" do - it "allow call .perform" do - worker = TestWorker.new - worker.perform("test") + + #mock PipefyMessage::Providers::AwsBroker -> MockBroker + + it "allow call .perform from instance worker when call perform_async from ClassMethod" do + TestWorker.perform_async expect($result).to eq "test" end end describe "#options class" do it "should set options in class" do - TestWorker.new expect(TestWorker.broker).to eq "aws" end end + + end From 697841764958d87b8bff1667e8b3abc17ec0ad4b Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:58:11 -0300 Subject: [PATCH 010/108] feat: create a parent broker and impl aws_broker --- lib/pipefy_message/providers/aws_broker.rb | 21 +++++++++++++++++++++ lib/pipefy_message/providers/broker.rb | 9 +++++++++ 2 files changed, 30 insertions(+) create mode 100644 lib/pipefy_message/providers/aws_broker.rb create mode 100644 lib/pipefy_message/providers/broker.rb diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb new file mode 100644 index 0000000..066a120 --- /dev/null +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -0,0 +1,21 @@ +require "aws-sdk-sqs" +require "json" + +module PipefyMessage + module Providers + class AwsBroker < Broker + def initialize(queue_url) + @poller = Aws::SQS::QueuePoller.new(queue_url) + @wait_time_seconds = 10 + end + + def poller() + ## Aws poller + @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + payload = JSON.parse(received_message.body) + yield(payload) + end + end + end + end +end \ No newline at end of file diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb new file mode 100644 index 0000000..de3a1aa --- /dev/null +++ b/lib/pipefy_message/providers/broker.rb @@ -0,0 +1,9 @@ +module PipefyMessage + module Providers + class Broker + def poller() + raise NotImplementedError + end + end + end +end \ No newline at end of file From 44efd09e4f1272151b8203dbef1576ce31ce58c6 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Wed, 13 Apr 2022 14:28:46 -0300 Subject: [PATCH 011/108] test: implement fail to broker Co-authored-by: Duda Ferreira --- lib/pipefy_message/worker.rb | 16 ++++++++++------ spec/worker_spec.rb | 27 ++++++++++++++++++--------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index e0eb55a..0a5f44b 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -19,12 +19,16 @@ def pipefymessage_options(opts = {}) end end - def perform_async() - instance_broker = build_instance_broker - obj = self.new - instance_broker.poller do |message| - obj.perform(message) - end + def perform_async() + begin + obj = self.new + build_instance_broker.poller do |message| + obj.perform(message) + end + rescue Exception => exception + # TODO: Implement retry + raise exception + end end def build_instance_broker() diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 38992a5..46be7f1 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -3,11 +3,16 @@ RSpec.describe PipefyMessage::Worker do $result =~ nil - # class MockBroker < Broker - # def poller - # yield("test") - # end - # end + class MockBroker < PipefyMessage::Providers::Broker + def poller + yield("test") + end + end + class MockBrokerFail < PipefyMessage::Providers::Broker + def poller + raise Exception.new "This is an exception" + end + end class TestWorker include PipefyMessage::Worker @@ -19,13 +24,17 @@ def perform(message) end describe "#perform" do - - #mock PipefyMessage::Providers::AwsBroker -> MockBroker - - it "allow call .perform from instance worker when call perform_async from ClassMethod" do + it "should call #perform from child instance when call #perform_async with success" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + TestWorker.perform_async expect($result).to eq "test" end + + it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) + expect{ TestWorker.perform_async }.to raise_error + end end describe "#options class" do From 80a06e20022ce90060e47caf9d5506ae82ef1b90 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Wed, 13 Apr 2022 15:59:15 -0300 Subject: [PATCH 012/108] feat: add config to broker --- lib/pipefy_message/providers/aws_broker.rb | 23 ++++++++++++++++++++++ spec/worker_spec.rb | 7 ++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 066a120..89924f7 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,11 +4,34 @@ module PipefyMessage module Providers class AwsBroker < Broker + + @@default_options = { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || "true") + } + def initialize(queue_url) + + @config = AwsBroker.config_options + + Aws.config.update(@config) + + + @sqs = Aws::SQS::Client.new(region: @config[:region]) + require "pry"; binding.pry + @poller = Aws::SQS::QueuePoller.new(queue_url) + @poller.before_request { stop! if user_interrupt == true } @wait_time_seconds = 10 end + def self.config_options + @@default_options + end + def poller() ## Aws poller @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 46be7f1..0472e50 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -16,16 +16,17 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws" + pipefymessage_options broker: "aws", queue: "http://localhost:4566/000000000000/pipefy-local-queue" def perform(message) + puts message $result = message end end describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + # allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) TestWorker.perform_async expect($result).to eq "test" @@ -33,7 +34,7 @@ def perform(message) it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error + expect{ TestWorker.perform_async }.to raise_error(Exception, /This is an exception/) end end From 3f4a254713c52fc1e4203a679826b2dfb027c139 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 12:56:23 -0300 Subject: [PATCH 013/108] feat: add resourceError --- lib/pipefy_message.rb | 1 + lib/pipefy_message/providers/errors.rb | 11 +++++++++++ spec/worker_spec.rb | 13 +++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 lib/pipefy_message/providers/errors.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index fd0a855..99112f3 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -7,6 +7,7 @@ require_relative "pipefy_message/base_publisher" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" +require_relative "pipefy_message/providers/errors" require "logger" module PipefyMessage diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb new file mode 100644 index 0000000..623d182 --- /dev/null +++ b/lib/pipefy_message/providers/errors.rb @@ -0,0 +1,11 @@ +module PipefyMessage + module Providers + module Errors + class ResourceError < StandardError + def initialize(msg="ResourceError") + super + end + end + end + end +end \ No newline at end of file diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 0472e50..5f2b9b5 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Worker do +RSpec.describe PipefyMessage::Worker do + $result =~ nil class MockBroker < PipefyMessage::Providers::Broker @@ -10,13 +11,13 @@ def poller end class MockBrokerFail < PipefyMessage::Providers::Broker def poller - raise Exception.new "This is an exception" + raise PipefyMessage::Providers::Errors::ResourceError end end class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue: "http://localhost:4566/000000000000/pipefy-local-queue" + pipefymessage_options broker: "aws", queue: "pipefy-local-queue" def perform(message) puts message @@ -26,15 +27,15 @@ def perform(message) describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do - # allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) TestWorker.perform_async expect($result).to eq "test" end - it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do + it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error(Exception, /This is an exception/) + expect{ TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end From 0df13ca5fed237608576415da02cd316afbd8cf2 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 12:56:57 -0300 Subject: [PATCH 014/108] fix: stub problem and configure poller --- lib/pipefy_message/providers/aws_broker.rb | 31 +++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 89924f7..c2abccd 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -9,23 +9,24 @@ class AwsBroker < Broker access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || "true") + region: (ENV["AWS_REGION"] || "us-east-1") } - def initialize(queue_url) - - @config = AwsBroker.config_options - - Aws.config.update(@config) - - - @sqs = Aws::SQS::Client.new(region: @config[:region]) - require "pry"; binding.pry + def initialize(queue_name) + + begin + @config = AwsBroker.config_options + Aws.config.update(@config) + + @sqs = Aws::SQS::Client.new + queue_url = @sqs.get_queue_url({queue_name: queue_name}).queue_url + @poller = Aws::SQS::QueuePoller.new(queue_url) + + @wait_time_seconds = 10 + rescue (Aws::SQS::Errors::NonExistentQueue) => exception + raise PipefyMessage::Providers::Errors::ResourceError(exception.message) + end - @poller = Aws::SQS::QueuePoller.new(queue_url) - @poller.before_request { stop! if user_interrupt == true } - @wait_time_seconds = 10 end def self.config_options @@ -35,7 +36,7 @@ def self.config_options def poller() ## Aws poller @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| - payload = JSON.parse(received_message.body) + payload = JSON.parse(received_message.body) yield(payload) end end From 496b17bac815a58aa42095a02252e2b87f387333 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 13:33:00 -0300 Subject: [PATCH 015/108] feat: a test to aws_broker --- lib/pipefy_message/providers/aws_broker.rb | 65 +++++++++++----------- spec/providers/aws_broker_spec.rb | 20 +++++++ 2 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 spec/providers/aws_broker_spec.rb diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index c2abccd..46ad36c 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -2,44 +2,41 @@ require "json" module PipefyMessage - module Providers - class AwsBroker < Broker + module Providers + class AwsBroker < Broker + @@default_options = { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) + } - @@default_options = { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1") - } + def initialize(queue_name) + @config = AwsBroker.config_options + Aws.config.update(@config) - def initialize(queue_name) + @sqs = Aws::SQS::Client.new + queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url + @poller = Aws::SQS::QueuePoller.new(queue_url) - begin - @config = AwsBroker.config_options - Aws.config.update(@config) - - @sqs = Aws::SQS::Client.new - queue_url = @sqs.get_queue_url({queue_name: queue_name}).queue_url - @poller = Aws::SQS::QueuePoller.new(queue_url) - - @wait_time_seconds = 10 - rescue (Aws::SQS::Errors::NonExistentQueue) => exception - raise PipefyMessage::Providers::Errors::ResourceError(exception.message) - end + @wait_time_seconds = 10 + super + rescue (Aws::SQS::Errors::NonExistentQueue) => e + raise PipefyMessage::Providers::Errors::ResourceError, e.message + end - end + def self.config_options + @@default_options + end - def self.config_options - @@default_options - end - - def poller() - ## Aws poller - @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| - payload = JSON.parse(received_message.body) - yield(payload) - end - end + def poller + ## Aws poller + @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + payload = JSON.parse(received_message.body) + yield(payload) end + end end -end \ No newline at end of file + end +end diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb new file mode 100644 index 0000000..d4d689b --- /dev/null +++ b/spec/providers/aws_broker_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative "../../lib/pipefy_message/providers/aws_broker" + +RSpec.describe PipefyMessage::Providers::AwsBroker do + context "#AwsBroker" do + before do + ENV["AWS_CLI_STUB_RESPONSE"] = "true" + end + + describe "should raise Errors" do + it "QueueNonExistError" do + expect do + PipefyMessage::Providers::AwsBroker.new("my_queue") + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, + /The specified queue my_queue does not exist for this wsdl version/) + end + end + end +end From 3d6922d75f1d2a428260019e3faee6c9c013170e Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 13:34:35 -0300 Subject: [PATCH 016/108] feat: adjust erros to aws_broker --- .rubocop.yml | 6 +++- lib/pipefy_message/providers/aws_broker.rb | 28 ++++++++-------- lib/pipefy_message/providers/errors.rb | 17 +++++----- lib/pipefy_message/worker.rb | 38 ++++++++++------------ spec/providers/aws_broker_spec.rb | 13 ++++++-- spec/worker_spec.rb | 12 +++---- 6 files changed, 63 insertions(+), 51 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4a34c43..5a741f2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,4 +14,8 @@ Layout/LineLength: Max: 120 Metrics/BlockLength: - IgnoredMethods: ['describe', 'context'] \ No newline at end of file + IgnoredMethods: ["describe", "context"] + +Lint/MissingSuper: + Exclude: + - "/**/*" diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 46ad36c..c6230dc 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,30 +4,32 @@ module PipefyMessage module Providers class AwsBroker < Broker - @@default_options = { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) - } - def initialize(queue_name) - @config = AwsBroker.config_options + @config = build_options Aws.config.update(@config) @sqs = Aws::SQS::Client.new + # require 'pry'; binding.pry queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url) @wait_time_seconds = 10 - super - rescue (Aws::SQS::Errors::NonExistentQueue) => e + rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end - def self.config_options - @@default_options + def default_options + { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") + } + end + + def build_options + default_options.merge({}) end def poller diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 623d182..7efaf90 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -1,11 +1,12 @@ +# frozen_string_literal: true module PipefyMessage - module Providers - module Errors - class ResourceError < StandardError - def initialize(msg="ResourceError") - super - end - end + module Providers + module Errors + class ResourceError < RuntimeError + def initialize(msg = "ResourceError") + super end + end end -end \ No newline at end of file + end +end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 0a5f44b..b03011b 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'singleton' +require "singleton" module PipefyMessage # ClassMethods @@ -8,35 +8,33 @@ module Worker def self.included(base) base.extend(ClassMethods) end - + # ClassMethods - module ClassMethods - def pipefymessage_options(opts = {}) + module ClassMethods + def pipefymessage_options(opts = {}) options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end end - - def perform_async() - begin - obj = self.new - build_instance_broker.poller do |message| - obj.perform(message) - end - rescue Exception => exception - # TODO: Implement retry - raise exception + + def perform_async + obj = new + build_instance_broker.poller do |message| + obj.perform(message) end + rescue Exception => e + # TODO: Implement retry + raise e end + end - def build_instance_broker() - map = {"aws" => "PipefyMessage::Providers::AwsBroker"} - require_relative "providers/#{self.broker}_broker" - - map[self.broker].constantize.new(self.queue) - end + def build_instance_broker + map = { "aws" => "PipefyMessage::Providers::AwsBroker" } + require_relative "providers/#{broker}_broker" + + map[broker].constantize.new(queue) end end end diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index d4d689b..68ca585 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -5,11 +5,20 @@ RSpec.describe PipefyMessage::Providers::AwsBroker do context "#AwsBroker" do before do - ENV["AWS_CLI_STUB_RESPONSE"] = "true" + stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end - describe "should raise Errors" do it "QueueNonExistError" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "my_queue" }) + .and_raise( + Aws::SQS::Errors::NonExistentQueue.new( + double(Aws::SQS::Client), + "The specified queue my_queue does not exist for this wsdl version" + ) + ) + expect do PipefyMessage::Providers::AwsBroker.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 5f2b9b5..b496bf2 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Worker do - +RSpec.describe PipefyMessage::Worker do $result =~ nil class MockBroker < PipefyMessage::Providers::Broker @@ -9,12 +8,13 @@ def poller yield("test") end end + class MockBrokerFail < PipefyMessage::Providers::Broker def poller raise PipefyMessage::Providers::Errors::ResourceError end end - + class TestWorker include PipefyMessage::Worker pipefymessage_options broker: "aws", queue: "pipefy-local-queue" @@ -28,14 +28,14 @@ def perform(message) describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - + TestWorker.perform_async expect($result).to eq "test" end it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + expect { TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end @@ -44,6 +44,4 @@ def perform(message) expect(TestWorker.broker).to eq "aws" end end - - end From 20b03f44ae59e6552293b5a697b7d5f92848a418 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 13:44:16 -0300 Subject: [PATCH 017/108] feat: adjust options --- lib/pipefy_message.rb | 5 ++--- lib/pipefy_message/providers/aws_broker.rb | 11 +++++------ lib/pipefy_message/worker.rb | 6 +++--- spec/worker_spec.rb | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 99112f3..1467621 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -11,11 +11,10 @@ require "logger" module PipefyMessage - def self.default_worker_options @default_worker_options ||= { - "broker" => "gcp", - "queue" => "http://localhost:4566" + "broker" => "aws", + "queue_name" => "my_queue" } end diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index c6230dc..07bdc4e 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,12 +4,11 @@ module PipefyMessage module Providers class AwsBroker < Broker - def initialize(queue_name) - @config = build_options + def initialize(queue_name, opts={}) + @config = build_options(opts) Aws.config.update(@config) - @sqs = Aws::SQS::Client.new - # require 'pry'; binding.pry + queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url) @@ -28,8 +27,8 @@ def default_options } end - def build_options - default_options.merge({}) + def build_options(opts) + default_options.merge(opts) end def poller diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index b03011b..f2c4706 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -12,8 +12,8 @@ def self.included(base) # ClassMethods module ClassMethods def pipefymessage_options(opts = {}) - options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) - options_hash.each do |k, v| + @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) + @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end @@ -34,7 +34,7 @@ def build_instance_broker map = { "aws" => "PipefyMessage::Providers::AwsBroker" } require_relative "providers/#{broker}_broker" - map[broker].constantize.new(queue) + map[broker].constantize.new(queue_name, @options_hash) end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index b496bf2..c6dce66 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -17,7 +17,7 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue: "pipefy-local-queue" + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message From 6a8716f0730b57751cdd9539f86731c60650322f Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:14:41 -0300 Subject: [PATCH 018/108] feat: add test to poller success --- lib/pipefy_message/providers/aws_broker.rb | 1 + spec/providers/aws_broker_spec.rb | 45 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 07bdc4e..6f17cab 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -6,6 +6,7 @@ module Providers class AwsBroker < Broker def initialize(queue_name, opts={}) @config = build_options(opts) + # require 'pry'; binding.pry Aws.config.update(@config) @sqs = Aws::SQS::Client.new diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index 68ca585..7667c0a 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -7,6 +7,36 @@ before do stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end + + describe "#poller" do + mocked_poller = nil + + before do + mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", + receipt_handle: "2312dasdas1231221312321adsads", + body: "{\"Message\": {\"foo\": \"bar\"}}" } + mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", + { skip_delete: true }) + mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } + + mocked_element = Aws::SQS::Types::Message.new(mocked_message) + mocked_list = Aws::Xml::DefaultList.new + mocked_list.append(mocked_element) + mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) + end + it "should consume message" do + worker = PipefyMessage::Providers::AwsBroker.new("my_queue") + worker.instance_variable_set(:@poller, mocked_poller) + + result = nil + expected_result = { "Message" => { "foo" => "bar" } } + worker.poller do |message| + result = message + end + expect(result).to eq expected_result + end + end + describe "should raise Errors" do it "QueueNonExistError" do allow_any_instance_of(Aws::SQS::Client) @@ -24,6 +54,21 @@ end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, /The specified queue my_queue does not exist for this wsdl version/) end + it "NetworkingError" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "my_queue" }) + .and_raise( + Seahorse::Client::NetworkingError.new( + Errno::ECONNREFUSED.new(""), + "Failed to open TCP connection" + ) + ) + + expect do + PipefyMessage::Providers::AwsBroker.new("my_queue") + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + end end end end From fc0ec26b91d49ebfb28f33e4514461f7aeba4860 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:35:29 -0300 Subject: [PATCH 019/108] refact: options to isolate arguments --- lib/pipefy_message/providers/aws_broker.rb | 37 ++++++++++++++++++---- lib/pipefy_message/providers/broker.rb | 18 +++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 6f17cab..690a201 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,10 +4,11 @@ module PipefyMessage module Providers class AwsBroker < Broker - def initialize(queue_name, opts={}) + attr_reader :config + + def initialize(queue_name, opts = {}) @config = build_options(opts) - # require 'pry'; binding.pry - Aws.config.update(@config) + Aws.config.update(@config[:aws]) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @@ -24,21 +25,45 @@ def default_options secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), + wait_time_seconds: 10 } end def build_options(opts) - default_options.merge(opts) + hash = default_options.merge(opts) + aws_hash = isolate_broker_arguments(hash) + + config_hash = { + aws: aws_hash + } + + hash.each do |k, v| + config_hash[k] = v unless aws_hash.key?(k) + end + + config_hash end def poller ## Aws poller - @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| payload = JSON.parse(received_message.body) yield(payload) end end + + private + + def isolate_broker_arguments(hash) + { + access_key_id: hash[:access_key_id], + secret_access_key: hash[:secret_access_key], + endpoint: hash[:endpoint], + region: hash[:region], + stub_responses: hash[:stub_responses] + } + end end end end diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index de3a1aa..dd79392 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -1,9 +1,13 @@ module PipefyMessage - module Providers - class Broker - def poller() - raise NotImplementedError - end - end + module Providers + class Broker + def poller + raise NotImplementedError + end + + def default_options + {} + end end -end \ No newline at end of file + end +end From 3de01cfd4b398ca3f7218b2c099b41ee7a08efcb Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:58:05 -0300 Subject: [PATCH 020/108] refact: build_instance_broker --- README.md | 17 ++++++++++++++--- lib/pipefy_message/worker.rb | 29 ++++++++++++++++------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8d9c823..c74b0db 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,22 @@ On the irb console: * Consume a message ```ruby - require 'pipefy_message' - message = PipefyMessage::Test.new - message.consume + require "pipefy_message" + + class TestWorker + include PipefyMessage::Worker + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + + def perform(message) + puts message + end + end + + TestWorker.perform_async ``` + + * Publish and Consume a message ```ruby require 'pipefy_message' diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index f2c4706..42b775c 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -19,22 +19,25 @@ def pipefymessage_options(opts = {}) end end + def build_instance_broker + map = { "aws" => "PipefyMessage::Providers::AwsBroker" } + require_relative "providers/#{broker}_broker" + + map[broker].constantize.new(queue_name, @options_hash) + end + def perform_async - obj = new - build_instance_broker.poller do |message| - obj.perform(message) + begin + obj = new + puts "await for messages..." + build_instance_broker.poller do |message| + obj.perform(message) + end + rescue Exception => e + # TODO: Implement retry + raise e end - rescue Exception => e - # TODO: Implement retry - raise e end end - - def build_instance_broker - map = { "aws" => "PipefyMessage::Providers::AwsBroker" } - require_relative "providers/#{broker}_broker" - - map[broker].constantize.new(queue_name, @options_hash) - end end end From 2e3ad1fe59132d3fc82278fefb18df785a7fc48e Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 15:14:40 -0300 Subject: [PATCH 021/108] refact: perform_asyn to process_message --- .vscode/settings.json | 3 +++ lib/pipefy_message/worker.rb | 2 +- spec/worker_spec.rb | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..622799f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rufo.useBundler": false, +} \ No newline at end of file diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 42b775c..cfd04d4 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -26,7 +26,7 @@ def build_instance_broker map[broker].constantize.new(queue_name, @options_hash) end - def perform_async + def process_message begin obj = new puts "await for messages..." diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index c6dce66..0820b68 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -26,16 +26,16 @@ def perform(message) end describe "#perform" do - it "should call #perform from child instance when call #perform_async with success" do + it "should call #perform from child instance when call #process_message with success" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - TestWorker.perform_async + TestWorker.process_message expect($result).to eq "test" end - it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do + it "should call #perform from child instance when call #process_message with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect { TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end From 45296e56a5cc2fce5692e43b4d9f272bb82c280e Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 18 Apr 2022 14:35:11 -0300 Subject: [PATCH 022/108] Ignore log folder --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 80ff942..68201e1 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ build-iPhoneSimulator/ .idea .rspec_status + +/log/ \ No newline at end of file From ba704e92375c2f5b542bc4f04e6bb1897d954686 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 18 Apr 2022 15:19:18 -0300 Subject: [PATCH 023/108] Add "spec" file for the logger module Really just manual tests at this point, but I figured I should have some --- spec/logger_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spec/logger_spec.rb diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb new file mode 100644 index 0000000..5db81ed --- /dev/null +++ b/spec/logger_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class LoggerTester + include Logging +end + +# Tbh, I dunno how to make these "real" tests; this is essentially +# a huge makefile for automating manually checked tests atm :P But +# it's better than nothing nor now. +RSpec.describe LoggerTester do + it "is available as an instance method" do + subject.logger.error("Instance logger error") + end + + it "is available as a class method" do + described_class.logger.error("Class logger error") + end +end From a3c4b654982636ff87279533f46028ae0a988da9 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 18 Apr 2022 15:48:36 -0300 Subject: [PATCH 024/108] Outline Logging module (shared logger object provider) --- lib/pipefy_message/logging.rb | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/pipefy_message/logging.rb diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb new file mode 100644 index 0000000..ded71a2 --- /dev/null +++ b/lib/pipefy_message/logging.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Provides a shared logger setup to all classes and instances of classes +# that require logging, when included. +# +# For instance: if class Test includes Logging, and test is an instance +# of Test (eg: test = Test.new), then both Test.logger and test.logger +# should provide a working logger with the same configurations. +# +# (see https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes) +module Logging + # Destination where logs should be saved. + def self.logfile + $stdout + end + + # Creates a logger object if it has not yet been instantiated, + # or returns the existing object. + def self.logger + @logger ||= Logger.new(logfile) + end + + # Logger method available to all instances of classes + # that include the Logging module (as an instance method). + def logger + Logging.logger + end + + # See module ClassLogger and method ClassLogger::logger. This method + # is required to include the logger method in ClassLogger as a class + # (rather than an instance) method. + def self.included(base) + base.extend(ClassLogger) + end + + # Encapsulates methods and attributes to be made available to classes rather than objects. + module ClassLogger + # Logger method available to all *classes* that include + # the Logging module (as a static/class method). + def logger + Logging.logger + end + end +end From 65b32c6320b14bafa2b0d4a1edbd5d7cbb3299f7 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 18 Apr 2022 17:37:45 -0300 Subject: [PATCH 025/108] Format JSON output, improve static method inclusion, require logging module --- lib/pipefy_message/logging.rb | 79 +++++++++++++++----------- lib/pipefy_message/providers/broker.rb | 2 + lib/pipefy_message/worker.rb | 8 ++- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index ded71a2..4b701e6 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -1,44 +1,57 @@ # frozen_string_literal: true -# Provides a shared logger setup to all classes and instances of classes -# that require logging, when included. -# -# For instance: if class Test includes Logging, and test is an instance -# of Test (eg: test = Test.new), then both Test.logger and test.logger -# should provide a working logger with the same configurations. -# -# (see https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes) -module Logging - # Destination where logs should be saved. - def self.logfile - $stdout - end +require "logger" +require "json" - # Creates a logger object if it has not yet been instantiated, - # or returns the existing object. - def self.logger - @logger ||= Logger.new(logfile) - end +module PipefyMessage + # Provides a shared logger setup to all classes and instances of classes + # that require logging, when included. + # + # For instance: if class Test includes Logging, and test is an instance + # of Test (eg: test = Test.new), then both Test.logger and test.logger + # should provide a working logger with the same configurations. + # + # (see https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes) + module Logging + # Destination where logs should be saved. + def self.logfile + $stdout + end - # Logger method available to all instances of classes - # that include the Logging module (as an instance method). - def logger - Logging.logger - end + # Creates a logger object if it has not yet been instantiated, + # or returns the existing object. + def self.logger + @logger ||= logger_setup + end - # See module ClassLogger and method ClassLogger::logger. This method - # is required to include the logger method in ClassLogger as a class - # (rather than an instance) method. - def self.included(base) - base.extend(ClassLogger) - end + # Configuration for a logger created by the Ruby logger gem. + def self.logger_setup + logger = Logger.new(logfile) + + logger.formatter = proc do |severity, datetime, progname, msg| + JSON.dump(date: "#{formatted_timestamp(datetime)}", severity:"#{severity}", message: msg) + "\n" + end - # Encapsulates methods and attributes to be made available to classes rather than objects. - module ClassLogger - # Logger method available to all *classes* that include - # the Logging module (as a static/class method). + logger + end + + # Allows for custom datetime formatting. Return the datetime + # parameter to use the default. + def self.formatted_timestamp(datetime) + # datetime.strftime("%Y-%m-%d %H:%M:%S") + datetime + end + + # Logger method available to all instances of classes + # that include the Logging module (as an instance method). def logger Logging.logger end + + # Includes module attributes and methods as *class*/static (rather + # than just instance) attributes and methods. + def self.included(base) + base.extend(self) + end end end diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index dd79392..78be181 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -1,6 +1,8 @@ module PipefyMessage module Providers class Broker + include PipefyMessage::Logging + def poller raise NotImplementedError end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index cfd04d4..8be3d84 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true require "singleton" +require_relative "logging" # why is this necessary? module PipefyMessage - # ClassMethods module Worker + include PipefyMessage::Logging + def self.included(base) + base.extend(self) base.extend(ClassMethods) end - - # ClassMethods module ClassMethods def pipefymessage_options(opts = {}) + logger.info("Setting worker options") @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } From 14b84c652f189a9ffd151d8b96e3bceb9fd0c6cd Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 18 Apr 2022 17:41:22 -0300 Subject: [PATCH 026/108] Refactor to include Logging as part of PipefyMessage --- lib/pipefy_message.rb | 1 + lib/pipefy_message/worker.rb | 3 +-- spec/logger_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 1467621..0d79bcb 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -8,6 +8,7 @@ require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" require_relative "pipefy_message/providers/errors" +require_relative "pipefy_message/logging" # shared logger config require "logger" module PipefyMessage diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 8be3d84..0089810 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -8,12 +8,11 @@ module Worker include PipefyMessage::Logging def self.included(base) - base.extend(self) + base.extend(self) # to include the logger as a static method base.extend(ClassMethods) end module ClassMethods def pipefymessage_options(opts = {}) - logger.info("Setting worker options") @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb index 5db81ed..2539ca5 100644 --- a/spec/logger_spec.rb +++ b/spec/logger_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class LoggerTester - include Logging + include PipefyMessage::Logging end # Tbh, I dunno how to make these "real" tests; this is essentially @@ -13,6 +13,6 @@ class LoggerTester end it "is available as a class method" do - described_class.logger.error("Class logger error") + described_class.logger.info("Class logger info") end end From dd2394e43d5e8f4052cabc77251fb65cac121a04 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 19 Apr 2022 11:18:19 -0300 Subject: [PATCH 027/108] Include calling obj information on JSON output --- lib/pipefy_message/logging.rb | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 4b701e6..d84e20c 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -28,9 +28,9 @@ def self.logger def self.logger_setup logger = Logger.new(logfile) - logger.formatter = proc do |severity, datetime, progname, msg| - JSON.dump(date: "#{formatted_timestamp(datetime)}", severity:"#{severity}", message: msg) + "\n" - end + # logger.formatter = proc do |severity, datetime, progname, msg| + # JSON.dump(date: "#{formatted_timestamp(datetime)}", severity:"#{severity}", message: msg) + "\n" + # end logger end @@ -42,9 +42,29 @@ def self.formatted_timestamp(datetime) datetime end + # Formats logger output as a JSON object, including information on + # the calling object. Should not be called directly; this method is + # called implicitly whenever a logger method is called. + def self.json_output(obj, severity, datetime, progname, msg) + timestamp = formatted_timestamp(datetime) + + {:date => "#{timestamp}", + :severity => "#{severity}", + :calling_obj => "#{obj}", + :calling_obj_class => "#{obj.class}", + :message => msg} + end + # Logger method available to all instances of classes # that include the Logging module (as an instance method). + # Includes information on the calling object. def logger + Logging.logger.formatter = proc do |severity, datetime, progname, msg| + json_hash = Logging.json_output(self, severity, datetime, progname, msg) + + JSON.dump(json_hash) + "\n" + end + Logging.logger end From 4deebe17bcfa3786184598ff0f350a52a58c07b8 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 19 Apr 2022 16:23:09 -0300 Subject: [PATCH 028/108] Add preliminary comments and logs to consumer implementation --- lib/pipefy_message.rb | 2 + lib/pipefy_message/logging.rb | 16 ++---- lib/pipefy_message/providers/aws_broker.rb | 30 +++++++++- lib/pipefy_message/providers/broker.rb | 4 +- lib/pipefy_message/worker.rb | 64 ++++++++++++++++++++-- 5 files changed, 97 insertions(+), 19 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 0d79bcb..f411634 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -10,6 +10,8 @@ require_relative "pipefy_message/providers/errors" require_relative "pipefy_message/logging" # shared logger config require "logger" +require "json" +require "benchmark" module PipefyMessage def self.default_worker_options diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index d84e20c..4cce86a 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "logger" -require "json" +# require "json" module PipefyMessage # Provides a shared logger setup to all classes and instances of classes @@ -26,17 +26,11 @@ def self.logger # Configuration for a logger created by the Ruby logger gem. def self.logger_setup - logger = Logger.new(logfile) - - # logger.formatter = proc do |severity, datetime, progname, msg| - # JSON.dump(date: "#{formatted_timestamp(datetime)}", severity:"#{severity}", message: msg) + "\n" - # end - - logger + Logger.new(logfile) end # Allows for custom datetime formatting. Return the datetime - # parameter to use the default. + # parameter (as is) to use the default. def self.formatted_timestamp(datetime) # datetime.strftime("%Y-%m-%d %H:%M:%S") datetime @@ -44,7 +38,7 @@ def self.formatted_timestamp(datetime) # Formats logger output as a JSON object, including information on # the calling object. Should not be called directly; this method is - # called implicitly whenever a logger method is called. + # called implicitly whenever a logger method is called. def self.json_output(obj, severity, datetime, progname, msg) timestamp = formatted_timestamp(datetime) @@ -68,7 +62,7 @@ def logger Logging.logger end - # Includes module attributes and methods as *class*/static (rather + # Includes module attributes and methods as class/static (rather # than just instance) attributes and methods. def self.included(base) base.extend(self) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 690a201..37cfc1a 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -3,22 +3,37 @@ module PipefyMessage module Providers + # AWS SQS client. class AwsBroker < Broker attr_reader :config def initialize(queue_name, opts = {}) @config = build_options(opts) Aws.config.update(@config[:aws]) + + logger.debug(JSON.dump({ + :options_set => @config, + :message_text => "AWS connection opened with options_set" + })) + @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url + + logger.debug(JSON.dump({ + :sqs_queue_url => queue_url, + :message_text => "AWS SQS url resolved to #{queue_url}" + })) + @poller = Aws::SQS::QueuePoller.new(queue_url) - @wait_time_seconds = 10 + @wait_time_seconds = 10 # shouldn't we use the config for this? rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end + # Hash with default options to be used in AWS access configuration + # if no overriding parameters are provided. def default_options { access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), @@ -30,6 +45,8 @@ def default_options } end + # Merges default options (returned by default_options) with the + # hash provided as an argument. The latter takes precedence. def build_options(opts) hash = default_options.merge(opts) aws_hash = isolate_broker_arguments(hash) @@ -45,9 +62,17 @@ def build_options(opts) config_hash end + # Initiates SQS queue polling, with wait_time_seconds as given in + # the initial configuration. def poller - ## Aws poller + logger.debug(JSON.dump({ + :message_text => "Initiating SQS polling..." + })) + @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| + logger.debug(JSON.dump({ + :message_text => "Message received by SQS poller" + })) payload = JSON.parse(received_message.body) yield(payload) end @@ -55,6 +80,7 @@ def poller private + # Options hash parser. def isolate_broker_arguments(hash) { access_key_id: hash[:access_key_id], diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index 78be181..1f39817 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -1,10 +1,12 @@ module PipefyMessage module Providers + # Provides a provider-agnostic, higher level abstraction for + # objects that provide pollers. Should be included in classes implemented for specific providers. Used by the Worker module. class Broker include PipefyMessage::Logging def poller - raise NotImplementedError + raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" end def default_options diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 0089810..caee2b1 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,38 +1,92 @@ # frozen_string_literal: true require "singleton" -require_relative "logging" # why is this necessary? +require_relative "logging" +# why is this necessary? It's been included in pipefy_message.rb module PipefyMessage + # Provides a provider-agnostic, higher level abstraction for + # dealing with queue polling and message parsing. + # Should be included by classes hat implement a perform method, for + # processing received messages. module Worker include PipefyMessage::Logging def self.included(base) - base.extend(self) # to include the logger as a static method - base.extend(ClassMethods) + base.extend(self) # to make the logger available as a static method + base.extend(ClassMethods) # see ClassMethods; this is what makes + # those methods available to the base class end + + # To be defined by classes that include this module. Processes + # messages received by the poller. Called by process_message from + # an instance of the including class. + def perform(message) + raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + end + + # Encapsulates methods to be included as class/static + # (rather than instance) attributes and methods. module ClassMethods + # Merges default worker options with the hash passed as + # an argument. The latter takes precedence. def pipefymessage_options(opts = {}) @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end + + logger.debug(JSON.dump({ + :options_set => @options_hash, + :message_text => "Set #{self.name} options to options_set" + })) end + # Initializes and returns an instance of a broker for + # the provider specified in the class options. def build_instance_broker map = { "aws" => "PipefyMessage::Providers::AwsBroker" } require_relative "providers/#{broker}_broker" + logger.info(JSON.dump({ + :broker => broker, + :message_text => "Initializing and returning instance of #{broker} broker" + })) + map[broker].constantize.new(queue_name, @options_hash) end + # Instantiates a broker object (see build_instance_broker method), + # polls the queue given in the options and forwards received + # messages for processing by a newly created instance of the class + # from which the call to process_message was made (see perform + # method in the parent module). def process_message begin obj = new - puts "await for messages..." + + logger.info(JSON.dump({ + :broker => broker, + :message_text => "Calling poller for #{broker} object" + })) + build_instance_broker.poller do |message| - obj.perform(message) + logger.info(JSON.dump({ + :broker => broker, + :message_text => "Message received by #{broker} poller to be processed by worker", + :received_message => message # necessary? TMI? + })) + + elapsed_time = Benchmark.realtime{ + # what would the best measurement be? + obj.perform(message) + } + + logger.info(JSON.dump({ + :duration_seconds => elapsed_time, + :message_text => "Message received by #{broker} poller processed by #{self.name} worker in #{elapsed_time} seconds" + })) end rescue Exception => e # TODO: Implement retry From 715bfcbe8171aac9c5bfcc220a8a83f2b61ab1ec Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 19 Apr 2022 16:26:31 -0300 Subject: [PATCH 029/108] Rephrase log message --- lib/pipefy_message/providers/aws_broker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 37cfc1a..be76a4f 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -21,8 +21,9 @@ def initialize(queue_name, opts = {}) queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url logger.debug(JSON.dump({ + :sqs_queue_name => queue_name, :sqs_queue_url => queue_url, - :message_text => "AWS SQS url resolved to #{queue_url}" + :message_text => "AWS SQS queue #{queue_name} URL found" })) @poller = Aws::SQS::QueuePoller.new(queue_url) From 6a597094bf02c22918f93a1d890af07b926eddc8 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 13:30:29 -0300 Subject: [PATCH 030/108] feat: add worker class to manager the active job --- lib/pipefy_message/worker.rb | 11 +++++++++++ spec/worker_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 lib/pipefy_message/worker.rb create mode 100644 spec/worker_spec.rb diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb new file mode 100644 index 0000000..6e01b87 --- /dev/null +++ b/lib/pipefy_message/worker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module PipefyMessage + module Worker + # ClassMethods + module ClassMethods #ActiveJob + def perform(body, options = {}); end + def pipefymessages_options(opts = {}); end + end + end +end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb new file mode 100644 index 0000000..36a6ea1 --- /dev/null +++ b/spec/worker_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.describe PipefyMessage::Worker do + $result =~ nil + + class TestWorker + include PipefyMessage::Worker + # pipefymessage_options broker: "aws", queue: "default", delay: 1, retry: 1 + + def perform(body) + $result = body + end + end + + describe "#perform" do + it "allow call .perform" do + worker = TestWorker.new + worker.perform("test") + expect($result).to eq "test" + end + end +end From 4cb8c1c8e29863673487655c22f0109b51e9bad4 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:40:42 -0300 Subject: [PATCH 031/108] feat: add libraries to gemfile --- Gemfile | 1 + Gemfile.lock | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Gemfile b/Gemfile index ca002f8..0c745a5 100644 --- a/Gemfile +++ b/Gemfile @@ -16,4 +16,5 @@ group :development, :test do gem "pry-doc" gem "rspec", "~> 3.0" gem "rubocop", "~> 1.21" + gem "solargraph" end diff --git a/Gemfile.lock b/Gemfile.lock index 3df6d36..0bce941 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,15 +29,27 @@ GEM aws-sigv4 (~> 1.1) aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) + backport (1.2.0) + benchmark (0.2.0) coderay (1.1.3) concurrent-ruby (1.1.9) diff-lcs (1.3) + e2mmap (0.1.0) i18n (1.10.0) concurrent-ruby (~> 1.0) + jaro_winkler (1.5.4) jmespath (1.5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) logger (1.3.0) method_source (1.0.0) minitest (5.15.0) + nokogiri (1.13.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.13.3-x86_64-linux) + racc (~> 1.4) parallel (1.21.0) parser (3.0.2.0) ast (~> 2.4.1) @@ -47,9 +59,12 @@ GEM pry-doc (1.3.0) pry (~> 0.11) yard (~> 0.9.11) + racc (1.6.0) rainbow (3.0.0) rake (13.0.6) regexp_parser (2.1.1) + reverse_markdown (2.1.1) + nokogiri rexml (3.2.5) rspec (3.8.0) rspec-core (~> 3.8.0) @@ -76,7 +91,23 @@ GEM rubocop-ast (1.12.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) + solargraph (0.41.2) + backport (~> 1.1) + benchmark + bundler (>= 1.17.2) + e2mmap + jaro_winkler (~> 1.5) + kramdown (~> 2.3) + kramdown-parser-gfm (~> 1.1) + parser (~> 3.0) + reverse_markdown (>= 1.0.5, < 3) + rubocop (>= 0.52) + thor (~> 1.0) + tilt (~> 2.0) + yard (~> 0.9, >= 0.9.24) + thor (1.2.1) thread_safe (0.3.6) + tilt (2.0.10) tzinfo (1.2.9) thread_safe (~> 0.1) unicode-display_width (2.1.0) @@ -99,6 +130,7 @@ DEPENDENCIES rake (~> 13.0) rspec (~> 3.0) rubocop (~> 1.21) + solargraph BUNDLED WITH 2.3.9 From 913f990a72faf211cebb7c74c891d2ac1325b7ad Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:41:04 -0300 Subject: [PATCH 032/108] feat: add config to rubocop --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index e93bd8f..4a34c43 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,6 @@ AllCops: TargetRubyVersion: 2.6 + NewCops: enable Style/StringLiterals: Enabled: true From df2b7cfe133c6a667de4ad5aabe0f52d5e99844f Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 14:41:21 -0300 Subject: [PATCH 033/108] feat: add worker to boot app --- lib/pipefy_message.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 091c722..6e9ed8a 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -6,6 +6,7 @@ require_relative "pipefy_message/base_consumer" require_relative "pipefy_message/base_publisher" require_relative "pipefy_message/logger" +require_relative "pipefy_message/worker" require "logger" module PipefyMessage From e5ff577cc2d9b2052d0b4cc10c387edcc93547e3 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 17:45:52 -0300 Subject: [PATCH 034/108] feat: add default worker options --- lib/pipefy_message.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 6e9ed8a..cfa5135 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -10,6 +10,14 @@ require "logger" module PipefyMessage + + def self.default_worker_options + @default_worker_options ||= { + "broker" => "gcp", + "queue" => "default" + } + end + # Simple Test class to validate the project class Test def initialize From b40d85f5f91621696c69fa3b91cb67dbed3e8bb5 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 11 Apr 2022 17:47:16 -0300 Subject: [PATCH 035/108] feat: add self.included to add attr in runtime --- lib/pipefy_message/worker.rb | 16 +++++++++++++--- spec/worker_spec.rb | 9 ++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 6e01b87..5071153 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,11 +1,21 @@ # frozen_string_literal: true module PipefyMessage + # ClassMethods module Worker + def self.included(base) + base.extend(ClassMethods) + end + # ClassMethods - module ClassMethods #ActiveJob - def perform(body, options = {}); end - def pipefymessages_options(opts = {}); end + module ClassMethods + def pipefymessage_options(opts = {}) + options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) + options_hash.each do |k, v| + singleton_class.class_eval { attr_accessor k } + send("#{k}=", v) + end + end end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 36a6ea1..c98e3ce 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -5,7 +5,7 @@ class TestWorker include PipefyMessage::Worker - # pipefymessage_options broker: "aws", queue: "default", delay: 1, retry: 1 + pipefymessage_options broker: "aws" def perform(body) $result = body @@ -19,4 +19,11 @@ def perform(body) expect($result).to eq "test" end end + + describe "#options class" do + it "should set options in class" do + TestWorker.new + expect(TestWorker.broker).to eq "aws" + end + end end From cb790c30269d48924e2be9554f2320c37f5344f4 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 15:29:59 -0300 Subject: [PATCH 036/108] feat: add .env.local to dev enviroment --- .env.local | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .env.local diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..c0b9490 --- /dev/null +++ b/.env.local @@ -0,0 +1,4 @@ + export AWS_ACCESS_KEY_ID=foo + export AWS_SECRET_ACCESS_KEY=bar + export AWS_ENDPOINT="http://localhost:4566" + export ENABLE_AWS_CLIENT_CONFIG=true From f2cf8a01387f868bc4599265f41a7444aa4f09ef Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:56:54 -0300 Subject: [PATCH 037/108] refact: url of queue Co-authored-by: Duda Ferreira --- lib/pipefy_message.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index cfa5135..e161624 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -7,6 +7,7 @@ require_relative "pipefy_message/base_publisher" require_relative "pipefy_message/logger" require_relative "pipefy_message/worker" +require_relative "pipefy_message/providers/broker" require "logger" module PipefyMessage @@ -14,7 +15,7 @@ module PipefyMessage def self.default_worker_options @default_worker_options ||= { "broker" => "gcp", - "queue" => "default" + "queue" => "http://localhost:4566" } end From 7c82f845dcc90d707f8da8db224a111a342a6088 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:57:42 -0300 Subject: [PATCH 038/108] feat: implement a perform_async method --- lib/pipefy_message/worker.rb | 23 ++++++++++++++++++++--- spec/worker_spec.rb | 21 +++++++++++++++------ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 5071153..e0eb55a 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,21 +1,38 @@ # frozen_string_literal: true +require 'singleton' + module PipefyMessage # ClassMethods module Worker def self.included(base) base.extend(ClassMethods) end - + # ClassMethods - module ClassMethods - def pipefymessage_options(opts = {}) + module ClassMethods + def pipefymessage_options(opts = {}) options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end end + + def perform_async() + instance_broker = build_instance_broker + obj = self.new + instance_broker.poller do |message| + obj.perform(message) + end + end + + def build_instance_broker() + map = {"aws" => "PipefyMessage::Providers::AwsBroker"} + require_relative "providers/#{self.broker}_broker" + + map[self.broker].constantize.new(self.queue) + end end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index c98e3ce..38992a5 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -2,28 +2,37 @@ RSpec.describe PipefyMessage::Worker do $result =~ nil + + # class MockBroker < Broker + # def poller + # yield("test") + # end + # end class TestWorker include PipefyMessage::Worker pipefymessage_options broker: "aws" - def perform(body) - $result = body + def perform(message) + $result = message end end describe "#perform" do - it "allow call .perform" do - worker = TestWorker.new - worker.perform("test") + + #mock PipefyMessage::Providers::AwsBroker -> MockBroker + + it "allow call .perform from instance worker when call perform_async from ClassMethod" do + TestWorker.perform_async expect($result).to eq "test" end end describe "#options class" do it "should set options in class" do - TestWorker.new expect(TestWorker.broker).to eq "aws" end end + + end From 1045cf4edec05b2572cf9c624267c113e871eb12 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 12 Apr 2022 17:58:11 -0300 Subject: [PATCH 039/108] feat: create a parent broker and impl aws_broker --- lib/pipefy_message/providers/aws_broker.rb | 21 +++++++++++++++++++++ lib/pipefy_message/providers/broker.rb | 9 +++++++++ 2 files changed, 30 insertions(+) create mode 100644 lib/pipefy_message/providers/aws_broker.rb create mode 100644 lib/pipefy_message/providers/broker.rb diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb new file mode 100644 index 0000000..066a120 --- /dev/null +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -0,0 +1,21 @@ +require "aws-sdk-sqs" +require "json" + +module PipefyMessage + module Providers + class AwsBroker < Broker + def initialize(queue_url) + @poller = Aws::SQS::QueuePoller.new(queue_url) + @wait_time_seconds = 10 + end + + def poller() + ## Aws poller + @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + payload = JSON.parse(received_message.body) + yield(payload) + end + end + end + end +end \ No newline at end of file diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb new file mode 100644 index 0000000..de3a1aa --- /dev/null +++ b/lib/pipefy_message/providers/broker.rb @@ -0,0 +1,9 @@ +module PipefyMessage + module Providers + class Broker + def poller() + raise NotImplementedError + end + end + end +end \ No newline at end of file From 8155d8c00860ab3d6c37ad064f2ef836b011ea4e Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Wed, 13 Apr 2022 14:28:46 -0300 Subject: [PATCH 040/108] test: implement fail to broker Co-authored-by: Duda Ferreira --- lib/pipefy_message/worker.rb | 16 ++++++++++------ spec/worker_spec.rb | 27 ++++++++++++++++++--------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index e0eb55a..0a5f44b 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -19,12 +19,16 @@ def pipefymessage_options(opts = {}) end end - def perform_async() - instance_broker = build_instance_broker - obj = self.new - instance_broker.poller do |message| - obj.perform(message) - end + def perform_async() + begin + obj = self.new + build_instance_broker.poller do |message| + obj.perform(message) + end + rescue Exception => exception + # TODO: Implement retry + raise exception + end end def build_instance_broker() diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 38992a5..46be7f1 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -3,11 +3,16 @@ RSpec.describe PipefyMessage::Worker do $result =~ nil - # class MockBroker < Broker - # def poller - # yield("test") - # end - # end + class MockBroker < PipefyMessage::Providers::Broker + def poller + yield("test") + end + end + class MockBrokerFail < PipefyMessage::Providers::Broker + def poller + raise Exception.new "This is an exception" + end + end class TestWorker include PipefyMessage::Worker @@ -19,13 +24,17 @@ def perform(message) end describe "#perform" do - - #mock PipefyMessage::Providers::AwsBroker -> MockBroker - - it "allow call .perform from instance worker when call perform_async from ClassMethod" do + it "should call #perform from child instance when call #perform_async with success" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + TestWorker.perform_async expect($result).to eq "test" end + + it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) + expect{ TestWorker.perform_async }.to raise_error + end end describe "#options class" do From c7e2dd6aceb1d34e95c2da3e38388713bafc9d2f Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Wed, 13 Apr 2022 15:59:15 -0300 Subject: [PATCH 041/108] feat: add config to broker --- lib/pipefy_message/providers/aws_broker.rb | 23 ++++++++++++++++++++++ spec/worker_spec.rb | 7 ++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 066a120..89924f7 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,11 +4,34 @@ module PipefyMessage module Providers class AwsBroker < Broker + + @@default_options = { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || "true") + } + def initialize(queue_url) + + @config = AwsBroker.config_options + + Aws.config.update(@config) + + + @sqs = Aws::SQS::Client.new(region: @config[:region]) + require "pry"; binding.pry + @poller = Aws::SQS::QueuePoller.new(queue_url) + @poller.before_request { stop! if user_interrupt == true } @wait_time_seconds = 10 end + def self.config_options + @@default_options + end + def poller() ## Aws poller @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 46be7f1..0472e50 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -16,16 +16,17 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws" + pipefymessage_options broker: "aws", queue: "http://localhost:4566/000000000000/pipefy-local-queue" def perform(message) + puts message $result = message end end describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + # allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) TestWorker.perform_async expect($result).to eq "test" @@ -33,7 +34,7 @@ def perform(message) it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error + expect{ TestWorker.perform_async }.to raise_error(Exception, /This is an exception/) end end From 109a8f321725ae7b109738e65661334582732429 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 12:56:23 -0300 Subject: [PATCH 042/108] feat: add resourceError --- lib/pipefy_message.rb | 1 + lib/pipefy_message/providers/errors.rb | 11 +++++++++++ spec/worker_spec.rb | 13 +++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 lib/pipefy_message/providers/errors.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index e161624..d91fef0 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -8,6 +8,7 @@ require_relative "pipefy_message/logger" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" +require_relative "pipefy_message/providers/errors" require "logger" module PipefyMessage diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb new file mode 100644 index 0000000..623d182 --- /dev/null +++ b/lib/pipefy_message/providers/errors.rb @@ -0,0 +1,11 @@ +module PipefyMessage + module Providers + module Errors + class ResourceError < StandardError + def initialize(msg="ResourceError") + super + end + end + end + end +end \ No newline at end of file diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 0472e50..5f2b9b5 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Worker do +RSpec.describe PipefyMessage::Worker do + $result =~ nil class MockBroker < PipefyMessage::Providers::Broker @@ -10,13 +11,13 @@ def poller end class MockBrokerFail < PipefyMessage::Providers::Broker def poller - raise Exception.new "This is an exception" + raise PipefyMessage::Providers::Errors::ResourceError end end class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue: "http://localhost:4566/000000000000/pipefy-local-queue" + pipefymessage_options broker: "aws", queue: "pipefy-local-queue" def perform(message) puts message @@ -26,15 +27,15 @@ def perform(message) describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do - # allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) TestWorker.perform_async expect($result).to eq "test" end - it "should call #perform from child instance when call #perform_async with fail(raise a exception)" do + it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error(Exception, /This is an exception/) + expect{ TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end From c5ba5455a0841309f14a8e39579f9e102a6e864e Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 12:56:57 -0300 Subject: [PATCH 043/108] fix: stub problem and configure poller --- lib/pipefy_message/providers/aws_broker.rb | 31 +++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 89924f7..c2abccd 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -9,23 +9,24 @@ class AwsBroker < Broker access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || "true") + region: (ENV["AWS_REGION"] || "us-east-1") } - def initialize(queue_url) - - @config = AwsBroker.config_options - - Aws.config.update(@config) - - - @sqs = Aws::SQS::Client.new(region: @config[:region]) - require "pry"; binding.pry + def initialize(queue_name) + + begin + @config = AwsBroker.config_options + Aws.config.update(@config) + + @sqs = Aws::SQS::Client.new + queue_url = @sqs.get_queue_url({queue_name: queue_name}).queue_url + @poller = Aws::SQS::QueuePoller.new(queue_url) + + @wait_time_seconds = 10 + rescue (Aws::SQS::Errors::NonExistentQueue) => exception + raise PipefyMessage::Providers::Errors::ResourceError(exception.message) + end - @poller = Aws::SQS::QueuePoller.new(queue_url) - @poller.before_request { stop! if user_interrupt == true } - @wait_time_seconds = 10 end def self.config_options @@ -35,7 +36,7 @@ def self.config_options def poller() ## Aws poller @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| - payload = JSON.parse(received_message.body) + payload = JSON.parse(received_message.body) yield(payload) end end From 0b1db3f0838131ac88c1f88c3d8d4ef73243f2b5 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Thu, 14 Apr 2022 13:33:00 -0300 Subject: [PATCH 044/108] feat: a test to aws_broker --- lib/pipefy_message/providers/aws_broker.rb | 65 +++++++++++----------- spec/providers/aws_broker_spec.rb | 20 +++++++ 2 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 spec/providers/aws_broker_spec.rb diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index c2abccd..46ad36c 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -2,44 +2,41 @@ require "json" module PipefyMessage - module Providers - class AwsBroker < Broker + module Providers + class AwsBroker < Broker + @@default_options = { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) + } - @@default_options = { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1") - } + def initialize(queue_name) + @config = AwsBroker.config_options + Aws.config.update(@config) - def initialize(queue_name) + @sqs = Aws::SQS::Client.new + queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url + @poller = Aws::SQS::QueuePoller.new(queue_url) - begin - @config = AwsBroker.config_options - Aws.config.update(@config) - - @sqs = Aws::SQS::Client.new - queue_url = @sqs.get_queue_url({queue_name: queue_name}).queue_url - @poller = Aws::SQS::QueuePoller.new(queue_url) - - @wait_time_seconds = 10 - rescue (Aws::SQS::Errors::NonExistentQueue) => exception - raise PipefyMessage::Providers::Errors::ResourceError(exception.message) - end + @wait_time_seconds = 10 + super + rescue (Aws::SQS::Errors::NonExistentQueue) => e + raise PipefyMessage::Providers::Errors::ResourceError, e.message + end - end + def self.config_options + @@default_options + end - def self.config_options - @@default_options - end - - def poller() - ## Aws poller - @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| - payload = JSON.parse(received_message.body) - yield(payload) - end - end + def poller + ## Aws poller + @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + payload = JSON.parse(received_message.body) + yield(payload) end + end end -end \ No newline at end of file + end +end diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb new file mode 100644 index 0000000..d4d689b --- /dev/null +++ b/spec/providers/aws_broker_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require_relative "../../lib/pipefy_message/providers/aws_broker" + +RSpec.describe PipefyMessage::Providers::AwsBroker do + context "#AwsBroker" do + before do + ENV["AWS_CLI_STUB_RESPONSE"] = "true" + end + + describe "should raise Errors" do + it "QueueNonExistError" do + expect do + PipefyMessage::Providers::AwsBroker.new("my_queue") + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, + /The specified queue my_queue does not exist for this wsdl version/) + end + end + end +end From 4d7dd5078fd8f310592f75a4e18218f0297f9b7a Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 13:34:35 -0300 Subject: [PATCH 045/108] feat: adjust erros to aws_broker --- .rubocop.yml | 6 +++- lib/pipefy_message/providers/aws_broker.rb | 28 ++++++++-------- lib/pipefy_message/providers/errors.rb | 17 +++++----- lib/pipefy_message/worker.rb | 38 ++++++++++------------ spec/providers/aws_broker_spec.rb | 13 ++++++-- spec/worker_spec.rb | 12 +++---- 6 files changed, 63 insertions(+), 51 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 4a34c43..5a741f2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,4 +14,8 @@ Layout/LineLength: Max: 120 Metrics/BlockLength: - IgnoredMethods: ['describe', 'context'] \ No newline at end of file + IgnoredMethods: ["describe", "context"] + +Lint/MissingSuper: + Exclude: + - "/**/*" diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 46ad36c..c6230dc 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,30 +4,32 @@ module PipefyMessage module Providers class AwsBroker < Broker - @@default_options = { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) - } - def initialize(queue_name) - @config = AwsBroker.config_options + @config = build_options Aws.config.update(@config) @sqs = Aws::SQS::Client.new + # require 'pry'; binding.pry queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url) @wait_time_seconds = 10 - super - rescue (Aws::SQS::Errors::NonExistentQueue) => e + rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end - def self.config_options - @@default_options + def default_options + { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") + } + end + + def build_options + default_options.merge({}) end def poller diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 623d182..7efaf90 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -1,11 +1,12 @@ +# frozen_string_literal: true module PipefyMessage - module Providers - module Errors - class ResourceError < StandardError - def initialize(msg="ResourceError") - super - end - end + module Providers + module Errors + class ResourceError < RuntimeError + def initialize(msg = "ResourceError") + super end + end end -end \ No newline at end of file + end +end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 0a5f44b..b03011b 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'singleton' +require "singleton" module PipefyMessage # ClassMethods @@ -8,35 +8,33 @@ module Worker def self.included(base) base.extend(ClassMethods) end - + # ClassMethods - module ClassMethods - def pipefymessage_options(opts = {}) + module ClassMethods + def pipefymessage_options(opts = {}) options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end end - - def perform_async() - begin - obj = self.new - build_instance_broker.poller do |message| - obj.perform(message) - end - rescue Exception => exception - # TODO: Implement retry - raise exception + + def perform_async + obj = new + build_instance_broker.poller do |message| + obj.perform(message) end + rescue Exception => e + # TODO: Implement retry + raise e end + end - def build_instance_broker() - map = {"aws" => "PipefyMessage::Providers::AwsBroker"} - require_relative "providers/#{self.broker}_broker" - - map[self.broker].constantize.new(self.queue) - end + def build_instance_broker + map = { "aws" => "PipefyMessage::Providers::AwsBroker" } + require_relative "providers/#{broker}_broker" + + map[broker].constantize.new(queue) end end end diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index d4d689b..68ca585 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -5,11 +5,20 @@ RSpec.describe PipefyMessage::Providers::AwsBroker do context "#AwsBroker" do before do - ENV["AWS_CLI_STUB_RESPONSE"] = "true" + stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end - describe "should raise Errors" do it "QueueNonExistError" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "my_queue" }) + .and_raise( + Aws::SQS::Errors::NonExistentQueue.new( + double(Aws::SQS::Client), + "The specified queue my_queue does not exist for this wsdl version" + ) + ) + expect do PipefyMessage::Providers::AwsBroker.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 5f2b9b5..b496bf2 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Worker do - +RSpec.describe PipefyMessage::Worker do $result =~ nil class MockBroker < PipefyMessage::Providers::Broker @@ -9,12 +8,13 @@ def poller yield("test") end end + class MockBrokerFail < PipefyMessage::Providers::Broker def poller raise PipefyMessage::Providers::Errors::ResourceError end end - + class TestWorker include PipefyMessage::Worker pipefymessage_options broker: "aws", queue: "pipefy-local-queue" @@ -28,14 +28,14 @@ def perform(message) describe "#perform" do it "should call #perform from child instance when call #perform_async with success" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - + TestWorker.perform_async expect($result).to eq "test" end it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect{ TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + expect { TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end @@ -44,6 +44,4 @@ def perform(message) expect(TestWorker.broker).to eq "aws" end end - - end From 374fa3e2f632ae929aac7d54a95d64bc46ec102a Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 13:44:16 -0300 Subject: [PATCH 046/108] feat: adjust options --- lib/pipefy_message.rb | 5 ++--- lib/pipefy_message/providers/aws_broker.rb | 11 +++++------ lib/pipefy_message/worker.rb | 6 +++--- spec/worker_spec.rb | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index d91fef0..01d6b0f 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -12,11 +12,10 @@ require "logger" module PipefyMessage - def self.default_worker_options @default_worker_options ||= { - "broker" => "gcp", - "queue" => "http://localhost:4566" + "broker" => "aws", + "queue_name" => "my_queue" } end diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index c6230dc..07bdc4e 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,12 +4,11 @@ module PipefyMessage module Providers class AwsBroker < Broker - def initialize(queue_name) - @config = build_options + def initialize(queue_name, opts={}) + @config = build_options(opts) Aws.config.update(@config) - @sqs = Aws::SQS::Client.new - # require 'pry'; binding.pry + queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url) @@ -28,8 +27,8 @@ def default_options } end - def build_options - default_options.merge({}) + def build_options(opts) + default_options.merge(opts) end def poller diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index b03011b..f2c4706 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -12,8 +12,8 @@ def self.included(base) # ClassMethods module ClassMethods def pipefymessage_options(opts = {}) - options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) - options_hash.each do |k, v| + @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) + @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end @@ -34,7 +34,7 @@ def build_instance_broker map = { "aws" => "PipefyMessage::Providers::AwsBroker" } require_relative "providers/#{broker}_broker" - map[broker].constantize.new(queue) + map[broker].constantize.new(queue_name, @options_hash) end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index b496bf2..c6dce66 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -17,7 +17,7 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue: "pipefy-local-queue" + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message From 7f68344dc3147d4e21a09069f5142cacc42f041e Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:14:41 -0300 Subject: [PATCH 047/108] feat: add test to poller success --- lib/pipefy_message/providers/aws_broker.rb | 1 + spec/providers/aws_broker_spec.rb | 45 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 07bdc4e..6f17cab 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -6,6 +6,7 @@ module Providers class AwsBroker < Broker def initialize(queue_name, opts={}) @config = build_options(opts) + # require 'pry'; binding.pry Aws.config.update(@config) @sqs = Aws::SQS::Client.new diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index 68ca585..7667c0a 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -7,6 +7,36 @@ before do stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end + + describe "#poller" do + mocked_poller = nil + + before do + mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", + receipt_handle: "2312dasdas1231221312321adsads", + body: "{\"Message\": {\"foo\": \"bar\"}}" } + mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", + { skip_delete: true }) + mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } + + mocked_element = Aws::SQS::Types::Message.new(mocked_message) + mocked_list = Aws::Xml::DefaultList.new + mocked_list.append(mocked_element) + mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) + end + it "should consume message" do + worker = PipefyMessage::Providers::AwsBroker.new("my_queue") + worker.instance_variable_set(:@poller, mocked_poller) + + result = nil + expected_result = { "Message" => { "foo" => "bar" } } + worker.poller do |message| + result = message + end + expect(result).to eq expected_result + end + end + describe "should raise Errors" do it "QueueNonExistError" do allow_any_instance_of(Aws::SQS::Client) @@ -24,6 +54,21 @@ end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, /The specified queue my_queue does not exist for this wsdl version/) end + it "NetworkingError" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "my_queue" }) + .and_raise( + Seahorse::Client::NetworkingError.new( + Errno::ECONNREFUSED.new(""), + "Failed to open TCP connection" + ) + ) + + expect do + PipefyMessage::Providers::AwsBroker.new("my_queue") + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + end end end end From 40b85abb9e1b1cd8b8b921a224259886ff6348c9 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:35:29 -0300 Subject: [PATCH 048/108] refact: options to isolate arguments --- lib/pipefy_message/providers/aws_broker.rb | 37 ++++++++++++++++++---- lib/pipefy_message/providers/broker.rb | 18 +++++++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 6f17cab..690a201 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -4,10 +4,11 @@ module PipefyMessage module Providers class AwsBroker < Broker - def initialize(queue_name, opts={}) + attr_reader :config + + def initialize(queue_name, opts = {}) @config = build_options(opts) - # require 'pry'; binding.pry - Aws.config.update(@config) + Aws.config.update(@config[:aws]) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @@ -24,21 +25,45 @@ def default_options secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), + wait_time_seconds: 10 } end def build_options(opts) - default_options.merge(opts) + hash = default_options.merge(opts) + aws_hash = isolate_broker_arguments(hash) + + config_hash = { + aws: aws_hash + } + + hash.each do |k, v| + config_hash[k] = v unless aws_hash.key?(k) + end + + config_hash end def poller ## Aws poller - @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| + @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| payload = JSON.parse(received_message.body) yield(payload) end end + + private + + def isolate_broker_arguments(hash) + { + access_key_id: hash[:access_key_id], + secret_access_key: hash[:secret_access_key], + endpoint: hash[:endpoint], + region: hash[:region], + stub_responses: hash[:stub_responses] + } + end end end end diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index de3a1aa..dd79392 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -1,9 +1,13 @@ module PipefyMessage - module Providers - class Broker - def poller() - raise NotImplementedError - end - end + module Providers + class Broker + def poller + raise NotImplementedError + end + + def default_options + {} + end end -end \ No newline at end of file + end +end From eeac6a2c8ef142103cb3258096ea8f5957cb3078 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 14:58:05 -0300 Subject: [PATCH 049/108] refact: build_instance_broker --- README.md | 17 ++++++++++++++--- lib/pipefy_message/worker.rb | 29 ++++++++++++++++------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8d9c823..c74b0db 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,22 @@ On the irb console: * Consume a message ```ruby - require 'pipefy_message' - message = PipefyMessage::Test.new - message.consume + require "pipefy_message" + + class TestWorker + include PipefyMessage::Worker + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + + def perform(message) + puts message + end + end + + TestWorker.perform_async ``` + + * Publish and Consume a message ```ruby require 'pipefy_message' diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index f2c4706..42b775c 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -19,22 +19,25 @@ def pipefymessage_options(opts = {}) end end + def build_instance_broker + map = { "aws" => "PipefyMessage::Providers::AwsBroker" } + require_relative "providers/#{broker}_broker" + + map[broker].constantize.new(queue_name, @options_hash) + end + def perform_async - obj = new - build_instance_broker.poller do |message| - obj.perform(message) + begin + obj = new + puts "await for messages..." + build_instance_broker.poller do |message| + obj.perform(message) + end + rescue Exception => e + # TODO: Implement retry + raise e end - rescue Exception => e - # TODO: Implement retry - raise e end end - - def build_instance_broker - map = { "aws" => "PipefyMessage::Providers::AwsBroker" } - require_relative "providers/#{broker}_broker" - - map[broker].constantize.new(queue_name, @options_hash) - end end end From dc6f246150cf593082d009ade6ddbab6110334cd Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Fri, 15 Apr 2022 15:14:40 -0300 Subject: [PATCH 050/108] refact: perform_asyn to process_message --- .vscode/settings.json | 3 +++ lib/pipefy_message/worker.rb | 2 +- spec/worker_spec.rb | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..622799f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rufo.useBundler": false, +} \ No newline at end of file diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 42b775c..cfd04d4 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -26,7 +26,7 @@ def build_instance_broker map[broker].constantize.new(queue_name, @options_hash) end - def perform_async + def process_message begin obj = new puts "await for messages..." diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index c6dce66..0820b68 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -26,16 +26,16 @@ def perform(message) end describe "#perform" do - it "should call #perform from child instance when call #perform_async with success" do + it "should call #perform from child instance when call #process_message with success" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - TestWorker.perform_async + TestWorker.process_message expect($result).to eq "test" end - it "should call #perform from child instance when call #perform_async with fail(raise a ResourceError)" do + it "should call #perform from child instance when call #process_message with fail(raise a ResourceError)" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect { TestWorker.perform_async }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end From 0e2385f9b143573e20aa6fc1bc900ca009d35ea2 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 19 Apr 2022 17:26:58 -0300 Subject: [PATCH 051/108] Merge CustomLogger class config into Logging module --- lib/pipefy_message/logging.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 4cce86a..e50f2d6 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -26,7 +26,16 @@ def self.logger # Configuration for a logger created by the Ruby logger gem. def self.logger_setup - Logger.new(logfile) + logger = Logger.new(logfile) + + if ENV.fetch("ASYNC_ENABLE_NON_ERROR_LOGS", "true") + logger.info! + else + logger.error! + end + + logger + end # Allows for custom datetime formatting. Return the datetime @@ -40,10 +49,12 @@ def self.formatted_timestamp(datetime) # the calling object. Should not be called directly; this method is # called implicitly whenever a logger method is called. def self.json_output(obj, severity, datetime, progname, msg) - timestamp = formatted_timestamp(datetime) + formatted_date = formatted_timestamp(datetime) - {:date => "#{timestamp}", - :severity => "#{severity}", + {:timestamp => "#{formatted_date}", + :level => "#{severity}", + :app => "#{progname}", + :contex => "async_processing", :calling_obj => "#{obj}", :calling_obj_class => "#{obj.class}", :message => msg} @@ -56,7 +67,7 @@ def logger Logging.logger.formatter = proc do |severity, datetime, progname, msg| json_hash = Logging.json_output(self, severity, datetime, progname, msg) - JSON.dump(json_hash) + "\n" + JSON.dump(json_hash) + ($INPUT_RECORD_SEPARATOR || "\n") end Logging.logger From 37f84c6cdb3dbf3d97a8faf4776963ecba8ba6a6 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 19 Apr 2022 17:28:53 -0300 Subject: [PATCH 052/108] refact: remove beta consumer version --- lib/pipefy_message.rb | 39 +------- lib/pipefy_message/base_consumer.rb | 15 --- lib/pipefy_message/broker/aws/sqs/consumer.rb | 39 -------- lib/pipefy_message/logging.rb | 15 +-- lib/pipefy_message/providers/aws_broker.rb | 32 +++--- lib/pipefy_message/worker.rb | 98 +++++++++++-------- spec/base_consumer_spec.rb | 33 ------- 7 files changed, 82 insertions(+), 189 deletions(-) delete mode 100644 lib/pipefy_message/base_consumer.rb delete mode 100644 lib/pipefy_message/broker/aws/sqs/consumer.rb delete mode 100644 spec/base_consumer_spec.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 2bda5b1..a5f9b9e 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -3,45 +3,14 @@ require_relative "pipefy_message/version" require_relative "pipefy_message/broker/aws/configuration" require_relative "pipefy_message/broker/aws/sns/publisher" -require_relative "pipefy_message/base_consumer" require_relative "pipefy_message/base_publisher" +require_relative "pipefy_message/logging" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" require_relative "pipefy_message/providers/errors" -require_relative "pipefy_message/logging" # shared logger config -require "logger" -require "json" -require "benchmark" +## +# PipefyMessage abstraction async process +## module PipefyMessage - def self.default_worker_options - @default_worker_options ||= { - "broker" => "aws", - "queue_name" => "my_queue" - } - end - - # Simple Test class to validate the project - class Test - def initialize - @log = PipefyMessage::CustomLogger.new - end - - def publish - payload = { foo: "bar" } - puts Publisher::BasePublisher.new.publish(payload, "pipefy-local-topic") - end - - def consume - @log.info("Starting the consumer process") - consumer = BaseConsumer.new("http://localhost:4566/000000000000/pipefy-local-queue") - @log.info("Creating new instance of consumer #{consumer}") - consumer.consume_message - end - - def publish_and_consume - publish - consume - end - end end diff --git a/lib/pipefy_message/base_consumer.rb b/lib/pipefy_message/base_consumer.rb deleted file mode 100644 index 264be41..0000000 --- a/lib/pipefy_message/base_consumer.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "aws-sdk-sqs" -require_relative "broker/aws/sqs/consumer" - -module PipefyMessage - # Base Consumer class provide by this gem, to be used for the external consumers to consume messages from a broker - class BaseConsumer < Consumer::AwsProvider::SqsConsumer - def process_message(message) - @log.info("Processing Message") - @log.info("Message body: #{message}") - message - end - end -end diff --git a/lib/pipefy_message/broker/aws/sqs/consumer.rb b/lib/pipefy_message/broker/aws/sqs/consumer.rb deleted file mode 100644 index c575537..0000000 --- a/lib/pipefy_message/broker/aws/sqs/consumer.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require "aws-sdk-sqs" -require "json" -require "logger" - -module PipefyMessage - module Consumer - module AwsProvider - # Aws SQS Consumer class to consume json messages from a specific queue - class SqsConsumer - def initialize(queue_url) - PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance.setup_connection - @poller = Aws::SQS::QueuePoller.new(queue_url) - @wait_time_seconds = ENV["AWS_SQS_CONSUME_WAIT_TIME_SEC"] || 10 - @log = PipefyMessage::CustomLogger.new - end - - def consume_message - @poller.poll(wait_time_seconds: @wait_time_seconds) do |received_message| - @log.info("Receiving Message from Broker") - @log.info("Message ID: #{received_message.message_id}") - payload = JSON.parse(received_message.body) - - process_message(payload["Message"]) - - rescue StandardError - @log.error("Failed to process message with ID: #{received_message.message_id}") - throw :skip_delete - end - end - - def process_message(_message) - raise "Must be implemented by a worker class" - end - end - end - end -end diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 4cce86a..008726f 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "logger" +require "json" # require "json" module PipefyMessage @@ -39,14 +40,14 @@ def self.formatted_timestamp(datetime) # Formats logger output as a JSON object, including information on # the calling object. Should not be called directly; this method is # called implicitly whenever a logger method is called. - def self.json_output(obj, severity, datetime, progname, msg) + def self.json_output(obj, severity, datetime, _progname, msg) timestamp = formatted_timestamp(datetime) - {:date => "#{timestamp}", - :severity => "#{severity}", - :calling_obj => "#{obj}", - :calling_obj_class => "#{obj.class}", - :message => msg} + { date: timestamp.to_s, + severity: severity.to_s, + calling_obj: obj.to_s, + calling_obj_class: obj.class.to_s, + message: msg } end # Logger method available to all instances of classes @@ -58,7 +59,7 @@ def logger JSON.dump(json_hash) + "\n" end - + Logging.logger end diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index be76a4f..5461e09 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -10,29 +10,22 @@ class AwsBroker < Broker def initialize(queue_name, opts = {}) @config = build_options(opts) Aws.config.update(@config[:aws]) - - logger.debug(JSON.dump({ - :options_set => @config, - :message_text => "AWS connection opened with options_set" - })) + logger.debug({ + options_set: @config, + message_text: "AWS connection opened with options_set" + }) @sqs = Aws::SQS::Client.new - queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url - logger.debug(JSON.dump({ - :sqs_queue_name => queue_name, - :sqs_queue_url => queue_url, - :message_text => "AWS SQS queue #{queue_name} URL found" - })) - @poller = Aws::SQS::QueuePoller.new(queue_url) - @wait_time_seconds = 10 # shouldn't we use the config for this? + @wait_time_seconds = 10 # shouldn't we use the config for this? rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end + ## # Hash with default options to be used in AWS access configuration # if no overriding parameters are provided. def default_options @@ -46,7 +39,8 @@ def default_options } end - # Merges default options (returned by default_options) with the + ## + # Merges default options (returned by default_options) with the # hash provided as an argument. The latter takes precedence. def build_options(opts) hash = default_options.merge(opts) @@ -63,17 +57,18 @@ def build_options(opts) config_hash end + ## # Initiates SQS queue polling, with wait_time_seconds as given in # the initial configuration. def poller logger.debug(JSON.dump({ - :message_text => "Initiating SQS polling..." - })) + message_text: "Initiating SQS polling..." + })) @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| logger.debug(JSON.dump({ - :message_text => "Message received by SQS poller" - })) + message_text: "Message received by SQS poller" + })) payload = JSON.parse(received_message.body) yield(payload) end @@ -81,6 +76,7 @@ def poller private + ## # Options hash parser. def isolate_broker_arguments(hash) { diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index caee2b1..3f2d9b3 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,97 +1,111 @@ # frozen_string_literal: true require "singleton" -require_relative "logging" -# why is this necessary? It's been included in pipefy_message.rb +require "benchmark" module PipefyMessage + ## # Provides a provider-agnostic, higher level abstraction for # dealing with queue polling and message parsing. # Should be included by classes hat implement a perform method, for # processing received messages. module Worker include PipefyMessage::Logging + ## + # default values to consumer + def self.default_worker_options + @default_worker_options ||= { + "broker" => "aws", + "queue_name" => "my_queue" + } + end + ## + # to make the logger available as a static method + # see ClassMethods; this is what makes + # those methods available to the base class def self.included(base) - base.extend(self) # to make the logger available as a static method - base.extend(ClassMethods) # see ClassMethods; this is what makes - # those methods available to the base class + base.extend(self) + base.extend(ClassMethods) end + ## # To be defined by classes that include this module. Processes # messages received by the poller. Called by process_message from # an instance of the including class. - def perform(message) - raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + def perform(_message) + raise NotImplementedError, + "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" end + ## # Encapsulates methods to be included as class/static # (rather than instance) attributes and methods. module ClassMethods + ## # Merges default worker options with the hash passed as # an argument. The latter takes precedence. def pipefymessage_options(opts = {}) - @options_hash = PipefyMessage.default_worker_options.merge(opts.transform_keys(&:to_s)) + @options_hash = Worker.default_worker_options.merge(opts.transform_keys(&:to_s)) @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end - logger.debug(JSON.dump({ - :options_set => @options_hash, - :message_text => "Set #{self.name} options to options_set" - })) + logger.debug({ + options_set: @options_hash, + message_text: "Set #{name} options to options_set" + }) end + ## # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker map = { "aws" => "PipefyMessage::Providers::AwsBroker" } require_relative "providers/#{broker}_broker" - logger.info(JSON.dump({ - :broker => broker, - :message_text => "Initializing and returning instance of #{broker} broker" - })) + logger.info({ + broker: broker, + message_text: "Initializing and returning instance of #{broker} broker" + }) map[broker].constantize.new(queue_name, @options_hash) end # Instantiates a broker object (see build_instance_broker method), - # polls the queue given in the options and forwards received + # polls the queue given in the options and forwards received # messages for processing by a newly created instance of the class - # from which the call to process_message was made (see perform + # from which the call to process_message was made (see perform # method in the parent module). def process_message - begin - obj = new - - logger.info(JSON.dump({ - :broker => broker, - :message_text => "Calling poller for #{broker} object" - })) + obj = new - build_instance_broker.poller do |message| - logger.info(JSON.dump({ - :broker => broker, - :message_text => "Message received by #{broker} poller to be processed by worker", - :received_message => message # necessary? TMI? - })) + logger.info({ + broker: broker, + message_text: "Calling poller for #{broker} object" + }) - elapsed_time = Benchmark.realtime{ - # what would the best measurement be? - obj.perform(message) - } + build_instance_broker.poller do |message| + logger.info({ + broker: broker, + message_text: "Message received by #{broker} poller to be processed by worker", + received_message: message # necessary? TMI? + }) - logger.info(JSON.dump({ - :duration_seconds => elapsed_time, - :message_text => "Message received by #{broker} poller processed by #{self.name} worker in #{elapsed_time} seconds" - })) + elapsed_time = Benchmark.realtime do + # what would the best measurement be? + obj.perform(message) end - rescue Exception => e - # TODO: Implement retry - raise e + + logger.info({ + duration_seconds: elapsed_time, + message_text: "Message received by #{broker} poller processed by #{name} worker in #{elapsed_time} seconds" + }) end + rescue Exception => e + # TODO: Implement retry + raise e end end end diff --git a/spec/base_consumer_spec.rb b/spec/base_consumer_spec.rb deleted file mode 100644 index 589a7fd..0000000 --- a/spec/base_consumer_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe PipefyMessage::BaseConsumer do - context "when I try to consume a message from SQS broker" do - before do - ENV["AWS_ENDPOINT"] = "http://localhost:4566" - ENV["AWS_ACCESS_KEY_ID"] = "foo" - ENV["AWS_SECRET_ACCESS_KEY"] = "bar" - ENV["AWS_CLI_STUB_RESPONSE"] = "true" - ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" - end - it "should consumer a message properly" do - consumer = PipefyMessage::BaseConsumer.new("http://localhost:4566/000000000000/pipefy-local-queue-test") - mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", - receipt_handle: "2312dasdas1231221312321adsads", - body: "{\"Message\": {\"foo\": \"bar\"}}" } - - mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/pipefy-local-queue-test", - { skip_delete: true }) - mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } - - mocked_element = Aws::SQS::Types::Message.new(mocked_message) - mocked_list = Aws::Xml::DefaultList.new - mocked_list.append(mocked_element) - - mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) - consumer.instance_variable_set(:@poller, mocked_poller) - - result = consumer.consume_message - expect(result.received_message_count).to eq 1 - end - end -end From 8bd71111053504e9c6aea1a962a91391b85f2463 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 19 Apr 2022 17:34:09 -0300 Subject: [PATCH 053/108] Use Logging module in pipefy_message.rb --- lib/pipefy_message.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 2bda5b1..6f93a33 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -23,9 +23,7 @@ def self.default_worker_options # Simple Test class to validate the project class Test - def initialize - @log = PipefyMessage::CustomLogger.new - end + include Logging def publish payload = { foo: "bar" } @@ -33,9 +31,9 @@ def publish end def consume - @log.info("Starting the consumer process") + logger.info("Starting the consumer process") consumer = BaseConsumer.new("http://localhost:4566/000000000000/pipefy-local-queue") - @log.info("Creating new instance of consumer #{consumer}") + logger.info("Creating new instance of consumer #{consumer}") consumer.consume_message end From d2f2c5c9dacf734cfd035ed54a075c6be334b665 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 19 Apr 2022 18:54:49 -0300 Subject: [PATCH 054/108] refact: logs and elapsed time to ensure --- lib/pipefy_message.rb | 6 ++-- lib/pipefy_message/logging.rb | 14 ++++----- lib/pipefy_message/providers/aws_broker.rb | 8 ++---- lib/pipefy_message/worker.rb | 33 ++++++++-------------- spec/providers/aws_broker_spec.rb | 5 ++++ 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 717a6e6..fe43e07 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -4,18 +4,16 @@ require_relative "pipefy_message/broker/aws/configuration" require_relative "pipefy_message/broker/aws/sns/publisher" require_relative "pipefy_message/base_publisher" +require_relative "pipefy_message/logger" require_relative "pipefy_message/logging" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" require_relative "pipefy_message/providers/errors" -require_relative "pipefy_message/logging" # shared logger config + require "logger" require "json" require "benchmark" - - - ## # PipefyMessage abstraction async process ## diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 1bb9fc1..24a6b31 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -27,16 +27,12 @@ def self.logger # Configuration for a logger created by the Ruby logger gem. def self.logger_setup + loglevels = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze logger = Logger.new(logfile) - - if ENV.fetch("ASYNC_ENABLE_NON_ERROR_LOGS", "true") - logger.info! - else - logger.error! - end - + level ||= loglevels.index ENV.fetch("LOG_LEVEL", "INFO") + level ||= Logger::INFO + logger.level = level logger - end # Allows for custom datetime formatting. Return the datetime @@ -49,7 +45,7 @@ def self.formatted_timestamp(datetime) # Formats logger output as a JSON object, including information on # the calling object. Should not be called directly; this method is # called implicitly whenever a logger method is called. - def self.json_output(obj, severity, datetime, _progname, msg) + def self.json_output(obj, severity, datetime, progname, msg) timestamp = formatted_timestamp(datetime) { date: timestamp.to_s, diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index 5461e09..e0190ad 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -10,15 +10,11 @@ class AwsBroker < Broker def initialize(queue_name, opts = {}) @config = build_options(opts) Aws.config.update(@config[:aws]) - logger.debug({ - options_set: @config, - message_text: "AWS connection opened with options_set" - }) + logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url - - @poller = Aws::SQS::QueuePoller.new(queue_url) + @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) @wait_time_seconds = 10 # shouldn't we use the config for this? rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 3f2d9b3..b29bc70 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -73,39 +73,30 @@ def build_instance_broker map[broker].constantize.new(queue_name, @options_hash) end + ## # Instantiates a broker object (see build_instance_broker method), # polls the queue given in the options and forwards received # messages for processing by a newly created instance of the class # from which the call to process_message was made (see perform # method in the parent module). def process_message + start = Time.now obj = new - - logger.info({ - broker: broker, - message_text: "Calling poller for #{broker} object" - }) + logger.info({ message_text: "Calling poller for #{broker} object" }) build_instance_broker.poller do |message| - logger.info({ - broker: broker, - message_text: "Message received by #{broker} poller to be processed by worker", - received_message: message # necessary? TMI? - }) - - elapsed_time = Benchmark.realtime do - # what would the best measurement be? - obj.perform(message) - end - - logger.info({ - duration_seconds: elapsed_time, - message_text: "Message received by #{broker} poller processed by #{name} worker in #{elapsed_time} seconds" - }) + logger.info({ message_text: "Message received by #{broker} poller to be processed by worker", + received_message: message }) + obj.perform(message) end rescue Exception => e - # TODO: Implement retry raise e + ensure + elapsed_time = (Time.now - start) * 1000.0 + logger.info({ + duration_seconds: elapsed_time, + message_text: "Message received by #{broker} poller processed by #{name} worker in #{elapsed_time} seconds" + }) end end end diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index 7667c0a..7026358 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -15,6 +15,10 @@ mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", receipt_handle: "2312dasdas1231221312321adsads", body: "{\"Message\": {\"foo\": \"bar\"}}" } + + Aws.config[:sqs] = { + stub_responses: true + } mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", { skip_delete: true }) mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } @@ -24,6 +28,7 @@ mocked_list.append(mocked_element) mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) end + it "should consume message" do worker = PipefyMessage::Providers::AwsBroker.new("my_queue") worker.instance_variable_set(:@poller, mocked_poller) From 52b4d731d4a38f1c1323bd0219147e2c6751f090 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 19 Apr 2022 19:03:44 -0300 Subject: [PATCH 055/108] refact: remove JSON.parse --- lib/pipefy_message/providers/aws_broker.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb index e0190ad..d5fd0a0 100644 --- a/lib/pipefy_message/providers/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_broker.rb @@ -57,14 +57,10 @@ def build_options(opts) # Initiates SQS queue polling, with wait_time_seconds as given in # the initial configuration. def poller - logger.debug(JSON.dump({ - message_text: "Initiating SQS polling..." - })) + logger.debug({message_text: "Initiating SQS polling..."}) @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| - logger.debug(JSON.dump({ - message_text: "Message received by SQS poller" - })) + logger.debug({ message_text: "Message received by SQS poller" }) payload = JSON.parse(received_message.body) yield(payload) end From 746f385fc7d06b3f4109c52447ef258f86d5f2ec Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 20 Apr 2022 13:29:39 -0300 Subject: [PATCH 056/108] Refactor publisher to incorporate new centralized logger instance --- lib/pipefy_message/broker/aws/sns/publisher.rb | 10 +++++----- lib/pipefy_message/logging.rb | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/pipefy_message/broker/aws/sns/publisher.rb b/lib/pipefy_message/broker/aws/sns/publisher.rb index d8ed02d..255285f 100644 --- a/lib/pipefy_message/broker/aws/sns/publisher.rb +++ b/lib/pipefy_message/broker/aws/sns/publisher.rb @@ -2,7 +2,7 @@ require "aws-sdk-sns" require "json" -require "logger" +require_relative "../../../logging" # why is this necessary? require_relative "../configuration" module PipefyMessage @@ -10,11 +10,11 @@ module Publisher module AwsProvider # Aws SNS Publisher class to publish json messages into a specific topic class SnsPublisher + include Logging def initialize aws_config = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance aws_config.setup_connection @sns = Aws::SNS::Resource.new - @log = PipefyMessage::CustomLogger.new default_arn_prefix = "arn:aws:sns:us-east-1:000000000000:" @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || default_arn_prefix @is_staging = ENV["ASYNC_APP_ENV"] == "staging" @@ -38,12 +38,12 @@ def do_publish(message, topic_name) topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) topic = @sns.topic(topic_arn) - @log.info("Publishing a json message to topic #{topic_arn}") + logger.info("Publishing a json message to topic #{topic_arn}") result = topic.publish({ message: message.to_json, message_structure: " json " }) - @log.info(" Message Published with ID #{result.message_id}") + logger.info(" Message Published with ID #{result.message_id}") result rescue StandardError => e - @log.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") + logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") end end end diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 24a6b31..2226b3e 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -2,7 +2,6 @@ require "logger" require "json" -# require "json" module PipefyMessage # Provides a shared logger setup to all classes and instances of classes From 890bce813d61b7963a9e16c5a69ca57d7b60623b Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 25 Apr 2022 14:03:12 -0300 Subject: [PATCH 057/108] Remove old logger --- lib/pipefy_message.rb | 1 - lib/pipefy_message/logger.rb | 42 ------------------------------------ 2 files changed, 43 deletions(-) delete mode 100644 lib/pipefy_message/logger.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index fe43e07..c6cf21f 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -4,7 +4,6 @@ require_relative "pipefy_message/broker/aws/configuration" require_relative "pipefy_message/broker/aws/sns/publisher" require_relative "pipefy_message/base_publisher" -require_relative "pipefy_message/logger" require_relative "pipefy_message/logging" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" diff --git a/lib/pipefy_message/logger.rb b/lib/pipefy_message/logger.rb deleted file mode 100644 index 2e65f61..0000000 --- a/lib/pipefy_message/logger.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require "English" -require "json" -require "logger" - -module PipefyMessage - # Custom Logger class to centralize and deal with logs needs - class CustomLogger - def initialize - @logger = Logger.new($stdout) - @is_log_enable = ENV.fetch("ASYNC_ENABLE_NON_ERROR_LOGS", "true") == "true" - - @logger.formatter = proc do |severity, datetime, progname, msg| - { level: severity, timestamp: datetime.to_s, app: progname, - context: "async_processing", message: msg }.to_json + $INPUT_RECORD_SEPARATOR - end - end - - def info(message) - return unless @is_log_enable - - @logger.info(message) - end - - def debug(message) - return unless @is_log_enable - - @logger.debug(message) - end - - def warn(message) - return unless @is_log_enable - - @logger.warn(message) - end - - def error(message) - @logger.error(message) - end - end -end From 7f38b093e42dd0a63e2b6921f65071c6a895732d Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 25 Apr 2022 14:39:31 -0300 Subject: [PATCH 058/108] refact: namespaces --- README.md | 6 +- lib/pipefy_message/providers/aws_broker.rb | 84 ------------------ .../providers/aws_client/sqs_broker.rb | 85 +++++++++++++++++++ lib/pipefy_message/worker.rb | 6 +- spec/providers/aws_broker_spec.rb | 12 +-- spec/worker_spec.rb | 4 +- 6 files changed, 99 insertions(+), 98 deletions(-) delete mode 100644 lib/pipefy_message/providers/aws_broker.rb create mode 100644 lib/pipefy_message/providers/aws_client/sqs_broker.rb diff --git a/README.md b/README.md index c74b0db..10c9ca5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ On the irb console: class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + pipefymessage_options broker: "sqs", queue_name: "pipefy-local-queue" def perform(message) puts message @@ -93,7 +93,7 @@ On the irb console: ## Project Stack -- [Aws SDK Ruby - SNS & SQS](https://github.com/aws/aws-sdk-ruby) +- [Aws SDK Ruby - SNS & SQS](https://github.com/aws_client/aws-sdk-ruby) - [Bundler](https://bundler.io/) - Docker-compose - [GitHub Actions](https://docs.github.com/en/actions) @@ -103,7 +103,7 @@ On the irb console: ## Brokers Documentation -* [SNS & SQS User guide](https://github.com/pipefy/pipefy_message/tree/main/lib/pipefy_message/broker/aws/README.md) +* [SNS & SQS User guide](https://github.com/pipefy/pipefy_message/tree/main/lib/pipefy_message/broker/aws_client/README.md) ## Contributing diff --git a/lib/pipefy_message/providers/aws_broker.rb b/lib/pipefy_message/providers/aws_broker.rb deleted file mode 100644 index d5fd0a0..0000000 --- a/lib/pipefy_message/providers/aws_broker.rb +++ /dev/null @@ -1,84 +0,0 @@ -require "aws-sdk-sqs" -require "json" - -module PipefyMessage - module Providers - # AWS SQS client. - class AwsBroker < Broker - attr_reader :config - - def initialize(queue_name, opts = {}) - @config = build_options(opts) - Aws.config.update(@config[:aws]) - logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) - - @sqs = Aws::SQS::Client.new - queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url - @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) - - @wait_time_seconds = 10 # shouldn't we use the config for this? - rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e - raise PipefyMessage::Providers::Errors::ResourceError, e.message - end - - ## - # Hash with default options to be used in AWS access configuration - # if no overriding parameters are provided. - def default_options - { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), - wait_time_seconds: 10 - } - end - - ## - # Merges default options (returned by default_options) with the - # hash provided as an argument. The latter takes precedence. - def build_options(opts) - hash = default_options.merge(opts) - aws_hash = isolate_broker_arguments(hash) - - config_hash = { - aws: aws_hash - } - - hash.each do |k, v| - config_hash[k] = v unless aws_hash.key?(k) - end - - config_hash - end - - ## - # Initiates SQS queue polling, with wait_time_seconds as given in - # the initial configuration. - def poller - logger.debug({message_text: "Initiating SQS polling..."}) - - @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| - logger.debug({ message_text: "Message received by SQS poller" }) - payload = JSON.parse(received_message.body) - yield(payload) - end - end - - private - - ## - # Options hash parser. - def isolate_broker_arguments(hash) - { - access_key_id: hash[:access_key_id], - secret_access_key: hash[:secret_access_key], - endpoint: hash[:endpoint], - region: hash[:region], - stub_responses: hash[:stub_responses] - } - end - end - end -end diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb new file mode 100644 index 0000000..237ec93 --- /dev/null +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -0,0 +1,85 @@ +require "aws-sdk-sqs" +require "json" + +module PipefyMessage + module Providers + module AwsClient + # AWS SQS client. + class SqsBroker < Broker + attr_reader :config + + def initialize(queue_name, opts = {}) + @config = build_options(opts) + Aws.config.update(@config[:aws]) + logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + + @sqs = Aws::SQS::Client.new + queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url + @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) + + rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e + raise PipefyMessage::Providers::Errors::ResourceError, e.message + end + + ## + # Hash with default options to be used in AWS access configuration + # if no overriding parameters are provided. + def default_options + { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), + wait_time_seconds: 10 + } + end + + ## + # Merges default options (returned by default_options) with the + # hash provided as an argument. The latter takes precedence. + def build_options(opts) + hash = default_options.merge(opts) + aws_hash = isolate_broker_arguments(hash) + + config_hash = { + aws: aws_hash + } + + hash.each do |k, v| + config_hash[k] = v unless aws_hash.key?(k) + end + + config_hash + end + + ## + # Initiates SQS queue polling, with wait_time_seconds as given in + # the initial configuration. + def poller + logger.debug({ message_text: "Initiating SQS polling..." }) + + @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| + logger.debug({ message_text: "Message received by SQS poller" }) + payload = JSON.parse(received_message.body) + yield(payload) + end + end + + private + + ## + # Options hash parser. + def isolate_broker_arguments(hash) + { + access_key_id: hash[:access_key_id], + secret_access_key: hash[:secret_access_key], + endpoint: hash[:endpoint], + region: hash[:region], + stub_responses: hash[:stub_responses] + } + end + end + end + end +end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index b29bc70..d5426f2 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -15,7 +15,7 @@ module Worker # default values to consumer def self.default_worker_options @default_worker_options ||= { - "broker" => "aws", + "broker" => "sqs", "queue_name" => "my_queue" } end @@ -62,8 +62,8 @@ def pipefymessage_options(opts = {}) # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker - map = { "aws" => "PipefyMessage::Providers::AwsBroker" } - require_relative "providers/#{broker}_broker" + map = { "sqs" => "PipefyMessage::Providers::AwsClient::SqsBroker" } + require_relative "providers/aws_client/#{broker}_broker" logger.info({ broker: broker, diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws_broker_spec.rb index 7026358..5222b74 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws_broker_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "../../lib/pipefy_message/providers/aws_broker" +require_relative "../../lib/pipefy_message/providers/aws_client/sqs_broker" -RSpec.describe PipefyMessage::Providers::AwsBroker do - context "#AwsBroker" do +RSpec.describe PipefyMessage::Providers::AwsClient::SqsBroker do + context "#SqsBroker" do before do stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end @@ -30,7 +30,7 @@ end it "should consume message" do - worker = PipefyMessage::Providers::AwsBroker.new("my_queue") + worker = PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") worker.instance_variable_set(:@poller, mocked_poller) result = nil @@ -55,7 +55,7 @@ ) expect do - PipefyMessage::Providers::AwsBroker.new("my_queue") + PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, /The specified queue my_queue does not exist for this wsdl version/) end @@ -71,7 +71,7 @@ ) expect do - PipefyMessage::Providers::AwsBroker.new("my_queue") + PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 0820b68..8656a25 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -17,7 +17,7 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + pipefymessage_options broker: "sqs", queue_name: "pipefy-local-queue" def perform(message) puts message @@ -41,7 +41,7 @@ def perform(message) describe "#options class" do it "should set options in class" do - expect(TestWorker.broker).to eq "aws" + expect(TestWorker.broker).to eq "sqs" end end end From 155f288708feb18330844f5cc579c0d74135b820 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 25 Apr 2022 15:31:36 -0300 Subject: [PATCH 059/108] refact: rename publisher class --- lib/pipefy_message.rb | 3 +-- lib/pipefy_message/providers/aws_client/sqs_broker.rb | 3 +-- lib/pipefy_message/{base_publisher.rb => publisher.rb} | 0 spec/providers/{aws_broker_spec.rb => aws/sqs_broker_spec.rb} | 4 +++- spec/{base_producer_spec.rb => publisher_spec.rb} | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename lib/pipefy_message/{base_publisher.rb => publisher.rb} (100%) rename spec/providers/{aws_broker_spec.rb => aws/sqs_broker_spec.rb} (92%) rename spec/{base_producer_spec.rb => publisher_spec.rb} (100%) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index c6cf21f..258cb26 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -2,9 +2,8 @@ require_relative "pipefy_message/version" require_relative "pipefy_message/broker/aws/configuration" -require_relative "pipefy_message/broker/aws/sns/publisher" -require_relative "pipefy_message/base_publisher" require_relative "pipefy_message/logging" +require_relative "pipefy_message/publisher" require_relative "pipefy_message/worker" require_relative "pipefy_message/providers/broker" require_relative "pipefy_message/providers/errors" diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 237ec93..ab8d5f2 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -5,7 +5,7 @@ module PipefyMessage module Providers module AwsClient # AWS SQS client. - class SqsBroker < Broker + class SqsBroker < PipefyMessage::Providers::Broker attr_reader :config def initialize(queue_name, opts = {}) @@ -16,7 +16,6 @@ def initialize(queue_name, opts = {}) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) - rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end diff --git a/lib/pipefy_message/base_publisher.rb b/lib/pipefy_message/publisher.rb similarity index 100% rename from lib/pipefy_message/base_publisher.rb rename to lib/pipefy_message/publisher.rb diff --git a/spec/providers/aws_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb similarity index 92% rename from spec/providers/aws_broker_spec.rb rename to spec/providers/aws/sqs_broker_spec.rb index 5222b74..2947be1 100644 --- a/spec/providers/aws_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -require_relative "../../lib/pipefy_message/providers/aws_client/sqs_broker" +require_relative "../../../lib/pipefy_message/providers/aws_client/sqs_broker" RSpec.describe PipefyMessage::Providers::AwsClient::SqsBroker do context "#SqsBroker" do before do + stub_const("ENV", ENV.to_hash.merge("AWS_ENDPOINT" => "http://localhost:4566")) stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end @@ -16,6 +17,7 @@ receipt_handle: "2312dasdas1231221312321adsads", body: "{\"Message\": {\"foo\": \"bar\"}}" } + Aws.config.update({ endpoint: "http://localhost:4566" }) Aws.config[:sqs] = { stub_responses: true } diff --git a/spec/base_producer_spec.rb b/spec/publisher_spec.rb similarity index 100% rename from spec/base_producer_spec.rb rename to spec/publisher_spec.rb From a7a32b5e97540c1286895f0d138cf47e6a8c24b1 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 25 Apr 2022 17:07:15 -0300 Subject: [PATCH 060/108] refact: move base_publisher to publisher Co-authored-by: Duda Ferreira --- lib/pipefy_message.rb | 14 +++ .../broker/aws/sns/publisher.rb | 51 ---------- .../providers/aws_client/aws_broker.rb | 8 ++ .../providers/aws_client/sns_broker.rb | 95 +++++++++++++++++++ lib/pipefy_message/providers/broker.rb | 4 + lib/pipefy_message/publisher.rb | 41 ++++---- lib/pipefy_message/worker.rb | 6 +- spec/providers/aws/sns_broker_spec.rb | 23 +++++ spec/publisher_spec.rb | 24 ++--- spec/sns_publisher_spec.rb | 32 ------- 10 files changed, 180 insertions(+), 118 deletions(-) delete mode 100644 lib/pipefy_message/broker/aws/sns/publisher.rb create mode 100644 lib/pipefy_message/providers/aws_client/aws_broker.rb create mode 100644 lib/pipefy_message/providers/aws_client/sns_broker.rb create mode 100644 spec/providers/aws/sns_broker_spec.rb delete mode 100644 spec/sns_publisher_spec.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 258cb26..1329a41 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -16,4 +16,18 @@ # PipefyMessage abstraction async process ## module PipefyMessage + def self.class_path + { + aws: { + publisher: { + class_name: "PipefyMessage::Providers::AwsClient::SnsBroker", + relative_path: "providers/aws_client/sns_broker" + }, + consumer: { + class_name: "PipefyMessage::Providers::AwsClient::SqsBroker", + relative_path: "providers/aws_client/sqs_broker" + } + } + } + end end diff --git a/lib/pipefy_message/broker/aws/sns/publisher.rb b/lib/pipefy_message/broker/aws/sns/publisher.rb deleted file mode 100644 index 255285f..0000000 --- a/lib/pipefy_message/broker/aws/sns/publisher.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require "aws-sdk-sns" -require "json" -require_relative "../../../logging" # why is this necessary? -require_relative "../configuration" - -module PipefyMessage - module Publisher - module AwsProvider - # Aws SNS Publisher class to publish json messages into a specific topic - class SnsPublisher - include Logging - def initialize - aws_config = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance - aws_config.setup_connection - @sns = Aws::SNS::Resource.new - default_arn_prefix = "arn:aws:sns:us-east-1:000000000000:" - @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || default_arn_prefix - @is_staging = ENV["ASYNC_APP_ENV"] == "staging" - end - - def publish(payload, topic_name) - message = prepare_payload(payload) - do_publish(message, topic_name) - end - - private - - def prepare_payload(payload) - # The 'Default' json key/entry it's mandatory to ruby sdk - { - "default" => payload - } - end - - def do_publish(message, topic_name) - topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) - topic = @sns.topic(topic_arn) - - logger.info("Publishing a json message to topic #{topic_arn}") - result = topic.publish({ message: message.to_json, message_structure: " json " }) - logger.info(" Message Published with ID #{result.message_id}") - result - rescue StandardError => e - logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") - end - end - end - end -end diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb new file mode 100644 index 0000000..b09951a --- /dev/null +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -0,0 +1,8 @@ +module PipefyMessage + module Providers + module AwsClient + class BuilderAwsConfig + end + end + end +end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb new file mode 100644 index 0000000..c40177b --- /dev/null +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -0,0 +1,95 @@ +require "aws-sdk-sqs" +require "json" + +module PipefyMessage + module Providers + module AwsClient + # AWS Sns client. + class SnsBroker < PipefyMessage::Providers::Broker + attr_reader :config + + def initialize(opts = {}) + @config = build_options(opts) + Aws.config.update(@config[:aws]) + logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + + @sns = Aws::SNS::Resource.new + @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] + @is_staging = ENV["ASYNC_APP_ENV"] == "staging" + rescue StandardError => e + raise PipefyMessage::Providers::Errors::ResourceError, e.message + end + + ## + # Hash with default options to be used in AWS access configuration + # if no overriding parameters are provided. + def default_options + { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), + wait_time_seconds: 10, + default_arn_prefix: "arn:aws:sns:us-east-1:000000000000:" + } + end + + ## + # Merges default options (returned by default_options) with the + # hash provided as an argument. The latter takes precedence. + def build_options(opts) + hash = default_options.merge(opts) + aws_hash = isolate_broker_arguments(hash) + + config_hash = { + aws: aws_hash + } + + hash.each do |k, v| + config_hash[k] = v unless aws_hash.key?(k) + end + + config_hash + end + + def publish(payload, topic_name) + begin + message = prepare_payload(payload) + topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) + topic = @sns.topic(topic_arn) + + logger.info("Publishing a json message to topic #{topic_arn}") + result = topic.publish({ message: message.to_json, message_structure: " json " }) + logger.info(" Message Published with ID #{result.message_id}") + result + rescue StandardError => e + logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") + end + + end + + private + + def prepare_payload(payload) + # The 'Default' json key/entry it's mandatory to ruby sdk + { + "default" => payload + } + end + + ## + # Options hash parser. + def isolate_broker_arguments(hash) + { + access_key_id: hash[:access_key_id], + secret_access_key: hash[:secret_access_key], + endpoint: hash[:endpoint], + region: hash[:region], + stub_responses: hash[:stub_responses] + } + end + end + end + end +end diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index 1f39817..cc0d5d0 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -9,6 +9,10 @@ def poller raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" end + def publish(payload, topic_name) + raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + end + def default_options {} end diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index f6ffa99..fbea163 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -1,31 +1,30 @@ # frozen_string_literal: true -require "active_support" -require "active_support/core_ext/string/inflections" -require_relative "broker/aws/sns/publisher" - module PipefyMessage - module Publisher - # Base Publisher provided by this gem, to be used for the external publishers to send messages to a broker - class BasePublisher - def publish(message, topic) - publisher_instance.publish(message, topic) - end + # Base Publisher provided by this gem, to be used for the external publishers to send messages to a broker + class Publisher + include PipefyMessage::Logging + + def initialize(broker = "aws") + @broker = broker + end + + def publish(message, topic) + publisher_instance.publish(message, topic) + end - private + private - def publisher_implementation - ENV["PUBLISHER_IMPL"] || "SnsPublisher" - end + def publisher_instance + map = PipefyMessage.class_path[@broker] + require_relative map[:publisher][:relative_path] - def resolve_publisher(publisher_name) - publishers_module = Hash["SnsPublisher" => "AwsProvider::SnsPublisher"] - "PipefyMessage::Publisher::#{publishers_module[publisher_name].to_s.camelize}".constantize - end + logger.info({ + broker: @broker, + message_text: "Initializing and returning instance of #{@broker} broker" + }) - def publisher_instance - resolve_publisher(publisher_implementation).new - end + map[:publisher][:class_name].constantize.new end end end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index d5426f2..ae55171 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -62,15 +62,15 @@ def pipefymessage_options(opts = {}) # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker - map = { "sqs" => "PipefyMessage::Providers::AwsClient::SqsBroker" } - require_relative "providers/aws_client/#{broker}_broker" + map = PipefyMessage.class_path[broker] + require_relative map[:consumer][:relative_path] logger.info({ broker: broker, message_text: "Initializing and returning instance of #{broker} broker" }) - map[broker].constantize.new(queue_name, @options_hash) + map[:consumer][:class_name].constantize.new(queue_name, @options_hash) end ## diff --git a/spec/providers/aws/sns_broker_spec.rb b/spec/providers/aws/sns_broker_spec.rb new file mode 100644 index 0000000..b9aba65 --- /dev/null +++ b/spec/providers/aws/sns_broker_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative "../../../lib/pipefy_message/providers/aws_client/sns_broker" + +RSpec.describe PipefyMessage::Providers::AwsClient::SnsBroker do + context "when I try to publish a message with Sns publisher" do + before(:each) do + stub_const("ENV", ENV.to_hash.merge("AWS_ENDPOINT" => "http://localhost:4566")) + stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) + end + it "should return a message ID" do + @publisher = PipefyMessage::Providers::AwsClient::SnsBroker.new + mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", + sequence_number: nil } + + allow(@publisher).to receive(:publish).and_return(mocked_return) + + payload = { foo: "bar" } + result = @publisher.publish(payload, "pipefy-local-topic") + expect(result).to eq mocked_return + end + end +end diff --git a/spec/publisher_spec.rb b/spec/publisher_spec.rb index 49066b8..9ee2309 100644 --- a/spec/publisher_spec.rb +++ b/spec/publisher_spec.rb @@ -1,26 +1,28 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Publisher::BasePublisher do +require_relative "../lib/pipefy_message/providers/aws_client/sns_broker" + +RSpec.describe PipefyMessage::Publisher do context "when I try to publish a message to SNS broker" do before do ENV["AWS_ENDPOINT"] = "http://localhost:4566" ENV["AWS_ACCESS_KEY_ID"] = "foo" ENV["AWS_SECRET_ACCESS_KEY"] = "bar" ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" + ENV["AWS_CLI_STUB_RESPONSE"] = "true" end it "should publish a message properly" do - mocked_publisher_impl = PipefyMessage::Publisher::AwsProvider::SnsPublisher.new + mocked_publisher_impl = PipefyMessage::Providers::AwsClient::SnsBroker.new mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", sequence_number: nil } - allow(mocked_publisher_impl).to receive(:publish).and_call_original - allow(mocked_publisher_impl).to receive(:do_publish).and_return(mocked_return) + allow(mocked_publisher_impl).to receive(:publish).and_return(mocked_return) - allow_any_instance_of(PipefyMessage::Publisher::BasePublisher) + allow_any_instance_of(PipefyMessage::Publisher) .to receive(:publisher_instance) .and_return(mocked_publisher_impl) - publisher = PipefyMessage::Publisher::BasePublisher.new + publisher = PipefyMessage::Publisher.new payload = { foo: "bar" } topic_name = "pipefy-local-topic" @@ -29,11 +31,11 @@ expect(mocked_publisher_impl).to have_received(:publish).with(payload, topic_name) end - it "should choose the correct broker implementation" do - result = PipefyMessage::Publisher::BasePublisher.new.send(:publisher_instance) - expected_impl = PipefyMessage::Publisher::AwsProvider::SnsPublisher + # it "should choose the correct broker implementation" do + # result = PipefyMessage::Publisher::BasePublisher.new.send(:publisher_instance) + # expected_impl = PipefyMessage::Publisher::AwsProvider::SnsPublisher - expect(result).to be_a expected_impl - end + # expect(result).to be_a expected_impl + # end end end diff --git a/spec/sns_publisher_spec.rb b/spec/sns_publisher_spec.rb deleted file mode 100644 index cf3da4f..0000000 --- a/spec/sns_publisher_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe PipefyMessage::Publisher::AwsProvider::SnsPublisher do - context "when I try to publish a message with Sns publisher" do - before(:each) do - ENV["AWS_ENDPOINT"] = "http://localhost:4566" - ENV["AWS_ACCESS_KEY_ID"] = "foo" - ENV["AWS_SECRET_ACCESS_KEY"] = "bar" - ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" - - @publisher = PipefyMessage::Publisher::AwsProvider::SnsPublisher.new - end - it "should return a message ID" do - mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", - sequence_number: nil } - - allow(@publisher).to receive(:do_publish).and_return(mocked_return) - - payload = { foo: "bar" } - result = @publisher.publish(payload, "pipefy-local-topic") - expect(result).to eq mocked_return - end - it "should prepare the payload" do - payload = { foo: "bar", bar: "foo" } - prepared_payload = @publisher.send(:prepare_payload, payload) - - expected_payload = { "default" => { bar: "foo", foo: "bar" } } - - expect(prepared_payload).to eq(expected_payload) - end - end -end From 183b6ceb432f91f73088d1e4d6e170fd72a17fd6 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 25 Apr 2022 17:31:53 -0300 Subject: [PATCH 061/108] refact: create instance by inflection --- lib/pipefy_message.rb | 2 ++ .../providers/aws_client/aws_broker.rb | 3 +- .../providers/aws_client/sns_broker.rb | 29 +++++++++---------- .../providers/aws_client/sqs_broker.rb | 2 +- lib/pipefy_message/publisher.rb | 2 +- spec/publisher_spec.rb | 10 +++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 1329a41..2471a63 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -11,6 +11,8 @@ require "logger" require "json" require "benchmark" +require "active_support" +require "active_support/core_ext/string/inflections" ## # PipefyMessage abstraction async process diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index b09951a..8de5202 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -1,7 +1,8 @@ module PipefyMessage module Providers module AwsClient - class BuilderAwsConfig + class AwsBroker < PipefyMessage::Providers::Broker + end end end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index c40177b..264c70d 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -4,8 +4,8 @@ module PipefyMessage module Providers module AwsClient - # AWS Sns client. - class SnsBroker < PipefyMessage::Providers::Broker + # AWS SNS client. + class SnsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config def initialize(opts = {}) @@ -13,7 +13,7 @@ def initialize(opts = {}) Aws.config.update(@config[:aws]) logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) - @sns = Aws::SNS::Resource.new + @sns = Aws::SNS::Resource.new @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] @is_staging = ENV["ASYNC_APP_ENV"] == "staging" rescue StandardError => e @@ -54,25 +54,22 @@ def build_options(opts) end def publish(payload, topic_name) - begin - message = prepare_payload(payload) - topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) - topic = @sns.topic(topic_arn) - - logger.info("Publishing a json message to topic #{topic_arn}") - result = topic.publish({ message: message.to_json, message_structure: " json " }) - logger.info(" Message Published with ID #{result.message_id}") - result - rescue StandardError => e - logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") - end + message = prepare_payload(payload) + topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) + topic = @sns.topic(topic_arn) + logger.info("Publishing a json message to topic #{topic_arn}") + result = topic.publish({ message: message.to_json, message_structure: " json " }) + logger.info(" Message Published with ID #{result.message_id}") + result + rescue StandardError => e + logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") end private def prepare_payload(payload) - # The 'Default' json key/entry it's mandatory to ruby sdk + # The 'Default' json key/entry is mandatory to ruby sdk { "default" => payload } diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index ab8d5f2..3fa068a 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -5,7 +5,7 @@ module PipefyMessage module Providers module AwsClient # AWS SQS client. - class SqsBroker < PipefyMessage::Providers::Broker + class SqsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config def initialize(queue_name, opts = {}) diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index fbea163..03f7ce8 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -16,7 +16,7 @@ def publish(message, topic) private def publisher_instance - map = PipefyMessage.class_path[@broker] + map = PipefyMessage.class_path[@broker.to_sym] require_relative map[:publisher][:relative_path] logger.info({ diff --git a/spec/publisher_spec.rb b/spec/publisher_spec.rb index 9ee2309..320afc5 100644 --- a/spec/publisher_spec.rb +++ b/spec/publisher_spec.rb @@ -31,11 +31,11 @@ expect(mocked_publisher_impl).to have_received(:publish).with(payload, topic_name) end - # it "should choose the correct broker implementation" do - # result = PipefyMessage::Publisher::BasePublisher.new.send(:publisher_instance) - # expected_impl = PipefyMessage::Publisher::AwsProvider::SnsPublisher + it "should choose the correct broker implementation" do + result = PipefyMessage::Publisher.new.send(:publisher_instance) + expected_impl = PipefyMessage::Providers::AwsClient::SnsBroker - # expect(result).to be_a expected_impl - # end + expect(result).to be_a expected_impl + end end end From 91a4cc6322e7740b5b22abb9499498a9e3bef881 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Mon, 25 Apr 2022 17:48:53 -0300 Subject: [PATCH 062/108] refact: fix "aws" and worker feature --- README.md | 2 +- .../providers/aws_client/sns_broker.rb | 1 + .../providers/aws_client/sqs_broker.rb | 1 + lib/pipefy_message/worker.rb | 4 ++-- main.rb | 12 ++++++++++++ spec/worker_spec.rb | 4 ++-- 6 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 main.rb diff --git a/README.md b/README.md index 10c9ca5..0ff3ff9 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ On the irb console: class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "sqs", queue_name: "pipefy-local-queue" + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 264c70d..c6d99a4 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -1,5 +1,6 @@ require "aws-sdk-sqs" require "json" +require_relative "aws_broker" module PipefyMessage module Providers diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 3fa068a..7e6ad3a 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -1,5 +1,6 @@ require "aws-sdk-sqs" require "json" +require_relative "aws_broker" module PipefyMessage module Providers diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index ae55171..e06c1e8 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -15,7 +15,7 @@ module Worker # default values to consumer def self.default_worker_options @default_worker_options ||= { - "broker" => "sqs", + "broker" => "aws", "queue_name" => "my_queue" } end @@ -62,7 +62,7 @@ def pipefymessage_options(opts = {}) # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker - map = PipefyMessage.class_path[broker] + map = PipefyMessage.class_path[broker.to_sym] require_relative map[:consumer][:relative_path] logger.info({ diff --git a/main.rb b/main.rb new file mode 100644 index 0000000..f24e129 --- /dev/null +++ b/main.rb @@ -0,0 +1,12 @@ +require "pipefy_message" + +class TestWorker + include PipefyMessage::Worker + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + + def perform(message) + puts message + end +end + +TestWorker.process_message diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 8656a25..0820b68 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -17,7 +17,7 @@ def poller class TestWorker include PipefyMessage::Worker - pipefymessage_options broker: "sqs", queue_name: "pipefy-local-queue" + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message @@ -41,7 +41,7 @@ def perform(message) describe "#options class" do it "should set options in class" do - expect(TestWorker.broker).to eq "sqs" + expect(TestWorker.broker).to eq "aws" end end end From 6952468906c0fbe383d751b9b520cfcabf935ffb Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 09:38:41 -0300 Subject: [PATCH 063/108] refact: Move common AWS broker methods to superclass --- .../providers/aws_client/aws_broker.rb | 47 ++++++++++++++++++- .../providers/aws_client/sns_broker.rb | 47 +------------------ .../providers/aws_client/sqs_broker.rb | 46 ------------------ 3 files changed, 47 insertions(+), 93 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 8de5202..2c15b11 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -2,7 +2,52 @@ module PipefyMessage module Providers module AwsClient class AwsBroker < PipefyMessage::Providers::Broker - + + ## + # Hash with default options to be used in AWS access configuration + # if no overriding parameters are provided. + def default_options + { + access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), + secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), + endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), + region: (ENV["AWS_REGION"] || "us-east-1"), + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), + wait_time_seconds: 10 + } + end + + ## + # Merges default options (returned by default_options) with the + # hash provided as an argument. The latter takes precedence. + def build_options(opts) + hash = default_options.merge(opts) + aws_hash = isolate_broker_arguments(hash) + + config_hash = { + aws: aws_hash + } + + hash.each do |k, v| + config_hash[k] = v unless aws_hash.key?(k) + end + + config_hash + end + + private + + ## + # Options hash parser. + def isolate_broker_arguments(hash) + { + access_key_id: hash[:access_key_id], + secret_access_key: hash[:secret_access_key], + endpoint: hash[:endpoint], + region: hash[:region], + stub_responses: hash[:stub_responses] + } + end end end end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index c6d99a4..e260256 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -17,43 +17,10 @@ def initialize(opts = {}) @sns = Aws::SNS::Resource.new @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] @is_staging = ENV["ASYNC_APP_ENV"] == "staging" - rescue StandardError => e + rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end - ## - # Hash with default options to be used in AWS access configuration - # if no overriding parameters are provided. - def default_options - { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), - wait_time_seconds: 10, - default_arn_prefix: "arn:aws:sns:us-east-1:000000000000:" - } - end - - ## - # Merges default options (returned by default_options) with the - # hash provided as an argument. The latter takes precedence. - def build_options(opts) - hash = default_options.merge(opts) - aws_hash = isolate_broker_arguments(hash) - - config_hash = { - aws: aws_hash - } - - hash.each do |k, v| - config_hash[k] = v unless aws_hash.key?(k) - end - - config_hash - end - def publish(payload, topic_name) message = prepare_payload(payload) topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) @@ -75,18 +42,6 @@ def prepare_payload(payload) "default" => payload } end - - ## - # Options hash parser. - def isolate_broker_arguments(hash) - { - access_key_id: hash[:access_key_id], - secret_access_key: hash[:secret_access_key], - endpoint: hash[:endpoint], - region: hash[:region], - stub_responses: hash[:stub_responses] - } - end end end end diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 7e6ad3a..a6d4102 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -21,38 +21,6 @@ def initialize(queue_name, opts = {}) raise PipefyMessage::Providers::Errors::ResourceError, e.message end - ## - # Hash with default options to be used in AWS access configuration - # if no overriding parameters are provided. - def default_options - { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), - wait_time_seconds: 10 - } - end - - ## - # Merges default options (returned by default_options) with the - # hash provided as an argument. The latter takes precedence. - def build_options(opts) - hash = default_options.merge(opts) - aws_hash = isolate_broker_arguments(hash) - - config_hash = { - aws: aws_hash - } - - hash.each do |k, v| - config_hash[k] = v unless aws_hash.key?(k) - end - - config_hash - end - ## # Initiates SQS queue polling, with wait_time_seconds as given in # the initial configuration. @@ -65,20 +33,6 @@ def poller yield(payload) end end - - private - - ## - # Options hash parser. - def isolate_broker_arguments(hash) - { - access_key_id: hash[:access_key_id], - secret_access_key: hash[:secret_access_key], - endpoint: hash[:endpoint], - region: hash[:region], - stub_responses: hash[:stub_responses] - } - end end end end From f8f5be6bb9387c98008eac5cbd8294b9113923a4 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 09:44:25 -0300 Subject: [PATCH 064/108] refact: Document AWS broker class, move shared imports --- lib/pipefy_message/providers/aws_client/aws_broker.rb | 9 +++++++-- lib/pipefy_message/providers/aws_client/sns_broker.rb | 2 -- lib/pipefy_message/providers/aws_client/sqs_broker.rb | 2 -- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 2c15b11..7d6f9ba 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -1,8 +1,13 @@ +require "aws-sdk-sqs" +require "json" + module PipefyMessage module Providers module AwsClient + ## + # "Abstract" superclass for brokers of AWS services, implementing + # AWS option parsing and connection setup. class AwsBroker < PipefyMessage::Providers::Broker - ## # Hash with default options to be used in AWS access configuration # if no overriding parameters are provided. @@ -47,7 +52,7 @@ def isolate_broker_arguments(hash) region: hash[:region], stub_responses: hash[:stub_responses] } - end + end end end end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index e260256..ae75a71 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -1,5 +1,3 @@ -require "aws-sdk-sqs" -require "json" require_relative "aws_broker" module PipefyMessage diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index a6d4102..193f5b8 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -1,5 +1,3 @@ -require "aws-sdk-sqs" -require "json" require_relative "aws_broker" module PipefyMessage From af416553f462bab093d5ed478b005ee7993fde61 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 10:54:51 -0300 Subject: [PATCH 065/108] "fix": Refact SQS err to SNS err on SNS rescue --- lib/pipefy_message/providers/aws_client/sns_broker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index ae75a71..bb6f1eb 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -15,7 +15,7 @@ def initialize(opts = {}) @sns = Aws::SNS::Resource.new @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] @is_staging = ENV["ASYNC_APP_ENV"] == "staging" - rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e + rescue Aws::SNS::Errors::ServiceError, Seahorse::Client::NetworkingError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end From b767f6185b8fbc3199266fc31478279478846dd3 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 10:56:34 -0300 Subject: [PATCH 066/108] refact: Open AWS connection on superclass constructor --- lib/pipefy_message/providers/aws_client/aws_broker.rb | 7 +++++++ lib/pipefy_message/providers/aws_client/sns_broker.rb | 4 +--- lib/pipefy_message/providers/aws_client/sqs_broker.rb | 4 +--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 7d6f9ba..1064b09 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -8,6 +8,13 @@ module AwsClient # "Abstract" superclass for brokers of AWS services, implementing # AWS option parsing and connection setup. class AwsBroker < PipefyMessage::Providers::Broker + + def initialize(opts = {}) + @config = build_options(opts) + Aws.config.update(@config[:aws]) + logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + end + ## # Hash with default options to be used in AWS access configuration # if no overriding parameters are provided. diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index bb6f1eb..e26e6d8 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -8,9 +8,7 @@ class SnsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config def initialize(opts = {}) - @config = build_options(opts) - Aws.config.update(@config[:aws]) - logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + super(opts) @sns = Aws::SNS::Resource.new @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 193f5b8..db8d7d0 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -8,9 +8,7 @@ class SqsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config def initialize(queue_name, opts = {}) - @config = build_options(opts) - Aws.config.update(@config[:aws]) - logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + super(opts) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url From a21292af93ef7e8315ffe3b61d9a2dff53caaab5 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 11:13:32 -0300 Subject: [PATCH 067/108] refact: Rewrite publisher logs in JSON format --- .../providers/aws_client/aws_broker.rb | 4 ++-- .../providers/aws_client/sns_broker.rb | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 1064b09..b1a7c47 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -12,9 +12,9 @@ class AwsBroker < PipefyMessage::Providers::Broker def initialize(opts = {}) @config = build_options(opts) Aws.config.update(@config[:aws]) - logger.debug({ options_set: @config, message_text: "AWS connection opened with options_set" }) + logger.debug({ options_set: @config, message_text: "AWS connection set up with options_set" }) end - + ## # Hash with default options to be used in AWS access configuration # if no overriding parameters are provided. diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index e26e6d8..12d34ce 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -22,18 +22,33 @@ def publish(payload, topic_name) topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) topic = @sns.topic(topic_arn) - logger.info("Publishing a json message to topic #{topic_arn}") + logger.info( + { topic_arn: topic_arn, + payload: payload, + message_text: "Attempting to publish a json message to topic #{topic_arn}" } + ) + result = topic.publish({ message: message.to_json, message_structure: " json " }) - logger.info(" Message Published with ID #{result.message_id}") + + logger.info( + { topic_arn: topic_arn, + id: result.message_id, + message_text: "Message published with ID #{result.message_id}" } + ) + result rescue StandardError => e - logger.error("Failed to publish message [#{message}], error details: [#{e.inspect}]") + logger.error( + { topic_arn: topic_arn, + message_text: "Failed to publish message", + error_details: e.inspect} + ) end private def prepare_payload(payload) - # The 'Default' json key/entry is mandatory to ruby sdk + # The 'Default' json key/entry is mandatory to Ruby sdk { "default" => payload } From 5309c6bce898f3832120c8b2106e6d84694a5648 Mon Sep 17 00:00:00 2001 From: Eduardo Hattori Date: Tue, 26 Apr 2022 14:32:15 -0300 Subject: [PATCH 068/108] refact: configuration --- lib/pipefy_message.rb | 1 - .../providers/aws_client/sns_broker.rb | 1 + spec/custom_configuration_spec.rb | 25 ------------------- spec/default_configuration_spec.rb | 25 ------------------- 4 files changed, 1 insertion(+), 51 deletions(-) delete mode 100644 spec/custom_configuration_spec.rb delete mode 100644 spec/default_configuration_spec.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 2471a63..4cd5313 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative "pipefy_message/version" -require_relative "pipefy_message/broker/aws/configuration" require_relative "pipefy_message/logging" require_relative "pipefy_message/publisher" require_relative "pipefy_message/worker" diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 12d34ce..087acc4 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -1,3 +1,4 @@ +require "aws-sdk-sns" require_relative "aws_broker" module PipefyMessage diff --git a/spec/custom_configuration_spec.rb b/spec/custom_configuration_spec.rb deleted file mode 100644 index 783a3c8..0000000 --- a/spec/custom_configuration_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig do - before do - ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" - end - context "when I try to connect with AWS provider (Localstack)" do - it "should return a custom client config with correct values" do - keys = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance.setup_connection - - expected = { endpoint: ENV["AWS_ENDPOINT"], access_key_id: ENV["AWS_ACCESS_KEY_ID"], - secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], region: "us-east-1", - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) } - - expect(keys).to eq expected - end - - it "should return a Singleton" do - first_instance = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance - second_instance = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance - - expect(first_instance).to eq(second_instance) - end - end -end diff --git a/spec/default_configuration_spec.rb b/spec/default_configuration_spec.rb deleted file mode 100644 index 14d90df..0000000 --- a/spec/default_configuration_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig do - before do - ENV["ENABLE_AWS_CLIENT_CONFIG"] = "false" - end - context "when I try to connect with AWS provider" do - it "should return a custom client config with correct values" do - keys = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance.setup_connection - - expected = { endpoint: ENV["AWS_ENDPOINT"], access_key_id: ENV["AWS_ACCESS_KEY_ID"], - secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], region: "us-east-1", - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] || false) } - - expect(keys).to eq expected - end - - it "should return a Singleton" do - first_instance = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance - second_instance = PipefyMessage::BrokerConfiguration::AwsProvider::ProviderConfig.instance - - expect(first_instance).to eq(second_instance) - end - end -end From e24bfc149e501aea22bbfc84c781b2d127179d5d Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 15:03:50 -0300 Subject: [PATCH 069/108] Documenting SNS broker (partial commit before pulling) --- lib/pipefy_message/providers/aws_client/sns_broker.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 12d34ce..2b1ccc1 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -17,6 +17,9 @@ def initialize(opts = {}) raise PipefyMessage::Providers::Errors::ResourceError, e.message end + ## + # Publishes a message with the given payload to the SNS topic + # with topic_name. def publish(payload, topic_name) message = prepare_payload(payload) topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) @@ -41,14 +44,16 @@ def publish(payload, topic_name) logger.error( { topic_arn: topic_arn, message_text: "Failed to publish message", - error_details: e.inspect} + error_details: e.inspect } ) end private + ## + # "Wraps" the message payload as the value of the "default" key + # in a hash, as specified by the AWS Ruby SDK. def prepare_payload(payload) - # The 'Default' json key/entry is mandatory to Ruby sdk { "default" => payload } From 9c8ceee27b7da6571346f4caed2973afa2ac22ed Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 15:25:49 -0300 Subject: [PATCH 070/108] refact: Remove old AWS configuration class --- .../broker/aws/configuration.rb | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 lib/pipefy_message/broker/aws/configuration.rb diff --git a/lib/pipefy_message/broker/aws/configuration.rb b/lib/pipefy_message/broker/aws/configuration.rb deleted file mode 100644 index d4d2909..0000000 --- a/lib/pipefy_message/broker/aws/configuration.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require "aws-sdk-sns" -require "singleton" - -module PipefyMessage - module BrokerConfiguration - module AwsProvider - # Aws Provider Config class to connect with the cloud resources - class ProviderConfig - include Singleton - - def setup_connection - Aws.config.update(retrieve_config) - end - - private - - def aws_client_config? - ENV["ENABLE_AWS_CLIENT_CONFIG"] == "true" - end - - def retrieve_config - config = { region: ENV["AWS_REGION"] || "us-east-1" } - merge_custom_config(config) - end - - def merge_custom_config(config) - if aws_client_config? - { access_key_id: ENV["AWS_ACCESS_KEY_ID"], secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], - endpoint: ENV["AWS_ENDPOINT"], stub_responses: ENV["AWS_CLI_STUB_RESPONSE"] || false }.merge(config) - else - config - end - end - end - end - end -end From 3e454cf0013433e75ea1e1a3e92296b4976bb86a Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 15:27:27 -0300 Subject: [PATCH 071/108] Move SQS, SNS README to AWS client dir --- .../{broker/aws => providers/aws_client}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename lib/pipefy_message/{broker/aws => providers/aws_client}/README.md (90%) diff --git a/lib/pipefy_message/broker/aws/README.md b/lib/pipefy_message/providers/aws_client/README.md similarity index 90% rename from lib/pipefy_message/broker/aws/README.md rename to lib/pipefy_message/providers/aws_client/README.md index 083bbb7..6c325f0 100644 --- a/lib/pipefy_message/broker/aws/README.md +++ b/lib/pipefy_message/providers/aws_client/README.md @@ -8,7 +8,7 @@ ### Aws-cli Installation -Before proceed to the next step it's required to have the aws-cli installed in your machine, for that follow [this guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). +Before proceeding to the next step it's required to have the aws-cli installed in your machine, for that follow [this guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). > **Note that we must have the project docker-compose localstack service running before executing the commands above** ### Creating new SNS Topic From fe597994a69bb01a042f1b5fb6cadf326b62bb06 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 15:55:51 -0300 Subject: [PATCH 072/108] Handle invalid provider option --- .../providers/aws_client/aws_broker.rb | 1 - lib/pipefy_message/providers/errors.rb | 6 +++++ lib/pipefy_message/worker.rb | 26 +++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index b1a7c47..9f0a6e0 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -8,7 +8,6 @@ module AwsClient # "Abstract" superclass for brokers of AWS services, implementing # AWS option parsing and connection setup. class AwsBroker < PipefyMessage::Providers::Broker - def initialize(opts = {}) @config = build_options(opts) Aws.config.update(@config[:aws]) diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 7efaf90..323a63b 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -7,6 +7,12 @@ def initialize(msg = "ResourceError") super end end + + class InvalidOption < ArgumentError + def initialize(msg = "InvalidOption") + super + end + end end end end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index e06c1e8..bb89543 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -7,12 +7,12 @@ module PipefyMessage ## # Provides a provider-agnostic, higher level abstraction for # dealing with queue polling and message parsing. - # Should be included by classes hat implement a perform method, for + # Should be included by classes that implement a perform method, for # processing received messages. module Worker include PipefyMessage::Logging ## - # default values to consumer + # Default options for consumer setup. def self.default_worker_options @default_worker_options ||= { "broker" => "aws", @@ -21,9 +21,8 @@ def self.default_worker_options end ## - # to make the logger available as a static method - # see ClassMethods; this is what makes - # those methods available to the base class + # Makes methods available as a static/class methods + # (see ClassMethods). def self.included(base) base.extend(self) base.extend(ClassMethods) @@ -62,15 +61,26 @@ def pipefymessage_options(opts = {}) # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker - map = PipefyMessage.class_path[broker.to_sym] - require_relative map[:consumer][:relative_path] + provider_map = PipefyMessage.class_path[broker.to_sym] + + if provider_map.nil? + logger.error({ + invalid_provider: broker, + message_text: "Invalid provider specified: #{broker}" + }) + + raise InvalidOption + end + + consumer_map = provider_map[:consumer] + require_relative consumer_map[:relative_path] logger.info({ broker: broker, message_text: "Initializing and returning instance of #{broker} broker" }) - map[:consumer][:class_name].constantize.new(queue_name, @options_hash) + consumer_map[:class_name].constantize.new(queue_name, @options_hash) end ## From 54dec704199e494fd77d23b5f3b5d4c01d33fccc Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 16:05:57 -0300 Subject: [PATCH 073/108] refact: Use described_class rather than hardcoded class name on RSpec tests --- .../providers/aws_client/sqs_broker.rb | 4 ++-- lib/pipefy_message/worker.rb | 13 +++++++++---- spec/pipefy_message_spec.rb | 2 +- spec/providers/aws/sns_broker_spec.rb | 2 +- spec/providers/aws/sqs_broker_spec.rb | 6 +++--- spec/publisher_spec.rb | 6 +++--- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index db8d7d0..f4d6d11 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -18,8 +18,8 @@ def initialize(queue_name, opts = {}) end ## - # Initiates SQS queue polling, with wait_time_seconds as given in - # the initial configuration. + # Initiates SQS queue polling, with wait_time_seconds as given + # in the initial configuration. def poller logger.debug({ message_text: "Initiating SQS polling..." }) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index bb89543..5e776e2 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -69,7 +69,10 @@ def build_instance_broker message_text: "Invalid provider specified: #{broker}" }) - raise InvalidOption + # (this is actually not good and should eventually be + # refactored; we should have a "less manual" way of logging + # errors) + raise InvalidOption, "Invalid provider specified: #{broker}" end consumer_map = provider_map[:consumer] @@ -80,7 +83,7 @@ def build_instance_broker message_text: "Initializing and returning instance of #{broker} broker" }) - consumer_map[:class_name].constantize.new(queue_name, @options_hash) + consumer_map[:class_name].constantize.new(queue_name, @options_hash) end ## @@ -95,8 +98,10 @@ def process_message logger.info({ message_text: "Calling poller for #{broker} object" }) build_instance_broker.poller do |message| - logger.info({ message_text: "Message received by #{broker} poller to be processed by worker", - received_message: message }) + logger.info({ + message_text: "Message received by #{broker} poller to be processed by worker", + received_message: message + }) obj.perform(message) end rescue Exception => e diff --git a/spec/pipefy_message_spec.rb b/spec/pipefy_message_spec.rb index 6a42df7..acd516d 100644 --- a/spec/pipefy_message_spec.rb +++ b/spec/pipefy_message_spec.rb @@ -2,7 +2,7 @@ RSpec.describe PipefyMessage do it "has a version number" do - expect(PipefyMessage::VERSION).not_to be nil + expect(described_class::VERSION).not_to be nil end it "does something useful" do diff --git a/spec/providers/aws/sns_broker_spec.rb b/spec/providers/aws/sns_broker_spec.rb index b9aba65..8da9f28 100644 --- a/spec/providers/aws/sns_broker_spec.rb +++ b/spec/providers/aws/sns_broker_spec.rb @@ -9,7 +9,7 @@ stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) end it "should return a message ID" do - @publisher = PipefyMessage::Providers::AwsClient::SnsBroker.new + @publisher = described_class.new mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", sequence_number: nil } diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index 2947be1..2b5da1e 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -32,7 +32,7 @@ end it "should consume message" do - worker = PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") + worker = described_class.new("my_queue") worker.instance_variable_set(:@poller, mocked_poller) result = nil @@ -57,7 +57,7 @@ ) expect do - PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") + described_class.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, /The specified queue my_queue does not exist for this wsdl version/) end @@ -73,7 +73,7 @@ ) expect do - PipefyMessage::Providers::AwsClient::SqsBroker.new("my_queue") + described_class.new("my_queue") end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end diff --git a/spec/publisher_spec.rb b/spec/publisher_spec.rb index 320afc5..0b00c2f 100644 --- a/spec/publisher_spec.rb +++ b/spec/publisher_spec.rb @@ -18,11 +18,11 @@ allow(mocked_publisher_impl).to receive(:publish).and_return(mocked_return) - allow_any_instance_of(PipefyMessage::Publisher) + allow_any_instance_of(described_class) .to receive(:publisher_instance) .and_return(mocked_publisher_impl) - publisher = PipefyMessage::Publisher.new + publisher = described_class.new payload = { foo: "bar" } topic_name = "pipefy-local-topic" @@ -32,7 +32,7 @@ end it "should choose the correct broker implementation" do - result = PipefyMessage::Publisher.new.send(:publisher_instance) + result = described_class.new.send(:publisher_instance) expected_impl = PipefyMessage::Providers::AwsClient::SnsBroker expect(result).to be_a expected_impl From 39263ce104b3e8ffa120a2218d851de63124d25b Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 16:12:13 -0300 Subject: [PATCH 074/108] refact: Specify which exceptions to handle (WIP - should be improved?) --- lib/pipefy_message/worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 5e776e2..54f6e44 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -104,7 +104,7 @@ def process_message }) obj.perform(message) end - rescue Exception => e + rescue PipefyMessage::Providers::Errors::ResourceError => e # (any others?) raise e ensure elapsed_time = (Time.now - start) * 1000.0 From f34f2b0a8388c188cb44ff0353f7ee0f4c41633a Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 16:12:35 -0300 Subject: [PATCH 075/108] refact: Separate contexts in worker tests --- spec/worker_spec.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 0820b68..c60d8f8 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -26,16 +26,20 @@ def perform(message) end describe "#perform" do - it "should call #perform from child instance when call #process_message with success" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - - TestWorker.process_message - expect($result).to eq "test" + context "successful polling" do + it "should call #perform from child instance when #process_message is called" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + + TestWorker.process_message + expect($result).to eq "test" + end end - it "should call #perform from child instance when call #process_message with fail(raise a ResourceError)" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) - expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + context "polling failure" do + it "should call #perform from child instance when #process_message is called" do + allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) + expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + end end end From 55b1c56e9501be157131b1eb4dbab07add84e08e Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 26 Apr 2022 17:14:50 -0300 Subject: [PATCH 076/108] Try to improve worker tests (keyword "try" :P) --- lib/pipefy_message/worker.rb | 2 +- spec/worker_spec.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 54f6e44..5db33db 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -72,7 +72,7 @@ def build_instance_broker # (this is actually not good and should eventually be # refactored; we should have a "less manual" way of logging # errors) - raise InvalidOption, "Invalid provider specified: #{broker}" + raise PipefyMessage::Providers::Errors::InvalidOption, "Invalid provider specified: #{broker}" end consumer_map = provider_map[:consumer] diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index c60d8f8..969e086 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -41,6 +41,10 @@ def perform(message) expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end + + it "should fail if called directly from the parent class" do + expect { TestWorker.perform("message") }.to raise_error NotImplementedError + end end describe "#options class" do @@ -48,4 +52,31 @@ def perform(message) expect(TestWorker.broker).to eq "aws" end end + + describe "#build_instance_broker" do + context "invalid provider" do + before(:all) do + TestWorker.broker = "NaN" + end + + after(:all) do + TestWorker.broker = "aws" # reverting + end + + it "should raise an error" do + expect { TestWorker.build_instance_broker }.to raise_error PipefyMessage::Providers::Errors::InvalidOption + end + end + + # context "valid provider" do + # it "should instantiate a consumer for the given queue" do + # # (I'd like to test that it does create an instance while + # # passing the correct queue and options as args, in a way + # # that doesn't rely on any specific provider implementation + # # so as not to make this an integration test, but I can't + # # even figure out how to do this atm lol This is rather + # # coupled :P) + # end + # end + end end From f654212dce813459a080a639ec5868952ba8dfc4 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 13:58:54 -0300 Subject: [PATCH 077/108] refact: Address Rubocop offenses Note: Perhaps build_instance_broker and process_message really could be handled better, though I suppressed a couple of cops. See comments. --- .rubocop.yml | 8 ++++ lib/pipefy_message.rb | 3 ++ .../providers/aws_client/aws_broker.rb | 2 + .../providers/aws_client/sns_broker.rb | 9 +++++ .../providers/aws_client/sqs_broker.rb | 2 + lib/pipefy_message/providers/broker.rb | 14 +++++-- lib/pipefy_message/providers/errors.rb | 21 ++++++++++ lib/pipefy_message/worker.rb | 37 ++++++++++++------ main.rb | 4 ++ spec/worker_spec.rb | 38 +++++++++---------- 10 files changed, 104 insertions(+), 34 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5a741f2..060c1fc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,14 @@ Layout/LineLength: Metrics/BlockLength: IgnoredMethods: ["describe", "context"] +Style/GlobalVars: + Exclude: + - "spec/worker_spec.rb" + # The introduction of a dummy global variable greatly simplified + # testing in this case, as it made reassignment between scopes much + # more straightforward. I didn't consider that particularly harmful. + # Thoughts? + Lint/MissingSuper: Exclude: - "/**/*" diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 4cd5313..c12a1b7 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -17,6 +17,8 @@ # PipefyMessage abstraction async process ## module PipefyMessage + # rubocop:disable Metrics/MethodLength + # (This method is long because it's essentially storing information.) def self.class_path { aws: { @@ -31,4 +33,5 @@ def self.class_path } } end + # rubocop:enable Metrics/MethodLength end diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 9f0a6e0..2f5502f 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "aws-sdk-sqs" require "json" diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 9cc2eb8..05e893c 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "aws-sdk-sns" require_relative "aws_broker" @@ -18,6 +20,12 @@ def initialize(opts = {}) raise PipefyMessage::Providers::Errors::ResourceError, e.message end + # rubocop:disable Metrics/MethodLength + # (The method below is long because of logger calls and the + # rescue block. I don't feel those are necessarily issues to be + # refactored -- thought maybe we _could_ handle that better. + # Thoughts?) + ## # Publishes a message with the given payload to the SNS topic # with topic_name. @@ -48,6 +56,7 @@ def publish(payload, topic_name) error_details: e.inspect } ) end + # rubocop:enable Metrics/MethodLength private diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index f4d6d11..171da6d 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "aws_broker" module PipefyMessage diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index cc0d5d0..9c1912b 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -1,16 +1,22 @@ +# frozen_string_literal: true + module PipefyMessage module Providers # Provides a provider-agnostic, higher level abstraction for - # objects that provide pollers. Should be included in classes implemented for specific providers. Used by the Worker module. + # objects that provide pollers. Should be included in classes + # implemented for specific providers. Used by the Worker module. class Broker include PipefyMessage::Logging + include PipefyMessage::Providers::Errors def poller - raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + error_msg = includer_should_implement(__method__) + raise NotImplementedError, error_msg end - def publish(payload, topic_name) - raise NotImplementedError, "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + def publish(_payload, _topic_name) + error_msg = includer_should_implement(__method__) + raise NotImplementedError, error_msg end def default_options diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 323a63b..40d3df1 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -1,18 +1,39 @@ # frozen_string_literal: true + module PipefyMessage module Providers + ## + # Provides higher-level error classes that can be used for + # handling similar error thrown by different providers, as well + # as common error messages. module Errors + ## + # Abstraction for service and networking errors. class ResourceError < RuntimeError def initialize(msg = "ResourceError") super end end + ## + # To be raised when an invalid value is passed as an option + # to a method call (eg: if a queueing service client is + # initialized with an invalid queue identifier). class InvalidOption < ArgumentError def initialize(msg = "InvalidOption") super end end + + # Error messages: + # (Should these be moved outside the providers directory?) + + ## + # To be used when raising NotImplementedError from an "abstract" + # method in a superclass. + def includer_should_implement(meth) + "Method #{meth} should be implemented by classes including #{method(meth).owner}" + end end end end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 5db33db..e0699b4 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -2,6 +2,7 @@ require "singleton" require "benchmark" +require_relative "providers/errors" module PipefyMessage ## @@ -11,6 +12,7 @@ module PipefyMessage # processing received messages. module Worker include PipefyMessage::Logging + include PipefyMessage::Providers::Errors ## # Default options for consumer setup. def self.default_worker_options @@ -33,8 +35,8 @@ def self.included(base) # messages received by the poller. Called by process_message from # an instance of the including class. def perform(_message) - raise NotImplementedError, - "Method #{__method__} should be implemented by classes including #{method(__method__).owner}" + error_msg = includer_should_implement(__method__) + raise NotImplementedError, error_msg end ## @@ -57,6 +59,12 @@ def pipefymessage_options(opts = {}) }) end + # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + # (The methods below are long because of logger calls and the + # rescue block. I don't feel those are necessarily issues to be + # refactored -- thought maybe we _could_ handle that better. + # Thoughts?) + ## # Initializes and returns an instance of a broker for # the provider specified in the class options. @@ -64,15 +72,16 @@ def build_instance_broker provider_map = PipefyMessage.class_path[broker.to_sym] if provider_map.nil? + error_msg = "Invalid provider specified: #{broker}" logger.error({ - invalid_provider: broker, - message_text: "Invalid provider specified: #{broker}" - }) + invalid_provider: broker, + message_text: error_msg + }) # (this is actually not good and should eventually be # refactored; we should have a "less manual" way of logging # errors) - raise PipefyMessage::Providers::Errors::InvalidOption, "Invalid provider specified: #{broker}" + raise PipefyMessage::Providers::Errors::InvalidOption, error_msg end consumer_map = provider_map[:consumer] @@ -95,13 +104,16 @@ def build_instance_broker def process_message start = Time.now obj = new - logger.info({ message_text: "Calling poller for #{broker} object" }) + logger.info({ + message_text: "Calling poller for #{broker} object" + }) build_instance_broker.poller do |message| logger.info({ - message_text: "Message received by #{broker} poller to be processed by worker", - received_message: message - }) + message_text: "Message received by #{broker} + poller to be processed by worker", + received_message: message + }) obj.perform(message) end rescue PipefyMessage::Providers::Errors::ResourceError => e # (any others?) @@ -110,9 +122,12 @@ def process_message elapsed_time = (Time.now - start) * 1000.0 logger.info({ duration_seconds: elapsed_time, - message_text: "Message received by #{broker} poller processed by #{name} worker in #{elapsed_time} seconds" + message_text: "Message received by #{broker} + poller processed by #{name} worker in + #{elapsed_time} seconds" }) end end + # rubocop:enable Metrics/MethodLength, Metrics/AbcSize end end diff --git a/main.rb b/main.rb index f24e129..28513b5 100644 --- a/main.rb +++ b/main.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + require "pipefy_message" +## +# Example worker class. class TestWorker include PipefyMessage::Worker pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 969e086..16253f9 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,35 +1,35 @@ # frozen_string_literal: true -RSpec.describe PipefyMessage::Worker do - $result =~ nil +$result =~ nil - class MockBroker < PipefyMessage::Providers::Broker - def poller - yield("test") - end +class MockBroker < PipefyMessage::Providers::Broker + def poller + yield("test") end +end - class MockBrokerFail < PipefyMessage::Providers::Broker - def poller - raise PipefyMessage::Providers::Errors::ResourceError - end +class MockBrokerFail < PipefyMessage::Providers::Broker + def poller + raise PipefyMessage::Providers::Errors::ResourceError end +end - class TestWorker - include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" +class TestWorker + include PipefyMessage::Worker + pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" - def perform(message) - puts message - $result = message - end + def perform(message) + puts message + $result = message end +end +RSpec.describe PipefyMessage::Worker do describe "#perform" do context "successful polling" do it "should call #perform from child instance when #process_message is called" do allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) - + TestWorker.process_message expect($result).to eq "test" end @@ -58,7 +58,7 @@ def perform(message) before(:all) do TestWorker.broker = "NaN" end - + after(:all) do TestWorker.broker = "aws" # reverting end From e05ea120cc6dcc8adeb2bb0d4973b16ba2ff2ac3 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 16:36:56 -0300 Subject: [PATCH 078/108] refact: Incorporate review feedback into Logging module --- Gemfile.lock | 1 + lib/pipefy_message/logging.rb | 43 ++++++++++++++++------------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0bce941..759740f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,6 +116,7 @@ GEM webrick (~> 1.7.0) PLATFORMS + ruby x86_64-darwin-20 x86_64-linux diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 2226b3e..bd72d18 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -4,50 +4,41 @@ require "json" module PipefyMessage - # Provides a shared logger setup to all classes and instances of classes - # that require logging, when included. + ## + # Provides a shared logger setup to all classes and instances of + # classes that require logging, when included. # - # For instance: if class Test includes Logging, and test is an instance - # of Test (eg: test = Test.new), then both Test.logger and test.logger - # should provide a working logger with the same configurations. + # For instance: if class Test includes Logging, and test is an + # instance of Test (eg: test = Test.new), then both Test.logger and + # test.logger should provide a working logger with the same + # configurations. # # (see https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes) module Logging - # Destination where logs should be saved. - def self.logfile - $stdout - end - + ## # Creates a logger object if it has not yet been instantiated, # or returns the existing object. def self.logger @logger ||= logger_setup end + ## # Configuration for a logger created by the Ruby logger gem. def self.logger_setup loglevels = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze - logger = Logger.new(logfile) - level ||= loglevels.index ENV.fetch("LOG_LEVEL", "INFO") - level ||= Logger::INFO + logger = Logger.new($stdout) + level ||= loglevels.index ENV.fetch("ASYNC_LOG_LEVEL", "INFO") + level ||= Logger::ERROR logger.level = level logger end - # Allows for custom datetime formatting. Return the datetime - # parameter (as is) to use the default. - def self.formatted_timestamp(datetime) - # datetime.strftime("%Y-%m-%d %H:%M:%S") - datetime - end - + ## # Formats logger output as a JSON object, including information on # the calling object. Should not be called directly; this method is # called implicitly whenever a logger method is called. def self.json_output(obj, severity, datetime, progname, msg) - timestamp = formatted_timestamp(datetime) - - { date: timestamp.to_s, + { date: datetime.to_s, level: severity.to_s, app: progname.to_s, context: "async_processing", @@ -56,6 +47,7 @@ def self.json_output(obj, severity, datetime, progname, msg) message: msg } end + ## # Logger method available to all instances of classes # that include the Logging module (as an instance method). # Includes information on the calling object. @@ -63,12 +55,17 @@ def logger Logging.logger.formatter = proc do |severity, datetime, progname, msg| json_hash = Logging.json_output(self, severity, datetime, progname, msg) + # The necessity of explicitly JSON.dumping this hash has been + # discussed in a code review. We should check how these logs + # will be processed on the other end and perhaps refactor + # accordingly. JSON.dump(json_hash) + ($INPUT_RECORD_SEPARATOR || "\n") end Logging.logger end + ## # Includes module attributes and methods as class/static (rather # than just instance) attributes and methods. def self.included(base) From aad3ff458063235d083935a266fe06f906ae94d4 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 16:42:56 -0300 Subject: [PATCH 079/108] refact: (post-review) Disable AbcSize, MethodLength globally Too many false positives triggered by logging. --- .rubocop.yml | 9 +++++++-- lib/pipefy_message.rb | 3 --- lib/pipefy_message/providers/aws_client/sns_broker.rb | 7 ------- lib/pipefy_message/worker.rb | 7 ------- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 060c1fc..78742f3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,5 +25,10 @@ Style/GlobalVars: # Thoughts? Lint/MissingSuper: - Exclude: - - "/**/*" + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false \ No newline at end of file diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index c12a1b7..4cd5313 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -17,8 +17,6 @@ # PipefyMessage abstraction async process ## module PipefyMessage - # rubocop:disable Metrics/MethodLength - # (This method is long because it's essentially storing information.) def self.class_path { aws: { @@ -33,5 +31,4 @@ def self.class_path } } end - # rubocop:enable Metrics/MethodLength end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 05e893c..11258f5 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -20,12 +20,6 @@ def initialize(opts = {}) raise PipefyMessage::Providers::Errors::ResourceError, e.message end - # rubocop:disable Metrics/MethodLength - # (The method below is long because of logger calls and the - # rescue block. I don't feel those are necessarily issues to be - # refactored -- thought maybe we _could_ handle that better. - # Thoughts?) - ## # Publishes a message with the given payload to the SNS topic # with topic_name. @@ -56,7 +50,6 @@ def publish(payload, topic_name) error_details: e.inspect } ) end - # rubocop:enable Metrics/MethodLength private diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index e0699b4..dd192aa 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -59,12 +59,6 @@ def pipefymessage_options(opts = {}) }) end - # rubocop:disable Metrics/MethodLength, Metrics/AbcSize - # (The methods below are long because of logger calls and the - # rescue block. I don't feel those are necessarily issues to be - # refactored -- thought maybe we _could_ handle that better. - # Thoughts?) - ## # Initializes and returns an instance of a broker for # the provider specified in the class options. @@ -128,6 +122,5 @@ def process_message }) end end - # rubocop:enable Metrics/MethodLength, Metrics/AbcSize end end From 597f7c628a5c0764da45822ceb00bb8b4847f718 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 17:58:54 -0300 Subject: [PATCH 080/108] feat: (post-review) Create module to autolog all custom errors --- lib/pipefy_message/logging.rb | 2 -- .../providers/aws_client/sns_broker.rb | 2 +- .../providers/aws_client/sqs_broker.rb | 2 +- lib/pipefy_message/providers/errors.rb | 27 ++++++++++++++----- lib/pipefy_message/worker.rb | 7 ----- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index bd72d18..05f9947 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -42,8 +42,6 @@ def self.json_output(obj, severity, datetime, progname, msg) level: severity.to_s, app: progname.to_s, context: "async_processing", - calling_obj: obj.to_s, - calling_obj_class: obj.class.to_s, message: msg } end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 11258f5..6ff6481 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -16,7 +16,7 @@ def initialize(opts = {}) @sns = Aws::SNS::Resource.new @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] @is_staging = ENV["ASYNC_APP_ENV"] == "staging" - rescue Aws::SNS::Errors::ServiceError, Seahorse::Client::NetworkingError => e + rescue StandardError raise PipefyMessage::Providers::Errors::ResourceError, e.message end diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 171da6d..4ed9b09 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -15,7 +15,7 @@ def initialize(queue_name, opts = {}) @sqs = Aws::SQS::Client.new queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) - rescue Aws::SQS::Errors::NonExistentQueue, Seahorse::Client::NetworkingError => e + rescue StandardError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 40d3df1..7186bc3 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -4,25 +4,38 @@ module PipefyMessage module Providers ## # Provides higher-level error classes that can be used for - # handling similar error thrown by different providers, as well + # handling similar errors thrown by different providers, as well # as common error messages. module Errors + ## - # Abstraction for service and networking errors. - class ResourceError < RuntimeError - def initialize(msg = "ResourceError") + # When included in an Exception class, automatically logs every + # time an instance is raised. + module LoggingError + prepend PipefyMessage::Logging + + def initialize(msg = nil) super + logger.error({ + error_class: self.class, + error_message: message, + stack_trace: full_message + }) end end + ## + # Abstraction for service and networking errors. + class ResourceError < RuntimeError + prepend PipefyMessage::Providers::Errors::LoggingError + end + ## # To be raised when an invalid value is passed as an option # to a method call (eg: if a queueing service client is # initialized with an invalid queue identifier). class InvalidOption < ArgumentError - def initialize(msg = "InvalidOption") - super - end + prepend PipefyMessage::Providers::Errors::LoggingError end # Error messages: diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index dd192aa..77dabfa 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -67,14 +67,7 @@ def build_instance_broker if provider_map.nil? error_msg = "Invalid provider specified: #{broker}" - logger.error({ - invalid_provider: broker, - message_text: error_msg - }) - # (this is actually not good and should eventually be - # refactored; we should have a "less manual" way of logging - # errors) raise PipefyMessage::Providers::Errors::InvalidOption, error_msg end From b0ca1d7ccf43fc996c041394d83828344734d32d Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 18:06:44 -0300 Subject: [PATCH 081/108] refact (minor): Please Rubocop --- lib/pipefy_message/logging.rb | 2 +- lib/pipefy_message/providers/errors.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 05f9947..9455986 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -37,7 +37,7 @@ def self.logger_setup # Formats logger output as a JSON object, including information on # the calling object. Should not be called directly; this method is # called implicitly whenever a logger method is called. - def self.json_output(obj, severity, datetime, progname, msg) + def self.json_output(_obj, severity, datetime, progname, msg) { date: datetime.to_s, level: severity.to_s, app: progname.to_s, diff --git a/lib/pipefy_message/providers/errors.rb b/lib/pipefy_message/providers/errors.rb index 7186bc3..9e3bf73 100644 --- a/lib/pipefy_message/providers/errors.rb +++ b/lib/pipefy_message/providers/errors.rb @@ -7,7 +7,6 @@ module Providers # handling similar errors thrown by different providers, as well # as common error messages. module Errors - ## # When included in an Exception class, automatically logs every # time an instance is raised. From f856b3aeededb33d958a67feb222795e1415b2a7 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 18:16:04 -0300 Subject: [PATCH 082/108] refact: (post-review) Move class_path hash to BrokerResolver class --- lib/pipefy_message.rb | 14 ----------- .../providers/broker_resolver.rb | 25 +++++++++++++++++++ lib/pipefy_message/publisher.rb | 19 +++++++++++--- lib/pipefy_message/worker.rb | 3 ++- 4 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 lib/pipefy_message/providers/broker_resolver.rb diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 4cd5313..67a9dac 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -17,18 +17,4 @@ # PipefyMessage abstraction async process ## module PipefyMessage - def self.class_path - { - aws: { - publisher: { - class_name: "PipefyMessage::Providers::AwsClient::SnsBroker", - relative_path: "providers/aws_client/sns_broker" - }, - consumer: { - class_name: "PipefyMessage::Providers::AwsClient::SqsBroker", - relative_path: "providers/aws_client/sqs_broker" - } - } - } - end end diff --git a/lib/pipefy_message/providers/broker_resolver.rb b/lib/pipefy_message/providers/broker_resolver.rb new file mode 100644 index 0000000..37b9a79 --- /dev/null +++ b/lib/pipefy_message/providers/broker_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module PipefyMessage + module Providers + ## + # Provides class names and paths to be used for each type of + # client, for each provider. + module BrokerResolver + def self.class_path + { + aws: { + publisher: { + class_name: "PipefyMessage::Providers::AwsClient::SnsBroker", + relative_path: "providers/aws_client/sns_broker" + }, + consumer: { + class_name: "PipefyMessage::Providers::AwsClient::SqsBroker", + relative_path: "providers/aws_client/sqs_broker" + } + } + } + end + end + end +end \ No newline at end of file diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index 03f7ce8..0fe4aba 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "providers/broker_resolver" + module PipefyMessage # Base Publisher provided by this gem, to be used for the external publishers to send messages to a broker class Publisher @@ -15,16 +17,27 @@ def publish(message, topic) private + ## + # Initializes and returns an instance of a broker for + # the provider specified in the class options. def publisher_instance - map = PipefyMessage.class_path[@broker.to_sym] - require_relative map[:publisher][:relative_path] + provider_map = PipefyMessage::Providers::BrokerResolver.class_path[@broker.to_sym] + + if provider_map.nil? + error_msg = "Invalid provider specified: #{@broker}" + + raise PipefyMessage::Providers::Errors::InvalidOption, error_msg + end + + publisher_map = provider_map[:publisher] + require_relative publisher_map[:relative_path] logger.info({ broker: @broker, message_text: "Initializing and returning instance of #{@broker} broker" }) - map[:publisher][:class_name].constantize.new + publisher_map[:class_name].constantize.new end end end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 77dabfa..7f8a69d 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -3,6 +3,7 @@ require "singleton" require "benchmark" require_relative "providers/errors" +require_relative "providers/broker_resolver" module PipefyMessage ## @@ -63,7 +64,7 @@ def pipefymessage_options(opts = {}) # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker - provider_map = PipefyMessage.class_path[broker.to_sym] + provider_map = PipefyMessage::Providers::BrokerResolver.class_path[broker.to_sym] if provider_map.nil? error_msg = "Invalid provider specified: #{broker}" From 7317f1d2eca47f37207c158ee9a90e49f9793805 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 18:17:09 -0300 Subject: [PATCH 083/108] refact: Add... missing trailing newline. Yeah. :P --- lib/pipefy_message/providers/broker_resolver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pipefy_message/providers/broker_resolver.rb b/lib/pipefy_message/providers/broker_resolver.rb index 37b9a79..493ed6d 100644 --- a/lib/pipefy_message/providers/broker_resolver.rb +++ b/lib/pipefy_message/providers/broker_resolver.rb @@ -22,4 +22,4 @@ def self.class_path end end end -end \ No newline at end of file +end From 788e30762ae97979f2f03b12e0ccf8818d999f75 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 27 Apr 2022 18:47:12 -0300 Subject: [PATCH 084/108] refact: (post-review) Move SQS-specific poller wait time option to SqsClient (from AwsClient) --- .../providers/aws_client/aws_broker.rb | 15 ++++++++------- .../providers/aws_client/sqs_broker.rb | 11 +++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 2f5502f..8fb839d 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -17,22 +17,23 @@ def initialize(opts = {}) end ## - # Hash with default options to be used in AWS access configuration - # if no overriding parameters are provided. + # Hash with default options to be used in AWS access + # configuration if no overriding parameters are provided. def default_options { access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true"), - wait_time_seconds: 10 + stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") } end ## - # Merges default options (returned by default_options) with the - # hash provided as an argument. The latter takes precedence. + # Moves AWS configuration options into a subhash, separate + # from any other options passed to child classes. Also + # merges default options (returned by default_options) with the + # hash provided as an argument; the latter takes precedence. def build_options(opts) hash = default_options.merge(opts) aws_hash = isolate_broker_arguments(hash) @@ -51,7 +52,7 @@ def build_options(opts) private ## - # Options hash parser. + # AWS option hash parser. def isolate_broker_arguments(hash) { access_key_id: hash[:access_key_id], diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 4ed9b09..dc2699f 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -5,6 +5,7 @@ module PipefyMessage module Providers module AwsClient + ## # AWS SQS client. class SqsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config @@ -19,6 +20,16 @@ def initialize(queue_name, opts = {}) raise PipefyMessage::Providers::Errors::ResourceError, e.message end + ## + # Extends AWS default options to include a value + # for queue poller wait times. + def default_options + aws_defaults = super + aws_defaults[:wait_time_seconds] = 10 + + aws_defaults + end + ## # Initiates SQS queue polling, with wait_time_seconds as given # in the initial configuration. From 23cb0319913085c9b9315b898ddd119c646de114 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Thu, 28 Apr 2022 17:30:49 -0300 Subject: [PATCH 085/108] refact (post-review): Remove logger methods made unnecessarily verbose by latest changes --- lib/pipefy_message/logging.rb | 39 ++++++++--------------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index 9455986..e5f5f78 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -18,8 +18,8 @@ module Logging ## # Creates a logger object if it has not yet been instantiated, # or returns the existing object. - def self.logger - @logger ||= logger_setup + def logger + @logger ||= Logging.logger_setup end ## @@ -30,37 +30,16 @@ def self.logger_setup level ||= loglevels.index ENV.fetch("ASYNC_LOG_LEVEL", "INFO") level ||= Logger::ERROR logger.level = level - logger - end - - ## - # Formats logger output as a JSON object, including information on - # the calling object. Should not be called directly; this method is - # called implicitly whenever a logger method is called. - def self.json_output(_obj, severity, datetime, progname, msg) - { date: datetime.to_s, - level: severity.to_s, - app: progname.to_s, - context: "async_processing", - message: msg } - end - ## - # Logger method available to all instances of classes - # that include the Logging module (as an instance method). - # Includes information on the calling object. - def logger - Logging.logger.formatter = proc do |severity, datetime, progname, msg| - json_hash = Logging.json_output(self, severity, datetime, progname, msg) - - # The necessity of explicitly JSON.dumping this hash has been - # discussed in a code review. We should check how these logs - # will be processed on the other end and perhaps refactor - # accordingly. - JSON.dump(json_hash) + ($INPUT_RECORD_SEPARATOR || "\n") + logger.formatter = proc do |severity, datetime, progname, msg| + { date: datetime.to_s, + level: severity.to_s, + app: progname.to_s, + context: "async_processing", + message: msg }.to_json + $INPUT_RECORD_SEPARATOR.to_s end - Logging.logger + logger end ## From 92dd3cc88a42fa229d18ec0a0a74626b59a681a8 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Thu, 28 Apr 2022 17:33:52 -0300 Subject: [PATCH 086/108] refact: Update log message to reflect ms time unit --- lib/pipefy_message/worker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 7f8a69d..52f0733 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -109,10 +109,10 @@ def process_message ensure elapsed_time = (Time.now - start) * 1000.0 logger.info({ - duration_seconds: elapsed_time, + duration_ms: elapsed_time, message_text: "Message received by #{broker} poller processed by #{name} worker in - #{elapsed_time} seconds" + #{elapsed_time} milliseconds" }) end end From 7badbfe63a344a544361f72595e2444f1db83e77 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Thu, 28 Apr 2022 18:43:27 -0300 Subject: [PATCH 087/108] refact: [WIP?] Improve separation of AWS-wide and service-specific configurations This includes no longer storing or logging AWS credentials --- .../providers/aws_client/aws_broker.rb | 11 ++++++++--- .../providers/aws_client/sns_broker.rb | 13 +++++++++++++ .../providers/aws_client/sqs_broker.rb | 9 ++++++--- lib/pipefy_message/worker.rb | 7 +++---- spec/providers/aws/sqs_broker_spec.rb | 4 ++-- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index 8fb839d..a4ded21 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -2,6 +2,7 @@ require "aws-sdk-sqs" require "json" +require "active_support/core_ext" # Hash#except module PipefyMessage module Providers @@ -11,9 +12,13 @@ module AwsClient # AWS option parsing and connection setup. class AwsBroker < PipefyMessage::Providers::Broker def initialize(opts = {}) - @config = build_options(opts) - Aws.config.update(@config[:aws]) - logger.debug({ options_set: @config, message_text: "AWS connection set up with options_set" }) + config = build_options(opts) + Aws.config.update(config[:aws]) + + @config = config.except(:aws).except(:broker) + # Stores any child class-specific option passed, + # but not the AWS connection configuration (most + # importantly: not the secret key). end ## diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 6ff6481..299a91c 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -6,6 +6,7 @@ module PipefyMessage module Providers module AwsClient + ## # AWS SNS client. class SnsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config @@ -14,12 +15,24 @@ def initialize(opts = {}) super(opts) @sns = Aws::SNS::Resource.new + logger.debug({ message_text: "SNS resource created" }) + @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] @is_staging = ENV["ASYNC_APP_ENV"] == "staging" rescue StandardError raise PipefyMessage::Providers::Errors::ResourceError, e.message end + ## + # Extends AWS default options to include a value + # for SNS-specific configurations. + def default_options + aws_defaults = super + aws_defaults[:default_arn_prefix] = "arn:aws:sns:us-east-1:000000000000" + + aws_defaults + end + ## # Publishes a message with the given payload to the SNS topic # with topic_name. diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index dc2699f..1766c2a 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -10,11 +10,13 @@ module AwsClient class SqsBroker < PipefyMessage::Providers::AwsClient::AwsBroker attr_reader :config - def initialize(queue_name, opts = {}) + def initialize(opts = {}) super(opts) @sqs = Aws::SQS::Client.new - queue_url = @sqs.get_queue_url({ queue_name: queue_name }).queue_url + logger.debug({ message_text: "SQS client created" }) + + queue_url = @sqs.get_queue_url({ queue_name: @config[:queue_name] }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) rescue StandardError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message @@ -22,10 +24,11 @@ def initialize(queue_name, opts = {}) ## # Extends AWS default options to include a value - # for queue poller wait times. + # for SQS-specific configurations. def default_options aws_defaults = super aws_defaults[:wait_time_seconds] = 10 + aws_defaults[:queue_name] = "my_queue" aws_defaults end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 52f0733..807ecdc 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -18,8 +18,7 @@ module Worker # Default options for consumer setup. def self.default_worker_options @default_worker_options ||= { - "broker" => "aws", - "queue_name" => "my_queue" + broker: "aws" } end @@ -48,7 +47,7 @@ module ClassMethods # Merges default worker options with the hash passed as # an argument. The latter takes precedence. def pipefymessage_options(opts = {}) - @options_hash = Worker.default_worker_options.merge(opts.transform_keys(&:to_s)) + @options_hash = Worker.default_worker_options.merge(opts) @options_hash.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) @@ -80,7 +79,7 @@ def build_instance_broker message_text: "Initializing and returning instance of #{broker} broker" }) - consumer_map[:class_name].constantize.new(queue_name, @options_hash) + consumer_map[:class_name].constantize.new(@options_hash) end ## diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index 2b5da1e..416c06a 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -32,7 +32,7 @@ end it "should consume message" do - worker = described_class.new("my_queue") + worker = described_class.new worker.instance_variable_set(:@poller, mocked_poller) result = nil @@ -57,7 +57,7 @@ ) expect do - described_class.new("my_queue") + described_class.new end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, /The specified queue my_queue does not exist for this wsdl version/) end From 9d0822425a67aa7bd579c5a80ebf0bc5d3962766 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 11:28:13 -0300 Subject: [PATCH 088/108] refact: Wait longer for localstack init (10 s wasn't enough on my machine) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2809acb..b1b4c6c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: command: > " # Wait for localstack boot - sleep 10 + sleep 30 # Creating SNS Topics aws --endpoint-url=http://localstack:4566 sns create-topic --name pipefy-local-topic From 0da3c7f345a6a0e890075fe0c19e0d0d187c4057 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 11:28:39 -0300 Subject: [PATCH 089/108] refact: Add queue name to logs if not already present --- .../providers/aws_client/sqs_broker.rb | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 1766c2a..25d0201 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -28,7 +28,7 @@ def initialize(opts = {}) def default_options aws_defaults = super aws_defaults[:wait_time_seconds] = 10 - aws_defaults[:queue_name] = "my_queue" + aws_defaults[:queue_name] = "pipefy-local-queue" aws_defaults end @@ -37,14 +37,25 @@ def default_options # Initiates SQS queue polling, with wait_time_seconds as given # in the initial configuration. def poller - logger.debug({ message_text: "Initiating SQS polling..." }) + logger.debug(build_log_hash("Initiating SQS polling on queue #{@config[:queue_name]}")) @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| - logger.debug({ message_text: "Message received by SQS poller" }) + logger.debug(build_log_hash("Message received by SQS poller on queue #{@config[:queue_name]}")) + payload = JSON.parse(received_message.body) yield(payload) end end + + ## + # Adds the queue name to logs, if not already present. + def build_log_hash(arg) + if arg.instance_of? Hash + { queue_name: @config[:queue_name] }.merge(arg) + else + { queue_name: @config[:queue_name], message_text: arg } + end + end end end end From 52985823a54d54223f3c6b6bfc6d5b30a64d91f5 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 11:29:19 -0300 Subject: [PATCH 090/108] refact: Reword/break lines --- lib/pipefy_message/publisher.rb | 2 +- lib/pipefy_message/worker.rb | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index 0fe4aba..13a9dd9 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -34,7 +34,7 @@ def publisher_instance logger.info({ broker: @broker, - message_text: "Initializing and returning instance of #{@broker} broker" + message_text: "Initializing instance of #{@broker} broker" }) publisher_map[:class_name].constantize.new diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 807ecdc..13706c8 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -76,7 +76,7 @@ def build_instance_broker logger.info({ broker: broker, - message_text: "Initializing and returning instance of #{broker} broker" + message_text: "Initializing instance of #{broker} broker" }) consumer_map[:class_name].constantize.new(@options_hash) @@ -97,8 +97,7 @@ def process_message build_instance_broker.poller do |message| logger.info({ - message_text: "Message received by #{broker} - poller to be processed by worker", + message_text: "Message received by #{broker} poller to be processed by worker", received_message: message }) obj.perform(message) @@ -109,9 +108,9 @@ def process_message elapsed_time = (Time.now - start) * 1000.0 logger.info({ duration_ms: elapsed_time, - message_text: "Message received by #{broker} - poller processed by #{name} worker in - #{elapsed_time} milliseconds" + message_text: "Message received by #{broker}" \ + "poller processed by #{name} worker" \ + "in #{elapsed_time} milliseconds" }) end end From 02d097d276e1f9c638d68e921c6a20ddefe58dcd Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 12:21:48 -0300 Subject: [PATCH 091/108] refact: Expand AWS SQS, SNS CLI readme --- .../providers/aws_client/README.md | 106 ++++++++++++++---- 1 file changed, 87 insertions(+), 19 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/README.md b/lib/pipefy_message/providers/aws_client/README.md index 6c325f0..6a1e504 100644 --- a/lib/pipefy_message/providers/aws_client/README.md +++ b/lib/pipefy_message/providers/aws_client/README.md @@ -1,23 +1,45 @@ -# AWS SNS & SQS User Guide +# User guide: AWS SNS & SQS with localstack and aws-cli -## Basics +## Basic concepts **SNS**: Amazon Simple Notification Service (SNS) is a fully managed pub/sub messaging, SMS, email, and mobile push notifications. More details at the [official website](https://aws.amazon.com/sns/). **SQS**: Amazon Simple Queue Service (SQS) is a fully managed message queuing service that enables you to decouple and scale microservices, distributed systems, and serverless applications. More details at the [official website](https://aws.amazon.com/sqs/). -### Aws-cli Installation +**localstack**: Localstack provides a local, containerized implementation of cloud services for testing and development. More details at the [official website](https://localstack.cloud/). + +**aws-cli**: The official command-line interface for AWS. More details at the [official website](https://docs.aws.amazon.com/cli/index.html). -Before proceeding to the next step it's required to have the aws-cli installed in your machine, for that follow [this guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). +## Setup +This guide will aid you in setting up a local "instance" of the SQS and SNS services inside a Docker container using localstack. Real-world AWS credentials are not required. +### Aws-cli Installation +To follow the instructions in the next sections, you will still need to install the aws-cli in your machine. In order to do so, please refer to [the official installation guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). > **Note that we must have the project docker-compose localstack service running before executing the commands above** -### Creating new SNS Topic -On your terminal run: +### Creating localstack container +By running +```bash +docker-compose up localstack -d +``` +you will start a container running localstack, whose ports 4566 and 4571 are mapped to the ports with the same numbers in the host machine. You will then be able to use `http://localhost:4566` to access the local AWS "clone" provided by localstack. + +**Troubleshooting note:** this command might fail if those ports are currently in use. In that case, you could manually create a container based on the `localstack/localstack-light:0.11.5` image, mapping the ports above to any other that are available on your machine. If you do so, remember to change the ports referred to by the commands below accordingly. + +## Using SQS and SNS with localstack +This section contains a brief outline on how to use basic commands that might be useful for testing a localstack setup. Please refer to the [official AWS CLI documentation](https://docs.aws.amazon.com/cli/index.html) for more information. + +The alphanumeric strings on the expected outputs listed below might be different from the ones you see. +### Creating a new SNS topic + +On your terminal, run: ```bash aws --endpoint-url=http://localhost:4566 sns create-topic --name pipe-events-topic ``` +**Parameters:** +* `name` is the final topic name that will be used on localstack; the value `pipe-events-topic` can be changed for any other name. + The expected output is: ```bash { @@ -25,16 +47,16 @@ The expected output is: } ``` -Parameters: -* **name** is the final topic name that will be used at localstack (can be changed for any name). - -### Creating new SQS Queue +### Creating a new SQS queue -On your terminal run: +On your terminal, run: ```bash aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name pipe-events-queue ``` +**Parameters:** +* `name` is the final queue name that will be used on localstack; the value `pipe-events-queue` can be changed for any other name. + The expected output is: ```bash { @@ -42,22 +64,68 @@ The expected output is: } ``` -Parameters: -* **queue-name** is the final queue name that will be used at localstack (can be changed for any name). +### Subscribing an SQS queue to an SNS topic -### Creating a new Subscription from SQS to SNS topic - -On your terminal run: +On your terminal, run: ```bash aws --endpoint-url=http://localhost:4566 sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:pipe-events-topic --protocol sqs --notification-endpoint http://localhost:4566/000000000000/pipe-events-queue ``` +**Parameters:** +* `topic-arn` is the output of the topic creation step. +* `notification-endpoint` is the output of the queue creation step. + The expected output is: ```bash { "SubscriptionArn": "arn:aws:sns:us-east-1:000000000000:pipe-events-topic:d5a44fe9-267b-4d10-b293-9b7f75f2ca09" } ``` -Parameters: -* **topic-arn** is the output of the topic creation. -* **notification-endpoint** is the output of the queue creation. + +### Publishing to an SNS topic +On your terminal, run: + +```bash +aws --endpoint-url=http://localhost:4566 sns publish --topic-arn arn:aws:sns:us-east-1:000000000000:pipe-events-topic --message "hello world" +``` +**Parameters:** +* `topic-arn` is the output of the topic creation step. +* `message` is a string containing the message payload. + +The expected output is: +```bash +{ + "MessageId": "baa71695-138d-4fd5-af6b-0d4da01cfa96" +} +``` + +### Receiving a message from an SQS queue +On your terminal, run: + +```bash +aws --endpoint-url=http://localhost:4566 sqs receive-message --queue-url http://localhost:4566/000000000000/pipe-events-queue +``` +**Parameters:** +* `queue-url` is the output of the topic creation step. + +The expected output is: +```bash +{ + "Messages": [ + { + "MessageId": "9c9ef40f-627b-bc26-845c-7ed76336fa87", + "ReceiptHandle": "flhheyojgccjatkhvycjssradvtplujdnbmrubjmfolsdnxzrezbwjfjnstzzmkudsoiyejzlqblcxvyxyebngtvzrppjymuukrwwfzebxkpbptqhulwtiiyousjvqmjqvwgdojhyfeiugqudbgmqgpmogtpoajmncnnpmwlybvylzlmdqvdkqosu", + "MD5OfBody": "aa79e44b37436d5d152323b3a3639440", + "Body": "{\"Type\": \"Notification\", \"MessageId\": \"baa71695-138d-4fd5-af6b-0d4da01cfa96\", \"Token\": null, \"TopicArn\": \"arn:aws:sns:us-east-1:000000000000:pipefy-local-topic\", \"Message\": \"hello world\", \"SubscribeURL\": null, \"Timestamp\": \"2022-04-29T15:09:53.968Z\", \"SignatureVersion\": \"1\", \"Signature\": \"EXAMPLEpH+..\", \"SigningCertURL\": \"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem\"}", + "Attributes": { + "SenderId": "AIDAIT2UOQQY3AUEKVGXU", + "SentTimestamp": "1651244993976", + "ApproximateReceiveCount": "1", + "ApproximateFirstReceiveTimestamp": "1651245077666" + } + } + ] +} +``` + +Note that, since our queue is subscribed to our topic, the message we received above was the one sent in the previous step (`hello world`). \ No newline at end of file From 4faaf42f2059a3c98337729300326c608f18bb92 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 17:29:19 -0300 Subject: [PATCH 092/108] refact/fix: Alter test to reflect new default arg value --- .gitignore | 4 +--- spec/providers/aws/sqs_broker_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 68201e1..715c5eb 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,4 @@ build-iPhoneSimulator/ # .rubocop-https?--* .idea -.rspec_status - -/log/ \ No newline at end of file +.rspec_status \ No newline at end of file diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index 416c06a..5de7536 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -48,7 +48,7 @@ it "QueueNonExistError" do allow_any_instance_of(Aws::SQS::Client) .to receive(:get_queue_url) - .with({ queue_name: "my_queue" }) + .with({ queue_name: "pipefy-local-queue" }) .and_raise( Aws::SQS::Errors::NonExistentQueue.new( double(Aws::SQS::Client), @@ -64,7 +64,7 @@ it "NetworkingError" do allow_any_instance_of(Aws::SQS::Client) .to receive(:get_queue_url) - .with({ queue_name: "my_queue" }) + .with({ queue_name: "pipefy-local-queue" }) .and_raise( Seahorse::Client::NetworkingError.new( Errno::ECONNREFUSED.new(""), From 9cc0163a8990be5943199dd47327dcb2cb613a6e Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Fri, 29 Apr 2022 17:51:13 -0300 Subject: [PATCH 093/108] refact: Address some minor issues from the PR review Sorry, this was my first time involved in a PR, I'd forgotten to keep track of the comments hahaha That's why I didn't do this earlier, thanks for your great reviews! :D --- lib/pipefy_message/logging.rb | 24 ++++++------- .../providers/aws_client/aws_broker.rb | 13 +++---- .../providers/aws_client/sns_broker.rb | 5 +-- .../providers/aws_client/sqs_broker.rb | 6 +--- lib/pipefy_message/providers/broker.rb | 36 ++++++++++++++----- 5 files changed, 47 insertions(+), 37 deletions(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index e5f5f78..f4bc230 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -15,6 +15,8 @@ module PipefyMessage # # (see https://stackoverflow.com/questions/917566/ruby-share-logger-instance-among-module-classes) module Logging + LOG_LEVELS = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze + ## # Creates a logger object if it has not yet been instantiated, # or returns the existing object. @@ -25,21 +27,17 @@ def logger ## # Configuration for a logger created by the Ruby logger gem. def self.logger_setup - loglevels = %w[DEBUG INFO WARN ERROR FATAL UNKNOWN].freeze - logger = Logger.new($stdout) - level ||= loglevels.index ENV.fetch("ASYNC_LOG_LEVEL", "INFO") - level ||= Logger::ERROR - logger.level = level + Logger.new($stdout).tap do |logger| + logger.level = LOG_LEVELS.index(ENV.fetch("ASYNC_LOG_LEVEL", "INFO")) || Logger::ERROR - logger.formatter = proc do |severity, datetime, progname, msg| - { date: datetime.to_s, - level: severity.to_s, - app: progname.to_s, - context: "async_processing", - message: msg }.to_json + $INPUT_RECORD_SEPARATOR.to_s + logger.formatter = proc do |severity, datetime, progname, msg| + { date: datetime.to_s, + level: severity.to_s, + app: progname.to_s, + context: "async_processing", + message: msg }.to_json + $INPUT_RECORD_SEPARATOR.to_s + end end - - logger end ## diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index a4ded21..f21e1ea 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -26,11 +26,11 @@ def initialize(opts = {}) # configuration if no overriding parameters are provided. def default_options { - access_key_id: (ENV["AWS_ACCESS_KEY_ID"] || "foo"), - secret_access_key: (ENV["AWS_SECRET_ACCESS_KEY"] || "bar"), - endpoint: (ENV["AWS_ENDPOINT"] || "http://localhost:4566"), - region: (ENV["AWS_REGION"] || "us-east-1"), - stub_responses: (ENV["AWS_CLI_STUB_RESPONSE"] == "true") + access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), + secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), + endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), + region: ENV.fetch("AWS_REGION", "us-east-1"), + stub_responses: ENV.fetch("AWS_CLI_STUB_RESPONSE", "true") } end @@ -57,7 +57,8 @@ def build_options(opts) private ## - # AWS option hash parser. + # Extracts AWS-level options from a hash that may also contain + # configurations related to child class-specific services. def isolate_broker_arguments(hash) { access_key_id: hash[:access_key_id], diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 299a91c..15a70d1 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -27,10 +27,7 @@ def initialize(opts = {}) # Extends AWS default options to include a value # for SNS-specific configurations. def default_options - aws_defaults = super - aws_defaults[:default_arn_prefix] = "arn:aws:sns:us-east-1:000000000000" - - aws_defaults + super.merge(default_arn_prefix: "arn:aws:sns:us-east-1:000000000000") end ## diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 25d0201..a1a8d19 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -26,11 +26,7 @@ def initialize(opts = {}) # Extends AWS default options to include a value # for SQS-specific configurations. def default_options - aws_defaults = super - aws_defaults[:wait_time_seconds] = 10 - aws_defaults[:queue_name] = "pipefy-local-queue" - - aws_defaults + super.merge(wait_time_seconds: 10, queue_name: "pipefy-local-queue") end ## diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb index 9c1912b..fe82da1 100644 --- a/lib/pipefy_message/providers/broker.rb +++ b/lib/pipefy_message/providers/broker.rb @@ -2,26 +2,44 @@ module PipefyMessage module Providers + ## # Provides a provider-agnostic, higher level abstraction for - # objects that provide pollers. Should be included in classes + # provider clients and resources. Should be included in classes # implemented for specific providers. Used by the Worker module. class Broker include PipefyMessage::Logging include PipefyMessage::Providers::Errors - def poller - error_msg = includer_should_implement(__method__) - raise NotImplementedError, error_msg - end + # def poller + # error_msg = includer_should_implement(__method__) + # raise NotImplementedError, error_msg + # end + + # def publish(_payload, _topic_name) + # error_msg = includer_should_implement(__method__) + # raise NotImplementedError, error_msg + # end - def publish(_payload, _topic_name) + # I actually believe these "abstract methods" shouldn't + # exist at this level, since they are only implemented by + # the "grandchildren" of this class, rather than its children. + # Unless the idea is to allow calling the grandchildren classes + # connected to each service from the child class itself (the one) + # connected to the provider -- eg: fire up SQS polling by calling + # aws_broker.poller directly, rather than instantiating an SQS- + # specific class. Thoughts? Comments? + + def default_options error_msg = includer_should_implement(__method__) raise NotImplementedError, error_msg end - def default_options - {} - end + # At the current point, as we're working solely with AWS, it's + # hard to know what this abstract class should or shouldn't have + # -- at least for me, since I don't know how this would work for + # other providers' SDKs. This class is mostly empty at the moment, + # but shared functionality should be abstracted away into it as + # other providers are introduced. end end end From bd1af3a7ece9d4830019accef608095b48cdadf4 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 2 May 2022 17:44:18 -0300 Subject: [PATCH 094/108] Remove AwsBroker -> {Sns, Sqs}Broker inheritance --- .../providers/aws_client/aws_broker.rb | 79 ++++++------------- .../providers/aws_client/sns_broker.rb | 11 ++- .../providers/aws_client/sqs_broker.rb | 11 ++- 3 files changed, 38 insertions(+), 63 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_broker.rb index f21e1ea..741ff58 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_broker.rb @@ -6,68 +6,33 @@ module PipefyMessage module Providers + ## + # Provides an AWS connection configuration "lazy loader" and classes + # for AWS service brokers. module AwsClient - ## - # "Abstract" superclass for brokers of AWS services, implementing - # AWS option parsing and connection setup. - class AwsBroker < PipefyMessage::Providers::Broker - def initialize(opts = {}) - config = build_options(opts) - Aws.config.update(config[:aws]) - - @config = config.except(:aws).except(:broker) - # Stores any child class-specific option passed, - # but not the AWS connection configuration (most - # importantly: not the secret key). - end - - ## - # Hash with default options to be used in AWS access - # configuration if no overriding parameters are provided. - def default_options - { - access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), - secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), - endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), - region: ENV.fetch("AWS_REGION", "us-east-1"), - stub_responses: ENV.fetch("AWS_CLI_STUB_RESPONSE", "true") - } - end - - ## - # Moves AWS configuration options into a subhash, separate - # from any other options passed to child classes. Also - # merges default options (returned by default_options) with the - # hash provided as an argument; the latter takes precedence. - def build_options(opts) - hash = default_options.merge(opts) - aws_hash = isolate_broker_arguments(hash) + include PipefyMessage::Logging - config_hash = { - aws: aws_hash - } - - hash.each do |k, v| - config_hash[k] = v unless aws_hash.key?(k) - end + ## + # Hash that fetches AWS options from environment variables or + # sets them to default values. + def self.set_options + { + access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), + secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), + endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), + region: ENV.fetch("AWS_REGION", "us-east-1"), + stub_responses: ENV.fetch("AWS_CLI_STUB_RESPONSE", "true") + } + end - config_hash - end + ## + # Sets up AWS options the first time an AWS service is used. + def self.aws_setup + return unless Aws.config.empty? - private + logger.info({ message_text: "AWS configurations set" }) - ## - # Extracts AWS-level options from a hash that may also contain - # configurations related to child class-specific services. - def isolate_broker_arguments(hash) - { - access_key_id: hash[:access_key_id], - secret_access_key: hash[:secret_access_key], - endpoint: hash[:endpoint], - region: hash[:region], - stub_responses: hash[:stub_responses] - } - end + Aws.config.update(set_options) end end end diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 15a70d1..c79cc17 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -8,11 +8,16 @@ module Providers module AwsClient ## # AWS SNS client. - class SnsBroker < PipefyMessage::Providers::AwsClient::AwsBroker + class SnsBroker + include PipefyMessage::Logging + include PipefyMessage::Providers::Errors + attr_reader :config def initialize(opts = {}) - super(opts) + @config = default_options.merge(opts) + + AwsClient.aws_setup @sns = Aws::SNS::Resource.new logger.debug({ message_text: "SNS resource created" }) @@ -27,7 +32,7 @@ def initialize(opts = {}) # Extends AWS default options to include a value # for SNS-specific configurations. def default_options - super.merge(default_arn_prefix: "arn:aws:sns:us-east-1:000000000000") + { default_arn_prefix: "arn:aws:sns:us-east-1:000000000000" } end ## diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index a1a8d19..91e91b4 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -7,11 +7,16 @@ module Providers module AwsClient ## # AWS SQS client. - class SqsBroker < PipefyMessage::Providers::AwsClient::AwsBroker + class SqsBroker + include PipefyMessage::Logging + include PipefyMessage::Providers::Errors + attr_reader :config def initialize(opts = {}) - super(opts) + @config = default_options.merge(opts) + + AwsClient.aws_setup @sqs = Aws::SQS::Client.new logger.debug({ message_text: "SQS client created" }) @@ -26,7 +31,7 @@ def initialize(opts = {}) # Extends AWS default options to include a value # for SQS-specific configurations. def default_options - super.merge(wait_time_seconds: 10, queue_name: "pipefy-local-queue") + { wait_time_seconds: 10, queue_name: "pipefy-local-queue" } end ## From 2be266a334438c1b47d54f925595eef6329f2e63 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 2 May 2022 17:49:46 -0300 Subject: [PATCH 095/108] refact: Remove currently unused Broker class We can always add abstractions once we have more brokers, but I don't think there was enough shared behavior between classes to justify having that "interface" now --- lib/pipefy_message/providers/broker.rb | 45 -------------------------- 1 file changed, 45 deletions(-) delete mode 100644 lib/pipefy_message/providers/broker.rb diff --git a/lib/pipefy_message/providers/broker.rb b/lib/pipefy_message/providers/broker.rb deleted file mode 100644 index fe82da1..0000000 --- a/lib/pipefy_message/providers/broker.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module PipefyMessage - module Providers - ## - # Provides a provider-agnostic, higher level abstraction for - # provider clients and resources. Should be included in classes - # implemented for specific providers. Used by the Worker module. - class Broker - include PipefyMessage::Logging - include PipefyMessage::Providers::Errors - - # def poller - # error_msg = includer_should_implement(__method__) - # raise NotImplementedError, error_msg - # end - - # def publish(_payload, _topic_name) - # error_msg = includer_should_implement(__method__) - # raise NotImplementedError, error_msg - # end - - # I actually believe these "abstract methods" shouldn't - # exist at this level, since they are only implemented by - # the "grandchildren" of this class, rather than its children. - # Unless the idea is to allow calling the grandchildren classes - # connected to each service from the child class itself (the one) - # connected to the provider -- eg: fire up SQS polling by calling - # aws_broker.poller directly, rather than instantiating an SQS- - # specific class. Thoughts? Comments? - - def default_options - error_msg = includer_should_implement(__method__) - raise NotImplementedError, error_msg - end - - # At the current point, as we're working solely with AWS, it's - # hard to know what this abstract class should or shouldn't have - # -- at least for me, since I don't know how this would work for - # other providers' SDKs. This class is mostly empty at the moment, - # but shared functionality should be abstracted away into it as - # other providers are introduced. - end - end -end From d63ca52f22dcbd0ce4d24aece931b059c4e91edd Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 2 May 2022 17:50:17 -0300 Subject: [PATCH 096/108] (forgot to include all files in the last commit, then pushed :P) --- lib/pipefy_message.rb | 1 - spec/worker_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 67a9dac..4e06beb 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -4,7 +4,6 @@ require_relative "pipefy_message/logging" require_relative "pipefy_message/publisher" require_relative "pipefy_message/worker" -require_relative "pipefy_message/providers/broker" require_relative "pipefy_message/providers/errors" require "logger" diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 16253f9..ad06dac 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -2,13 +2,13 @@ $result =~ nil -class MockBroker < PipefyMessage::Providers::Broker +class MockBroker def poller yield("test") end end -class MockBrokerFail < PipefyMessage::Providers::Broker +class MockBrokerFail def poller raise PipefyMessage::Providers::Errors::ResourceError end From c53ff12a1f9ee91a1c0d782ebebd33aaed984568 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Mon, 2 May 2022 18:16:31 -0300 Subject: [PATCH 097/108] feat/refact?: Allow setting broker options from Worker module and Publisher class --- lib/pipefy_message/publisher.rb | 7 ++++--- lib/pipefy_message/worker.rb | 21 ++++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index 13a9dd9..82962cf 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -7,8 +7,9 @@ module PipefyMessage class Publisher include PipefyMessage::Logging - def initialize(broker = "aws") + def initialize(broker = "aws", broker_opts = {}) @broker = broker + @broker_opts = broker_opts end def publish(message, topic) @@ -34,10 +35,10 @@ def publisher_instance logger.info({ broker: @broker, - message_text: "Initializing instance of #{@broker} broker" + message_text: "Initializing instance of #{@broker} publisher" }) - publisher_map[:class_name].constantize.new + publisher_map[:class_name].constantize.new(@broker_opts) end end end diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 13706c8..015e907 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -47,22 +47,33 @@ module ClassMethods # Merges default worker options with the hash passed as # an argument. The latter takes precedence. def pipefymessage_options(opts = {}) - @options_hash = Worker.default_worker_options.merge(opts) - @options_hash.each do |k, v| + @pipefymessage_options = Worker.default_worker_options.merge(opts) + @pipefymessage_options.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end logger.debug({ - options_set: @options_hash, + options_set: @pipefymessage_options, message_text: "Set #{name} options to options_set" }) end + ## + # Sets broker-specific options to be passed to the broker's + # constructor. + def pipefymessage_broker_options(opts = {}) + @pipefymessage_broker_opts = opts + end + ## # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_instance_broker + pipefymessage_options if @broker.nil? + + pipefymessage_broker_options if @pipefymessage_broker_opts.nil? + provider_map = PipefyMessage::Providers::BrokerResolver.class_path[broker.to_sym] if provider_map.nil? @@ -76,10 +87,10 @@ def build_instance_broker logger.info({ broker: broker, - message_text: "Initializing instance of #{broker} broker" + message_text: "Initializing instance of #{broker} consumer" }) - consumer_map[:class_name].constantize.new(@options_hash) + consumer_map[:class_name].constantize.new(@pipefymessage_broker_opts) end ## From 253dd05e31f23c598f221a7979aa7d75baca68b5 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 3 May 2022 14:16:43 -0300 Subject: [PATCH 098/108] Add and improve RSpec tests --- .rubocop.yml | 8 -- .../{aws_broker.rb => aws_client.rb} | 1 - .../providers/aws_client/sns_broker.rb | 8 +- .../providers/aws_client/sqs_broker.rb | 4 +- lib/pipefy_message/worker.rb | 1 - spec/logger_spec.rb | 4 +- spec/providers/aws/aws_client_spec.rb | 53 ++++++++ spec/providers/aws/aws_stub_context.rb | 7 + spec/providers/aws/sns_broker_spec.rb | 53 ++++++-- spec/providers/aws/sqs_broker_spec.rb | 125 +++++++++--------- spec/publisher_spec.rb | 49 +++++-- spec/worker_spec.rb | 14 +- 12 files changed, 221 insertions(+), 106 deletions(-) rename lib/pipefy_message/providers/aws_client/{aws_broker.rb => aws_client.rb} (95%) create mode 100644 spec/providers/aws/aws_client_spec.rb create mode 100644 spec/providers/aws/aws_stub_context.rb diff --git a/.rubocop.yml b/.rubocop.yml index 78742f3..bde3979 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,14 +16,6 @@ Layout/LineLength: Metrics/BlockLength: IgnoredMethods: ["describe", "context"] -Style/GlobalVars: - Exclude: - - "spec/worker_spec.rb" - # The introduction of a dummy global variable greatly simplified - # testing in this case, as it made reassignment between scopes much - # more straightforward. I didn't consider that particularly harmful. - # Thoughts? - Lint/MissingSuper: Enabled: false diff --git a/lib/pipefy_message/providers/aws_client/aws_broker.rb b/lib/pipefy_message/providers/aws_client/aws_client.rb similarity index 95% rename from lib/pipefy_message/providers/aws_client/aws_broker.rb rename to lib/pipefy_message/providers/aws_client/aws_client.rb index 741ff58..0d8f384 100644 --- a/lib/pipefy_message/providers/aws_client/aws_broker.rb +++ b/lib/pipefy_message/providers/aws_client/aws_client.rb @@ -2,7 +2,6 @@ require "aws-sdk-sqs" require "json" -require "active_support/core_ext" # Hash#except module PipefyMessage module Providers diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index c79cc17..5bd7a22 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "aws-sdk-sns" -require_relative "aws_broker" +require_relative "aws_client" module PipefyMessage module Providers @@ -12,7 +12,7 @@ class SnsBroker include PipefyMessage::Logging include PipefyMessage::Providers::Errors - attr_reader :config + attr_reader :config, :topic_arn_prefix def initialize(opts = {}) @config = default_options.merge(opts) @@ -40,9 +40,11 @@ def default_options # with topic_name. def publish(payload, topic_name) message = prepare_payload(payload) - topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) + topic_arn = @topic_arn_prefix + ":" + (@is_staging ? "#{topic_name}-staging" : topic_name) topic = @sns.topic(topic_arn) + # require "pry"; binding.pry + logger.info( { topic_arn: topic_arn, payload: payload, diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 91e91b4..13f2485 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "aws_broker" +require_relative "aws_client" module PipefyMessage module Providers @@ -18,6 +18,8 @@ def initialize(opts = {}) AwsClient.aws_setup + # require "pry"; binding.pry + @sqs = Aws::SQS::Client.new logger.debug({ message_text: "SQS client created" }) diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/worker.rb index 015e907..b49469f 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/worker.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "singleton" require "benchmark" require_relative "providers/errors" require_relative "providers/broker_resolver" diff --git a/spec/logger_spec.rb b/spec/logger_spec.rb index 2539ca5..62705fe 100644 --- a/spec/logger_spec.rb +++ b/spec/logger_spec.rb @@ -5,8 +5,8 @@ class LoggerTester end # Tbh, I dunno how to make these "real" tests; this is essentially -# a huge makefile for automating manually checked tests atm :P But -# it's better than nothing nor now. +# a makefile for automating manually checked tests atm :P But it's +# better than nothing nor now. RSpec.describe LoggerTester do it "is available as an instance method" do subject.logger.error("Instance logger error") diff --git a/spec/providers/aws/aws_client_spec.rb b/spec/providers/aws/aws_client_spec.rb new file mode 100644 index 0000000..ecf1848 --- /dev/null +++ b/spec/providers/aws/aws_client_spec.rb @@ -0,0 +1,53 @@ +require "aws-sdk-sqs" + +require_relative "../../../lib/pipefy_message/providers/aws_client/aws_client" + +class TestClient + include PipefyMessage::Providers::AwsClient + + def initialize + PipefyMessage::Providers::AwsClient.aws_setup + end +end + +RSpec.describe PipefyMessage::Providers::AwsClient do + describe "#initialize" do + let(:aws_opts) do + { + access_key_id: "changing-some-default-values", # changed + secret_access_key: "so-we-know-it-worked", # changed + endpoint: "http://localhost:4566", # default (not set) + region: "us-east-1", # default (not set) + stub_responses: "true" # default (not set) + } + end + + before do + changed_opts = { + "AWS_ACCESS_KEY_ID" => aws_opts[:access_key_id], + "AWS_SECRET_ACCESS_KEY" => aws_opts[:secret_access_key] + } + + stub_const("ENV", ENV.to_hash.merge(changed_opts)) + end + + after do + Aws.config = {} # undoing changes + # (to avoid test "cross-contamination") + end + + it "should read and set configurations from env vars" do + TestClient.new + expect(Aws.config).to eq aws_opts + end + + it "should not set configurations more than once" do + TestClient.new + + ENV["AWS_CLI_STUB_RESPONSE"] = "false" + TestClient.new + + expect(Aws.config).to eq aws_opts + end + end +end diff --git a/spec/providers/aws/aws_stub_context.rb b/spec/providers/aws/aws_stub_context.rb new file mode 100644 index 0000000..b407310 --- /dev/null +++ b/spec/providers/aws/aws_stub_context.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.shared_context "AWS stub" do + before(:suite) do + stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) + end +end diff --git a/spec/providers/aws/sns_broker_spec.rb b/spec/providers/aws/sns_broker_spec.rb index 8da9f28..01e99b5 100644 --- a/spec/providers/aws/sns_broker_spec.rb +++ b/spec/providers/aws/sns_broker_spec.rb @@ -1,23 +1,54 @@ # frozen_string_literal: true require_relative "../../../lib/pipefy_message/providers/aws_client/sns_broker" +require_relative "aws_stub_context" RSpec.describe PipefyMessage::Providers::AwsClient::SnsBroker do - context "when I try to publish a message with Sns publisher" do - before(:each) do - stub_const("ENV", ENV.to_hash.merge("AWS_ENDPOINT" => "http://localhost:4566")) - stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) + describe "#initialize" do + let(:prefix) { "test" } + let(:env_prefix) { "env" } + + it "should set the default ARN prefix from a hash arg" do + sns_broker = described_class.new({ default_arn_prefix: prefix }) + default_prefix = sns_broker.config[:default_arn_prefix] + + expect(default_prefix).to eq prefix + expect(sns_broker.topic_arn_prefix).to eq default_prefix + end + + it "should have a default default ARN prefix" do + sns_broker = described_class.new + + default_prefix = sns_broker.config[:default_arn_prefix] + + expect(default_prefix).to_not eq nil + expect(sns_broker.topic_arn_prefix).to eq default_prefix + end + + it "should use a nondefault ARN prefix from an env var" do + stub_const("ENV", ENV.to_hash.merge({ "AWS_SNS_ARN_PREFIX" => env_prefix })) + + sns_broker = described_class.new + + expect(sns_broker.topic_arn_prefix).to eq env_prefix + expect(sns_broker.config[:default_arn_prefix]).to_not eq env_prefix end - it "should return a message ID" do - @publisher = described_class.new - mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", - sequence_number: nil } + end - allow(@publisher).to receive(:publish).and_return(mocked_return) + describe "#publish" do + include_context "AWS stub" + + it "should return a message ID and a sequence number" do + publisher = described_class.new payload = { foo: "bar" } - result = @publisher.publish(payload, "pipefy-local-topic") - expect(result).to eq mocked_return + topic_name = "pipefy-local-topic" + + result = publisher.publish(payload, topic_name) + + # AWS default stub values: + expect(result.message_id).to eq "messageId" + expect(result.sequence_number).to eq "String" end end end diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index 5de7536..5d469ea 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -1,81 +1,88 @@ # frozen_string_literal: true +require "aws-sdk-sqs" + require_relative "../../../lib/pipefy_message/providers/aws_client/sqs_broker" +require_relative "aws_stub_context" RSpec.describe PipefyMessage::Providers::AwsClient::SqsBroker do - context "#SqsBroker" do - before do - stub_const("ENV", ENV.to_hash.merge("AWS_ENDPOINT" => "http://localhost:4566")) - stub_const("ENV", ENV.to_hash.merge("AWS_CLI_STUB_RESPONSE" => "true")) + include_context "AWS stub" + + describe "#initialize" do + let(:test_queue) { "test-queue" } + let(:sqs_opts) do + { + wait_time_seconds: 10, # default (not set) + queue_name: test_queue # changed + } end - describe "#poller" do - mocked_poller = nil + it "should set configurations vars from a hash arg or use defaults" do + sqs_broker = described_class.new(queue_name: sqs_opts[:queue_name]) + + expect(sqs_broker.config).to eq sqs_opts + end + end - before do - mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", - receipt_handle: "2312dasdas1231221312321adsads", - body: "{\"Message\": {\"foo\": \"bar\"}}" } + describe "#poller" do + it "should consume a message" do + mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", + receipt_handle: "2312dasdas1231221312321adsads", + body: "{\"Message\": {\"foo\": \"bar\"}}" } - Aws.config.update({ endpoint: "http://localhost:4566" }) - Aws.config[:sqs] = { - stub_responses: true - } - mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", - { skip_delete: true }) - mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } - mocked_element = Aws::SQS::Types::Message.new(mocked_message) - mocked_list = Aws::Xml::DefaultList.new - mocked_list.append(mocked_element) - mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) - end + mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", + { skip_delete: true }) + mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } - it "should consume message" do - worker = described_class.new - worker.instance_variable_set(:@poller, mocked_poller) + mocked_element = Aws::SQS::Types::Message.new(mocked_message) + mocked_list = Aws::Xml::DefaultList.new + mocked_list.append(mocked_element) + mocked_poller.client.stub_responses(:receive_message, messages: mocked_list) - result = nil - expected_result = { "Message" => { "foo" => "bar" } } - worker.poller do |message| - result = message - end - expect(result).to eq expected_result + worker = described_class.new + worker.instance_variable_set(:@poller, mocked_poller) + + result = nil + expected_result = { "Message" => { "foo" => "bar" } } + worker.poller do |message| + result = message end + expect(result).to eq expected_result end + end - describe "should raise Errors" do - it "QueueNonExistError" do - allow_any_instance_of(Aws::SQS::Client) - .to receive(:get_queue_url) - .with({ queue_name: "pipefy-local-queue" }) - .and_raise( - Aws::SQS::Errors::NonExistentQueue.new( - double(Aws::SQS::Client), - "The specified queue my_queue does not exist for this wsdl version" - ) + describe "raised errors" do + it "should raise NonExistentQueue" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "pipefy-local-queue" }) + .and_raise( + Aws::SQS::Errors::NonExistentQueue.new( + double(Aws::SQS::Client), + "The specified queue my_queue does not exist for this wsdl version" ) + ) - expect do - described_class.new - end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, - /The specified queue my_queue does not exist for this wsdl version/) - end - it "NetworkingError" do - allow_any_instance_of(Aws::SQS::Client) - .to receive(:get_queue_url) - .with({ queue_name: "pipefy-local-queue" }) - .and_raise( - Seahorse::Client::NetworkingError.new( - Errno::ECONNREFUSED.new(""), - "Failed to open TCP connection" - ) + expect do + described_class.new + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, + /The specified queue my_queue does not exist for this wsdl version/) + end + it "should raise NetworkingError" do + allow_any_instance_of(Aws::SQS::Client) + .to receive(:get_queue_url) + .with({ queue_name: "pipefy-local-queue" }) + .and_raise( + Seahorse::Client::NetworkingError.new( + Errno::ECONNREFUSED.new(""), + "Failed to open TCP connection" ) + ) - expect do - described_class.new("my_queue") - end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) - end + expect do + described_class.new("my_queue") + end.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end end diff --git a/spec/publisher_spec.rb b/spec/publisher_spec.rb index 0b00c2f..f9f6ded 100644 --- a/spec/publisher_spec.rb +++ b/spec/publisher_spec.rb @@ -2,15 +2,47 @@ require_relative "../lib/pipefy_message/providers/aws_client/sns_broker" +class TestBroker + def publish(message, topic); end +end + RSpec.describe PipefyMessage::Publisher do + it "forwards the message to be published by an instance" do + test_broker = instance_double("TestBroker") + allow(test_broker).to receive(:publish) + + allow_any_instance_of(described_class) + .to receive(:publisher_instance) + .and_return(test_broker) + + publisher = described_class.new + + payload = { foo: "bar" } + topic_name = "pipefy-local-topic" + publisher.publish(payload, topic_name) + + expect(test_broker).to have_received(:publish).with(payload, topic_name) + end + context "when I try to publish a message to SNS broker" do before do - ENV["AWS_ENDPOINT"] = "http://localhost:4566" - ENV["AWS_ACCESS_KEY_ID"] = "foo" - ENV["AWS_SECRET_ACCESS_KEY"] = "bar" - ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" - ENV["AWS_CLI_STUB_RESPONSE"] = "true" + changed_opts = { + "AWS_ENDPOINT" => "http://localhost:4566", + "AWS_ACCESS_KEY_ID" => "foo", + "AWS_SECRET_ACCESS_KEY" => "bar", + "ENABLE_AWS_CLIENT_CONFIG" => "true", + "AWS_CLI_STUB_RESPONSE" => "true" + } + + stub_const("ENV", ENV.to_hash.merge(changed_opts)) end + it "should choose the correct broker implementation" do + result = described_class.new.send(:publisher_instance) + expected_impl = PipefyMessage::Providers::AwsClient::SnsBroker + + expect(result).to be_a expected_impl + end + it "should publish a message properly" do mocked_publisher_impl = PipefyMessage::Providers::AwsClient::SnsBroker.new mocked_return = { message_id: "5482c8be-db2c-44ec-a899-3aa52e424cc3", @@ -30,12 +62,5 @@ expect(result).to eq mocked_return expect(mocked_publisher_impl).to have_received(:publish).with(payload, topic_name) end - - it "should choose the correct broker implementation" do - result = described_class.new.send(:publisher_instance) - expected_impl = PipefyMessage::Providers::AwsClient::SnsBroker - - expect(result).to be_a expected_impl - end end end diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index ad06dac..b00ab92 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true -$result =~ nil - class MockBroker - def poller - yield("test") - end + def poller; end end class MockBrokerFail @@ -20,7 +16,6 @@ class TestWorker def perform(message) puts message - $result = message end end @@ -28,10 +23,13 @@ def perform(message) describe "#perform" do context "successful polling" do it "should call #perform from child instance when #process_message is called" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBroker.new) + mock_broker = instance_double("MockBroker") + allow(mock_broker).to receive(:poller).with(no_args) + + allow(TestWorker).to receive(:build_instance_broker).and_return(mock_broker) TestWorker.process_message - expect($result).to eq "test" + expect(mock_broker).to have_received(:poller) end end From b2f8d9c0bfd453faff24a41f15a2e016b19d6fa5 Mon Sep 17 00:00:00 2001 From: Jleber Date: Tue, 3 May 2022 16:45:55 -0300 Subject: [PATCH 099/108] [WIP] Polish --- lib/pipefy_message.rb | 2 +- lib/pipefy_message/{worker.rb => consumer.rb} | 55 +++++++------------ .../providers/aws_client/sns_broker.rb | 19 +------ .../providers/aws_client/sqs_broker.rb | 29 +++++----- .../providers/broker_resolver.rb | 23 ++++++++ lib/pipefy_message/publisher.rb | 22 ++------ main.rb | 4 +- spec/providers/aws/aws_client_spec.rb | 2 + spec/providers/aws/sqs_broker_spec.rb | 9 ++- 9 files changed, 71 insertions(+), 94 deletions(-) rename lib/pipefy_message/{worker.rb => consumer.rb} (63%) diff --git a/lib/pipefy_message.rb b/lib/pipefy_message.rb index 4e06beb..1566b62 100644 --- a/lib/pipefy_message.rb +++ b/lib/pipefy_message.rb @@ -3,7 +3,7 @@ require_relative "pipefy_message/version" require_relative "pipefy_message/logging" require_relative "pipefy_message/publisher" -require_relative "pipefy_message/worker" +require_relative "pipefy_message/consumer" require_relative "pipefy_message/providers/errors" require "logger" diff --git a/lib/pipefy_message/worker.rb b/lib/pipefy_message/consumer.rb similarity index 63% rename from lib/pipefy_message/worker.rb rename to lib/pipefy_message/consumer.rb index b49469f..5b57796 100644 --- a/lib/pipefy_message/worker.rb +++ b/lib/pipefy_message/consumer.rb @@ -10,13 +10,13 @@ module PipefyMessage # dealing with queue polling and message parsing. # Should be included by classes that implement a perform method, for # processing received messages. - module Worker + module Consumer include PipefyMessage::Logging include PipefyMessage::Providers::Errors ## # Default options for consumer setup. - def self.default_worker_options - @default_worker_options ||= { + def self.default_consumer_options + @default_consumer_options ||= { broker: "aws" } end @@ -45,15 +45,15 @@ module ClassMethods ## # Merges default worker options with the hash passed as # an argument. The latter takes precedence. - def pipefymessage_options(opts = {}) - @pipefymessage_options = Worker.default_worker_options.merge(opts) - @pipefymessage_options.each do |k, v| + def options(opts = {}) + @options = Consumer.default_consumer_options.merge(opts) + @options.each do |k, v| singleton_class.class_eval { attr_accessor k } send("#{k}=", v) end logger.debug({ - options_set: @pipefymessage_options, + options_set: @options, message_text: "Set #{name} options to options_set" }) end @@ -61,39 +61,22 @@ def pipefymessage_options(opts = {}) ## # Sets broker-specific options to be passed to the broker's # constructor. - def pipefymessage_broker_options(opts = {}) - @pipefymessage_broker_opts = opts + def broker_options(opts = {}) + @broker_opts = opts end ## # Initializes and returns an instance of a broker for # the provider specified in the class options. - def build_instance_broker - pipefymessage_options if @broker.nil? - - pipefymessage_broker_options if @pipefymessage_broker_opts.nil? - - provider_map = PipefyMessage::Providers::BrokerResolver.class_path[broker.to_sym] - - if provider_map.nil? - error_msg = "Invalid provider specified: #{broker}" - - raise PipefyMessage::Providers::Errors::InvalidOption, error_msg - end - - consumer_map = provider_map[:consumer] - require_relative consumer_map[:relative_path] - - logger.info({ - broker: broker, - message_text: "Initializing instance of #{broker} consumer" - }) - - consumer_map[:class_name].constantize.new(@pipefymessage_broker_opts) + def build_consumer_instance + options if @broker.nil? + broker_options if @broker_opts.nil? + consumer_map = resolve_broker(@broker, "consumer") + consumer_map[:class_name].constantize.new(@broker_opts) end ## - # Instantiates a broker object (see build_instance_broker method), + # Instantiates a broker object (see build_consumer_instance method), # polls the queue given in the options and forwards received # messages for processing by a newly created instance of the class # from which the call to process_message was made (see perform @@ -102,12 +85,12 @@ def process_message start = Time.now obj = new logger.info({ - message_text: "Calling poller for #{broker} object" + message_text: "Calling poller for #{@broker} object" }) - build_instance_broker.poller do |message| + build_consumer_instance.poller do |message| logger.info({ - message_text: "Message received by #{broker} poller to be processed by worker", + message_text: "Message received by #{@broker} poller to be processed by consumer", received_message: message }) obj.perform(message) @@ -118,7 +101,7 @@ def process_message elapsed_time = (Time.now - start) * 1000.0 logger.info({ duration_ms: elapsed_time, - message_text: "Message received by #{broker}" \ + message_text: "Message received by #{@broker}" \ "poller processed by #{name} worker" \ "in #{elapsed_time} milliseconds" }) diff --git a/lib/pipefy_message/providers/aws_client/sns_broker.rb b/lib/pipefy_message/providers/aws_client/sns_broker.rb index 5bd7a22..70af198 100644 --- a/lib/pipefy_message/providers/aws_client/sns_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sns_broker.rb @@ -12,39 +12,26 @@ class SnsBroker include PipefyMessage::Logging include PipefyMessage::Providers::Errors - attr_reader :config, :topic_arn_prefix - - def initialize(opts = {}) - @config = default_options.merge(opts) - + def initialize(_opts = {}) AwsClient.aws_setup @sns = Aws::SNS::Resource.new logger.debug({ message_text: "SNS resource created" }) - @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || @config[:default_arn_prefix] + @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || "arn:aws:sns:us-east-1:000000000000:" @is_staging = ENV["ASYNC_APP_ENV"] == "staging" rescue StandardError raise PipefyMessage::Providers::Errors::ResourceError, e.message end - ## - # Extends AWS default options to include a value - # for SNS-specific configurations. - def default_options - { default_arn_prefix: "arn:aws:sns:us-east-1:000000000000" } - end - ## # Publishes a message with the given payload to the SNS topic # with topic_name. def publish(payload, topic_name) message = prepare_payload(payload) - topic_arn = @topic_arn_prefix + ":" + (@is_staging ? "#{topic_name}-staging" : topic_name) + topic_arn = @topic_arn_prefix + (@is_staging ? "#{topic_name}-staging" : topic_name) topic = @sns.topic(topic_arn) - # require "pry"; binding.pry - logger.info( { topic_arn: topic_arn, payload: payload, diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index 13f2485..abc1ca5 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -11,31 +11,23 @@ class SqsBroker include PipefyMessage::Logging include PipefyMessage::Providers::Errors - attr_reader :config - def initialize(opts = {}) @config = default_options.merge(opts) AwsClient.aws_setup - # require "pry"; binding.pry - @sqs = Aws::SQS::Client.new logger.debug({ message_text: "SQS client created" }) + @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || "arn:aws:sns:us-east-1:000000000000" + @is_staging = ENV["ASYNC_APP_ENV"] == "staging" + queue_url = @sqs.get_queue_url({ queue_name: @config[:queue_name] }).queue_url @poller = Aws::SQS::QueuePoller.new(queue_url, { client: @sqs }) rescue StandardError => e raise PipefyMessage::Providers::Errors::ResourceError, e.message end - ## - # Extends AWS default options to include a value - # for SQS-specific configurations. - def default_options - { wait_time_seconds: 10, queue_name: "pipefy-local-queue" } - end - ## # Initiates SQS queue polling, with wait_time_seconds as given # in the initial configuration. @@ -50,14 +42,19 @@ def poller end end + private + + ## + # Extends AWS default options to include a value + # for SQS-specific configurations. + def default_options + { wait_time_seconds: 10, queue_name: "pipefy-local-queue" } + end + ## # Adds the queue name to logs, if not already present. def build_log_hash(arg) - if arg.instance_of? Hash - { queue_name: @config[:queue_name] }.merge(arg) - else - { queue_name: @config[:queue_name], message_text: arg } - end + { queue_name: @config[:queue_name], message_text: arg } end end end diff --git a/lib/pipefy_message/providers/broker_resolver.rb b/lib/pipefy_message/providers/broker_resolver.rb index 493ed6d..85c73a8 100644 --- a/lib/pipefy_message/providers/broker_resolver.rb +++ b/lib/pipefy_message/providers/broker_resolver.rb @@ -6,6 +6,29 @@ module Providers # Provides class names and paths to be used for each type of # client, for each provider. module BrokerResolver + ## + # Initializes and returns an instance of a broker for + # the provider specified in the class options. + def self.resolve_broker(broker, type) + provider_map = class_path[broker.to_sym] + + if provider_map.nil? + error_msg = "Invalid provider specified: #{broker}" + + raise PipefyMessage::Providers::Errors::InvalidOption, error_msg + end + + map = provider_map[:type] + require_relative map[:relative_path] + + logger.info({ + broker: broker, + message_text: "Initializing instance of #{broker} #{type}" + }) + + map + end + def self.class_path { aws: { diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index 82962cf..458dab0 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -10,10 +10,11 @@ class Publisher def initialize(broker = "aws", broker_opts = {}) @broker = broker @broker_opts = broker_opts + @publisher_instance = build_publisher_instance end def publish(message, topic) - publisher_instance.publish(message, topic) + @publisher_instance.publish(message, topic) end private @@ -21,23 +22,8 @@ def publish(message, topic) ## # Initializes and returns an instance of a broker for # the provider specified in the class options. - def publisher_instance - provider_map = PipefyMessage::Providers::BrokerResolver.class_path[@broker.to_sym] - - if provider_map.nil? - error_msg = "Invalid provider specified: #{@broker}" - - raise PipefyMessage::Providers::Errors::InvalidOption, error_msg - end - - publisher_map = provider_map[:publisher] - require_relative publisher_map[:relative_path] - - logger.info({ - broker: @broker, - message_text: "Initializing instance of #{@broker} publisher" - }) - + def build_publisher_instance + publisher_map = resolve_broker(@broker, "publisher") publisher_map[:class_name].constantize.new(@broker_opts) end end diff --git a/main.rb b/main.rb index 28513b5..2fbf169 100644 --- a/main.rb +++ b/main.rb @@ -5,8 +5,8 @@ ## # Example worker class. class TestWorker - include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + include PipefyMessage::Consumer + options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message diff --git a/spec/providers/aws/aws_client_spec.rb b/spec/providers/aws/aws_client_spec.rb index ecf1848..166e648 100644 --- a/spec/providers/aws/aws_client_spec.rb +++ b/spec/providers/aws/aws_client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "aws-sdk-sqs" require_relative "../../../lib/pipefy_message/providers/aws_client/aws_client" diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index 5d469ea..a015a0e 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -27,12 +27,11 @@ describe "#poller" do it "should consume a message" do mocked_message = { message_id: "44c44782-fee1-6784-d614-43b73c0bda8d", - receipt_handle: "2312dasdas1231221312321adsads", - body: "{\"Message\": {\"foo\": \"bar\"}}" } - + receipt_handle: "2312dasdas1231221312321adsads", + body: "{\"Message\": {\"foo\": \"bar\"}}" } mocked_poller = Aws::SQS::QueuePoller.new("http://localhost:4566/000000000000/my_queue", - { skip_delete: true }) + { skip_delete: true }) mocked_poller.before_request { |stats| throw :stop_polling if stats.received_message_count > 0 } mocked_element = Aws::SQS::Types::Message.new(mocked_message) @@ -67,7 +66,7 @@ expect do described_class.new end.to raise_error(PipefyMessage::Providers::Errors::ResourceError, - /The specified queue my_queue does not exist for this wsdl version/) + /The specified queue my_queue does not exist for this wsdl version/) end it "should raise NetworkingError" do allow_any_instance_of(Aws::SQS::Client) From 6716678337a6560e8af2396ac9a5a2e28c4d7c13 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Tue, 3 May 2022 17:53:59 -0300 Subject: [PATCH 100/108] refact: Refactor tests to match lib folder refactor --- lib/pipefy_message/consumer.rb | 1 + .../providers/broker_resolver.rb | 10 +++++----- lib/pipefy_message/publisher.rb | 1 + spec/{worker_spec.rb => consumer_spec.rb} | 12 +++++------ spec/providers/aws/sns_broker_spec.rb | 20 ++++--------------- spec/providers/aws/sqs_broker_spec.rb | 2 +- spec/publisher_spec.rb | 6 +++--- 7 files changed, 21 insertions(+), 31 deletions(-) rename spec/{worker_spec.rb => consumer_spec.rb} (81%) diff --git a/lib/pipefy_message/consumer.rb b/lib/pipefy_message/consumer.rb index 5b57796..381de3c 100644 --- a/lib/pipefy_message/consumer.rb +++ b/lib/pipefy_message/consumer.rb @@ -13,6 +13,7 @@ module PipefyMessage module Consumer include PipefyMessage::Logging include PipefyMessage::Providers::Errors + include PipefyMessage::Providers::BrokerResolver ## # Default options for consumer setup. def self.default_consumer_options diff --git a/lib/pipefy_message/providers/broker_resolver.rb b/lib/pipefy_message/providers/broker_resolver.rb index 85c73a8..633b298 100644 --- a/lib/pipefy_message/providers/broker_resolver.rb +++ b/lib/pipefy_message/providers/broker_resolver.rb @@ -9,8 +9,8 @@ module BrokerResolver ## # Initializes and returns an instance of a broker for # the provider specified in the class options. - def self.resolve_broker(broker, type) - provider_map = class_path[broker.to_sym] + def resolve_broker(broker, type) + provider_map = PipefyMessage::Providers::BrokerResolver.class_path[broker.to_sym] if provider_map.nil? error_msg = "Invalid provider specified: #{broker}" @@ -18,7 +18,7 @@ def self.resolve_broker(broker, type) raise PipefyMessage::Providers::Errors::InvalidOption, error_msg end - map = provider_map[:type] + map = provider_map[type.to_sym] require_relative map[:relative_path] logger.info({ @@ -34,11 +34,11 @@ def self.class_path aws: { publisher: { class_name: "PipefyMessage::Providers::AwsClient::SnsBroker", - relative_path: "providers/aws_client/sns_broker" + relative_path: "aws_client/sns_broker" }, consumer: { class_name: "PipefyMessage::Providers::AwsClient::SqsBroker", - relative_path: "providers/aws_client/sqs_broker" + relative_path: "aws_client/sqs_broker" } } } diff --git a/lib/pipefy_message/publisher.rb b/lib/pipefy_message/publisher.rb index 458dab0..43f5782 100644 --- a/lib/pipefy_message/publisher.rb +++ b/lib/pipefy_message/publisher.rb @@ -6,6 +6,7 @@ module PipefyMessage # Base Publisher provided by this gem, to be used for the external publishers to send messages to a broker class Publisher include PipefyMessage::Logging + include PipefyMessage::Providers::BrokerResolver def initialize(broker = "aws", broker_opts = {}) @broker = broker diff --git a/spec/worker_spec.rb b/spec/consumer_spec.rb similarity index 81% rename from spec/worker_spec.rb rename to spec/consumer_spec.rb index b00ab92..9904c25 100644 --- a/spec/worker_spec.rb +++ b/spec/consumer_spec.rb @@ -11,22 +11,22 @@ def poller end class TestWorker - include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" + include PipefyMessage::Consumer + options broker: "aws", queue_name: "pipefy-local-queue" def perform(message) puts message end end -RSpec.describe PipefyMessage::Worker do +RSpec.describe PipefyMessage::Consumer do describe "#perform" do context "successful polling" do it "should call #perform from child instance when #process_message is called" do mock_broker = instance_double("MockBroker") allow(mock_broker).to receive(:poller).with(no_args) - allow(TestWorker).to receive(:build_instance_broker).and_return(mock_broker) + allow(TestWorker).to receive(:build_consumer_instance).and_return(mock_broker) TestWorker.process_message expect(mock_broker).to have_received(:poller) @@ -35,7 +35,7 @@ def perform(message) context "polling failure" do it "should call #perform from child instance when #process_message is called" do - allow(TestWorker).to receive(:build_instance_broker).and_return(MockBrokerFail.new) + allow(TestWorker).to receive(:build_consumer_instance).and_return(MockBrokerFail.new) expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end @@ -62,7 +62,7 @@ def perform(message) end it "should raise an error" do - expect { TestWorker.build_instance_broker }.to raise_error PipefyMessage::Providers::Errors::InvalidOption + expect { TestWorker.build_consumer_instance }.to raise_error PipefyMessage::Providers::Errors::InvalidOption end end diff --git a/spec/providers/aws/sns_broker_spec.rb b/spec/providers/aws/sns_broker_spec.rb index 01e99b5..19886df 100644 --- a/spec/providers/aws/sns_broker_spec.rb +++ b/spec/providers/aws/sns_broker_spec.rb @@ -8,30 +8,18 @@ let(:prefix) { "test" } let(:env_prefix) { "env" } - it "should set the default ARN prefix from a hash arg" do - sns_broker = described_class.new({ default_arn_prefix: prefix }) - default_prefix = sns_broker.config[:default_arn_prefix] - - expect(default_prefix).to eq prefix - expect(sns_broker.topic_arn_prefix).to eq default_prefix - end - - it "should have a default default ARN prefix" do + it "should have a default ARN prefix" do sns_broker = described_class.new - default_prefix = sns_broker.config[:default_arn_prefix] - - expect(default_prefix).to_not eq nil - expect(sns_broker.topic_arn_prefix).to eq default_prefix + expect(sns_broker.instance_variable_get(:@topic_arn_prefix)).to_not eq nil end - it "should use a nondefault ARN prefix from an env var" do + it "should set a nondefault ARN prefix from an env var" do stub_const("ENV", ENV.to_hash.merge({ "AWS_SNS_ARN_PREFIX" => env_prefix })) sns_broker = described_class.new - expect(sns_broker.topic_arn_prefix).to eq env_prefix - expect(sns_broker.config[:default_arn_prefix]).to_not eq env_prefix + expect(sns_broker.instance_variable_get(:@topic_arn_prefix)).to eq env_prefix end end diff --git a/spec/providers/aws/sqs_broker_spec.rb b/spec/providers/aws/sqs_broker_spec.rb index a015a0e..92b7cbd 100644 --- a/spec/providers/aws/sqs_broker_spec.rb +++ b/spec/providers/aws/sqs_broker_spec.rb @@ -20,7 +20,7 @@ it "should set configurations vars from a hash arg or use defaults" do sqs_broker = described_class.new(queue_name: sqs_opts[:queue_name]) - expect(sqs_broker.config).to eq sqs_opts + expect(sqs_broker.instance_variable_get(:@config)).to eq sqs_opts end end diff --git a/spec/publisher_spec.rb b/spec/publisher_spec.rb index f9f6ded..906c992 100644 --- a/spec/publisher_spec.rb +++ b/spec/publisher_spec.rb @@ -12,7 +12,7 @@ def publish(message, topic); end allow(test_broker).to receive(:publish) allow_any_instance_of(described_class) - .to receive(:publisher_instance) + .to receive(:build_publisher_instance) .and_return(test_broker) publisher = described_class.new @@ -37,7 +37,7 @@ def publish(message, topic); end stub_const("ENV", ENV.to_hash.merge(changed_opts)) end it "should choose the correct broker implementation" do - result = described_class.new.send(:publisher_instance) + result = described_class.new.send(:build_publisher_instance) expected_impl = PipefyMessage::Providers::AwsClient::SnsBroker expect(result).to be_a expected_impl @@ -51,7 +51,7 @@ def publish(message, topic); end allow(mocked_publisher_impl).to receive(:publish).and_return(mocked_return) allow_any_instance_of(described_class) - .to receive(:publisher_instance) + .to receive(:build_publisher_instance) .and_return(mocked_publisher_impl) publisher = described_class.new From 280981504dd6e993595a596d7bb8a58efcf5d444 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:41:57 -0300 Subject: [PATCH 101/108] Update project documentation --- .gitignore | 1 + .vscode/settings.json | 3 -- README.md | 80 ++++++++++++++++++----------- lib/samples/my_awesome_consumer.rb | 16 ++++++ lib/samples/my_awesome_publisher.rb | 14 +++++ main.rb | 16 ------ 6 files changed, 82 insertions(+), 48 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 lib/samples/my_awesome_consumer.rb create mode 100644 lib/samples/my_awesome_publisher.rb delete mode 100644 main.rb diff --git a/.gitignore b/.gitignore index 715c5eb..e17ff54 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,5 @@ build-iPhoneSimulator/ # .rubocop-https?--* .idea +.vscode .rspec_status \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 622799f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rufo.useBundler": false, -} \ No newline at end of file diff --git a/README.md b/README.md index 0ff3ff9..98c380e 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,57 @@ Or install it yourself as: ## Usage -### Development +### Publisher + +To use the publisher capabilities is required to "import our gem" at the desired class and call the publish method, see the example below: + +```ruby +require "pipefy_message" + +## +# Example publisher class. +class PublisherExampleClass + def awesomeLogic + + ## business logic + + payload = { foo: "bar" } + publisher = PipefyMessage::Publisher.new + result = publisher.publish(payload, "pipefy-local-topic") + puts result ## will print some data like the messageID and so on + end +end +``` + +### Consumer + +To use the consumer capabilities is required to "import our gem" at your consumer class, include the abstraction, define the `perform` method and finally call the method `process_message` to start the consuming process, see the example below: + +```ruby +require "pipefy_message" + +## +# Example consumer class. +class ConsumerExampleClass + include PipefyMessage::Consumer + options queue_name: "pipefy-local-queue" + + def perform(message) + puts "Received message #{message} from broker" + ## Fill with your business logic here + end +end + +ConsumerExampleClass.process_message +``` + +### Development - Test To test changes without install this dependency on your application, on your terminal go to the project root and execute: ```console - export AWS_ACCESS_KEY_ID=foo - export AWS_SECRET_ACCESS_KEY=bar - export AWS_ENDPOINT="http://localhost:4566" export ENABLE_AWS_CLIENT_CONFIG=true - + make build-app make build-app-infra ``` @@ -61,34 +102,15 @@ On the irb console: * Publish a message ```ruby - require 'pipefy_message' - message = PipefyMessage::Test.new - message.publish + require_relative 'lib/samples/my_awesome_publisher.rb' + publisher = MyAwesomePublisher.new + publisher.publish ``` * Consume a message ```ruby - require "pipefy_message" - - class TestWorker - include PipefyMessage::Worker - pipefymessage_options broker: "aws", queue_name: "pipefy-local-queue" - - def perform(message) - puts message - end - end - - TestWorker.perform_async - ``` - - - -* Publish and Consume a message - ```ruby - require 'pipefy_message' - message = PipefyMessage::Test.new - message.publish_and_consume + require_relative 'lib/samples/my_awesome_consumer.rb' + MyAwesomeConsumer.process_message ``` ## Project Stack diff --git a/lib/samples/my_awesome_consumer.rb b/lib/samples/my_awesome_consumer.rb new file mode 100644 index 0000000..2ebc820 --- /dev/null +++ b/lib/samples/my_awesome_consumer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "pipefy_message" + +## +# Example consumer class. +class MyAwesomeConsumer + include PipefyMessage::Consumer + options queue_name: "pipefy-local-queue" + + def perform(message) + puts "Received message #{message} from broker" + ## Fill with our logic here + end +end + diff --git a/lib/samples/my_awesome_publisher.rb b/lib/samples/my_awesome_publisher.rb new file mode 100644 index 0000000..afc021d --- /dev/null +++ b/lib/samples/my_awesome_publisher.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "pipefy_message" + +## +# Example publisher class. +class MyAwesomePublisher + def publish + payload = { foo: "bar" } + publisher = PipefyMessage::Publisher.new + result = publisher.publish(payload, "pipefy-local-topic") + puts result + end +end \ No newline at end of file diff --git a/main.rb b/main.rb deleted file mode 100644 index 2fbf169..0000000 --- a/main.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "pipefy_message" - -## -# Example worker class. -class TestWorker - include PipefyMessage::Consumer - options broker: "aws", queue_name: "pipefy-local-queue" - - def perform(message) - puts message - end -end - -TestWorker.process_message From 12b62930c1f0b1d57cf75400b393b4551b23e9a1 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:42:32 -0300 Subject: [PATCH 102/108] Adjust Aws Client --- .../providers/aws_client/aws_client.rb | 41 ++++++++++++------- spec/providers/aws/aws_client_spec.rb | 3 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_client.rb b/lib/pipefy_message/providers/aws_client/aws_client.rb index 0d8f384..702ac17 100644 --- a/lib/pipefy_message/providers/aws_client/aws_client.rb +++ b/lib/pipefy_message/providers/aws_client/aws_client.rb @@ -11,19 +11,6 @@ module Providers module AwsClient include PipefyMessage::Logging - ## - # Hash that fetches AWS options from environment variables or - # sets them to default values. - def self.set_options - { - access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), - secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), - endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), - region: ENV.fetch("AWS_REGION", "us-east-1"), - stub_responses: ENV.fetch("AWS_CLI_STUB_RESPONSE", "true") - } - end - ## # Sets up AWS options the first time an AWS service is used. def self.aws_setup @@ -31,7 +18,33 @@ def self.aws_setup logger.info({ message_text: "AWS configurations set" }) - Aws.config.update(set_options) + Aws.config.update(retrieve_config) + end + + private + + def self.aws_client_config? + ENV["ENABLE_AWS_CLIENT_CONFIG"] == "true" + end + + # Hash that fetches AWS options from environment variables or + # sets the base and custom values. + def self.retrieve_config + config = { region: ENV["AWS_REGION"] || "us-east-1" } + merge_custom_config(config) + end + + def self.merge_custom_config(config) + if aws_client_config? + { + access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), + secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), + endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), + stub_responses: ENV["AWS_CLI_STUB_RESPONSE"] || false + }.merge(config) + else + config + end end end end diff --git a/spec/providers/aws/aws_client_spec.rb b/spec/providers/aws/aws_client_spec.rb index 166e648..e568668 100644 --- a/spec/providers/aws/aws_client_spec.rb +++ b/spec/providers/aws/aws_client_spec.rb @@ -27,7 +27,8 @@ def initialize before do changed_opts = { "AWS_ACCESS_KEY_ID" => aws_opts[:access_key_id], - "AWS_SECRET_ACCESS_KEY" => aws_opts[:secret_access_key] + "AWS_SECRET_ACCESS_KEY" => aws_opts[:secret_access_key], + "AWS_CLI_STUB_RESPONSE" => aws_opts[:stub_responses] } stub_const("ENV", ENV.to_hash.merge(changed_opts)) From 1860bcf4eb1ed6ce670602655c3272cd5eb397a0 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:43:03 -0300 Subject: [PATCH 103/108] Fix for log separator --- lib/pipefy_message/logging.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pipefy_message/logging.rb b/lib/pipefy_message/logging.rb index f4bc230..207b884 100644 --- a/lib/pipefy_message/logging.rb +++ b/lib/pipefy_message/logging.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "English" require "logger" require "json" @@ -35,7 +36,7 @@ def self.logger_setup level: severity.to_s, app: progname.to_s, context: "async_processing", - message: msg }.to_json + $INPUT_RECORD_SEPARATOR.to_s + message: msg }.to_json + $INPUT_RECORD_SEPARATOR end end end From 67a2a425667e36e4fce512976bb6cfab55921fa2 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:44:28 -0300 Subject: [PATCH 104/108] Adjust consumer stuff --- lib/pipefy_message/consumer.rb | 57 +++++++------------ .../providers/aws_client/sqs_broker.rb | 5 +- spec/consumer_spec.rb | 24 ++++---- 3 files changed, 38 insertions(+), 48 deletions(-) diff --git a/lib/pipefy_message/consumer.rb b/lib/pipefy_message/consumer.rb index 381de3c..5b78de7 100644 --- a/lib/pipefy_message/consumer.rb +++ b/lib/pipefy_message/consumer.rb @@ -14,6 +14,7 @@ module Consumer include PipefyMessage::Logging include PipefyMessage::Providers::Errors include PipefyMessage::Providers::BrokerResolver + ## # Default options for consumer setup. def self.default_consumer_options @@ -47,33 +48,15 @@ module ClassMethods # Merges default worker options with the hash passed as # an argument. The latter takes precedence. def options(opts = {}) - @options = Consumer.default_consumer_options.merge(opts) - @options.each do |k, v| - singleton_class.class_eval { attr_accessor k } - send("#{k}=", v) - end - - logger.debug({ - options_set: @options, - message_text: "Set #{name} options to options_set" - }) - end - - ## - # Sets broker-specific options to be passed to the broker's - # constructor. - def broker_options(opts = {}) - @broker_opts = opts + @consumer_options = Consumer.default_consumer_options.merge(opts) end ## # Initializes and returns an instance of a broker for # the provider specified in the class options. def build_consumer_instance - options if @broker.nil? - broker_options if @broker_opts.nil? - consumer_map = resolve_broker(@broker, "consumer") - consumer_map[:class_name].constantize.new(@broker_opts) + consumer_map = resolve_broker(@consumer_options[:broker], "consumer") + consumer_map[:class_name].constantize.new(@consumer_options) end ## @@ -85,27 +68,27 @@ def build_consumer_instance def process_message start = Time.now obj = new - logger.info({ - message_text: "Calling poller for #{@broker} object" - }) - build_consumer_instance.poller do |message| + logger.info({ message_text: "Calling consumer poller" }) + + build_consumer_instance.poller do |payload| + logger.info({ + message_text: "Message received by poller to be processed by consumer", + received_message: payload + }) + + obj.perform(payload["Message"]) + + elapsed_time = (Time.now - start) * 1000.0 logger.info({ - message_text: "Message received by #{@broker} poller to be processed by consumer", - received_message: message + duration_ms: elapsed_time, + message_text: "Message received by consumer poller, processed " \ + "in #{elapsed_time} milliseconds" }) - obj.perform(message) end - rescue PipefyMessage::Providers::Errors::ResourceError => e # (any others?) + rescue PipefyMessage::Providers::Errors::ResourceError => e + logger.error("Failed to process message, details #{e.inspect}") raise e - ensure - elapsed_time = (Time.now - start) * 1000.0 - logger.info({ - duration_ms: elapsed_time, - message_text: "Message received by #{@broker}" \ - "poller processed by #{name} worker" \ - "in #{elapsed_time} milliseconds" - }) end end end diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index abc1ca5..e975ba7 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -32,13 +32,16 @@ def initialize(opts = {}) # Initiates SQS queue polling, with wait_time_seconds as given # in the initial configuration. def poller - logger.debug(build_log_hash("Initiating SQS polling on queue #{@config[:queue_name]}")) + logger.info(build_log_hash("Initiating SQS polling on queue #{@config[:queue_name]}")) @poller.poll(wait_time_seconds: @config[:wait_time_seconds]) do |received_message| logger.debug(build_log_hash("Message received by SQS poller on queue #{@config[:queue_name]}")) payload = JSON.parse(received_message.body) yield(payload) + + rescue StandardError => e + raise PipefyMessage::Providers::Errors::ResourceError, e.message end end diff --git a/spec/consumer_spec.rb b/spec/consumer_spec.rb index 9904c25..152caab 100644 --- a/spec/consumer_spec.rb +++ b/spec/consumer_spec.rb @@ -10,7 +10,7 @@ def poller end end -class TestWorker +class TestConsumer include PipefyMessage::Consumer options broker: "aws", queue_name: "pipefy-local-queue" @@ -20,49 +20,53 @@ def perform(message) end RSpec.describe PipefyMessage::Consumer do + before do + ENV["ENABLE_AWS_CLIENT_CONFIG"] = "true" + ENV["AWS_CLI_STUB_RESPONSE"] = "true" + end describe "#perform" do context "successful polling" do it "should call #perform from child instance when #process_message is called" do mock_broker = instance_double("MockBroker") allow(mock_broker).to receive(:poller).with(no_args) - allow(TestWorker).to receive(:build_consumer_instance).and_return(mock_broker) + allow(TestConsumer).to receive(:build_consumer_instance).and_return(mock_broker) - TestWorker.process_message + TestConsumer.process_message expect(mock_broker).to have_received(:poller) end end context "polling failure" do it "should call #perform from child instance when #process_message is called" do - allow(TestWorker).to receive(:build_consumer_instance).and_return(MockBrokerFail.new) - expect { TestWorker.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) + allow(TestConsumer).to receive(:build_consumer_instance).and_return(MockBrokerFail.new) + expect { TestConsumer.process_message }.to raise_error(PipefyMessage::Providers::Errors::ResourceError) end end it "should fail if called directly from the parent class" do - expect { TestWorker.perform("message") }.to raise_error NotImplementedError + expect { TestConsumer.perform("message") }.to raise_error NotImplementedError end end describe "#options class" do it "should set options in class" do - expect(TestWorker.broker).to eq "aws" + expect(TestConsumer.options[:broker]).to eq "aws" end end describe "#build_instance_broker" do context "invalid provider" do before(:all) do - TestWorker.broker = "NaN" + TestConsumer.options[:broker] = "NaN" end after(:all) do - TestWorker.broker = "aws" # reverting + TestConsumer.options[:broker] = "aws" # reverting end it "should raise an error" do - expect { TestWorker.build_consumer_instance }.to raise_error PipefyMessage::Providers::Errors::InvalidOption + expect { TestConsumer.build_consumer_instance }.to raise_error PipefyMessage::Providers::Errors::InvalidOption end end From ecacdb1f4becf9d7da57c00265373e5fafe344c9 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:45:07 -0300 Subject: [PATCH 105/108] Adjust log message and add sample consumer class --- lib/pipefy_message/providers/broker_resolver.rb | 2 +- lib/samples/my_awesome_consumer.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/providers/broker_resolver.rb b/lib/pipefy_message/providers/broker_resolver.rb index 633b298..50a433d 100644 --- a/lib/pipefy_message/providers/broker_resolver.rb +++ b/lib/pipefy_message/providers/broker_resolver.rb @@ -23,7 +23,7 @@ def resolve_broker(broker, type) logger.info({ broker: broker, - message_text: "Initializing instance of #{broker} #{type}" + message_text: "Resolved instance of #{broker} #{type}" }) map diff --git a/lib/samples/my_awesome_consumer.rb b/lib/samples/my_awesome_consumer.rb index 2ebc820..bb3953f 100644 --- a/lib/samples/my_awesome_consumer.rb +++ b/lib/samples/my_awesome_consumer.rb @@ -12,5 +12,4 @@ def perform(message) puts "Received message #{message} from broker" ## Fill with our logic here end -end - +end \ No newline at end of file From 58f40d6f19c357ceb12920d79d2a0d8fdfffdc96 Mon Sep 17 00:00:00 2001 From: Jleber Date: Wed, 4 May 2022 16:45:59 -0300 Subject: [PATCH 106/108] Linter adjustments --- lib/pipefy_message/providers/aws_client/aws_client.rb | 2 -- lib/samples/my_awesome_consumer.rb | 2 +- lib/samples/my_awesome_publisher.rb | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_client.rb b/lib/pipefy_message/providers/aws_client/aws_client.rb index 702ac17..e1bbb06 100644 --- a/lib/pipefy_message/providers/aws_client/aws_client.rb +++ b/lib/pipefy_message/providers/aws_client/aws_client.rb @@ -21,8 +21,6 @@ def self.aws_setup Aws.config.update(retrieve_config) end - private - def self.aws_client_config? ENV["ENABLE_AWS_CLIENT_CONFIG"] == "true" end diff --git a/lib/samples/my_awesome_consumer.rb b/lib/samples/my_awesome_consumer.rb index bb3953f..59d9ef6 100644 --- a/lib/samples/my_awesome_consumer.rb +++ b/lib/samples/my_awesome_consumer.rb @@ -12,4 +12,4 @@ def perform(message) puts "Received message #{message} from broker" ## Fill with our logic here end -end \ No newline at end of file +end diff --git a/lib/samples/my_awesome_publisher.rb b/lib/samples/my_awesome_publisher.rb index afc021d..279f37f 100644 --- a/lib/samples/my_awesome_publisher.rb +++ b/lib/samples/my_awesome_publisher.rb @@ -11,4 +11,4 @@ def publish result = publisher.publish(payload, "pipefy-local-topic") puts result end -end \ No newline at end of file +end From 6d0f63d1a3c5f7ceaef24c65be1e8d5033c55ea1 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 4 May 2022 18:14:08 -0300 Subject: [PATCH 107/108] refact (minor): Nitpicky uniformization of || vs fetch --- lib/pipefy_message/providers/aws_client/aws_client.rb | 2 +- lib/pipefy_message/providers/aws_client/sqs_broker.rb | 2 +- spec/providers/aws/aws_client_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pipefy_message/providers/aws_client/aws_client.rb b/lib/pipefy_message/providers/aws_client/aws_client.rb index e1bbb06..9d06eef 100644 --- a/lib/pipefy_message/providers/aws_client/aws_client.rb +++ b/lib/pipefy_message/providers/aws_client/aws_client.rb @@ -38,7 +38,7 @@ def self.merge_custom_config(config) access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID", "foo"), secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY", "bar"), endpoint: ENV.fetch("AWS_ENDPOINT", "http://localhost:4566"), - stub_responses: ENV["AWS_CLI_STUB_RESPONSE"] || false + stub_responses: ENV.fetch("AWS_CLI_STUB_RESPONSE", false) }.merge(config) else config diff --git a/lib/pipefy_message/providers/aws_client/sqs_broker.rb b/lib/pipefy_message/providers/aws_client/sqs_broker.rb index e975ba7..5fbe07d 100644 --- a/lib/pipefy_message/providers/aws_client/sqs_broker.rb +++ b/lib/pipefy_message/providers/aws_client/sqs_broker.rb @@ -19,7 +19,7 @@ def initialize(opts = {}) @sqs = Aws::SQS::Client.new logger.debug({ message_text: "SQS client created" }) - @topic_arn_prefix = ENV["AWS_SNS_ARN_PREFIX"] || "arn:aws:sns:us-east-1:000000000000" + @topic_arn_prefix = ENV.fetch("AWS_SNS_ARN_PREFIX", "arn:aws:sns:us-east-1:000000000000") @is_staging = ENV["ASYNC_APP_ENV"] == "staging" queue_url = @sqs.get_queue_url({ queue_name: @config[:queue_name] }).queue_url diff --git a/spec/providers/aws/aws_client_spec.rb b/spec/providers/aws/aws_client_spec.rb index e568668..b44a1af 100644 --- a/spec/providers/aws/aws_client_spec.rb +++ b/spec/providers/aws/aws_client_spec.rb @@ -20,7 +20,7 @@ def initialize secret_access_key: "so-we-know-it-worked", # changed endpoint: "http://localhost:4566", # default (not set) region: "us-east-1", # default (not set) - stub_responses: "true" # default (not set) + stub_responses: "true" # changed } end From dbe13de346724fcfc9a28179c4602b11852d4f98 Mon Sep 17 00:00:00 2001 From: Eduarda Ferreira Date: Wed, 4 May 2022 18:25:00 -0300 Subject: [PATCH 108/108] Quick README review w/ minor grammar-related edits and clarifications --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 98c380e..6159f00 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # PipefyMessage -This project it's a gem who provides a simple way to produce and consume messages for async processing. +This project is a gem that provides a simple way to produce and consume messages for async processing. + +The current implementation supports AWS SNS for sending messages (by publishing them to topics) and AWS SQS for receiving them (via queue polling). ## Requirements @@ -33,7 +35,7 @@ Or install it yourself as: ### Publisher -To use the publisher capabilities is required to "import our gem" at the desired class and call the publish method, see the example below: +To use the publisher capabilities it is required to "import our gem" at the desired class, create an instance of the Publisher class and call the publish method on it. See the example below: ```ruby require "pipefy_message" @@ -55,7 +57,7 @@ end ### Consumer -To use the consumer capabilities is required to "import our gem" at your consumer class, include the abstraction, define the `perform` method and finally call the method `process_message` to start the consuming process, see the example below: +To use the consumer capabilities it is required to "import our gem" at your consumer class, include the abstraction, define the `perform` method and finally call the method `process_message` on the consumer class (not an instance of it) to start the consuming process, see the example below: ```ruby require "pipefy_message" @@ -77,7 +79,7 @@ ConsumerExampleClass.process_message ### Development - Test -To test changes without install this dependency on your application, on your terminal go to the project root and execute: +To test changes without installing this dependency on your application, on your terminal go to the project root and execute: ```console export ENABLE_AWS_CLIENT_CONFIG=true