nulogy_message_bus_producer 2.0.0 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
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"