monsoon-droplet 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7e6232ff9dd488d25b28e378d87905d90a8e80bf
4
+ data.tar.gz: 6628745f73d30cc0c87cb5d19c916fc9a4f38e3e
5
+ SHA512:
6
+ metadata.gz: fb6209b101d0aa6437d9b90fba8e215f2f8d81439ec9754b5a1ecd8b35c93e19523e2c1dee06ecca4be7ce7073fc43aabf5e709e983b880edf5ad81045733e4f
7
+ data.tar.gz: 16ba8785319d604cf311db264f70cb00abc1482314b58f34d97f70831d2e7688827aaf9672c8e26131ba30fe39bd620b867342b1c9ce218e09073746449728d2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Neil Gupta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Monsoon
2
+
3
+ [https://www.github.com/neilgupta/monsoon](https://www.github.com/neilgupta/monsoon)
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/monsoon-droplet.png)](http://badge.fury.io/rb/monsoon-droplet)
6
+
7
+ Monsoon writes messages to a stream of data. That stream can be anything, from an external service to a log or even an in-memory array, as long as you write an adapter for it. Out of the box, Monsoon includes adapters for AWS Kinesis and stdout.
8
+
9
+ Monsoon allows you to define versioned contracts for your messages, treating your streams as an API for other clients to consume. Each message sent by Monsoon includes only the keys defined in the contract, the version of the contract used, and whether or not that contract is deprecated.
10
+
11
+ ## Installation
12
+
13
+ Install the gem on your machine:
14
+
15
+ ```
16
+ gem install monsoon-droplet
17
+ ```
18
+
19
+ Or add it to your `Gemfile`:
20
+
21
+ ```ruby
22
+ gem 'monsoon-droplet'
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ Create `/config/initializers/monsoon.rb` and add:
28
+
29
+ ```ruby
30
+ require 'monsoon'
31
+ require 'monsoon/streams/kinesis'
32
+ require 'monsoon/streams/console'
33
+
34
+ Monsoon.versions_schema = {
35
+ "analytics": {
36
+ "1.0.0": ["user_id", "event_type", "timestamp", "data"],
37
+ "1.0.1": ["user_id", "event_type", "timestamp", "data"],
38
+ "1.1.0": ["user_id", "event_type", "timestamp", "data", "resource_id"],
39
+ "2.0.0": ["event_type", "timestamp", "data", "resource_id"]
40
+ },
41
+ "transcodes": {
42
+ "1.0.0": ["filename", "status"],
43
+ "2.0.0": ["filename", "sources"]
44
+ }
45
+ }
46
+
47
+ # if using the Kinesis stream...
48
+ ENV['AWS_ACCESS_KEY_ID'] = "YOUR AWS ACCESS KEY"
49
+ ENV['AWS_SECRET_ACCESS_KEY'] = "YOUR AWS SECRET"
50
+ ENV['AWS_REGION'] = "YOUR AWS REGION"
51
+ ```
52
+
53
+ ### Stream Adapters
54
+
55
+ To add a stream adapter, just require it:
56
+
57
+ ```ruby
58
+ require 'monsoon/streams/kinesis'
59
+ ```
60
+
61
+ This will add the built-in AWS Kinesis adapter. `monsoon/streams/console` is also built-in for testing by writing your streams to STDOUT.
62
+
63
+ You can write your own stream adapter by creating a class that implements `#put_records(stream_name, records_array)` and then add it to Monsoon with `Monsoon.streams << YourNewAdapter.new`. See `Monsoon::Streams::Console` for an example.
64
+
65
+ Monsoon will send your messages to all added streams by default.
66
+
67
+ ### Versions Schema
68
+
69
+ To auto-generate versioned copies of your message, configure Monsoon with your versions schema. This is just a hash of stream names and keys that tells Monsoon, given a stream name, here's a list of versions we support and the keys each version should include in its message. For example, if you have the following schema defined:
70
+
71
+ ```ruby
72
+ Monsoon.versions_schema = {
73
+ "analytics": {
74
+ "1.0.0": ["user_id", "event_type", "timestamp", "data"],
75
+ "1.0.1": ["user_id", "event_type", "timestamp", "data"],
76
+ "1.1.0": ["user_id", "event_type", "timestamp", "data", "resource_id"],
77
+ "2.0.0": ["event_type", "timestamp", "data", "resource_id"]
78
+ },
79
+ "transcodes": {
80
+ "1.0.0": ["filename", "status"],
81
+ "2.0.0": ["filename", "sources"]
82
+ }
83
+ }
84
+ ```
85
+
86
+ and you want to send the message `{user_id: 1, event_type: 'play', timestamp: 23, data: 'some other data', resource_id: 10}` to your `analytics` stream, Monsoon will automatically generate 4 versions of your message for each defined version. The versioned message will only include the keys needed for that version, as well as 2 new keys: 'droplet_version' and 'droplet_deprecated'. `droplet_version` tells the receiving client which version they're reading, and `version_deprecated` is true if there is a newer version defined in the schema.
87
+
88
+ If the original message does not include all of the keys needed for an older version (e.g., `user_id` is left out in the above example), then Abacus will only send the versions it has the data for (e.g., it will only send v2.0+ for the 'analytics' stream).
89
+
90
+ ### Droplets
91
+
92
+ To send a message after you've configured Monsoon, just create a new Droplet and stream it:
93
+
94
+ ```ruby
95
+ droplet = Monsoon::Droplet.new("analytics", {user_id: 1, event_type: 'play', timestamp: 23, data: 'some other data', resource_id: 10})
96
+ droplet.stream
97
+ ```
98
+
99
+ Droplets can also take a hash with the following options:
100
+
101
+ * `:versioning` - can be `:skip`, `:enforce`, or `nil`
102
+ - `:skip` will ignore the versions schema and write the data exactly as passed
103
+ - `:enforce` will require using the schema and will write nothing if unable to version
104
+ - `nil` will try to write versioned droplets and fallback to raw data (default)
105
+
106
+ `#stream` can also take an optional stream adapter (any object that implements `#put_records`) to only stream to that adapter. For example:
107
+
108
+ ```ruby
109
+ droplet = Monsoon::Droplet.new("my_stream", {user_id: 1, event_type: 'play', timestamp: 23}, {versioning: :skip})
110
+ droplet.stream(YourNewAdapter.new)
111
+ ```
112
+
113
+ ## Author
114
+
115
+ Neil Gupta [http://metamorphium.com](http://metamorphium.com)
116
+
117
+ ## License
118
+
119
+ The MIT License (MIT) Copyright (c) 2016 Neil Gupta. See [MIT-LICENSE](https://raw.github.com/neilgupta/monsoon/master/MIT-LICENSE)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/lib/monsoon.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'monsoon/sem_ver'
2
+ require 'monsoon/versions_schema'
3
+ require 'monsoon/droplet'
4
+
5
+ module Monsoon
6
+ @@streams = []
7
+ @@versions_schema = {}
8
+
9
+ def self.streams
10
+ @@streams
11
+ end
12
+
13
+ def self.streams=(streams)
14
+ @@streams = streams
15
+ end
16
+
17
+ def self.versions_schema
18
+ @@versions_schema
19
+ end
20
+
21
+ def self.versions_schema=(versions_schema)
22
+ @@versions_schema = versions_schema
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module Monsoon
2
+ class Droplet
3
+ attr_reader :stream_name
4
+ attr_reader :options
5
+ attr_reader :raw_data
6
+ attr_reader :data
7
+
8
+ # new - creates a droplet
9
+ # stream_name - name of the stream to write to (required)
10
+ # data - hash of data to write to stream (required)
11
+ # options - optional hash
12
+ # :versioning => can be :skip, :enforce, or nil
13
+ # :skip will ignore the versions schema and write the data exactly as passed
14
+ # :enforce will require using the schema and will write nothing if unable to version
15
+ # nil will try to write versioned droplets and fallback to raw data (default)
16
+ #
17
+ # @example
18
+ # Monsoon::Droplet.new('analytics', {user_id: 3, event_type: 'play'}, {versioning: :enforce})
19
+ def initialize(stream_name, raw_data, options = {})
20
+ @stream_name = stream_name
21
+ @options = options
22
+ @raw_data = raw_data
23
+ @data = VersionsSchema.new(@stream_name).get_droplets(@raw_data) unless @options[:versioning] == :skip
24
+ @data = [@raw_data] if blank? && @options[:versioning] != :enforce
25
+ end
26
+
27
+ # stream - writes the droplet to all configured stream adapters
28
+ # streamer - (optional) stream adapter instance to limit which adapter is used (adapter must implement `#put_records`)
29
+ #
30
+ # @example
31
+ # droplet = Monsoon::Droplet.new('analytics', {user_id: 3, event_type: 'play'})
32
+ # droplet.stream(Monsoon::Streams::Console.new)
33
+ def stream(streamer = false)
34
+ return if blank?
35
+ streamer ? streamer.put_records(@stream_name, @data) : Monsoon.streams.each {|s| stream(s) }
36
+ end
37
+
38
+ def blank?
39
+ @data.nil? || @data.empty?
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ # This is a helper class for parsing and comparing semantic version strings (eg "1.1.0")
2
+ module Monsoon
3
+ class SemVer
4
+ include Comparable
5
+ attr_reader :major, :minor, :patch
6
+
7
+ def initialize(version)
8
+ version = version.to_s if version.is_a?(Symbol)
9
+ raise ArgumentError, 'Not a valid semantic version' unless self.class.valid?(version)
10
+
11
+ ver = version.split('.')
12
+ @major = ver[0].to_i
13
+ @minor = ver[1].to_i
14
+ @patch = ver[2].to_i
15
+ end
16
+
17
+ def <=> (other)
18
+ other = self.class.new(other) if self.class.valid?(other)
19
+ [major, minor, patch] <=> [other.major, other.minor, other.patch] if other.is_a?(self.class)
20
+ end
21
+
22
+ def to_s
23
+ "#{major}.#{minor}.#{patch}"
24
+ end
25
+
26
+ def self.valid?(version)
27
+ version = version.to_s if version.is_a?(Symbol)
28
+ version.is_a?(String) && version.split('.').take(3).map(&:to_i).reduce(0, :+) > 0
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Monsoon
4
+ module Streams
5
+ class Console
6
+ def put_records(stream, records)
7
+ records.each do |r|
8
+ puts "Streaming to #{stream}: #{JSON.pretty_generate(r)}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Monsoon.streams << Monsoon::Streams::Console.new
@@ -0,0 +1,23 @@
1
+ require 'aws-sdk'
2
+
3
+ module Monsoon
4
+ module Streams
5
+ class Kinesis
6
+ def initialize
7
+ @client = Aws::Kinesis::Client.new
8
+ end
9
+
10
+ def put_records(stream, records)
11
+ data = records.map do |r|
12
+ {
13
+ data: JSON.generate(r),
14
+ partition_key: 'monsoon'
15
+ }
16
+ end
17
+ @client.put_records(records: data, stream_name: stream)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ Monsoon.streams << Monsoon::Streams::Kinesis.new
@@ -0,0 +1,3 @@
1
+ module Monsoon
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,40 @@
1
+ module Monsoon
2
+ class VersionsSchema
3
+ attr_reader :stream
4
+
5
+ def initialize(stream)
6
+ @stream = stream
7
+ end
8
+
9
+ def get_droplets(record)
10
+ return if record.nil?
11
+
12
+ schema.map do |version, keys|
13
+ symbolized_keys = keys.map(&:to_sym)
14
+ # Only keep the record keys that are defined in this version's schema
15
+ versioned_record = record.select{|k,v| symbolized_keys.include?(k.to_sym)}
16
+ # stringify record keys
17
+ versioned_record = Hash[versioned_record.map { |k, v| [k.to_s, v] }]
18
+ # add versioning metadata
19
+ versioned_record.merge({
20
+ 'droplet_version' => version.to_s,
21
+ 'droplet_deprecated' => deprecated?(version)
22
+ }) if versioned_record.keys.length == keys.length # make sure we got all the keys for this version
23
+ end.compact
24
+ end
25
+
26
+ def deprecated?(version)
27
+ SemVer.new(version) < versions.max_by {|v| SemVer.new(v)} unless versions.empty?
28
+ end
29
+
30
+ def versions
31
+ schema.keys
32
+ end
33
+
34
+ private
35
+
36
+ def schema
37
+ @schema ||= Monsoon.versions_schema[@stream.to_sym] || Monsoon.versions_schema[@stream.to_s] || {}
38
+ end
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monsoon-droplet
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Neil Gupta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Monsoon makes sending messages to an external resource super easy by
56
+ versioning your messages so other services won't break when your messages change.
57
+ email: neil@instructure.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - lib/monsoon.rb
66
+ - lib/monsoon/droplet.rb
67
+ - lib/monsoon/sem_ver.rb
68
+ - lib/monsoon/streams/console.rb
69
+ - lib/monsoon/streams/kinesis.rb
70
+ - lib/monsoon/version.rb
71
+ - lib/monsoon/versions_schema.rb
72
+ homepage: https://github.com/neilgupta/monsoon
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.4.6
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Super simple message versioning
96
+ test_files: []
97
+ has_rdoc: