rimless 0.3.0 → 1.0.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 +4 -4
- data/.rubocop.yml +2 -2
- data/.travis.yml +10 -2
- data/Appraisals +5 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile +1 -1
- data/Makefile +5 -2
- data/README.md +141 -0
- data/gemfiles/rails_4.2.gemfile +4 -6
- data/gemfiles/rails_5.0.gemfile +4 -6
- data/gemfiles/rails_5.1.gemfile +4 -6
- data/gemfiles/rails_5.2.gemfile +4 -6
- data/gemfiles/rails_6.0.gemfile +8 -0
- data/lib/rimless/base_consumer.rb +30 -0
- data/lib/rimless/configuration.rb +16 -0
- data/lib/rimless/consumer.rb +189 -0
- data/lib/rimless/consumer_job.rb +10 -0
- data/lib/rimless/kafka_helpers.rb +1 -1
- data/lib/rimless/karafka/avro_deserializer.rb +25 -0
- data/lib/rimless/karafka/base64_interchanger.rb +31 -0
- data/lib/rimless/karafka/passthrough_mapper.rb +29 -0
- data/lib/rimless/railtie.rb +12 -0
- data/lib/rimless/rspec/helpers.rb +25 -0
- data/lib/rimless/rspec.rb +14 -1
- data/lib/rimless/tasks/consumer.rake +28 -0
- data/lib/rimless/tasks/generator.rake +36 -0
- data/lib/rimless/tasks/stats.rake +16 -0
- data/lib/rimless/tasks/templates/application_consumer.rb +6 -0
- data/lib/rimless/tasks/templates/custom_consumer.rb +12 -0
- data/lib/rimless/tasks/templates/custom_consumer_spec.rb +22 -0
- data/lib/rimless/tasks/templates/karafka.rb +20 -0
- data/lib/rimless/version.rb +1 -1
- data/lib/rimless.rb +13 -0
- data/log/development.log +0 -0
- data/rimless.gemspec +4 -1
- metadata +62 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89eb85ed138758a0b67496d3816fe748d7119eb9afcd9e5d08c2e116d24b28f8
|
4
|
+
data.tar.gz: dea1976e01d8719aee285af77f40ec140622d15997996bbffa1f34ba61949c99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a26b32866b7a5a52a5921ee07f7d400051773d28a45b62eb536b9febe704879587ddcf69a8dabf25e5799d3367c789e4767e9525c6be7dea59ef07c3cb7fdbfe
|
7
|
+
data.tar.gz: b13f1768f869ed85c7ea27ef83c1667999e5503a2c9c400406c2348493a869136c6c7ba528f02e2b815a8eb9bf4325359e1e2fdc41951ffaefe6ce92bd1f072c
|
data/.rubocop.yml
CHANGED
@@ -8,12 +8,12 @@ Documentation:
|
|
8
8
|
|
9
9
|
AllCops:
|
10
10
|
DisplayCopNames: true
|
11
|
-
TargetRubyVersion: 2.
|
11
|
+
TargetRubyVersion: 2.5
|
12
12
|
Exclude:
|
13
13
|
- bin/**/*
|
14
14
|
- vendor/**/*
|
15
15
|
- build/**/*
|
16
|
-
- gemfiles
|
16
|
+
- gemfiles/**/*
|
17
17
|
|
18
18
|
Metrics/BlockLength:
|
19
19
|
Exclude:
|
data/.travis.yml
CHANGED
@@ -6,18 +6,26 @@ sudo: false
|
|
6
6
|
language: ruby
|
7
7
|
cache: bundler
|
8
8
|
rvm:
|
9
|
+
- 2.7
|
9
10
|
- 2.6
|
10
11
|
- 2.5
|
11
|
-
- 2.4
|
12
|
-
- 2.3
|
13
12
|
|
14
13
|
gemfile:
|
15
14
|
- gemfiles/rails_4.2.gemfile
|
16
15
|
- gemfiles/rails_5.0.gemfile
|
17
16
|
- gemfiles/rails_5.1.gemfile
|
18
17
|
- gemfiles/rails_5.2.gemfile
|
18
|
+
- gemfiles/rails_6.0.gemfile
|
19
19
|
|
20
20
|
before_install: gem install bundler
|
21
|
+
install:
|
22
|
+
# Rails 4 is not Ruby 2.7 compatible, so we skip this build
|
23
|
+
- |
|
24
|
+
[[ "${BUNDLE_GEMFILE}" =~ rails_4 && \
|
25
|
+
"${TRAVIS_RUBY_VERSION}" =~ 2.7 ]] && exit || true
|
26
|
+
# Regular build
|
27
|
+
- bundle install --jobs=3 --retry=3
|
28
|
+
|
21
29
|
before_script:
|
22
30
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
23
31
|
- chmod +x ./cc-test-reporter
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
data/Dockerfile
CHANGED
data/Makefile
CHANGED
@@ -26,12 +26,13 @@ RM ?= rm
|
|
26
26
|
XARGS ?= xargs
|
27
27
|
|
28
28
|
# Container binaries
|
29
|
-
BUNDLE ?= bundle
|
30
29
|
APPRAISAL ?= appraisal
|
30
|
+
BUNDLE ?= bundle
|
31
|
+
GEM ?= gem
|
31
32
|
RAKE ?= rake
|
32
|
-
YARD ?= yard
|
33
33
|
RAKE ?= rake
|
34
34
|
RUBOCOP ?= rubocop
|
35
|
+
YARD ?= yard
|
35
36
|
|
36
37
|
# Files
|
37
38
|
GEMFILES ?= $(subst _,-,$(patsubst $(GEMFILES_DIR)/%.gemfile,%,\
|
@@ -75,6 +76,8 @@ install:
|
|
75
76
|
# Install the dependencies
|
76
77
|
@$(MKDIR) -p $(VENDOR_DIR)
|
77
78
|
@$(call run-shell,$(BUNDLE) check || $(BUNDLE) install --path $(VENDOR_DIR))
|
79
|
+
@$(call run-shell,GEM_HOME=vendor/bundle/ruby/$${RUBY_MAJOR}.0 \
|
80
|
+
$(GEM) install bundler -v "~> 1.0")
|
78
81
|
@$(call run-shell,$(BUNDLE) exec $(APPRAISAL) install)
|
79
82
|
|
80
83
|
update: install
|
data/README.md
CHANGED
@@ -23,6 +23,11 @@ opinionated framework which sets up solid conventions for producing messages.
|
|
23
23
|
- [Confluent Schema Registry Subject](#confluent-schema-registry-subject)
|
24
24
|
- [Organize and write schema definitions](#organize-and-write-schema-definitions)
|
25
25
|
- [Producing messages](#producing-messages)
|
26
|
+
- [Consuming messages](#consuming-messages)
|
27
|
+
- [Routing messages to consumers](#routing-messages-to-consumers)
|
28
|
+
- [Consuming event messages](#consuming-event-messages)
|
29
|
+
- [Listing consumer routes](#listing-consumer-routes)
|
30
|
+
- [Starting the consumer process(es)](#starting-the-consumer-processes)
|
26
31
|
- [Encoding/Decoding messages](#encodingdecoding-messages)
|
27
32
|
- [Handling of schemaless deep blobs](#handling-of-schemaless-deep-blobs)
|
28
33
|
- [Writing tests for your messages](#writing-tests-for-your-messages)
|
@@ -80,6 +85,9 @@ Rimless.configure do |conf|
|
|
80
85
|
# The Confluent Schema Registry API URL,
|
81
86
|
# set to HAUSGOLD defaults when not set
|
82
87
|
conf.schema_registry_url = 'http://your.schema-registry.local'
|
88
|
+
|
89
|
+
# The Sidekiq job queue to use for consuming jobs
|
90
|
+
config.consumer_job_queue = 'default'
|
83
91
|
end
|
84
92
|
```
|
85
93
|
|
@@ -98,6 +106,7 @@ available configuration options:
|
|
98
106
|
* **KAFKA_CLIENT_ID**: The Apache Kafka client identifier, falls back the the local application name.
|
99
107
|
* **KAFKA_BROKERS**: A comma separated list of Apache Kafka brokers for cluster discovery (Plaintext, no-auth/no-SSL only for now) (eg. `kafka://your.domain:9092,kafka..`)
|
100
108
|
* **KAFKA_SCHEMA_REGISTRY_URL**: The Confluent Schema Registry API URL to use for schema registrations.
|
109
|
+
* **KAFKA_SIDEKIQ_JOB_QUEUE**: The Sidekiq job queue to use for consuming jobs. Falls back to `default`.
|
101
110
|
|
102
111
|
### Conventions
|
103
112
|
|
@@ -279,6 +288,138 @@ Rimless.message(data: user, schema: :user_v1,
|
|
279
288
|
Rimless.async_raw_message(data: encoded, topic: :users)
|
280
289
|
```
|
281
290
|
|
291
|
+
### Consuming messages
|
292
|
+
|
293
|
+
The rimless gem makes it super easy to build consumer logic right into your
|
294
|
+
(Rails, standalone) application by utilizing the [Karafka
|
295
|
+
framework](https://github.com/karafka/karafka) under the hood. When you have
|
296
|
+
the rimless gem already installed you are ready to rumble to setup your
|
297
|
+
application to consume Apache Kafka messages. Just run the `$ rake
|
298
|
+
rimless:install` and all the consuming setup is done for you.
|
299
|
+
|
300
|
+
Afterwards you find the `karafka.rb` file at the root of your project together
|
301
|
+
with an example consumer (including specs). The default configuration follows
|
302
|
+
the base conventions and ships some opinions on the architecture. The
|
303
|
+
architecture looks like this:
|
304
|
+
|
305
|
+
```
|
306
|
+
+----[Apache Kafka]
|
307
|
+
|
|
308
|
+
fetch message batches
|
309
|
+
|
|
310
|
+
v
|
311
|
+
+-----------------------------+
|
312
|
+
| Karafka/Rimless Consumer | +--------------------------------------+
|
313
|
+
| Shares a single consumer |--->| Sidekiq |
|
314
|
+
| group, multiple processes | | Runs the consumer class (children |
|
315
|
+
+-----------------------------+ | of Rimless::BaseConsumer) for each |
|
316
|
+
| message (Rimless::ConsumerJob), |
|
317
|
+
| one message per job |
|
318
|
+
+--------------------------------------+
|
319
|
+
```
|
320
|
+
|
321
|
+
This architecture allows the consumer process to run mostly non-blocking and
|
322
|
+
the messages can be processed concurrently via Sidekiq. (including the error
|
323
|
+
handling and retrying)
|
324
|
+
|
325
|
+
#### Routing messages to consumers
|
326
|
+
|
327
|
+
The `karafka.rb` file at the root of your project is dedicated to configure the
|
328
|
+
consumer process, including the routing table. The routing is as easy as it
|
329
|
+
gets by following this pattern: `topic => consumer`. Here comes a the full
|
330
|
+
examples:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
# Setup the topic-consumer routing table and boot the consumer application
|
334
|
+
Rimless.consumer.topics(
|
335
|
+
{ app: :your_app, name: :your_topic } => CustomConsumer
|
336
|
+
).boot!
|
337
|
+
```
|
338
|
+
|
339
|
+
The key side of the hash is anything which is understood by the `Rimless.topic`
|
340
|
+
method. With one addition: you can change `:name` to `:names` and pass an array
|
341
|
+
of strings or symbols to listen to multiple application topics with a single
|
342
|
+
configuration line.
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
Rimless.consumer.topics(
|
346
|
+
{ app: :your_app, names: %i[a b c] } => CustomConsumer
|
347
|
+
).boot!
|
348
|
+
|
349
|
+
# is identical to:
|
350
|
+
|
351
|
+
Rimless.consumer.topics(
|
352
|
+
{ app: :your_app, name: :a } => CustomConsumer,
|
353
|
+
{ app: :your_app, name: :b } => CustomConsumer,
|
354
|
+
{ app: :your_app, name: :c } => CustomConsumer
|
355
|
+
).boot!
|
356
|
+
```
|
357
|
+
|
358
|
+
#### Consuming event messages
|
359
|
+
|
360
|
+
By convention it makes sense to produce messages with various event types on a
|
361
|
+
single Apache Kafka topic. This is fine, they just must follow a single
|
362
|
+
constrain: each message must contain an `event`-named field at the Apache Avro
|
363
|
+
schema with a dedicated name. This allow to structure data at Kafka like this:
|
364
|
+
|
365
|
+
```
|
366
|
+
Topic: production.users-api.users
|
367
|
+
Events: user_created, user_updated, user_deleted
|
368
|
+
```
|
369
|
+
|
370
|
+
While respecting this convention your consumer classes will be super clean. See
|
371
|
+
the following example: (we keep the users api example)
|
372
|
+
|
373
|
+
```ruby
|
374
|
+
class UserApiConsumer < ApplicationConsumer
|
375
|
+
def user_created(schema_field1:, optional_schema_field2: nil)
|
376
|
+
# Do whatever you need when a user was created
|
377
|
+
end
|
378
|
+
end
|
379
|
+
```
|
380
|
+
|
381
|
+
Just name a method like the name of the event and specify all Apache Avro
|
382
|
+
schema fields of it, except the event field. The messages will be automatically
|
383
|
+
decoded with the help of the schema registry. All hashes/arrays ship deeply
|
384
|
+
symbolized keys for easy access.
|
385
|
+
|
386
|
+
**Heads up!** All messages with events which are not reflected by a method will
|
387
|
+
just be ignored.
|
388
|
+
|
389
|
+
See the automatically generated spec (`spec/consumers/custom_consumer_spec.rb`)
|
390
|
+
for an example on how to test this.
|
391
|
+
|
392
|
+
#### Listing consumer routes
|
393
|
+
|
394
|
+
The rimless gem ships a simple tool to view all your consumer routes and the
|
395
|
+
event messages it reacts on. Just run:
|
396
|
+
|
397
|
+
```shell
|
398
|
+
# Print all Apache Kafka consumer routes
|
399
|
+
$ rake rimless:routes
|
400
|
+
|
401
|
+
# Topic: users-api.users
|
402
|
+
# Consumer: UserApiConsumer
|
403
|
+
# Events: user_created
|
404
|
+
```
|
405
|
+
|
406
|
+
#### Starting the consumer process(es)
|
407
|
+
|
408
|
+
From system integration perspective you just need to start the consumer
|
409
|
+
processes and Sidekiq to get the thing going. Rimless allows you to start the
|
410
|
+
consumer with `$ rake rimless:consumer` or you can just use the [Karafka
|
411
|
+
binary](https://github.com/karafka/karafka/wiki/Fetching-messages) to start the
|
412
|
+
consumer (`$ bundle exec karafka server`). Both work identically.
|
413
|
+
|
414
|
+
When running inside a Rails application the consumer application initialization
|
415
|
+
is automatically done for Sidekiq. Otherwise you need to initialize the
|
416
|
+
consumer application manually with:
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
# Manual consumer application initialization
|
420
|
+
Sidekiq.configure_server { Rimless.consumer.initialize! }
|
421
|
+
```
|
422
|
+
|
282
423
|
### Encoding/Decoding messages
|
283
424
|
|
284
425
|
By convention we focus on the [Apache Avro](https://avro.apache.org/) data
|
data/gemfiles/rails_4.2.gemfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "activesupport", "~> 4.2.11"
|
6
|
+
gem "railties", "~> 4.2.11"
|
9
7
|
|
10
|
-
gemspec path:
|
8
|
+
gemspec path: "../"
|
data/gemfiles/rails_5.0.gemfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "activesupport", "~> 5.0.7"
|
6
|
+
gem "railties", "~> 5.0.7"
|
9
7
|
|
10
|
-
gemspec path:
|
8
|
+
gemspec path: "../"
|
data/gemfiles/rails_5.1.gemfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "activesupport", "~> 5.1.6"
|
6
|
+
gem "railties", "~> 5.1.6"
|
9
7
|
|
10
|
-
gemspec path:
|
8
|
+
gemspec path: "../"
|
data/gemfiles/rails_5.2.gemfile
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
# This file was generated by Appraisal
|
4
2
|
|
5
|
-
source
|
3
|
+
source "https://rubygems.org"
|
6
4
|
|
7
|
-
gem
|
8
|
-
gem
|
5
|
+
gem "activesupport", "~> 5.2.2"
|
6
|
+
gem "railties", "~> 5.2.2"
|
9
7
|
|
10
|
-
gemspec path:
|
8
|
+
gemspec path: "../"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
# The base consumer where all Apache Kafka messages will be processed. It
|
5
|
+
# comes with some simple conventions to keep the actual application code
|
6
|
+
# simple to use.
|
7
|
+
class BaseConsumer < ::Karafka::BaseConsumer
|
8
|
+
# A generic message consuming handler which resolves the message event name
|
9
|
+
# to an actual method. All message data (top-level keys) is passed down to
|
10
|
+
# the event method as symbol arguments.
|
11
|
+
def consume
|
12
|
+
# We ignore events we do not handle by definition
|
13
|
+
send(event, **arguments) if respond_to? event
|
14
|
+
end
|
15
|
+
|
16
|
+
# Prepare the message payload as event method arguments.
|
17
|
+
#
|
18
|
+
# @return [Hash{Symbol => Mixed}] the event method arguments
|
19
|
+
def arguments
|
20
|
+
params.payload.except(:event)
|
21
|
+
end
|
22
|
+
|
23
|
+
# A shortcut to fetch the event name from the Kafka message.
|
24
|
+
#
|
25
|
+
# @return [Symbol] the event name of the current message
|
26
|
+
def event
|
27
|
+
params.payload[:event].to_sym
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -53,5 +53,21 @@ module Rimless
|
|
53
53
|
ENV.fetch('KAFKA_SCHEMA_REGISTRY_URL',
|
54
54
|
'http://schema-registry.message-bus.local')
|
55
55
|
end
|
56
|
+
|
57
|
+
# The Sidekiq job queue to use for consuming jobs
|
58
|
+
config_accessor(:consumer_job_queue) do
|
59
|
+
ENV.fetch('KAFKA_SIDEKIQ_JOB_QUEUE', 'default').to_sym
|
60
|
+
end
|
61
|
+
|
62
|
+
# A custom writer for the consumer job queue name.
|
63
|
+
#
|
64
|
+
# @param val [String, Symbol] the new job queue name
|
65
|
+
def consumer_job_queue=(val)
|
66
|
+
config.consumer_job_queue = val.to_sym
|
67
|
+
# Refresh the consumer job queue
|
68
|
+
Rimless::ConsumerJob.sidekiq_options(
|
69
|
+
queue: Rimless.configuration.consumer_job_queue
|
70
|
+
)
|
71
|
+
end
|
56
72
|
end
|
57
73
|
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
# The global rimless Apache Kafka consumer application based on
|
5
|
+
# the Karafka framework.
|
6
|
+
#
|
7
|
+
# rubocop:disable Style/ClassVars because we just work as a singleton
|
8
|
+
class ConsumerApp < ::Karafka::App
|
9
|
+
# We track our own initialization with this class variable
|
10
|
+
@@rimless_initialized = false
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Initialize the Karafka framework and our global consumer application
|
14
|
+
# with all our conventions/opinions.
|
15
|
+
#
|
16
|
+
# @return [Rimless::ConsumerApp] our self for chaining
|
17
|
+
def initialize!
|
18
|
+
# When already initialized, skip it
|
19
|
+
return self if @@rimless_initialized
|
20
|
+
|
21
|
+
# Initialize all the parts one by one
|
22
|
+
initialize_rails!
|
23
|
+
initialize_monitors!
|
24
|
+
initialize_karafka!
|
25
|
+
initialize_logger!
|
26
|
+
initialize_code_reload!
|
27
|
+
|
28
|
+
# Load the custom Karafka boot file when it exists, it contains
|
29
|
+
# custom configurations and the topic/consumer routing table
|
30
|
+
require ::Karafka.boot_file if ::Karafka.boot_file.exist?
|
31
|
+
|
32
|
+
# Set our custom initialization process as completed to
|
33
|
+
# skip subsequent calls
|
34
|
+
@@rimless_initialized = true
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if Rails is available and not already initialized, then
|
39
|
+
# initialize it.
|
40
|
+
def initialize_rails!
|
41
|
+
rails_env = ::Karafka.root.join('config', 'environment.rb')
|
42
|
+
|
43
|
+
# Stop, when Rails is already initialized
|
44
|
+
return if defined? Rails
|
45
|
+
|
46
|
+
# Stop, when there is no Rails at all
|
47
|
+
return unless rails_env.exist?
|
48
|
+
|
49
|
+
ENV['RAILS_ENV'] ||= 'development'
|
50
|
+
ENV['KARAFKA_ENV'] = ENV['RAILS_ENV']
|
51
|
+
require rails_env
|
52
|
+
Rails.application.eager_load!
|
53
|
+
end
|
54
|
+
|
55
|
+
# We like to listen to instrumentation and logging events to allow our
|
56
|
+
# users to handle them like they need.
|
57
|
+
def initialize_monitors!
|
58
|
+
[
|
59
|
+
WaterDrop::Instrumentation::StdoutListener.new,
|
60
|
+
::Karafka::Instrumentation::StdoutListener.new,
|
61
|
+
::Karafka::Instrumentation::ProctitleListener.new
|
62
|
+
].each do |listener|
|
63
|
+
::Karafka.monitor.subscribe(listener)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Configure the pure basics on the Karafka application.
|
68
|
+
#
|
69
|
+
# rubocop:disable Metrics/MethodLength because of the various settings
|
70
|
+
def initialize_karafka!
|
71
|
+
setup do |config|
|
72
|
+
mapper = Rimless::Karafka::PassthroughMapper.new
|
73
|
+
config.consumer_mapper = config.topic_mapper = mapper
|
74
|
+
config.deserializer = Rimless::Karafka::AvroDeserializer.new
|
75
|
+
config.kafka.seed_brokers = Rimless.configuration.kafka_brokers
|
76
|
+
config.client_id = Rimless.configuration.client_id
|
77
|
+
config.logger = Rimless.logger
|
78
|
+
config.backend = :sidekiq
|
79
|
+
config.batch_fetching = true
|
80
|
+
config.batch_consuming = false
|
81
|
+
config.shutdown_timeout = 10
|
82
|
+
end
|
83
|
+
end
|
84
|
+
# rubocop:enable Metrics/MethodLength
|
85
|
+
|
86
|
+
# When we run in development mode, we want to write the logs
|
87
|
+
# to file and to stdout.
|
88
|
+
def initialize_logger!
|
89
|
+
return unless Rimless.env.development? && server?
|
90
|
+
|
91
|
+
STDOUT.sync = true
|
92
|
+
Rimless.logger.extend(ActiveSupport::Logger.broadcast(
|
93
|
+
ActiveSupport::Logger.new($stdout)
|
94
|
+
))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Perform code hot-reloading when we are in Rails and in development
|
98
|
+
# mode.
|
99
|
+
def initialize_code_reload!
|
100
|
+
return unless defined?(Rails) && Rails.env.development?
|
101
|
+
|
102
|
+
::Karafka.monitor.subscribe(::Karafka::CodeReloader.new(
|
103
|
+
*Rails.application.reloaders
|
104
|
+
))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Allows the user to re-configure the Karafka application if this is
|
108
|
+
# needed. (eg. to set some ruby-kafka driver default settings, etc)
|
109
|
+
#
|
110
|
+
# @return [Rimless::ConsumerApp] our self for chaining
|
111
|
+
def configure(&block)
|
112
|
+
setup(&block)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# Configure the topics-consumer routing table in a lean way.
|
117
|
+
#
|
118
|
+
# Examples:
|
119
|
+
#
|
120
|
+
# topics({ app: :test_app, name: :admins } => YourConsumer)
|
121
|
+
# topics({ app: :test_app, names: %i[users admins] } => YourConsumer)
|
122
|
+
#
|
123
|
+
# @param topics [Hash{Hash => Class}] the topic to consumer mapping
|
124
|
+
#
|
125
|
+
# rubocop:disable Metrics/MethodLength because of the Karafka DSL
|
126
|
+
def topics(topics)
|
127
|
+
consumer_groups.draw do
|
128
|
+
consumer_group Rimless.configuration.client_id do
|
129
|
+
topics.each do |topic_parts, dest_consumer|
|
130
|
+
Rimless.consumer.topic_names(topic_parts).each do |topic_name|
|
131
|
+
topic(topic_name) do
|
132
|
+
consumer dest_consumer
|
133
|
+
worker Rimless::ConsumerJob
|
134
|
+
interchanger Rimless::Karafka::Base64Interchanger
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
self
|
142
|
+
end
|
143
|
+
# rubocop:enable Metrics/MethodLength
|
144
|
+
|
145
|
+
# Build the conventional Apache Kafka topic names from the given parts.
|
146
|
+
# This allows various forms like single strings/symbols and a hash in the
|
147
|
+
# form of +{ app: [String, Symbol], name: [String, Symbol], names:
|
148
|
+
# [Array<String, Symbol>] }+. This allows the maximum of flexibility.
|
149
|
+
#
|
150
|
+
# @param parts [String, Symbol, Hash{Symbol => Mixed}] the topic
|
151
|
+
# name parts
|
152
|
+
# @return [Array<String>] the full topic names
|
153
|
+
def topic_names(parts)
|
154
|
+
# We have a single app, but multiple names so we handle them
|
155
|
+
if parts.is_a?(Hash) && parts.key?(:names)
|
156
|
+
return parts[:names].map do |name|
|
157
|
+
Rimless.topic(parts.merge(name: name))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# We cannot handle the given input
|
162
|
+
[Rimless.topic(parts)]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check if we run as the Karafka server (consumer) process or not.
|
166
|
+
#
|
167
|
+
# @return [Boolean] whenever we run as the Karafka server or not
|
168
|
+
def server?
|
169
|
+
$PROGRAM_NAME.end_with?('karafka') && ARGV.include?('server')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
# rubocop:enable Style/ClassVars
|
174
|
+
|
175
|
+
# A rimless top-level concern which adds lean access to
|
176
|
+
# the consumer application.
|
177
|
+
module Consumer
|
178
|
+
extend ActiveSupport::Concern
|
179
|
+
|
180
|
+
class_methods do
|
181
|
+
# A simple shortcut to fetch the Karafka consumer application.
|
182
|
+
#
|
183
|
+
# @return [Rimless::ConsumerApp] the Karafka consumer application class
|
184
|
+
def consumer
|
185
|
+
ConsumerApp.initialize!
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
# The base consumer job where each message is processed asynchronous via
|
5
|
+
# Sidekiq. We need to inherit the Karafka base worker class into a custom
|
6
|
+
# one, otherwise it fails.
|
7
|
+
class ConsumerJob < ::Karafka::BaseWorker
|
8
|
+
sidekiq_options queue: Rimless.configuration.consumer_job_queue
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
module Karafka
|
5
|
+
# A custom Apache Avro compatible message deserializer.
|
6
|
+
class AvroDeserializer
|
7
|
+
# Deserialize an Apache Avro encoded Apache Kafka message.
|
8
|
+
#
|
9
|
+
# @param message [String] the binary blob to deserialize
|
10
|
+
# @return [Hash{Symbol => Mixed}] the deserialized Apache Avro message
|
11
|
+
def call(message)
|
12
|
+
# We use sparsed hashes inside of Apache Avro messages for schema-less
|
13
|
+
# blobs of data, such as loosely structured metadata blobs. Thats a
|
14
|
+
# somewhat bad idea on strictly typed and defined messages, but their
|
15
|
+
# occurence should be rare.
|
16
|
+
Rimless
|
17
|
+
.decode(message.payload)
|
18
|
+
.yield_self { |data| Sparsify(data, sparse_array: true) }
|
19
|
+
.yield_self { |data| data.transform_keys { |key| key.delete('\\') } }
|
20
|
+
.yield_self { |data| Unsparsify(data, sparse_array: true) }
|
21
|
+
.deep_symbolize_keys
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
module Karafka
|
5
|
+
# Allow the +karafka-sidekiq-backend+ gem to transfer binary Apache Kafka
|
6
|
+
# messages to the actual Sidekiq job.
|
7
|
+
#
|
8
|
+
# rubocop:disable Security/MarshalLoad because we encode/decode the
|
9
|
+
# messages in our own controlled context
|
10
|
+
class Base64Interchanger
|
11
|
+
# Encode a binary Apache Kafka message(s) so they can be passed to the
|
12
|
+
# Sidekiq +Rimless::ConsumerJob+.
|
13
|
+
#
|
14
|
+
# @param params_batch [Mixed] the raw message(s) to encode
|
15
|
+
# @return [String] the marshaled+base64 encoded data
|
16
|
+
def self.encode(params_batch)
|
17
|
+
Base64.encode64(Marshal.dump(params_batch.to_a))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Decode the binary Apache Kafka message(s) so they can be processed by
|
21
|
+
# the Sidekiq +Rimless::ConsumerJob+.
|
22
|
+
#
|
23
|
+
# @param params_string [String] the marshaled+base64 encoded data
|
24
|
+
# @return [Mixed] the unmarshaled+base64 decoded data
|
25
|
+
def self.decode(params_string)
|
26
|
+
Marshal.load(Base64.decode64(params_string))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# rubocop:enable Security/MarshalLoad
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rimless
|
4
|
+
module Karafka
|
5
|
+
# The Karafka framework makes some assumptions about the consumer group and
|
6
|
+
# topic names. We have our own opinions/conventions, so we just pass them
|
7
|
+
# through unmodified.
|
8
|
+
class PassthroughMapper
|
9
|
+
# We do not want to modify the given consumer group name, so we
|
10
|
+
# pass it through.
|
11
|
+
#
|
12
|
+
# @param raw_consumer_group_name [String, Symbol] the original
|
13
|
+
# consumer group name
|
14
|
+
# @return [String, Symbol] the original consumer group name
|
15
|
+
def call(raw_consumer_group_name)
|
16
|
+
raw_consumer_group_name
|
17
|
+
end
|
18
|
+
|
19
|
+
# We do not want to modify the given topic name, so we pass it through.
|
20
|
+
#
|
21
|
+
# @param topic [String, Symbol] the original topic name
|
22
|
+
# @return [String, Symbol] the original topic name
|
23
|
+
def incoming(topic)
|
24
|
+
topic
|
25
|
+
end
|
26
|
+
alias outgoing incoming
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/rimless/railtie.rb
CHANGED
@@ -20,6 +20,18 @@ module Rimless
|
|
20
20
|
config.after_initialize do
|
21
21
|
# Reconfigure our dependencies
|
22
22
|
Rimless.configure_dependencies
|
23
|
+
|
24
|
+
# Load the Karafka application inside the Sidekiq server application
|
25
|
+
if defined? Sidekiq
|
26
|
+
Sidekiq.configure_server do
|
27
|
+
Rimless.consumer.initialize!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Load all our Rake tasks if we're supposed to do
|
33
|
+
rake_tasks do
|
34
|
+
Dir[File.join(__dir__, 'tasks', '*.rake')].each { |file| load file }
|
23
35
|
end
|
24
36
|
end
|
25
37
|
end
|
@@ -13,6 +13,31 @@ module Rimless
|
|
13
13
|
def avro_parse(data, **opts)
|
14
14
|
Rimless.avro_decode(data, **opts)
|
15
15
|
end
|
16
|
+
|
17
|
+
# A simple helper to fake a deserialized Apache Kafka message for
|
18
|
+
# consuming.
|
19
|
+
#
|
20
|
+
# @param payload [Hash{Symbol => Mixed}] the message payload
|
21
|
+
# @param topic [String, Hash{Symbol => Mixed}] the actual message
|
22
|
+
# topic (full as string, or parts via hash)
|
23
|
+
# @return [OpenStruct] the fake deserialized Kafka message
|
24
|
+
#
|
25
|
+
# rubocop:disable Metrics/MethodLength because of the various properties
|
26
|
+
def kafka_message(topic: nil, headers: {}, **payload)
|
27
|
+
OpenStruct.new(
|
28
|
+
topic: Rimless.topic(topic),
|
29
|
+
headers: headers,
|
30
|
+
payload: payload,
|
31
|
+
is_control_record: false,
|
32
|
+
key: nil,
|
33
|
+
offset: 206,
|
34
|
+
partition: 0,
|
35
|
+
create_time: Time.current,
|
36
|
+
receive_time: Time.current,
|
37
|
+
deserialized: true
|
38
|
+
)
|
39
|
+
end
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
16
41
|
end
|
17
42
|
end
|
18
43
|
end
|
data/lib/rimless/rspec.rb
CHANGED
@@ -6,6 +6,7 @@ require 'avro_turf/test/fake_confluent_schema_registry_server'
|
|
6
6
|
require 'rimless'
|
7
7
|
require 'rimless/rspec/helpers'
|
8
8
|
require 'rimless/rspec/matchers'
|
9
|
+
require 'karafka/testing/rspec/helpers'
|
9
10
|
|
10
11
|
# RSpec 1.x and 2.x compatibility
|
11
12
|
#
|
@@ -13,10 +14,17 @@ require 'rimless/rspec/matchers'
|
|
13
14
|
raise 'No RSPEC_CONFIGURER is defined, webmock is missing?' \
|
14
15
|
unless defined?(RSPEC_CONFIGURER)
|
15
16
|
|
17
|
+
# rubocop:disable Metrics/BlockLength because we have to configure
|
18
|
+
# RSpec properly
|
16
19
|
RSPEC_CONFIGURER.configure do |config|
|
17
20
|
config.include Rimless::RSpec::Helpers
|
18
21
|
config.include Rimless::RSpec::Matchers
|
19
22
|
|
23
|
+
# Set the custom +consumer+ type for consumer spec files
|
24
|
+
config.define_derived_metadata(file_path: %r{/spec/consumers/}) do |meta|
|
25
|
+
meta[:type] = :consumer
|
26
|
+
end
|
27
|
+
|
20
28
|
# Take care of the initial test configuration.
|
21
29
|
config.before(:suite) do
|
22
30
|
# This allows parallel test execution without race conditions on the
|
@@ -37,7 +45,7 @@ RSPEC_CONFIGURER.configure do |config|
|
|
37
45
|
# the help of the faked (inlined) Schema Registry server. This allows us to
|
38
46
|
# perform the actual Apache Avro message encoding/decoding without the need
|
39
47
|
# to have a Schema Registry up and running.
|
40
|
-
config.before(:each) do
|
48
|
+
config.before(:each) do |example|
|
41
49
|
# Get the Excon connection from the AvroTurf instance
|
42
50
|
connection = Rimless.avro.instance_variable_get(:@registry)
|
43
51
|
.instance_variable_get(:@upstream)
|
@@ -55,5 +63,10 @@ RSPEC_CONFIGURER.configure do |config|
|
|
55
63
|
|
56
64
|
# Reconfigure the Rimless AvroTurf instance
|
57
65
|
Rimless.configure_avro_turf
|
66
|
+
|
67
|
+
# When the example type is a Kafka consumer, we must initialize
|
68
|
+
# the Karafka framework first.
|
69
|
+
Rimless.consumer.initialize! if example.metadata[:type] == :consumer
|
58
70
|
end
|
59
71
|
end
|
72
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :rimless do
|
4
|
+
desc 'Start the Apache Kafka consumer'
|
5
|
+
task :consumer do
|
6
|
+
system 'bundle exec karafka server'
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Print all the consumer routes'
|
10
|
+
task routes: :environment do
|
11
|
+
require 'rimless'
|
12
|
+
|
13
|
+
Rimless.consumer.consumer_groups.each do |consumer_group|
|
14
|
+
consumer_group.topics.each do |topic|
|
15
|
+
name = topic.name.split('.')[1..-1].join('.')
|
16
|
+
|
17
|
+
puts "# Topic: #{name}"
|
18
|
+
puts "# Consumer: #{topic.consumer}"
|
19
|
+
|
20
|
+
base = topic.consumer.superclass.new(topic).methods
|
21
|
+
events = topic.consumer.new(topic).methods - base
|
22
|
+
|
23
|
+
puts "# Events: #{events.join(', ')}"
|
24
|
+
puts
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :rimless do
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
# Install a template file to the project.
|
7
|
+
#
|
8
|
+
# @param src [String] the template source file name
|
9
|
+
# @param dest [Array<String>] the relative destination parts
|
10
|
+
def install_template(src, *dest)
|
11
|
+
src = File.join(__dir__, 'templates', src)
|
12
|
+
dest = File.join(Dir.pwd, *dest, File.basename(src))
|
13
|
+
|
14
|
+
return puts "# [Skip] #{dest}" if File.exist? dest
|
15
|
+
|
16
|
+
puts "# [Install] #{dest}"
|
17
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
18
|
+
FileUtils.copy(src, dest)
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Install the Rimless consumer components'
|
22
|
+
task :install do
|
23
|
+
install_template('karafka.rb')
|
24
|
+
install_template('application_consumer.rb', 'app', 'consumers')
|
25
|
+
install_template('custom_consumer.rb', 'app', 'consumers')
|
26
|
+
install_template('custom_consumer_spec.rb', 'spec', 'consumers')
|
27
|
+
|
28
|
+
puts <<~OUTPUT
|
29
|
+
#
|
30
|
+
# Installation done.
|
31
|
+
#
|
32
|
+
# You can now configure your routes at the +karafka.rb+ file at
|
33
|
+
# your project root. And list all routes with +rake rimless:routes+.
|
34
|
+
OUTPUT
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if defined?(Rails) && !Rails.env.production?
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
task :stats do
|
7
|
+
require 'rails/code_statistics'
|
8
|
+
|
9
|
+
[
|
10
|
+
[:unshift, 'Consumer', 'app/consumers']
|
11
|
+
].each do |method, type, dir|
|
12
|
+
::STATS_DIRECTORIES.send(method, [type, dir])
|
13
|
+
::CodeStatistics::TEST_TYPES << type if type.include? 'specs'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A dedicated consumer to handle event-messages from your producing application.
|
4
|
+
# Just write a method with the name of an event and it is called directly with
|
5
|
+
# all the event data as parameters.
|
6
|
+
class CustomConsumer < ApplicationConsumer
|
7
|
+
# Handle +custom_event+ event messages.
|
8
|
+
def custom_event(property1:, property2: nil)
|
9
|
+
# Do whatever you need to do
|
10
|
+
[property1, property2]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe CustomConsumer do
|
6
|
+
let(:topic) { Rimless.topic(app: :your_app, name: :your_topic) }
|
7
|
+
let(:instance) { karafka_consumer_for(topic) }
|
8
|
+
let(:action) { instance.consume }
|
9
|
+
let(:params) { kafka_message(topic: topic, **payload) }
|
10
|
+
|
11
|
+
before { allow(instance).to receive(:params).and_return(params) }
|
12
|
+
|
13
|
+
context 'with custom_event message' do
|
14
|
+
let(:payload) do
|
15
|
+
{ event: 'custom_event', property1: 'test', property2: nil }
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the payload properties' do
|
19
|
+
expect(action).to be_eql(['test', nil])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rimless'
|
4
|
+
|
5
|
+
# Setup the topic-consumer routing table and boot the consumer application
|
6
|
+
Rimless.consumer.topics(
|
7
|
+
{ app: :your_app, name: :your_topic } => CustomConsumer
|
8
|
+
).boot!
|
9
|
+
|
10
|
+
# Configure Karafka/ruby-kafka settings
|
11
|
+
# Rimless.consumer.configure do |config|
|
12
|
+
# # See https://github.com/karafka/karafka/wiki/Configuration
|
13
|
+
# # config.kafka.start_from_beginning = false
|
14
|
+
# end
|
15
|
+
|
16
|
+
# We want a less verbose logging on development
|
17
|
+
# Rimless.logger.level = Logger::INFO if Rails.env.development?
|
18
|
+
|
19
|
+
# Use a different Sidekiq queue for the consumer jobs
|
20
|
+
# Rimless.configuration.consumer_job_queue = :messages
|
data/lib/rimless/version.rb
CHANGED
data/lib/rimless.rb
CHANGED
@@ -11,6 +11,8 @@ require 'active_support/core_ext/hash'
|
|
11
11
|
require 'active_support/core_ext/string'
|
12
12
|
require 'waterdrop'
|
13
13
|
require 'avro_turf/messaging'
|
14
|
+
require 'karafka'
|
15
|
+
require 'karafka-sidekiq-backend'
|
14
16
|
require 'sparsify'
|
15
17
|
require 'erb'
|
16
18
|
require 'pp'
|
@@ -24,6 +26,16 @@ module Rimless
|
|
24
26
|
autoload :AvroUtils, 'rimless/avro_utils'
|
25
27
|
autoload :KafkaHelpers, 'rimless/kafka_helpers'
|
26
28
|
autoload :Dependencies, 'rimless/dependencies'
|
29
|
+
autoload :BaseConsumer, 'rimless/base_consumer'
|
30
|
+
autoload :Consumer, 'rimless/consumer'
|
31
|
+
autoload :ConsumerJob, 'rimless/consumer_job'
|
32
|
+
|
33
|
+
# All Karafka-framework related components
|
34
|
+
module Karafka
|
35
|
+
autoload :Base64Interchanger, 'rimless/karafka/base64_interchanger'
|
36
|
+
autoload :PassthroughMapper, 'rimless/karafka/passthrough_mapper'
|
37
|
+
autoload :AvroDeserializer, 'rimless/karafka/avro_deserializer'
|
38
|
+
end
|
27
39
|
|
28
40
|
# Load standalone code
|
29
41
|
require 'rimless/version'
|
@@ -34,4 +46,5 @@ module Rimless
|
|
34
46
|
include Rimless::AvroHelpers
|
35
47
|
include Rimless::KafkaHelpers
|
36
48
|
include Rimless::Dependencies
|
49
|
+
include Rimless::Consumer
|
37
50
|
end
|
data/log/development.log
ADDED
File without changes
|
data/rimless.gemspec
CHANGED
@@ -25,6 +25,9 @@ Gem::Specification.new do |spec|
|
|
25
25
|
|
26
26
|
spec.add_runtime_dependency 'activesupport', '>= 4.2.0'
|
27
27
|
spec.add_runtime_dependency 'avro_turf', '~> 0.11.0'
|
28
|
+
spec.add_runtime_dependency 'karafka', '~> 1.3'
|
29
|
+
spec.add_runtime_dependency 'karafka-sidekiq-backend', '~> 1.3'
|
30
|
+
spec.add_runtime_dependency 'karafka-testing', '~> 1.3'
|
28
31
|
spec.add_runtime_dependency 'sinatra'
|
29
32
|
spec.add_runtime_dependency 'sparsify', '~> 1.1'
|
30
33
|
spec.add_runtime_dependency 'waterdrop', '~> 1.2'
|
@@ -34,7 +37,7 @@ Gem::Specification.new do |spec|
|
|
34
37
|
spec.add_development_dependency 'bundler', '>= 1.16', '< 3'
|
35
38
|
spec.add_development_dependency 'factory_bot', '~> 4.11'
|
36
39
|
spec.add_development_dependency 'railties', '>= 4.2.0'
|
37
|
-
spec.add_development_dependency 'rake', '~>
|
40
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
38
41
|
spec.add_development_dependency 'rdoc', '~> 6.1'
|
39
42
|
spec.add_development_dependency 'redcarpet', '~> 3.4'
|
40
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rimless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hermann Mayer
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -38,6 +38,48 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.11.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: karafka
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: karafka-sidekiq-backend
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: karafka-testing
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
41
83
|
- !ruby/object:Gem::Dependency
|
42
84
|
name: sinatra
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -162,14 +204,14 @@ dependencies:
|
|
162
204
|
requirements:
|
163
205
|
- - "~>"
|
164
206
|
- !ruby/object:Gem::Version
|
165
|
-
version: '
|
207
|
+
version: '13.0'
|
166
208
|
type: :development
|
167
209
|
prerelease: false
|
168
210
|
version_requirements: !ruby/object:Gem::Requirement
|
169
211
|
requirements:
|
170
212
|
- - "~>"
|
171
213
|
- !ruby/object:Gem::Version
|
172
|
-
version: '
|
214
|
+
version: '13.0'
|
173
215
|
- !ruby/object:Gem::Dependency
|
174
216
|
name: rdoc
|
175
217
|
requirement: !ruby/object:Gem::Requirement
|
@@ -345,18 +387,33 @@ files:
|
|
345
387
|
- gemfiles/rails_5.0.gemfile
|
346
388
|
- gemfiles/rails_5.1.gemfile
|
347
389
|
- gemfiles/rails_5.2.gemfile
|
390
|
+
- gemfiles/rails_6.0.gemfile
|
348
391
|
- lib/rimless.rb
|
349
392
|
- lib/rimless/avro_helpers.rb
|
350
393
|
- lib/rimless/avro_utils.rb
|
394
|
+
- lib/rimless/base_consumer.rb
|
351
395
|
- lib/rimless/configuration.rb
|
352
396
|
- lib/rimless/configuration_handling.rb
|
397
|
+
- lib/rimless/consumer.rb
|
398
|
+
- lib/rimless/consumer_job.rb
|
353
399
|
- lib/rimless/dependencies.rb
|
354
400
|
- lib/rimless/kafka_helpers.rb
|
401
|
+
- lib/rimless/karafka/avro_deserializer.rb
|
402
|
+
- lib/rimless/karafka/base64_interchanger.rb
|
403
|
+
- lib/rimless/karafka/passthrough_mapper.rb
|
355
404
|
- lib/rimless/railtie.rb
|
356
405
|
- lib/rimless/rspec.rb
|
357
406
|
- lib/rimless/rspec/helpers.rb
|
358
407
|
- lib/rimless/rspec/matchers.rb
|
408
|
+
- lib/rimless/tasks/consumer.rake
|
409
|
+
- lib/rimless/tasks/generator.rake
|
410
|
+
- lib/rimless/tasks/stats.rake
|
411
|
+
- lib/rimless/tasks/templates/application_consumer.rb
|
412
|
+
- lib/rimless/tasks/templates/custom_consumer.rb
|
413
|
+
- lib/rimless/tasks/templates/custom_consumer_spec.rb
|
414
|
+
- lib/rimless/tasks/templates/karafka.rb
|
359
415
|
- lib/rimless/version.rb
|
416
|
+
- log/development.log
|
360
417
|
- rimless.gemspec
|
361
418
|
homepage: https://github.com/hausgold/rimless
|
362
419
|
licenses: []
|
@@ -376,7 +433,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
376
433
|
- !ruby/object:Gem::Version
|
377
434
|
version: '0'
|
378
435
|
requirements: []
|
379
|
-
rubygems_version: 3.
|
436
|
+
rubygems_version: 3.1.2
|
380
437
|
signing_key:
|
381
438
|
specification_version: 4
|
382
439
|
summary: A bundle of opinionated Apache Kafka / Confluent Schema Registry helpers.
|