nats_wave 1.1.8 → 1.1.10
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/Gemfile.lock +1 -1
- data/lib/nats_wave/active_record_extension.rb +93 -0
- data/lib/nats_wave/adapters/active_record.rb +207 -0
- data/lib/nats_wave/adapters/datadog_metrics.rb +1 -1
- data/lib/nats_wave/client.rb +411 -154
- data/lib/nats_wave/concerns/mappable.rb +481 -117
- data/lib/nats_wave/configuration.rb +1 -1
- data/lib/nats_wave/database_connector.rb +51 -0
- data/lib/nats_wave/publisher.rb +142 -39
- data/lib/nats_wave/railtie.rb +134 -1
- data/lib/nats_wave/subscriber.rb +391 -1
- data/lib/nats_wave/version.rb +1 -1
- data/lib/nats_wave.rb +114 -8
- metadata +3 -3
- data/lib/nats_wave/concerns/publishable.rb +0 -216
@@ -18,7 +18,7 @@ module NatsWave
|
|
18
18
|
def initialize(options = {})
|
19
19
|
@nats_url = ENV['NATS_URL'] || "nats://localhost:4222"
|
20
20
|
@service_name = ENV['NATS_SERVICE_NAME'] || "purplewave"
|
21
|
-
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.
|
21
|
+
@version = ENV['NATS_SERVICE_VERSION'] || "1.1.10"
|
22
22
|
@instance_id = ENV['NATS_INSTANCE_ID'] || Socket.gethostname
|
23
23
|
@database_url = ENV['NATS_DATABASE_URL'] || nil
|
24
24
|
@connection_pool_size = (ENV['NATS_CONNECTION_POOL_SIZE'] || 10).to_i
|
@@ -1,3 +1,54 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# module NatsWave
|
4
|
+
# class DatabaseConnector
|
5
|
+
# def initialize(config)
|
6
|
+
# @config = config
|
7
|
+
# @adapter = determine_adapter
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# def apply_change(model:, action:, data:, metadata:)
|
11
|
+
# case action.to_s.downcase
|
12
|
+
# when 'create'
|
13
|
+
# create_record(model, data, metadata)
|
14
|
+
# when 'update'
|
15
|
+
# update_record(model, data, metadata)
|
16
|
+
# when 'delete', 'destroy'
|
17
|
+
# delete_record(model, data, metadata)
|
18
|
+
# else
|
19
|
+
# NatsWave.logger.warn("Unknown action: #{action}")
|
20
|
+
# end
|
21
|
+
# rescue StandardError => e
|
22
|
+
# NatsWave.logger.error("Database operation failed: #{e.message}")
|
23
|
+
# raise DatabaseError, "Database operation failed: #{e.message}"
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def connected?
|
27
|
+
# @adapter.connected?
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# private
|
31
|
+
#
|
32
|
+
# def determine_adapter
|
33
|
+
# raise ConfigurationError, 'No supported database adapter found' unless defined?(ActiveRecord)
|
34
|
+
#
|
35
|
+
# Adapters::ActiveRecord.new(@config)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def create_record(model, data, metadata)
|
39
|
+
# @adapter.create(model, data, metadata)
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def update_record(model, data, metadata)
|
43
|
+
# @adapter.update(model, data, metadata)
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def delete_record(model, data, metadata)
|
47
|
+
# @adapter.delete(model, data, metadata)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
|
1
52
|
# frozen_string_literal: true
|
2
53
|
|
3
54
|
module NatsWave
|
data/lib/nats_wave/publisher.rb
CHANGED
@@ -1,47 +1,150 @@
|
|
1
|
-
# # frozen_string_literal: true
|
2
|
-
#
|
3
|
-
# require 'securerandom'
|
4
|
-
# require 'json'
|
5
|
-
#
|
6
|
-
# # begin
|
7
|
-
# # require 'nats/client'
|
8
|
-
# # rescue LoadError
|
9
|
-
# # # NATS not available - define a mock for testing
|
10
|
-
# # module NATS
|
11
|
-
# # def self.connect(url, options = {})
|
12
|
-
# # NatsClient.new
|
13
|
-
# # end
|
14
|
-
# #
|
15
|
-
# # class NatsClient
|
16
|
-
# # def connected?
|
17
|
-
# # false
|
18
|
-
# # end
|
1
|
+
# # # frozen_string_literal: true
|
19
2
|
# #
|
20
|
-
# #
|
21
|
-
# #
|
22
|
-
# # end
|
3
|
+
# # require 'securerandom'
|
4
|
+
# # require 'json'
|
23
5
|
# #
|
24
|
-
# #
|
25
|
-
# #
|
26
|
-
# #
|
27
|
-
# #
|
28
|
-
# #
|
6
|
+
# # # begin
|
7
|
+
# # # require 'nats/client'
|
8
|
+
# # # rescue LoadError
|
9
|
+
# # # # NATS not available - define a mock for testing
|
10
|
+
# # # module NATS
|
11
|
+
# # # def self.connect(url, options = {})
|
12
|
+
# # # NatsClient.new
|
13
|
+
# # # end
|
14
|
+
# # #
|
15
|
+
# # # class NatsClient
|
16
|
+
# # # def connected?
|
17
|
+
# # # false
|
18
|
+
# # # end
|
19
|
+
# # #
|
20
|
+
# # # def publish(subject, message)
|
21
|
+
# # # puts "NATS: Publishing to #{subject}: #{message}"
|
22
|
+
# # # end
|
23
|
+
# # #
|
24
|
+
# # # def subscribe(subject, options = {})
|
25
|
+
# # # puts "NATS: Subscribing to #{subject}"
|
26
|
+
# # # yield('{"mock": "message"}') if block_given?
|
27
|
+
# # # Subscription.new
|
28
|
+
# # # end
|
29
|
+
# # #
|
30
|
+
# # # def close
|
31
|
+
# # # true
|
32
|
+
# # # end
|
33
|
+
# # # end
|
34
|
+
# # #
|
35
|
+
# # # class Subscription
|
36
|
+
# # # def unsubscribe
|
37
|
+
# # # true
|
38
|
+
# # # end
|
39
|
+
# # # end
|
40
|
+
# # # end
|
41
|
+
# # # end
|
29
42
|
# #
|
30
|
-
# # def close
|
31
|
-
# # true
|
32
|
-
# # end
|
33
|
-
# # end
|
34
|
-
# #
|
35
|
-
# # class Subscription
|
36
|
-
# # def unsubscribe
|
37
|
-
# # true
|
38
|
-
# # end
|
39
|
-
# # end
|
40
|
-
# # end
|
41
|
-
# # end
|
42
43
|
#
|
44
|
+
# # frozen_string_literal: true
|
45
|
+
#
|
46
|
+
# module NatsWave
|
47
|
+
# class Publisher
|
48
|
+
# attr_reader :config, :client
|
49
|
+
#
|
50
|
+
# def initialize(config, client, middleware_stack = [])
|
51
|
+
# @config = config
|
52
|
+
# @client = client
|
53
|
+
# @message_transformer = MessageTransformer.new(config)
|
54
|
+
# @dead_letter_queue = DeadLetterQueue.new(config) if config.dead_letter_queue
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def publish(subject:, model:, action:, data:, metadata: {})
|
58
|
+
# return unless @config.publishing_enabled
|
59
|
+
#
|
60
|
+
# message = build_message(subject, model, action, data, metadata)
|
61
|
+
# full_subject = build_full_subject(subject)
|
62
|
+
#
|
63
|
+
# if @config.async_publishing && defined?(Concurrent)
|
64
|
+
# publish_async(full_subject, message)
|
65
|
+
# else
|
66
|
+
# publish_sync(full_subject, message)
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# # Metrics.increment_published_messages(full_subject)
|
70
|
+
# rescue => e
|
71
|
+
# NatsWave.logger.error("Failed to publish message: #{e.message}")
|
72
|
+
# @dead_letter_queue&.store_failed_message(message, e, 0)
|
73
|
+
# raise PublishError, "Failed to publish message: #{e.message}"
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# def publish_batch(events)
|
77
|
+
# batch_message = {
|
78
|
+
# batch_id: SecureRandom.uuid,
|
79
|
+
# events: events,
|
80
|
+
# timestamp: Time.current.iso8601,
|
81
|
+
# source: build_source_info
|
82
|
+
# }
|
83
|
+
#
|
84
|
+
# subject = "#{@config.default_subject_prefix}.batch"
|
85
|
+
#
|
86
|
+
# @client.publish(subject, batch_message.to_json)
|
87
|
+
#
|
88
|
+
# # Metrics.increment_published_messages(subject)
|
89
|
+
# NatsWave.logger.info("Published batch with #{events.size} events")
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# def connected?
|
93
|
+
# @client&.connected?
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# def disconnect
|
97
|
+
# # Publisher cleanup if needed
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# private
|
101
|
+
#
|
102
|
+
# def build_message(subject, model, action, data, metadata)
|
103
|
+
# @message_transformer.build_standard_message(
|
104
|
+
# subject: subject,
|
105
|
+
# model: model,
|
106
|
+
# action: action,
|
107
|
+
# data: data,
|
108
|
+
# metadata: metadata,
|
109
|
+
# source: build_source_info
|
110
|
+
# )
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# def build_source_info
|
114
|
+
# {
|
115
|
+
# service: @config.service_name,
|
116
|
+
# version: @config.version,
|
117
|
+
# instance_id: @config.instance_id,
|
118
|
+
# environment: defined?(Rails) ? Rails.env : 'test'
|
119
|
+
# }
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# def build_full_subject(subject)
|
123
|
+
# if @config.default_subject_prefix && !subject.start_with?(@config.default_subject_prefix)
|
124
|
+
# "#{@config.default_subject_prefix}.#{subject}"
|
125
|
+
# else
|
126
|
+
# subject
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# def publish_sync(subject, message)
|
131
|
+
# @client.publish(subject, message.to_json)
|
132
|
+
# NatsWave.logger.debug("Published sync message to #{subject}")
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
# def publish_async(subject, message)
|
136
|
+
# if defined?(Concurrent)
|
137
|
+
# Concurrent::Future.execute do
|
138
|
+
# @client.publish(subject, message.to_json)
|
139
|
+
# NatsWave.logger.debug("Published async message to #{subject}")
|
140
|
+
# end
|
141
|
+
# else
|
142
|
+
# publish_sync(subject, message)
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
# end
|
43
147
|
|
44
|
-
# frozen_string_literal: true
|
45
148
|
|
46
149
|
module NatsWave
|
47
150
|
class Publisher
|
data/lib/nats_wave/railtie.rb
CHANGED
@@ -1,3 +1,123 @@
|
|
1
|
+
# # frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# require 'rails/railtie'
|
4
|
+
#
|
5
|
+
# module NatsWave
|
6
|
+
# class Railtie < Rails::Railtie
|
7
|
+
# config.nats_wave = ActiveSupport::OrderedOptions.new
|
8
|
+
#
|
9
|
+
# initializer "nats_wave.configure" do |app|
|
10
|
+
# # Load configuration from Rails config
|
11
|
+
# if app.config.respond_to?(:nats_wave)
|
12
|
+
# NatsWave.configure do |config|
|
13
|
+
# app.config.nats_wave.each do |key, value|
|
14
|
+
# config.send("#{key}=", value) if config.respond_to?("#{key}=")
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Load YAML configuration if it exists
|
20
|
+
# config_file = Rails.root.join('config', 'nats_wave.yml')
|
21
|
+
# if File.exist?(config_file)
|
22
|
+
# begin
|
23
|
+
# erb_content = ERB.new(File.read(config_file)).result
|
24
|
+
# yaml_data = YAML.safe_load(erb_content)
|
25
|
+
# yaml_config = yaml_data&.dig(Rails.env.to_s) || yaml_data&.dig(Rails.env) || {}
|
26
|
+
#
|
27
|
+
# NatsWave.configure do |config|
|
28
|
+
# # NATS Configuration
|
29
|
+
# config.nats_url = yaml_config.dig('nats', 'url') if yaml_config.dig('nats', 'url')
|
30
|
+
# config.connection_pool_size = yaml_config.dig('nats', 'connection_pool_size') if yaml_config.dig('nats', 'connection_pool_size')
|
31
|
+
# config.timeout = yaml_config.dig('nats', 'timeout') if yaml_config.dig('nats', 'timeout')
|
32
|
+
# config.reconnect_attempts = yaml_config.dig('nats', 'reconnect_attempts') if yaml_config.dig('nats', 'reconnect_attempts')
|
33
|
+
#
|
34
|
+
# # Service Configuration
|
35
|
+
# config.service_name = yaml_config.dig('publishing', 'default_subject_prefix') if yaml_config.dig('publishing', 'default_subject_prefix')
|
36
|
+
# config.version = Rails.application.class.parent_name.underscore if defined?(Rails.application.class.parent_name)
|
37
|
+
#
|
38
|
+
# # Publishing Configuration
|
39
|
+
# config.publishing_enabled = yaml_config.dig('publishing', 'enabled') unless yaml_config.dig('publishing', 'enabled').nil?
|
40
|
+
# config.default_subject_prefix = yaml_config.dig('publishing', 'default_subject_prefix') if yaml_config.dig('publishing', 'default_subject_prefix')
|
41
|
+
# config.batch_size = yaml_config.dig('publishing', 'batch_size') if yaml_config.dig('publishing', 'batch_size')
|
42
|
+
# config.async_publishing = yaml_config.dig('publishing', 'async') unless yaml_config.dig('publishing', 'async').nil?
|
43
|
+
#
|
44
|
+
# # Subscription Configuration
|
45
|
+
# config.subscription_enabled = yaml_config.dig('subscription', 'enabled') unless yaml_config.dig('subscription', 'enabled').nil?
|
46
|
+
# config.queue_group = yaml_config.dig('subscription', 'queue_group') if yaml_config.dig('subscription', 'queue_group')
|
47
|
+
#
|
48
|
+
# # Middleware Configuration
|
49
|
+
# config.middleware_authentication_enabled = yaml_config.dig('middleware', 'authentication', 'enabled') unless yaml_config.dig('middleware', 'authentication', 'enabled').nil?
|
50
|
+
# config.middleware_validation_enabled = yaml_config.dig('middleware', 'validation', 'enabled') unless yaml_config.dig('middleware', 'validation', 'enabled').nil?
|
51
|
+
# config.middleware_logging_enabled = yaml_config.dig('middleware', 'logging', 'enabled') unless yaml_config.dig('middleware', 'logging', 'enabled').nil?
|
52
|
+
# config.auth_secret_key = yaml_config.dig('middleware', 'authentication', 'secret_key') if yaml_config.dig('middleware', 'authentication', 'secret_key')
|
53
|
+
# config.schema_registry_url = yaml_config.dig('middleware', 'validation', 'schema_registry') if yaml_config.dig('middleware', 'validation', 'schema_registry')
|
54
|
+
# config.log_level = yaml_config.dig('middleware', 'logging', 'level') if yaml_config.dig('middleware', 'logging', 'level')
|
55
|
+
#
|
56
|
+
# # Error Handling Configuration
|
57
|
+
# config.max_retries = yaml_config.dig('error_handling', 'max_retries') if yaml_config.dig('error_handling', 'max_retries')
|
58
|
+
# config.retry_delay = yaml_config.dig('error_handling', 'retry_delay') if yaml_config.dig('error_handling', 'retry_delay')
|
59
|
+
# config.dead_letter_queue = yaml_config.dig('error_handling', 'dead_letter_queue') if yaml_config.dig('error_handling', 'dead_letter_queue')
|
60
|
+
# end
|
61
|
+
# rescue => e
|
62
|
+
# Rails.logger.warn "Failed to load NATS Wave YAML configuration: #{e.message}"
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # Environment-specific overrides
|
66
|
+
# if Rails.env.development?
|
67
|
+
# NatsWave.configure do |config|
|
68
|
+
# config.log_level = "debug"
|
69
|
+
# config.middleware_authentication_enabled = false
|
70
|
+
# end
|
71
|
+
# elsif Rails.env.test?
|
72
|
+
# NatsWave.configure do |config|
|
73
|
+
# config.publishing_enabled = false
|
74
|
+
# config.subscription_enabled = false
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# # initializer "nats_wave.active_record" do
|
81
|
+
# # ActiveSupport.on_load(:active_record) do
|
82
|
+
# # # Load the ActiveRecord extension (this replaces the old publishable)
|
83
|
+
# # require 'nats_wave/adapters/active_record'
|
84
|
+
# # include NatsWave::ActiveRecord
|
85
|
+
# # end
|
86
|
+
# # end
|
87
|
+
#
|
88
|
+
# rake_tasks do
|
89
|
+
# load "tasks/nats_wave.rake"
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# generators do
|
93
|
+
# require 'generators/nats_wave/install_generator'
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# # Initialize client on Rails boot
|
97
|
+
# initializer "nats_wave.initialize_client", after: :load_config_initializers do
|
98
|
+
# Rails.application.config.after_initialize do
|
99
|
+
# if NatsWave.configuration&.publishing_enabled || NatsWave.configuration&.subscription_enabled
|
100
|
+
# Thread.new do
|
101
|
+
# sleep 2 # Give Rails time to fully boot
|
102
|
+
# begin
|
103
|
+
# NatsWave.client
|
104
|
+
# NatsWave.logger.info "NatsWave client initialized"
|
105
|
+
#
|
106
|
+
# # Start subscriber if enabled
|
107
|
+
# if NatsWave.configuration&.subscription_enabled
|
108
|
+
# NatsWave.client.start_subscriber
|
109
|
+
# NatsWave.logger.info "NatsWave subscriber started"
|
110
|
+
# end
|
111
|
+
# rescue => e
|
112
|
+
# NatsWave.logger.error "Failed to initialize NatsWave client: #{e.message}"
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
|
1
121
|
# frozen_string_literal: true
|
2
122
|
|
3
123
|
require 'rails/railtie'
|
@@ -77,9 +197,16 @@ module NatsWave
|
|
77
197
|
end
|
78
198
|
end
|
79
199
|
|
200
|
+
# Load extensions before ActiveRecord loads models
|
201
|
+
initializer "nats_wave.load_extensions", before: :load_config_initializers do
|
202
|
+
# Load the extensions early so they're available when models are loaded
|
203
|
+
require 'nats_wave/active_record_extension'
|
204
|
+
require 'nats_wave/concerns/mappable'
|
205
|
+
end
|
206
|
+
|
80
207
|
initializer "nats_wave.active_record" do
|
81
208
|
ActiveSupport.on_load(:active_record) do
|
82
|
-
|
209
|
+
# The modules are already loaded, just include them
|
83
210
|
include NatsWave::ActiveRecordExtension
|
84
211
|
end
|
85
212
|
end
|
@@ -101,6 +228,12 @@ module NatsWave
|
|
101
228
|
begin
|
102
229
|
NatsWave.client
|
103
230
|
NatsWave.logger.info "NatsWave client initialized"
|
231
|
+
|
232
|
+
# Start subscriber if enabled
|
233
|
+
if NatsWave.configuration&.subscription_enabled
|
234
|
+
NatsWave.client.start_subscriber
|
235
|
+
NatsWave.logger.info "NatsWave subscriber started"
|
236
|
+
end
|
104
237
|
rescue => e
|
105
238
|
NatsWave.logger.error "Failed to initialize NatsWave client: #{e.message}"
|
106
239
|
end
|