dionysus-rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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