shared_broker 1.0.0 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bd8ee9106eaae6c3d6dc646f85ba199a94788dc05d4f295401867af33067f1e
4
- data.tar.gz: 53f5375846be98871776de4756caad2143ca9c3732cf5b012df0bdeaabea840a
3
+ metadata.gz: 545d29807ba6d68209d43f93f98b81b0e30532efe6d799d5df4f59650e8c44f8
4
+ data.tar.gz: 5eb9ac3cf999257d3c9f94db10140aee626ed42c1658dd727ca4e2b05f25eee9
5
5
  SHA512:
6
- metadata.gz: 20296dd5d51625729e336b9bbb8e224ba9e4964cd6d4b2a5561c250f4e72f7329760de8d53c91809d3be1bb779bc516d773d3a4158c7e0d605d3c93385cf16a0
7
- data.tar.gz: 630b5a633699b1fc8ff6707ca9e8ce61dbad0004a6d0cd2e0dc98f5c223a29de75e609c9012e2d5b43c50262da5c48e8f4919ab0345e11e2a0e15db458e5d8a2
6
+ metadata.gz: 64d138f86ba1e88e86b2a65224df35946972843c928a73531b92b00e21bda3ab102a45b32e51db09a5c1f1d1ef7a6b9983e04978c5597408f74537ce01cbec1d
7
+ data.tar.gz: 7239a6c03ae9b78a8682e579a7d6becdd3c3edc782f8df587d13440d26f3f96e3695c96768890cb110372c3e6819f81bbd40ea90f628c21d03427743275dabb3
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
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.1.0] - Unreleased
9
+
10
+ ### Added
11
+ - Phase 4: Pipeline of customizable Middlewares (Interceptors) for publishing and subscribing messages.
12
+ - Phase 4: Distributed Tracing using W3C Trace Context (`traceparent`/`tracestate`) injection and extraction.
13
+
14
+ ## [1.0.0] - 2026-06-06
15
+
16
+ ### Added
17
+ - Phase 1: Fault Tolerance & Resilience features:
18
+ - Exponential backoff retry loop for message processing.
19
+ - Automatic Dead Letter Queue (DLQ) routing with rich metadata headers (`x_failed_at`, `x_exception_class`, `x_exception_message`, `x_original_routing_key`).
20
+ - Thread-safe `CircuitBreaker` wrapping all outbound publisher calls.
21
+ - Phase 2: Schema Validation and Security:
22
+ - Outbound/Inbound boundary validation using `dry-schema`.
23
+ - Transparent AES-256-GCM symmetric payload encryption by default, configurable with `SharedBroker.encryption_key`.
24
+ - Phase 3: Scalable Adapters:
25
+ - Apache Kafka adapter (`SharedBroker::Adapters::Kafka`) with dynamic dependency loading.
26
+ - Redis Pub/Sub adapter (`SharedBroker::Adapters::Redis`) with list-based DLQ routing.
27
+ - Comprehensive test coverage for all the above features using isolated fakes and Minitest.
28
+
29
+ ## [0.1.0] - 2026-06-06
30
+
31
+ ### Added
32
+ - Initial release with the pluggable `Client` messaging system.
33
+ - `InMemory` adapter for local testing.
34
+ - `RabbitMQ` adapter using the `bunny` gem.
35
+ - 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
- # 1. Configure Validation Schemas (dry-schema)
53
- user_created_schema = Dry::Schema.Params do
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
- # 3. Configure the Adapter based on Environment
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 or Redis for development
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
- # 4. Instantiate the Client by Injecting the Adapter and optional custom Circuit Breaker configuration
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 (Advanced Features)
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
- # 5. Initialize Telemetry (OpenTelemetry)
110
+ ```ruby
88
111
  SharedBroker::Telemetry.configure(service_name: "my_microservice")
89
112
  ```
90
113
 
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "json"
5
+ require "time"
6
+
7
+ module SharedBroker
8
+ module Adapters
9
+ class Redis < Base
10
+ def initialize(redis_url:)
11
+ begin
12
+ require "redis"
13
+ rescue LoadError
14
+ raise unless defined?(::Redis)
15
+ end
16
+ @redis = ::Redis.new(url: redis_url)
17
+ end
18
+
19
+ def publish(topic, message, correlation_id: nil)
20
+ unless message.is_a?(Hash)
21
+ raise ArgumentError, "Expected message to be a Hash, got #{message.class} with value #{message.inspect}"
22
+ end
23
+
24
+ payload = message.merge(_correlation_id: correlation_id)
25
+ @redis.publish(topic, payload.to_json)
26
+ end
27
+
28
+ def subscribe(topic, queue_name, max_retries: 3, backoff_base: 2, &block)
29
+ Thread.new do
30
+ @redis.subscribe(topic) do |on|
31
+ on.message do |_channel, msg_json|
32
+ data = JSON.parse(msg_json, symbolize_names: true)
33
+ attempts = 0
34
+ begin
35
+ block.call(data)
36
+ rescue => e
37
+ attempts += 1
38
+ if attempts <= max_retries
39
+ sleep(backoff_base**attempts)
40
+ retry
41
+ else
42
+ publish_to_dlq(topic, queue_name, msg_json, e)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def publish_to_dlq(topic, queue_name, payload_json, exception)
53
+ dlq_key = "dlq:#{topic}:#{queue_name}"
54
+ dlq_payload = {
55
+ payload: JSON.parse(payload_json, symbolize_names: true),
56
+ x_failed_at: Time.now.utc.iso8601,
57
+ x_exception_class: exception.class.name,
58
+ x_exception_message: exception.message
59
+ }
60
+ @redis.rpush(dlq_key, dlq_payload.to_json)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SharedBroker
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shared_broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gemini Antigravity
@@ -130,6 +130,7 @@ 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
@@ -137,18 +138,19 @@ files:
137
138
  - lib/shared_broker/adapters/in_memory.rb
138
139
  - lib/shared_broker/adapters/kafka.rb
139
140
  - lib/shared_broker/adapters/rabbit_mq.rb
141
+ - lib/shared_broker/adapters/redis.rb
140
142
  - lib/shared_broker/cipher.rb
141
143
  - lib/shared_broker/circuit_breaker.rb
142
144
  - lib/shared_broker/telemetry.rb
143
145
  - lib/shared_broker/validation.rb
144
146
  - lib/shared_broker/version.rb
145
147
  - sig/shared_broker.rbs
146
- homepage: https://github.com/onkai/shared_broker
148
+ homepage: https://github.com/wesleyskap/shared_broker
147
149
  licenses:
148
150
  - MIT
149
151
  metadata:
150
- source_code_uri: https://github.com/onkai/shared_broker
151
- changelog_uri: https://github.com/onkai/shared_broker/blob/main/CHANGELOG.md
152
+ source_code_uri: https://github.com/wesleyskap/shared_broker
153
+ changelog_uri: https://github.com/wesleyskap/shared_broker/blob/main/CHANGELOG.md
152
154
  post_install_message:
153
155
  rdoc_options: []
154
156
  require_paths: