eventosaurus 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/.gitignore +10 -0
- data/.rspec +1 -0
- data/.rubocop.yml +28 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +46 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +307 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +12 -0
- data/circle.yml +6 -0
- data/eventosaurus.gemspec +39 -0
- data/lib/eventosaurus/configuration.rb +82 -0
- data/lib/eventosaurus/models/query.rb +51 -0
- data/lib/eventosaurus/models/table.rb +97 -0
- data/lib/eventosaurus/persistors/sidekiq.rb +12 -0
- data/lib/eventosaurus/persistors/synchronous.rb +16 -0
- data/lib/eventosaurus/query_builder.rb +67 -0
- data/lib/eventosaurus/railtie.rb +12 -0
- data/lib/eventosaurus/services/table_manager_service.rb +76 -0
- data/lib/eventosaurus/storable.rb +125 -0
- data/lib/eventosaurus/tasks/eventosaurus.rake +31 -0
- data/lib/eventosaurus/version.rb +7 -0
- data/lib/eventosaurus/workers/sidekiq.rb +20 -0
- data/lib/eventosaurus.rb +10 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a8ef4f20b8d48c0c0e035f514ba4088578e9068
|
4
|
+
data.tar.gz: 9038157130f2b42586d4fee746a3ebd394d3d0c2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90643659fb8232e9f6d713d946adbd8f984d49038d00a9095bd30a79df287796603ffcb449104ea79829f8f4944dd0906d5a204742d72a66579739d8888116a9
|
7
|
+
data.tar.gz: 158959e5a2f144a853cf7fa300301dde2d08dc742143b98ab826984ee081d79981d9a40a73df7bce2b38d28602e10e94cfcfd076fc6000c504c1bc171b50cb02
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3
|
3
|
+
Include:
|
4
|
+
- ./Rakefile
|
5
|
+
Exclude:
|
6
|
+
- bin/**/*
|
7
|
+
- vendor/bundle/**/*
|
8
|
+
|
9
|
+
Lint/EndAlignment:
|
10
|
+
AlignWith: variable
|
11
|
+
|
12
|
+
Metrics/LineLength:
|
13
|
+
Max: 120
|
14
|
+
|
15
|
+
Style/AlignParameters:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Style/DotPosition:
|
22
|
+
EnforcedStyle: leading
|
23
|
+
|
24
|
+
Style/FrozenStringLiteralComment:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/StringLiterals:
|
28
|
+
Enabled: false
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.0
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# 1.0.0
|
2
|
+
|
3
|
+
* changes name to 'eventosaurus'
|
4
|
+
* adds a test_mode
|
5
|
+
|
6
|
+
# 0.6.0
|
7
|
+
|
8
|
+
* uses event_uuid as sort key rather than created_at
|
9
|
+
|
10
|
+
# 0.5.5
|
11
|
+
|
12
|
+
* fixes bug in chainable local secondary indices
|
13
|
+
|
14
|
+
# 0.5.4
|
15
|
+
|
16
|
+
* set default sidekiq retry count to 3
|
17
|
+
* fix logger bug
|
18
|
+
|
19
|
+
# 0.5.3
|
20
|
+
|
21
|
+
* scope rake tasks to environment_prefix
|
22
|
+
|
23
|
+
# 0.5.2
|
24
|
+
|
25
|
+
* first release after code review
|
26
|
+
|
27
|
+
# 0.5.1
|
28
|
+
|
29
|
+
# 0.5.0
|
30
|
+
|
31
|
+
* adds dynamic query methods based on table definition
|
32
|
+
|
33
|
+
# 0.4.0
|
34
|
+
|
35
|
+
* removes dynamoid gem in favor of native aws-sdk usage
|
36
|
+
* adds rake tasks to build tables
|
37
|
+
|
38
|
+
# 0.3.0
|
39
|
+
|
40
|
+
* use Sidekiq for delayed jobs
|
41
|
+
* utilize Configuration class
|
42
|
+
* event jobs are idempotent
|
43
|
+
|
44
|
+
# 0.2.0
|
45
|
+
|
46
|
+
* uses DynamoID 1.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at engineering@blueapron.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Blue Apron, inc.
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
# Eventosaurus
|
2
|
+
|
3
|
+
[![CircleCI](https://circleci.com/gh/blueapron/eventosaurus.svg?style=shield)](https://circleci.com/gh/blueapron/eventosaurus)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/blueapron/eventosaurus/badges/gpa.svg)](https://codeclimate.com/github/blueapron/eventosaurus)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/blueapron/eventosaurus/badges/coverage.svg)](https://codeclimate.com/github/blueapron/eventosaurus/coverage)
|
6
|
+
[![Issue Count](https://codeclimate.com/github/blueapron/eventosaurus/badges/issue_count.svg)](https://codeclimate.com/github/blueapron/eventosaurus)
|
7
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/blueapron/eventosaurus.svg)](https://gemnasium.com/github.com/blueapron/eventosaurus)
|
8
|
+
|
9
|
+
Enables easy asynchronous event storing and querying on [DynamoDB][dynamodb]
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'eventosaurus'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install eventosaurus
|
26
|
+
|
27
|
+
|
28
|
+
## Setting up a local DynamoDB server
|
29
|
+
|
30
|
+
When developing, use the local DynamoDB server. Install and kick off your local instance:
|
31
|
+
|
32
|
+
Set the following environment variables:
|
33
|
+
|
34
|
+
```
|
35
|
+
EVENT_ENVIRONMENT_PREFIX=localhost
|
36
|
+
AWS_ENDPOINT=http://localhost:8000
|
37
|
+
```
|
38
|
+
|
39
|
+
Install and kick off DynamoDB:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
$ brew install dynamodb-local
|
43
|
+
$ ln -sfv /usr/local/opt/dynamodb-local/*.plist ~/Library/LaunchAgents
|
44
|
+
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.dynamodb-local.plist
|
45
|
+
```
|
46
|
+
|
47
|
+
## Configuration
|
48
|
+
|
49
|
+
You need to add the following initializer (ex: `config/initializers/eventosaurus.rb`):
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
Eventosaurus.configure do |config|
|
53
|
+
config.use_sidekiq
|
54
|
+
|
55
|
+
# ex: localhost, productions
|
56
|
+
config.environment_prefix = ENV['EVENT_ENVIRONMENT_PREFIX']
|
57
|
+
|
58
|
+
config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
|
59
|
+
config.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
|
60
|
+
config.aws_region = ENV['AWS_REGION']
|
61
|
+
|
62
|
+
# optional, used for local dynamodb
|
63
|
+
config.aws_endpoint = ENV['AWS_ENDPOINT']
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Sidekiq Alternatives
|
68
|
+
|
69
|
+
Eventosaurus ships with both synchronous and asynchronous options. By default Eventosaurus uses sidekiq to persist data to DynamoDB asynchronously.
|
70
|
+
|
71
|
+
For synchronous persistance:
|
72
|
+
```ruby
|
73
|
+
Eventosaurus.configure do |config|
|
74
|
+
# ...
|
75
|
+
config.use_synchronous
|
76
|
+
# ...
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
To use your own persistence mechanism, reference the below two files:
|
81
|
+
|
82
|
+
* [`lib/eventosaurus/persistors/sidekiq.rb`](https://github.com/blueapron/eventosaurus/blob/master/lib/eventosaurus/persistors/sidekiq.rb)
|
83
|
+
* [`lib/eventosaurus/workers/sidekiq.rb`](https://github.com/blueapron/eventosaurus/blob/master/lib/eventosaurus/workers/sidekiq.rb)
|
84
|
+
|
85
|
+
And include it in your configuration
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
require 'custom_persistor'
|
89
|
+
Eventosaurus.configure do |config|
|
90
|
+
# ...
|
91
|
+
config.persistor = CustomPersistor
|
92
|
+
# ...
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
## Event Representation
|
97
|
+
|
98
|
+
Every event type is represented by a class that includes `Eventosaurus::Storable`. Each class must do two things:
|
99
|
+
|
100
|
+
0. define the table using the `table_definition` macro
|
101
|
+
1. define the `details` class method, which defines the event interface
|
102
|
+
|
103
|
+
Here is an example of an event definition:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
module Events
|
107
|
+
class PhoneCall
|
108
|
+
include Eventosaurus::Storable
|
109
|
+
|
110
|
+
table_definition name: :phone_call, partition_key: { person_id: :n }
|
111
|
+
|
112
|
+
def self.details( person_id:, phone_number:, last_called:)
|
113
|
+
{
|
114
|
+
'person_id' => person_id
|
115
|
+
'phone_number' => phone_number.to_s,
|
116
|
+
'last_called' => last_called
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
There are some built-in attributes for your events:
|
124
|
+
|
125
|
+
0. the gem defines the range of your partition key to be `event_uuid.` When writing an event, this attribute is enforced to be unique, preventing duplicate writes. See Event Duplication Prevention below.
|
126
|
+
0. the event also stores the timestamp, which represents the time when the gem client calls `.store`
|
127
|
+
|
128
|
+
## Building the Tables
|
129
|
+
|
130
|
+
DynamoDB must have the tables needed to run your events. Once you've written your event classes you must run a rake task to create the tables. The tables are namespaced by your environment, as defined in the environment_prefix variable mentioned above. So if you build locally, the table name might be `localhost_core_customer_audit`. This will allow us to quickly get up and running on new environments. For the time being, the rake task expects your event definitions to be in `app/models/events`. **Be sure to put them there!**. Rake tasks are scoped to only work with tables that begin with your environment_prefix. This means even if staging and production point to the same dynamodb account, the `drop_tables` task will only drop tables from the environment specified.
|
131
|
+
|
132
|
+
```
|
133
|
+
rake eventosaurus:create_tables
|
134
|
+
```
|
135
|
+
*(NOTE: in the short term, you will have to manually run this create tables task upon deploy, as well as staging environments and anyone who pulls code utilizing these tables. This is temporary and there is an [outstanding task](https://app.asana.com/0/35948616193167/101210257934375) to change this.)*
|
136
|
+
|
137
|
+
|
138
|
+
You may then verify the tables were created:
|
139
|
+
|
140
|
+
```
|
141
|
+
rake eventosaurus:list_tables
|
142
|
+
```
|
143
|
+
|
144
|
+
See the JSON used to create your tables:
|
145
|
+
|
146
|
+
```
|
147
|
+
rake eventosaurus:describe_tables
|
148
|
+
```
|
149
|
+
|
150
|
+
If you decide to (╯°□°)╯︵ ┻━┻
|
151
|
+
|
152
|
+
```
|
153
|
+
rake eventosaurus:drop_tables
|
154
|
+
```
|
155
|
+
|
156
|
+
## Storing Data
|
157
|
+
|
158
|
+
To store data use the `.store` class method on your event class. Use the same signature as your `details` method mentioned above:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
# Somewhere in your app:
|
162
|
+
def check_for_audit(row)
|
163
|
+
if audit_time?
|
164
|
+
Events::CoreCustomerAudit.store(
|
165
|
+
updated_row: row,
|
166
|
+
updated_attribute: 'first_name',
|
167
|
+
updated_by_user_id: 35,
|
168
|
+
audit_occurred_at: UTC.now
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
## Querying Data
|
175
|
+
|
176
|
+
The gem gives you some dynamic methods to query your data based on your table definition. It's important to keep in mind that you are working with DynamoDB. It is not meant to be a data store that is accessed generically. It expects you to know the queries you want to run upfront. Good for us, we are storing each event type in its own table, so we can make good guesses about this. To this end, eventosaurus creates getters for the attributes you listed in your table definition:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
Events::CoreCustomerAudit.by_customer_id(5)
|
180
|
+
Events::CoreCustomerAudit.by_customer_id(5).by_table_name('users').count
|
181
|
+
|
182
|
+
# event_uuid & created_at included for free :)
|
183
|
+
Events::CoreCustomerAudit.by_created_at('2015-01-04', 'GT')
|
184
|
+
```
|
185
|
+
|
186
|
+
The queries above return eventosaurus Query objects. To actually execute the query, use the `run` method:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
Events::CoreCustomerAudit.by_customer_id(5).count.run
|
190
|
+
```
|
191
|
+
|
192
|
+
Note:
|
193
|
+
|
194
|
+
0. In the last example we query by created_at even though it was not listed in the table definition. This is because each table gets the created_at timestamp column as well as the event_uuid column added.
|
195
|
+
1. The operator defaults to 'EQ' (equals) but there are many to choose from: EQ, NE, IN, LE, LT, GE, GT, CONTAINS, NOT_CONTAINS, BEGINS_WITH
|
196
|
+
2. DynamoDB only allows a **single** secondary index to accompany the partition key. This means the following query will not work the way you think:
|
197
|
+
|
198
|
+
```
|
199
|
+
# too many secondary predicates. after one secondary index is used, the rest will be full scans on whatever comes back after the first local index.
|
200
|
+
Events::CoreCustomerAudit.by_created_at('2015-01-04', 'GT').by_table_name('users')
|
201
|
+
```
|
202
|
+
|
203
|
+
**To sum it up: for speed, you are allowed 0||1 partition key condition and 0||1 secondary condition. No more than that.**
|
204
|
+
|
205
|
+
## Test mode
|
206
|
+
|
207
|
+
Test mode can be enabled by placing the following in rails_helper.rb (or equivalent):
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
Eventosaurus.enable_test_mode
|
211
|
+
```
|
212
|
+
|
213
|
+
## On Choosing the proper table_definition for your event
|
214
|
+
|
215
|
+
When considering the correct partition_key, there are a few considerations. The first is to consider the predicates you will filter by. The predicates you use the most should probably become your partition_key. The second is the number of different values you expect to see in your partition. The more you have, the better. This is a complicated subject, and understanding of how DynamoDB works (partion keys, local and global secondary indexes) should be understood before creating an event. [Here][dynamodbbestpractices] you can find more detail about best practices, and of course hopefully a co-worker Near You can help too.
|
216
|
+
|
217
|
+
## Event Error Handling upon calling .store
|
218
|
+
|
219
|
+
When you call `.store`, you will be utilizing your `.details` method. Sadly, sometimes you will make mistakes and the gem will raise. Happily, you can decide what to do about the errors. If your call to `.store` raises, the `on_error` class method is called with the error as an argument. Feel free to overwrite this class method in your Event class:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
module Events
|
223
|
+
class NeatEvent
|
224
|
+
include Eventosaurus::Storable
|
225
|
+
|
226
|
+
# ... your primary event code here...
|
227
|
+
|
228
|
+
def on_error(error)
|
229
|
+
HaikuNotifier.write_haiku_with_error(error)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
## Event Duplication Prevention
|
236
|
+
|
237
|
+
This gem has two methods of preventing duplicate events from being written to DynamoDB, and each method addresses a different way duplication can occur.
|
238
|
+
|
239
|
+
|
240
|
+
### Background on the event_uuid column
|
241
|
+
|
242
|
+
Before getting into the two methods below, the foundational piece of information is that DynamoDB writes can be configured to fail if a duplicate value is found on an attribute. The Event Gem leverages this by using an 'event_uuid' as the table's sort key, and setting our writes to fail if the event_uuid already exists.
|
243
|
+
|
244
|
+
### Duplication Cause 1: Double processing of asynchronous jobs.
|
245
|
+
|
246
|
+
If the same job that sends an event to dynamodb gets run twice, we need to make sure we don't store two events. This is handled by the mechanism explained above: writes are instructed to fail if they see the event_uuid already exists.
|
247
|
+
|
248
|
+
### Duplication Cause 2: Gem client erroneously calls the .store method multiple times
|
249
|
+
|
250
|
+
If your (the gem client's) code has an error, and your code calls Events::NeatEvent.store more than intended, the Event Gem can be configured to help defend and ensure only 1 event is stored. To guard against this, you can create a composite primary key, based on the fields of your choosing. This composite key is then digested and used as the basis of the event_uuid. You may use the macro `compsite_primary_key` to achieve this duplication defense:
|
251
|
+
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
module Events
|
255
|
+
class NeatEvent
|
256
|
+
|
257
|
+
composite_primary_key :location, :employee_name, :employee_action
|
258
|
+
end
|
259
|
+
```
|
260
|
+
|
261
|
+
The above example will cause a string like the following to be generated and used as a digest for the event_uuid:
|
262
|
+
|
263
|
+
```
|
264
|
+
location=gardens:employee_name=Markus:employee_action=ate grapes
|
265
|
+
```
|
266
|
+
|
267
|
+
Even if you call Events::NeatEvent.store(args) multiple times with the same args, only one event will be created.
|
268
|
+
|
269
|
+
If you do not opt to use the composite_primary_key feature, the Event Gem will use `SecureRandom.uuid` to generate the uuid, which has a much less likely chance of collision than you winning the lottery (It follows RFC 4122)
|
270
|
+
|
271
|
+
#### Do not add the created_at attr to your list of composite_primary_key attrs
|
272
|
+
|
273
|
+
Using a timestamp representing the creation-time of the event (aka the created_at attr) in the composite_primary_key is not advisable, as accidental duplicate events might have slightly different timestamps, and thus slightly different UUIDs. Put simply: _do not add the created_at attr to your list of composite_primary_key attrs._
|
274
|
+
|
275
|
+
**Note** that any attribute listed in the composite_primary_key macro promotes that attribute to a required attribute.
|
276
|
+
|
277
|
+
|
278
|
+
## Using the AWS SDK (V2) Client Directly
|
279
|
+
|
280
|
+
To access the [Aws SDK v2 client][awssdkv2] directly (for educational purposes only), access via `Eventosaurus.configuration.dynamodb_client`. For example:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
Eventosaurus.configuration.dynamodb_client.list_tables
|
284
|
+
```
|
285
|
+
|
286
|
+
## Development
|
287
|
+
|
288
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
289
|
+
|
290
|
+
## Releasing
|
291
|
+
|
292
|
+
Version bumps should be done straight in master after appropriate PRs are merged.
|
293
|
+
|
294
|
+
Gem release best practices [here](https://github.com/blueapron/guides/blob/master/best_practices/developing_ruby_gems.md)
|
295
|
+
|
296
|
+
## Contributing
|
297
|
+
|
298
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/blueapron/eventosaurus.
|
299
|
+
|
300
|
+
## License
|
301
|
+
|
302
|
+
The gem is Copyright 2015 Blue Apron, Inc.
|
303
|
+
|
304
|
+
[dynamodbbestpractices]: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html#GuidelinesForTables.UniformWorkload
|
305
|
+
[dynamodb]: https://aws.amazon.com/dynamodb/
|
306
|
+
[localdynamo]: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html
|
307
|
+
[awssdkv2]: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "eventosaurus"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
require "pry"
|
10
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
bundle install
|
6
|
+
|
7
|
+
# Do any other automated setup that you need to do here
|
8
|
+
|
9
|
+
echo -e "\n************************************************************\n"
|
10
|
+
echo -e "Install a local DynamoDB server: "
|
11
|
+
echo -e "http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html"
|
12
|
+
echo -e "\n************************************************************\n"
|
data/circle.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'eventosaurus/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'eventosaurus'
|
8
|
+
spec.version = Eventosaurus::VERSION
|
9
|
+
spec.authors = ['Blue Apron Engineering']
|
10
|
+
spec.email = ['engineering@blueapron.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Enables easy reporting of events to an event store.}
|
13
|
+
spec.description = %q{Enables easy reporting of events to an event store.}
|
14
|
+
spec.homepage = 'https://github.com/blueapron/eventosaurus'
|
15
|
+
|
16
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = 'bin'
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'activesupport'
|
24
|
+
spec.add_dependency 'aws-sdk', '~> 2.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'rubocop'
|
31
|
+
spec.add_development_dependency 'sidekiq'
|
32
|
+
spec.add_development_dependency 'simplecov'
|
33
|
+
spec.add_development_dependency 'timecop'
|
34
|
+
|
35
|
+
if ENV['CIRCLECI']
|
36
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
37
|
+
spec.add_development_dependency 'rspec_junit_formatter'
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Eventosaurus
|
4
|
+
class << self
|
5
|
+
attr_accessor :configuration
|
6
|
+
|
7
|
+
def configure
|
8
|
+
yield(configuration) if block_given?
|
9
|
+
|
10
|
+
configuration.configure_aws
|
11
|
+
configuration.configure_dynamodb
|
12
|
+
end
|
13
|
+
|
14
|
+
def configuration
|
15
|
+
@configuration ||= Configuration.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def enable_test_mode
|
19
|
+
Storable::ClassMethods.redefine_method(:store) do
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
Models::Query.redefine_method(:run) do
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Configuration
|
30
|
+
attr_accessor :aws_access_key_id
|
31
|
+
attr_accessor :aws_endpoint
|
32
|
+
attr_accessor :aws_secret_access_key
|
33
|
+
attr_accessor :aws_region
|
34
|
+
attr_accessor :environment_prefix
|
35
|
+
attr_accessor :dynamodb_client
|
36
|
+
attr_accessor :dynamodb_table_namespace
|
37
|
+
attr_accessor :dynamodb_warn_on_scan
|
38
|
+
attr_accessor :logger
|
39
|
+
attr_accessor :persistor
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@logger = Logger.new(STDOUT)
|
43
|
+
end
|
44
|
+
|
45
|
+
def use_synchronous
|
46
|
+
require 'eventosaurus/persistors/synchronous'
|
47
|
+
|
48
|
+
@persistor = Eventosaurus::Persistors::Synchronous
|
49
|
+
end
|
50
|
+
|
51
|
+
def use_sidekiq
|
52
|
+
require 'eventosaurus/persistors/sidekiq'
|
53
|
+
|
54
|
+
@persistor = Eventosaurus::Persistors::Sidekiq
|
55
|
+
end
|
56
|
+
|
57
|
+
def configure_aws
|
58
|
+
Aws.config.update(region: aws_region, credentials: aws_credentials)
|
59
|
+
end
|
60
|
+
|
61
|
+
def aws_credentials
|
62
|
+
self.aws_region ||= 'us-east-2'
|
63
|
+
self.aws_access_key_id ||= 'unset'
|
64
|
+
self.aws_secret_access_key ||= 'unset'
|
65
|
+
|
66
|
+
@aws_credientials ||= Aws::Credentials.new(aws_access_key_id, aws_secret_access_key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure_dynamodb
|
70
|
+
self.environment_prefix ||= 'unset'
|
71
|
+
|
72
|
+
args = {
|
73
|
+
region: aws_region,
|
74
|
+
credentials: aws_credentials
|
75
|
+
}
|
76
|
+
|
77
|
+
args[:endpoint] = aws_endpoint if aws_endpoint.present?
|
78
|
+
|
79
|
+
self.dynamodb_client = Aws::DynamoDB::Client.new(args)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|