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

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