deimos-ruby 1.8.1.pre.beta1 → 1.8.1.pre.beta2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 335328a0ca5fb9147405485a66944ebe0d05af552ec09ec9605d76a7e7da7e9a
4
- data.tar.gz: 438a9a91bda52be144d63867a4ae42dd1901cc7472f61232044aa934efebb464
3
+ metadata.gz: e6f44746ef34713f973b24f4cf80954915545edec5e0f006e4d1a6c751478afa
4
+ data.tar.gz: 17adb23fdc3e735cf8eab51f4c1df2bbcf1140c9a5696c0f8e17dc38bd728ba9
5
5
  SHA512:
6
- metadata.gz: 3e01c5c9db93cb6e0f56dafa2f1b301c30b9cc11c397ca35f77b80cfe62bd9600eacb8774516ee1c81337193687944a8c7bec3cba400df6ea8b1c33af7e82c27
7
- data.tar.gz: 9de8cea35db671c43123d096653a6423ccfcaad8a2b8e9478f787e879bd7fd50a626604b8626f79b5f8654c84130fded58385d6f75fe44e59e1bfb5a284e1096
6
+ metadata.gz: d920dc6b7b2020364a7095ab61a986a3bb16c5fac5367227ed8cbded422399e78887fc91ecce81d9857c66bce5739f6423335e835ca87612a1a2306c50cd93b0
7
+ data.tar.gz: 78fd60871b0852b03e91e2223afbe5ac419dc929d6de6340799f8376f89efffd2775e4d93025ab8089cac5a7579d00bc33c64c3f9ec50cc3fb603f6381e3d82a
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
+ ## 1.8.1-beta2 - 2020-07-28
11
+
12
+ ### Features :star:
13
+ - Add `SchemaControllerMixin` to encode and decode schema-encoded
14
+ payloads in Rails controllers.
15
+
10
16
  ## 1.8.1-beta1 - 2020-07-22
11
17
 
12
18
  ### Fixes :wrench:
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deimos-ruby (1.8.1.pre.beta1)
4
+ deimos-ruby (1.8.1.pre.beta2)
5
5
  avro_turf (~> 0.11)
6
6
  phobos (~> 1.9)
7
7
  ruby-kafka (~> 0.7)
@@ -87,7 +87,7 @@ GEM
87
87
  rake (~> 13.0)
88
88
  dogstatsd-ruby (4.8.1)
89
89
  erubi (1.9.0)
90
- excon (0.75.0)
90
+ excon (0.76.0)
91
91
  exponential-backoff (0.0.4)
92
92
  ffi (1.13.1)
93
93
  formatador (0.2.5)
@@ -205,6 +205,14 @@ GEM
205
205
  rspec-mocks (3.9.1)
206
206
  diff-lcs (>= 1.2.0, < 2.0)
207
207
  rspec-support (~> 3.9.0)
208
+ rspec-rails (4.0.1)
209
+ actionpack (>= 4.2)
210
+ activesupport (>= 4.2)
211
+ railties (>= 4.2)
212
+ rspec-core (~> 3.9)
213
+ rspec-expectations (~> 3.9)
214
+ rspec-mocks (~> 3.9)
215
+ rspec-support (~> 3.9)
208
216
  rspec-support (3.9.3)
209
217
  rspec_junit_formatter (0.4.1)
210
218
  rspec-core (>= 2, < 4, != 2.12.0)
@@ -250,7 +258,6 @@ PLATFORMS
250
258
  ruby
251
259
 
252
260
  DEPENDENCIES
253
- activerecord (~> 6)
254
261
  activerecord-import
255
262
  avro (~> 1.9)
256
263
  database_cleaner (~> 1.7)
@@ -265,6 +272,7 @@ DEPENDENCIES
265
272
  rails (~> 6)
266
273
  rake (~> 13)
267
274
  rspec (~> 3)
275
+ rspec-rails (~> 4)
268
276
  rspec_junit_formatter (~> 0.3)
269
277
  rubocop (~> 0.72)
270
278
  rubocop-rspec (~> 1.27)
data/README.md CHANGED
@@ -22,6 +22,7 @@ Built on Phobos and hence Ruby-Kafka.
22
22
  * [Kafka Message Keys](#kafka-message-keys)
23
23
  * [Consumers](#consumers)
24
24
  * [Rails Integration](#rails-integration)
25
+ * [Controller Mixin](#controller-mixin)
25
26
  * [Database Backend](#database-backend)
26
27
  * [Database Poller](#database-poller)
27
28
  * [Running Consumers](#running-consumers)
@@ -447,6 +448,58 @@ class Widget < ActiveRecord::Base
447
448
  end
448
449
  ```
449
450
 
451
+ ### Controller Mixin
452
+
453
+ Deimos comes with a mixin for `ActionController` which automatically encodes and decodes schema
454
+ payloads. There are some advantages to encoding your data in e.g. Avro rather than straight JSON,
455
+ particularly if your service is talking to another backend service rather than the front-end
456
+ browser:
457
+
458
+ * It enforces a contract between services. Solutions like [OpenAPI](https://swagger.io/specification/)
459
+ do this as well, but in order for the client to know the contract, usually some kind of code
460
+ generation has to happen. Using schemas ensures both sides know the contract without having to change code.
461
+ In addition, OpenAPI is now a huge and confusing format, and using simpler schema formats
462
+ can be beneficial.
463
+ * Using Avro or Protobuf ensures both forwards and backwards compatibility,
464
+ which reduces the need for versioning since both sides can simply ignore fields they aren't aware
465
+ of.
466
+ * Encoding and decoding using Avro or Protobuf is generally faster than straight JSON, and
467
+ results in smaller payloads and therefore less network traffic.
468
+
469
+ To use the mixin, add the following to your `WhateverController`:
470
+
471
+ ```ruby
472
+ class WhateverController < ApplicationController
473
+ include Deimos::Utils::SchemaControllerMixin
474
+
475
+ request_namespace 'my.namespace.requests'
476
+ response_namespace 'my.namespace.responses'
477
+
478
+ # Add a "schemas" line for all routes that should encode/decode schemas.
479
+ # Default is to match the schema name to the route name.
480
+ schemas :index
481
+ # will look for: my.namespace.requests.Index.avsc
482
+ # my.namespace.responses.Index.avsc
483
+
484
+ # If all routes use the default, you can add them all at once
485
+ schemas :index, :show, :update
486
+
487
+ # Different schemas can be specified as well
488
+ schemas :index, :show, request: 'IndexRequest', response: 'IndexResponse'
489
+
490
+ # To access the encoded data, use the `payload` helper method, and to render it back,
491
+ # use the `render_schema` method.
492
+
493
+ def index
494
+ response = { 'response_id' => payload['request_id'] + 'hi mom' }
495
+ render_schema(response)
496
+ end
497
+ end
498
+ ```
499
+
500
+ To make use of this feature, your requests and responses need to have the correct content type.
501
+ For Avro content, this is the `avro/binary` content type.
502
+
450
503
  # Database Backend
451
504
 
452
505
  Deimos provides a way to allow Kafka messages to be created inside a
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency('ruby-kafka', '~> 0.7')
24
24
  spec.add_runtime_dependency('sigurd', '0.0.1')
25
25
 
26
- spec.add_development_dependency('activerecord', '~> 6')
27
26
  spec.add_development_dependency('activerecord-import')
28
27
  spec.add_development_dependency('avro', '~> 1.9')
29
28
  spec.add_development_dependency('database_cleaner', '~> 1.7')
@@ -38,6 +37,7 @@ Gem::Specification.new do |spec|
38
37
  spec.add_development_dependency('rake', '~> 13')
39
38
  spec.add_development_dependency('rspec', '~> 3')
40
39
  spec.add_development_dependency('rspec_junit_formatter', '~>0.3')
40
+ spec.add_development_dependency('rspec-rails', '~> 4')
41
41
  spec.add_development_dependency('rubocop', '~> 0.72')
42
42
  spec.add_development_dependency('rubocop-rspec', '~> 1.27')
43
43
  spec.add_development_dependency('sqlite3', '~> 1.3')
@@ -23,6 +23,7 @@ require 'deimos/monkey_patches/phobos_producer'
23
23
  require 'deimos/monkey_patches/phobos_cli'
24
24
 
25
25
  require 'deimos/railtie' if defined?(Rails)
26
+ require 'deimos/utils/schema_controller_mixin' if defined?(ActionController)
26
27
 
27
28
  if defined?(ActiveRecord)
28
29
  require 'deimos/kafka_source'
@@ -82,6 +82,11 @@ module Deimos
82
82
  :avro_validation
83
83
  end
84
84
 
85
+ # @override
86
+ def self.content_type
87
+ 'avro/binary'
88
+ end
89
+
85
90
  private
86
91
 
87
92
  # @param schema [String]
@@ -71,6 +71,12 @@ module Deimos
71
71
  :mock
72
72
  end
73
73
 
74
+ # The content type to use when encoding / decoding requests over HTTP via ActionController.
75
+ # @return [String]
76
+ def self.content_type
77
+ raise NotImplementedError
78
+ end
79
+
74
80
  # Encode a payload. To be defined by subclass.
75
81
  # @param payload [Hash]
76
82
  # @param schema [Symbol|String]
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ module Utils
5
+ # Mixin to automatically decode schema-encoded payloads when given the correct content type,
6
+ # and provide the `render_schema` method to encode the payload for responses.
7
+ module SchemaControllerMixin
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ Mime::Type.register('avro/binary', :avro)
12
+
13
+ attr_accessor :payload
14
+
15
+ if respond_to?(:before_filter)
16
+ before_filter(:decode_schema, if: :schema_format?)
17
+ else
18
+ before_action(:decode_schema, if: :schema_format?)
19
+ end
20
+ end
21
+
22
+ # :nodoc:
23
+ module ClassMethods
24
+ # @return [Hash<String, Hash<Symbol, String>>]
25
+ def schema_mapping
26
+ @schema_mapping ||= {}
27
+ end
28
+
29
+ # Indicate which schemas should be assigned to actions.
30
+ # @param actions [Symbol]
31
+ # @param request [String]
32
+ # @param response [String]
33
+ def schemas(*actions, request: nil, response: nil)
34
+ actions.each do |action|
35
+ request ||= action.to_s.titleize
36
+ response ||= action.to_s.titleize
37
+ schema_mapping[action.to_s] = { request: request, response: response }
38
+ end
39
+ end
40
+
41
+ # @return [Hash<Symbol, String>]
42
+ def namespaces
43
+ @namespaces ||= {}
44
+ end
45
+
46
+ # Set the namespace for both requests and responses.
47
+ # @param name [String]
48
+ def namespace(name)
49
+ request_namespace(name)
50
+ response_namespace(name)
51
+ end
52
+
53
+ # Set the namespace for requests.
54
+ # @param name [String]
55
+ def request_namespace(name)
56
+ namespaces[:request] = name
57
+ end
58
+
59
+ # Set the namespace for repsonses.
60
+ # @param name [String]
61
+ def response_namespace(name)
62
+ namespaces[:response] = name
63
+ end
64
+ end
65
+
66
+ # @return [Boolean]
67
+ def schema_format?
68
+ request.content_type == Deimos.schema_backend_class.content_type
69
+ end
70
+
71
+ # Get the namespace from either an existing instance variable, or tease it out of the schema.
72
+ # @param type [Symbol] :request or :response
73
+ # @return [Array<String, String>] the namespace and schema.
74
+ def parse_namespace(type)
75
+ namespace = self.class.namespaces[type]
76
+ schema = self.class.schema_mapping[params['action']][type]
77
+ if schema.nil?
78
+ raise "No #{type} schema defined for #{params[:controller]}##{params[:action]}!"
79
+ end
80
+
81
+ if namespace.nil?
82
+ last_period = schema.rindex('.')
83
+ namespace, schema = schema.split(last_period)
84
+ end
85
+ if namespace.nil? || schema.nil?
86
+ raise "No request namespace defined for #{params[:controller]}##{params[:action]}!"
87
+ end
88
+
89
+ [namespace, schema]
90
+ end
91
+
92
+ # Decode the payload with the parameters.
93
+ def decode_schema
94
+ namespace, schema = parse_namespace(:request)
95
+ decoder = Deimos.schema_backend(schema: schema, namespace: namespace)
96
+ @payload = decoder.decode(request.body.read).with_indifferent_access
97
+ request.body.rewind if request.body.respond_to?(:rewind)
98
+ end
99
+
100
+ # Render a hash into a payload as specified by the configured schema and namespace.
101
+ # @param payload [Hash]
102
+ def render_schema(payload, schema: nil, namespace: nil)
103
+ namespace, schema = parse_namespace(:response) if !schema && !namespace
104
+ encoder = Deimos.schema_backend(schema: schema, namespace: namespace)
105
+ encoded = encoder.encode(payload)
106
+ response.headers['Content-Type'] = encoder.class.content_type
107
+ send_data(encoded)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.8.1-beta1'
4
+ VERSION = '1.8.1-beta2'
5
5
  end
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.request",
3
+ "name": "Index",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "request_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.request",
3
+ "name": "UpdateRequest",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "update_request_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.response",
3
+ "name": "Index",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "response_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.response",
3
+ "name": "UpdateResponse",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "update_response_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
4
4
  require 'active_record'
5
+ require 'action_controller/railtie'
5
6
  require 'database_cleaner'
6
7
  require 'deimos'
7
8
  require 'deimos/metrics/mock'
@@ -11,6 +12,11 @@ require 'active_support/testing/time_helpers'
11
12
  require 'activerecord-import'
12
13
  require 'handlers/my_batch_consumer'
13
14
  require 'handlers/my_consumer'
15
+ require 'rspec/rails'
16
+
17
+ class DeimosApp < Rails::Application
18
+ end
19
+ DeimosApp.initialize!
14
20
 
15
21
  # Helpers for Executor/DbProducer
16
22
  module TestRunners
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deimos/utils/schema_controller_mixin'
4
+ require 'deimos/schema_backends/avro_local'
5
+
6
+ RSpec.describe Deimos::Utils::SchemaControllerMixin, type: :controller do
7
+
8
+ before(:each) do
9
+ Deimos.configure do
10
+ schema.backend(:avro_local)
11
+ end
12
+ end
13
+
14
+ controller(ActionController::Base) do
15
+ include Deimos::Utils::SchemaControllerMixin # rubocop:disable RSpec/DescribedClass
16
+
17
+ request_namespace 'com.my-namespace.request'
18
+ response_namespace 'com.my-namespace.response'
19
+ schemas :index, :show
20
+ schemas :update, request: 'UpdateRequest', response: 'UpdateResponse'
21
+
22
+ # :nodoc:
23
+ def index
24
+ render_schema({ 'response_id' => payload[:request_id] + ' mom' })
25
+ end
26
+
27
+ # :nodoc:
28
+ def show
29
+ render_schema({ 'response_id' => payload[:request_id] + ' dad' })
30
+ end
31
+
32
+ # :nodoc:
33
+ def update
34
+ render_schema({ 'update_response_id' => payload[:update_request_id] + ' sis' })
35
+ end
36
+ end
37
+
38
+ it 'should render the correct response for index' do
39
+ request_backend = Deimos.schema_backend(schema: 'Index',
40
+ namespace: 'com.my-namespace.request')
41
+ response_backend = Deimos.schema_backend(schema: 'Index',
42
+ namespace: 'com.my-namespace.response')
43
+ request.content_type = 'avro/binary'
44
+ get :index, body: request_backend.encode({ 'request_id' => 'hi' })
45
+ expect(response_backend.decode(response.body)).to eq({ 'response_id' => 'hi mom' })
46
+ end
47
+
48
+ it 'should render the correct response for show' do
49
+ request_backend = Deimos.schema_backend(schema: 'Index',
50
+ namespace: 'com.my-namespace.request')
51
+ response_backend = Deimos.schema_backend(schema: 'Index',
52
+ namespace: 'com.my-namespace.response')
53
+ request.content_type = 'avro/binary'
54
+ get :show, params: { id: 1 }, body: request_backend.encode({ 'request_id' => 'hi' })
55
+ expect(response_backend.decode(response.body)).to eq({ 'response_id' => 'hi dad' })
56
+ end
57
+
58
+ it 'should render the correct response for update' do
59
+ request_backend = Deimos.schema_backend(schema: 'UpdateRequest',
60
+ namespace: 'com.my-namespace.request')
61
+ response_backend = Deimos.schema_backend(schema: 'UpdateResponse',
62
+ namespace: 'com.my-namespace.response')
63
+ request.content_type = 'avro/binary'
64
+ post :update, params: { id: 1 }, body: request_backend.encode({ 'update_request_id' => 'hi' })
65
+ expect(response_backend.decode(response.body)).to eq({ 'update_response_id' => 'hi sis' })
66
+ end
67
+
68
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1.pre.beta1
4
+ version: 1.8.1.pre.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-22 00:00:00.000000000 Z
11
+ date: 2020-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro_turf
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.0.1
69
- - !ruby/object:Gem::Dependency
70
- name: activerecord
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '6'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '6'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: activerecord-import
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -276,6 +262,20 @@ dependencies:
276
262
  - - "~>"
277
263
  - !ruby/object:Gem::Version
278
264
  version: '0.3'
265
+ - !ruby/object:Gem::Dependency
266
+ name: rspec-rails
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - "~>"
270
+ - !ruby/object:Gem::Version
271
+ version: '4'
272
+ type: :development
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - "~>"
277
+ - !ruby/object:Gem::Version
278
+ version: '4'
279
279
  - !ruby/object:Gem::Dependency
280
280
  name: rubocop
281
281
  requirement: !ruby/object:Gem::Requirement
@@ -399,6 +399,7 @@ files:
399
399
  - lib/deimos/utils/deadlock_retry.rb
400
400
  - lib/deimos/utils/inline_consumer.rb
401
401
  - lib/deimos/utils/lag_reporter.rb
402
+ - lib/deimos/utils/schema_controller_mixin.rb
402
403
  - lib/deimos/version.rb
403
404
  - lib/generators/deimos/active_record/templates/migration.rb.tt
404
405
  - lib/generators/deimos/active_record/templates/model.rb.tt
@@ -450,12 +451,17 @@ files:
450
451
  - spec/schemas/com/my-namespace/Wibble.avsc
451
452
  - spec/schemas/com/my-namespace/Widget.avsc
452
453
  - spec/schemas/com/my-namespace/WidgetTheSecond.avsc
454
+ - spec/schemas/com/my-namespace/request/Index.avsc
455
+ - spec/schemas/com/my-namespace/request/UpdateRequest.avsc
456
+ - spec/schemas/com/my-namespace/response/Index.avsc
457
+ - spec/schemas/com/my-namespace/response/UpdateResponse.avsc
453
458
  - spec/spec_helper.rb
454
459
  - spec/utils/db_poller_spec.rb
455
460
  - spec/utils/db_producer_spec.rb
456
461
  - spec/utils/deadlock_retry_spec.rb
457
462
  - spec/utils/lag_reporter_spec.rb
458
463
  - spec/utils/platform_schema_validation_spec.rb
464
+ - spec/utils/schema_controller_mixin_spec.rb
459
465
  - support/deimos-solo.png
460
466
  - support/deimos-with-name-next.png
461
467
  - support/deimos-with-name.png
@@ -524,9 +530,14 @@ test_files:
524
530
  - spec/schemas/com/my-namespace/Wibble.avsc
525
531
  - spec/schemas/com/my-namespace/Widget.avsc
526
532
  - spec/schemas/com/my-namespace/WidgetTheSecond.avsc
533
+ - spec/schemas/com/my-namespace/request/Index.avsc
534
+ - spec/schemas/com/my-namespace/request/UpdateRequest.avsc
535
+ - spec/schemas/com/my-namespace/response/Index.avsc
536
+ - spec/schemas/com/my-namespace/response/UpdateResponse.avsc
527
537
  - spec/spec_helper.rb
528
538
  - spec/utils/db_poller_spec.rb
529
539
  - spec/utils/db_producer_spec.rb
530
540
  - spec/utils/deadlock_retry_spec.rb
531
541
  - spec/utils/lag_reporter_spec.rb
532
542
  - spec/utils/platform_schema_validation_spec.rb
543
+ - spec/utils/schema_controller_mixin_spec.rb