sbmt-strangler 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +54 -0
  4. data/.rubocop_todo.yml +0 -0
  5. data/Appraisals +20 -0
  6. data/CHANGELOG.md +106 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +21 -0
  9. data/README.md +86 -0
  10. data/Rakefile +12 -0
  11. data/config/initializers/strangler.rb +5 -0
  12. data/config/initializers/yabeda.rb +40 -0
  13. data/dip.yml +67 -0
  14. data/docker-compose.yml +19 -0
  15. data/docs/img/01-proxy_mode.png +0 -0
  16. data/docs/img/02-mirror_mode.png +0 -0
  17. data/docs/img/03-replace_mode.png +0 -0
  18. data/lefthook-local.dip_example.yml +4 -0
  19. data/lefthook.yml +6 -0
  20. data/lib/sbmt/strangler/action.rb +37 -0
  21. data/lib/sbmt/strangler/action_invoker.rb +43 -0
  22. data/lib/sbmt/strangler/builder.rb +66 -0
  23. data/lib/sbmt/strangler/configurable.rb +32 -0
  24. data/lib/sbmt/strangler/configuration.rb +25 -0
  25. data/lib/sbmt/strangler/const_definer.rb +36 -0
  26. data/lib/sbmt/strangler/controller.rb +30 -0
  27. data/lib/sbmt/strangler/engine.rb +11 -0
  28. data/lib/sbmt/strangler/error_tracker.rb +34 -0
  29. data/lib/sbmt/strangler/errors.rb +7 -0
  30. data/lib/sbmt/strangler/feature_flags.rb +59 -0
  31. data/lib/sbmt/strangler/flipper.rb +38 -0
  32. data/lib/sbmt/strangler/http/client.rb +41 -0
  33. data/lib/sbmt/strangler/http/transport.rb +89 -0
  34. data/lib/sbmt/strangler/http.rb +89 -0
  35. data/lib/sbmt/strangler/logger.rb +48 -0
  36. data/lib/sbmt/strangler/metric_tracker.rb +69 -0
  37. data/lib/sbmt/strangler/mixin.rb +56 -0
  38. data/lib/sbmt/strangler/version.rb +7 -0
  39. data/lib/sbmt/strangler/work_modes/base.rb +22 -0
  40. data/lib/sbmt/strangler/work_modes/mirror.rb +73 -0
  41. data/lib/sbmt/strangler/work_modes/proxy.rb +20 -0
  42. data/lib/sbmt/strangler/work_modes/replace.rb +65 -0
  43. data/lib/sbmt/strangler.rb +84 -0
  44. data/sbmt-strangler.gemspec +59 -0
  45. metadata +473 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Strangler
7
+ module WorkModes
8
+ class Mirror < Base
9
+ include Dry::Monads::Result::Mixin
10
+
11
+ def call
12
+ mirror_task = Concurrent::Promises.future { mirror_call }
13
+ proxy_task = Concurrent::Promises.future { http_request(http_params) }
14
+
15
+ mirror_call_result = mirror_task.value!
16
+ origin_response = proxy_task.value!
17
+
18
+ begin
19
+ track_mirror_call(mirror_call_result.success?)
20
+ if mirror_call_result.success?
21
+ mirror_result = mirror_call_result.value!
22
+ origin_result = copy_of_origin_result(origin_response)
23
+ compare_call_result = compare_call(origin_result, mirror_result)
24
+ track_compare_call(compare_call_result.success?)
25
+ track_compare_call_result(compare_call_result.value!) if compare_call_result.success?
26
+ end
27
+ rescue => err
28
+ handle_error(err)
29
+ end
30
+
31
+ render_origin_response(origin_response)
32
+ end
33
+
34
+ private
35
+
36
+ delegate :http_params, :http_request, :render_origin_response, to: :rails_controller
37
+ delegate :track_mirror_call, :track_compare_call, :track_compare_call_result, to: :metric_tracker
38
+
39
+ def copy_of_origin_result(origin_response)
40
+ if origin_response.success?
41
+ origin_response.value!.deep_dup
42
+ else
43
+ origin_response.failure.deep_dup
44
+ end
45
+ end
46
+
47
+ def mirror_call
48
+ value = strangler_action.mirror.call(rails_controller)
49
+ Success(value)
50
+ rescue => err
51
+ handle_error(err)
52
+ Failure(nil)
53
+ end
54
+
55
+ MATCH_ERROR = :error
56
+
57
+ def compare_call(origin_result, mirror_result)
58
+ cmp = strangler_action.compare.call(origin_result, mirror_result)
59
+ raise "Strangler action compare lambda must return a boolean value instead of #{cmp}!" unless cmp.in?([true, false])
60
+ Success(cmp)
61
+ rescue => err
62
+ handle_error(err)
63
+ Failure(nil)
64
+ end
65
+
66
+ def handle_error(err)
67
+ Sbmt::Strangler.error_tracker.error(err)
68
+ Sbmt::Strangler.logger.error(err)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Strangler
7
+ module WorkModes
8
+ class Proxy < Base
9
+ def call
10
+ origin_response = http_request(http_params)
11
+ render_origin_response(origin_response)
12
+ end
13
+
14
+ private
15
+
16
+ delegate :http_params, :http_request, :render_origin_response, to: :rails_controller
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Strangler
7
+ module WorkModes
8
+ class Replace < Base
9
+ include Dry::Monads::Result::Mixin
10
+
11
+ def call
12
+ mirror_call_result = mirror_call
13
+ track_mirror_call(mirror_call_result.success?)
14
+
15
+ unless mirror_call_result.success?
16
+ render(
17
+ json: {error: "Mirror lambda call failed!"},
18
+ status: :internal_server_error
19
+ ) # TODO: Возможно стоит сделать фолбэк на проксирование?
20
+ return
21
+ end
22
+
23
+ render_call_result = render_call(mirror_call_result.value!)
24
+ track_render_call(render_call_result.success?)
25
+
26
+ unless render_call_result.success?
27
+ render(
28
+ json: {error: "Render lambda call failed!"},
29
+ status: :internal_server_error
30
+ ) # TODO: Возможно стоит сделать фолбэк на проксирование?
31
+ return
32
+ end
33
+
34
+ render render_call_result.value!
35
+ end
36
+
37
+ private
38
+
39
+ delegate :render, to: :rails_controller
40
+ delegate :track_mirror_call, :track_render_call, to: :metric_tracker
41
+
42
+ def mirror_call
43
+ value = strangler_action.mirror.call(rails_controller)
44
+ Success(value)
45
+ rescue => err
46
+ handle_error(err)
47
+ Failure(nil)
48
+ end
49
+
50
+ def render_call(mirror_result)
51
+ value = strangler_action.render.call(mirror_result)
52
+ Success(value)
53
+ rescue => err
54
+ handle_error(err)
55
+ Failure(nil)
56
+ end
57
+
58
+ def handle_error(err)
59
+ Sbmt::Strangler.error_tracker.error(err)
60
+ Sbmt::Strangler.logger.error(err)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "dry-monads"
5
+ require "yabeda"
6
+ require "faraday"
7
+ require "faraday/net_http_persistent"
8
+ require "flipper"
9
+ require "concurrent"
10
+ require "securerandom"
11
+ require "oj"
12
+
13
+ begin
14
+ require "sentry-rails"
15
+ rescue LoadError
16
+ # optional dependency
17
+ end
18
+
19
+ begin
20
+ require "opentelemetry-instrumentation-concurrent_ruby"
21
+ rescue LoadError
22
+ Warning.warn <<~WARN if Object.const_defined?(:Opentelemetry)
23
+ WARNING! It looks like you're using OpenTelemetry but you didn't install an instrumentation for the concurrent-ruby gem.
24
+ sbmt-strangler runs your code using concurrent-ruby futures in mirror mode, so this is very adviced to install
25
+ the instrumentation (opentelemetry-instrumentation-concurrent_ruby gem) to get traces in mirror mode!
26
+ WARN
27
+ end
28
+
29
+ require_relative "strangler/configurable"
30
+ require_relative "strangler/http"
31
+ require_relative "strangler/action"
32
+ require_relative "strangler/controller"
33
+ require_relative "strangler/configuration"
34
+ require_relative "strangler/mixin"
35
+ require_relative "strangler/builder"
36
+ require_relative "strangler/action_invoker"
37
+ require_relative "strangler/const_definer"
38
+ require_relative "strangler/errors"
39
+ require_relative "strangler/error_tracker"
40
+ require_relative "strangler/logger"
41
+ require_relative "strangler/flipper"
42
+ require_relative "strangler/feature_flags"
43
+ require_relative "strangler/metric_tracker"
44
+ require_relative "strangler/work_modes/proxy"
45
+ require_relative "strangler/work_modes/mirror"
46
+ require_relative "strangler/work_modes/replace"
47
+
48
+ require_relative "strangler/engine"
49
+
50
+ module Sbmt
51
+ module Strangler
52
+ module_function
53
+
54
+ # Public: Configure strangler.
55
+ #
56
+ # Sbmt::Strangler.configure do |config|
57
+ # config.controller(...) do |controller|
58
+ # controller.action(...) do {...}
59
+ # end
60
+ # end
61
+ #
62
+ # Yields Sbmt::Strangler::Configuration instance.
63
+ def configure
64
+ yield configuration if block_given?
65
+ end
66
+
67
+ # Public: Returns Sbmt::Strangler::Configuration instance.
68
+ def configuration
69
+ @configuration ||= Configuration.new
70
+ end
71
+
72
+ def action_controller_base_class
73
+ @action_controller_base_class ||= configuration.action_controller_base_class.constantize
74
+ end
75
+
76
+ def error_tracker
77
+ @error_tracker ||= configuration.error_tracker.constantize
78
+ end
79
+
80
+ def logger
81
+ @logger ||= Sbmt::Strangler::Logger.new
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/sbmt/strangler/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "sbmt-strangler"
7
+ spec.version = Sbmt::Strangler::VERSION
8
+ spec.authors = ["sbermarket team"]
9
+
10
+ spec.summary = "Utility for strangler pattern"
11
+ spec.description = spec.summary
12
+ spec.homepage = "https://github.com/SberMarket-Tech/sbmt-strangler"
13
+ spec.required_ruby_version = ">= 3.1.0"
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/-/blob/master/CHANGELOG.md"
20
+ spec.metadata["rubygems_mfa_required"] = "false" # rubocop:disable Gemspec/RequireMFA
21
+
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "faraday", "> 2.0"
32
+ spec.add_dependency "faraday-net_http_persistent", "~> 2.0"
33
+ spec.add_dependency "net-http-persistent", ">= 4.0.1"
34
+ spec.add_dependency "rails", ">= 6.1", "< 8"
35
+ spec.add_dependency "yabeda", ">= 0.11"
36
+ spec.add_dependency "flipper", ">= 1.2.2"
37
+ spec.add_dependency "concurrent-ruby", ">= 1.2.3"
38
+ spec.add_dependency "oj"
39
+ spec.add_dependency "dry-monads"
40
+ spec.add_dependency "dry-struct"
41
+
42
+ spec.add_development_dependency "appraisal"
43
+ spec.add_development_dependency "bundler"
44
+ spec.add_development_dependency "combustion"
45
+ spec.add_development_dependency "rake"
46
+ spec.add_development_dependency "rspec"
47
+ spec.add_development_dependency "rspec-rails"
48
+ spec.add_development_dependency "rspec_junit_formatter"
49
+ spec.add_development_dependency "rswag-specs"
50
+ spec.add_development_dependency "rubocop"
51
+ spec.add_development_dependency "rubocop-rails"
52
+ spec.add_development_dependency "rubocop-rspec"
53
+ spec.add_development_dependency "rubocop-performance"
54
+ spec.add_development_dependency "vcr"
55
+ spec.add_development_dependency "standard", ">= 1.7"
56
+ spec.add_development_dependency "zeitwerk"
57
+ spec.add_development_dependency "sentry-rails", "> 5.2.0"
58
+ spec.add_development_dependency "debug"
59
+ end