staged_event 0.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.
- checksums.yaml +7 -0
- data/README.md +59 -0
- data/lib/generators/staged_event/install_generator.rb +24 -0
- data/lib/generators/staged_event/templates/create_staged_events.rb.erb +12 -0
- data/lib/generators/staged_event/templates/initializer.rb.erb +51 -0
- data/lib/staged_event/backoff_timer.rb +31 -0
- data/lib/staged_event/configuration.rb +25 -0
- data/lib/staged_event/event_envelope.proto +14 -0
- data/lib/staged_event/event_envelope_pb.rb +18 -0
- data/lib/staged_event/google_pub_sub/helper.rb +19 -0
- data/lib/staged_event/google_pub_sub/publisher.rb +30 -0
- data/lib/staged_event/google_pub_sub/subscriber.rb +71 -0
- data/lib/staged_event/model.rb +9 -0
- data/lib/staged_event/publisher.rb +9 -0
- data/lib/staged_event/publisher_process.rb +48 -0
- data/lib/staged_event/railtie.rb +12 -0
- data/lib/staged_event/subscriber.rb +9 -0
- data/lib/staged_event/subscriber_process.rb +22 -0
- data/lib/staged_event/version.rb +5 -0
- data/lib/staged_event.rb +72 -0
- data/lib/tasks/publisher.rake +6 -0
- data/lib/tasks/subscriber.rake +6 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 626ddab89e43654e2f9f7444efbbb778e40a02882142e5bab971a253d0586278
|
4
|
+
data.tar.gz: 77bf67900459f69099d58913d8ae49140c934d49239f1ebf043e0924d127632e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 19f8edb5ba417a59bae2d5e62f68437d34a1563b3996fe2d848c6515292136970f071ef7ed75bbc6e9bdac7404a317b5330ba8d1cd3a4bfca40ca0160ea7ba1b
|
7
|
+
data.tar.gz: 7dc404d84752a7aeeda6897cbb47dda3168d86219e3fdc809e21b5dac230a829d2b266fb361f51ac21d0acf0b5ab3bd566a5121181b4853644b4b272a1f55f4e
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# StagedEvent
|
2
|
+
|
3
|
+
StagedEvent is a Ruby implementation of the [transactional outbox](https://microservices.io/patterns/data/transactional-outbox.html) architectural pattern.
|
4
|
+
|
5
|
+
This gem is relevant for any process that wants to publish events to a set of subscribers. It automatically saves outgoing events as records in a database table, from which a separate background process reads, publishes, and (once publishing is confirmed) deletes them. StagedEvent also provides a listener to subscribe to and receive these events from other processes.
|
6
|
+
|
7
|
+
Staging events this way allows them to be persisted within the same database transaction as associated domain-specific records, guaranteeing at-least-once delivery and thus eventual consistency with the receiving system(s).
|
8
|
+
|
9
|
+
For now, StagedEvent makes the following assumptions:
|
10
|
+
- Events are defined as protobufs (and you have generated Ruby classes for them)
|
11
|
+
- [Google Pub/Sub](https://cloud.google.com/pubsub/) is the underyling messaging infrastructure
|
12
|
+
- You're using ActiveRecord (with a Postgres database >= version 9.5, due to the usage of the SQL `SKIP LOCKED` clause).
|
13
|
+
|
14
|
+
### Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "staged_event"
|
20
|
+
```
|
21
|
+
|
22
|
+
Then, in your application directory:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
$ bundle install
|
26
|
+
$ rails generate staged_event:install
|
27
|
+
```
|
28
|
+
|
29
|
+
This will create an initializer at `config/initializers/staged_event.rb`, which you should modify with settings specific to your application.
|
30
|
+
|
31
|
+
The installer will also generate a migration to create the database table for staged events waiting to be published. Remember to run it using:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
rails db:migrate
|
35
|
+
```
|
36
|
+
|
37
|
+
### Publishing Events
|
38
|
+
|
39
|
+
In order for events to actually be published, StagedEvent provides a rake task that should be kept running alongside your application server(s):
|
40
|
+
|
41
|
+
```bash
|
42
|
+
rake staged_event:publisher
|
43
|
+
```
|
44
|
+
|
45
|
+
### Receiving Events
|
46
|
+
|
47
|
+
In order to receive events from publishers, StagedEvent provides a rake task that should be kept running alongside your application server(s):
|
48
|
+
|
49
|
+
```bash
|
50
|
+
rake staged_event:subscriber
|
51
|
+
```
|
52
|
+
|
53
|
+
In order to process incoming events, you define a callback in the staged_event initializer file.
|
54
|
+
|
55
|
+
### Regenerating Ruby from protobufs
|
56
|
+
|
57
|
+
StagedEvent uses a one-off protobuf definition to serialize and deserialize events that are defined as protobufs. In case it becomes necessary to recreate the auto-generated ruby, the command for that (from the repository root) is:
|
58
|
+
|
59
|
+
protoc --ruby_out=./ "./lib/staged_event/event_envelope.proto"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module StagedEvent
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
|
10
|
+
class << self
|
11
|
+
delegate :next_migration_number, to: ActiveRecord::Generators::Base
|
12
|
+
end
|
13
|
+
|
14
|
+
source_paths << File.join(File.dirname(__FILE__), "templates")
|
15
|
+
|
16
|
+
def create_migration_file
|
17
|
+
migration_template "create_staged_events.rb.erb", "db/migrate/create_staged_events.rb"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_initializer
|
21
|
+
template "initializer.rb.erb", "config/initializers/staged_event.rb"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateStagedEvents < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
enable_extension "uuid-ossp"
|
4
|
+
enable_extension "pgcrypto"
|
5
|
+
|
6
|
+
create_table :staged_events, id: :uuid do |t|
|
7
|
+
t.string :topic
|
8
|
+
t.binary :data, null: false
|
9
|
+
t.datetime :created_at, null: false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
StagedEvent::Configuration.configure do |config|
|
4
|
+
config.google_pubsub do |google_pubsub|
|
5
|
+
# The contents of the Pub/Sub keyfile as a Hash
|
6
|
+
google_pubsub.credentials = {}
|
7
|
+
|
8
|
+
# This specifies the mapping between the "topic" values stored on events in
|
9
|
+
# the database, and the associated Pub/Sub topic id. This level of indirection
|
10
|
+
# lets you use arbitrary topic names in your application code instead of being
|
11
|
+
# tied to names stored in a third-party infrastructure.
|
12
|
+
# The first entry in the map is used as the default for when the event does
|
13
|
+
# not specify a topic.
|
14
|
+
google_pubsub.topic_map = {
|
15
|
+
default: "topic_id_1",
|
16
|
+
}
|
17
|
+
|
18
|
+
google_pubsub.subscription_ids = [ "subscription_id_1" ]
|
19
|
+
end
|
20
|
+
|
21
|
+
config.publisher do |publisher|
|
22
|
+
# The number of events that a publisher will transactionally publish and remove
|
23
|
+
# from the database in each iteration.
|
24
|
+
publisher.batch_size = 5
|
25
|
+
end
|
26
|
+
|
27
|
+
config.subscriber do |subscriber|
|
28
|
+
# This callback will be run for every message received, and is passed the serialized
|
29
|
+
# event data.
|
30
|
+
subscriber.event_received_callback = lambda do |serialized_data|
|
31
|
+
#
|
32
|
+
# To respond to the the event, one could either
|
33
|
+
# 1) enqueue a background job with serialized_data as a parameter
|
34
|
+
# 2) handle the event in-process. For example:
|
35
|
+
#
|
36
|
+
# event = StagedEvent.deserialize_event(serialized_data)
|
37
|
+
#
|
38
|
+
# event.id is globally-unique and can be used for idempotency.
|
39
|
+
# event.data is the event deserialized into its original form (typically a
|
40
|
+
# protobuf object).
|
41
|
+
#
|
42
|
+
# if event.data.is_a?(MyEvents::SomethingHappened)
|
43
|
+
# handle_something_happening
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# WARNING: At the moment, the google pub/sub subscriber only has one thread
|
47
|
+
# per subscription id, so execution of this callback _blocks_ receipt of
|
48
|
+
# subsequent events for the subscription.
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
class BackoffTimer
|
5
|
+
INCREMENTS_BEFORE_BACKOFF = 5
|
6
|
+
MAX_VALUE = 25
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def increment
|
13
|
+
@increments += 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@increments = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
# returns 1 until the timer has been incremented n times, after which it
|
22
|
+
# returns n^2 (until reaching a set maximum)
|
23
|
+
backoff_time = [ 1, increments - INCREMENTS_BEFORE_BACKOFF + 1 ].max**2
|
24
|
+
[ backoff_time, MAX_VALUE ].min
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_accessor :increments
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "directive"
|
4
|
+
|
5
|
+
module StagedEvent
|
6
|
+
module Configuration
|
7
|
+
extend Directive
|
8
|
+
|
9
|
+
configuration_options do
|
10
|
+
nested :google_pubsub do
|
11
|
+
option :credentials
|
12
|
+
option :topic_map
|
13
|
+
option :subscription_ids
|
14
|
+
end
|
15
|
+
|
16
|
+
nested :publisher do
|
17
|
+
option :batch_size
|
18
|
+
end
|
19
|
+
|
20
|
+
nested :subscriber do
|
21
|
+
option :event_received_callback
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
syntax = 'proto3';
|
2
|
+
|
3
|
+
package staged_event;
|
4
|
+
|
5
|
+
import "google/protobuf/any.proto";
|
6
|
+
|
7
|
+
// EventEnvelope allows any protobuf object to be embedded into its Any field.
|
8
|
+
// By doing so, type information about the embedded object is included. This allows
|
9
|
+
// a receiver to deserialize the object without knowing its type ahead of time.
|
10
|
+
|
11
|
+
message EventEnvelope {
|
12
|
+
google.protobuf.Any event = 1;
|
13
|
+
string uuid = 2;
|
14
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: lib/staged_event/event_envelope.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
require 'google/protobuf/any_pb'
|
7
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
8
|
+
add_file("lib/staged_event/event_envelope.proto", :syntax => :proto3) do
|
9
|
+
add_message "staged_event.EventEnvelope" do
|
10
|
+
optional :event, :message, 1, "google.protobuf.Any"
|
11
|
+
optional :uuid, :string, 2
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module StagedEvent
|
17
|
+
EventEnvelope = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("staged_event.EventEnvelope").msgclass
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
module GooglePubSub
|
5
|
+
class Helper
|
6
|
+
class << self
|
7
|
+
def new_google_pubsub
|
8
|
+
credentials = Configuration.config.google_pubsub.credentials.to_h
|
9
|
+
project_id = credentials[:project_id]
|
10
|
+
|
11
|
+
Google::Cloud::PubSub.new(
|
12
|
+
project_id: project_id,
|
13
|
+
credentials: credentials,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
module GooglePubSub
|
5
|
+
class Publisher < StagedEvent::Publisher
|
6
|
+
def initialize
|
7
|
+
@google_pubsub = Helper.new_google_pubsub
|
8
|
+
|
9
|
+
raise ArgumentError, "topic_map must be initialized" unless topic_map.is_a?(Hash) && topic_map.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
def publish(model)
|
13
|
+
topic_name = model.topic || topic_map.keys.first
|
14
|
+
topic_id = topic_map.fetch(topic_name)
|
15
|
+
google_topic = google_pubsub.topic(topic_id, skip_lookup: true)
|
16
|
+
|
17
|
+
# https://github.com/googleapis/google-cloud-ruby/blob/720d14d3641a60c0fab0bf8519bdd730a753a897/google-cloud-pubsub/lib/google/cloud/pubsub/topic.rb#L655
|
18
|
+
google_topic.publish(model.data)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :google_pubsub
|
24
|
+
|
25
|
+
def topic_map
|
26
|
+
Configuration.config.google_pubsub.topic_map
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
module GooglePubSub
|
5
|
+
class Subscriber < StagedEvent::Subscriber
|
6
|
+
include Technologic
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@google_pubsub = Helper.new_google_pubsub
|
10
|
+
|
11
|
+
raise ArgumentError, "event_received_callback is undefined" unless event_received_callback.respond_to?(:call)
|
12
|
+
end
|
13
|
+
|
14
|
+
def receive_events
|
15
|
+
threads = []
|
16
|
+
|
17
|
+
subscription_ids.each do |subscription_id|
|
18
|
+
threads << Thread.new do
|
19
|
+
receive_events_from_subscription(subscription_id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
threads.each(&:join)
|
24
|
+
end
|
25
|
+
|
26
|
+
def receive_events_from_subscription(subscription_id)
|
27
|
+
subscription = google_pubsub.subscription(subscription_id)
|
28
|
+
|
29
|
+
loop do
|
30
|
+
received_messages = subscription.pull(immediate: false)
|
31
|
+
received_messages.each do |received_message|
|
32
|
+
event_received_callback.call(received_message.data)
|
33
|
+
received_message.acknowledge!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue StandardError => exception
|
37
|
+
error :subscription_failed, exception: exception.message
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO: Google pub/sub has built-in multi-threaded listeners but I haven't
|
42
|
+
# been able to successfully receive any messages using that API yet.
|
43
|
+
#
|
44
|
+
# def receive_events_from_subscription(subscription_id)
|
45
|
+
# subscription = google_pubsub.subscription(subscription_id)
|
46
|
+
# subscriber = subscription.listen(threads: { callback: 5 }) do |message|
|
47
|
+
# event_received_callback.call(message.data)
|
48
|
+
# message.acknowledge!
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# # Start background threads that will call the block passed to listen.
|
52
|
+
# subscriber.start
|
53
|
+
#
|
54
|
+
# # Block, letting processing threads continue in the background
|
55
|
+
# sleep
|
56
|
+
# end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
attr_reader :google_pubsub
|
61
|
+
|
62
|
+
def subscription_ids
|
63
|
+
Configuration.config.google_pubsub.subscription_ids
|
64
|
+
end
|
65
|
+
|
66
|
+
def event_received_callback
|
67
|
+
Configuration.config.subscriber.event_received_callback
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
class PublisherProcess
|
5
|
+
include Technologic
|
6
|
+
|
7
|
+
def initialize(publisher)
|
8
|
+
@publisher = publisher
|
9
|
+
@backoff_timer = BackoffTimer.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
loop do
|
14
|
+
sleep(backoff_timer.value)
|
15
|
+
publish_next_batch
|
16
|
+
end
|
17
|
+
rescue StandardError => exception
|
18
|
+
error :publish_failed, exception: exception.message
|
19
|
+
backoff_timer.increment
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :publisher, :backoff_timer
|
26
|
+
|
27
|
+
def publish_next_batch
|
28
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
29
|
+
ActiveRecord::Base.transaction do
|
30
|
+
events = Model.order(:created_at).limit(batch_size).lock("FOR UPDATE SKIP LOCKED")
|
31
|
+
if events.any?
|
32
|
+
events.each do |event|
|
33
|
+
publisher.publish(event)
|
34
|
+
end
|
35
|
+
events.destroy_all
|
36
|
+
backoff_timer.reset
|
37
|
+
else
|
38
|
+
backoff_timer.increment
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def batch_size
|
45
|
+
Configuration.config.publisher.batch_size
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StagedEvent
|
4
|
+
class SubscriberProcess
|
5
|
+
include Technologic
|
6
|
+
|
7
|
+
def initialize(subscriber)
|
8
|
+
@subscriber = subscriber
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
subscriber.receive_events
|
13
|
+
rescue StandardError => exception
|
14
|
+
error :subscriber_main_loop_failed, exception: exception.message
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :subscriber
|
21
|
+
end
|
22
|
+
end
|
data/lib/staged_event.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "google/cloud/pubsub"
|
4
|
+
|
5
|
+
require_relative "staged_event/backoff_timer"
|
6
|
+
require_relative "staged_event/configuration"
|
7
|
+
require_relative "staged_event/event_envelope_pb"
|
8
|
+
require_relative "staged_event/model"
|
9
|
+
require_relative "staged_event/publisher"
|
10
|
+
require_relative "staged_event/subscriber"
|
11
|
+
require_relative "staged_event/publisher_process"
|
12
|
+
require_relative "staged_event/subscriber_process"
|
13
|
+
require_relative "staged_event/version"
|
14
|
+
|
15
|
+
require_relative "staged_event/google_pub_sub/helper"
|
16
|
+
require_relative "staged_event/google_pub_sub/publisher"
|
17
|
+
require_relative "staged_event/google_pub_sub/subscriber"
|
18
|
+
|
19
|
+
require_relative "staged_event/railtie" if defined?(Rails)
|
20
|
+
|
21
|
+
module StagedEvent
|
22
|
+
class Error < StandardError; end
|
23
|
+
|
24
|
+
class DeserializationError < Error; end
|
25
|
+
class UnknownEventTypeError < Error; end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
# Builds an ActiveRecord model from a proto object representing an event.
|
29
|
+
# If the model is committed to the database, it will be published by the
|
30
|
+
# publisher process.
|
31
|
+
#
|
32
|
+
# @param [Instance of a protobuf generated class] proto the event data to publish
|
33
|
+
# @option kwargs [String] :topic the topic that will receive the event
|
34
|
+
# @return [StagedEvent::Model] an ActiveRecord model ready to be saved
|
35
|
+
def from_proto(proto, **kwargs)
|
36
|
+
uuid = SecureRandom.uuid
|
37
|
+
|
38
|
+
envelope = EventEnvelope.new(
|
39
|
+
event: {
|
40
|
+
type_url: proto.class.descriptor.name,
|
41
|
+
value: proto.class.encode(proto),
|
42
|
+
},
|
43
|
+
uuid: uuid,
|
44
|
+
)
|
45
|
+
|
46
|
+
data = EventEnvelope.encode(envelope)
|
47
|
+
topic = kwargs.fetch(:topic, nil)
|
48
|
+
|
49
|
+
Model.new(id: uuid, data: data, topic: topic)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Converts serialized event data received from a publisher into an object with
|
53
|
+
# the event (and its metadata) in accessible form
|
54
|
+
#
|
55
|
+
# @param [String] serialized_data the serialized event data
|
56
|
+
# @return [OpenStruct] an object representing the event
|
57
|
+
def deserialize_event(serialized_data)
|
58
|
+
envelope = EventEnvelope.decode(serialized_data)
|
59
|
+
event_descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(envelope.event.type_url)
|
60
|
+
raise UnknownEventTypeError if event_descriptor.blank?
|
61
|
+
|
62
|
+
proto = event_descriptor.msgclass.decode(envelope.event.value)
|
63
|
+
|
64
|
+
OpenStruct.new(
|
65
|
+
id: envelope.uuid,
|
66
|
+
data: proto,
|
67
|
+
)
|
68
|
+
rescue Google::Protobuf::ParseError
|
69
|
+
raise DeserializationError
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: staged_event
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Cross
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: directive
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.23.1.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.23.1.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: google-protobuf
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.8.0
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 4.0.0
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 3.8.0
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 4.0.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: google-cloud-pubsub
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: byebug
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: database_cleaner
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: faker
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rspec
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: sqlite3
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: rake
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
type: :development
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
description: A transactional outbox implementation for Ruby/ActiveRecord
|
160
|
+
email: andrew.cross@freshly.com
|
161
|
+
executables: []
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files: []
|
164
|
+
files:
|
165
|
+
- README.md
|
166
|
+
- lib/generators/staged_event/install_generator.rb
|
167
|
+
- lib/generators/staged_event/templates/create_staged_events.rb.erb
|
168
|
+
- lib/generators/staged_event/templates/initializer.rb.erb
|
169
|
+
- lib/staged_event.rb
|
170
|
+
- lib/staged_event/backoff_timer.rb
|
171
|
+
- lib/staged_event/configuration.rb
|
172
|
+
- lib/staged_event/event_envelope.proto
|
173
|
+
- lib/staged_event/event_envelope_pb.rb
|
174
|
+
- lib/staged_event/google_pub_sub/helper.rb
|
175
|
+
- lib/staged_event/google_pub_sub/publisher.rb
|
176
|
+
- lib/staged_event/google_pub_sub/subscriber.rb
|
177
|
+
- lib/staged_event/model.rb
|
178
|
+
- lib/staged_event/publisher.rb
|
179
|
+
- lib/staged_event/publisher_process.rb
|
180
|
+
- lib/staged_event/railtie.rb
|
181
|
+
- lib/staged_event/subscriber.rb
|
182
|
+
- lib/staged_event/subscriber_process.rb
|
183
|
+
- lib/staged_event/version.rb
|
184
|
+
- lib/tasks/publisher.rake
|
185
|
+
- lib/tasks/subscriber.rake
|
186
|
+
homepage: https://github.com/Freshly/staged_event
|
187
|
+
licenses:
|
188
|
+
- MIT
|
189
|
+
metadata: {}
|
190
|
+
post_install_message:
|
191
|
+
rdoc_options: []
|
192
|
+
require_paths:
|
193
|
+
- lib
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
requirements: []
|
205
|
+
rubygems_version: 3.2.15
|
206
|
+
signing_key:
|
207
|
+
specification_version: 4
|
208
|
+
summary: StagedEvent
|
209
|
+
test_files: []
|