monsoon-droplet 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +119 -0
- data/Rakefile +6 -0
- data/lib/monsoon.rb +24 -0
- data/lib/monsoon/droplet.rb +42 -0
- data/lib/monsoon/sem_ver.rb +31 -0
- data/lib/monsoon/streams/console.rb +15 -0
- data/lib/monsoon/streams/kinesis.rb +23 -0
- data/lib/monsoon/version.rb +3 -0
- data/lib/monsoon/versions_schema.rb +40 -0
- metadata +97 -0
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
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,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:
|