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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +188 -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 +47 -19
  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 +6 -0
  8. data/lib/nulogy_message_bus_producer/repopulate_replication_slots.rb +25 -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} +1 -2
  11. data/lib/nulogy_message_bus_producer/{public_subscription_event.rb → subscription_event.rb} +1 -1
  12. data/lib/nulogy_message_bus_producer/subscriptions/no_variables.rb +43 -0
  13. data/lib/nulogy_message_bus_producer/subscriptions/postgres_transport.rb +85 -0
  14. data/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker.rb +70 -0
  15. data/lib/nulogy_message_bus_producer/version.rb +1 -1
  16. data/lib/tasks/engine/message_bus_producer.rake +11 -0
  17. data/spec/dummy/config/database.yml +1 -1
  18. data/spec/dummy/config/puma.rb +2 -2
  19. data/spec/dummy/db/migrate/20201005164116_create_active_storage_tables.active_storage.rb +5 -0
  20. data/spec/dummy/db/schema.rb +3 -5
  21. data/spec/dummy/log/development.log +510 -0
  22. data/spec/dummy/log/test.log +18126 -0
  23. data/spec/integration/lib/nulogy_message_bus_producer/repopulate_replication_slots_spec.rb +141 -0
  24. data/spec/integration/lib/nulogy_message_bus_producer/subscriber_graphql_schema_validator_spec.rb +49 -0
  25. data/spec/integration/lib/nulogy_message_bus_producer/subscription_spec.rb +61 -0
  26. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/no_variables_spec.rb +46 -0
  27. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/postgres_transport_spec.rb +135 -0
  28. data/spec/integration/lib/nulogy_message_bus_producer/subscriptions/risky_subscription_blocker_spec.rb +49 -0
  29. data/spec/integration_spec_helper.rb +5 -0
  30. data/spec/spec_helper.rb +0 -40
  31. data/spec/support/kafka.rb +105 -0
  32. data/spec/support/kafka_connect.rb +31 -0
  33. data/spec/support/spec_utils.rb +16 -0
  34. data/spec/support/sql_helpers.rb +45 -0
  35. data/spec/support/subscription_helpers.rb +52 -0
  36. data/spec/support/test_graphql_schema.rb +48 -0
  37. metadata +89 -38
  38. data/lib/nulogy_message_bus_producer/postgres_public_subscriptions.rb +0 -117
  39. data/spec/integration/lib/graphql_api/postgres_public_subscriptions_spec.rb +0 -122
  40. data/spec/integration/lib/graphql_api/validators/subscriber_graphql_schema_validator_spec.rb +0 -76
  41. data/spec/unit/lib/graphql_api/models/public_subscription_spec.rb +0 -66
  42. 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
@@ -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
@@ -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"