dionysus-rb 0.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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +61 -0
  3. data/.github/workflows/ci.yml +77 -0
  4. data/.gitignore +12 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +175 -0
  7. data/.rubocop_todo.yml +53 -0
  8. data/CHANGELOG.md +227 -0
  9. data/Gemfile +10 -0
  10. data/Gemfile.lock +258 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +1206 -0
  13. data/Rakefile +10 -0
  14. data/assets/logo.svg +51 -0
  15. data/bin/console +11 -0
  16. data/bin/karafka_health_check +14 -0
  17. data/bin/outbox_worker_health_check +12 -0
  18. data/bin/setup +8 -0
  19. data/dionysus-rb.gemspec +64 -0
  20. data/docker-compose.yml +44 -0
  21. data/lib/dionysus/checks/health_check.rb +50 -0
  22. data/lib/dionysus/checks.rb +7 -0
  23. data/lib/dionysus/consumer/batch_events_publisher.rb +33 -0
  24. data/lib/dionysus/consumer/config.rb +97 -0
  25. data/lib/dionysus/consumer/deserializer.rb +231 -0
  26. data/lib/dionysus/consumer/dionysus_event.rb +42 -0
  27. data/lib/dionysus/consumer/karafka_consumer_generator.rb +56 -0
  28. data/lib/dionysus/consumer/params_batch_processor.rb +65 -0
  29. data/lib/dionysus/consumer/params_batch_transformations/remove_duplicates_strategy.rb +54 -0
  30. data/lib/dionysus/consumer/params_batch_transformations.rb +4 -0
  31. data/lib/dionysus/consumer/persistor.rb +157 -0
  32. data/lib/dionysus/consumer/registry.rb +84 -0
  33. data/lib/dionysus/consumer/synced_data/assign_columns_from_synced_data.rb +27 -0
  34. data/lib/dionysus/consumer/synced_data/assign_columns_from_synced_data_job.rb +26 -0
  35. data/lib/dionysus/consumer/synced_data.rb +4 -0
  36. data/lib/dionysus/consumer/synchronizable_model.rb +93 -0
  37. data/lib/dionysus/consumer/workers_group.rb +18 -0
  38. data/lib/dionysus/consumer.rb +36 -0
  39. data/lib/dionysus/monitor.rb +48 -0
  40. data/lib/dionysus/producer/base_responder.rb +46 -0
  41. data/lib/dionysus/producer/config.rb +104 -0
  42. data/lib/dionysus/producer/deleted_record_serializer.rb +17 -0
  43. data/lib/dionysus/producer/genesis/performed.rb +11 -0
  44. data/lib/dionysus/producer/genesis/stream_job.rb +13 -0
  45. data/lib/dionysus/producer/genesis/streamer/base_job.rb +44 -0
  46. data/lib/dionysus/producer/genesis/streamer/standard_job.rb +43 -0
  47. data/lib/dionysus/producer/genesis/streamer.rb +40 -0
  48. data/lib/dionysus/producer/genesis.rb +62 -0
  49. data/lib/dionysus/producer/karafka_responder_generator.rb +133 -0
  50. data/lib/dionysus/producer/key.rb +14 -0
  51. data/lib/dionysus/producer/model_serializer.rb +105 -0
  52. data/lib/dionysus/producer/outbox/active_record_publishable.rb +74 -0
  53. data/lib/dionysus/producer/outbox/datadog_latency_reporter.rb +26 -0
  54. data/lib/dionysus/producer/outbox/datadog_latency_reporter_job.rb +11 -0
  55. data/lib/dionysus/producer/outbox/datadog_latency_reporter_scheduler.rb +47 -0
  56. data/lib/dionysus/producer/outbox/datadog_tracer.rb +32 -0
  57. data/lib/dionysus/producer/outbox/duplicates_filter.rb +26 -0
  58. data/lib/dionysus/producer/outbox/event_name.rb +26 -0
  59. data/lib/dionysus/producer/outbox/health_check.rb +48 -0
  60. data/lib/dionysus/producer/outbox/latency_tracker.rb +43 -0
  61. data/lib/dionysus/producer/outbox/model.rb +117 -0
  62. data/lib/dionysus/producer/outbox/producer.rb +26 -0
  63. data/lib/dionysus/producer/outbox/publishable.rb +106 -0
  64. data/lib/dionysus/producer/outbox/publisher.rb +131 -0
  65. data/lib/dionysus/producer/outbox/records_processor.rb +56 -0
  66. data/lib/dionysus/producer/outbox/runner.rb +120 -0
  67. data/lib/dionysus/producer/outbox/tombstone_publisher.rb +22 -0
  68. data/lib/dionysus/producer/outbox.rb +103 -0
  69. data/lib/dionysus/producer/partition_key.rb +42 -0
  70. data/lib/dionysus/producer/registry/validator.rb +32 -0
  71. data/lib/dionysus/producer/registry.rb +165 -0
  72. data/lib/dionysus/producer/serializer.rb +52 -0
  73. data/lib/dionysus/producer/suppressor.rb +18 -0
  74. data/lib/dionysus/producer.rb +121 -0
  75. data/lib/dionysus/railtie.rb +9 -0
  76. data/lib/dionysus/rb/version.rb +5 -0
  77. data/lib/dionysus/rb.rb +8 -0
  78. data/lib/dionysus/support/rspec/outbox_publishable.rb +78 -0
  79. data/lib/dionysus/topic_name.rb +15 -0
  80. data/lib/dionysus/utils/default_message_filter.rb +25 -0
  81. data/lib/dionysus/utils/exponential_backoff.rb +7 -0
  82. data/lib/dionysus/utils/karafka_datadog_listener.rb +20 -0
  83. data/lib/dionysus/utils/karafka_sentry_listener.rb +9 -0
  84. data/lib/dionysus/utils/null_error_handler.rb +6 -0
  85. data/lib/dionysus/utils/null_event_bus.rb +5 -0
  86. data/lib/dionysus/utils/null_hermes_event_producer.rb +5 -0
  87. data/lib/dionysus/utils/null_instrumenter.rb +7 -0
  88. data/lib/dionysus/utils/null_lock_client.rb +13 -0
  89. data/lib/dionysus/utils/null_model_factory.rb +5 -0
  90. data/lib/dionysus/utils/null_mutex_provider.rb +7 -0
  91. data/lib/dionysus/utils/null_retry_provider.rb +7 -0
  92. data/lib/dionysus/utils/null_tracer.rb +5 -0
  93. data/lib/dionysus/utils/null_transaction_provider.rb +15 -0
  94. data/lib/dionysus/utils/sidekiq_batched_job_distributor.rb +24 -0
  95. data/lib/dionysus/utils.rb +6 -0
  96. data/lib/dionysus/version.rb +7 -0
  97. data/lib/dionysus-rb.rb +3 -0
  98. data/lib/dionysus.rb +133 -0
  99. data/lib/tasks/dionysus.rake +18 -0
  100. data/log/development.log +0 -0
  101. data/sig/dionysus/rb.rbs +6 -0
  102. metadata +585 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Dionysus::Utils::NullTracer
4
+ def self.trace(_event_name, _topic); end
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Dionysus::Utils::NullTransactionProvider
4
+ def self.transaction
5
+ yield
6
+ end
7
+
8
+ def self.connection_pool
9
+ self
10
+ end
11
+
12
+ def self.with_connection
13
+ yield
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Dionysus::Utils::SidekiqBatchedJobDistributor
4
+ attr_reader :batch_size, :units_count, :time_range_in_seconds
5
+ private :batch_size, :units_count, :time_range_in_seconds
6
+
7
+ def initialize(batch_size:, units_count:, time_range_in_seconds:)
8
+ @batch_size = batch_size
9
+ @units_count = units_count
10
+ @time_range_in_seconds = time_range_in_seconds
11
+ end
12
+
13
+ def number_of_batches
14
+ (units_count.to_d / batch_size).ceil
15
+ end
16
+
17
+ def time_per_batch
18
+ (time_range_in_seconds.to_d / number_of_batches).floor
19
+ end
20
+
21
+ def enqueue_batch(job_class, queue, batch_number, *job_arguments)
22
+ job_class.set(queue: queue).perform_in(batch_number * time_per_batch, *job_arguments)
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dionysus::Utils
4
+ autoload :KarafkaDatadogListener, "dionysus/utils/karafka_datadog_listener"
5
+ autoload :KarafkaSentryListener, "dionysus/utils/karafka_sentry_listener"
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dionysus
4
+ module Version
5
+ end
6
+ VERSION = "0.1.0"
7
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dionysus"
data/lib/dionysus.rb ADDED
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "sidekiq"
5
+ require "sidekiq-cron"
6
+ require "karafka"
7
+ require "dry-monitor"
8
+ require "concurrent/array"
9
+ require "file-based-healthcheck"
10
+ require "sigurd"
11
+ require "securerandom"
12
+ require "zeitwerk"
13
+
14
+ module Dionysus
15
+ CONSUMER_GROUP_PREFIX = "dionysus_consumer_group_for"
16
+
17
+ def self.loader
18
+ @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
19
+ loader.ignore("#{__dir__}/dionysus-rb.rb")
20
+ end
21
+ end
22
+
23
+ def self.initialize_application!(environment:, seed_brokers:, client_id:, logger:, draw_routing: true, consumer_group_prefix: CONSUMER_GROUP_PREFIX)
24
+ ENV["KARAFKA_ENV"] = environment
25
+
26
+ karafka_app = Class.new(Karafka::App) do
27
+ setup do |config|
28
+ config.kafka = {
29
+ "bootstrap.servers": seed_brokers.join(","),
30
+ "client.id": client_id
31
+ }
32
+ config.client_id = client_id
33
+ config.logger = logger
34
+ yield config if block_given?
35
+ end
36
+ end
37
+
38
+ Object.const_set(:KarafkaApp, karafka_app)
39
+ self.karafka_application = karafka_app
40
+ evaluate_routing(consumer_group_prefix: consumer_group_prefix) if consumer_registry.present? && draw_routing
41
+ end
42
+
43
+ def self.karafka_application=(karafka_app)
44
+ @karafka_application = karafka_app
45
+ end
46
+
47
+ def self.karafka_application
48
+ @karafka_application
49
+ end
50
+
51
+ def self.health_check=(health_check)
52
+ @health_check = health_check
53
+
54
+ Karafka.monitor.subscribe("app.initialized") do |_event|
55
+ health_check = Dionysus.health_check
56
+
57
+ health_check&.app_initialized!
58
+ end
59
+
60
+ Karafka.monitor.subscribe("statistics.emitted") do |_event|
61
+ health_check = Dionysus.health_check
62
+
63
+ health_check&.register_heartbeat
64
+ end
65
+
66
+ Karafka.monitor.subscribe("consumer.consumed") do |_event|
67
+ health_check = Dionysus.health_check
68
+
69
+ health_check&.register_heartbeat
70
+ end
71
+
72
+ Karafka.monitor.subscribe("app.stopped") do |_event|
73
+ health_check = Dionysus.health_check
74
+
75
+ health_check&.app_stopped!
76
+ end
77
+ end
78
+
79
+ def self.health_check
80
+ @health_check
81
+ end
82
+
83
+ def self.outbox_worker_health_check
84
+ @outbox_worker_health_check ||= Dionysus::Producer::Outbox::HealthCheck.new
85
+ end
86
+
87
+ def self.enable_outbox_worker_healthcheck
88
+ monitor.subscribe("outbox_producer.started") { outbox_worker_health_check.register_heartbeat }
89
+ monitor.subscribe("outbox_producer.stopped") { outbox_worker_health_check.worker_stopped }
90
+ monitor.subscribe("outbox_producer.heartbeat") { outbox_worker_health_check.register_heartbeat }
91
+ end
92
+
93
+ def self.logger
94
+ if karafka_application
95
+ karafka_application.config.logger
96
+ else
97
+ Logger.new($stdout)
98
+ end
99
+ end
100
+
101
+ def self.consumer_registry
102
+ @consumer_registry
103
+ end
104
+
105
+ def self.inject_routing!(registry)
106
+ @consumer_registry = registry
107
+ end
108
+
109
+ def self.monitor
110
+ @monitor ||= Dionysus::Monitor.new
111
+ end
112
+
113
+ def self.evaluate_routing(consumer_group_prefix:)
114
+ consumer_group_name = "#{consumer_group_prefix}_#{karafka_application.config.client_id}"
115
+ karafka_application.instance_exec(consumer_registry) do |registry|
116
+ consumer_groups.draw do
117
+ consumer_group consumer_group_name do
118
+ registry.registrations.each do |_, registration|
119
+ registration.topics.each do |topic|
120
+ topic topic.to_s do
121
+ consumer topic.consumer
122
+ instance_eval(&topic.extensions_block) if topic.extensions_block
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ private_class_method :evaluate_routing
131
+ end
132
+
133
+ Dionysus.loader.setup
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :dionysus do
4
+ desc "Starts the Dionysus RB Outbox Producer"
5
+ task producer: :environment do
6
+ $stdout.sync = true
7
+ Rails.logger.info("Running dionysus:producer rake task.")
8
+ threads_number = ENV.fetch("DIONYSUS_RB_THREADS_NUMBER", 1).to_i
9
+ Dionysus::Producer.start_outbox_worker(threads_number: threads_number)
10
+ end
11
+
12
+ desc "Validates config for :attributes for observables"
13
+ task validate_columns: :environment do
14
+ $stdout.sync = true
15
+ Rails.logger.info("Running dionysus:validate_columns rake task.")
16
+ Dionysus::Producer::Registry::Validator.new.validate_columns
17
+ end
18
+ end
File without changes
@@ -0,0 +1,6 @@
1
+ module Dionysus
2
+ module Rb
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end