hivent 1.0.1

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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +19 -0
  3. data/.gitignore +14 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +1063 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov.template +1 -0
  8. data/.travis.yml +23 -0
  9. data/.version +1 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +21 -0
  12. data/README.md +196 -0
  13. data/bin/hivent +5 -0
  14. data/hivent.gemspec +34 -0
  15. data/lib/hivent.rb +32 -0
  16. data/lib/hivent/abstract_signal.rb +63 -0
  17. data/lib/hivent/cli/consumer.rb +60 -0
  18. data/lib/hivent/cli/runner.rb +50 -0
  19. data/lib/hivent/cli/start_option_parser.rb +53 -0
  20. data/lib/hivent/config.rb +22 -0
  21. data/lib/hivent/config/options.rb +51 -0
  22. data/lib/hivent/emitter.rb +41 -0
  23. data/lib/hivent/life_cycle_event_handler.rb +41 -0
  24. data/lib/hivent/redis/consumer.rb +82 -0
  25. data/lib/hivent/redis/extensions.rb +26 -0
  26. data/lib/hivent/redis/lua/consumer.lua +179 -0
  27. data/lib/hivent/redis/lua/producer.lua +27 -0
  28. data/lib/hivent/redis/producer.rb +24 -0
  29. data/lib/hivent/redis/redis.rb +14 -0
  30. data/lib/hivent/redis/signal.rb +36 -0
  31. data/lib/hivent/rspec.rb +11 -0
  32. data/lib/hivent/signal.rb +14 -0
  33. data/lib/hivent/spec.rb +11 -0
  34. data/lib/hivent/spec/matchers.rb +14 -0
  35. data/lib/hivent/spec/matchers/emit.rb +116 -0
  36. data/lib/hivent/spec/signal.rb +60 -0
  37. data/lib/hivent/version.rb +6 -0
  38. data/spec/codeclimate_helper.rb +5 -0
  39. data/spec/fixtures/cli/bootstrap_consumers.rb +7 -0
  40. data/spec/fixtures/cli/life_cycle_event_test.rb +8 -0
  41. data/spec/hivent/abstract_signal_spec.rb +161 -0
  42. data/spec/hivent/cli/consumer_spec.rb +68 -0
  43. data/spec/hivent/cli/runner_spec.rb +75 -0
  44. data/spec/hivent/cli/start_option_parser_spec.rb +48 -0
  45. data/spec/hivent/life_cycle_event_handler_spec.rb +38 -0
  46. data/spec/hivent/redis/consumer_spec.rb +348 -0
  47. data/spec/hivent/redis/signal_spec.rb +155 -0
  48. data/spec/hivent_spec.rb +100 -0
  49. data/spec/spec/matchers/emit_spec.rb +66 -0
  50. data/spec/spec/signal_spec.rb +72 -0
  51. data/spec/spec_helper.rb +27 -0
  52. data/spec/support/matchers/exit_with_code.rb +28 -0
  53. data/spec/support/stdout_helpers.rb +25 -0
  54. metadata +267 -0
@@ -0,0 +1 @@
1
+ 2.3.1
@@ -0,0 +1 @@
1
+ SimpleCov.start 'test_frameworks'
@@ -0,0 +1,23 @@
1
+ sudo: false # http://docs.travis-ci.com/user/migrating-from-legacy/
2
+
3
+ services:
4
+ - redis-server
5
+
6
+ addons:
7
+ code_climate:
8
+ repo_token: fef398071a8b9a86caa653c05372f91be29cd3d31548bd6f9950cb1c4f324ff6
9
+
10
+ language: ruby
11
+
12
+ rvm:
13
+ - 2.3.1
14
+ - 2.2.5
15
+
16
+ env:
17
+ - >
18
+ REDIS_URL=redis://localhost:6379
19
+
20
+ cache: bundler
21
+
22
+ script:
23
+ - bundle exec rspec
@@ -0,0 +1 @@
1
+ 1.0.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ source "http://rubygems.org"
3
+
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Bruno Abrantes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,196 @@
1
+ # Hivent
2
+
3
+ An event stream implementation that aggregates facts about your application.
4
+
5
+ ## Configuration
6
+
7
+ ### Redis Backend
8
+
9
+ ```ruby
10
+ Hivent.configure do |config|
11
+ config.backend = :redis
12
+ config.endpoint = "redis://localhost:6379/0"
13
+ config.partition_count = 4
14
+ config.life_cycle_event_handler = MyHandler.new
15
+ config.client_id = "my_app_name"
16
+ end
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Receive
22
+
23
+ Receiving works on an instance of a `Signal`. For each event received, the given block will be executed once.
24
+ You may either specify a version to receive or decide to receive all events for that signal regardless of their version.
25
+
26
+ ```ruby
27
+ signal = Hivent::Signal.new("model_name:created")
28
+
29
+ # Handle all events for this signal
30
+ signal.receive do |event|
31
+ # Do something with the event
32
+ # event['payload'] contains the payload
33
+ # event['meta'] contains information about the event
34
+ end
35
+
36
+ # Handle version 2 events for this signal
37
+ signal.receive(version: 2) do |event|
38
+ # Do something with the event
39
+ # event['payload'] contains the payload
40
+ # event['meta'] contains information about the event
41
+ end
42
+ ```
43
+
44
+ #### Wildcard signals
45
+
46
+ You can receive all events as well by using the `*` wildcard. Partial wildcards (such as `my_event:*`) are not supported at this time.
47
+
48
+ ```ruby
49
+ signal = Hivent::Signal.new("*")
50
+
51
+ # Handle all events
52
+ signal.receive do |event|
53
+ # Do something with the event
54
+ # event['payload'] contains the payload
55
+ # event['meta'] contains information about the event
56
+ end
57
+ ```
58
+
59
+ #### Worker process
60
+
61
+ To receive events, a consumer process needs to be started using the provided CLI.
62
+
63
+ Start the consumer:
64
+
65
+ ```bash
66
+ bundle exec hivent start -r app/events.rb
67
+ ```
68
+
69
+ For more details on the available options see:
70
+
71
+ ```bash
72
+ bundle exec hivent --help
73
+ bundle exec hivent start --help
74
+ ```
75
+
76
+ ##### Daemonization
77
+ The library does not offer any options to daemonize or parallelize your consumers. You are encouraged to use other tools such as [Foreman](https://ddollar.github.io/foreman/) and [Upstart](http://upstart.ubuntu.com) to achieve this.
78
+
79
+ With these two tools, you can set up a `Procfile` for the consumer:
80
+
81
+ ```
82
+ consumer: bundle exec hivent start -r app/events.rb
83
+ ```
84
+
85
+ And then use Foreman's `export` feature to convert it to an upstart job:
86
+
87
+ ```bash
88
+ foreman export upstart -a myapp -m consumer=4 -u myuser /etc/init
89
+
90
+ service myapp start
91
+ ```
92
+
93
+ This will start 4 consumer processes running under the `myuser` user. The processes will be daemonized and monitored by upstart.
94
+ If your consumers need environment variables, Foreman can [pick them up from a `.env` file](https://ddollar.github.io/foreman/#ENVIRONMENT) placed next to your `Procfile`:
95
+
96
+ ```
97
+ APP_ENV=production
98
+ REDIS_URL=redis://something:6379
99
+ ```
100
+
101
+ ##### Callbacks for Life Cycle Events
102
+
103
+ To add error reporting or logging of consumed events you can configure an handler that is invoked by the consumer when certain lifecycle events occur.
104
+ To implement this handler create a class that inherits from [`Hivent::LifeCycleEventHandler`](lib/hivent/life_cycle_event_handler.rb) and overwrite one or more of it's methods.
105
+
106
+ ```ruby
107
+ class MyHandler < Hivent::LifeCycleEventHandler
108
+
109
+ def application_registered(client_id, events, partition_count)
110
+ # log info to logging service
111
+ end
112
+
113
+ def event_processing_succeeded(event_name, event_version, payload)
114
+ # log event processing
115
+ end
116
+
117
+ def event_processing_failed(exception, payload, raw_payload, dead_letter_queue_name)
118
+ # report to some exception notification service
119
+ end
120
+
121
+ end
122
+ ```
123
+
124
+ The handler needs to be configured in the gem's configuration block. The default handler ignores all life cycle events.
125
+
126
+ ### Emit
127
+
128
+ You can use any name to identify your signals.
129
+
130
+ All signals are versioned. The version has to be specified as the second parameter of `emit` and will be part of the events meta data.
131
+
132
+ ```ruby
133
+ Hivent::Signal.new("model_name:created").emit({ key: "value" }, version: 1)
134
+ # => Signal name is added as meta attribute "name"
135
+ ```
136
+
137
+ #### Meta Data
138
+
139
+ Each emitted event will automatically be enriched with meta data containing the correlation ID (`cid`), the `producer` of the event (the `client_id` provided in the configuration block) and the `created_at` timestamp.
140
+
141
+ The event name and version will be added to the events meta data.
142
+
143
+ ##### Correlation ID
144
+
145
+ To pass in a correlation ID (e.g. from a previously consumed message) use:
146
+
147
+ ```ruby
148
+ cid = event['meta']['cid']
149
+ Hivent::Signal.new("model_name:created").emit({ key: "value" }, version: 1, cid: cid)
150
+ ```
151
+
152
+ #### Keyed Messages
153
+
154
+ Sometimes it's required to pass a key alongside the message that is used to assign the message to a specific partition (which ensures order of events within this partition).
155
+
156
+ ```ruby
157
+ signal = Hivent::Signal.new("model_name:created")
158
+ signal.emit({ key: "value" }, key: "my_custom_key")
159
+ ```
160
+
161
+ ## Run the Tests
162
+
163
+ The test suite requires a running Redis server (default: `redis://localhost:6379/15`). To point to a different Redis pass in an environment variable when starting the tests.
164
+
165
+ ```ruby
166
+ REDIS_URL=redis://path_to_redis:port/database bundle exec rspec
167
+ ```
168
+
169
+ ## Test Helpers
170
+
171
+ To help you write awesome tests, an RSpec helper is provided. To use it, require 'hivent/rspec' before running your test suite:
172
+
173
+ ```ruby
174
+ # in spec_helper.rb
175
+
176
+ require 'hivent/rspec'
177
+ ```
178
+
179
+ ### Matchers
180
+
181
+ #### Emit
182
+
183
+ Test whether a signal has been emitted. Optionally, you can define a version.
184
+
185
+ ```ruby
186
+ expect { a_method }.to emit('a:signal')
187
+ expect { another_method }.not_to emit('another:signal')
188
+ expect { another_method }.to emit('a:signal', version: 2)
189
+ ```
190
+
191
+ You may also assert whether a signal was emitted with a given payload.
192
+ This matcher asserts that the signal's payload contains the given hash.
193
+
194
+ ```ruby
195
+ expect { subject }.to emit(:event_name).with({ foo: 'bar' })
196
+ ```
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require_relative "../lib/hivent/cli/runner"
4
+
5
+ Hivent::CLI::Runner.new(ARGV.dup).run
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'hivent/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "hivent"
9
+ spec.version = Hivent::VERSION
10
+ spec.authors = ["Bruno Abrantes"]
11
+ spec.email = ["bruno@brunoabrantes.com"]
12
+ spec.summary = "An event stream implementation that aggregates facts about your application"
13
+ spec.description = ""
14
+ spec.homepage = "https://github.com/inf0rmer/hivent-ruby"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "activesupport", "~> 5.0"
23
+ spec.add_dependency "retryable", "~> 2.0"
24
+ spec.add_dependency "redis", "~> 3.3"
25
+ spec.add_dependency "event_emitter", "~> 0.2"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.12"
28
+ spec.add_development_dependency "rspec", "~> 3.5"
29
+ spec.add_development_dependency "rspec-its", "~> 1.2"
30
+ spec.add_development_dependency "pry-byebug", "~> 3.4"
31
+ spec.add_development_dependency "simplecov", "~> 0.12"
32
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 0.6"
33
+ spec.add_development_dependency "rubocop", "~> 0.43"
34
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require "active_support"
3
+ require "active_support/core_ext"
4
+ require "retryable"
5
+ require "json"
6
+ require "event_emitter"
7
+
8
+ require "hivent/config"
9
+
10
+ require "hivent/signal"
11
+ require "hivent/abstract_signal"
12
+ require "hivent/emitter"
13
+
14
+ require "hivent/redis/redis"
15
+ require "hivent/redis/extensions"
16
+ require "hivent/redis/signal"
17
+ require "hivent/life_cycle_event_handler"
18
+ require "hivent/redis/consumer"
19
+
20
+ module Hivent
21
+
22
+ extend self
23
+
24
+ def configure
25
+ block_given? ? yield(Hivent::Config) : Hivent::Config
26
+ end
27
+
28
+ def self.emitter
29
+ @emitter ||= Emitter.new
30
+ end
31
+
32
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module Hivent
3
+
4
+ class AbstractSignal
5
+
6
+ attr_reader :name, :producer, :client_id
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ @producer = nil
11
+ @client_id = Hivent::Config.client_id
12
+ end
13
+
14
+ def emit(payload, version:, cid: nil, key: nil)
15
+ build_message(payload, cid, version).tap do |message|
16
+ send_message(message, partition_key(key, message), version)
17
+ end
18
+ end
19
+
20
+ def receive(version: nil, &block)
21
+ Hivent.emitter.on(event_name(version), &block)
22
+ Hivent.emitter.events << { name: name, version: version }
23
+ end
24
+
25
+ private
26
+
27
+ def partition_key(key, message)
28
+ key = key || message[:payload].to_json
29
+
30
+ Zlib.crc32(key)
31
+ end
32
+
33
+ def send_message(_message, _key, _version)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def build_message(payload, cid, version)
38
+ {
39
+ payload: payload,
40
+ meta: meta_data(cid, version)
41
+ }
42
+ end
43
+
44
+ def meta_data(cid, version)
45
+ {
46
+ event_uuid: SecureRandom.hex,
47
+ name: name,
48
+ version: version,
49
+ cid: (cid || SecureRandom.hex),
50
+ producer: client_id,
51
+ created_at: Time.now.utc
52
+ }
53
+ end
54
+
55
+ def event_name(version = nil)
56
+ return Emitter::WILDCARD if name.to_sym == :*
57
+
58
+ version ? "#{name}:#{version}" : name
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require "socket"
3
+ require "fileutils"
4
+ require "pathname"
5
+ require "timeout"
6
+
7
+ module Hivent
8
+
9
+ module CLI
10
+
11
+ class Consumer
12
+
13
+ def self.run!(args)
14
+ new(args).run!
15
+ end
16
+
17
+ def initialize(options)
18
+ @options = options
19
+ end
20
+
21
+ def run!
22
+ configure
23
+ register_service
24
+
25
+ worker_name = "#{Socket.gethostname}:#{Process.pid}"
26
+ @worker = Hivent::Redis::Consumer.new(@redis, @service_name, worker_name, @life_cycle_event_handler)
27
+
28
+ @worker.run!
29
+ end
30
+
31
+ private
32
+
33
+ def configure
34
+ # use load instead of require to allow multiple runs of this method in specs
35
+ load @options[:require]
36
+
37
+ @service_name = Hivent::Config.client_id
38
+ @partition_count = Hivent::Config.partition_count
39
+ @life_cycle_event_handler = Hivent::Config.life_cycle_event_handler ||
40
+ Hivent::LifeCycleEventHandler.new
41
+ @events = Hivent.emitter.events
42
+ @redis = Hivent::Redis.redis
43
+ end
44
+
45
+ def register_service
46
+ # TODO: cleanup unused events for this service from the registry
47
+ @redis.set("#{@service_name}:partition_count", @partition_count)
48
+
49
+ @events.each do |event|
50
+ @redis.sadd(event[:name], @service_name)
51
+ end
52
+
53
+ @life_cycle_event_handler.application_registered(@service_name, @events.deep_dup, @partition_count)
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end