bug_bunny 3.1.0 → 3.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.
data/README.md CHANGED
@@ -1,8 +1,39 @@
1
1
  # 🐰 BugBunny
2
2
 
3
- **BugBunny** es un framework RPC para Ruby on Rails sobre **RabbitMQ**.
3
+ [![Gem Version](https://badge.fury.io/rb/bug_bunny.svg)](https://badge.fury.io/rb/bug_bunny)
4
4
 
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.
5
+ **Active Record over AMQP.**
6
+
7
+ BugBunny transforma la complejidad de la mensajería asíncrona (RabbitMQ) en una arquitectura **RESTful familiar** para desarrolladores Rails. Envía mensajes como si estuvieras usando Active Record y procésalos como si fueran Controladores de Rails.
8
+
9
+ ---
10
+
11
+ ## 📖 Tabla de Contenidos
12
+ - [Introducción: La Filosofía](#-introducción-la-filosofía)
13
+ - [Instalación](#-instalación)
14
+ - [Configuración Inicial](#-configuración-inicial)
15
+ - [Configuración de Infraestructura en Cascada](#-configuración-de-infraestructura-en-cascada-nuevo-v31)
16
+ - [Modo Cliente: Recursos (ORM)](#-modo-cliente-recursos-orm)
17
+ - [Definición y Atributos](#1-definición-y-atributos-híbridos)
18
+ - [CRUD y Consultas](#2-crud-y-consultas-restful)
19
+ - [Contexto Dinámico (.with)](#3-contexto-dinámico-with)
20
+ - [Client Middleware](#4-client-middleware-interceptores)
21
+ - [Modo Servidor: Controladores](#-modo-servidor-controladores)
22
+ - [Ruteo Inteligente](#1-ruteo-inteligente)
23
+ - [El Controlador](#2-el-controlador)
24
+ - [Manejo de Errores](#3-manejo-de-errores-declarativo)
25
+ - [Observabilidad y Tracing](#-observabilidad-y-tracing)
26
+ - [Guía de Producción](#-guía-de-producción)
27
+
28
+ ---
29
+
30
+ ## 💡 Introducción: La Filosofía
31
+
32
+ En lugar de pensar en "Exchanges" y "Queues", BugBunny inyecta verbos HTTP (`GET`, `POST`, `PUT`, `DELETE`) y rutas (`users/1`) en los headers de AMQP.
33
+
34
+ * **Tu código (Cliente):** `User.create(name: 'Gabi')`
35
+ * **Protocolo (BugBunny):** Envía `POST /users` (Header `type: users`) vía RabbitMQ.
36
+ * **Worker (Servidor):** Recibe el mensaje y ejecuta `UsersController#create`.
6
37
 
7
38
  ---
8
39
 
@@ -11,495 +42,296 @@ Su filosofía es **"Active Record over AMQP"**. Transforma la complejidad de la
11
42
  Agrega la gema a tu `Gemfile`:
12
43
 
13
44
  ```ruby
14
- gem 'bug_bunny'
45
+ gem 'bug_bunny', '~> 3.1'
15
46
  ```
16
47
 
17
- Ejecuta el bundle:
48
+ Ejecuta el bundle e instala los archivos base:
18
49
 
19
50
  ```bash
20
51
  bundle install
21
- ```
22
-
23
- Genera los archivos de configuración iniciales:
24
-
25
- ```bash
26
52
  rails g bug_bunny:install
27
53
  ```
28
54
 
29
- Esto creará:
55
+ Esto genera:
30
56
  1. `config/initializers/bug_bunny.rb`
31
57
  2. `app/rabbit/controllers/`
32
58
 
33
59
  ---
34
60
 
35
- ## ⚙️ Configuración
36
-
37
- ### 1. Inicializador y Logging
61
+ ## ⚙️ Configuración Inicial
38
62
 
39
- BugBunny separa los logs de la aplicación (Requests) de los logs del driver (Heartbeats/Frames) para mantener la consola limpia.
63
+ Para entornos productivos (Puma/Sidekiq), es **obligatorio** configurar un Pool de conexiones.
40
64
 
41
65
  ```ruby
42
66
  # config/initializers/bug_bunny.rb
43
67
 
44
68
  BugBunny.configure do |config|
45
- # --- Credenciales ---
69
+ # 1. Credenciales
46
70
  config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
47
71
  config.username = ENV.fetch('RABBITMQ_USER', 'guest')
48
72
  config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
49
73
  config.vhost = ENV.fetch('RABBITMQ_VHOST', '/')
50
74
 
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
- rails_logger = Rails.logger
58
-
59
- if defined?(ActiveSupport::TaggedLogging) && !rails_logger.respond_to?(:tagged)
60
- config.logger = ActiveSupport::TaggedLogging.new(rails_logger)
61
- else
62
- config.logger = rails_logger
63
- end
75
+ # 2. Timeouts y Recuperación
76
+ config.rpc_timeout = 10 # Segundos máx para esperar respuesta (Síncrono)
77
+ config.network_recovery_interval = 5 # Reintento de conexión
64
78
 
65
- # Logger de Bunny (Driver): Silencia el ruido de bajo nivel (WARN)
66
- if defined?(ActiveSupport::TaggedLogging) && !rails_logger.respond_to?(:tagged)
67
- config.bunny_logger = ActiveSupport::TaggedLogging.new(rails_logger)
68
- else
69
- config.bunny_logger = rails_logger
70
- end
71
- config.bunny_logger.level = Logger::WARN
79
+ # 3. Logging
80
+ config.logger = Rails.logger
81
+ end
72
82
 
73
- # Controller Namaspeace
74
- config.controller_namespace = 'MyApp::AsyncHandlers' # Default: 'Rabbit::Controllers'
83
+ # 4. Connection Pool (CRÍTICO para concurrencia)
84
+ # Define un pool global para compartir conexiones entre hilos
85
+ BUG_BUNNY_POOL = ConnectionPool.new(size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i, timeout: 5) do
86
+ BugBunny.create_connection
75
87
  end
88
+
89
+ # Inyecta el pool en los recursos
90
+ BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
76
91
  ```
77
92
 
78
- ### 2. Connection Pool (Crítico) 🧵
93
+ ---
94
+
95
+ ## 🏗️ Configuración de Infraestructura en Cascada (Nuevo v3.1)
96
+
97
+ BugBunny v3.1 introduce un sistema de configuración jerárquico para los parámetros de RabbitMQ (como la durabilidad de Exchanges y Colas). Las opciones se resuelven en el siguiente orden de prioridad:
98
+
99
+ 1. **Defaults de la Gema:** Rápidos y efímeros (`durable: false`).
100
+ 2. **Configuración Global:** Definida en el inicializador para todo el entorno.
101
+ 3. **Configuración de Recurso:** Atributos de clase en modelos específicos.
102
+ 4. **Configuración al Vuelo:** Parámetros pasados en la llamada `.with` o en el Cliente manual.
79
103
 
80
- 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.
104
+ **Ejemplo de Configuración Global (Nivel 2):**
105
+ Útil para hacer que todos los recursos en el entorno de pruebas sean auto-borrables.
81
106
 
82
107
  ```ruby
83
108
  # config/initializers/bug_bunny.rb
84
-
85
- # Define el pool global (ajusta el tamaño según tus hilos de Puma/Sidekiq)
86
- BUG_BUNNY_POOL = ConnectionPool.new(size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i, timeout: 5) do
87
- BugBunny.create_connection
109
+ BugBunny.configure do |config|
110
+ if Rails.env.test?
111
+ config.exchange_options = { auto_delete: true }
112
+ config.queue_options = { auto_delete: true }
113
+ end
88
114
  end
89
-
90
- # Inyecta el pool a los recursos para que lo usen automáticamente
91
- BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
92
115
  ```
93
116
 
94
117
  ---
95
118
 
96
- ## 🚀 Modo Resource (ORM / Cliente)
119
+ ## 🚀 Modo Cliente: Recursos (ORM)
97
120
 
98
- Define modelos que actúan como proxies de recursos remotos. BugBunny se encarga de serializar, "wrappear" parámetros y enviar el verbo correcto.
121
+ Los recursos son proxies de servicios remotos. Heredan de `BugBunny::Resource`.
99
122
 
100
- ### Definición del Modelo
123
+ ### 1. Definición y Atributos Híbridos
124
+ BugBunny v3 es **Schema-less**. Soporta atributos tipados (ActiveModel) y dinámicos simultáneamente, además de definir su propia infraestructura.
101
125
 
102
126
  ```ruby
103
127
  # app/models/manager/service.rb
104
128
  class Manager::Service < BugBunny::Resource
105
- # 1. Configuración de Transporte
106
- self.exchange = 'box_cluster_manager'
107
- self.exchange_type = 'direct'
129
+ # Configuración de Transporte
130
+ self.exchange = 'cluster_events'
131
+ self.exchange_type = 'topic'
132
+
133
+ # Configuración de Infraestructura Específica (Nivel 3)
134
+ # Este recurso crítico sobrevivirá a reinicios del servidor RabbitMQ
135
+ self.exchange_options = { durable: true, auto_delete: false }
108
136
 
109
- # 2. Configuración Lógica (Routing)
110
- # Define la URL base y la routing key por defecto.
137
+ # Configuración de Ruteo (La "URL" base)
111
138
  self.resource_name = 'services'
112
139
 
113
- # 3. Wrapping de Parámetros (Opcional)
114
- # Por defecto usa el nombre del modelo sin módulo (Manager::Service -> 'service').
115
- # Puedes forzarlo con:
116
- # self.param_key = 'docker_service'
140
+ # A. Atributos Tipados (Opcional, para casting)
141
+ attribute :created_at, :datetime
142
+ attribute :replicas, :integer, default: 1
143
+
144
+ # B. Validaciones (Funcionan en ambos tipos)
145
+ validates :name, presence: true
117
146
  end
118
147
  ```
119
148
 
120
- ### CRUD RESTful
121
-
122
- Las operaciones de Ruby se traducen a verbos HTTP sobre AMQP.
149
+ ### 2. CRUD y Consultas RESTful
123
150
 
124
151
  ```ruby
125
152
  # --- LEER (GET) ---
126
- # Envia: GET services
127
- # Routing Key: "services"
128
- services = Manager::Service.all
129
-
153
+ # RPC: Espera respuesta del worker.
130
154
  # Envia: GET services/123
131
155
  service = Manager::Service.find('123')
132
156
 
157
+ # --- BÚSQUEDAS AVANZADAS ---
158
+ # Soporta Hashes anidados para filtros complejos.
159
+ # Envia: GET services?q[status]=active&q[tags][]=web
160
+ Manager::Service.where(q: { status: 'active', tags: ['web'] })
161
+
133
162
  # --- CREAR (POST) ---
134
- # Envia: POST services
135
- # Body: { "service": { "name": "nginx", "replicas": 3 } }
136
- # Nota: Envuelve los params automáticamente en la clave 'service'.
163
+ # RPC: Envía payload y espera el objeto persistido.
164
+ # Payload: { "service": { "name": "nginx", "replicas": 3 } }
137
165
  svc = Manager::Service.create(name: 'nginx', replicas: 3)
138
166
 
139
167
  # --- ACTUALIZAR (PUT) ---
140
- # Envia: PUT services/123
141
- # Body: { "service": { "replicas": 5 } }
142
- svc.update(replicas: 5)
168
+ # Dirty Tracking: Solo envía los campos que cambiaron.
169
+ svc.name = 'nginx-pro'
170
+ svc.save
143
171
 
144
172
  # --- ELIMINAR (DELETE) ---
145
- # Envia: DELETE services/123
146
173
  svc.destroy
147
174
  ```
148
175
 
149
- ### Contexto Dinámico (`.with`)
150
-
151
- 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.
176
+ ### 3. Contexto Dinámico (`.with`)
177
+ Puedes sobrescribir la configuración de enrutamiento o infraestructura para una ejecución específica sin afectar al modelo global (Thread-Safe).
152
178
 
153
179
  ```ruby
154
- # La instancia nace sabiendo que pertenece a la routing_key 'urgent'
155
- svc = Manager::Service.with(routing_key: 'urgent').new(name: 'redis')
156
-
157
- # ... lógica de negocio ...
158
-
159
- # Al guardar, BugBunny recuerda el contexto y envía a 'urgent'
160
- svc.save
161
- # Log: [BugBunny] [POST] '/services' | Routing Key: 'urgent'
180
+ # Nivel 4: Configuración al vuelo. Inyectamos opciones solo para esta llamada.
181
+ Manager::Service.with(
182
+ routing_key: 'high_priority',
183
+ exchange_options: { durable: false } # Ignora el durable: true de la clase
184
+ ).create(name: 'redis_temp')
162
185
  ```
163
186
 
164
- ### Soporte de Parámetros Anidados (Nested Queries)
165
- En la versión 3.0.3 arreglaste la serialización usando `Rack::Utils`. Esto es una "feature" poderosa que permite filtrar por hashes complejos, algo muy común en APIs modernas.
166
-
167
- **Sugerencia:** Agregar un ejemplo en la sección **CRUD RESTful > LEER (GET)**:
187
+ ### 4. Client Middleware (Interceptores)
188
+ Intercepta peticiones antes de salir hacia RabbitMQ. Ideal para inyectar Auth o Headers.
168
189
 
169
190
  ```ruby
170
- # --- LEER CON FILTROS AVANZADOS ---
171
- # Soporta hashes anidados (gracias a Rack::Utils)
172
- # Envia: GET services?q[status]=active&q[tags][]=web
173
- Manager::Service.where(q: { status: 'active', tags: ['web'] })
174
- ```
175
-
176
- ### 🔌 Manipulación de Headers (Middleware)
177
-
178
- BugBunny permite interceptar y modificar las peticiones antes de que se envíen a RabbitMQ utilizando `client_middleware`. Esto es ideal para inyectar trazas, autenticación o metadatos de contexto.
179
-
180
- Existen 3 formas principales de usarlo:
181
-
182
- #### 1. Definición Inline (Rápida)
183
- Ideal para inyectar headers estáticos específicos de un recurso.
184
- ```ruby
185
- class Payment < BugBunny::Resource
191
+ class Manager::Service < BugBunny::Resource
186
192
  client_middleware do |stack|
187
193
  stack.use(Class.new(BugBunny::Middleware::Base) do
188
194
  def on_request(env)
189
- env.headers['X-Service-Version'] = 'v2'
190
- env.headers['Content-Type'] = 'application/json'
195
+ env.headers['Authorization'] = "Bearer #{ENV['API_TOKEN']}"
196
+ env.headers['X-App-Version'] = '1.0.0'
191
197
  end
192
198
  end)
193
199
  end
194
200
  end
195
201
  ```
196
202
 
197
- #### 2. Clase Reutilizable (Recomendada)
198
- Si tienes lógica compartida (ej: Autenticación), define una clase y úsala en múltiples recursos.
199
-
200
- ```ruby
201
- # app/middleware/auth_middleware.rb
202
- class AuthMiddleware < BugBunny::Middleware::Base
203
- def on_request(env)
204
- env.headers['Authorization'] = "Bearer #{ENV['API_KEY']}"
205
- end
206
- end
207
-
208
- # app/models/user.rb
209
- class User < BugBunny::Resource
210
- client_middleware do |stack|
211
- stack.use AuthMiddleware
212
- end
213
- end
214
- ```
215
-
216
- #### 3. Contexto Dinámico (Pro)
217
- Permite inyectar valores que cambian en cada petición (como el Usuario actual o Tenant), leyendo de variables globales thread-safe (como CurrentAttributes en Rails).
218
-
219
- ```ruby
220
- # Middleware que lee el Tenant actual
221
- # app/middleware/tenant_middleware.rb
222
- class TenantMiddleware < BugBunny::Middleware::Base
223
- def on_request(env)
224
- # Ejemplo usando Rails CurrentAttributes
225
- if Current.tenant_id
226
- env.headers['X-Tenant-ID'] = Current.tenant_id
227
- end
228
- end
229
- end
230
-
231
- class Order < BugBunny::Resource
232
- client_middleware do |stack|
233
- stack.use TenantMiddleware
234
- end
235
- end
236
- ```
237
-
238
203
  ---
239
204
 
240
- ## 📡 Modo Servidor (Worker & Router)
205
+ ## 📡 Modo Servidor: Controladores
206
+
207
+ BugBunny implementa un **Router** que despacha mensajes a controladores basándose en el header `type` (URL) y `x-http-method`.
241
208
 
242
- BugBunny incluye un **Router Inteligente** que despacha mensajes a controladores basándose en el Verbo y el Path, imitando a Rails.
209
+ ### 1. Ruteo Inteligente
210
+ El consumidor infiere automáticamente la acción:
243
211
 
244
- ### 1. El Controlador (`app/rabbit/controllers/`)
212
+ | Verbo AMQP | Path (Header `type`) | Controlador | Acción |
213
+ | :--- | :--- | :--- | :--- |
214
+ | `GET` | `services` | `ServicesController` | `index` |
215
+ | `GET` | `services/123` | `ServicesController` | `show` |
216
+ | `POST` | `services` | `ServicesController` | `create` |
217
+ | `PUT` | `services/123` | `ServicesController` | `update` |
218
+ | `DELETE` | `services/123` | `ServicesController` | `destroy` |
219
+ | `POST` | `services/123/restart` | `ServicesController` | `restart` (Custom) |
245
220
 
246
- Hereda de `BugBunny::Controller`. Tienes acceso a `params`, `before_action` y `rescue_from`.
221
+ ### 2. El Controlador
222
+ Ubicación: `app/rabbit/controllers/`.
247
223
 
248
224
  ```ruby
249
- # app/rabbit/controllers/services_controller.rb
250
225
  class ServicesController < BugBunny::Controller
251
- # Callbacks
252
- before_action :set_service, only: %i[show update destroy]
226
+ # Callbacks estándar
227
+ before_action :set_service, only: [:show, :update]
253
228
 
254
- # GET services
255
- def index
256
- render status: 200, json: DockerService.all
229
+ def show
230
+ # Renderiza JSON que viajará de vuelta por la cola reply-to
231
+ render status: 200, json: { id: @service.id, state: 'running' }
257
232
  end
258
233
 
259
- # POST services
260
234
  def create
261
- # BugBunny wrappea los params automáticamente en el Resource.
262
- # Aquí los consumimos con seguridad usando Strong Parameters simulados o hash access.
263
- # params[:service] estará disponible gracias al param_key del Resource.
264
-
265
- result = DockerService.create(params[:service])
266
- render status: 201, json: result
235
+ # BugBunny envuelve los params automáticamente (param_key)
236
+ # params[:service] => { name: '...', replicas: ... }
237
+ if Service.create(params[:service])
238
+ render status: 201, json: { status: 'created' }
239
+ else
240
+ render status: 422, json: { errors: 'Invalid' }
241
+ end
267
242
  end
268
243
 
269
244
  private
270
245
 
271
246
  def set_service
272
- # params[:id] se extrae automágicamente de la URL (Route Param)
273
- @service = DockerService.find(params[:id])
274
-
275
- unless @service
276
- render status: 404, json: { error: "Service not found" }
277
- end
247
+ # params[:id] se extrae del Path
248
+ @service = Service.find(params[:id])
278
249
  end
279
250
  end
280
251
  ```
281
252
 
282
- ### 2. Manejo de Errores (`rescue_from`)
283
-
284
- Puedes definir un `ApplicationController` base para manejar errores de forma centralizada y declarativa.
253
+ ### 3. Manejo de Errores Declarativo
254
+ Captura excepciones y devuélvelas como códigos de estado AMQP/HTTP.
285
255
 
286
256
  ```ruby
287
- # app/rabbit/controllers/application.rb
288
257
  class ApplicationController < BugBunny::Controller
289
- # Manejo específico
290
- rescue_from ActiveRecord::RecordNotFound do
258
+ rescue_from ActiveRecord::RecordNotFound do |e|
291
259
  render status: :not_found, json: { error: "Resource missing" }
292
260
  end
293
261
 
294
- rescue_from ActiveModel::ValidationError do |e|
295
- render status: :unprocessable_entity, json: e.model.errors
296
- end
297
-
298
- # Catch-all (Red de seguridad)
299
262
  rescue_from StandardError do |e|
300
263
  BugBunny.configuration.logger.error(e)
301
- render status: :internal_server_error, json: { error: "Internal Error" }
302
- end
303
- end
304
- ```
305
-
306
- ### 3. Namespace de Controladores (Opcional)
307
-
308
- Por defecto, BugBunny busca los controladores dentro del módulo `Rabbit::Controllers`. Esto implica que tus archivos deben estar en `app/rabbit/controllers/`.
309
-
310
- Si prefieres organizar tus consumidores en otro lugar (ej: dentro de un dominio específico o carpeta existente), puedes cambiar el namespace.
311
-
312
- **Configuración:**
313
- ```ruby
314
- # config/initializers/bug_bunny.rb
315
- BugBunny.configure do |config|
316
- config.controller_namespace = 'Billing::Events'
317
- end
318
- ```
319
-
320
- ### 4. Tabla de Ruteo (Convención)
321
-
322
- El Router infiere la acción automáticamente:
323
-
324
- | Verbo | URL Pattern | Controlador | Acción |
325
- | :--- | :--- | :--- | :--- |
326
- | `GET` | `services` | `ServicesController` | `index` |
327
- | `GET` | `services/12` | `ServicesController` | `show` |
328
- | `POST` | `services` | `ServicesController` | `create` |
329
- | `PUT` | `services/12` | `ServicesController` | `update` |
330
- | `DELETE` | `services/12` | `ServicesController` | `destroy` |
331
- | `POST` | `services/12/restart` | `ServicesController` | `restart` (Custom) |
332
-
333
- ### 🔎 Observabilidad y Logging
334
-
335
- BugBunny implementa un sistema de **Tracing Distribuido** nativo. Esto permite rastrear una petición desde que se origina en tu aplicación (Producer) hasta que es procesada por el worker (Consumer), manteniendo el mismo ID de traza (`correlation_id`) en todos los logs.
336
-
337
- #### 1. Productor: Inyectar el Trace ID
338
-
339
- Para asegurar que los mensajes salgan de tu aplicación con el ID de traza correcto (por ejemplo, el `X-Request-Id` de Rails, Sidekiq o tu propio `Current.request_id`), debes inyectarlo antes de publicar el mensaje.
340
-
341
- La forma recomendada es crear un Middleware y registrarlo globalmente.
342
-
343
- **A. Crear el Middleware**
344
-
345
- ```ruby
346
- # app/middleware/correlation_injector.rb
347
- class CorrelationInjector < BugBunny::Middleware::Base
348
- def on_request(env)
349
- # Ejemplo: Si usas Rails CurrentAttributes o similar
350
- if defined?(Current) && Current.request_id
351
- env.correlation_id = Current.request_id
352
- end
264
+ render status: :internal_server_error, json: { error: "Crash" }
353
265
  end
354
266
  end
355
267
  ```
356
268
 
357
- **B. Registrar el Middleware (Initializer)**
358
-
359
- ```ruby
360
- # config/initializers/bug_bunny.rb
361
- require 'bug_bunny'
362
- require_relative '../../app/middleware/correlation_injector'
363
-
364
- # Módulo para interceptar la inicialización de cualquier cliente
365
- module BugBunnyGlobalMiddleware
366
- def initialize(pool:)
367
- super
368
- @stack.use CorrelationInjector
369
- end
370
- end
371
-
372
- # Aplicamos el parche para que afecte a Resources y Clientes manuales
373
- BugBunny::Client.prepend(BugBunnyGlobalMiddleware)
374
- ```
375
-
376
269
  ---
377
270
 
378
- #### 2. Consumidor: Logging Automático
271
+ ## 🔎 Observabilidad y Tracing
379
272
 
380
- El consumidor de BugBunny está diseñado para garantizar la trazabilidad "out-of-the-box".
273
+ > **Novedad v3.1:** BugBunny implementa Distributed Tracing nativo.
381
274
 
382
- ##### A. Comportamiento por Defecto
383
- Al recibir un mensaje, el Consumidor realiza automáticamente los siguientes pasos:
384
- 1. Extrae el `correlation_id` de las propiedades AMQP (o genera un UUID si no existe).
385
- 2. Envuelve todo el procesamiento en un bloque de log etiquetado (`tagged logging`).
386
- 3. Pasa el ID al Controlador.
275
+ El `correlation_id` se mantiene intacto a través de toda la cadena: `Producer -> RabbitMQ -> Consumer -> Controller`.
387
276
 
388
- **No necesitas configurar nada.** Tus logs se verán así automáticamente:
277
+ ### 1. Logs Automáticos (Consumer)
278
+ No requiere configuración. El worker envuelve la ejecución en bloques de log etiquetados con el UUID.
389
279
 
390
280
  ```text
391
- [d41d8cd9-8f00...] [Consumer] Listening on queue...
392
- [d41d8cd9-8f00...] [API] Procesando usuario 123...
281
+ [d41d8cd9...] [Consumer] Listening on queue...
282
+ [d41d8cd9...] [API] Processing ServicesController#create...
393
283
  ```
394
284
 
395
- ##### B. Configuración Global (Initializer)
396
- Si deseas agregar tags estáticos que aparezcan en **todos** los mensajes procesados por este worker (como el nombre del servicio, versión o entorno), agrégalos a `config.log_tags`.
397
-
398
- > **Nota:** No agregues `:uuid` aquí, ya que el Consumidor lo agrega automáticamente.
285
+ ### 2. Logs de Negocio (Controller)
286
+ Inyecta contexto rico (Tenant, Usuario, IP) en los logs usando `log_tags`.
399
287
 
400
288
  ```ruby
401
- BugBunny.configure do |config|
402
- # ... configuración de conexión ...
403
-
404
- # Tags globales adicionales
405
- config.log_tags = [
406
- 'WORKER',
407
- ->(_) { ENV['APP_VERSION'] }
289
+ # app/rabbit/controllers/application_controller.rb
290
+ class ApplicationController < BugBunny::Controller
291
+ self.log_tags = [
292
+ ->(c) { c.params[:tenant_id] }, # Agrega [Tenant-55]
293
+ ->(c) { c.headers['X-Source'] } # Agrega [Console]
408
294
  ]
409
295
  end
410
296
  ```
411
297
 
412
- **Resultado en Log:**
413
- ```text
414
- [d41d8cd9...] [WORKER] [v1.0.2] [API] Procesando mensaje...
415
- ```
416
-
417
- ##### C. Configuración por Controlador (Contexto Rico)
418
- Para agregar información específica del mensaje o lógica de negocio (como IDs de inquilinos, usuario actual, o headers específicos), utiliza `self.log_tags` en tus controladores.
419
-
420
- Esto aprovecha el `around_action` nativo de la gema para inyectar contexto.
298
+ ### 3. Inyección en el Productor
299
+ Para que tus logs de Rails y Rabbit coincidan, usa un middleware global:
421
300
 
422
301
  ```ruby
423
- # app/rabbit/controllers/application_controller.rb
424
- module Rabbit
425
- module Controllers
426
- class ApplicationController < BugBunny::Controller
427
- # Define tags dinámicos basados en el mensaje actual
428
- self.log_tags = [
429
- ->(c) { c.params[:tenant_id] }, # Tag del Tenant (si viene en el body)
430
- ->(c) { c.headers['X-Source'] } # Tag del origen
431
- ]
432
- end
302
+ # config/initializers/bug_bunny.rb
303
+ # Middleware para inyectar Current.request_id de Rails al mensaje Rabbit
304
+ class CorrelationInjector < BugBunny::Middleware::Base
305
+ def on_request(env)
306
+ env.correlation_id = Current.request_id if defined?(Current)
433
307
  end
434
308
  end
435
- ```
436
309
 
437
- **Resultado Final en Log:**
438
- (UUID Automático + Tag Global + Tag de Controlador)
439
- ```text
440
- [d41d8cd9...] [WORKER] [Tenant-55] [Console] Creando usuario...
441
- ```
442
-
443
- ---
444
-
445
- ## 🔌 Modo Publisher (Cliente Manual)
446
-
447
- Si necesitas enviar mensajes crudos fuera de la lógica Resource, usa `BugBunny::Client`.
448
-
449
- ```ruby
450
- client = BugBunny::Client.new(pool: BUG_BUNNY_POOL)
451
-
452
- # --- REQUEST (Síncrono / RPC) ---
453
- # Espera la respuesta. Lanza BugBunny::RequestTimeout si falla.
454
- response = client.request('services/123/logs',
455
- method: :get,
456
- exchange: 'logs_exchange',
457
- timeout: 5
458
- )
459
- puts response['body']
460
-
461
- # --- PUBLISH (Asíncrono / Fire-and-Forget) ---
462
- # No espera respuesta.
463
- client.publish('audit/events',
464
- method: :post,
465
- body: { event: 'login', user_id: 1 }
466
- )
310
+ BugBunny::Client.prepend(Module.new {
311
+ def initialize(pool:)
312
+ super
313
+ @stack.use CorrelationInjector
314
+ end
315
+ })
467
316
  ```
468
317
 
469
- ### ⚠️ Consideraciones sobre RPC (Direct Reply-To)
470
-
471
- BugBunny utiliza el mecanismo nativo `amq.rabbitmq.reply-to` para las peticiones RPC. Esto maximiza el rendimiento eliminando la necesidad de crear colas temporales por cada petición.
472
-
473
- **Trade-off:**
474
- Al usar este mecanismo, las respuestas son efímeras. Si el proceso Cliente (tu aplicación Rails/Sidekiq) se reinicia abruptamente justo después de enviar la petición pero milisegundos antes de procesar la respuesta, **esa respuesta se perderá**.
475
-
476
- **Recomendación:**
477
- Diseña tus acciones de Controlador RPC (`POST`, `PUT`) para que sean **idempotentes**.
478
- * *Mal diseño:* "Crear pago" (si se reintenta, cobra doble).
479
- * *Buen diseño:* "Crear pago con ID X" (si se reintenta y ya existe, devuelve el recibo existente).
480
-
481
- Esto permite que, ante un `BugBunny::RequestTimeout` por caída del cliente, puedas reintentar la operación de forma segura.
482
-
483
318
  ---
484
319
 
485
- ## 🏗 Arquitectura REST-over-AMQP
486
-
487
- BugBunny desacopla el transporte de la lógica usando headers estándar.
320
+ ## 🧵 Guía de Producción
488
321
 
489
- 1. **Semántica:** El mensaje lleva headers `type` (URL) y `x-http-method` (Verbo).
490
- 2. **Ruteo:** El consumidor lee estos headers y ejecuta el controlador correspondiente.
491
- 3. **Parametros:** `params` unifica:
492
- * **Route Params:** `services/123` -> `params[:id] = 123`
493
- * **Query Params:** `services?force=true` -> `params[:force] = true`
494
- * **Body:** Payload JSON fusionado en el hash.
322
+ ### Connection Pooling
323
+ Es vital usar `ConnectionPool` si usas servidores web multi-hilo (Puma) o workers (Sidekiq). BugBunny no gestiona hilos internamente; se apoya en el pool.
495
324
 
496
- ### Logs Estructurados
325
+ ### Fork Safety
326
+ BugBunny incluye un `Railtie` que detecta automáticamente cuando Rails hace un "Fork" (ej: Puma en modo Cluster o Spring). Desconecta automáticamente las conexiones heredadas para evitar corrupción de datos en los sockets TCP.
497
327
 
498
- Facilita el debugging mostrando claramente qué recurso se está tocando y por dónde viaja.
328
+ ### RPC y "Direct Reply-To"
329
+ Para máxima velocidad, BugBunny usa `amq.rabbitmq.reply-to`.
330
+ * **Trade-off:** Si el cliente (Rails) se reinicia justo después de enviar un mensaje RPC pero antes de recibir la respuesta, esa respuesta se pierde.
331
+ * **Recomendación:** Diseña tus acciones RPC (`POST`, `PUT`) para que sean **idempotentes** (seguras de reintentar ante un timeout).
499
332
 
500
- ```text
501
- [BugBunny] [POST] '/services' | Exchange: 'cluster' (Type: direct) | Routing Key: 'node-1'
502
- ```
333
+ ### Seguridad
334
+ El Router incluye protecciones contra **Remote Code Execution (RCE)**. Verifica estrictamente que la clase instanciada herede de `BugBunny::Controller` antes de ejecutarla, impidiendo la inyección de clases arbitrarias de Ruby vía el header `type`.
503
335
 
504
336
  ---
505
337