shared_broker 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +40 -17
- data/lib/shared_broker/middleware_pipeline.rb +25 -0
- data/lib/shared_broker/middlewares/open_telemetry_propagation.rb +39 -0
- data/lib/shared_broker/version.rb +1 -1
- data/lib/shared_broker.rb +63 -51
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d47439f3383e9d40d13664b67dc8b77318b64f628e6a1efa0f4684130ff2ff4b
|
|
4
|
+
data.tar.gz: 85d081f58ac1608a1629b29f14c634b370b963c65338eb7f9a32ace26bc90502
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25151e9182b26d17334f04b121831703eee6c8b95981ac2b235085472501f06ec97c2a6b14cedc434dd3ee49963577b8eceedc175680525025d31e3246ac81dd
|
|
7
|
+
data.tar.gz: 07cb36e946dd24260fdd70d7cd995f4aeae8167b764d1df18e2026bc523481ec4d788308f720494472985a3b1a4adc7ae8751ded5abbd20b02c8c475c10a97c6
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.2.0] - 2026-06-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Pipeline of customizable Middlewares (Interceptors) for publishing and subscribing messages.
|
|
12
|
+
- Distributed Tracing using W3C Trace Context (`traceparent`/`tracestate`) context injection and extraction.
|
|
13
|
+
|
|
14
|
+
## [1.1.1] - 2026-06-06
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Restructured `README.md` to clearly separate minimum required configuration from optional/advanced setups.
|
|
18
|
+
|
|
19
|
+
## [1.1.0] - 2026-06-06
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Scalable Adapters:
|
|
23
|
+
- Apache Kafka adapter (`SharedBroker::Adapters::Kafka`) with dynamic dependency loading.
|
|
24
|
+
- Redis Pub/Sub adapter (`SharedBroker::Adapters::Redis`) with list-based DLQ routing.
|
|
25
|
+
- Mock test configurations for Kafka and Redis to run adapter unit tests.
|
|
26
|
+
|
|
27
|
+
## [1.0.0] - 2026-06-06
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Fault Tolerance & Resilience features:
|
|
31
|
+
- Exponential backoff retry loop for message processing.
|
|
32
|
+
- Automatic Dead Letter Queue (DLQ) routing with rich metadata headers (`x_failed_at`, `x_exception_class`, `x_exception_message`, `x_original_routing_key`).
|
|
33
|
+
- Thread-safe `CircuitBreaker` wrapping all outbound publisher calls.
|
|
34
|
+
- Schema Validation and Security:
|
|
35
|
+
- Outbound/Inbound boundary validation using `dry-schema`.
|
|
36
|
+
- Transparent AES-256-GCM symmetric payload encryption by default, configurable with `SharedBroker.encryption_key`.
|
|
37
|
+
- Comprehensive test coverage for all the above features using isolated fakes and Minitest.
|
|
38
|
+
|
|
39
|
+
## [0.1.0] - 2026-06-06
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- Initial release with the pluggable `Client` messaging system.
|
|
43
|
+
- `InMemory` adapter for local testing.
|
|
44
|
+
- `RabbitMQ` adapter using the `bunny` gem.
|
|
45
|
+
- Basic OpenTelemetry instrumentation utility (`SharedBroker::Telemetry`).
|
data/README.md
CHANGED
|
@@ -44,36 +44,55 @@ bundle install
|
|
|
44
44
|
|
|
45
45
|
## Configuration
|
|
46
46
|
|
|
47
|
-
Create an initializer in your Rails application (`config/initializers/shared_broker.rb`)
|
|
47
|
+
Create an initializer in your Rails application (`config/initializers/shared_broker.rb`). Below is the breakdown of what is **required** versus what is **optional**.
|
|
48
|
+
|
|
49
|
+
### 1. Required Configuration (Minimum Setup)
|
|
50
|
+
|
|
51
|
+
You must configure the adapter depending on the environment, initialize the client, and configure the payload encryption key (required since AES-256-GCM is active by default):
|
|
48
52
|
|
|
49
53
|
```ruby
|
|
50
54
|
require "shared_broker"
|
|
51
55
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
required(:id).filled(:integer)
|
|
55
|
-
required(:email).filled(:string)
|
|
56
|
-
end
|
|
57
|
-
SharedBroker::Validation.register("user.created", user_created_schema)
|
|
58
|
-
|
|
59
|
-
# 2. Configure Payload Encryption Key (AES-256-GCM)
|
|
60
|
-
# Expects a 32-byte string. Default key is used in development if ENV is not set.
|
|
56
|
+
# A. Configure Payload Encryption Key (AES-256-GCM)
|
|
57
|
+
# Expects a 32-byte string. Use a secure production key in production.
|
|
61
58
|
SharedBroker.encryption_key = ENV.fetch("SHARED_BROKER_ENCRYPTION_KEY") { "a" * 32 }
|
|
62
59
|
|
|
63
|
-
#
|
|
60
|
+
# B. Configure the Adapter based on Environment
|
|
64
61
|
if Rails.env.test?
|
|
65
62
|
# In-memory adapter prevents external queue dependency during unit tests
|
|
66
63
|
BROKER_ADAPTER = SharedBroker::Adapters::InMemory.new
|
|
67
|
-
elsif Rails.env.production?
|
|
68
|
-
# High-throughput production setup using Kafka
|
|
69
|
-
BROKER_ADAPTER = SharedBroker::Adapters::Kafka.new(seed_brokers: ["kafka-1:9092", "kafka-2:9092"])
|
|
70
64
|
else
|
|
71
|
-
# Connects to RabbitMQ
|
|
65
|
+
# Connects to real RabbitMQ broker
|
|
72
66
|
amqp_url = ENV.fetch("RABBITMQ_URL") { "amqp://guest:guest@localhost:5672" }
|
|
73
67
|
BROKER_ADAPTER = SharedBroker::Adapters::RabbitMQ.new(amqp_url: amqp_url)
|
|
74
68
|
end
|
|
75
69
|
|
|
76
|
-
#
|
|
70
|
+
# C. Instantiate the Client by Injecting the Adapter
|
|
71
|
+
SPOT_BROKER = SharedBroker::Client.new(adapter: BROKER_ADAPTER)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### 2. Optional Configuration
|
|
77
|
+
|
|
78
|
+
These features can be configured optionally depending on your needs.
|
|
79
|
+
|
|
80
|
+
#### A. Event Payload Validation (dry-schema)
|
|
81
|
+
Register schemas to validate payload structure automatically on outbound (`publish`) and inbound (`subscribe`) boundaries:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
user_created_schema = Dry::Schema.Params do
|
|
85
|
+
required(:id).filled(:integer)
|
|
86
|
+
required(:email).filled(:string)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
SharedBroker::Validation.register("user.created", user_created_schema)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### B. Custom Circuit Breaker
|
|
93
|
+
By default, the client instantiates a standard Circuit Breaker. You can provide a custom one to tune the failure threshold and recovery window:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
77
96
|
custom_circuit_breaker = SharedBroker::CircuitBreaker.new(
|
|
78
97
|
failure_threshold: 5, # trip circuit after 5 failures
|
|
79
98
|
recovery_timeout: 30 # wait 30 seconds before attempting recovery
|
|
@@ -83,8 +102,12 @@ SPOT_BROKER = SharedBroker::Client.new(
|
|
|
83
102
|
adapter: BROKER_ADAPTER,
|
|
84
103
|
circuit_breaker: custom_circuit_breaker
|
|
85
104
|
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### C. Initialize Distributed Tracing (OpenTelemetry)
|
|
108
|
+
Initialize the OpenTelemetry SDK with auto-instrumentation for the microservice:
|
|
86
109
|
|
|
87
|
-
|
|
110
|
+
```ruby
|
|
88
111
|
SharedBroker::Telemetry.configure(service_name: "my_microservice")
|
|
89
112
|
```
|
|
90
113
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SharedBroker
|
|
4
|
+
class MiddlewarePipeline
|
|
5
|
+
def initialize(middlewares)
|
|
6
|
+
@middlewares = Array(middlewares)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def execute(topic, message, metadata = {}, &block)
|
|
10
|
+
run_middleware(0, topic, message, metadata, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def run_middleware(index, topic, message, metadata, &block)
|
|
16
|
+
if index >= @middlewares.size
|
|
17
|
+
yield
|
|
18
|
+
else
|
|
19
|
+
@middlewares[index].call(topic, message, metadata) do
|
|
20
|
+
run_middleware(index + 1, topic, message, metadata, &block)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "opentelemetry"
|
|
4
|
+
|
|
5
|
+
module SharedBroker
|
|
6
|
+
module Middlewares
|
|
7
|
+
class OpenTelemetryPropagation
|
|
8
|
+
def call(topic, message, metadata)
|
|
9
|
+
if metadata[:operation] == :publish
|
|
10
|
+
carrier = {}
|
|
11
|
+
::OpenTelemetry.propagation.inject(carrier)
|
|
12
|
+
carrier.each do |key, value|
|
|
13
|
+
message["_#{key}".to_sym] = value
|
|
14
|
+
end
|
|
15
|
+
yield
|
|
16
|
+
elsif metadata[:operation] == :subscribe
|
|
17
|
+
carrier = {}
|
|
18
|
+
message.each do |key, value|
|
|
19
|
+
if key.to_s.start_with?("_trace")
|
|
20
|
+
carrier[key.to_s.sub(/^_/, "")] = value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
parent_context = ::OpenTelemetry.propagation.extract(carrier)
|
|
25
|
+
::OpenTelemetry::Context.with_current(parent_context) do
|
|
26
|
+
tracer = ::OpenTelemetry.tracer_provider.tracer("shared_broker")
|
|
27
|
+
tracer.in_span("#{topic} process", kind: :consumer) do |span|
|
|
28
|
+
span.set_attribute("messaging.system", "shared_broker")
|
|
29
|
+
span.set_attribute("messaging.destination", topic)
|
|
30
|
+
yield
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
yield
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/shared_broker.rb
CHANGED
|
@@ -1,51 +1,63 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "shared_broker/version"
|
|
4
|
-
require_relative "shared_broker/telemetry"
|
|
5
|
-
require_relative "shared_broker/circuit_breaker"
|
|
6
|
-
require_relative "shared_broker/validation"
|
|
7
|
-
require_relative "shared_broker/cipher"
|
|
8
|
-
require_relative "shared_broker/
|
|
9
|
-
require_relative "shared_broker/
|
|
10
|
-
require_relative "shared_broker/adapters/
|
|
11
|
-
require_relative "shared_broker/adapters/
|
|
12
|
-
require_relative "shared_broker/adapters/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
SharedBroker::
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "shared_broker/version"
|
|
4
|
+
require_relative "shared_broker/telemetry"
|
|
5
|
+
require_relative "shared_broker/circuit_breaker"
|
|
6
|
+
require_relative "shared_broker/validation"
|
|
7
|
+
require_relative "shared_broker/cipher"
|
|
8
|
+
require_relative "shared_broker/middleware_pipeline"
|
|
9
|
+
require_relative "shared_broker/middlewares/open_telemetry_propagation"
|
|
10
|
+
require_relative "shared_broker/adapters/base"
|
|
11
|
+
require_relative "shared_broker/adapters/in_memory"
|
|
12
|
+
require_relative "shared_broker/adapters/rabbit_mq"
|
|
13
|
+
require_relative "shared_broker/adapters/kafka"
|
|
14
|
+
require_relative "shared_broker/adapters/redis"
|
|
15
|
+
|
|
16
|
+
module SharedBroker
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :encryption_key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Default key for development/test if not set
|
|
22
|
+
@encryption_key = ENV.fetch("SHARED_BROKER_ENCRYPTION_KEY") { "a" * 32 }
|
|
23
|
+
|
|
24
|
+
class Client
|
|
25
|
+
attr_reader :circuit_breaker, :middleware_pipeline
|
|
26
|
+
|
|
27
|
+
def initialize(adapter:, circuit_breaker: nil, middlewares: nil)
|
|
28
|
+
unless adapter.respond_to?(:publish) && adapter.respond_to?(:subscribe)
|
|
29
|
+
raise ArgumentError, "Expected adapter to respond to :publish and :subscribe, got #{adapter.class} with value #{adapter.inspect}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@adapter = adapter
|
|
33
|
+
@circuit_breaker = circuit_breaker || CircuitBreaker.new
|
|
34
|
+
|
|
35
|
+
resolved_middlewares = middlewares || [SharedBroker::Middlewares::OpenTelemetryPropagation.new]
|
|
36
|
+
@middleware_pipeline = MiddlewarePipeline.new(resolved_middlewares)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def publish(topic, message, correlation_id: nil)
|
|
40
|
+
metadata = { correlation_id: correlation_id, operation: :publish }
|
|
41
|
+
@middleware_pipeline.execute(topic, message, metadata) do
|
|
42
|
+
SharedBroker::Validation.validate!(topic, message)
|
|
43
|
+
encrypted_msg = SharedBroker::Cipher.encrypt(message, SharedBroker.encryption_key)
|
|
44
|
+
|
|
45
|
+
@circuit_breaker.run do
|
|
46
|
+
@adapter.publish(topic, encrypted_msg, correlation_id: correlation_id)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def subscribe(topic, queue_name, max_retries: 3, backoff_base: 2, &block)
|
|
52
|
+
@adapter.subscribe(topic, queue_name, max_retries: max_retries, backoff_base: backoff_base) do |raw_message|
|
|
53
|
+
decrypted_msg = SharedBroker::Cipher.decrypt(raw_message, SharedBroker.encryption_key)
|
|
54
|
+
SharedBroker::Validation.validate!(topic, decrypted_msg)
|
|
55
|
+
|
|
56
|
+
metadata = { correlation_id: decrypted_msg[:_correlation_id], operation: :subscribe, queue_name: queue_name }
|
|
57
|
+
@middleware_pipeline.execute(topic, decrypted_msg, metadata) do
|
|
58
|
+
block.call(decrypted_msg)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shared_broker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Wesley Lima
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bunny
|
|
@@ -125,11 +125,12 @@ dependencies:
|
|
|
125
125
|
description: Shared library for asynchronous messaging, distributed tracing, and customizable
|
|
126
126
|
adapters.
|
|
127
127
|
email:
|
|
128
|
-
-
|
|
128
|
+
- wesleyskap@gmail.com
|
|
129
129
|
executables: []
|
|
130
130
|
extensions: []
|
|
131
131
|
extra_rdoc_files: []
|
|
132
132
|
files:
|
|
133
|
+
- CHANGELOG.md
|
|
133
134
|
- README.md
|
|
134
135
|
- Rakefile
|
|
135
136
|
- lib/shared_broker.rb
|
|
@@ -140,6 +141,8 @@ files:
|
|
|
140
141
|
- lib/shared_broker/adapters/redis.rb
|
|
141
142
|
- lib/shared_broker/cipher.rb
|
|
142
143
|
- lib/shared_broker/circuit_breaker.rb
|
|
144
|
+
- lib/shared_broker/middleware_pipeline.rb
|
|
145
|
+
- lib/shared_broker/middlewares/open_telemetry_propagation.rb
|
|
143
146
|
- lib/shared_broker/telemetry.rb
|
|
144
147
|
- lib/shared_broker/validation.rb
|
|
145
148
|
- lib/shared_broker/version.rb
|
|
@@ -149,7 +152,7 @@ licenses:
|
|
|
149
152
|
- MIT
|
|
150
153
|
metadata:
|
|
151
154
|
source_code_uri: https://github.com/wesleyskap/shared_broker
|
|
152
|
-
changelog_uri: https://github.com/wesleyskap/shared_broker/blob/
|
|
155
|
+
changelog_uri: https://github.com/wesleyskap/shared_broker/blob/master/CHANGELOG.md
|
|
153
156
|
post_install_message:
|
|
154
157
|
rdoc_options: []
|
|
155
158
|
require_paths:
|