nulogy_message_bus_producer 1.0.4 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +183 -15
  3. data/Rakefile +6 -2
  4. data/db/migrate/20201005150212_rename_tenant_id_and_public.rb +6 -0
  5. data/lib/nulogy_message_bus_producer.rb +61 -25
  6. data/lib/nulogy_message_bus_producer/{base_public_subscription.rb → base_subscription.rb} +1 -1
  7. data/lib/nulogy_message_bus_producer/config.rb +72 -0
  8. data/lib/nulogy_message_bus_producer/repopulate_replication_slots.rb +23 -0
  9. data/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator.rb +1 -1
  10. data/lib/nulogy_message_bus_producer/{public_subscription.rb → subscription.rb} +4 -3
  11. data/lib/nulogy_message_bus_producer/{public_subscription_event.rb → subscription_event.rb} +1 -1
  12. data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +85 -0
  13. data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +58 -0
  14. data/lib/nulogy_message_bus_producer/version.rb +1 -1
  15. data/lib/tasks/engine/message_bus_producer.rake +11 -0
  16. data/spec/dummy/config/database.yml +1 -1
  17. data/spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb +5 -0
  18. data/spec/dummy/db/schema.rb +3 -5
  19. data/spec/dummy/log/development.log +2217 -31
  20. data/spec/dummy/log/test.log +27556 -16
  21. data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +133 -0
  22. data/spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb +49 -0
  23. data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +63 -0
  24. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +137 -0
  25. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +51 -0
  26. data/spec/integration_spec_helper.rb +6 -0
  27. data/spec/spec_helper.rb +0 -25
  28. data/spec/support/kafka.rb +98 -0
  29. data/spec/support/kafka_connect.rb +31 -0
  30. data/spec/support/spec_utils.rb +15 -0
  31. data/spec/support/sql_helpers.rb +47 -0
  32. data/spec/support/subscription_helpers.rb +52 -0
  33. data/spec/support/test_graphql_schema.rb +47 -0
  34. metadata +88 -39
  35. data/lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb +0 -102
  36. data/spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb +0 -16
  37. data/spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb +0 -76
  38. data/spec/unit/lib/graphql_api/models/public_subscription_spec.rb +0 -56
  39. 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
@@ -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
@@ -1,6 +0,0 @@
1
- # Make all rspec configuration changes to this file.
2
- # Leave automatically generated configuration files untouched to facilitate gem upgrades.
3
-
4
- require "nulogy_message_bus_producer"
5
- require "rspec/core"
6
- require "spec_helper"