staged_event 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|