nats_wave 1.1.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.idea/.gitignore +8 -0
  3. data/.idea/misc.xml +4 -0
  4. data/.idea/modules.xml +8 -0
  5. data/.idea/nats_wave.iml +169 -0
  6. data/.idea/vcs.xml +6 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +16 -0
  9. data/CHANGELOG.md +5 -0
  10. data/CODE_OF_CONDUCT.md +136 -0
  11. data/Gemfile +15 -0
  12. data/Gemfile.lock +332 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +985 -0
  15. data/Rakefile +12 -0
  16. data/config/nats_wave.yml +65 -0
  17. data/examples/catalog_model.rb +36 -0
  18. data/examples/configuration_examples.rb +68 -0
  19. data/examples/user_model.rb +58 -0
  20. data/lib/generators/nats_wave/install_generator.rb +40 -0
  21. data/lib/generators/nats_wave/templates/README +31 -0
  22. data/lib/generators/nats_wave/templates/create_nats_wave_failed_messages.rb +20 -0
  23. data/lib/generators/nats_wave/templates/create_nats_wave_failed_subscriptions.rb +20 -0
  24. data/lib/generators/nats_wave/templates/initializer.rb +64 -0
  25. data/lib/generators/nats_wave/templates/nats_wave.yml +65 -0
  26. data/lib/nats_wave/adapters/active_record.rb +206 -0
  27. data/lib/nats_wave/adapters/datadog_metrics.rb +93 -0
  28. data/lib/nats_wave/auto_registration.rb +109 -0
  29. data/lib/nats_wave/client.rb +142 -0
  30. data/lib/nats_wave/concerns/mappable.rb +172 -0
  31. data/lib/nats_wave/concerns/publishable.rb +216 -0
  32. data/lib/nats_wave/configuration.rb +105 -0
  33. data/lib/nats_wave/database_connector.rb +50 -0
  34. data/lib/nats_wave/dead_letter_queue.rb +146 -0
  35. data/lib/nats_wave/errors.rb +27 -0
  36. data/lib/nats_wave/message_transformer.rb +95 -0
  37. data/lib/nats_wave/metrics.rb +220 -0
  38. data/lib/nats_wave/middleware/authentication.rb +65 -0
  39. data/lib/nats_wave/middleware/base.rb +19 -0
  40. data/lib/nats_wave/middleware/logging.rb +58 -0
  41. data/lib/nats_wave/middleware/validation.rb +74 -0
  42. data/lib/nats_wave/model_mapper.rb +125 -0
  43. data/lib/nats_wave/model_registry.rb +150 -0
  44. data/lib/nats_wave/publisher.rb +151 -0
  45. data/lib/nats_wave/railtie.rb +111 -0
  46. data/lib/nats_wave/schema_registry.rb +77 -0
  47. data/lib/nats_wave/subscriber.rb +161 -0
  48. data/lib/nats_wave/version.rb +5 -0
  49. data/lib/nats_wave.rb +97 -0
  50. data/lib/tasks/nats_wave.rake +360 -0
  51. data/sig/nats_wave.rbs +5 -0
  52. metadata +385 -0
data/README.md ADDED
@@ -0,0 +1,985 @@
1
+ # 🌊 NatsWave
2
+
3
+ A comprehensive NATS-based event messaging system for Rails applications that enables seamless communication between
4
+ different teams, products, and services in asset management and valuation workflows.
5
+
6
+ [![Gem Version](https://badge.fury.io/rb/nats_wave.svg)](https://badge.fury.io/rb/nats_wave)
7
+ [![Build Status](https://github.com/PurpleWave/nats_wave/workflows/CI/badge.svg)](https://github.com/PurpleWave/nats_wave/actions)
8
+ [![Code Climate](https://codeclimate.com/github/PurpleWave/nats_wave/badges/gpa.svg)](https://codeclimate.com/github/PurpleWave/nats_wave)
9
+ [![Test Coverage](https://codeclimate.com/github/PurpleWave/nats_wave/badges/coverage.svg)](https://codeclimate.com/github/PurpleWave/nats_wave/coverage)
10
+
11
+ ## πŸ“‹ Table of Contents
12
+
13
+ - [Features](#-features)
14
+ - [Installation](#-installation)
15
+ - [Quick Start](#-quick-start)
16
+ - [Configuration](#-configuration)
17
+ - [Model-Based Architecture](#-model-based-architecture)
18
+ - [Publishing Events](#-publishing-events)
19
+ - [Subscribing to Events](#-subscribing-to-events)
20
+ - [Data Mapping & Transformation](#-data-mapping--transformation)
21
+ - [Monitoring & Metrics](#-monitoring--metrics)
22
+ - [Error Handling](#-error-handling)
23
+ - [Production Deployment](#-production-deployment)
24
+ - [API Reference](#-api-reference)
25
+ - [Examples](#-examples)
26
+ - [Contributing](#-contributing)
27
+ - [License](#-license)
28
+
29
+ ## ✨ Features
30
+
31
+ - **πŸš€ Model-Based Configuration** - Configure subscriptions and mappings directly in your models
32
+ - **πŸ”„ Automatic Event Publishing** - ActiveRecord integration publishes model changes automatically
33
+ - **πŸ—ΊοΈ Intelligent Data Mapping** - Transform data between different schemas and field names
34
+ - **πŸ“Š Datadog Integration** - Built-in metrics and monitoring support
35
+ - **πŸ›‘οΈ Error Handling** - Dead letter queues, retries, and graceful failure handling
36
+ - **πŸ”§ Middleware System** - Authentication, validation, and logging middleware
37
+ - **⚑ High Performance** - Connection pooling, async publishing, and batch operations
38
+ - **πŸ₯ Health Monitoring** - Built-in health checks and status endpoints
39
+ - **πŸ“ˆ Auto-Discovery** - Automatic registration of model subscriptions
40
+ - **πŸ§ͺ Test-Friendly** - Comprehensive test helpers and mocking support
41
+
42
+ ## πŸ“¦ Installation
43
+
44
+ Add this line to your Rails application's Gemfile:
45
+
46
+ ```ruby
47
+ gem 'nats_wave'
48
+
49
+ # Optional: For enhanced monitoring
50
+ gem 'dogstatsd-ruby' # For Datadog metrics
51
+ ```
52
+
53
+ And then execute:
54
+
55
+ ```bash
56
+ bundle install
57
+ ```
58
+
59
+ Generate the configuration files:
60
+
61
+ ```bash
62
+ rails generate nats_wave:install
63
+ rails db:migrate
64
+ ```
65
+
66
+ ## πŸš€ Quick Start
67
+
68
+ ### 1. Basic Configuration
69
+
70
+ ```ruby
71
+ # config/initializers/nats_wave.rb
72
+ NatsWave.configure do |config|
73
+ config.nats_url = ENV.fetch('NATS_URL', 'nats://localhost:4222')
74
+ config.service_name = 'valuation_service'
75
+ config.default_subject_prefix = "#{config.service_name}.events"
76
+ end
77
+ ```
78
+
79
+ ### 2. Add to Your Models
80
+
81
+ ```ruby
82
+ # app/models/asset.rb
83
+ class Asset < ApplicationRecord
84
+ include NatsWave::NatsPublishable
85
+ include NatsWave::Concerns::Mappable
86
+
87
+ # Automatically publish asset events
88
+ nats_publishable(
89
+ actions: [:create, :update, :destroy],
90
+ skip_attributes: [:notes, :edge_data],
91
+ include_associations: [:package, :estimates]
92
+ )
93
+
94
+ # Subscribe to external asset data from IMS
95
+ nats_wave_maps_from 'IMSAsset',
96
+ subjects: ['ims.assets.*', 'inventory.assets.*'],
97
+ field_mappings: {
98
+ 'asset_id' => 'unique_id',
99
+ 'description' => 'description',
100
+ 'manufacturer' => 'make',
101
+ 'model_name' => 'model',
102
+ 'year_manufactured' => 'year',
103
+ 'serial_number' => 'serial',
104
+ 'vin_number' => 'vin'
105
+ },
106
+ transformations: {
107
+ 'make' => ->(make) { make&.upcase&.strip },
108
+ 'year' => ->(year) { year.to_i if year.present? }
109
+ },
110
+ unique_fields: [:unique_id, :vin, :serial]
111
+ end
112
+ ```
113
+
114
+ ### 3. Start the Subscriber
115
+
116
+ ```bash
117
+ # Development - auto-starts with Rails
118
+ rails server
119
+
120
+ # Production - run dedicated subscriber process
121
+ rails nats_wave:start
122
+ ```
123
+
124
+ ### 4. Test the Integration
125
+
126
+ ```bash
127
+ # Test connectivity
128
+ rails nats_wave:health
129
+
130
+ # Show model subscriptions
131
+ rails nats_wave:model_subscriptions
132
+
133
+ # Publish a test message
134
+ rails nats_wave:test
135
+ ```
136
+
137
+ ## βš™οΈ Configuration
138
+
139
+ ### Environment Variables
140
+
141
+ ```bash
142
+ # Required
143
+ NATS_URL=nats://your-nats-server:4222
144
+ SERVICE_NAME=valuation_service
145
+
146
+ # Optional
147
+ NATS_AUTH_SECRET=your-secret-key
148
+ DD_AGENT_HOST=localhost # For Datadog metrics
149
+ DD_AGENT_PORT=8125
150
+ ```
151
+
152
+ ### Advanced Configuration
153
+
154
+ ```ruby
155
+ # config/initializers/nats_wave.rb
156
+ NatsWave.configure do |config|
157
+ # NATS Connection
158
+ config.nats_url = ENV.fetch('NATS_URL')
159
+ config.service_name = ENV.fetch('SERVICE_NAME')
160
+ config.connection_pool_size = 10
161
+ config.reconnect_attempts = 3
162
+
163
+ # Publishing
164
+ config.publishing_enabled = !Rails.env.test?
165
+ config.async_publishing = Rails.env.production?
166
+ config.default_subject_prefix = "#{config.service_name}.events"
167
+
168
+ # Subscription
169
+ config.subscription_enabled = !Rails.env.test?
170
+ config.queue_group = "#{config.service_name}_consumers"
171
+
172
+ # Middleware
173
+ config.middleware_authentication_enabled = Rails.env.production?
174
+ config.auth_secret_key = ENV['NATS_AUTH_SECRET']
175
+ config.middleware_logging_enabled = true
176
+ config.log_level = Rails.env.production? ? 'info' : 'debug'
177
+
178
+ # Error Handling
179
+ config.max_retries = 3
180
+ config.retry_delay = 5
181
+ end
182
+
183
+ # Configure Datadog metrics
184
+ if defined?(Datadog::Statsd)
185
+ NatsWave::Metrics.configure_datadog(
186
+ namespace: 'valuation_app.nats_wave',
187
+ tags: {
188
+ service: NatsWave.configuration.service_name,
189
+ environment: Rails.env
190
+ }
191
+ )
192
+ end
193
+ ```
194
+
195
+ ## πŸ—οΈ Model-Based Architecture
196
+
197
+ NatsWave uses a model-centric approach where each model defines its own subscriptions and mappings.
198
+
199
+ ### Publishing Events
200
+
201
+ ```ruby
202
+
203
+ class Package < ApplicationRecord
204
+ include NatsWave::NatsPublishable
205
+
206
+ nats_publishable(
207
+ actions: [:create, :update], # Which actions to publish
208
+ skip_attributes: [:notes, :valuation_notes], # Attributes to exclude
209
+ include_associations: [:organization, :assets, :contact_user], # Include related data
210
+ if: -> { workflow_status == 'active' }, # Conditional publishing
211
+ unless: -> { is_shared? }, # Skip conditions
212
+ subject_prefix: 'packages' # Custom subject prefix
213
+ )
214
+
215
+ private
216
+
217
+ # Add custom metadata to published events
218
+ def nats_wave_metadata
219
+ {
220
+ organization_name: organization&.name,
221
+ asset_count: assets.count,
222
+ total_estimated_value: assets.sum(&:estimated_hammer),
223
+ valuation_complete: valuation_complete?
224
+ }
225
+ end
226
+ end
227
+ ```
228
+
229
+ ### Subscribing to External Events
230
+
231
+ ```ruby
232
+
233
+ class Asset < ApplicationRecord
234
+ include NatsWave::Concerns::Mappable
235
+
236
+ # Map from inventory management system
237
+ nats_wave_maps_from 'InventoryAsset',
238
+ subjects: ['inventory.assets.*', 'warehouse.assets.*'],
239
+ field_mappings: {
240
+ 'inventory_id' => 'unique_id',
241
+ 'asset_description' => 'description',
242
+ 'manufacturer' => 'make',
243
+ 'model_number' => 'model',
244
+ 'year_built' => 'year',
245
+ 'serial_num' => 'serial',
246
+ 'vin_code' => 'vin',
247
+ 'location_code' => 'location'
248
+ },
249
+ transformations: {
250
+ 'make' => ->(make) { make&.upcase&.strip },
251
+ 'model' => ->(model) { model&.titleize&.strip },
252
+ 'year' => ->(year) { year.to_i if year.present? },
253
+ 'estimated_hammer' => ->(value) { value.to_f.round(2) }
254
+ },
255
+ unique_fields: [:unique_id, :vin, :serial],
256
+ sync_strategy: :upsert,
257
+ conditions: {
258
+ 'status' => 'available',
259
+ 'condition' => ['good', 'fair', 'excellent']
260
+ }
261
+
262
+ # Map from auction system
263
+ nats_wave_maps_from 'AuctionItem',
264
+ subjects: ['auction.items.*'],
265
+ field_mappings: {
266
+ 'lot_number' => 'lot_number',
267
+ 'hammer_price' => 'pw_contract_price',
268
+ 'sale_date' => 'pw_contract_date'
269
+ },
270
+ conditions: {
271
+ 'auction_status' => 'sold'
272
+ }
273
+ end
274
+ ```
275
+
276
+ ### Custom Event Handlers
277
+
278
+ ```ruby
279
+
280
+ class Estimate < ApplicationRecord
281
+ include NatsWave::Concerns::Mappable
282
+
283
+ # Subscribe to valuation requests
284
+ nats_wave_subscribes_to 'valuation.requests.*' do |message|
285
+ ValuationRequestProcessor.process_request(message)
286
+ end
287
+
288
+ # Subscribe to market price updates
289
+ nats_wave_subscribes_to 'market.prices.*' do |message|
290
+ MarketPriceService.update_estimates(message)
291
+ end
292
+
293
+ # Subscribe to auction results for comparison
294
+ nats_wave_subscribes_to 'auction.results.*' do |message|
295
+ AuctionResultAnalyzer.analyze_accuracy(message)
296
+ end
297
+ end
298
+ ```
299
+
300
+ ## πŸ“€ Publishing Events
301
+
302
+ ### Automatic Publishing
303
+
304
+ Events are automatically published when you include `NatsPublishable` in your models:
305
+
306
+ ```ruby
307
+ # This automatically publishes to "valuation_service.events.asset.create"
308
+ asset = Asset.create!(
309
+ make: "Caterpillar",
310
+ model: "320D",
311
+ year: 2018,
312
+ serial: "ABC123456",
313
+ description: "Excavator - Track Type"
314
+ )
315
+
316
+ # This publishes to "valuation_service.events.asset.update"
317
+ asset.update!(estimated_hammer: 125000)
318
+
319
+ # This publishes to "valuation_service.events.asset.destroy"
320
+ asset.destroy!
321
+ ```
322
+
323
+ ### Manual Publishing
324
+
325
+ ```ruby
326
+ # Publish custom valuation events
327
+ NatsWave.publish(
328
+ subject: 'valuation.completed',
329
+ model: 'ValuationSession',
330
+ action: 'complete',
331
+ data: {
332
+ package_id: package.id,
333
+ asset_count: package.assets.count,
334
+ total_value: package.total_estimated_value,
335
+ appraiser_id: current_user.id
336
+ },
337
+ metadata: {
338
+ organization_id: package.organization.id,
339
+ completion_time: Time.current,
340
+ valuation_method: 'comparative_analysis'
341
+ }
342
+ )
343
+
344
+ # Batch publishing for performance
345
+ events = assets.map do |asset|
346
+ {
347
+ subject: 'asset.valued',
348
+ model: 'Asset',
349
+ action: 'update',
350
+ data: asset.attributes.except('notes', 'edge_data')
351
+ }
352
+ end
353
+
354
+ NatsWave.client.publish_batch(events)
355
+ ```
356
+
357
+ ### Conditional Publishing
358
+
359
+ ```ruby
360
+
361
+ class Asset < ApplicationRecord
362
+ include NatsWave::NatsPublishable
363
+
364
+ nats_publishable(
365
+ if: -> { should_publish_to_external_systems? },
366
+ unless: -> { asset_status == 'draft' }
367
+ )
368
+
369
+ private
370
+
371
+ def should_publish_to_external_systems?
372
+ asset_status == 'completed' &&
373
+ package.workflow_status == 'finalized' &&
374
+ !package.is_shared?
375
+ end
376
+ end
377
+ ```
378
+
379
+ ## πŸ“₯ Subscribing to Events
380
+
381
+ ### Automatic Model Syncing
382
+
383
+ When you configure `nats_wave_maps_from`, NatsWave automatically:
384
+
385
+ 1. Subscribes to the specified subjects
386
+ 2. Transforms incoming data using field mappings
387
+ 3. Applies custom transformations
388
+ 4. Syncs data to your local models
389
+
390
+ ```ruby
391
+ # Incoming message from inventory system:
392
+ {
393
+ "subject": "inventory.assets.update",
394
+ "model": "InventoryAsset",
395
+ "action": "update",
396
+ "data": {
397
+ "inventory_id": "INV-2024-001",
398
+ "asset_description": "2020 Ford F-150 Pickup Truck",
399
+ "manufacturer": "ford",
400
+ "model_number": "F-150",
401
+ "year_built": "2020",
402
+ "serial_num": "1FTFW1ET5LFA12345",
403
+ "vin_code": "1FTFW1ET5LFA12345",
404
+ "estimated_value": 35000.00
405
+ }
406
+ }
407
+
408
+ # Automatically transforms to:
409
+ {
410
+ "unique_id": "INV-2024-001",
411
+ "description": "2020 Ford F-150 Pickup Truck",
412
+ "make": "FORD",
413
+ "model": "F-150",
414
+ "year": 2020,
415
+ "serial": "1FTFW1ET5LFA12345",
416
+ "vin": "1FTFW1ET5LFA12345",
417
+ "estimated_hammer": 35000.00
418
+ }
419
+
420
+ # And updates/creates the local Asset record
421
+ ```
422
+
423
+ ### Custom Subscription Handlers
424
+
425
+ ```ruby
426
+
427
+ class Organization < ApplicationRecord
428
+ include NatsWave::Concerns::Mappable
429
+
430
+ # Process user activity for analytics
431
+ nats_wave_subscribes_to 'users.*.login', 'users.*.logout' do |message|
432
+ UserActivityTracker.track_activity(message)
433
+ end
434
+
435
+ # Process package completions with custom queue group
436
+ nats_wave_subscribes_to 'packages.*.completed',
437
+ queue_group: 'reporting_processors' do |message|
438
+ PackageCompletionReporter.generate_report(message)
439
+ end
440
+ end
441
+ ```
442
+
443
+ ## πŸ—ΊοΈ Data Mapping & Transformation
444
+
445
+ ### Field Mappings
446
+
447
+ ```ruby
448
+ nats_wave_maps_from 'ExternalAsset',
449
+ field_mappings: {
450
+ 'external_id' => 'unique_id',
451
+ 'asset_make' => 'make',
452
+ 'asset_model' => 'model',
453
+ 'manufacture_year' => 'year',
454
+ 'serial_number' => 'serial',
455
+ 'vehicle_identification' => 'vin',
456
+ 'asset_location' => 'location'
457
+ }
458
+ ```
459
+
460
+ ### Data Transformations
461
+
462
+ ```ruby
463
+ nats_wave_maps_from 'ExternalAsset',
464
+ transformations: {
465
+ # Lambda transformations
466
+ 'estimated_hammer' => ->(value) { value.to_f.round(2) },
467
+ 'make' => ->(make) { make&.upcase&.strip },
468
+ 'model' => ->(model) { model&.titleize&.strip },
469
+
470
+ # Method transformations
471
+ 'vin' => :normalize_vin,
472
+ 'serial' => :normalize_serial,
473
+
474
+ # Complex transformations with access to full record
475
+ 'description' => ->(value, record) {
476
+ "#{record['year']} #{record['make']} #{record['model']} #{value}".strip
477
+ }
478
+ }
479
+
480
+ private
481
+
482
+ def normalize_vin(vin)
483
+ vin&.upcase&.gsub(/[^A-Z0-9]/, '')
484
+ end
485
+
486
+ def normalize_serial(serial)
487
+ serial&.upcase&.strip
488
+ end
489
+ ```
490
+
491
+ ### Conditional Syncing
492
+
493
+ ```ruby
494
+ nats_wave_maps_from 'ExternalAsset',
495
+ conditions: {
496
+ 'status' => 'available', # Exact match
497
+ 'condition' => ['good', 'fair', 'excellent'], # Array inclusion
498
+ 'value' => ->(value) { value.to_f > 1000 }, # Custom logic
499
+ 'category' => /^(vehicle|equipment|machinery)$/ # Regex match
500
+ }
501
+ ```
502
+
503
+ ### Sync Strategies
504
+
505
+ ```ruby
506
+ nats_wave_maps_from 'ExternalAsset',
507
+ sync_strategy: :upsert, # Create or update (default)
508
+ # sync_strategy: :create_only, # Only create new records
509
+ # sync_strategy: :update_only, # Only update existing records
510
+ unique_fields: [:unique_id, :vin, :serial] # Fields to match existing records
511
+ ```
512
+
513
+ ## πŸ“Š Monitoring & Metrics
514
+
515
+ ### Datadog Integration
516
+
517
+ NatsWave provides comprehensive Datadog metrics out of the box:
518
+
519
+ ```ruby
520
+ # Automatic metrics tracked:
521
+ -nats_wave.messages.published # Messages published by subject/model
522
+ -nats_wave.messages.received # Messages received by subject/model
523
+ -nats_wave.processing_time # Message processing duration
524
+ -nats_wave.errors # Errors by type and subject
525
+ -nats_wave.connection_status # NATS connection health
526
+ -nats_wave.sync.operations # Model sync operations
527
+ -nats_wave.retries # Retry attempts
528
+ -nats_wave.valuations.completed # Valuation completion events
529
+ -nats_wave.assets.synced # Asset synchronization events
530
+ ```
531
+
532
+ ### Health Checks
533
+
534
+ ```ruby
535
+ # Built-in health check endpoint
536
+ GET /health/n ats_wave
537
+
538
+ # Response:
539
+ {
540
+ "nats_connected": true,
541
+ "database_connected": true,
542
+ "timestamp": "2024-01-01T12:00:00Z",
543
+ "service_name": "valuation_service",
544
+ "version": "1.1.0",
545
+ "instance_id": "web-1",
546
+ "published_subjects": [
547
+ "valuation_service.events.asset.*",
548
+ "valuation_service.events.package.*",
549
+ "valuation_service.events.estimate.*"
550
+ ],
551
+ "subscribed_subjects": [
552
+ "inventory.assets.*",
553
+ "auction.results.*",
554
+ "market.prices.*"
555
+ ],
556
+ "nats_url": "nats://your-server:4222"
557
+ }
558
+ ```
559
+
560
+ ### Monitoring Commands
561
+
562
+ ```bash
563
+ # Check overall health
564
+ rails nats_wave:health
565
+
566
+ # Show all model subscriptions
567
+ rails nats_wave:model_subscriptions
568
+
569
+ # Monitor live NATS activity
570
+ rails nats_wave:monitor
571
+
572
+ # Show failed messages
573
+ rails nats_wave:show_failed
574
+
575
+ # Retry failed messages
576
+ rails nats_wave:retry_failed
577
+ ```
578
+
579
+ ## πŸ›‘οΈ Error Handling
580
+
581
+ ### Dead Letter Queue
582
+
583
+ Failed messages are automatically stored in a dead letter queue with retry logic:
584
+
585
+ ```ruby
586
+ # Automatic retry with exponential backoff
587
+ # Retry 1: after 5 seconds
588
+ # Retry 2: after 25 seconds
589
+ # Retry 3: after 125 seconds
590
+ # After max retries: moved to permanent failure storage
591
+ ```
592
+
593
+ ### Error Recovery
594
+
595
+ ```ruby
596
+ # Retry failed messages
597
+ rails nats_wave: retry_failed
598
+
599
+ # Clear failed messages (after manual review)
600
+ rails nats_wave: clear_failed
601
+
602
+ # View failed message details
603
+ failed_messages = NatsWave::DeadLetterQueue.new(NatsWave.configuration)
604
+ failed_messages.get_failed_messages.each do |msg|
605
+ puts "Error: #{msg[:error]}"
606
+ puts "Retry Count: #{msg[:retry_count]}"
607
+ puts "Message: #{msg[:message]}"
608
+ end
609
+ ```
610
+
611
+ ### Custom Error Handling
612
+
613
+ ```ruby
614
+
615
+ class CustomErrorHandler
616
+ def self.handle_failed_message(message, error, retry_count)
617
+ # Send to monitoring service
618
+ ErrorTracker.notify(error, {
619
+ message: message,
620
+ retry_count: retry_count,
621
+ service: 'nats_wave'
622
+ })
623
+
624
+ # Custom retry logic for valuation-specific errors
625
+ if retry_count < 5 && error.is_a?(ValuationTimeoutError)
626
+ RetryValuationJob.set(wait: 30.seconds).perform_later(message)
627
+ end
628
+ end
629
+ end
630
+ ```
631
+
632
+ ## πŸš€ Production Deployment
633
+
634
+ ### Docker Setup
635
+
636
+ ```dockerfile
637
+ # Dockerfile
638
+ FROM ruby:3.2-slim
639
+
640
+ WORKDIR /app
641
+ COPY Gemfile Gemfile.lock ./
642
+ RUN bundle install --deployment
643
+
644
+ COPY . .
645
+
646
+ # Health check
647
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
648
+ CMD curl -f http://localhost:3000/health/nats_wave || exit 1
649
+
650
+ EXPOSE 3000
651
+ CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
652
+ ```
653
+
654
+ ### Docker Compose
655
+
656
+ ```yaml
657
+ # docker-compose.yml
658
+ version: '3.8'
659
+ services:
660
+ nats:
661
+ image: nats:latest
662
+ ports:
663
+ - "4222:4222"
664
+ - "8222:8222"
665
+ command: "--http_port 8222 --js"
666
+
667
+ valuation_app:
668
+ build: .
669
+ depends_on:
670
+ - nats
671
+ - redis
672
+ - postgres
673
+ environment:
674
+ - NATS_URL=nats://nats:4222
675
+ - SERVICE_NAME=valuation_service
676
+ ports:
677
+ - "3000:3000"
678
+ ```
679
+
680
+ ### Kubernetes Deployment
681
+
682
+ ```yaml
683
+ # k8s/deployment.yaml
684
+ apiVersion: apps/v1
685
+ kind: Deployment
686
+ metadata:
687
+ name: valuation-service
688
+ spec:
689
+ replicas: 3
690
+ selector:
691
+ matchLabels:
692
+ app: valuation-service
693
+ template:
694
+ metadata:
695
+ labels:
696
+ app: valuation-service
697
+ spec:
698
+ containers:
699
+ - name: app
700
+ image: valuation-service:latest
701
+ env:
702
+ - name: NATS_URL
703
+ value: "nats://nats-cluster:4222"
704
+ - name: SERVICE_NAME
705
+ value: "valuation-service"
706
+ ports:
707
+ - containerPort: 3000
708
+ livenessProbe:
709
+ httpGet:
710
+ path: /health/nats_wave
711
+ port: 3000
712
+ initialDelaySeconds: 30
713
+ periodSeconds: 10
714
+ ```
715
+
716
+ ### Separate Subscriber Process
717
+
718
+ For high-volume applications, run subscribers in dedicated processes:
719
+
720
+ ```bash
721
+ # Procfile
722
+ web: bundle exec rails server -p $PORT
723
+ worker: bundle exec rails nats_wave:start
724
+ ```
725
+
726
+ ## πŸ’‘ Examples
727
+
728
+ ### Asset Management Integration
729
+
730
+ ```ruby
731
+ # app/models/asset.rb
732
+ class Asset < ApplicationRecord
733
+ include NatsWave::NatsPublishable
734
+ include NatsWave::Concerns::Mappable
735
+
736
+ nats_publishable(
737
+ actions: [:create, :update],
738
+ include_associations: [:package, :estimates, :images]
739
+ )
740
+
741
+ # Sync from inventory management system
742
+ nats_wave_maps_from 'InventoryItem',
743
+ subjects: ['inventory.items.*'],
744
+ field_mappings: {
745
+ 'item_id' => 'unique_id',
746
+ 'description' => 'description',
747
+ 'manufacturer' => 'make',
748
+ 'model_name' => 'model',
749
+ 'year_manufactured' => 'year',
750
+ 'serial_number' => 'serial',
751
+ 'vin_number' => 'vin',
752
+ 'location_code' => 'location'
753
+ },
754
+ transformations: {
755
+ 'estimated_hammer' => ->(value) { value.to_f.round(2) },
756
+ 'make' => ->(make) { make&.upcase&.strip }
757
+ }
758
+
759
+ # Sync from auction management
760
+ nats_wave_maps_from 'AuctionAsset',
761
+ subjects: ['auction.assets.*'],
762
+ field_mappings: {
763
+ 'lot_number' => 'lot_number',
764
+ 'hammer_price' => 'pw_contract_price',
765
+ 'sale_date' => 'pw_contract_date'
766
+ },
767
+ conditions: {
768
+ 'status' => 'sold'
769
+ }
770
+ end
771
+
772
+ # app/models/package.rb
773
+ class Package < ApplicationRecord
774
+ include NatsWave::NatsPublishable
775
+
776
+ nats_publishable(
777
+ actions: [:create, :update],
778
+ if: -> { workflow_status == 'finalized' }
779
+ )
780
+
781
+ # Listen for IMS submission confirmations
782
+ nats_wave_subscribes_to 'ims.submissions.confirmed.*' do |message|
783
+ package = Package.find_by(id: message['data']['package_id'])
784
+ package&.mark_as_submitted!
785
+ end
786
+ end
787
+ ```
788
+
789
+ ### Valuation Workflow
790
+
791
+ ```ruby
792
+ # app/models/estimate.rb
793
+ class Estimate < ApplicationRecord
794
+ include NatsWave::NatsPublishable
795
+ include NatsWave::Concerns::Mappable
796
+
797
+ nats_publishable(
798
+ skip_attributes: [:notes, :source_data]
799
+ )
800
+
801
+ # Sync from external valuation service
802
+ nats_wave_maps_from 'ExternalValuation',
803
+ subjects: ['valuations.external.*'],
804
+ field_mappings: {
805
+ 'valuation_id' => 'key',
806
+ 'asset_identifier' => 'asset_id',
807
+ 'appraiser_id' => 'user_id',
808
+ 'estimated_value' => 'value',
809
+ 'valuation_notes' => 'notes'
810
+ },
811
+ transformations: {
812
+ 'value' => ->(value) { (value.to_f * 100).round }, # Convert to cents
813
+ 'notes' => ->(notes) { notes&.strip }
814
+ },
815
+ unique_fields: [:key, :asset_id, :user_id]
816
+
817
+ # Listen for market price updates
818
+ nats_wave_subscribes_to 'market.prices.*' do |message|
819
+ MarketPriceAnalyzer.update_estimates_based_on_market(message)
820
+ end
821
+ end
822
+
823
+ # app/models/valuation_request.rb
824
+ class ValuationRequest < ApplicationRecord
825
+ include NatsWave::NatsPublishable
826
+
827
+ nats_publishable(
828
+ actions: [:create, :update]
829
+ )
830
+
831
+ # Process valuation assignments
832
+ nats_wave_subscribes_to 'valuations.assignments.*' do |message|
833
+ ValuationAssignmentProcessor.process_assignment(message)
834
+ end
835
+ end
836
+ ```
837
+
838
+ ### Organization & User Management
839
+
840
+ ```ruby
841
+ # app/models/user.rb
842
+ class User < ApplicationRecord
843
+ include NatsWave::NatsPublishable
844
+ include NatsWave::Concerns::Mappable
845
+
846
+ nats_publishable(
847
+ skip_attributes: [:api_auth_key, :source_details]
848
+ )
849
+
850
+ # Sync from authentication service
851
+ nats_wave_maps_from 'AuthUser',
852
+ subjects: ['auth.users.*'],
853
+ field_mappings: {
854
+ 'user_id' => 'key',
855
+ 'email_address' => 'email',
856
+ 'full_name' => 'name',
857
+ 'is_active' => 'active'
858
+ },
859
+ transformations: {
860
+ 'email' => ->(email) { email&.downcase },
861
+ 'active' => ->(active) { active == 'true' || active == true }
862
+ },
863
+ unique_fields: [:key, :email]
864
+ end
865
+
866
+ # app/models/organization.rb
867
+ class Organization < ApplicationRecord
868
+ include NatsWave::NatsPublishable
869
+ include NatsWave::Concerns::Mappable
870
+
871
+ nats_publishable(
872
+ skip_attributes: [:api_auth_key, :workflow_config]
873
+ )
874
+
875
+ # Sync from CRM system
876
+ nats_wave_maps_from 'CRMOrganization',
877
+ subjects: ['crm.organizations.*'],
878
+ field_mappings: {
879
+ 'organization_id' => 'key',
880
+ 'company_name' => 'name',
881
+ 'primary_contact' => 'contact',
882
+ 'email_address' => 'email',
883
+ 'phone_number' => 'phone',
884
+ 'is_active' => 'active'
885
+ },
886
+ unique_fields: [:key, :name]
887
+ end
888
+ ```
889
+
890
+ ### Image and Media Processing
891
+
892
+ ```ruby
893
+ # app/models/image.rb
894
+ class Image < ApplicationRecord
895
+ include NatsWave::NatsPublishable
896
+
897
+ nats_publishable(
898
+ actions: [:create, :update],
899
+ skip_attributes: [:source_data, :import_url],
900
+ if: -> { !deleted? && sizes.present? }
901
+ )
902
+
903
+ # Listen for image processing completion
904
+ nats_wave_subscribes_to 'media.processing.completed.*' do |message|
905
+ ImageProcessingService.handle_completion(message)
906
+ end
907
+
908
+ # Listen for IMS image submissions
909
+ nats_wave_subscribes_to 'ims.images.*' do |message|
910
+ IMSImageProcessor.process_submission(message)
911
+ end
912
+ end
913
+
914
+ # app/models/video.rb
915
+ class Video < ApplicationRecord
916
+ include NatsWave::NatsPublishable
917
+
918
+ nats_publishable(
919
+ actions: [:create, :update],
920
+ if: -> { !deleted? && url.present? }
921
+ )
922
+
923
+ # Listen for video processing updates
924
+ nats_wave_subscribes_to 'media.videos.*' do |message|
925
+ VideoProcessingService.handle_update(message)
926
+ end
927
+ end
928
+ ```
929
+
930
+ ## 🀝 Contributing
931
+
932
+ We welcome contributions! Please see our [Contributing Guide](CODE_OF_CONDUCT.md) for details.
933
+
934
+ ### Development Setup
935
+
936
+ ```bash
937
+ git clone https://github.com/PurpleWave/nats_wave.git
938
+ cd nats_wave
939
+ bundle install
940
+
941
+ # Start NATS server for testing
942
+ docker run -p 4222:4222 -p 8222:8222 nats:latest --http_port 8222
943
+
944
+ # Run tests
945
+ bundle exec rspec
946
+
947
+ # Run linting
948
+ bundle exec rubocop
949
+ ```
950
+
951
+ ### Testing
952
+
953
+ ```bash
954
+ # Run all tests
955
+ bundle exec rspec
956
+
957
+ # Run specific test files
958
+ bundle exec rspec spec/nats_wave/publisher_spec.rb
959
+
960
+ # Run with coverage
961
+ COVERAGE=true bundle exec rspec
962
+ ```
963
+
964
+ ## πŸ“ Changelog
965
+
966
+ See [CHANGELOG.md](CHANGELOG.md) for details about changes in each version.
967
+
968
+ ## πŸ“„ License
969
+
970
+ This gem is available as open source under the terms of the [MIT License](LICENSE.txt).
971
+
972
+ ## πŸ™‹β€β™‚οΈ Support
973
+
974
+ - **Documentation**: [GitHub Wiki](https://github.com/PurpleWave/nats_wave/wiki)
975
+ - **Issues**: [GitHub Issues](https://github.com/PurpleWave/nats_wave/issues)
976
+ - **Discussions**: [GitHub Discussions](https://github.com/PurpleWave/nats_wave/discussions)
977
+
978
+ ## 🏒 About Purple Wave
979
+
980
+ NatsWave is developed and maintained by [Purple Wave](https://purplewave.com) to enable seamless communication between
981
+ our microservices and teams in the asset valuation and auction industry.
982
+
983
+ ---
984
+
985
+ **Made with ❀️ by Jeffrey Dabo**