karafka 0.5.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.
- checksums.yaml +7 -0
- data/.gitignore +68 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +202 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +216 -0
- data/MIT-LICENCE +18 -0
- data/README.md +831 -0
- data/Rakefile +17 -0
- data/bin/karafka +7 -0
- data/karafka.gemspec +34 -0
- data/lib/karafka.rb +73 -0
- data/lib/karafka/app.rb +45 -0
- data/lib/karafka/base_controller.rb +162 -0
- data/lib/karafka/base_responder.rb +118 -0
- data/lib/karafka/base_worker.rb +41 -0
- data/lib/karafka/capistrano.rb +2 -0
- data/lib/karafka/capistrano/karafka.cap +84 -0
- data/lib/karafka/cli.rb +52 -0
- data/lib/karafka/cli/base.rb +74 -0
- data/lib/karafka/cli/console.rb +23 -0
- data/lib/karafka/cli/flow.rb +46 -0
- data/lib/karafka/cli/info.rb +26 -0
- data/lib/karafka/cli/install.rb +45 -0
- data/lib/karafka/cli/routes.rb +39 -0
- data/lib/karafka/cli/server.rb +59 -0
- data/lib/karafka/cli/worker.rb +26 -0
- data/lib/karafka/connection/consumer.rb +29 -0
- data/lib/karafka/connection/listener.rb +54 -0
- data/lib/karafka/connection/message.rb +17 -0
- data/lib/karafka/connection/topic_consumer.rb +48 -0
- data/lib/karafka/errors.rb +50 -0
- data/lib/karafka/fetcher.rb +40 -0
- data/lib/karafka/helpers/class_matcher.rb +77 -0
- data/lib/karafka/helpers/multi_delegator.rb +31 -0
- data/lib/karafka/loader.rb +77 -0
- data/lib/karafka/logger.rb +52 -0
- data/lib/karafka/monitor.rb +82 -0
- data/lib/karafka/params/interchanger.rb +33 -0
- data/lib/karafka/params/params.rb +102 -0
- data/lib/karafka/patches/dry/configurable/config.rb +37 -0
- data/lib/karafka/process.rb +61 -0
- data/lib/karafka/responders/builder.rb +33 -0
- data/lib/karafka/responders/topic.rb +43 -0
- data/lib/karafka/responders/usage_validator.rb +59 -0
- data/lib/karafka/routing/builder.rb +89 -0
- data/lib/karafka/routing/route.rb +80 -0
- data/lib/karafka/routing/router.rb +38 -0
- data/lib/karafka/server.rb +53 -0
- data/lib/karafka/setup/config.rb +57 -0
- data/lib/karafka/setup/configurators/base.rb +33 -0
- data/lib/karafka/setup/configurators/celluloid.rb +20 -0
- data/lib/karafka/setup/configurators/sidekiq.rb +34 -0
- data/lib/karafka/setup/configurators/water_drop.rb +19 -0
- data/lib/karafka/setup/configurators/worker_glass.rb +13 -0
- data/lib/karafka/status.rb +23 -0
- data/lib/karafka/templates/app.rb.example +26 -0
- data/lib/karafka/templates/application_controller.rb.example +5 -0
- data/lib/karafka/templates/application_responder.rb.example +9 -0
- data/lib/karafka/templates/application_worker.rb.example +12 -0
- data/lib/karafka/templates/config.ru.example +13 -0
- data/lib/karafka/templates/sidekiq.yml.example +26 -0
- data/lib/karafka/version.rb +6 -0
- data/lib/karafka/workers/builder.rb +49 -0
- data/log/.gitkeep +0 -0
- metadata +267 -0
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rake'
|
3
|
+
require 'polishgeeks-dev-tools'
|
4
|
+
|
5
|
+
PolishGeeks::DevTools.setup do |config|
|
6
|
+
config.brakeman = false
|
7
|
+
config.haml_lint = false
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Self check using polishgeeks-dev-tools'
|
11
|
+
task :check do
|
12
|
+
PolishGeeks::DevTools::Runner.new.execute(
|
13
|
+
PolishGeeks::DevTools::Logger.new
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
task default: :check
|
data/bin/karafka
ADDED
data/karafka.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'karafka/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'karafka'
|
8
|
+
spec.version = ::Karafka::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.authors = ['Maciej Mensfeld', 'Pavlo Vavruk']
|
11
|
+
spec.email = %w( maciej@mensfeld.pl pavlo.vavruk@gmail.com )
|
12
|
+
spec.homepage = 'https://github.com/karafka/karafka'
|
13
|
+
spec.summary = %q{ Ruby based Microframework for handling Apache Kafka incoming messages }
|
14
|
+
spec.description = %q{ Microframework used to simplify Kafka based Ruby applications }
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.add_development_dependency 'bundler', '~> 1.2'
|
18
|
+
|
19
|
+
spec.add_dependency 'ruby-kafka', '= 0.3.15'
|
20
|
+
spec.add_dependency 'sidekiq', '~> 4.2'
|
21
|
+
spec.add_dependency 'worker-glass', '~> 0.2'
|
22
|
+
spec.add_dependency 'celluloid', '~> 0.17'
|
23
|
+
spec.add_dependency 'envlogic', '~> 1.0'
|
24
|
+
spec.add_dependency 'waterdrop', '~> 0.3'
|
25
|
+
spec.add_dependency 'rake', '~> 11.3'
|
26
|
+
spec.add_dependency 'thor', '~> 0.19'
|
27
|
+
spec.add_dependency 'activesupport', '~> 5.0'
|
28
|
+
spec.add_dependency 'dry-configurable', '~> 0.1.7'
|
29
|
+
spec.required_ruby_version = '>= 2.3.0'
|
30
|
+
|
31
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
|
32
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = %w( lib )
|
34
|
+
end
|
data/lib/karafka.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
%w(
|
2
|
+
rake
|
3
|
+
ostruct
|
4
|
+
rubygems
|
5
|
+
bundler
|
6
|
+
English
|
7
|
+
celluloid/current
|
8
|
+
waterdrop
|
9
|
+
pathname
|
10
|
+
timeout
|
11
|
+
logger
|
12
|
+
kafka
|
13
|
+
sidekiq
|
14
|
+
worker_glass
|
15
|
+
envlogic
|
16
|
+
thor
|
17
|
+
fileutils
|
18
|
+
dry-configurable
|
19
|
+
active_support/callbacks
|
20
|
+
active_support/core_ext/class/subclasses
|
21
|
+
active_support/core_ext/hash/indifferent_access
|
22
|
+
active_support/descendants_tracker
|
23
|
+
active_support/inflector
|
24
|
+
karafka/loader
|
25
|
+
karafka/status
|
26
|
+
).each { |lib| require lib }
|
27
|
+
|
28
|
+
# Karafka library
|
29
|
+
module Karafka
|
30
|
+
extend Envlogic
|
31
|
+
|
32
|
+
class << self
|
33
|
+
# @return [Logger] logger that we want to use. Will use ::Karafka::Logger by default
|
34
|
+
def logger
|
35
|
+
@logger ||= App.config.logger
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [::Karafka::Monitor] monitor that we want to use. Will use dummy monitor by default
|
39
|
+
def monitor
|
40
|
+
@monitor ||= App.config.monitor
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String] root path of this gem
|
44
|
+
def gem_root
|
45
|
+
Pathname.new(File.expand_path('../..', __FILE__))
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] Karafka app root path (user application path)
|
49
|
+
def root
|
50
|
+
Pathname.new(File.dirname(ENV['BUNDLE_GEMFILE']))
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] path to Karafka gem root core
|
54
|
+
def core_root
|
55
|
+
Pathname.new(File.expand_path('../karafka', __FILE__))
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] path to a default file that contains booting procedure etc
|
59
|
+
# @note By default it is a file called 'app.rb' but it can be specified as you wish if you
|
60
|
+
# have Karafka that is merged into a Sinatra/Rails app and app.rb is taken.
|
61
|
+
# It will be used for console/workers/etc
|
62
|
+
# @example Standard only-Karafka case
|
63
|
+
# Karafka.boot_file #=> '/home/app_path/app.rb'
|
64
|
+
# @example Non standard case
|
65
|
+
# KARAFKA_BOOT_FILE='/home/app_path/karafka.rb'
|
66
|
+
# Karafka.boot_file #=> '/home/app_path/karafka.rb'
|
67
|
+
def boot_file
|
68
|
+
Pathname.new(ENV['KARAFKA_BOOT_FILE'] || File.join(Karafka.root, 'app.rb'))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Karafka::Loader.new.load!(Karafka.core_root)
|
data/lib/karafka/app.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Karafka
|
2
|
+
# App class
|
3
|
+
class App
|
4
|
+
class << self
|
5
|
+
# Sets up the whole configuration
|
6
|
+
# @param [Block] block configuration block
|
7
|
+
def setup(&block)
|
8
|
+
Setup::Config.setup(&block)
|
9
|
+
initialize!
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets up all the internal components and bootstrap whole app
|
13
|
+
# We need to know details about routes in order to setup components,
|
14
|
+
# that's why we don't setup them after std setup is done
|
15
|
+
def boot!
|
16
|
+
Setup::Config.setup_components
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Karafka::Config] config instance
|
20
|
+
def config
|
21
|
+
Setup::Config.config
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Karafka::Routing::Builder] routes builder instance
|
25
|
+
def routes
|
26
|
+
Routing::Builder.instance
|
27
|
+
end
|
28
|
+
|
29
|
+
Status.instance_methods(false).each do |delegated|
|
30
|
+
define_method(delegated) do
|
31
|
+
Status.instance.public_send(delegated)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Methods that should be delegated to Karafka module
|
36
|
+
%i(
|
37
|
+
root env logger monitor
|
38
|
+
).each do |delegated|
|
39
|
+
define_method(delegated) do
|
40
|
+
Karafka.public_send(delegated)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# Karafka module namespace
|
2
|
+
module Karafka
|
3
|
+
# Base controller from which all Karafka controllers should inherit
|
4
|
+
# Similar to Rails controllers we can define before_enqueue callbacks
|
5
|
+
# that will be executed
|
6
|
+
#
|
7
|
+
# Note that if before_enqueue return false, the chain will be stopped and
|
8
|
+
# the perform method won't be executed in sidekiq (won't peform_async it)
|
9
|
+
#
|
10
|
+
# @example Create simple controller
|
11
|
+
# class ExamplesController < Karafka::BaseController
|
12
|
+
# def perform
|
13
|
+
# # some logic here
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Create a controller with a block before_enqueue
|
18
|
+
# class ExampleController < Karafka::BaseController
|
19
|
+
# before_enqueue do
|
20
|
+
# # Here we should have some checking logic
|
21
|
+
# # If false is returned, won't schedule a perform action
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def perform
|
25
|
+
# # some logic here
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example Create a controller with a method before_enqueue
|
30
|
+
# class ExampleController < Karafka::BaseController
|
31
|
+
# before_enqueue :before_method
|
32
|
+
#
|
33
|
+
# def perform
|
34
|
+
# # some logic here
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# private
|
38
|
+
#
|
39
|
+
# def before_method
|
40
|
+
# # Here we should have some checking logic
|
41
|
+
# # If false is returned, won't schedule a perform action
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @example Create a controller with an after_failure action
|
46
|
+
# class ExampleController < Karafka::BaseController
|
47
|
+
# def perform
|
48
|
+
# # some logic here
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# def after_failure
|
52
|
+
# # action taken in case perform fails
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
class BaseController
|
56
|
+
extend ActiveSupport::DescendantsTracker
|
57
|
+
include ActiveSupport::Callbacks
|
58
|
+
|
59
|
+
# The schedule method is wrapped with a set of callbacks
|
60
|
+
# We won't run perform at the backend if any of the callbacks
|
61
|
+
# returns false
|
62
|
+
# @see http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-get_callbacks
|
63
|
+
define_callbacks :schedule
|
64
|
+
|
65
|
+
# This will be set based on routing settings
|
66
|
+
# From 0.4 a single controller can handle multiple topics jobs
|
67
|
+
attr_accessor :group, :topic, :worker, :parser, :interchanger, :responder
|
68
|
+
|
69
|
+
class << self
|
70
|
+
# Creates a callback that will be executed before scheduling to Sidekiq
|
71
|
+
# @param method_name [Symbol, String] method name or nil if we plan to provide a block
|
72
|
+
# @yield A block with a code that should be executed before scheduling
|
73
|
+
# @note If value returned is false, will chalt the chain and not schedlue to Sidekiq
|
74
|
+
# @example Define a block before_enqueue callback
|
75
|
+
# before_enqueue do
|
76
|
+
# # logic here
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @example Define a class name before_enqueue callback
|
80
|
+
# before_enqueue :method_name
|
81
|
+
def before_enqueue(method_name = nil, &block)
|
82
|
+
set_callback :schedule, :before, method_name ? method_name : block
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Creates lazy loaded params object
|
87
|
+
# @note Until first params usage, it won't parse data at all
|
88
|
+
# @param message [Karafka::Connection::Message, Hash] message with raw content or a hash
|
89
|
+
# from Sidekiq that allows us to build params.
|
90
|
+
def params=(message)
|
91
|
+
@params = Karafka::Params::Params.build(message, self)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Executes the default controller flow, runs callbacks and if not halted
|
95
|
+
# will schedule a perform task in sidekiq
|
96
|
+
def schedule
|
97
|
+
run_callbacks :schedule do
|
98
|
+
perform_async
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Hash] hash with all controller details - it works similar to #params method however
|
103
|
+
# it won't parse data so it will return unparsed details about controller and its parameters
|
104
|
+
# @example Get data about ctrl
|
105
|
+
# ctrl.to_h #=> { "worker"=>WorkerClass, "parsed"=>false, "content"=>"{}" }
|
106
|
+
def to_h
|
107
|
+
@params
|
108
|
+
end
|
109
|
+
|
110
|
+
# Method that will perform business logic on data received from Kafka
|
111
|
+
# @note This method needs bo be implemented in a subclass. We stub it here as a failover if
|
112
|
+
# someone forgets about it or makes on with typo
|
113
|
+
def perform
|
114
|
+
raise NotImplementedError, 'Implement this in a subclass'
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# @return [Karafka::Params::Params] Karafka params that is a hash with indifferent access
|
120
|
+
# @note Params internally are lazy loaded before first use. That way we can skip parsing
|
121
|
+
# process if we have before_enqueue that rejects some incoming messages without using params
|
122
|
+
# It can be also used when handling really heavy data (in terms of parsing). Without direct
|
123
|
+
# usage outside of worker scope, it will pass raw data into sidekiq, so we won't use Karafka
|
124
|
+
# working time to parse this data. It will happen only in the worker (where it can take time)
|
125
|
+
# that way Karafka will be able to process data really quickly. On the other hand, if we
|
126
|
+
# decide to use params somewhere before it hits worker logic, it won't parse it again in
|
127
|
+
# the worker - it will use already loaded data and pass it to Redis
|
128
|
+
# @note Invokation of this method will cause load all the data into params object. If you want
|
129
|
+
# to get access without parsing, please access @params directly
|
130
|
+
def params
|
131
|
+
@params.retrieve
|
132
|
+
end
|
133
|
+
|
134
|
+
# Responds with given data using given responder. This allows us to have a similar way of
|
135
|
+
# defining flows like synchronous protocols
|
136
|
+
# @param data Anything we want to pass to responder based on which we want to trigger further
|
137
|
+
# Kafka responding
|
138
|
+
# @raise [Karafka::Errors::ResponderMissing] raised when we don't have a responder defined,
|
139
|
+
# but we still try to use this method
|
140
|
+
def respond_with(*data)
|
141
|
+
raise(Errors::ResponderMissing, self.class) unless responder
|
142
|
+
|
143
|
+
Karafka.monitor.notice(self.class, data: data)
|
144
|
+
responder.new.call(*data)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Enqueues the execution of perform method into a worker.
|
148
|
+
# @note Each worker needs to have a class #perform_async method that will allow us to pass
|
149
|
+
# parameters into it. We always pass topic as a first argument and this request params
|
150
|
+
# as a second one (we pass topic to be able to build back the controller in the worker)
|
151
|
+
def perform_async
|
152
|
+
Karafka.monitor.notice(self.class, to_h)
|
153
|
+
|
154
|
+
# We use @params directly (instead of #params) because of lazy loading logic that is behind
|
155
|
+
# it. See Karafka::Params::Params class for more details about that
|
156
|
+
worker.perform_async(
|
157
|
+
topic,
|
158
|
+
interchanger.load(@params)
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Karafka
|
2
|
+
# Base responder from which all Karafka responders should inherit
|
3
|
+
# Similar to Rails responders concept. It allows us to design flow from one app to another
|
4
|
+
# by isolating what responses should be sent (and where) based on a given action
|
5
|
+
# It differs from Rails responders in the way it works: in std http request we can have one
|
6
|
+
# response, here we can have unlimited number of them
|
7
|
+
#
|
8
|
+
# It has a simple API for defining where should we respond (and if it is required)
|
9
|
+
#
|
10
|
+
# @example Basic usage (each registered topic is required to be used by default)
|
11
|
+
# class Responder < BaseResponder
|
12
|
+
# topic :new_action
|
13
|
+
#
|
14
|
+
# def respond(data)
|
15
|
+
# respond_to :new_action, data
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @example Marking topic as optional (we won't have to use it)
|
20
|
+
# class Responder < BaseResponder
|
21
|
+
# topic :required_topic
|
22
|
+
# topic :new_action, optional: true
|
23
|
+
#
|
24
|
+
# def respond(data)
|
25
|
+
# respond_to :required_topic, data
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @example Multiple times used topic
|
30
|
+
# class Responder < BaseResponder
|
31
|
+
# topic :required_topic, multiple_usage: true
|
32
|
+
#
|
33
|
+
# def respond(data)
|
34
|
+
# data.each do |subset|
|
35
|
+
# respond_to :required_topic, subset
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @example Accept multiple arguments to a respond method
|
41
|
+
# class Responder < BaseResponder
|
42
|
+
# topic :users_actions
|
43
|
+
# topic :articles_viewed
|
44
|
+
#
|
45
|
+
# def respond(user, article)
|
46
|
+
# respond_to :users_actions, user
|
47
|
+
# respond_to :articles_viewed, article
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
class BaseResponder
|
51
|
+
# Definitions of all topics that we want to be able to use in this responder should go here
|
52
|
+
class_attribute :topics
|
53
|
+
|
54
|
+
class << self
|
55
|
+
# Registers a topic as on to which we will be able to respond
|
56
|
+
# @param topic_name [Symbol, String] name of topic to which we want to respond
|
57
|
+
# @param options [Hash] hash with optional configuration details
|
58
|
+
def topic(topic_name, options = {})
|
59
|
+
self.topics ||= {}
|
60
|
+
topic_obj = Responders::Topic.new(topic_name, options)
|
61
|
+
self.topics[topic_obj.name] = topic_obj
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a responder object
|
66
|
+
# @return [Karafka::BaseResponder] base responder descendant responder
|
67
|
+
def initialize
|
68
|
+
@used_topics = []
|
69
|
+
end
|
70
|
+
|
71
|
+
# Performs respond and validates that all the response requirement were met
|
72
|
+
# @param data Anything that we want to respond with
|
73
|
+
# @note We know that validators should be executed also before sending data to topics, however
|
74
|
+
# the implementation gets way more complicated then, that's why we check after everything
|
75
|
+
# was sent using responder
|
76
|
+
def call(*data)
|
77
|
+
respond(*data)
|
78
|
+
validate!
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Method that needs to be implemented in a subclass. It should handle responding
|
84
|
+
# on registered topics
|
85
|
+
# @raise [NotImplementedError] This method needs to be implemented in a subclass
|
86
|
+
def respond(*_data)
|
87
|
+
raise NotImplementedError, 'Implement this in a subclass'
|
88
|
+
end
|
89
|
+
|
90
|
+
# This method allow us to respond to a single topic with a given data. It can be used
|
91
|
+
# as many times as we need. Especially when we have 1:n flow
|
92
|
+
# @param topic [Symbol, String] topic to which we want to respond
|
93
|
+
# @param data [String, Object] string or object that we want to send
|
94
|
+
# @note Note that if we pass object here (not a string), this method will invoke a #to_json
|
95
|
+
# on it.
|
96
|
+
# @note Respond to does not accept multiple data arguments.
|
97
|
+
def respond_to(topic, data)
|
98
|
+
Karafka.monitor.notice(self.class, topic: topic, data: data)
|
99
|
+
|
100
|
+
topic = topic.to_s
|
101
|
+
@used_topics << topic
|
102
|
+
|
103
|
+
::WaterDrop::Message.new(
|
104
|
+
topic,
|
105
|
+
data.is_a?(String) ? data : data.to_json
|
106
|
+
).send!
|
107
|
+
end
|
108
|
+
|
109
|
+
# Checks if we met all the topics requirements. It will fail if we didn't send a message to
|
110
|
+
# a registered required topic, etc.
|
111
|
+
def validate!
|
112
|
+
Responders::UsageValidator.new(
|
113
|
+
self.class.topics || {},
|
114
|
+
@used_topics
|
115
|
+
).validate!
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|