nulogy_message_bus_producer 1.0.4 → 3.2.0
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 +183 -15
- data/Rakefile +6 -2
- data/db/migrate/20201005150212_rename_tenant_id_and_public.rb +6 -0
- data/lib/nulogy_message_bus_producer.rb +61 -25
- data/lib/nulogy_message_bus_producer/{base_public_subscription.rb → base_subscription.rb} +1 -1
- data/lib/nulogy_message_bus_producer/config.rb +72 -0
- data/lib/nulogy_message_bus_producer/repopulate_replication_slots.rb +23 -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} +4 -3
- data/lib/nulogy_message_bus_producer/{public_subscription_event.rb → subscription_event.rb} +1 -1
- data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +85 -0
- data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +58 -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/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 +2217 -31
- data/spec/dummy/log/test.log +27556 -16
- data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +133 -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 +63 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +137 -0
- data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +51 -0
- data/spec/integration_spec_helper.rb +6 -0
- data/spec/spec_helper.rb +0 -25
- data/spec/support/kafka.rb +98 -0
- data/spec/support/kafka_connect.rb +31 -0
- data/spec/support/spec_utils.rb +15 -0
- data/spec/support/sql_helpers.rb +47 -0
- data/spec/support/subscription_helpers.rb +52 -0
- data/spec/support/test_graphql_schema.rb +47 -0
- metadata +88 -39
- data/lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb +0 -102
- data/spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb +0 -16
- 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 -56
- data/spec/unit_spec_helper.rb +0 -6
@@ -1,102 +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.where(event_type: event.name, schema_key: @schema_key).each do |subscription|
|
45
|
-
yield subscription.id
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def read_subscription(subscription_id)
|
50
|
-
query_string = PublicSubscription.find_by(id: subscription_id).query
|
51
|
-
|
52
|
-
{
|
53
|
-
query_string: query_string,
|
54
|
-
operation_name: nil
|
55
|
-
}
|
56
|
-
end
|
57
|
-
|
58
|
-
def deliver(subscription_id, result)
|
59
|
-
tenant_id = result.query.context.object[:tenant_id]
|
60
|
-
subscription = PublicSubscription.find_by(id: subscription_id)
|
61
|
-
|
62
|
-
PublicSubscriptionEvent.create_or_update(
|
63
|
-
id: SecureRandom.uuid,
|
64
|
-
public_subscription_id: subscription_id,
|
65
|
-
partition_key: "#{subscription.subscription_group_id},#{tenant_id}",
|
66
|
-
tenant_id: tenant_id,
|
67
|
-
event_json: result.to_h["data"],
|
68
|
-
topic_name: subscription.topic_name
|
69
|
-
)
|
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 convert_subscription_result_query_to_regular_query(query, event)
|
92
|
-
selections = query.document.definitions.first.selections
|
93
|
-
|
94
|
-
event_selection = selections.find { |e| e.name == event.name }
|
95
|
-
|
96
|
-
# TODO: This assumes the query_type.rb takes an argument called 'id'. This should check the query to lookup the argument name
|
97
|
-
# TODO: Can we do this by manipulating GQL objects?
|
98
|
-
inner_query = event_selection.selections.first.to_query_string.sub("{", "(id: $id) {")
|
99
|
-
"query ($id: UUID!) { #{inner_query} }"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
@@ -1,16 +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
|
-
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.register_schema("test", "NulogyMessageBusProducer::Specs::TestSchema")
|
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,56 +0,0 @@
|
|
1
|
-
require "unit_spec_helper"
|
2
|
-
|
3
|
-
RSpec.describe NulogyMessageBusProducer::PublicSubscription do
|
4
|
-
context "when validating" do
|
5
|
-
before do
|
6
|
-
NulogyMessageBusProducer.register_schema("test", "NulogyMessageBusProducer::Specs::TestSchema")
|
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 an invalid query" do
|
22
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(
|
23
|
-
schema_key: "test",
|
24
|
-
query: <<~QUERY
|
25
|
-
query ($id: UUID!) {
|
26
|
-
foo (id: $id) {
|
27
|
-
a_field_that_does_not_exist
|
28
|
-
}
|
29
|
-
}
|
30
|
-
QUERY
|
31
|
-
)
|
32
|
-
|
33
|
-
model.validate
|
34
|
-
|
35
|
-
expect(model).to_not be_valid
|
36
|
-
expect(model.errors[:query]).to contain_exactly("Field 'a_field_that_does_not_exist' doesn't exist on type 'testObject' (id: <new_record>)")
|
37
|
-
end
|
38
|
-
|
39
|
-
it "valid with a valid query" do
|
40
|
-
model = NulogyMessageBusProducer::PublicSubscription.new(
|
41
|
-
schema_key: "test",
|
42
|
-
query: <<~QUERY
|
43
|
-
query ($id: UUID!) {
|
44
|
-
foo (id: $id) {
|
45
|
-
id
|
46
|
-
}
|
47
|
-
}
|
48
|
-
QUERY
|
49
|
-
)
|
50
|
-
|
51
|
-
model.validate
|
52
|
-
|
53
|
-
expect(model.errors).to_not include(:query)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/spec/unit_spec_helper.rb
DELETED