karafka 2.4.8 → 2.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +0 -1
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +8 -5
  7. data/Gemfile.lock +24 -15
  8. data/bin/integrations +5 -0
  9. data/certs/cert.pem +26 -0
  10. data/config/locales/errors.yml +5 -0
  11. data/config/locales/pro_errors.yml +34 -0
  12. data/karafka.gemspec +1 -1
  13. data/lib/karafka/admin.rb +42 -0
  14. data/lib/karafka/base_consumer.rb +23 -0
  15. data/lib/karafka/contracts/config.rb +2 -0
  16. data/lib/karafka/contracts/consumer_group.rb +17 -0
  17. data/lib/karafka/errors.rb +3 -2
  18. data/lib/karafka/instrumentation/logger_listener.rb +3 -0
  19. data/lib/karafka/instrumentation/notifications.rb +3 -0
  20. data/lib/karafka/instrumentation/vendors/appsignal/client.rb +32 -11
  21. data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +1 -1
  22. data/lib/karafka/messages/message.rb +6 -0
  23. data/lib/karafka/pro/loader.rb +3 -1
  24. data/lib/karafka/pro/processing/strategies/dlq/default.rb +16 -1
  25. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +5 -1
  26. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +17 -1
  27. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +17 -1
  28. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +22 -6
  29. data/lib/karafka/pro/recurring_tasks/consumer.rb +105 -0
  30. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +53 -0
  31. data/lib/karafka/pro/recurring_tasks/contracts/task.rb +41 -0
  32. data/lib/karafka/pro/recurring_tasks/deserializer.rb +35 -0
  33. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +87 -0
  34. data/lib/karafka/pro/recurring_tasks/errors.rb +34 -0
  35. data/lib/karafka/pro/recurring_tasks/executor.rb +152 -0
  36. data/lib/karafka/pro/recurring_tasks/listener.rb +38 -0
  37. data/lib/karafka/pro/recurring_tasks/matcher.rb +38 -0
  38. data/lib/karafka/pro/recurring_tasks/schedule.rb +63 -0
  39. data/lib/karafka/pro/recurring_tasks/serializer.rb +113 -0
  40. data/lib/karafka/pro/recurring_tasks/setup/config.rb +52 -0
  41. data/lib/karafka/pro/recurring_tasks/task.rb +151 -0
  42. data/lib/karafka/pro/recurring_tasks.rb +87 -0
  43. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +131 -0
  44. data/lib/karafka/pro/routing/features/recurring_tasks/config.rb +28 -0
  45. data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +40 -0
  46. data/lib/karafka/pro/routing/features/recurring_tasks/proxy.rb +27 -0
  47. data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +44 -0
  48. data/lib/karafka/pro/routing/features/recurring_tasks.rb +25 -0
  49. data/lib/karafka/pro/routing/features/scheduled_messages/builder.rb +131 -0
  50. data/lib/karafka/pro/routing/features/scheduled_messages/config.rb +28 -0
  51. data/lib/karafka/pro/routing/features/scheduled_messages/contracts/topic.rb +40 -0
  52. data/lib/karafka/pro/routing/features/scheduled_messages/proxy.rb +27 -0
  53. data/lib/karafka/pro/routing/features/scheduled_messages/topic.rb +44 -0
  54. data/lib/karafka/pro/routing/features/scheduled_messages.rb +24 -0
  55. data/lib/karafka/pro/scheduled_messages/consumer.rb +185 -0
  56. data/lib/karafka/pro/scheduled_messages/contracts/config.rb +56 -0
  57. data/lib/karafka/pro/scheduled_messages/contracts/message.rb +61 -0
  58. data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +79 -0
  59. data/lib/karafka/pro/scheduled_messages/day.rb +45 -0
  60. data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +46 -0
  61. data/lib/karafka/pro/scheduled_messages/deserializers/payload.rb +35 -0
  62. data/lib/karafka/pro/scheduled_messages/dispatcher.rb +122 -0
  63. data/lib/karafka/pro/scheduled_messages/errors.rb +28 -0
  64. data/lib/karafka/pro/scheduled_messages/max_epoch.rb +41 -0
  65. data/lib/karafka/pro/scheduled_messages/proxy.rb +176 -0
  66. data/lib/karafka/pro/scheduled_messages/schema_validator.rb +37 -0
  67. data/lib/karafka/pro/scheduled_messages/serializer.rb +55 -0
  68. data/lib/karafka/pro/scheduled_messages/setup/config.rb +60 -0
  69. data/lib/karafka/pro/scheduled_messages/state.rb +62 -0
  70. data/lib/karafka/pro/scheduled_messages/tracker.rb +64 -0
  71. data/lib/karafka/pro/scheduled_messages.rb +67 -0
  72. data/lib/karafka/processing/executor.rb +6 -0
  73. data/lib/karafka/processing/strategies/default.rb +10 -0
  74. data/lib/karafka/processing/strategies/dlq.rb +16 -2
  75. data/lib/karafka/processing/strategies/dlq_mom.rb +25 -6
  76. data/lib/karafka/processing/worker.rb +11 -1
  77. data/lib/karafka/railtie.rb +11 -42
  78. data/lib/karafka/routing/features/dead_letter_queue/config.rb +3 -0
  79. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  80. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +7 -2
  81. data/lib/karafka/routing/features/eofed/contracts/topic.rb +12 -0
  82. data/lib/karafka/routing/topic.rb +14 -0
  83. data/lib/karafka/setup/config.rb +3 -0
  84. data/lib/karafka/version.rb +1 -1
  85. data.tar.gz.sig +0 -0
  86. metadata +68 -25
  87. metadata.gz.sig +0 -0
  88. data/certs/cert_chain.pem +0 -26
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module RecurringTasks
17
+ # Represents the current code-context schedule with defined tasks and their cron execution
18
+ # details. Single schedule includes all the information about all the tasks that we have
19
+ # defined and to be executed in a given moment in time.
20
+ class Schedule
21
+ # @return [String]
22
+ attr_reader :version
23
+
24
+ # @return [Hash<String, Task>]
25
+ attr_reader :tasks
26
+
27
+ # @param version [String] schedule version. In case of usage of versioning it is used to
28
+ # ensure, that older still active processes do not intercept the assignment to run older
29
+ # version of the scheduler. It is important to make sure, that this string is comparable.
30
+ def initialize(version:)
31
+ @version = version
32
+ @tasks = {}
33
+ end
34
+
35
+ # Adds task into the tasks accumulator
36
+ # @param task [Task]
37
+ # @note In case of multiple tasks with the same id, it will overwrite
38
+ def <<(task)
39
+ @tasks[task.id] = task
40
+ end
41
+
42
+ # Iterates over tasks yielding them one after another
43
+ # @param block [Proc] block that will be executed with each task
44
+ def each(&block)
45
+ @tasks.each_value(&block)
46
+ end
47
+
48
+ # @param id [String] id of a particular recurring task
49
+ # @return [Task, nil] task with a given id or nil if not found
50
+ def find(id)
51
+ @tasks[id]
52
+ end
53
+
54
+ # Allows us to have a nice DSL for defining schedules
55
+ # @param args [Array] attributes accepted by the task initializer
56
+ # @param block [Proc] block to execute
57
+ def schedule(**args, &block)
58
+ self << Task.new(**args, &block)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module RecurringTasks
17
+ # Converts schedule command and log details into data we can dispatch to Kafka.
18
+ class Serializer
19
+ # Current recurring tasks related schema structure
20
+ SCHEMA_VERSION = '1.0'
21
+
22
+ # @param schedule [Karafka::Pro::RecurringTasks::Schedule] schedule to serialize
23
+ # @return [String] serialized and compressed current schedule data with its tasks and their
24
+ # current state.
25
+ def schedule(schedule)
26
+ tasks = {}
27
+
28
+ schedule.each do |task|
29
+ tasks[task.id] = {
30
+ id: task.id,
31
+ cron: task.cron.original,
32
+ previous_time: task.previous_time.to_i,
33
+ next_time: task.next_time.to_i,
34
+ enabled: task.enabled?
35
+ }
36
+ end
37
+
38
+ data = {
39
+ schema_version: SCHEMA_VERSION,
40
+ schedule_version: schedule.version,
41
+ dispatched_at: Time.now.to_f,
42
+ type: 'schedule',
43
+ tasks: tasks
44
+ }
45
+
46
+ compress(
47
+ serialize(data)
48
+ )
49
+ end
50
+
51
+ # @param command_name [String] command name
52
+ # @param task_id [String] task id or '*' to match all.
53
+ # @return [String] serialized and compressed command data
54
+ def command(command_name, task_id)
55
+ data = {
56
+ schema_version: SCHEMA_VERSION,
57
+ schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
58
+ dispatched_at: Time.now.to_f,
59
+ type: 'command',
60
+ command: {
61
+ name: command_name
62
+ },
63
+ task: {
64
+ id: task_id
65
+ }
66
+ }
67
+
68
+ compress(
69
+ serialize(data)
70
+ )
71
+ end
72
+
73
+ # @param event [Karafka::Core::Monitoring::Event] recurring task dispatch event
74
+ # @return [String] serialized and compressed event log data
75
+ def log(event)
76
+ task = event[:task]
77
+
78
+ data = {
79
+ schema_version: SCHEMA_VERSION,
80
+ schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
81
+ dispatched_at: Time.now.to_f,
82
+ type: 'log',
83
+ task: {
84
+ id: task.id,
85
+ time_taken: event.payload[:time] || -1,
86
+ result: event.payload.key?(:error) ? 'failure' : 'success'
87
+ }
88
+ }
89
+
90
+ compress(
91
+ serialize(data)
92
+ )
93
+ end
94
+
95
+ private
96
+
97
+ # @param hash [Hash] hash to cast to json
98
+ # @return [String] json hash
99
+ def serialize(hash)
100
+ hash.to_json
101
+ end
102
+
103
+ # Compresses the provided data
104
+ #
105
+ # @param data [String] data to compress
106
+ # @return [String] compressed data
107
+ def compress(data)
108
+ Zlib::Deflate.deflate(data)
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module RecurringTasks
17
+ # Setup and config related recurring tasks components
18
+ module Setup
19
+ # Config for recurring tasks
20
+ class Config
21
+ extend ::Karafka::Core::Configurable
22
+
23
+ setting(:consumer_class, default: Consumer)
24
+ setting(:deserializer, default: Deserializer.new)
25
+ setting(:group_id, default: 'karafka_recurring_tasks')
26
+ # By default we will run the scheduling every 15 seconds since we provide a minute-based
27
+ # precision
28
+ setting(:interval, default: 15_000)
29
+ # Should we log the executions. If true (default) with each cron execution, there will
30
+ # be a special message published. Useful for debugging.
31
+ setting(:logging, default: true)
32
+
33
+ # Producer to be used by the recurring tasks.
34
+ # By default it is a `Karafka.producer`, however it may be overwritten if we want to use
35
+ # a separate instance in case of heavy usage of the transactional producer, etc.
36
+ setting(
37
+ :producer,
38
+ constructor: -> { ::Karafka.producer },
39
+ lazy: true
40
+ )
41
+
42
+ setting(:topics) do
43
+ setting(:schedules, default: 'karafka_recurring_tasks_schedules')
44
+ setting(:logs, default: 'karafka_recurring_tasks_logs')
45
+ end
46
+
47
+ configure
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module RecurringTasks
17
+ # Represents a single recurring task that can be executed when the time comes.
18
+ # Tasks should be lightweight. Anything heavy should be executed by scheduling appropriate
19
+ # jobs here.
20
+ class Task
21
+ include Helpers::ConfigImporter.new(
22
+ monitor: %i[monitor]
23
+ )
24
+
25
+ # @return [String] this task id
26
+ attr_reader :id
27
+
28
+ # @return [Fugit::Cron] cron from parsing the raw cron expression
29
+ attr_reader :cron
30
+
31
+ # Allows for update of previous time when restoring the materialized state
32
+ attr_accessor :previous_time
33
+
34
+ # @param id [String] unique id. If re-used between versions, will replace older occurrence.
35
+ # @param cron [String] cron expression matching this task expected execution times.
36
+ # @param previous_time [Integer, Time] previous time this task was executed. 0 if never.
37
+ # @param enabled [Boolean] should this task be enabled. Users may disable given task
38
+ # temporarily, this is why we need to know that.
39
+ # @param block [Proc] code to execute.
40
+ def initialize(id:, cron:, previous_time: 0, enabled: true, &block)
41
+ @id = id
42
+ @cron = ::Fugit::Cron.do_parse(cron)
43
+ @previous_time = previous_time
44
+ @start_time = Time.now
45
+ @executable = block
46
+ @enabled = enabled
47
+ @trigger = false
48
+ @changed = false
49
+ end
50
+
51
+ # @return [Boolean] true if anything in the task has changed and we should persist it
52
+ def changed?
53
+ @changed
54
+ end
55
+
56
+ # Disables this task execution indefinitely
57
+ def disable
58
+ touch
59
+ @enabled = false
60
+ end
61
+
62
+ # Enables back this task
63
+ def enable
64
+ touch
65
+ @enabled = true
66
+ end
67
+
68
+ # @return [Boolean] is this an executable task
69
+ def enabled?
70
+ @enabled
71
+ end
72
+
73
+ # Triggers the execution of this task at the earliest opportunity
74
+ def trigger
75
+ touch
76
+ @trigger = true
77
+ end
78
+
79
+ # @return [EtOrbi::EoTime] next execution time
80
+ def next_time
81
+ @cron.next_time(@previous_time.to_i.zero? ? @start_time : @previous_time)
82
+ end
83
+
84
+ # @return [Boolean] should we execute this task at this moment in time
85
+ def call?
86
+ return true if @trigger
87
+ return false unless enabled?
88
+
89
+ # Ensure the job is only due if current_time is strictly after the next_time
90
+ # Please note that we can only compare eorbi against time and not the other way around
91
+ next_time <= Time.now
92
+ end
93
+
94
+ # Executes the given task and publishes appropriate notification bus events.
95
+ def call
96
+ monitor.instrument(
97
+ 'recurring_tasks.task.executed',
98
+ task: self
99
+ ) do
100
+ # We check for presence of the `@executable` because user can define cron schedule
101
+ # without the code block
102
+ return unless @executable
103
+
104
+ execute
105
+ end
106
+ rescue StandardError => e
107
+ monitor.instrument(
108
+ 'error.occurred',
109
+ caller: self,
110
+ error: e,
111
+ task: self,
112
+ type: 'recurring_tasks.task.execute.error'
113
+ )
114
+ ensure
115
+ @trigger = false
116
+ @previous_time = Time.now
117
+ end
118
+
119
+ # Runs the executable block without any instrumentation or error handling. Useful for
120
+ # debugging and testing
121
+ def execute
122
+ @executable.call
123
+ end
124
+
125
+ # Removes the changes indicator flag
126
+ def clear
127
+ @changed = false
128
+ end
129
+
130
+ # @return [Hash] hash version of the task. Used for contract validation.
131
+ def to_h
132
+ {
133
+ id: id,
134
+ cron: @cron.original,
135
+ previous_time: previous_time,
136
+ next_time: next_time,
137
+ changed: changed?,
138
+ enabled: enabled?
139
+ }
140
+ end
141
+
142
+ private
143
+
144
+ # Marks the task as changed
145
+ def touch
146
+ @changed = true
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ # Recurring tasks functionality
17
+ module RecurringTasks
18
+ class << self
19
+ # @return [Schedule, nil] current defined schedule or nil if not defined
20
+ def schedule
21
+ @schedule || define('0.0.0') {}
22
+ end
23
+
24
+ # Simplified API for schedules definitions and validates the tasks data
25
+ #
26
+ # @param version [String]
27
+ # @param block [Proc]
28
+ #
29
+ # @example
30
+ # Karafka::Pro::RecurringTasks.define('1.0.1') do
31
+ # schedule(id: 'mailer', cron: '* * * * *') do
32
+ # MailingJob.perform_async
33
+ # end
34
+ # end
35
+ def define(version = '1.0.0', &block)
36
+ @schedule = Schedule.new(version: version)
37
+ @schedule.instance_exec(&block)
38
+
39
+ @schedule.each do |task|
40
+ Contracts::Task.new.validate!(task.to_h)
41
+ end
42
+
43
+ @schedule
44
+ end
45
+
46
+ # Defines nice command methods to dispatch cron requests
47
+ Executor::COMMANDS.each do |command_name|
48
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
49
+ # @param task_id [String] task to which we want to dispatch command or '*' if to all
50
+ def #{command_name}(task_id)
51
+ Dispatcher.command('#{command_name}', task_id)
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ # Below are private APIs
57
+
58
+ # Sets up additional config scope, validations and other things
59
+ #
60
+ # @param config [Karafka::Core::Configurable::Node] root node config
61
+ def pre_setup(config)
62
+ # Expand the config with this feature specific stuff
63
+ config.instance_eval do
64
+ setting(:recurring_tasks, default: Setup::Config.config)
65
+ end
66
+ end
67
+
68
+ # @param config [Karafka::Core::Configurable::Node] root node config
69
+ def post_setup(config)
70
+ RecurringTasks::Contracts::Config.new.validate!(config.to_h)
71
+
72
+ # Published after task is successfully executed
73
+ Karafka.monitor.notifications_bus.register_event('recurring_tasks.task.executed')
74
+
75
+ # Initialize empty dummy schedule, so we always have one and so we do not have to
76
+ # deal with a case where there is no schedule
77
+ RecurringTasks.schedule
78
+
79
+ # User can disable logging of executions, in which case we don't track them
80
+ return unless Karafka::App.config.recurring_tasks.logging
81
+
82
+ Karafka.monitor.subscribe(Listener.new)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Routing extensions for recurring tasks
20
+ module Builder
21
+ # Enabled recurring tasks operations and adds needed topics and other stuff.
22
+ #
23
+ # @param active [Boolean] should recurring tasks be active. We use a boolean flag to
24
+ # have API consistency in the system, so it matches other routing related APIs.
25
+ # @param block [Proc] optional reconfiguration of the topics definitions.
26
+ def recurring_tasks(active = false, &block)
27
+ return unless active
28
+
29
+ # We only require zlib when we decide to run recurring tasks because it is not needed
30
+ # otherwise.
31
+ require 'zlib'
32
+ ensure_fugit_availability!
33
+
34
+ tasks_cfg = App.config.recurring_tasks
35
+ topics_cfg = tasks_cfg.topics
36
+
37
+ consumer_group tasks_cfg.group_id do
38
+ # Registers the primary topic that we use to control schedules execution. This is
39
+ # the one that we use to trigger recurring tasks.
40
+ schedules_topic = topic(topics_cfg.schedules) do
41
+ consumer tasks_cfg.consumer_class
42
+ deserializer tasks_cfg.deserializer
43
+ # Because the topic method name as well as builder proxy method name is the same
44
+ # we need to reference it via target directly
45
+ target.recurring_tasks(true)
46
+
47
+ # We manage offsets directly because messages can have both schedules and
48
+ # commands and we need to apply them only when we need to
49
+ manual_offset_management(true)
50
+
51
+ # We use multi-batch operations and in-memory state for schedules. This needs to
52
+ # always operate without re-creation.
53
+ consumer_persistence(true)
54
+
55
+ # This needs to be enabled for the eof to work correctly
56
+ kafka('enable.partition.eof': true, inherit: true)
57
+ eofed(true)
58
+
59
+ # Favour latency. This is a low traffic topic that only accepts user initiated
60
+ # low-frequency commands
61
+ max_messages(1)
62
+ # Since we always react on the received message, we can block for longer periods
63
+ # of time
64
+ max_wait_time(10_000)
65
+
66
+ # Since the execution of particular tasks is isolated and guarded, it should not
67
+ # leak. This means, that this is to handle errors like schedule version
68
+ # incompatibility and other errors that will not go away without a redeployment
69
+ pause_timeout(60 * 1_000)
70
+ pause_max_timeout(60 * 1_000)
71
+
72
+ # Keep older data for a day and compact to the last state available
73
+ config(
74
+ 'cleanup.policy': 'compact,delete',
75
+ 'retention.ms': 86_400_000
76
+ )
77
+
78
+ # This is the core of execution. Since we're producers of states, we need a way
79
+ # to tick without having new data
80
+ periodic(
81
+ interval: App.config.recurring_tasks.interval,
82
+ during_pause: false,
83
+ during_retry: false
84
+ )
85
+
86
+ # If this is the direct schedules redefinition style, we run it
87
+ # The second one (see end of this method) allows for linear reconfiguration of
88
+ # both the topics
89
+ instance_eval(&block) if block && block.arity.zero?
90
+ end
91
+
92
+ # This topic is to store logs that we can then inspect either from the admin or via
93
+ # the Web UI
94
+ logs_topic = topic(topics_cfg.logs) do
95
+ active(false)
96
+ deserializer tasks_cfg.deserializer
97
+ target.recurring_tasks(true)
98
+
99
+ # Keep cron logs of executions for a week and after that remove. Week should be
100
+ # enough and should not produce too much data.
101
+ config(
102
+ 'cleanup.policy': 'delete',
103
+ 'retention.ms': 604_800_000
104
+ )
105
+ end
106
+
107
+ yield(schedules_topic, logs_topic) if block && block.arity.positive?
108
+ end
109
+ end
110
+
111
+ # Checks if fugit is present. If not, will try to require it as it might not have
112
+ # been required but is available. If fails, will crash.
113
+ def ensure_fugit_availability!
114
+ return if Object.const_defined?(:Fugit)
115
+
116
+ require 'fugit'
117
+ rescue LoadError
118
+ raise(
119
+ ::Karafka::Errors::DependencyConstraintsError,
120
+ <<~ERROR_MSG
121
+ Failed to require fugit gem.
122
+ Add it to your Gemfile, as it is required for the recurring tasks to work.
123
+ ERROR_MSG
124
+ )
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Recurring tasks configuration
20
+ Config = Struct.new(
21
+ :active,
22
+ keyword_init: true
23
+ ) { alias_method :active?, :active }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Namespace for recurring tasks contracts
20
+ module Contracts
21
+ # Rules around recurring tasks settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:recurring_tasks) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Routing proxy extensions for recurring tasks
20
+ module Proxy
21
+ include Builder
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end