bug_bunny 3.0.0 → 3.0.2
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 +36 -0
- data/README.md +172 -161
- data/lib/bug_bunny/client.rb +22 -39
- data/lib/bug_bunny/config.rb +10 -0
- data/lib/bug_bunny/consumer.rb +91 -57
- data/lib/bug_bunny/controller.rb +131 -57
- data/lib/bug_bunny/producer.rb +10 -2
- data/lib/bug_bunny/request.rb +30 -70
- data/lib/bug_bunny/resource.rb +179 -226
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -1
- metadata +21 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 42bf10dd1d3e749561b43b298e6dda00f9b6a76a40dce49103d1d094faa39948
|
|
4
|
+
data.tar.gz: 3088ecec60567a95972ed7abc7dff8f6dd9f5019ad6e0a19bb15dcae61e68bb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccf976738d355512f3effec2d84a94087c62b41b329516df4cb15ca99034385da216592a7b52acec6aa224708b7c5c272f17dde349a8d5fe88f3903e33ff1122
|
|
7
|
+
data.tar.gz: 60252a5667d1178b4ac9de5118dd21cfd5538c12c9f69360988f493ffea50c81e4efec2cc08f50570fc9e76c9a57d9bb08c1ef6d75aef4a6b25f995fc765b512
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.0.2] - 2026-02-12
|
|
4
|
+
|
|
5
|
+
### 🚀 Features
|
|
6
|
+
* **Automatic Parameter Wrapping:** `BugBunny::Resource` now automatically wraps the payload inside a root key derived from the model name (e.g., `Manager::Service` -> `{ service: { ... } }`). This mimics Rails form behavior and enables the use of `params.require(:service)` in controllers.
|
|
7
|
+
* Added `self.param_key = '...'` to `BugBunny::Resource` to allow custom root keys.
|
|
8
|
+
* **Declarative Error Handling:** Added Rails-like `rescue_from` DSL to `BugBunny::Controller`. You can now register exception handlers at the class level without overriding methods manually.
|
|
9
|
+
```ruby
|
|
10
|
+
rescue_from ActiveRecord::RecordNotFound do |e|
|
|
11
|
+
render status: :not_found, json: { error: e.message }
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
* **RPC Timeouts on Crash:** Fixed a critical issue where the Client would hang until timeout (`BugBunny::RequestTimeout`) if the Consumer crashed or the route was not found.
|
|
17
|
+
* The Consumer now catches `NameError` (Route not found) and returns a **501 Not Implemented**.
|
|
18
|
+
* The Consumer catches unhandled `StandardError` (App crash) and returns a **500 Internal Server Error**.
|
|
19
|
+
* Ensures a reply is ALWAYS sent to the caller, preventing blocking processes.
|
|
20
|
+
|
|
21
|
+
### 🛠 Improvements
|
|
22
|
+
* **Controller:** Refactored `BugBunny::Controller` to include a default safety net that catches unhandled errors and logs them properly before returning a 500 status.
|
|
23
|
+
|
|
24
|
+
## [3.0.1] - 2026-02-10
|
|
25
|
+
|
|
26
|
+
### 🚀 Features: RESTful Architecture
|
|
27
|
+
* **HTTP Verbs over AMQP:** Implemented support for semantic HTTP verbs (`GET`, `POST`, `PUT`, `DELETE`) within AMQP headers (`x-http-method`). This enables a true RESTful design over RabbitMQ.
|
|
28
|
+
* **Smart Router:** The `BugBunny::Consumer` now behaves like a Rails Router. It automatically infers the controller action based on the combination of the **Verb** and the **URL Path** (e.g., `GET users/1` dispatches to `show`, `POST users` to `create`).
|
|
29
|
+
* **Resource CRUD Mapping:** `BugBunny::Resource` now maps Ruby operations to their specific REST verbs:
|
|
30
|
+
* `create` -> `POST`
|
|
31
|
+
* `update` -> `PUT`
|
|
32
|
+
* `destroy` -> `DELETE`
|
|
33
|
+
* `find/where` -> `GET`.
|
|
34
|
+
|
|
35
|
+
### 🛠 Improvements
|
|
36
|
+
* **Client API:** Updated `BugBunny::Client#request` and `#publish` to accept a `method:` argument (e.g., `client.request('users', method: :post)`), giving developers full control over the request semantics without changing the method signature.
|
|
37
|
+
* **Request Metadata:** `BugBunny::Request` now handles the `method` attribute and ensures it is properly injected into the AMQP headers for the consumer to read.
|
|
38
|
+
|
|
3
39
|
## [3.0.0] - 2026-02-05
|
|
4
40
|
|
|
5
41
|
### ⚠ Breaking Changes
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**BugBunny** es un framework RPC para Ruby on Rails sobre **RabbitMQ**.
|
|
4
4
|
|
|
5
|
-
Su filosofía es **"Active Record over AMQP"**.
|
|
5
|
+
Su filosofía es **"Active Record over AMQP"**. Transforma la complejidad de la mensajería asíncrona en una arquitectura **RESTful simulada**. Los mensajes viajan con Verbos HTTP (`GET`, `POST`, `PUT`, `DELETE`) inyectados en los headers AMQP, permitiendo que un **Router Inteligente** despache las peticiones a controladores Rails estándar.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -20,251 +20,262 @@ Ejecuta el bundle:
|
|
|
20
20
|
bundle install
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Genera los archivos de configuración iniciales:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
rails g bug_bunny:install
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Esto creará:
|
|
30
|
+
1. `config/initializers/bug_bunny.rb`
|
|
31
|
+
2. `app/rabbit/controllers/`
|
|
32
|
+
|
|
29
33
|
---
|
|
30
34
|
|
|
31
35
|
## ⚙️ Configuración
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
### 1. Inicializador y Logging
|
|
38
|
+
|
|
39
|
+
BugBunny separa los logs de la aplicación (Requests) de los logs del driver (Heartbeats/Frames) para mantener la consola limpia.
|
|
34
40
|
|
|
35
41
|
```ruby
|
|
36
42
|
# config/initializers/bug_bunny.rb
|
|
37
43
|
|
|
38
44
|
BugBunny.configure do |config|
|
|
45
|
+
# --- Credenciales ---
|
|
39
46
|
config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
|
|
40
47
|
config.username = ENV.fetch('RABBITMQ_USER', 'guest')
|
|
41
48
|
config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
|
|
42
49
|
config.vhost = ENV.fetch('RABBITMQ_VHOST', '/')
|
|
43
50
|
|
|
44
|
-
# Timeouts
|
|
45
|
-
config.rpc_timeout = 10
|
|
46
|
-
config.network_recovery_interval = 5
|
|
51
|
+
# --- Timeouts ---
|
|
52
|
+
config.rpc_timeout = 10 # Timeout para esperar respuesta (Síncrono)
|
|
53
|
+
config.network_recovery_interval = 5 # Segundos para reintentar conexión
|
|
54
|
+
|
|
55
|
+
# --- Logging (Niveles recomendados) ---
|
|
56
|
+
# Logger de BugBunny: Muestra tus requests (INFO)
|
|
57
|
+
config.logger = Logger.new(STDOUT)
|
|
58
|
+
config.logger.level = Logger::INFO
|
|
59
|
+
|
|
60
|
+
# Logger de Bunny (Driver): Silencia el ruido de bajo nivel (WARN)
|
|
61
|
+
config.bunny_logger = Logger.new(STDOUT)
|
|
62
|
+
config.bunny_logger.level = Logger::WARN
|
|
47
63
|
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Connection Pool (Crítico) 🧵
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
Para entornos concurrentes como **Puma** o **Sidekiq**, es **obligatorio** definir un Pool de conexiones global. BugBunny no gestiona hilos automáticamente sin esta configuración.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# config/initializers/bug_bunny.rb
|
|
72
|
+
|
|
73
|
+
# Define el pool global (ajusta el tamaño según tus hilos de Puma/Sidekiq)
|
|
50
74
|
BUG_BUNNY_POOL = ConnectionPool.new(size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i, timeout: 5) do
|
|
51
75
|
BugBunny.create_connection
|
|
52
76
|
end
|
|
53
77
|
|
|
54
|
-
#
|
|
78
|
+
# Inyecta el pool a los recursos para que lo usen automáticamente
|
|
55
79
|
BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
|
|
56
80
|
```
|
|
57
81
|
|
|
58
82
|
---
|
|
59
83
|
|
|
60
|
-
## 🚀 Modo Resource (ORM /
|
|
84
|
+
## 🚀 Modo Resource (ORM / Cliente)
|
|
61
85
|
|
|
62
|
-
Define modelos que actúan como
|
|
86
|
+
Define modelos que actúan como proxies de recursos remotos. BugBunny se encarga de serializar, "wrappear" parámetros y enviar el verbo correcto.
|
|
63
87
|
|
|
64
|
-
###
|
|
65
|
-
Ideal cuando quieres enrutar por acción. La Routing Key se genera automáticamente usando `resource_name.action`.
|
|
88
|
+
### Definición del Modelo
|
|
66
89
|
|
|
67
90
|
```ruby
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
# Define el nombre lógico del recurso.
|
|
74
|
-
# 1. Routing Key: 'users.create', 'users.show.12'
|
|
75
|
-
# 2. Header Type: 'users/create', 'users/show/12'
|
|
76
|
-
self.resource_name = 'users'
|
|
77
|
-
|
|
78
|
-
# No necesitas definir atributos, BugBunny soporta atributos dinámicos (Schema-less)
|
|
79
|
-
end
|
|
80
|
-
```
|
|
91
|
+
# app/models/manager/service.rb
|
|
92
|
+
class Manager::Service < BugBunny::Resource
|
|
93
|
+
# 1. Configuración de Transporte
|
|
94
|
+
self.exchange = 'box_cluster_manager'
|
|
95
|
+
self.exchange_type = 'direct'
|
|
81
96
|
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
# 2. Configuración Lógica (Routing)
|
|
98
|
+
# Define la URL base y la routing key por defecto.
|
|
99
|
+
self.resource_name = 'services'
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
self.
|
|
89
|
-
self.exchange_type = 'direct'
|
|
90
|
-
|
|
91
|
-
# FORZAMOS LA ROUTING KEY.
|
|
92
|
-
# Todo viaja con esta key, sin importar la acción.
|
|
93
|
-
self.routing_key = 'manager_queue'
|
|
94
|
-
|
|
95
|
-
# Define el nombre lógico para el Controlador.
|
|
96
|
-
# Header Type: 'box_manager/create', 'box_manager/show/12'
|
|
97
|
-
self.resource_name = 'box_manager'
|
|
101
|
+
# 3. Wrapping de Parámetros (Opcional)
|
|
102
|
+
# Por defecto usa el nombre del modelo sin módulo (Manager::Service -> 'service').
|
|
103
|
+
# Puedes forzarlo con:
|
|
104
|
+
# self.param_key = 'docker_service'
|
|
98
105
|
end
|
|
99
106
|
```
|
|
100
107
|
|
|
101
|
-
###
|
|
108
|
+
### CRUD RESTful
|
|
102
109
|
|
|
103
|
-
|
|
110
|
+
Las operaciones de Ruby se traducen a verbos HTTP sobre AMQP.
|
|
104
111
|
|
|
105
112
|
```ruby
|
|
106
|
-
# ---
|
|
107
|
-
#
|
|
108
|
-
# Routing Key: "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
user.destroy
|
|
113
|
+
# --- LEER (GET) ---
|
|
114
|
+
# Envia: GET services
|
|
115
|
+
# Routing Key: "services"
|
|
116
|
+
services = Manager::Service.all
|
|
117
|
+
|
|
118
|
+
# Envia: GET services/123
|
|
119
|
+
service = Manager::Service.find('123')
|
|
120
|
+
|
|
121
|
+
# --- CREAR (POST) ---
|
|
122
|
+
# Envia: POST services
|
|
123
|
+
# Body: { "service": { "name": "nginx", "replicas": 3 } }
|
|
124
|
+
# Nota: Envuelve los params automáticamente en la clave 'service'.
|
|
125
|
+
svc = Manager::Service.create(name: 'nginx', replicas: 3)
|
|
126
|
+
|
|
127
|
+
# --- ACTUALIZAR (PUT) ---
|
|
128
|
+
# Envia: PUT services/123
|
|
129
|
+
# Body: { "service": { "replicas": 5 } }
|
|
130
|
+
svc.update(replicas: 5)
|
|
131
|
+
|
|
132
|
+
# --- ELIMINAR (DELETE) ---
|
|
133
|
+
# Envia: DELETE services/123
|
|
134
|
+
svc.destroy
|
|
129
135
|
```
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## 🔌 Modo Publisher (Cliente Manual)
|
|
134
|
-
|
|
135
|
-
Si no necesitas mapear un recurso o quieres enviar mensajes crudos ("Fire-and-Forget"), puedes usar `BugBunny::Client` directamente.
|
|
136
|
-
|
|
137
|
-
### 1. Instanciar el Cliente
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
# Puedes inyectar middlewares personalizados aquí si lo deseas
|
|
141
|
-
client = BugBunny::Client.new(pool: BUG_BUNNY_POOL) do |conn|
|
|
142
|
-
conn.use BugBunny::Middleware::JsonResponse
|
|
143
|
-
end
|
|
144
|
-
```
|
|
137
|
+
### Contexto Dinámico (`.with`)
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
Envía el mensaje y retorna inmediatamente. Ideal para eventos o logs.
|
|
139
|
+
Puedes cambiar la configuración (Routing Key, Exchange) para una operación específica sin afectar al modelo global. El contexto se mantiene durante el ciclo de vida del objeto.
|
|
148
140
|
|
|
149
141
|
```ruby
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
exchange: 'events.topic',
|
|
153
|
-
exchange_type: 'topic',
|
|
154
|
-
routing_key: 'alerts.critical',
|
|
155
|
-
body: { message: 'CPU High', server: 'web-1' }
|
|
156
|
-
)
|
|
157
|
-
```
|
|
142
|
+
# La instancia nace sabiendo que pertenece a la routing_key 'urgent'
|
|
143
|
+
svc = Manager::Service.with(routing_key: 'urgent').new(name: 'redis')
|
|
158
144
|
|
|
159
|
-
|
|
160
|
-
Envía el mensaje y bloquea el hilo esperando la respuesta del consumidor.
|
|
145
|
+
# ... lógica de negocio ...
|
|
161
146
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
response = client.request('math/calculate',
|
|
166
|
-
exchange: 'rpc.direct',
|
|
167
|
-
routing_key: 'calculator',
|
|
168
|
-
body: { a: 10, b: 20 },
|
|
169
|
-
timeout: 5 # Segundos de espera máxima
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
puts response['body'] # => { "result": 30 }
|
|
173
|
-
|
|
174
|
-
rescue BugBunny::RequestTimeout
|
|
175
|
-
puts "El servidor tardó demasiado."
|
|
176
|
-
end
|
|
147
|
+
# Al guardar, BugBunny recuerda el contexto y envía a 'urgent'
|
|
148
|
+
svc.save
|
|
149
|
+
# Log: [BugBunny] [POST] '/services' | Routing Key: 'urgent'
|
|
177
150
|
```
|
|
178
151
|
|
|
179
152
|
---
|
|
180
153
|
|
|
181
|
-
## 📡 Modo Servidor (
|
|
154
|
+
## 📡 Modo Servidor (Worker & Router)
|
|
182
155
|
|
|
183
|
-
BugBunny incluye un **Router Inteligente** que
|
|
156
|
+
BugBunny incluye un **Router Inteligente** que despacha mensajes a controladores basándose en el Verbo y el Path, imitando a Rails.
|
|
184
157
|
|
|
185
|
-
### 1.
|
|
158
|
+
### 1. El Controlador (`app/rabbit/controllers/`)
|
|
186
159
|
|
|
187
|
-
|
|
160
|
+
Hereda de `BugBunny::Controller`. Tienes acceso a `params`, `before_action` y `rescue_from`.
|
|
188
161
|
|
|
189
162
|
```ruby
|
|
190
|
-
# app/rabbit/controllers/
|
|
191
|
-
class
|
|
163
|
+
# app/rabbit/controllers/services_controller.rb
|
|
164
|
+
class ServicesController < BugBunny::Controller
|
|
165
|
+
# Callbacks
|
|
166
|
+
before_action :set_service, only: %i[show update destroy]
|
|
192
167
|
|
|
193
|
-
#
|
|
168
|
+
# GET services
|
|
194
169
|
def index
|
|
195
|
-
|
|
196
|
-
users = User.where(active: params[:active])
|
|
197
|
-
render status: 200, json: users
|
|
170
|
+
render status: 200, json: DockerService.all
|
|
198
171
|
end
|
|
199
172
|
|
|
200
|
-
#
|
|
201
|
-
def
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
render status: 404, json: { error: 'Not Found' }
|
|
209
|
-
end
|
|
173
|
+
# POST services
|
|
174
|
+
def create
|
|
175
|
+
# BugBunny wrappea los params automáticamente en el Resource.
|
|
176
|
+
# Aquí los consumimos con seguridad usando Strong Parameters simulados o hash access.
|
|
177
|
+
# params[:service] estará disponible gracias al param_key del Resource.
|
|
178
|
+
|
|
179
|
+
result = DockerService.create(params[:service])
|
|
180
|
+
render status: 201, json: result
|
|
210
181
|
end
|
|
211
182
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
render status:
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def set_service
|
|
186
|
+
# params[:id] se extrae automágicamente de la URL (Route Param)
|
|
187
|
+
@service = DockerService.find(params[:id])
|
|
188
|
+
|
|
189
|
+
unless @service
|
|
190
|
+
render status: 404, json: { error: "Service not found" }
|
|
220
191
|
end
|
|
221
192
|
end
|
|
222
193
|
end
|
|
223
194
|
```
|
|
224
195
|
|
|
225
|
-
### 2.
|
|
196
|
+
### 2. Manejo de Errores (`rescue_from`)
|
|
226
197
|
|
|
227
|
-
|
|
228
|
-
bundle exec rake bug_bunny:work
|
|
229
|
-
```
|
|
198
|
+
Puedes definir un `ApplicationController` base para manejar errores de forma centralizada y declarativa.
|
|
230
199
|
|
|
231
|
-
|
|
200
|
+
```ruby
|
|
201
|
+
# app/rabbit/controllers/application.rb
|
|
202
|
+
class ApplicationController < BugBunny::Controller
|
|
203
|
+
# Manejo específico
|
|
204
|
+
rescue_from ActiveRecord::RecordNotFound do
|
|
205
|
+
render status: :not_found, json: { error: "Resource missing" }
|
|
206
|
+
end
|
|
232
207
|
|
|
233
|
-
|
|
208
|
+
rescue_from ActiveModel::ValidationError do |e|
|
|
209
|
+
render status: :unprocessable_entity, json: e.model.errors
|
|
210
|
+
end
|
|
234
211
|
|
|
235
|
-
|
|
212
|
+
# Catch-all (Red de seguridad)
|
|
213
|
+
rescue_from StandardError do |e|
|
|
214
|
+
BugBunny.configuration.logger.error(e)
|
|
215
|
+
render status: :internal_server_error, json: { error: "Internal Error" }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
236
219
|
|
|
237
|
-
|
|
220
|
+
### 3. Tabla de Ruteo (Convención)
|
|
221
|
+
|
|
222
|
+
El Router infiere la acción automáticamente:
|
|
223
|
+
|
|
224
|
+
| Verbo | URL Pattern | Controlador | Acción |
|
|
238
225
|
| :--- | :--- | :--- | :--- |
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
226
|
+
| `GET` | `services` | `ServicesController` | `index` |
|
|
227
|
+
| `GET` | `services/12` | `ServicesController` | `show` |
|
|
228
|
+
| `POST` | `services` | `ServicesController` | `create` |
|
|
229
|
+
| `PUT` | `services/12` | `ServicesController` | `update` |
|
|
230
|
+
| `DELETE` | `services/12` | `ServicesController` | `destroy` |
|
|
231
|
+
| `POST` | `services/12/restart` | `ServicesController` | `restart` (Custom) |
|
|
244
232
|
|
|
245
233
|
---
|
|
246
234
|
|
|
247
|
-
##
|
|
235
|
+
## 🔌 Modo Publisher (Cliente Manual)
|
|
248
236
|
|
|
249
|
-
|
|
237
|
+
Si necesitas enviar mensajes crudos fuera de la lógica Resource, usa `BugBunny::Client`.
|
|
250
238
|
|
|
251
239
|
```ruby
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
client = BugBunny::Client.new(pool: BUG_BUNNY_POOL)
|
|
241
|
+
|
|
242
|
+
# --- REQUEST (Síncrono / RPC) ---
|
|
243
|
+
# Espera la respuesta. Lanza BugBunny::RequestTimeout si falla.
|
|
244
|
+
response = client.request('services/123/logs',
|
|
245
|
+
method: :get,
|
|
246
|
+
exchange: 'logs_exchange',
|
|
247
|
+
timeout: 5
|
|
248
|
+
)
|
|
249
|
+
puts response['body']
|
|
256
250
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
# --- PUBLISH (Asíncrono / Fire-and-Forget) ---
|
|
252
|
+
# No espera respuesta.
|
|
253
|
+
client.publish('audit/events',
|
|
254
|
+
method: :post,
|
|
255
|
+
body: { event: 'login', user_id: 1 }
|
|
256
|
+
)
|
|
260
257
|
```
|
|
261
258
|
|
|
262
|
-
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 🏗 Arquitectura REST-over-AMQP
|
|
262
|
+
|
|
263
|
+
BugBunny desacopla el transporte de la lógica usando headers estándar.
|
|
264
|
+
|
|
265
|
+
1. **Semántica:** El mensaje lleva headers `type` (URL) y `x-http-method` (Verbo).
|
|
266
|
+
2. **Ruteo:** El consumidor lee estos headers y ejecuta el controlador correspondiente.
|
|
267
|
+
3. **Parametros:** `params` unifica:
|
|
268
|
+
* **Route Params:** `services/123` -> `params[:id] = 123`
|
|
269
|
+
* **Query Params:** `services?force=true` -> `params[:force] = true`
|
|
270
|
+
* **Body:** Payload JSON fusionado en el hash.
|
|
263
271
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
272
|
+
### Logs Estructurados
|
|
273
|
+
|
|
274
|
+
Facilita el debugging mostrando claramente qué recurso se está tocando y por dónde viaja.
|
|
275
|
+
|
|
276
|
+
```text
|
|
277
|
+
[BugBunny] [POST] '/services' | Exchange: 'cluster' (Type: direct) | Routing Key: 'node-1'
|
|
278
|
+
```
|
|
268
279
|
|
|
269
280
|
---
|
|
270
281
|
|
data/lib/bug_bunny/client.rb
CHANGED
|
@@ -4,31 +4,25 @@ require_relative 'middleware/stack'
|
|
|
4
4
|
module BugBunny
|
|
5
5
|
# Cliente principal para realizar peticiones a RabbitMQ.
|
|
6
6
|
#
|
|
7
|
-
# Implementa el patrón "Onion Middleware" (Arquitectura de Cebolla) similar a Faraday
|
|
8
|
-
#
|
|
9
|
-
# como las respuestas entrantes mediante una pila de middlewares.
|
|
7
|
+
# Implementa el patrón "Onion Middleware" (Arquitectura de Cebolla) similar a Faraday.
|
|
8
|
+
# Mantiene una interfaz flexible donde el verbo HTTP se pasa como opción.
|
|
10
9
|
#
|
|
11
|
-
# @example
|
|
12
|
-
# client
|
|
10
|
+
# @example Petición RPC (GET)
|
|
11
|
+
# client.request('users/123', method: :get)
|
|
13
12
|
#
|
|
14
|
-
# @example
|
|
15
|
-
# client
|
|
16
|
-
# conn.use BugBunny::Middleware::RaiseError
|
|
17
|
-
# conn.use BugBunny::Middleware::JsonResponse
|
|
18
|
-
# conn.use BugBunny::Middleware::Logger, Rails.logger
|
|
19
|
-
# end
|
|
13
|
+
# @example Publicación Fire-and-Forget (POST)
|
|
14
|
+
# client.publish('logs', method: :post, body: { msg: 'Error' })
|
|
20
15
|
class Client
|
|
21
16
|
# @return [ConnectionPool] El pool de conexiones subyacente a RabbitMQ.
|
|
22
17
|
attr_reader :pool
|
|
23
18
|
|
|
24
|
-
# @return [BugBunny::Middleware::Stack] La pila de middlewares configurada
|
|
19
|
+
# @return [BugBunny::Middleware::Stack] La pila de middlewares configurada.
|
|
25
20
|
attr_reader :stack
|
|
26
21
|
|
|
27
22
|
# Inicializa un nuevo cliente.
|
|
28
23
|
#
|
|
29
24
|
# @param pool [ConnectionPool] Pool de conexiones a RabbitMQ configurado previamente.
|
|
30
25
|
# @yield [stack] Bloque opcional para configurar la pila de middlewares.
|
|
31
|
-
# @yieldparam stack [BugBunny::Middleware::Stack] El objeto stack para registrar middlewares con {#use}.
|
|
32
26
|
# @raise [ArgumentError] Si no se proporciona un `pool`.
|
|
33
27
|
def initialize(pool:)
|
|
34
28
|
raise ArgumentError, "BugBunny::Client requiere un 'pool:'" if pool.nil?
|
|
@@ -39,21 +33,16 @@ module BugBunny
|
|
|
39
33
|
|
|
40
34
|
# Realiza una petición Síncrona (RPC / Request-Response).
|
|
41
35
|
#
|
|
42
|
-
# Envía un mensaje y bloquea la ejecución del hilo actual hasta recibir
|
|
43
|
-
# correlacionada del servidor o hasta que se supere el tiempo de espera (timeout).
|
|
36
|
+
# Envía un mensaje y bloquea la ejecución del hilo actual hasta recibir respuesta.
|
|
44
37
|
#
|
|
45
|
-
# @param url [String] La ruta
|
|
46
|
-
# @param args [Hash] Opciones de configuración
|
|
47
|
-
# @option args [
|
|
48
|
-
# @option args [
|
|
49
|
-
# @option args [
|
|
50
|
-
# @option args [
|
|
51
|
-
# @
|
|
52
|
-
# @
|
|
53
|
-
# @yield [req] Bloque opcional para configurar el objeto Request directamente.
|
|
54
|
-
# @yieldparam req [BugBunny::Request] Objeto request configurable.
|
|
55
|
-
# @return [Hash] La respuesta del servidor, conteniendo habitualmente `status` y `body`.
|
|
56
|
-
# @raise [BugBunny::RequestTimeout] Si no se recibe respuesta en el tiempo límite.
|
|
38
|
+
# @param url [String] La ruta del recurso (ej: 'users/1').
|
|
39
|
+
# @param args [Hash] Opciones de configuración.
|
|
40
|
+
# @option args [Symbol] :method El verbo HTTP (:get, :post, :put, :delete). Default: :get.
|
|
41
|
+
# @option args [Object] :body El cuerpo del mensaje.
|
|
42
|
+
# @option args [Hash] :headers Headers AMQP adicionales.
|
|
43
|
+
# @option args [Integer] :timeout Tiempo máximo de espera.
|
|
44
|
+
# @yield [req] Bloque para configurar el objeto Request directamente.
|
|
45
|
+
# @return [Hash] La respuesta del servidor.
|
|
57
46
|
def request(url, **args)
|
|
58
47
|
run_in_pool(:rpc, url, args) do |req|
|
|
59
48
|
yield req if block_given?
|
|
@@ -62,13 +51,9 @@ module BugBunny
|
|
|
62
51
|
|
|
63
52
|
# Realiza una publicación Asíncrona (Fire-and-Forget).
|
|
64
53
|
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# @param url [String] La ruta o acción del mensaje.
|
|
69
|
-
# @param args [Hash] Mismas opciones que {#request}, excepto `:timeout` (no aplica).
|
|
70
|
-
# @yield [req] Bloque opcional para configurar el objeto Request.
|
|
71
|
-
# @yieldparam req [BugBunny::Request] Objeto request configurable.
|
|
54
|
+
# @param url [String] La ruta del evento/recurso.
|
|
55
|
+
# @param args [Hash] Mismas opciones que {#request}, excepto `:timeout`.
|
|
56
|
+
# @yield [req] Bloque para configurar el objeto Request.
|
|
72
57
|
# @return [void]
|
|
73
58
|
def publish(url, **args)
|
|
74
59
|
run_in_pool(:fire, url, args) do |req|
|
|
@@ -78,16 +63,14 @@ module BugBunny
|
|
|
78
63
|
|
|
79
64
|
private
|
|
80
65
|
|
|
81
|
-
# Ejecuta la lógica de envío dentro del contexto del Pool
|
|
82
|
-
#
|
|
83
|
-
# @param method_name [Symbol] El método a invocar en el Producer (:rpc o :fire).
|
|
84
|
-
# @param url [String] La URL/Acción del request.
|
|
85
|
-
# @param args [Hash] Argumentos pasados al método público.
|
|
66
|
+
# Ejecuta la lógica de envío dentro del contexto del Pool.
|
|
67
|
+
# Mapea los argumentos al objeto Request y ejecuta la cadena de middlewares.
|
|
86
68
|
def run_in_pool(method_name, url, args)
|
|
87
69
|
# 1. Builder del Request
|
|
88
70
|
req = BugBunny::Request.new(url)
|
|
89
71
|
|
|
90
72
|
# 2. Syntactic Sugar: Mapeo de argumentos a atributos del Request
|
|
73
|
+
req.method = args[:method] if args[:method]
|
|
91
74
|
req.body = args[:body] if args[:body]
|
|
92
75
|
req.exchange = args[:exchange] if args[:exchange]
|
|
93
76
|
req.exchange_type = args[:exchange_type] if args[:exchange_type]
|
data/lib/bug_bunny/config.rb
CHANGED
|
@@ -26,6 +26,10 @@ module BugBunny
|
|
|
26
26
|
# @return [Logger] Instancia del logger para depuración (default: Logger a STDOUT).
|
|
27
27
|
attr_accessor :logger
|
|
28
28
|
|
|
29
|
+
# @return [Logger] Logger específico para el driver Bunny (Conexión, Heartbeats, Frames).
|
|
30
|
+
# Se recomienda nivel WARN para evitar ruido.
|
|
31
|
+
attr_accessor :bunny_logger
|
|
32
|
+
|
|
29
33
|
# @return [Boolean] Si `true`, Bunny intentará reconectar automáticamente ante fallos de red (default: true).
|
|
30
34
|
attr_accessor :automatically_recover
|
|
31
35
|
|
|
@@ -58,7 +62,13 @@ module BugBunny
|
|
|
58
62
|
|
|
59
63
|
# Inicializa la configuración con valores por defecto seguros.
|
|
60
64
|
def initialize
|
|
65
|
+
# Logger de la Aplicación (BugBunny) -> INFO (Ves tus requests)
|
|
61
66
|
@logger = Logger.new(STDOUT)
|
|
67
|
+
@logger.level = Logger::INFO
|
|
68
|
+
|
|
69
|
+
# Logger del Driver (Bunny) -> WARN (Oculta el ruido de "handle_frame")
|
|
70
|
+
@bunny_logger = Logger.new(STDOUT)
|
|
71
|
+
@bunny_logger.level = Logger::WARN
|
|
62
72
|
@automatically_recover = true
|
|
63
73
|
@network_recovery_interval = 5
|
|
64
74
|
@connection_timeout = 10
|