strum-esb 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ce4f150ec3058cd6296aadaba679e410a53b0b0f50843c0ec1d71f0c8ce5134
4
- data.tar.gz: cc4a35c95f53aa9fdbc8b7b341aee355c513fab06d4af19240bb454b95d26ad9
3
+ metadata.gz: 115db6eaed48ee3c437b15fbf8103815214a5cdcc4d0d040960fc6ad73708190
4
+ data.tar.gz: dd0f567ec63c61781272ea0931a7348be7df38de6661b5d9da77ccf55e17855d
5
5
  SHA512:
6
- metadata.gz: 804f610986a6b13f1948a3a4fea93fb53733c0693c7301a19fb0eba8ede1ed7d05549f7d3bd4d889264f5346c48bac7299597fd1aab740e3dfe5189750cd741b
7
- data.tar.gz: 38cce564ca77e76433efdda7bea1b5566712c87f3d9abfba0debe102fe2933809afd0ecf6e3c71fbe7870d99383f4fc6ca3972234e0522036829a3e20eed827b
6
+ metadata.gz: 3275b5108de6e8f5ac0bf8413d8fb53e0939855c9621fb1ac054b5ae9ecc693ff368a5d55d5e794956f75f038ba106eb4c3452c377b273eda7e064f713b6b605
7
+ data.tar.gz: 679fc2e04636143bbfcceb7fba20cb79972475313ed2731977035b1ceb21ca981d77247b03fc668c92d3e809894edeae16e5b74f92afbde55b2d21a77f287c4d
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
+ NewCops: enable
3
4
 
4
5
  # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
5
6
  Style/HashSyntax:
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
  # Changelog
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.0] - 2022-03-14
6
+ ### Added
7
+ - `content_type` option to send message by [@valeriia.kolisnyk]
8
+ - serialization/deserialization for protobuf by [@valeriia.kolisnyk]
9
+ - `json` and `protobuf_service` switchers for handler by [@valeriia.kolisnyk]
10
+
5
11
  ## [0.1.1] - 2021-06-01
6
12
  ### Added
7
13
  - `pre_publish_hooks` to configuration by [@Samuron]
data/Gemfile CHANGED
@@ -8,7 +8,9 @@ gemspec
8
8
  gem "bundler", "~> 2.1.4"
9
9
  gem "rspec", "~> 3"
10
10
  gem "rubocop", "~> 1.15"
11
+ gem "rubocop-rspec", "~> 2.3.0"
11
12
 
12
13
  gem "debase", "~> 0.2.4"
13
14
  gem "ruby-debug-ide", "~> 0.7.2"
14
15
 
16
+ gem "dry-inflector", "~> 0.2.1"
data/Gemfile.lock CHANGED
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- strum-esb (0.1.1)
4
+ strum-esb (0.3.0)
5
5
  bunny (~> 2.15)
6
6
  connection_pool (~> 2.2.2)
7
- dry-configurable (~> 0.12)
7
+ dry-configurable (~> 0.12.1)
8
+ dry-inflector (~> 0.2.1)
8
9
  json (~> 2.3)
9
10
  sneakers (~> 2.12)
10
11
 
@@ -13,10 +14,10 @@ GEM
13
14
  specs:
14
15
  amq-protocol (2.3.2)
15
16
  ast (2.4.2)
16
- bunny (2.18.0)
17
+ bunny (2.19.0)
17
18
  amq-protocol (~> 2.3, >= 2.3.1)
18
19
  sorted_set (~> 1, >= 1.0.2)
19
- concurrent-ruby (1.1.8)
20
+ concurrent-ruby (1.1.9)
20
21
  connection_pool (2.2.5)
21
22
  debase (0.2.4.1)
22
23
  debase-ruby_core_source (>= 0.10.2)
@@ -25,15 +26,16 @@ GEM
25
26
  dry-configurable (0.12.1)
26
27
  concurrent-ruby (~> 1.0)
27
28
  dry-core (~> 0.5, >= 0.5.0)
28
- dry-core (0.5.0)
29
+ dry-core (0.7.1)
29
30
  concurrent-ruby (~> 1.0)
30
- json (2.5.1)
31
+ dry-inflector (0.2.1)
32
+ json (2.6.1)
31
33
  parallel (1.20.1)
32
34
  parser (3.0.1.1)
33
35
  ast (~> 2.4.1)
34
36
  rainbow (3.0.0)
35
37
  rake (12.3.3)
36
- rbtree (0.4.4)
38
+ rbtree (0.4.5)
37
39
  regexp_parser (2.1.1)
38
40
  rexml (3.2.5)
39
41
  rspec (3.10.0)
@@ -60,12 +62,15 @@ GEM
60
62
  unicode-display_width (>= 1.4.0, < 3.0)
61
63
  rubocop-ast (1.6.0)
62
64
  parser (>= 3.0.1.1)
65
+ rubocop-rspec (2.3.0)
66
+ rubocop (~> 1.0)
67
+ rubocop-ast (>= 1.1.0)
63
68
  ruby-debug-ide (0.7.2)
64
69
  rake (>= 0.8.1)
65
70
  ruby-progressbar (1.11.0)
66
71
  serverengine (2.1.1)
67
72
  sigdump (~> 0.2.2)
68
- set (1.0.1)
73
+ set (1.0.2)
69
74
  sigdump (0.2.4)
70
75
  sneakers (2.12.0)
71
76
  bunny (~> 2.14)
@@ -76,7 +81,7 @@ GEM
76
81
  sorted_set (1.0.3)
77
82
  rbtree
78
83
  set (~> 1.0)
79
- thor (1.1.0)
84
+ thor (1.2.1)
80
85
  unicode-display_width (2.0.0)
81
86
 
82
87
  PLATFORMS
@@ -85,8 +90,10 @@ PLATFORMS
85
90
  DEPENDENCIES
86
91
  bundler (~> 2.1.4)
87
92
  debase (~> 0.2.4)
93
+ dry-inflector (~> 0.2.1)
88
94
  rspec (~> 3)
89
95
  rubocop (~> 1.15)
96
+ rubocop-rspec (~> 2.3.0)
90
97
  ruby-debug-ide (~> 0.7.2)
91
98
  strum-esb!
92
99
 
data/README.md CHANGED
@@ -30,11 +30,28 @@ Strum::Esb.config.action_exchange = `demo.actions`
30
30
  Strum::Esb.config.notice_exchange = `demo.notice`
31
31
  ```
32
32
 
33
- Pre publish hooks allow you to configure custom code that will be able to modify properties of the message that will be published or execute any arbitrary logic
33
+ Before publish hooks allow you to configure custom code that will be able to modify properties of the message that will be published or execute any arbitrary logic
34
34
  ```ruby
35
- Strum::Esb.config.pre_publish_hooks << ->(body, properties) { properties[:correlation_id] = "hello world" }
35
+ Strum::Esb.config.before_publish_hooks << ->(body, properties) { properties[:correlation_id] = "hello world" }
36
36
  ```
37
37
 
38
+ Before handler hooks allow you to inspect raw data before any processing has strated:
39
+ ```ruby
40
+ Strum::Esb.config.before_handler_hooks << ->(deserialized_msg, delivery_info, metadata) do
41
+ puts "We don't even know if msg is valid JSON, this is some text from RabbitMQ"
42
+ puts deserialized_msg
43
+ end
44
+ ```
45
+
46
+ After handler hook allows you to have insight on how handler was executed:
47
+ ```ruby
48
+ Strum::Esb.config.after_handler_hooks << ->(deserialized_msg, delivery_info, metadata, payload, error) do
49
+ puts "Message was deserialized succesfully" if payload
50
+ puts "Error occured" if error
51
+ end
52
+ ```
53
+
54
+
38
55
  ### Sending message:
39
56
 
40
57
  ```ruby
@@ -77,6 +94,45 @@ module Queues
77
94
  end
78
95
  ```
79
96
 
97
+ ### Sending protobuf via strum-esb
98
+ 1. Encode your protobuf message using `encode` method to send it.
99
+ 2. Define `protobuf_service` in your handler, and disable `json`:
100
+
101
+ ```ruby
102
+ require "strum/esb"
103
+ require "contracts/test_pb"
104
+
105
+ module Queues
106
+ class Resource
107
+ include Strum::Esb::Handler
108
+
109
+ json false
110
+ protobuf_service Test::Router::Service
111
+ from_queue "resource-queue",
112
+ bindings: {
113
+ events: %w[user/create user/update],
114
+
115
+ }
116
+
117
+ def handler(payload)
118
+ # code here ...
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
124
+ Your rpc action must have the same name as your method in queue class, for example: if method is `action_get_user`, rpc action needs to be `ActionGetUser`.
125
+
126
+ 3. Pass content type as additional param when you send Strum::Esb message:
127
+ ```ruby
128
+ Strum::Esb::Action.call("payload", "action", "resource", content_type: "application/x-protobuf")
129
+ ```
130
+ or add it as before_publish_hook:
131
+ ```ruby
132
+ Strum::Esb.config.before_publish_hooks << lambda { |_body, properties|
133
+ properties[:content_type] = "application/x-protobuf"
134
+ }
135
+ ```
80
136
 
81
137
  ## Development
82
138
 
@@ -30,10 +30,18 @@ module Strum
30
30
  def handlers
31
31
  @handlers || {}
32
32
  end
33
+
34
+ def protobuf_service(service)
35
+ Strum::Esb.config.protobuf_service = service
36
+ end
37
+
38
+ def json(switcher)
39
+ Strum::Esb.config.json = switcher
40
+ end
33
41
  end
34
42
 
35
- def work_with_params(deserialized_msg, _delivery_info, metadata)
36
- notice_data = JSON.parse(deserialized_msg)
43
+ def work_with_params(deserialized_msg, delivery_info, metadata)
44
+ Strum::Esb.config.before_handler_hooks.each { |hook| hook.call(deserialized_msg, delivery_info, metadata) }
37
45
 
38
46
  snake_case_modify = ->(string) { string.nil? ? nil : string&.to_s.gsub(/[^a-zA-Z0-9]/, "_")&.downcase }
39
47
  parse_header = ->(string) { metadata[:headers] && metadata[:headers][string] }
@@ -63,23 +71,39 @@ module Strum
63
71
 
64
72
  method_name = ([*methods_names] << "handler").find { |n| respond_to?(n, include_all: true) }
65
73
 
74
+ unless Strum::Esb.config.serializer.enabled?(metadata[:content_type])
75
+ logger.error "Content type #{metadata[:content_type]} is disabled by handler. Message rejected #{metadata[:headers]} with payload #{deserialized_msg}"
76
+ return reject!
77
+ end
78
+
79
+ payload, valid_payload = Strum::Esb.config.serializer.deserialize(deserialized_msg, metadata[:content_type], method_name)
80
+
81
+ unless valid_payload
82
+ logger.error "Content type is invalid. Message rejected #{metadata[:headers]} with payload #{payload}"
83
+ return reject!
84
+ end
85
+
66
86
  unless method_name
67
- logger.error "Handler not found. Message rejected #{metadata[:headers]} with payload #{notice_data}"
87
+ logger.error "Handler not found. Message rejected #{metadata[:headers]} with payload #{payload}"
68
88
  return reject!
69
89
  end
70
90
 
91
+ error = nil
71
92
  method_params = method(method_name)
72
93
  .parameters
73
94
  .map { |param| _, param_name = param; param_name }
74
95
  .then { |m| m & %I[action resource event state info chain] }
75
96
  handler_params = method_params.each_with_object({}) { |i, res| res[i] = eval(i.to_s); }
76
- logger.info("Handler #{method_name} found. Payload: #{notice_data}")
77
- handler_params.count.positive? ? send(method_name, notice_data, handler_params) : send(method_name, notice_data)
97
+ logger.info("Handler #{method_name} found. Payload: #{payload}")
98
+ handler_params.count.positive? ? send(method_name, payload, handler_params) : send(method_name, payload)
78
99
  logger.info("Handler #{method_name} executed")
79
100
  ack!
80
101
  rescue StandardError => e
102
+ error = e
81
103
  logger.error e
82
104
  reject!
105
+ ensure
106
+ Strum::Esb.config.after_handler_hooks.each { |hook| hook.call(deserialized_msg, delivery_info, metadata, payload, error) }
83
107
  end
84
108
 
85
109
  def after_headers_hook; end
@@ -87,7 +111,7 @@ module Strum
87
111
  private
88
112
 
89
113
  def action_handler_methods(action, resource)
90
- if custom_handler = self.class.handlers[["action", action, resource].join("-")]
114
+ if (custom_handler = self.class.handlers[["action", action, resource].join("-")])
91
115
  %W[#{custom_handler} action_#{action}_#{resource} action_handler]
92
116
  else
93
117
  %W[action_#{action}_#{resource} action_handler]
@@ -105,7 +129,7 @@ module Strum
105
129
  end
106
130
 
107
131
  def info_handler_methods(info)
108
- if custom_handler = self.class.handlers[["info", info].join("-")]
132
+ if (custom_handler = self.class.handlers[["info", info].join("-")])
109
133
  %W[#{custom_handler} info_#{info} info_handler]
110
134
  else
111
135
  %W[info_#{info} info_handler]
@@ -5,26 +5,30 @@ module Strum
5
5
  # Strum Message
6
6
  class Message
7
7
  class << self
8
- def publish(exchange:, headers:, payload:)
8
+ def publish(exchange:, headers:, payload:, content_type: "application/json")
9
9
  rabbit_exchange ||= Strum::Esb.config.rabbit_channel_pool.with { |rabbit_channel| rabbit_channel.headers(exchange, durable: true) }
10
10
  properties = {
11
11
  headers: headers,
12
- content_type: "application/json"
12
+ content_type: content_type
13
13
  }
14
14
 
15
- Strum::Esb.config.pre_publish_hooks.each do |hook|
16
- hook.call(payload, properties)
17
- end
15
+ Strum::Esb.config.before_publish_hooks.each { |hook| hook.call(payload, properties) }
18
16
 
19
17
  properties[:headers] = {} unless properties[:headers].is_a?(Hash)
20
18
  properties[:headers]["pipeline"] ||= Thread.current[:pipeline] if Thread.current[:pipeline]
21
19
  properties[:headers]["pipeline-id"] ||= Thread.current[:pipeline_id] if Thread.current[:pipeline_id]
22
- if chain = Thread.current[:chain]
20
+ if (chain = Thread.current[:chain])
23
21
  payload["chain"] ||= chain
24
22
  headers["chain"] ||= chain
25
23
  end
26
-
27
- rabbit_exchange.publish(payload.to_json, **properties)
24
+
25
+ payload, valid_payload = Strum::Esb.config.serializer.serialize(payload, properties[:content_type])
26
+ unless valid_payload
27
+ Sneakers.logger.error "Content type is invalid. Message rejected #{properties[:headers]} with payload #{payload}"
28
+ return
29
+ end
30
+
31
+ rabbit_exchange.publish(payload, **properties)
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strum
4
+ module Esb
5
+ # Payload serializer/deserializer
6
+ class Serializer
7
+ CONTENT_TYPES = %w[application/json application/x-protobuf].freeze
8
+
9
+ %i[serialize deserialize].each do |base_method|
10
+ define_method base_method do |payload, content_type, queue_method_name = nil|
11
+ return [payload, false] unless valid_content_type?(content_type)
12
+
13
+ perform_method_name = content_type_method_name(base_method, content_type)
14
+ payload = send(*[perform_method_name, payload, queue_method_name].compact)
15
+ [payload, true]
16
+ rescue StandardError
17
+ [payload, false]
18
+ end
19
+ end
20
+
21
+ def enabled?(content_type)
22
+ case data_type(content_type)
23
+ when "json"
24
+ Strum::Esb.config.json
25
+ when "protobuf"
26
+ Strum::Esb.config.protobuf_service
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def valid_content_type?(content_type)
35
+ CONTENT_TYPES.include?(content_type)
36
+ end
37
+
38
+ def content_type_method_name(action, content_type)
39
+ "#{action}_#{data_type(content_type)}"
40
+ end
41
+
42
+ def data_type(content_type)
43
+ content_type.gsub("-", "/").split("/").last
44
+ end
45
+
46
+ def deserialize_json(payload, _queue_method_name)
47
+ JSON.parse(payload)
48
+ end
49
+
50
+ def deserialize_protobuf(payload, queue_method_name)
51
+ action_name = inflector.camelize(queue_method_name)
52
+ rpc_struct = Strum::Esb.config.protobuf_service.rpc_descs[action_name.to_sym]
53
+ rpc_struct.input.decode(payload)
54
+ end
55
+
56
+ def serialize_json(payload)
57
+ payload.to_json
58
+ end
59
+
60
+ def serialize_protobuf(payload)
61
+ payload
62
+ end
63
+
64
+ def inflector
65
+ Dry::Inflector.new
66
+ end
67
+ end
68
+ end
69
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Strum
4
4
  module Esb
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/strum/esb.rb CHANGED
@@ -15,11 +15,14 @@ require "strum/esb/event"
15
15
  require "strum/esb/notice"
16
16
  require "strum/esb/info"
17
17
  require "dry/configurable"
18
+ require "dry/inflector"
19
+ require "strum/esb/serializer"
18
20
 
19
21
  module Strum
20
22
  module Esb
21
23
  extend Dry::Configurable
22
24
 
25
+ setting :sneakers_workers, ENV.fetch("SNEAKERS_WORKERS", 1)
23
26
  setting :exchange, "strum.general"
24
27
  setting :info_exchange, "strum.info"
25
28
  setting :event_exchange, "strum.events"
@@ -34,7 +37,12 @@ module Strum
34
37
  end
35
38
  setting :before_fork_hooks, []
36
39
  setting :after_fork_hooks, []
37
- setting :pre_publish_hooks, []
40
+ setting :before_publish_hooks, []
41
+ setting :before_handler_hooks, []
42
+ setting :after_handler_hooks, []
43
+ setting :serializer, Serializer.new
44
+ setting :protobuf_service, false
45
+ setting :json, true
38
46
 
39
47
  Strum::Esb.config.before_fork_hooks << proc { DB.disconnect } if defined?(DB)
40
48
 
@@ -44,8 +52,8 @@ module Strum
44
52
  class Error < StandardError; end
45
53
 
46
54
  Sneakers.configure(
47
- log: STDOUT,
48
- workers: Etc.nprocessors,
55
+ log: $stdout,
56
+ workers: Strum::Esb.config.sneakers_workers,
49
57
  hooks: {
50
58
  before_fork: -> { Strum::Esb.config.before_fork_hooks.each(&:call) },
51
59
  after_fork: -> { Strum::Esb.config.after_fork_hooks.each(&:call) }
@@ -91,8 +91,8 @@ module QueuePatch
91
91
  end
92
92
 
93
93
  def notices_subscribes(notices, **custom_headers)
94
- notices.each do |_info|
95
- exchange, resource, notice = Strum::Esb::Functions.notice_explain(_info)
94
+ notices.each do |_notice|
95
+ exchange, resource, notice = Strum::Esb::Functions.notice_explain(_notice)
96
96
  raise StandardError "notice binding format must be a `exchange:resource`" unless resource
97
97
 
98
98
  @opts[:header_bindings] << {
data/strum-esb.gemspec CHANGED
@@ -30,7 +30,8 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  spec.add_dependency "bunny", "~> 2.15"
32
32
  spec.add_dependency "connection_pool", "~> 2.2.2"
33
+ spec.add_dependency "dry-configurable", "~> 0.12.1"
34
+ spec.add_dependency "dry-inflector", "~> 0.2.1"
33
35
  spec.add_dependency "json", "~> 2.3"
34
36
  spec.add_dependency "sneakers", "~> 2.12"
35
- spec.add_dependency "dry-configurable", "~> 0.12"
36
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strum-esb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Serhiy Nazarov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-02 00:00:00.000000000 Z
11
+ date: 2022-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -39,47 +39,61 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.2.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: json
42
+ name: dry-configurable
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.3'
47
+ version: 0.12.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.3'
54
+ version: 0.12.1
55
55
  - !ruby/object:Gem::Dependency
56
- name: sneakers
56
+ name: dry-inflector
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '2.12'
61
+ version: 0.2.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '2.12'
68
+ version: 0.2.1
69
69
  - !ruby/object:Gem::Dependency
70
- name: dry-configurable
70
+ name: json
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.12'
75
+ version: '2.3'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '0.12'
82
+ version: '2.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sneakers
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.12'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.12'
83
97
  description: Publish and subscribe rabbitMQ messages
84
98
  email:
85
99
  - sn@nazarov.com.ua
@@ -107,6 +121,7 @@ files:
107
121
  - lib/strum/esb/info.rb
108
122
  - lib/strum/esb/message.rb
109
123
  - lib/strum/esb/notice.rb
124
+ - lib/strum/esb/serializer.rb
110
125
  - lib/strum/esb/version.rb
111
126
  - lib/strum/patch/sneakers/queue_patch.rb
112
127
  - strum-esb.gemspec