phobos_temp_fork 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +13 -0
  3. data/.env +1 -0
  4. data/.gitignore +16 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +26 -0
  7. data/.rubocop_common.yml +29 -0
  8. data/.rubocop_todo.yml +7 -0
  9. data/.rubosync.yml +2 -0
  10. data/.ruby-version +1 -0
  11. data/.travis.yml +37 -0
  12. data/CHANGELOG.md +170 -0
  13. data/Dockerfile +14 -0
  14. data/Gemfile +8 -0
  15. data/LICENSE.txt +176 -0
  16. data/README.md +699 -0
  17. data/Rakefile +8 -0
  18. data/bin/console +19 -0
  19. data/bin/phobos +10 -0
  20. data/bin/setup +8 -0
  21. data/config/phobos.yml.example +137 -0
  22. data/docker-compose.yml +28 -0
  23. data/examples/handler_saving_events_database.rb +51 -0
  24. data/examples/handler_using_async_producer.rb +17 -0
  25. data/examples/publishing_messages_without_consumer.rb +82 -0
  26. data/lib/phobos/actions/process_batch.rb +35 -0
  27. data/lib/phobos/actions/process_batch_inline.rb +61 -0
  28. data/lib/phobos/actions/process_message.rb +49 -0
  29. data/lib/phobos/batch_handler.rb +23 -0
  30. data/lib/phobos/batch_message.rb +21 -0
  31. data/lib/phobos/cli.rb +69 -0
  32. data/lib/phobos/cli/runner.rb +48 -0
  33. data/lib/phobos/cli/start.rb +71 -0
  34. data/lib/phobos/constants.rb +33 -0
  35. data/lib/phobos/deep_struct.rb +39 -0
  36. data/lib/phobos/echo_handler.rb +11 -0
  37. data/lib/phobos/errors.rb +6 -0
  38. data/lib/phobos/executor.rb +103 -0
  39. data/lib/phobos/handler.rb +23 -0
  40. data/lib/phobos/instrumentation.rb +25 -0
  41. data/lib/phobos/listener.rb +192 -0
  42. data/lib/phobos/log.rb +23 -0
  43. data/lib/phobos/processor.rb +67 -0
  44. data/lib/phobos/producer.rb +171 -0
  45. data/lib/phobos/test.rb +3 -0
  46. data/lib/phobos/test/helper.rb +29 -0
  47. data/lib/phobos/version.rb +5 -0
  48. data/lib/phobos_temp_fork.rb +175 -0
  49. data/logo.png +0 -0
  50. data/phobos.gemspec +69 -0
  51. data/phobos_boot.rb +31 -0
  52. data/utils/create-topic.sh +13 -0
  53. metadata +308 -0
data/README.md ADDED
@@ -0,0 +1,699 @@
1
+ ![Phobos](https://raw.githubusercontent.com/klarna/phobos/master/logo.png)
2
+
3
+ [![Build Status](https://travis-ci.com/phobos/phobos.svg?branch=master)](https://travis-ci.com/phobos/phobos)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/e3814d747c91247b24c6/maintainability)](https://codeclimate.com/github/phobos/phobos/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/e3814d747c91247b24c6/test_coverage)](https://codeclimate.com/github/phobos/phobos/test_coverage)
6
+
7
+ # Phobos
8
+
9
+ Simplifying Kafka for Ruby apps!
10
+
11
+ Phobos is a micro framework and library for applications dealing with [Apache Kafka](http://kafka.apache.org/).
12
+
13
+ - It wraps common behaviors needed by consumers and producers in an easy and convenient API
14
+ - It uses [ruby-kafka](https://github.com/zendesk/ruby-kafka) as its Kafka client and core component
15
+ - It provides a CLI for starting and stopping a standalone application ready to be used for production purposes
16
+
17
+ Why Phobos? Why not `ruby-kafka` directly? Well, `ruby-kafka` is just a client. You still need to write a lot of code to manage proper consuming and producing of messages. You need to do proper message routing, error handling, retrying, backing off and maybe logging/instrumenting the message management process. You also need to worry about setting up a platform independent test environment that works on CI as well as any local machine, and even on your deployment pipeline. Finally, you also need to consider how to deploy your app and how to start it.
18
+
19
+ With Phobos by your side, all this becomes smooth sailing.
20
+
21
+ ## Table of Contents
22
+
23
+ 1. [Installation](#installation)
24
+ 1. [Usage](#usage)
25
+ 1. [Standalone apps](#usage-standalone-apps)
26
+ 1. [Consuming messages from Kafka](#usage-consuming-messages-from-kafka)
27
+ 1. [Producing messages to Kafka](#usage-producing-messages-to-kafka)
28
+ 1. [As library in another app](#usage-as-library)
29
+ 1. [Configuration file](#usage-configuration-file)
30
+ 1. [Instrumentation](#usage-instrumentation)
31
+ 1. [Plugins](#plugins)
32
+ 1. [Development](#development)
33
+ 1. [Test](#test)
34
+ 1. [Upgrade Notes](#upgrade-notes)
35
+
36
+ ## <a name="installation"></a> Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ ```ruby
41
+ gem 'phobos'
42
+ ```
43
+
44
+ And then execute:
45
+
46
+ ```sh
47
+ $ bundle
48
+ ```
49
+
50
+ Or install it yourself as:
51
+
52
+ ```sh
53
+ $ gem install phobos
54
+ ```
55
+
56
+ ## <a name="usage"></a> Usage
57
+
58
+ Phobos can be used in two ways: as a standalone application or to support Kafka features in your existing project - including Rails apps. It provides a CLI tool to run it.
59
+
60
+ ### <a name="usage-standalone-apps"></a> Standalone apps
61
+
62
+ Standalone apps have benefits such as individual deploys and smaller code bases. If consuming from Kafka is your version of microservices, Phobos can be of great help.
63
+
64
+ ### Setup
65
+
66
+ To create an application with Phobos you need two things:
67
+ * A configuration file (more details in the [Configuration file](#usage-configuration-file) section)
68
+ * A `phobos_boot.rb` (or the name of your choice) to properly load your code into Phobos executor
69
+
70
+ Use the Phobos CLI command __init__ to bootstrap your application. Example:
71
+
72
+ ```sh
73
+ # call this command inside your app folder
74
+ $ phobos init
75
+ create config/phobos.yml
76
+ create phobos_boot.rb
77
+ ```
78
+
79
+ `phobos.yml` is the configuration file and `phobos_boot.rb` is the place to load your code.
80
+
81
+ ### Consumers (listeners and handlers)
82
+
83
+ In Phobos apps __listeners__ are configured against Kafka - they are our consumers. A listener requires a __handler__ (a ruby class where you should process incoming messages), a Kafka __topic__, and a Kafka __group_id__. Consumer groups are used to coordinate the listeners across machines. We write the __handlers__ and Phobos makes sure to run them for us. An example of a handler is:
84
+
85
+ ```ruby
86
+ class MyHandler
87
+ include Phobos::Handler
88
+
89
+ def consume(payload, metadata)
90
+ # payload - This is the content of your Kafka message, Phobos does not attempt to
91
+ # parse this content, it is delivered raw to you
92
+ # metadata - A hash with useful information about this event, it contains: The event key,
93
+ # partition number, offset, retry_count, topic, group_id, and listener_id
94
+ end
95
+ end
96
+ ```
97
+
98
+ Writing a handler is all you need to allow Phobos to work - it will take care of execution, retries and concurrency.
99
+
100
+ To start Phobos the __start__ command is used, example:
101
+
102
+ ```sh
103
+ $ phobos start
104
+ [2016-08-13T17:29:59:218+0200Z] INFO -- Phobos : <Hash> {:message=>"Phobos configured", :env=>"development"}
105
+ ______ _ _
106
+ | ___ \ | | |
107
+ | |_/ / |__ ___ | |__ ___ ___
108
+ | __/| '_ \ / _ \| '_ \ / _ \/ __|
109
+ | | | | | | (_) | |_) | (_) \__ \
110
+ \_| |_| |_|\___/|_.__/ \___/|___/
111
+
112
+ phobos_boot.rb - find this file at ~/Projects/example/phobos_boot.rb
113
+
114
+ [2016-08-13T17:29:59:272+0200Z] INFO -- Phobos : <Hash> {:message=>"Listener started", :listener_id=>"6d5d2c", :group_id=>"test-1", :topic=>"test"}
115
+ ```
116
+
117
+ By default, the __start__ command will look for the configuration file at `config/phobos.yml` and it will load the file `phobos_boot.rb` if it exists. In the example above all example files generated by the __init__ command are used as is. It is possible to change both files, use `-c` for the configuration file and `-b` for the boot file. Example:
118
+
119
+ ```sh
120
+ $ phobos start -c /var/configs/my.yml -b /opt/apps/boot.rb
121
+ ```
122
+
123
+ You may also choose to configure phobos with a hash from within your boot file.
124
+ In this case, disable loading the config file with the `--skip-config` option:
125
+
126
+ ```sh
127
+ $ phobos start -b /opt/apps/boot.rb --skip-config
128
+ ```
129
+
130
+ ### <a name="usage-consuming-messages-from-kafka"></a> Consuming messages from Kafka
131
+
132
+ Messages from Kafka are consumed using __handlers__. You can use Phobos __executors__ or include it in your own project [as a library](#usage-as-library), but __handlers__ will always be used. To create a handler class, simply include the module `Phobos::Handler`. This module allows Phobos to manage the life cycle of your handler.
133
+
134
+ A handler is required to implement the method `#consume(payload, metadata)`.
135
+
136
+ Instances of your handler will be created for every message, so keep a constructor without arguments. If `consume` raises an exception, Phobos will retry the message indefinitely, applying the back off configuration presented in the configuration file. The `metadata` hash will contain a key called `retry_count` with the current number of retries for this message. To skip a message, simply return from `#consume`.
137
+
138
+ The `metadata` hash will also contain a key called `headers` with the headers of the consumed message.
139
+
140
+ When the listener starts, the class method `.start` will be called with the `kafka_client` used by the listener. Use this hook as a chance to setup necessary code for your handler. The class method `.stop` will be called during listener shutdown.
141
+
142
+ ```ruby
143
+ class MyHandler
144
+ include Phobos::Handler
145
+
146
+ def self.start(kafka_client)
147
+ # setup handler
148
+ end
149
+
150
+ def self.stop
151
+ # teardown
152
+ end
153
+
154
+ def consume(payload, metadata)
155
+ # consume or skip message
156
+ end
157
+ end
158
+ ```
159
+
160
+ It is also possible to control the execution of `#consume` with the method `#around_consume(payload, metadata)`. This method receives the payload and metadata, and then invokes `#consume` method by means of a block; example:
161
+
162
+ ```ruby
163
+ class MyHandler
164
+ include Phobos::Handler
165
+
166
+ def around_consume(payload, metadata)
167
+ Phobos.logger.info "consuming..."
168
+ output = yield payload, metadata
169
+ Phobos.logger.info "done, output: #{output}"
170
+ end
171
+
172
+ def consume(payload, metadata)
173
+ # consume or skip message
174
+ end
175
+ end
176
+ ```
177
+
178
+ Note: `around_consume` was previously defined as a class method. The current code supports both implementations, giving precendence to the class method, but future versions will no longer support `.around_consume`.
179
+
180
+ ```ruby
181
+ class MyHandler
182
+ include Phobos::Handler
183
+
184
+ def self.around_consume(payload, metadata)
185
+ Phobos.logger.info "consuming..."
186
+ output = yield payload, metadata
187
+ Phobos.logger.info "done, output: #{output}"
188
+ end
189
+
190
+ def consume(payload, metadata)
191
+ # consume or skip message
192
+ end
193
+ end
194
+ ```
195
+
196
+ Take a look at the examples folder for some ideas.
197
+
198
+ The hander life cycle can be illustrated as:
199
+
200
+ `.start` -> `#consume` -> `.stop`
201
+
202
+ or optionally,
203
+
204
+ `.start` -> `#around_consume` [ `#consume` ] -> `.stop`
205
+
206
+ #### Batch Consumption
207
+
208
+ In addition to the regular handler, Phobos provides a `BatchHandler`. The
209
+ basic ideas are identical, except that instead of being passed a single message
210
+ at a time, the `BatchHandler` is passed a batch of messages. All methods
211
+ follow the same pattern as the regular handler except that they each
212
+ end in `_batch` and are passed an array of `Phobos::BatchMessage`s instead
213
+ of a single payload.
214
+
215
+ To enable handling of batches on the consumer side, you must specify
216
+ a delivery method of `inline_batch` in [phobos.yml](config/phobos.yml.example),
217
+ and your handler must include `BatchHandler`. Using a delivery method of `batch`
218
+ assumes that you are still processing the messages one at a time and should
219
+ use `Handler`.
220
+
221
+ When using `inline_batch`, each instance of `Phobos::BatchMessage` will contain an
222
+ instance method `headers` with the headers for that message.
223
+
224
+ ```ruby
225
+ class MyBatchHandler
226
+ include Phobos::BatchHandler
227
+
228
+ def around_consume_batch(payloads, metadata)
229
+ payloads.each do |p|
230
+ p.payload[:timestamp] = Time.zone.now
231
+ end
232
+
233
+ yield payloads, metadata
234
+ end
235
+
236
+ def consume_batch(payloads, metadata)
237
+ payloads.each do |p|
238
+ logger.info("Got payload #{p.payload}, #{p.partition}, #{p.offset}, #{p.key}, #{p.payload[:timestamp]}")
239
+ end
240
+ end
241
+
242
+ end
243
+ ```
244
+
245
+ Note that retry logic will happen on the *batch* level in this case. If you are
246
+ processing messages individually and an error happens in the middle, Phobos's
247
+ retry logic will retry the entire batch. If this is not the behavior you want,
248
+ consider using `batch` instead of `inline_batch`.
249
+
250
+ ### <a name="usage-producing-messages-to-kafka"></a> Producing messages to Kafka
251
+
252
+ `ruby-kafka` provides several options for publishing messages, Phobos offers them through the module `Phobos::Producer`. It is possible to turn any ruby class into a producer (including your handlers), just include the producer module, example:
253
+
254
+ ```ruby
255
+ class MyProducer
256
+ include Phobos::Producer
257
+ end
258
+ ```
259
+
260
+ Phobos is designed for multi threading, thus the producer is always bound to the current thread. It is possible to publish messages from objects and classes, pick the option that suits your code better.
261
+ The producer module doesn't pollute your classes with a thousand methods, it includes a single method the class and in the instance level: `producer`.
262
+
263
+ ```ruby
264
+ my = MyProducer.new
265
+ my.producer.publish(topic: 'topic', payload: 'message-payload', key: 'partition and message key')
266
+
267
+ # The code above has the same effect of this code:
268
+ MyProducer.producer.publish(topic: 'topic', payload: 'message-payload', key: 'partition and message key')
269
+ ```
270
+
271
+ The signature for the `publish` method is as follows:
272
+
273
+ ```ruby
274
+ def publish(topic: topic, payload: payload, key: nil, partition_key: nil, headers: nil)
275
+ ```
276
+
277
+ When publishing a message with headers, the `headers` argument must be a hash:
278
+
279
+ ```ruby
280
+ my = MyProducer.new
281
+ my.producer.publish(topic: 'topic', payload: 'message-payload', key: 'partition and message key', headers: { header_1: 'value 1' })
282
+ ```
283
+
284
+ It is also possible to publish several messages at once:
285
+
286
+ ```ruby
287
+ MyProducer
288
+ .producer
289
+ .publish_list([
290
+ { topic: 'A', payload: 'message-1', key: '1' },
291
+ { topic: 'B', payload: 'message-2', key: '2' },
292
+ { topic: 'B', payload: 'message-3', key: '3', headers: { header_1: 'value 1', header_2: 'value 2' } }
293
+ ])
294
+ ```
295
+
296
+ There are two flavors of producers: __regular__ producers and __async__ producers.
297
+
298
+ Regular producers will deliver the messages synchronously and disconnect, it doesn't matter if you use `publish` or `publish_list`; by default, after the messages get delivered the producer will disconnect.
299
+
300
+ Async producers will accept your messages without blocking, use the methods `async_publish` and `async_publish_list` to use async producers.
301
+
302
+ An example of using handlers to publish messages:
303
+
304
+ ```ruby
305
+ class MyHandler
306
+ include Phobos::Handler
307
+ include Phobos::Producer
308
+
309
+ PUBLISH_TO = 'topic2'
310
+
311
+ def consume(payload, metadata)
312
+ producer.async_publish(topic: PUBLISH_TO, payload: {key: 'value'}.to_json)
313
+ end
314
+ end
315
+ ```
316
+
317
+ #### <a name="producer-config"></a> Note about configuring producers
318
+
319
+ Since the handler life cycle is managed by the Listener, it will make sure the producer is properly closed before it stops. When calling the producer outside a handler remember, you need to shutdown them manually before you close the application. Use the class method `async_producer_shutdown` to safely shutdown the producer.
320
+
321
+ Without configuring the Kafka client, the producers will create a new one when needed (once per thread). To disconnect from kafka call `kafka_client.close`.
322
+
323
+ ```ruby
324
+ # This method will block until everything is safely closed
325
+ MyProducer
326
+ .producer
327
+ .async_producer_shutdown
328
+
329
+ MyProducer
330
+ .producer
331
+ .kafka_client
332
+ .close
333
+ ```
334
+
335
+ ### <a name="persistent-connection"></a> Note about producers with persistent connections
336
+
337
+ By default, regular producers will automatically disconnect after every `publish` call. You can change this behavior (which reduces connection overhead, TLS etc - which increases speed significantly) by setting the `persistent_connections` config in `phobos.yml`. When set, regular producers behave identically to async producers and will also need to be shutdown manually using the `sync_producer_shutdown` method.
338
+
339
+ Since regular producers with persistent connections have open connections, you need to manually disconnect from Kafka when ending your producers' life cycle:
340
+
341
+ ```ruby
342
+ MyProducer
343
+ .producer
344
+ .sync_producer_shutdown
345
+ ```
346
+
347
+ ### <a name="usage-as-library"></a> Phobos as a library in an existing project
348
+
349
+ When running as a standalone service, Phobos sets up a `Listener` and `Executor` for you. When you use Phobos as a library in your own project, you need to set these components up yourself.
350
+
351
+ First, call the method `configure` with the path of your configuration file or with configuration settings hash.
352
+
353
+ ```ruby
354
+ Phobos.configure('config/phobos.yml')
355
+ ```
356
+ or
357
+ ```ruby
358
+ Phobos.configure(kafka: { client_id: 'phobos' }, logger: { file: 'log/phobos.log' })
359
+ ```
360
+
361
+ __Listener__ connects to Kafka and acts as your consumer. To create a listener you need a handler class, a topic, and a group id.
362
+
363
+ ```ruby
364
+ listener = Phobos::Listener.new(
365
+ handler: Phobos::EchoHandler,
366
+ group_id: 'group1',
367
+ topic: 'test'
368
+ )
369
+
370
+ # start method blocks
371
+ Thread.new { listener.start }
372
+
373
+ listener.id # 6d5d2c (all listeners have an id)
374
+ listener.stop # stop doesn't block
375
+ ```
376
+
377
+ This is all you need to consume from Kafka with back off retries.
378
+
379
+ An __executor__ is the supervisor of all listeners. It loads all listeners configured in `phobos.yml`. The executor keeps the listeners running and restarts them when needed.
380
+
381
+ ```ruby
382
+ executor = Phobos::Executor.new
383
+
384
+ # start doesn't block
385
+ executor.start
386
+
387
+ # stop will block until all listers are properly stopped
388
+ executor.stop
389
+ ```
390
+
391
+ When using Phobos __executors__ you don't care about how listeners are created, just provide the configuration under the `listeners` section in the configuration file and you are good to go.
392
+
393
+ ### <a name="usage-configuration-file"></a> Configuration file
394
+ The configuration file is organized in 6 sections. Take a look at the example file, [config/phobos.yml.example](https://github.com/klarna/phobos/blob/master/config/phobos.yml.example).
395
+
396
+ The file will be parsed through ERB so ERB syntax/file extension is supported beside the YML format.
397
+
398
+ __logger__ configures the logger for all Phobos components. It automatically
399
+ outputs to `STDOUT` and it saves the log in the configured file.
400
+
401
+ __kafka__ provides configurations for every `Kafka::Client` created over the application.
402
+ All [options supported by `ruby-kafka`][ruby-kafka-client] can be provided.
403
+
404
+ __producer__ provides configurations for all producers created over the application,
405
+ the options are the same for regular and async producers.
406
+ All [options supported by `ruby-kafka`][ruby-kafka-producer] can be provided.
407
+ If the __kafka__ key is present under __producer__, it is merged into the top-level __kafka__, allowing different connection configuration for producers.
408
+
409
+ __consumer__ provides configurations for all consumer groups created over the application.
410
+ All [options supported by `ruby-kafka`][ruby-kafka-consumer] can be provided.
411
+ If the __kafka__ key is present under __consumer__, it is merged into the top-level __kafka__, allowing different connection configuration for consumers.
412
+
413
+
414
+ __backoff__ Phobos provides automatic retries for your handlers. If an exception
415
+ is raised, the listener will retry following the back off configured here.
416
+ Backoff can also be configured per listener.
417
+
418
+ __listeners__ is the list of listeners configured. Each listener represents a consumer group.
419
+
420
+ [ruby-kafka-client]: http://www.rubydoc.info/gems/ruby-kafka/Kafka%2FClient%3Ainitialize
421
+ [ruby-kafka-consumer]: http://www.rubydoc.info/gems/ruby-kafka/Kafka%2FClient%3Aconsumer
422
+ [ruby-kafka-producer]: http://www.rubydoc.info/gems/ruby-kafka/Kafka%2FClient%3Aproducer
423
+
424
+ #### Additional listener configuration
425
+
426
+ In some cases it's useful to share _most_ of the configuration between
427
+ multiple phobos processes, but have each process run different listeners. In
428
+ that case, a separate yaml file can be created and loaded with the `-l` flag.
429
+ Example:
430
+
431
+ ```sh
432
+ $ phobos start -c /var/configs/my.yml -l /var/configs/additional_listeners.yml
433
+ ```
434
+
435
+ Note that the config file _must_ still specify a listeners section, though it
436
+ can be empty.
437
+
438
+ #### Custom configuration/logging
439
+
440
+ Phobos can be configured using a hash rather than the config file directly. This
441
+ can be useful if you want to do some pre-processing before sending the file
442
+ to Phobos. One particularly useful aspect is the ability to provide Phobos
443
+ with a custom logger, e.g. by reusing the Rails logger:
444
+
445
+ ```ruby
446
+ Phobos.configure(
447
+ custom_logger: Rails.logger,
448
+ custom_kafka_logger: Rails.logger
449
+ )
450
+ ```
451
+
452
+ If these keys are given, they will override the `logger` keys in the Phobos
453
+ config file.
454
+
455
+ ### <a name="usage-instrumentation"></a> Instrumentation
456
+
457
+ Some operations are instrumented using [Active Support Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html).
458
+
459
+ In order to receive notifications you can use the module `Phobos::Instrumentation`, example:
460
+
461
+ ```ruby
462
+ Phobos::Instrumentation.subscribe('listener.start') do |event|
463
+ puts(event.payload)
464
+ end
465
+ ```
466
+
467
+ `Phobos::Instrumentation` is a convenience module around `ActiveSupport::Notifications`, feel free to use it or not. All Phobos events are in the `phobos` namespace. `Phobos::Instrumentation` will always look at `phobos.` events.
468
+
469
+ #### Executor notifications
470
+ * `executor.retry_listener_error` is sent when the listener crashes and the executor wait for a restart. It includes the following payload:
471
+ * listener_id
472
+ * retry_count
473
+ * waiting_time
474
+ * exception_class
475
+ * exception_message
476
+ * backtrace
477
+ * `executor.stop` is sent when executor stops
478
+
479
+ #### Listener notifications
480
+ * `listener.start_handler` is sent when invoking `handler.start(kafka_client)`. It includes the following payload:
481
+ * listener_id
482
+ * group_id
483
+ * topic
484
+ * handler
485
+ * `listener.start` is sent when listener starts. It includes the following payload:
486
+ * listener_id
487
+ * group_id
488
+ * topic
489
+ * handler
490
+ * `listener.process_batch` is sent after process a batch. It includes the following payload:
491
+ * listener_id
492
+ * group_id
493
+ * topic
494
+ * handler
495
+ * batch_size
496
+ * partition
497
+ * offset_lag
498
+ * highwater_mark_offset
499
+ * `listener.process_message` is sent after processing a message. It includes the following payload:
500
+ * listener_id
501
+ * group_id
502
+ * topic
503
+ * handler
504
+ * key
505
+ * partition
506
+ * offset
507
+ * retry_count
508
+ * `listener.process_batch_inline` is sent after processing a batch with `batch_inline` mode. It includes the following payload:
509
+ * listener_id
510
+ * group_id
511
+ * topic
512
+ * handler
513
+ * batch_size
514
+ * partition
515
+ * offset_lag
516
+ * retry_count
517
+ * `listener.retry_handler_error` is sent after waiting for `handler#consume` retry. It includes the following payload:
518
+ * listener_id
519
+ * group_id
520
+ * topic
521
+ * handler
522
+ * key
523
+ * partition
524
+ * offset
525
+ * retry_count
526
+ * waiting_time
527
+ * exception_class
528
+ * exception_message
529
+ * backtrace
530
+ * `listener.retry_handler_error_batch` is sent after waiting for `handler#consume_batch` retry. It includes the following payload:
531
+ * listener_id
532
+ * group_id
533
+ * topic
534
+ * handler
535
+ * batch_size
536
+ * partition
537
+ * offset_lag
538
+ * retry_count
539
+ * waiting_time
540
+ * exception_class
541
+ * exception_message
542
+ * backtrace
543
+ * `listener.retry_aborted` is sent after waiting for a retry but the listener was stopped before the retry happened. It includes the following payload:
544
+ * listener_id
545
+ * group_id
546
+ * topic
547
+ * handler
548
+ * `listener.stopping` is sent when the listener receives signal to stop.
549
+ * listener_id
550
+ * group_id
551
+ * topic
552
+ * handler
553
+ * `listener.stop_handler` is sent after stopping the handler.
554
+ * listener_id
555
+ * group_id
556
+ * topic
557
+ * handler
558
+ * `listener.stop` is send after stopping the listener.
559
+ * listener_id
560
+ * group_id
561
+ * topic
562
+ * handler
563
+
564
+ ## <a name="plugins"></a> Plugins
565
+
566
+ List of gems that enhance Phobos:
567
+
568
+ * [Phobos DB Checkpoint](https://github.com/klarna/phobos_db_checkpoint) is drop in replacement to Phobos::Handler, extending it with the following features:
569
+ * Persists your Kafka events to an active record compatible database
570
+ * Ensures that your handler will consume messages only once
571
+ * Allows your system to quickly reprocess events in case of failures
572
+
573
+ * [Phobos Checkpoint UI](https://github.com/klarna/phobos_checkpoint_ui) gives your Phobos DB Checkpoint powered app a web gui with the features below. Maintaining a Kafka consumer app has never been smoother:
574
+ * Search events and inspect payload
575
+ * See failures and retry / delete them
576
+
577
+ * [Phobos Prometheus](https://github.com/phobos/phobos_prometheus) adds prometheus metrics to your phobos consumer.
578
+ * Measures total messages and batches processed
579
+ * Measures total duration needed to process each message (and batch)
580
+ * Adds `/metrics` endpoit to scrape data
581
+
582
+ ## <a name="development"></a> Development
583
+
584
+ After checking out the repo:
585
+ * make sure `docker` is installed and running (for windows and mac this also includes `docker-compose`).
586
+ * Linux: make sure `docker-compose` is installed and running.
587
+ * run `bin/setup` to install dependencies
588
+ * run `docker-compose up -d --force-recreate kafka zookeeper` to start the required kafka containers
589
+ * run tests to confirm no environmental issues
590
+ * wait a few seconds for kafka broker to get set up - `sleep 30`
591
+ * run `docker-compose run --rm test`
592
+ * make sure it reports `X examples, 0 failures`
593
+
594
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
595
+
596
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
597
+
598
+ ## <a name="test"></a> Test
599
+
600
+ Phobos exports a spec helper that can help you test your consumer. The Phobos lifecycle will conveniently be activated for you with minimal setup required.
601
+
602
+ * `process_message(handler:, payload:, metadata: {}, encoding: nil)` - Invokes your handler with payload and metadata, using a dummy listener (encoding and metadata are optional).
603
+
604
+ ```ruby
605
+ ### spec_helper.rb
606
+ require 'phobos/test/helper'
607
+ RSpec.configure do |config|
608
+ config.include Phobos::Test::Helper
609
+ config.before(:each) do
610
+ Phobos.configure(path_to_my_config_file)
611
+ end
612
+ end
613
+
614
+ ### Spec file
615
+ describe MyConsumer do
616
+ let(:payload) { 'foo' }
617
+ let(:metadata) { Hash(foo: 'bar') }
618
+
619
+ it 'consumes my message' do
620
+ expect_any_instance_of(described_class).to receive(:around_consume).with(payload, metadata).once.and_call_original
621
+ expect_any_instance_of(described_class).to receive(:consume).with(payload, metadata).once.and_call_original
622
+
623
+ process_message(handler: described_class, payload: payload, metadata: metadata)
624
+ end
625
+ end
626
+ ```
627
+
628
+ ## <a name="upgrade-notes"></a> Upgrade Notes
629
+
630
+ Version 2.0 removes deprecated ways of defining producers and consumers:
631
+ * The `before_consume` method has been removed. You can have this behavior in the first part of an `around_consume` method.
632
+ * `around_consume` is now only available as an instance method, and it must yield the values to pass to the `consume` method.
633
+ * `publish` and `async_publish` now only accept keyword arguments, not positional arguments.
634
+
635
+ Example pre-2.0:
636
+ ```ruby
637
+ class MyHandler
638
+ include Phobos::Handler
639
+
640
+ def before_consume(payload, metadata)
641
+ payload[:id] = 1
642
+ end
643
+
644
+ def self.around_consume(payload, metadata)
645
+ metadata[:key] = 5
646
+ yield
647
+ end
648
+ end
649
+ ```
650
+
651
+ In 2.0:
652
+ ```ruby
653
+ class MyHandler
654
+ include Phobos::Handler
655
+
656
+ def around_consume(payload, metadata)
657
+ new_payload = payload.dup
658
+ new_metadata = metadata.dup
659
+ new_payload[:id] = 1
660
+ new_metadata[:key] = 5
661
+ yield new_payload, new_metadata
662
+ end
663
+ end
664
+ ```
665
+
666
+ Producer, 1.9:
667
+ ```ruby
668
+ producer.publish('my-topic', { payload_value: 1}, 5, 3, {header_val: 5})
669
+ ```
670
+
671
+ Producer 2.0:
672
+ ```ruby
673
+ producer.publish(topic: 'my-topic', payload: { payload_value: 1}, key: 5,
674
+ partition_key: 3, headers: { header_val: 5})
675
+ ```
676
+
677
+ Version 1.8.2 introduced a new `persistent_connections` setting for regular producers. This reduces the number of connections used to produce messages and you should consider setting it to true. This does require a manual shutdown call - please see [Producers with persistent connections](#persistent-connection).
678
+
679
+ ## Contributing
680
+
681
+ Bug reports and pull requests are welcome on GitHub at https://github.com/klarna/phobos.
682
+
683
+ ## Linting
684
+
685
+ Phobos projects Rubocop to lint the code, and in addition all projects use [Rubocop Rules](https://github.com/klippx/rubocop_rules) to maintain a shared rubocop configuration. Updates to the shared configurations are done in [phobos/shared](https://github.com/phobos/shared) repo, where you can also find instructions on how to apply the new settings to the Phobos projects.
686
+
687
+ ## Acknowledgements
688
+
689
+ Thanks to Sebastian Norde for the awesome logo!
690
+
691
+ ## License
692
+
693
+ Copyright 2016 Klarna
694
+
695
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
696
+
697
+ You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
698
+
699
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.