nulogy_message_bus_producer 2.0.0 → 3.2.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 +4 -4
- data/README.md +188 -15
- data/Rakefile +6 -2
- data/db/migrate/20201005150212_rename_tenant_id_and_public.rb +6 -0
- data/lib/nulogy_message_bus_producer.rb +47 -19
- data/lib/nulogy_message_bus_producer/{base_public_subscription.rb → base_subscription.rb} +1 -1
- data/lib/nulogy_message_bus_producer/config.rb +6 -0
- data/lib/nulogy_message_bus_producer/repopulate_replication_slots.rb +25 -0
- data/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb +1 -1
- data/lib/nulogy_message_bus_producer/{public_subscription.rb → subscription.rb} +1 -2
- data/lib/nulogy_message_bus_producer/{public_subscription_event.rb → subscription_event.rb} +1 -1
- data/lib/nulogy_message_bus_producer/subscriptions/no_variables.rb +43 -0
- data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +85 -0
- data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +70 -0
- data/lib/nulogy_message_bus_producer/version.rb +1 -1
- data/lib/tasks/engine/message_bus_producer.rake +11 -0
- data/spec/dummy/config/database.yml +1 -1
- data/spec/dummy/config/puma.rb +2 -2
- data/spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb +5 -0
- data/spec/dummy/db/schema.rb +3 -5
- data/spec/dummy/log/development.log +510 -0
- data/spec/dummy/log/test.log +18126 -0
- data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +141 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb +49 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +61 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/no_variables_spec.rb +46 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +135 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +49 -0
- data/spec/integration_spec_helper.rb +5 -0
- data/spec/spec_helper.rb +0 -40
- data/spec/support/kafka.rb +105 -0
- data/spec/support/kafka_connect.rb +31 -0
- data/spec/support/spec_utils.rb +16 -0
- data/spec/support/sql_helpers.rb +45 -0
- data/spec/support/subscription_helpers.rb +52 -0
- data/spec/support/test_graphql_schema.rb +48 -0
- metadata +89 -38
- data/lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb +0 -117
- data/spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb +0 -122
- data/spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb +0 -76
- data/spec/unit/lib/graphql_api/models/public_subscription_spec.rb +0 -66
- data/spec/unit_spec_helper.rb +0 -6
@@ -1,117 +0,0 @@
|
|
1
|
-
module NulogyMessageBusProducer
|
2
|
-
# Subscription class to `use` when developing Message Bus-backed subscriptions
|
3
|
-
# For example,
|
4
|
-
#
|
5
|
-
# class SomeSchema < GraphQL::Schema
|
6
|
-
# ...
|
7
|
-
# use NulogyMessageBusProducer::PostgresPublicSubscriptions
|
8
|
-
# end
|
9
|
-
#
|
10
|
-
# It expects that schema to already be registered, or will raise an error.
|
11
|
-
#
|
12
|
-
# NulogyMessageBusProducer.register_schema("some_schema", "SomeSchema")
|
13
|
-
class PostgresPublicSubscriptions < GraphQL::Subscriptions
|
14
|
-
def initialize(options = {})
|
15
|
-
super
|
16
|
-
|
17
|
-
@schema_key = NulogyMessageBusProducer.resolve_schema_key(options.fetch(:schema))
|
18
|
-
end
|
19
|
-
|
20
|
-
# This method is copied from GraphQL::Subscriptions and customized.
|
21
|
-
# Check for changes in the superclass when upgrading the graphql gem.
|
22
|
-
def execute(subscription_id, event, object) # rubocop:disable Metrics/MethodLength
|
23
|
-
query_data = read_subscription(subscription_id)
|
24
|
-
query_string = query_data.fetch(:query_string)
|
25
|
-
variables = { id: object[:uuid] }
|
26
|
-
context = object[:context]
|
27
|
-
operation_name = query_data.fetch(:operation_name)
|
28
|
-
result = @schema.execute(
|
29
|
-
query: query_string,
|
30
|
-
context: context,
|
31
|
-
subscription_topic: event.topic,
|
32
|
-
operation_name: operation_name,
|
33
|
-
variables: variables,
|
34
|
-
root_value: object
|
35
|
-
)
|
36
|
-
deliver(subscription_id, result)
|
37
|
-
rescue GraphQL::Schema::Subscription::NoUpdateError
|
38
|
-
# This update was skipped in user code; do nothing.
|
39
|
-
rescue GraphQL::Schema::Subscription::UnsubscribedError
|
40
|
-
delete_subscription(subscription_id)
|
41
|
-
end
|
42
|
-
|
43
|
-
def each_subscription_id(event)
|
44
|
-
PublicSubscription
|
45
|
-
.where(event_type: event.name, schema_key: @schema_key)
|
46
|
-
.ids
|
47
|
-
.each { |id| yield id }
|
48
|
-
end
|
49
|
-
|
50
|
-
def read_subscription(subscription_id)
|
51
|
-
query_string = PublicSubscription.find_by(id: subscription_id).query
|
52
|
-
|
53
|
-
{
|
54
|
-
query_string: query_string,
|
55
|
-
operation_name: nil
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
def deliver(subscription_id, result)
|
60
|
-
if result["errors"]&.any?
|
61
|
-
NulogyMessageBusProducer.config.handle_event_generation_error(
|
62
|
-
subscription_id: subscription_id,
|
63
|
-
context: result.query.context.object,
|
64
|
-
variables: result.query.provided_variables,
|
65
|
-
result: result
|
66
|
-
)
|
67
|
-
else
|
68
|
-
create_event(subscription_id, result)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def write_subscription(query, events)
|
73
|
-
events.each do |event|
|
74
|
-
PublicSubscription.create_or_update(
|
75
|
-
id: event.arguments[:subscription_id],
|
76
|
-
subscription_group_id: event.arguments[:subscription_group_id],
|
77
|
-
event_type: event.name,
|
78
|
-
schema_key: @schema_key,
|
79
|
-
query: convert_subscription_result_query_to_regular_query(query, event),
|
80
|
-
topic_name: event.arguments[:topic_name]
|
81
|
-
)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def delete_subscription(subscription_id)
|
86
|
-
PublicSubscription.find_by(id: subscription_id).destroy
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
def create_event(subscription_id, result)
|
92
|
-
tenant_id = result.query.context.object[:tenant_id]
|
93
|
-
subscription = PublicSubscription.find_by(id: subscription_id)
|
94
|
-
|
95
|
-
PublicSubscriptionEvent.create_or_update(
|
96
|
-
id: SecureRandom.uuid,
|
97
|
-
public_subscription_id: subscription_id,
|
98
|
-
partition_key: "#{subscription.subscription_group_id},#{tenant_id}",
|
99
|
-
tenant_id: tenant_id,
|
100
|
-
event_json: result.to_h["data"],
|
101
|
-
topic_name: subscription.topic_name
|
102
|
-
)
|
103
|
-
end
|
104
|
-
|
105
|
-
def convert_subscription_result_query_to_regular_query(query, event)
|
106
|
-
selections = query.document.definitions.first.selections
|
107
|
-
|
108
|
-
event_selection = selections.find { |e| e.name == event.name }
|
109
|
-
|
110
|
-
# TODO: This assumes the query_type.rb takes an argument called 'id'.
|
111
|
-
# This should check the query to lookup the argument name
|
112
|
-
# TODO: Can we do this by manipulating GQL objects?
|
113
|
-
inner_query = event_selection.selections.first.to_query_string.sub("{", "(id: $id) {")
|
114
|
-
"query ($id: UUID!) { #{inner_query} }"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
@@ -1,122 +0,0 @@
|
|
1
|
-
require "integration_spec_helper"
|
2
|
-
|
3
|
-
RSpec.describe NulogyMessageBusProducer::PostgresPublicSubscriptions do
|
4
|
-
context "when the class is not registered" do
|
5
|
-
it "raises" do
|
6
|
-
unregistered_schema = Class.new(GraphQL::Schema) do
|
7
|
-
use(NulogyMessageBusProducer::PostgresPublicSubscriptions)
|
8
|
-
end
|
9
|
-
|
10
|
-
expect do
|
11
|
-
# this find triggers the plugin to be loaded
|
12
|
-
unregistered_schema.find("something")
|
13
|
-
end.to raise_error(KeyError, /The schema registry did not contain an entry/)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
context "when subscription is triggered" do
|
18
|
-
before do
|
19
|
-
NulogyMessageBusProducer.config.register_schema(
|
20
|
-
schema: "NulogyMessageBusProducer::Specs::TestSchema",
|
21
|
-
key: "test"
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "generates events" do
|
26
|
-
create_subscription(event_type: "testCreated")
|
27
|
-
create_subscription(event_type: "testCreated")
|
28
|
-
|
29
|
-
uuid = SecureRandom.uuid
|
30
|
-
|
31
|
-
expect do
|
32
|
-
NulogyMessageBusProducer::Specs::TestSchema.subscriptions.trigger(
|
33
|
-
"testCreated",
|
34
|
-
{},
|
35
|
-
{ uuid: uuid, tenant_id: SecureRandom.uuid }
|
36
|
-
)
|
37
|
-
end.to change(NulogyMessageBusProducer::PublicSubscriptionEvent, :count).by(2)
|
38
|
-
|
39
|
-
event_data = NulogyMessageBusProducer::PublicSubscriptionEvent.pluck(:event_json)
|
40
|
-
expect(event_data).to all(include("foo" => { "id" => uuid }))
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
context "when a subscription error occurs" do
|
45
|
-
context "when failing with raise" do
|
46
|
-
before do
|
47
|
-
NulogyMessageBusProducer.config.producing_events_fails_with(:raise)
|
48
|
-
end
|
49
|
-
|
50
|
-
it "raises an exception" do
|
51
|
-
expect { trigger_erroneous_subscription }.to raise_error(/A subscription event could not be produced/)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
context "when failing with a soft fail" do
|
56
|
-
before do
|
57
|
-
NulogyMessageBusProducer.config.producing_events_fails_with(:soft_fail)
|
58
|
-
end
|
59
|
-
|
60
|
-
it "does not raise an error" do
|
61
|
-
trigger_erroneous_subscription
|
62
|
-
end
|
63
|
-
|
64
|
-
it "calls the provided block" do
|
65
|
-
called = false
|
66
|
-
|
67
|
-
NulogyMessageBusProducer.config.producing_events_fails_with(:soft_fail) do |_|
|
68
|
-
called = true
|
69
|
-
end
|
70
|
-
|
71
|
-
trigger_erroneous_subscription
|
72
|
-
|
73
|
-
expect(called).to be(true)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def trigger_erroneous_subscription
|
79
|
-
NulogyMessageBusProducer.config.register_schema(schema: "NulogyMessageBusProducer::Specs::TestSchema", key: "test")
|
80
|
-
|
81
|
-
event_type = "testCreated"
|
82
|
-
|
83
|
-
subscription = create_subscription(event_type: event_type)
|
84
|
-
simulate_invalid_query(subscription)
|
85
|
-
|
86
|
-
NulogyMessageBusProducer::Specs::TestSchema.subscriptions.trigger(
|
87
|
-
event_type,
|
88
|
-
{},
|
89
|
-
{ uuid: SecureRandom.uuid, tenant_id: SecureRandom.uuid }
|
90
|
-
)
|
91
|
-
end
|
92
|
-
|
93
|
-
def create_subscription(**overrides) # rubocop:disable Metrics/MethodLength
|
94
|
-
args = {
|
95
|
-
id: SecureRandom.uuid,
|
96
|
-
subscription_group_id: SecureRandom.uuid,
|
97
|
-
topic_name: "test_topic",
|
98
|
-
schema_key: "test",
|
99
|
-
event_type: "test_event_type",
|
100
|
-
query: <<~QUERY
|
101
|
-
query ($id: UUID!) {
|
102
|
-
foo (id: $id) {
|
103
|
-
id
|
104
|
-
}
|
105
|
-
}
|
106
|
-
QUERY
|
107
|
-
}.merge(overrides)
|
108
|
-
|
109
|
-
NulogyMessageBusProducer::PublicSubscription.create_or_update(args)
|
110
|
-
end
|
111
|
-
|
112
|
-
def simulate_invalid_query(subscription)
|
113
|
-
subscription.query = <<~QUERY
|
114
|
-
query ($id: UUID!) {
|
115
|
-
foo (id: $id) {
|
116
|
-
a_field_that_does_not_exist
|
117
|
-
}
|
118
|
-
}
|
119
|
-
QUERY
|
120
|
-
subscription.save(validate: false)
|
121
|
-
end
|
122
|
-
end
|
data/spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require "integration_spec_helper"
|
2
|
-
|
3
|
-
RSpec.describe NulogyMessageBusProducer::SubscriberGraphqlSchemaValidator do
|
4
|
-
subject(:validator) { NulogyMessageBusProducer::SubscriberGraphqlSchemaValidator.new }
|
5
|
-
|
6
|
-
before do
|
7
|
-
NulogyMessageBusProducer.config.register_schema(schema: "NulogyMessageBusProducer::Specs::TestSchema", key: "test")
|
8
|
-
end
|
9
|
-
|
10
|
-
describe "#validate" do
|
11
|
-
context "when a valid query is present" do
|
12
|
-
it "return true" do
|
13
|
-
create_subscription(<<~QUERY)
|
14
|
-
query ($id: UUID!) {
|
15
|
-
foo (id: $id) {
|
16
|
-
id
|
17
|
-
}
|
18
|
-
}
|
19
|
-
QUERY
|
20
|
-
|
21
|
-
expect(validator.validate).to be(true)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "when an invalid query is present" do
|
26
|
-
let(:subscription_with_error) do
|
27
|
-
subscription = create_subscription(<<~QUERY)
|
28
|
-
query ($id: UUID!) {
|
29
|
-
foo (id: $id) {
|
30
|
-
id
|
31
|
-
}
|
32
|
-
}
|
33
|
-
QUERY
|
34
|
-
|
35
|
-
subscription.query = <<~BAD_QUERY
|
36
|
-
query ($id: UUID!) {
|
37
|
-
foo (id: $id) {
|
38
|
-
a_field_that_does_not_exist
|
39
|
-
}
|
40
|
-
}
|
41
|
-
BAD_QUERY
|
42
|
-
subscription.save(validate: false)
|
43
|
-
subscription
|
44
|
-
end
|
45
|
-
|
46
|
-
it "returns false" do
|
47
|
-
subscription_with_error
|
48
|
-
|
49
|
-
expect(validator.validate).to be(false)
|
50
|
-
end
|
51
|
-
|
52
|
-
it "has errors" do
|
53
|
-
subscription_with_error
|
54
|
-
|
55
|
-
validator.validate
|
56
|
-
|
57
|
-
expect(validator.errors).to contain_exactly(
|
58
|
-
"Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject' (id: #{subscription_with_error.id})"
|
59
|
-
)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def create_subscription(query, event_type = "testEvent", schema_key: "test")
|
65
|
-
NulogyMessageBusProducer::PublicSubscription.create_or_update(
|
66
|
-
query: query,
|
67
|
-
event_type: event_type,
|
68
|
-
schema_key: schema_key,
|
69
|
-
|
70
|
-
# not relavant, but needed for DB constraints
|
71
|
-
id: SecureRandom.uuid,
|
72
|
-
subscription_group_id: SecureRandom.uuid,
|
73
|
-
topic_name: SecureRandom.uuid
|
74
|
-
)
|
75
|
-
end
|
76
|
-
end
|
@@ -1,66 +0,0 @@
|
|
1
|
-
require "unit_spec_helper"
|
2
|
-
|
3
|
-
RSpec.describe NulogyMessageBusProducer::PublicSubscription do
|
4
|
-
context "when validating" do
|
5
|
-
before do
|
6
|
-
NulogyMessageBusProducer.config.register_schema schema: "NulogyMessageBusProducer::Specs::TestSchema", key: "test"
|
7
|
-
end
|
8
|
-
|
9
|
-
it "is invalid with a blank query" do
|
10
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(
|
11
|
-
schema_key: "test",
|
12
|
-
query: ""
|
13
|
-
)
|
14
|
-
|
15
|
-
model.validate
|
16
|
-
|
17
|
-
expect(model).to_not be_valid
|
18
|
-
expect(model.errors[:query]).to contain_exactly("can't be blank")
|
19
|
-
end
|
20
|
-
|
21
|
-
it "is invalid with blank schema_key" do
|
22
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(schema_key: "")
|
23
|
-
|
24
|
-
model.validate
|
25
|
-
|
26
|
-
expect(model.errors[:schema_key]).to be_present
|
27
|
-
end
|
28
|
-
|
29
|
-
it "is invalid with an invalid query" do
|
30
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(
|
31
|
-
schema_key: "test",
|
32
|
-
query: <<~QUERY
|
33
|
-
query ($id: UUID!) {
|
34
|
-
foo (id: $id) {
|
35
|
-
a_field_that_does_not_exist
|
36
|
-
}
|
37
|
-
}
|
38
|
-
QUERY
|
39
|
-
)
|
40
|
-
|
41
|
-
model.validate
|
42
|
-
|
43
|
-
expect(model).to_not be_valid
|
44
|
-
expect(model.errors[:query]).to contain_exactly(
|
45
|
-
"Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject' (id: <new_record>)"
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
it "valid with a valid query" do
|
50
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(
|
51
|
-
schema_key: "test",
|
52
|
-
query: <<~QUERY
|
53
|
-
query ($id: UUID!) {
|
54
|
-
foo (id: $id) {
|
55
|
-
id
|
56
|
-
}
|
57
|
-
}
|
58
|
-
QUERY
|
59
|
-
)
|
60
|
-
|
61
|
-
model.validate
|
62
|
-
|
63
|
-
expect(model.errors).to_not include(:query)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
data/spec/unit_spec_helper.rb
DELETED