nulogy_message_bus_consumer 0.1.0 → 0.3.3
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/Rakefile +12 -2
- data/config/credentials/message-bus-us-east-1.key +1 -0
- data/config/credentials/message-bus-us-east-1.yml.enc +1 -0
- data/lib/nulogy_message_bus_consumer.rb +37 -27
- data/lib/nulogy_message_bus_consumer/config.rb +4 -0
- data/lib/nulogy_message_bus_consumer/handlers/log_unprocessed_messages.rb +18 -0
- data/lib/nulogy_message_bus_consumer/kafka_utils.rb +62 -0
- data/lib/nulogy_message_bus_consumer/message.rb +42 -13
- data/lib/nulogy_message_bus_consumer/null_logger.rb +9 -0
- data/lib/nulogy_message_bus_consumer/pipeline.rb +44 -0
- data/lib/nulogy_message_bus_consumer/steps/commit_on_success.rb +37 -0
- data/lib/nulogy_message_bus_consumer/steps/connect_to_message_bus.rb +12 -2
- data/lib/nulogy_message_bus_consumer/steps/log_messages.rb +19 -1
- data/lib/nulogy_message_bus_consumer/steps/monitor_replication_lag.rb +1 -1
- data/lib/nulogy_message_bus_consumer/steps/seek_beginning_of_topic.rb +10 -0
- data/lib/nulogy_message_bus_consumer/steps/{stream_individual_messages.rb → stream_messages.rb} +4 -12
- data/lib/nulogy_message_bus_consumer/steps/stream_messages_until_none_are_left.rb +26 -0
- data/lib/nulogy_message_bus_consumer/version.rb +1 -1
- data/lib/tasks/engine/message_bus_consumer.rake +15 -0
- metadata +133 -12
- data/lib/nulogy_message_bus_consumer/steps/result_validation.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c47fb7fab95cd7d64e9654c0ce43fec64f77f8718331f8028afc6c18ab993f81
|
4
|
+
data.tar.gz: 3c656a7dea18d149f04ccfbc3dc4db04e9aa7b3cd6183be7b0a1a45337c3a975
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04320f62a752bcfcc4bad93bd0611ba0f8945018dfb1680016ab739b70ba66cb7ebcf5062fb12f316745e59386714613c44f17f7feea4006a56b8625da119e23
|
7
|
+
data.tar.gz: c480ced5fb50feeb8d655cb8c0b45c19a12bd733088db6fe4bf78ce33d08a7051594eb86a011227d2cc1363ea058e1544435c318824a6c9549be2e32fb6f65ef
|
data/Rakefile
CHANGED
@@ -16,7 +16,17 @@ end
|
|
16
16
|
|
17
17
|
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
18
18
|
load "rails/tasks/engine.rake"
|
19
|
-
|
20
19
|
load "rails/tasks/statistics.rake"
|
21
20
|
|
22
|
-
require "
|
21
|
+
require "rspec/core"
|
22
|
+
require "rspec/core/rake_task"
|
23
|
+
RSpec::Core::RakeTask.new(:spec)
|
24
|
+
require "rubocop/rake_task"
|
25
|
+
RuboCop::RakeTask.new
|
26
|
+
task default: %i[spec rubocop]
|
27
|
+
|
28
|
+
require "rake/release"
|
29
|
+
|
30
|
+
Rake::Release::Task.load_all do |spec|
|
31
|
+
spec.version_tag = "nulogy_message_bus_consumer-v#{spec.version}"
|
32
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
dfa19863b2709390893da4c2fb85579a
|
@@ -0,0 +1 @@
|
|
1
|
+
/WXdqUYePaHqAq3P0iTEsrLiMfRKzp2qYYh7K+q6LUNgi4eHNv0+SoLdI1bUNb9UaGvDPGNT3fCwAdkurk5Iud16ok3b4wD6yZ7UkfqbXqZaKH/dciQ5s63p9Hiuq1rbfcqoZ3KR1SXYAwvy8vNqbdwbAzz2N66B1wE5fNibZZlrWzXJjLReiTcNyxNbCPz6vwEwFF52RntuYlIJo4Nkm8vEk3No+HWrOkM8xptr5qApbd+RowLCLZ4/kcAMDB/XPiGobf0AOFv1NUR/9ChEy20usa8Fqd6HtEn4A25HnAC0uaN0K8ZRXjxhMpnXtfMBItn7yxyJ1ubjRZK5a1xVBRU7L/CVV9ZuIsqAHL1++gH5FBrEe83ZIUhN7AzngMDlOPKGiCLiZLrm18I1AEQrD7tJLyXos15AeAzj--cER8cN0iMLwu8Le+--FL7dhMTgr6xL6SkMnYKmeg==
|
@@ -1,52 +1,62 @@
|
|
1
|
+
require "rdkafka"
|
2
|
+
|
1
3
|
require "nulogy_message_bus_consumer/engine"
|
4
|
+
|
2
5
|
require "nulogy_message_bus_consumer/config"
|
6
|
+
require "nulogy_message_bus_consumer/handlers/log_unprocessed_messages"
|
7
|
+
require "nulogy_message_bus_consumer/kafka_utils"
|
3
8
|
require "nulogy_message_bus_consumer/message"
|
9
|
+
require "nulogy_message_bus_consumer/null_logger"
|
10
|
+
require "nulogy_message_bus_consumer/pipeline"
|
4
11
|
require "nulogy_message_bus_consumer/processed_message"
|
12
|
+
require "nulogy_message_bus_consumer/steps/commit_on_success"
|
5
13
|
require "nulogy_message_bus_consumer/steps/connect_to_message_bus"
|
6
14
|
require "nulogy_message_bus_consumer/steps/deduplicate_messages"
|
7
15
|
require "nulogy_message_bus_consumer/steps/log_messages"
|
8
16
|
require "nulogy_message_bus_consumer/steps/monitor_replication_lag"
|
9
|
-
require "nulogy_message_bus_consumer/steps/
|
10
|
-
require "nulogy_message_bus_consumer/steps/
|
17
|
+
require "nulogy_message_bus_consumer/steps/seek_beginning_of_topic"
|
18
|
+
require "nulogy_message_bus_consumer/steps/stream_messages"
|
19
|
+
require "nulogy_message_bus_consumer/steps/stream_messages_until_none_are_left"
|
11
20
|
|
12
21
|
module NulogyMessageBusConsumer
|
13
22
|
module_function
|
14
23
|
|
24
|
+
mattr_accessor :config
|
25
|
+
mattr_accessor :logger
|
26
|
+
|
27
|
+
def configure(options = {})
|
28
|
+
self.config ||= Config.new
|
29
|
+
config.update(options) if options.present?
|
30
|
+
yield(config) if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
@logger ||= NullLogger.new
|
35
|
+
end
|
36
|
+
|
15
37
|
def invoke_pipeline(*steps)
|
16
|
-
|
38
|
+
Pipeline.new(steps).invoke
|
17
39
|
end
|
18
40
|
|
19
|
-
def
|
20
|
-
[
|
41
|
+
def recommended_consumer_pipeline(config: self.config, logger: self.logger)
|
42
|
+
Pipeline.new([
|
21
43
|
# The first three are really system processing steps
|
22
44
|
Steps::ConnectToMessageBus.new(config, logger),
|
23
45
|
Steps::MonitorReplicationLag.new(logger),
|
24
|
-
Steps::
|
46
|
+
Steps::StreamMessages.new(logger),
|
25
47
|
# Message processing steps start here.
|
26
48
|
Steps::LogMessages.new(logger),
|
49
|
+
Steps::CommitOnSuccess.new,
|
27
50
|
Steps::DeduplicateMessages.new(logger),
|
28
|
-
|
29
|
-
]
|
51
|
+
])
|
30
52
|
end
|
31
53
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def compose_with_merged_args(context, func)
|
44
|
-
lambda do |**inner_args|
|
45
|
-
existing = context.keys & inner_args.keys
|
46
|
-
|
47
|
-
raise "Cannot override existing key(s): #{existing.join(', ')}" if existing.any?
|
48
|
-
|
49
|
-
func.call(**context.merge(inner_args))
|
50
|
-
end
|
54
|
+
def consumer_audit_pipeline(config: self.config, logger: self.logger)
|
55
|
+
Pipeline.new([
|
56
|
+
Steps::ConnectToMessageBus.new(config, logger),
|
57
|
+
Steps::SeekBeginningOfTopic.new,
|
58
|
+
Steps::StreamMessagesUntilNoneAreLeft.new(logger),
|
59
|
+
Handlers::LogUnprocessedMessages.new(logger),
|
60
|
+
])
|
51
61
|
end
|
52
62
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module NulogyMessageBusConsumer
|
2
|
+
module Handlers
|
3
|
+
class LogUnprocessedMessages
|
4
|
+
def initialize(logger)
|
5
|
+
@logger = logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(message:, **_)
|
9
|
+
return if ProcessedMessage.exists?(id: message.id)
|
10
|
+
|
11
|
+
@logger.warn(JSON.dump(
|
12
|
+
event: "unprocessed_message",
|
13
|
+
kafka_message: message.to_h
|
14
|
+
))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module NulogyMessageBusConsumer
|
2
|
+
module KafkaUtils
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def wait_for_assignment(consumer)
|
6
|
+
wait_for { !consumer.assignment.empty? }
|
7
|
+
end
|
8
|
+
|
9
|
+
def wait_for_unassignment(consumer)
|
10
|
+
wait_for { consumer.assignment.empty? }
|
11
|
+
end
|
12
|
+
|
13
|
+
def wait_for(attempts: 100, interval: 0.1)
|
14
|
+
attempts.times do
|
15
|
+
break if yield
|
16
|
+
|
17
|
+
sleep interval
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def every_message_until_none_are_left(consumer)
|
22
|
+
Enumerator.new do |yielder|
|
23
|
+
while (message = consumer.poll(250))
|
24
|
+
yielder.yield(message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def seek_beginning(consumer)
|
30
|
+
wait_for_assignment(consumer)
|
31
|
+
assigned_partitions(consumer).each do |topic_name, partition|
|
32
|
+
message = Message.new(
|
33
|
+
topic: topic_name,
|
34
|
+
partition: partition,
|
35
|
+
offset: BEGINNING_OFFSET
|
36
|
+
)
|
37
|
+
consumer.seek(message)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def seek_ending(consumer)
|
42
|
+
wait_for_assignment(consumer)
|
43
|
+
assigned_partitions(consumer).each do |topic_name, partition|
|
44
|
+
message = Message.new(
|
45
|
+
topic: topic_name,
|
46
|
+
partition: partition,
|
47
|
+
offset: END_OFFSET
|
48
|
+
)
|
49
|
+
consumer.seek(message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def assigned_partitions(consumer)
|
54
|
+
consumer.assignment.to_h
|
55
|
+
.flat_map { |topic_name, partitions| [topic_name].product(partitions) }
|
56
|
+
.map { |topic_name, partition| [topic_name, partition.partition] }
|
57
|
+
end
|
58
|
+
|
59
|
+
BEGINNING_OFFSET = 0
|
60
|
+
END_OFFSET = -1
|
61
|
+
end
|
62
|
+
end
|
@@ -1,21 +1,50 @@
|
|
1
1
|
module NulogyMessageBusConsumer
|
2
|
-
Message
|
3
|
-
:
|
4
|
-
:
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
|
9
|
-
|
2
|
+
class Message
|
3
|
+
attr_reader :event_data
|
4
|
+
attr_reader :event_data_unparsed
|
5
|
+
attr_reader :id
|
6
|
+
attr_reader :key
|
7
|
+
attr_reader :offset
|
8
|
+
attr_reader :partition
|
9
|
+
attr_reader :subscription_id
|
10
|
+
attr_reader :company_uuid
|
11
|
+
attr_reader :timestamp
|
12
|
+
attr_reader :topic
|
13
|
+
attr_reader :created_at
|
14
|
+
|
15
|
+
def initialize(attrs = {})
|
16
|
+
attrs.each { |key, value| instance_variable_set("@#{key}", value) }
|
17
|
+
end
|
18
|
+
|
10
19
|
def self.from_kafka(kafka_message)
|
11
|
-
envelope_data = JSON.parse(kafka_message.payload)
|
20
|
+
envelope_data = JSON.parse(kafka_message.payload, symbolize_names: true)
|
21
|
+
event_data =
|
22
|
+
begin
|
23
|
+
JSON.parse(envelope_data[:event_json], symbolize_names: true)
|
24
|
+
rescue StandardError
|
25
|
+
{}
|
26
|
+
end
|
12
27
|
|
13
28
|
new(
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
29
|
+
event_data: event_data,
|
30
|
+
event_data_unparsed: envelope_data[:event_json],
|
31
|
+
id: envelope_data[:id],
|
32
|
+
key: kafka_message.key,
|
33
|
+
offset: kafka_message.offset,
|
34
|
+
partition: kafka_message.partition,
|
35
|
+
subscription_id: envelope_data[:subscription_id] || envelope_data[:public_subscription_id],
|
36
|
+
company_uuid: envelope_data[:company_uuid] || envelope_data[:tenant_id],
|
37
|
+
timestamp: kafka_message.timestamp,
|
38
|
+
topic: kafka_message.topic,
|
39
|
+
created_at: envelope_data[:created_at]
|
18
40
|
)
|
19
41
|
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
instance_variable_names.each_with_object({}) do |attribute_name, hash|
|
45
|
+
attribute_name.sub!("@", "")
|
46
|
+
hash[attribute_name] = public_send(attribute_name)
|
47
|
+
end
|
48
|
+
end
|
20
49
|
end
|
21
50
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module NulogyMessageBusConsumer
|
2
|
+
class Pipeline
|
3
|
+
def initialize(steps)
|
4
|
+
@steps = steps
|
5
|
+
end
|
6
|
+
|
7
|
+
def invoke
|
8
|
+
convert_steps_into_lambda.call
|
9
|
+
end
|
10
|
+
|
11
|
+
def insert(step, after:)
|
12
|
+
index = @steps.find_index { |s| s.is_a?(after) }
|
13
|
+
@steps.insert(index + 1, step)
|
14
|
+
end
|
15
|
+
|
16
|
+
def append(step)
|
17
|
+
@steps << step
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def convert_steps_into_lambda
|
23
|
+
last_step = ->(**_) { raise "Handlers are the end of the line. Do not use yield." }
|
24
|
+
|
25
|
+
@steps.reverse.reduce(last_step) do |composed_steps, previous_step|
|
26
|
+
lambda do |**args|
|
27
|
+
invoke_next = compose_with_merged_args(args, composed_steps)
|
28
|
+
previous_step.call(**args, &invoke_next)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def compose_with_merged_args(existing_args, func)
|
34
|
+
lambda do |**yielded_args|
|
35
|
+
args_to_be_overridden = existing_args.keys & yielded_args.keys
|
36
|
+
if args_to_be_overridden.any?
|
37
|
+
raise "Cannot override existing argument(s): #{args_to_be_overridden.join(', ')}"
|
38
|
+
end
|
39
|
+
|
40
|
+
func.call(**existing_args.merge(yielded_args))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module NulogyMessageBusConsumer
|
2
|
+
module Steps
|
3
|
+
class CommitOnSuccess
|
4
|
+
def call(kafka_consumer:, message:, **_)
|
5
|
+
result = yield
|
6
|
+
|
7
|
+
raise_if_invalid(result)
|
8
|
+
|
9
|
+
if result == :success
|
10
|
+
kafka_consumer.store_offset(message)
|
11
|
+
kafka_consumer.commit
|
12
|
+
else
|
13
|
+
reconnect_to_reprocess_same_message(kafka_consumer)
|
14
|
+
end
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def reconnect_to_reprocess_same_message(kafka_consumer)
|
22
|
+
subscriptions = kafka_consumer.subscription
|
23
|
+
kafka_consumer.unsubscribe
|
24
|
+
kafka_consumer.subscribe(*subscriptions.to_h.keys)
|
25
|
+
end
|
26
|
+
|
27
|
+
def raise_if_invalid(result)
|
28
|
+
return if %i[success failure].include?(result)
|
29
|
+
|
30
|
+
raise(
|
31
|
+
StandardError,
|
32
|
+
"'#{result}' is not a valid processing outcome. Must be :success or :failure"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,28 +1,38 @@
|
|
1
1
|
module NulogyMessageBusConsumer
|
2
2
|
module Steps
|
3
3
|
class ConnectToMessageBus
|
4
|
-
def initialize(config, logger)
|
4
|
+
def initialize(config, logger, kafka_consumer: nil)
|
5
5
|
@config = config
|
6
6
|
@logger = logger
|
7
|
+
@kafka_consumer = kafka_consumer
|
7
8
|
end
|
8
9
|
|
9
10
|
def call(**_)
|
10
11
|
@logger.info("Connecting to the MessageBus")
|
11
|
-
consumer =
|
12
|
+
consumer = build_consumer
|
12
13
|
@logger.info("Using consumer group id: #{@config.consumer_group_id}")
|
13
14
|
|
15
|
+
consumer.subscribe(@config.topic_name)
|
16
|
+
@logger.info("Listening for kafka messages on topic #{@config.topic_name}")
|
17
|
+
|
14
18
|
trap("TERM") { consumer.close }
|
15
19
|
|
20
|
+
KafkaUtils.wait_for_assignment(consumer)
|
16
21
|
yield(kafka_consumer: consumer)
|
17
22
|
end
|
18
23
|
|
19
24
|
private
|
20
25
|
|
26
|
+
def build_consumer
|
27
|
+
@kafka_consumer || Rdkafka::Config.new(consumer_config).consumer
|
28
|
+
end
|
29
|
+
|
21
30
|
def consumer_config
|
22
31
|
{
|
23
32
|
"bootstrap.servers": @config.bootstrap_servers,
|
24
33
|
"enable.auto.commit": false,
|
25
34
|
"group.id": @config.consumer_group_id,
|
35
|
+
"enable.auto.offset.store": false,
|
26
36
|
}
|
27
37
|
end
|
28
38
|
end
|
@@ -1,8 +1,16 @@
|
|
1
1
|
module NulogyMessageBusConsumer
|
2
2
|
module Steps
|
3
|
+
class Clock
|
4
|
+
# milliseconds since epoch
|
5
|
+
def now
|
6
|
+
Time.zone.now.to_datetime.strftime("%Q").to_i
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
class LogMessages
|
4
|
-
def initialize(logger)
|
11
|
+
def initialize(logger, clock: Clock.new)
|
5
12
|
@logger = logger
|
13
|
+
@clock = clock
|
6
14
|
end
|
7
15
|
|
8
16
|
def call(message:, **_)
|
@@ -14,15 +22,25 @@ module NulogyMessageBusConsumer
|
|
14
22
|
|
15
23
|
result = yield
|
16
24
|
|
25
|
+
millis = diff_millis(message.created_at, @clock.now)
|
17
26
|
@logger.info(JSON.dump({
|
18
27
|
event: "message_processed",
|
19
28
|
kafka_message_id: message.id,
|
20
29
|
message: "Processed #{message.id}",
|
21
30
|
result: result,
|
31
|
+
time_to_processed: millis,
|
22
32
|
}))
|
23
33
|
|
24
34
|
result
|
25
35
|
end
|
36
|
+
|
37
|
+
# Debezium appears to be giving us nanos since epoch
|
38
|
+
# https://github.com/debezium/debezium/blob/5a115e902cdc1dc399ec02758dd1039a33e99bc2/debezium-core/src/main/java/io/debezium/jdbc/JdbcValueConverters.java#L237
|
39
|
+
def diff_millis(oldest_nanos, newest_millis)
|
40
|
+
old_millis = oldest_nanos / 1000
|
41
|
+
|
42
|
+
newest_millis - old_millis
|
43
|
+
end
|
26
44
|
end
|
27
45
|
end
|
28
46
|
end
|
@@ -15,7 +15,7 @@ module NulogyMessageBusConsumer
|
|
15
15
|
# Delayed start. If we attempt to read consumer#committed immediately, it may fail.
|
16
16
|
# We suspect this is because the consumer#committed is called before the consumer
|
17
17
|
# has finished connecting. There appears to be a race condition.
|
18
|
-
|
18
|
+
KafkaUtils.wait_for_assignment(kafka_consumer)
|
19
19
|
|
20
20
|
loop do
|
21
21
|
lag_per_topic = kafka_consumer.lag(kafka_consumer.committed)
|
data/lib/nulogy_message_bus_consumer/steps/{stream_individual_messages.rb → stream_messages.rb}
RENAMED
@@ -1,22 +1,16 @@
|
|
1
1
|
module NulogyMessageBusConsumer
|
2
2
|
module Steps
|
3
|
-
class
|
4
|
-
def initialize(
|
5
|
-
@config = config
|
3
|
+
class StreamMessages
|
4
|
+
def initialize(logger)
|
6
5
|
@logger = logger
|
7
6
|
end
|
8
7
|
|
9
8
|
def call(kafka_consumer:, **_)
|
10
|
-
kafka_consumer.subscribe(@config.topic_name)
|
11
|
-
@logger.info "Listening for kafka messages on topic #{@config.topic_name}"
|
12
|
-
|
13
9
|
kafka_consumer.each do |kafka_message|
|
14
|
-
|
15
|
-
message:
|
10
|
+
yield(
|
11
|
+
message: Message.from_kafka(kafka_message),
|
16
12
|
kafka_message: kafka_message
|
17
13
|
)
|
18
|
-
|
19
|
-
kafka_consumer.commit if result == :success
|
20
14
|
end
|
21
15
|
rescue StandardError => e
|
22
16
|
@logger.error(JSON.dump({
|
@@ -25,8 +19,6 @@ module NulogyMessageBusConsumer
|
|
25
19
|
message: e.message,
|
26
20
|
}))
|
27
21
|
|
28
|
-
kafka_consumer.unsubscribe
|
29
|
-
|
30
22
|
raise
|
31
23
|
end
|
32
24
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NulogyMessageBusConsumer
|
2
|
+
module Steps
|
3
|
+
class StreamMessagesUntilNoneAreLeft
|
4
|
+
def initialize(logger)
|
5
|
+
@logger = logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(kafka_consumer:, **_)
|
9
|
+
KafkaUtils.every_message_until_none_are_left(kafka_consumer).each do |kafka_message|
|
10
|
+
yield(
|
11
|
+
message: Message.from_kafka(kafka_message),
|
12
|
+
kafka_message: kafka_message
|
13
|
+
)
|
14
|
+
end
|
15
|
+
rescue StandardError => e
|
16
|
+
@logger.error(JSON.dump({
|
17
|
+
event: "message_processing_errored",
|
18
|
+
class: e.class,
|
19
|
+
message: e.message,
|
20
|
+
}))
|
21
|
+
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :message_bus_consumer do
|
2
|
+
desc "Verifies that the messages in the message bus have been processed"
|
3
|
+
task audit: :environment do
|
4
|
+
logger = Rails.logger
|
5
|
+
config = NulogyMessageBusConsumer::Config.new(
|
6
|
+
consumer_group_id: ENV.fetch("MB_AUDIT_GROUP"),
|
7
|
+
bootstrap_servers: ENV.fetch("MB_BOOTSTRAP_SERVERS"),
|
8
|
+
topic_name: ENV.fetch("MB_CONSUMER_TOPIC")
|
9
|
+
)
|
10
|
+
|
11
|
+
NulogyMessageBusConsumer
|
12
|
+
.consumer_audit_pipeline(config: config, logger: logger)
|
13
|
+
.invoke
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nulogy_message_bus_consumer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nulogy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -38,20 +38,118 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdkafka
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler-audit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.7.0.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.7.0.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dotenv
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.7.6
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.7.6
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.2.3
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.2.3
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
41
125
|
- !ruby/object:Gem::Dependency
|
42
126
|
name: rails
|
43
127
|
requirement: !ruby/object:Gem::Requirement
|
44
128
|
requirements:
|
45
129
|
- - '='
|
46
130
|
- !ruby/object:Gem::Version
|
47
|
-
version: 6.0.3
|
131
|
+
version: 6.0.3.1
|
48
132
|
type: :development
|
49
133
|
prerelease: false
|
50
134
|
version_requirements: !ruby/object:Gem::Requirement
|
51
135
|
requirements:
|
52
136
|
- - '='
|
53
137
|
- !ruby/object:Gem::Version
|
54
|
-
version: 6.0.3
|
138
|
+
version: 6.0.3.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rake-release
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.2.1
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.2.1
|
55
153
|
- !ruby/object:Gem::Dependency
|
56
154
|
name: rspec
|
57
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +178,20 @@ dependencies:
|
|
80
178
|
- - '='
|
81
179
|
- !ruby/object:Gem::Version
|
82
180
|
version: 2.2.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rspec-rails
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - '='
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 4.0.1
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 4.0.1
|
83
195
|
- !ruby/object:Gem::Dependency
|
84
196
|
name: rubocop
|
85
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,33 +207,33 @@ dependencies:
|
|
95
207
|
- !ruby/object:Gem::Version
|
96
208
|
version: 0.81.0
|
97
209
|
- !ruby/object:Gem::Dependency
|
98
|
-
name: rubocop-
|
210
|
+
name: rubocop-rails
|
99
211
|
requirement: !ruby/object:Gem::Requirement
|
100
212
|
requirements:
|
101
213
|
- - '='
|
102
214
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
215
|
+
version: 2.5.2
|
104
216
|
type: :development
|
105
217
|
prerelease: false
|
106
218
|
version_requirements: !ruby/object:Gem::Requirement
|
107
219
|
requirements:
|
108
220
|
- - '='
|
109
221
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
222
|
+
version: 2.5.2
|
111
223
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
224
|
+
name: rubocop-rspec
|
113
225
|
requirement: !ruby/object:Gem::Requirement
|
114
226
|
requirements:
|
115
227
|
- - '='
|
116
228
|
- !ruby/object:Gem::Version
|
117
|
-
version: 1.
|
229
|
+
version: 1.38.1
|
118
230
|
type: :development
|
119
231
|
prerelease: false
|
120
232
|
version_requirements: !ruby/object:Gem::Requirement
|
121
233
|
requirements:
|
122
234
|
- - '='
|
123
235
|
- !ruby/object:Gem::Version
|
124
|
-
version: 1.
|
236
|
+
version: 1.38.1
|
125
237
|
description:
|
126
238
|
email:
|
127
239
|
- tass@nulogy.com
|
@@ -130,20 +242,29 @@ extensions: []
|
|
130
242
|
extra_rdoc_files: []
|
131
243
|
files:
|
132
244
|
- Rakefile
|
245
|
+
- config/credentials/message-bus-us-east-1.key
|
246
|
+
- config/credentials/message-bus-us-east-1.yml.enc
|
133
247
|
- config/routes.rb
|
134
248
|
- db/migrate/20200509095105_create_message_bus_processed_messages.rb
|
135
249
|
- lib/nulogy_message_bus_consumer.rb
|
136
250
|
- lib/nulogy_message_bus_consumer/config.rb
|
137
251
|
- lib/nulogy_message_bus_consumer/engine.rb
|
252
|
+
- lib/nulogy_message_bus_consumer/handlers/log_unprocessed_messages.rb
|
253
|
+
- lib/nulogy_message_bus_consumer/kafka_utils.rb
|
138
254
|
- lib/nulogy_message_bus_consumer/message.rb
|
255
|
+
- lib/nulogy_message_bus_consumer/null_logger.rb
|
256
|
+
- lib/nulogy_message_bus_consumer/pipeline.rb
|
139
257
|
- lib/nulogy_message_bus_consumer/processed_message.rb
|
258
|
+
- lib/nulogy_message_bus_consumer/steps/commit_on_success.rb
|
140
259
|
- lib/nulogy_message_bus_consumer/steps/connect_to_message_bus.rb
|
141
260
|
- lib/nulogy_message_bus_consumer/steps/deduplicate_messages.rb
|
142
261
|
- lib/nulogy_message_bus_consumer/steps/log_messages.rb
|
143
262
|
- lib/nulogy_message_bus_consumer/steps/monitor_replication_lag.rb
|
144
|
-
- lib/nulogy_message_bus_consumer/steps/
|
145
|
-
- lib/nulogy_message_bus_consumer/steps/
|
263
|
+
- lib/nulogy_message_bus_consumer/steps/seek_beginning_of_topic.rb
|
264
|
+
- lib/nulogy_message_bus_consumer/steps/stream_messages.rb
|
265
|
+
- lib/nulogy_message_bus_consumer/steps/stream_messages_until_none_are_left.rb
|
146
266
|
- lib/nulogy_message_bus_consumer/version.rb
|
267
|
+
- lib/tasks/engine/message_bus_consumer.rake
|
147
268
|
homepage: https://github.com/nulogy/message-bus/tree/master/gems/nulogy_message_bus_consumer
|
148
269
|
licenses: []
|
149
270
|
metadata:
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module NulogyMessageBusConsumer
|
2
|
-
module Steps
|
3
|
-
class ResultValidation
|
4
|
-
def call(**_)
|
5
|
-
result = yield
|
6
|
-
|
7
|
-
return result if %i[success failure].include?(result)
|
8
|
-
|
9
|
-
raise(
|
10
|
-
StandardError,
|
11
|
-
"'#{result}' is not a valid processing outcome. Must be :success or :failure"
|
12
|
-
)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|