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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9bb8754c88d1b03e5d4a32bc244c6e14351d0e875dfd12bbf9011cb08c450ce
4
+ data.tar.gz: 3ac98da59470beb7f2d023352072bc589867e5af9d2160d07a805529577e7482
5
+ SHA512:
6
+ metadata.gz: e1d13adacfa0630878515a07ae6619ddd23d2182a1fb17183be233ef7c10e0552c8917404d9728961e2fc14d0aa68131c328245b111465a56370a6d8e3d87086
7
+ data.tar.gz: 405bf3d3146936259e97aeb52f6d0a5065d5080be4891b9cff97463fde3df32a6d998a5b1b023b630b1e1fc4c35dc353384dfcf09431f9549d7e641cd065ef69
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --require rails_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,54 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ # We want Exclude directives from different
4
+ # config files to get merged, not overwritten
5
+ inherit_mode:
6
+ merge:
7
+ - Exclude
8
+
9
+ AllCops:
10
+ TargetRubyVersion: 3.3
11
+ TargetRailsVersion: 7.1
12
+ NewCops: enable
13
+ SuggestExtensions: false
14
+
15
+ require:
16
+ # Performance cops are bundled with Standard
17
+ - rubocop-performance
18
+ - rubocop-rails
19
+ - rubocop-rspec
20
+ # Standard's config uses custom cops,
21
+ # so it must be loaded
22
+ - standard
23
+
24
+ inherit_gem:
25
+ standard: config/base.yml
26
+
27
+ # rubocop-rails
28
+ Rails/SkipsModelValidations:
29
+ Enabled: false
30
+
31
+ # rubocop-rspec
32
+ RSpec/AnyInstance:
33
+ Enabled: false
34
+
35
+ RSpec/MultipleExpectations:
36
+ Enabled: false
37
+
38
+ RSpec/LetSetup:
39
+ Enabled: false
40
+
41
+ RSpec/StubbedMock:
42
+ Enabled: false
43
+
44
+ RSpec/MessageSpies:
45
+ Enabled: false
46
+
47
+ RSpec/NestedGroups:
48
+ Max: 10
49
+
50
+ RSpec/ExampleLength:
51
+ Max: 35
52
+
53
+ RSpec/MultipleMemoizedHelpers:
54
+ Max: 15
data/.rubocop_todo.yml ADDED
File without changes
data/Appraisals ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # See compatibility table at https://www.fastruby.io/blog/ruby/rails/versions/compatibility-table.html
4
+
5
+ versions_map = {
6
+ "7.0" => %w[3.1],
7
+ "7.1" => %w[3.2 3.3]
8
+ }
9
+
10
+ current_ruby_version = RUBY_VERSION.split(".").first(2).join(".")
11
+
12
+ versions_map.each do |rails_version, ruby_versions|
13
+ ruby_versions.each do |ruby_version|
14
+ next if ruby_version != current_ruby_version
15
+
16
+ appraise "railties-#{ruby_version}-#{rails_version}" do
17
+ gem "railties", "~> #{rails_version}.0"
18
+ end
19
+ end
20
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1,106 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
+ and this project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## [0.9.1] - 2024-07-02
9
+
10
+ ### Added
11
+ - Add put request support
12
+
13
+ ## [0.9.0] - 2024-06-06
14
+
15
+ ### Added
16
+ - Add handler for Faraday::ConnectionFailed
17
+ - Add retry connection support
18
+
19
+ ## [0.8.0] - 2024-06-05
20
+
21
+ ### Added
22
+ - Add `action.render = ->(mirror_result) { ... }` lambda
23
+
24
+ ## [0.7.0] - 2024-06-03
25
+
26
+ ### Added
27
+ - allow to configure timeout per route
28
+
29
+ ### Changed
30
+ - rename "proxy_http_verb" to "proxy_http_method"
31
+
32
+ ## [0.6.0] - 2024-05-30
33
+
34
+ ### Added
35
+ - add support for enabling work mode by request header
36
+
37
+ ## [0.5.0] - 2024-05-22
38
+
39
+ ### Added
40
+ - use [net_http_persistent](https://github.com/lostisland/faraday-net_http_persistent) for proxying
41
+
42
+ ## [0.4.0] - 2024-05-22
43
+
44
+ ### Added
45
+ - enable replace mode (it was temporarily commented out in the source code)
46
+ - in the replace mode a value returned from action.mirror block is passed directly to the #render method
47
+ - action.flipper_actor lambda can now return an array of actor IDs instead of a single actor ID
48
+
49
+ ### Changed
50
+ - automatic feature flags name now has BEM-like formatting: "controller-path__action-name--work-mode" (because of Flipper UI limitations/recomendations)
51
+ - action.compare is called for failed origin/proxy responses too
52
+ - the first argument of action.compare block (called origin_result) is a hash of the form {body:, status:, headers:}
53
+ - metric renamed: sbmt_strangler_compare_result -> sbmt_strangler_compare_call_result
54
+
55
+ ## [0.3.1] - 2024-04-24
56
+
57
+ ### Fixed
58
+ - Create feature flags after the app fully initialized and handle flag creation errors
59
+
60
+ ## [0.3.0] - 2024-04-22
61
+
62
+ ### Added
63
+ - add support for mirror and replace modes
64
+ - add config options: action.mirror, action.compare, configuration.flipper_actor
65
+ - add to Mixin: #track_mirror_call, #track_compare_call, #track_compare_result
66
+
67
+ ### Changed
68
+ - rename Mixin#render_proxy_response to Mixin#render_origin_response
69
+ - rename Mixin#track_work_tactic to Mixin#track_work_mode
70
+ - Sbmt::Strangler::Http::Transport#get|post_request returns response body as String (it skips parsing body as JSON).
71
+
72
+ ### Fixed
73
+ - Fix from v0.2.2 ported to v0.3.0
74
+
75
+ ## [0.2.2] - 2024-04-19
76
+
77
+ ### Fixed
78
+ - filter out IDs like 'R608473650' from Faraday metric 'path' tag
79
+
80
+ ## [0.2.0] - 2024-04-09
81
+
82
+ ### Added
83
+ - add metrics for http client request
84
+
85
+ ### Changed
86
+
87
+ ### Fixed
88
+
89
+ ## [0.1.1] - 2024-04-08
90
+
91
+ ### Added
92
+
93
+ ### Changed
94
+
95
+ ### Fixed
96
+ - ignore inherit constants in Sbmt::Strangler::ConstDefiner
97
+
98
+ ## [0.1.0] - 2024-04-05
99
+
100
+ ### Added
101
+ - initial release with proxy tooling
102
+
103
+ ### Changed
104
+
105
+ ### Fixed
106
+ - ignore inherit constants in Sbmt::Strangler::ConstDefiner
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 SberMarket Tech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Sbmt-strangler
2
+
3
+ Sbmt-strangler is an implementation of Strangler-pattern for replacing legacy system http endpoints with three stages: proxying, mirroring, replacing.
4
+
5
+ ## Strangler Pattern
6
+
7
+ [The strangler pattern](https://microservices.io/patterns/refactoring/strangler-application.html) is a software design and architectural pattern used in the context of legacy system modernization or application migration. The pattern’s name comes from the way certain plants, like strangler figs, grow around host trees, eventually replacing or “strangling” them entirely. Similarly, the Strangler Pattern aims to replace or evolve an existing system gradually, without causing any significant disruptions to the overall system functionality.
8
+
9
+ ## Features
10
+
11
+ - **Automatic proxying**: with help of DSL, the proxying algorithm is effortlessly implemented. A Rails controller and a proxying client are dynamically created. All you need to provide is the processed URL and the address of the original service (refine Rails route is also necessary).
12
+ - **Observability** - a comprehensive observability suite, with native support for [OpenTelementry](https://github.com/open-telemetry/opentelemetry-ruby), alongside proxy metrics based on the [Yabeda](https://github.com/yabeda-rb/yabeda) framework.
13
+ - **Smooth Mode Switching** allows you to safely and smoothly switch between modes using the [Flipper](https://github.com/flippercloud/flipper)-based feature flags.
14
+ - **Mirroring mode metrics** aid in tracking the accuracy and completeness of transferring business logic to a new service by comparing responses from both the legacy system and the new service.
15
+
16
+ # Usage
17
+
18
+ ## Proxying
19
+
20
+ *Proxying* mode is a setup where traffic destined for the legacy system is first routed through the service, only to be fully redirected back to the legacy system. While the service doesn't offer any extra functionalities, it does gain control over the traffic, allowing for future replacements.
21
+
22
+ ![Proxy-mode](docs/img/01-proxy_mode.png).
23
+
24
+ To determine the proxy mode, you need to:
25
+
26
+ 1. Set the service path in the Rails controller, which will be generated automatically.
27
+ 2. Specify the path to the legacy system and the HTTP verbs used.
28
+ 3. Optionally, define a whitelist of proxied HTTP headers, as business logic can depend on them (original params & headers are available)
29
+
30
+ ```ruby
31
+ Sbmt::Strangler.configure do |strangler|
32
+ strangler.controller("api/hello") do |controller|
33
+ controller.headers_allowlist = %w[HTTP_API_VERSION HTTP_USER_AGENT HTTP_X_REQUEST_ID]
34
+ controller.params_tracking_allowlist = %w[name surname]
35
+
36
+ controller.action("index") do |action|
37
+ action.proxy_url = "http://example.com:8080/api/hello"
38
+ action.proxy_http_method = :post
39
+ end
40
+ end
41
+ ```
42
+
43
+ ## Mirroring
44
+
45
+ *Mirroring* is an approach where requests are simultaneously processed by both the service and the monolith. The outcomes are then compared to calculate an accuracy metric, enabling timely monitoring of the service's endpoint performance. Importantly, the client receives the result from the monolith, ensuring that mirroring does not affect user experience.
46
+
47
+ ![Mirror-mode](docs/img/02-mirror_mode.png).
48
+
49
+ To determine the mirroring mode, it is necessary to describe the business logic of the endpoint which will run in parallel with the request's proxying in the legacy system. Depending on the result, the success/failure metric of the mirroring logic will be logged.
50
+
51
+ The mirroring mode involves comparing the results of the response from the legacy system with the results of calculations from the new implementation. To make this comparison, it is necessary to describe the corresponding lambda (compare).
52
+
53
+ ```ruby
54
+ Sbmt::Strangler.configure do |strangler|
55
+ strangler.controller("api/hello") do |controller|
56
+ controller.headers_allowlist = %w[HTTP_API_VERSION HTTP_USER_AGENT HTTP_X_REQUEST_ID]
57
+ controller.params_tracking_allowlist = %w[name surname]
58
+
59
+ controller.action("index") do |action|
60
+ action.proxy_url = "http://example.com:8080/api/hello"
61
+ action.proxy_http_method = :post
62
+
63
+ # new implementation of legacy system business logic
64
+ action.mirror = ->(rails_controller) do
65
+ name, surname = rails_controller.params.values_at(:name, :surname)
66
+ {json: {greeting: "Hello, #{name} #{surname}!"}}
67
+ end
68
+
69
+ # compare legacy system response and mirror lambda calculation result
70
+ action.compare = ->(origin_result, mirror_result) do
71
+ origin_result[:status] == 200 &&
72
+ JSON.parse(origin_result[:body]) == mirror_result[:json]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ ```
78
+
79
+ ## Replacement
80
+
81
+ *Replacement* refers to the process of substituting the functionality of a legacy system with a new implementation. In this scenario, the legacy system can either be completely removed from the framework or utilized to enhance the response outcomes of the new service.
82
+
83
+ ![Replace-mode](docs/img/03-replace_mode.png).
84
+
85
+
86
+ To enable replacement mode, use the feature flag. Replacement mode uses mirror lambda for replacement logic.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.after_initialize do
4
+ Sbmt::Strangler::Builder.call!
5
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Strangler
5
+ module Metrics
6
+ module Yabeda
7
+ HTTP_BUCKETS = [0.01, 0.02, 0.04, 0.1, 0.2, 0.5, 0.8, 1, 1.5, 2, 5, 15, 30, 60].freeze
8
+
9
+ ::Yabeda.configure do
10
+ group :sbmt_strangler do
11
+ histogram :http_request_duration,
12
+ tags: %i[name method status host path],
13
+ unit: :seconds,
14
+ buckets: HTTP_BUCKETS,
15
+ comment: "HTTP client request duration"
16
+
17
+ counter :params_usage,
18
+ comment: "Parameters usage counter",
19
+ tags: %i[params controller action]
20
+ counter :work_mode,
21
+ comment: "Work mode counter (mode: proxy, mirror, replace)",
22
+ tags: %i[mode params controller action]
23
+ counter :mirror_call,
24
+ comment: "Mirror lambda call counter (success: true, false)",
25
+ tags: %i[success params controller action]
26
+ counter :compare_call,
27
+ comment: "Compare lambda call counter (success: true, false)",
28
+ tags: %i[success params controller action]
29
+ counter :compare_call_result,
30
+ comment: "Compare lambda call result counter (value: true, false)",
31
+ tags: %i[value params controller action]
32
+ counter :render_call,
33
+ comment: "Render lambda call counter (success: true, false)",
34
+ tags: %i[success params controller action]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
data/dip.yml ADDED
@@ -0,0 +1,67 @@
1
+ version: '7'
2
+
3
+ environment:
4
+ RUBY_VERSION: '3.3'
5
+
6
+ compose:
7
+ files:
8
+ - docker-compose.yml
9
+
10
+ interaction:
11
+ bash:
12
+ description: Open the Bash shell in app's container
13
+ service: ruby
14
+ command: /bin/bash
15
+
16
+ bundle:
17
+ description: Run Bundler commands
18
+ service: ruby
19
+ command: bundle
20
+
21
+ rails:
22
+ description: Run RoR commands
23
+ service: ruby
24
+ command: bundle exec rails
25
+
26
+ appraisal:
27
+ description: Run Appraisal commands
28
+ service: ruby
29
+ command: bundle exec appraisal
30
+
31
+ swaggerize:
32
+ description: Build swagger docs
33
+ service: ruby
34
+ environment:
35
+ RAILS_ENV: test
36
+ command: bundle exec rspec --format Rswag::Specs::SwaggerFormatter --order defined spec/requests/
37
+
38
+ rspec:
39
+ description: Run Rspec commands
40
+ service: ruby
41
+ command: bundle exec rspec
42
+ subcommands:
43
+ all:
44
+ command: bundle exec appraisal rspec
45
+ rails-6.1:
46
+ command: bundle exec appraisal rails-6.1 rspec
47
+ rails-7.0:
48
+ command: bundle exec appraisal rails-7.0 rspec
49
+ rails-7.1:
50
+ command: bundle exec appraisal rails-7.1 rspec
51
+
52
+ rubocop:
53
+ description: Run Ruby linter
54
+ service: ruby
55
+ command: bundle exec rubocop
56
+
57
+ setup:
58
+ description: Install deps
59
+ service: ruby
60
+ command: bin/setup
61
+
62
+ provision:
63
+ - dip compose down --volumes
64
+ - cp -f lefthook-local.dip_example.yml lefthook-local.yml
65
+ - rm -f Gemfile.lock
66
+ - rm -f gemfiles/*gemfile*
67
+ - dip setup
@@ -0,0 +1,19 @@
1
+ services:
2
+ ruby:
3
+ image: ${RUBY_DOCKER_IMAGE:-ruby}:${RUBY_VERSION:-3.3}
4
+ environment:
5
+ HISTFILE: /app/tmp/.bash_history
6
+ BUNDLE_PATH: /usr/local/bundle
7
+ BUNDLE_CONFIG: /app/.bundle/config
8
+ prometheus_multiproc_dir: ./tmp
9
+ command: bash
10
+ working_dir: /app
11
+ volumes:
12
+ - .:/app:cached
13
+ - ${RUBYGEMS_PATH:-..}:/app/vendor/gems:cached
14
+ - bundler_data:/usr/local/bundle
15
+ tmpfs:
16
+ - /tmp
17
+
18
+ volumes:
19
+ bundler_data:
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ pre-commit:
2
+ commands:
3
+ rubocop:
4
+ run: dip {cmd}
data/lefthook.yml ADDED
@@ -0,0 +1,6 @@
1
+ pre-commit:
2
+ commands:
3
+ rubocop:
4
+ tags: backend
5
+ glob: "{*.rb,**/*.rb,Gemfile,Rakefile}"
6
+ run: bundle exec rubocop -A --force-exclusion {staged_files} && git add {staged_files}
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Strangler
5
+ class Action
6
+ extend Sbmt::Strangler::Configurable
7
+
8
+ option :params_tracking_allowlist, :headers_allowlist, :flipper_actor, default_from: :controller
9
+ option :proxy_url
10
+ option :proxy_http_method, default: :get
11
+ option :mirror, default: ->(_rails_controller) {}
12
+ option :compare, default: ->(_origin_result, _mirror_result) { false }
13
+ option :render, default: ->(mirror_result) { mirror_result }
14
+
15
+ attr_reader :name, :controller
16
+
17
+ def initialize(name, controller, &)
18
+ @name = name
19
+ @controller = controller
20
+
21
+ yield(self)
22
+ end
23
+
24
+ def full_name
25
+ "#{controller.name}##{name}"
26
+ end
27
+
28
+ def http
29
+ @http ||= ActiveSupport::InheritableOptions.new(controller.http)
30
+ end
31
+
32
+ def http_client
33
+ @http_client ||= Sbmt::Strangler::Http::Client.new(http_options: http)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Strangler
5
+ class ActionInvoker
6
+ attr_reader :rails_controller, :strangler_action, :metric_tracker, :feature_flags
7
+
8
+ def initialize(rails_controller:, strangler_action:, metric_tracker:, feature_flags:)
9
+ @rails_controller = rails_controller
10
+ @strangler_action = strangler_action
11
+ @metric_tracker = metric_tracker
12
+ @feature_flags = feature_flags
13
+ end
14
+
15
+ delegate :track_params_usage, :track_work_mode, :log_unallowed_params, to: :metric_tracker
16
+
17
+ def call
18
+ track_params_usage
19
+ log_unallowed_params
20
+ track_work_mode(work_mode_class.name.demodulize.underscore)
21
+ work_mode_class.new(
22
+ rails_controller:,
23
+ strangler_action:,
24
+ metric_tracker:,
25
+ feature_flags:
26
+ ).call
27
+ end
28
+
29
+ private
30
+
31
+ def work_mode_class
32
+ @work_mode_class ||=
33
+ if feature_flags.replace?
34
+ Sbmt::Strangler::WorkModes::Replace
35
+ elsif feature_flags.mirror?
36
+ Sbmt::Strangler::WorkModes::Mirror
37
+ else
38
+ Sbmt::Strangler::WorkModes::Proxy
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Strangler
5
+ class Builder
6
+ attr_reader :configuration
7
+
8
+ # @param configuration [Sbmt::Strangler::Configuration]
9
+ def initialize(configuration)
10
+ @configuration = configuration
11
+ end
12
+
13
+ def call!
14
+ build_controllers
15
+ add_feature_flags
16
+ end
17
+
18
+ def self.call!(configuration = Sbmt::Strangler.configuration)
19
+ new(configuration).call!
20
+ end
21
+
22
+ private
23
+
24
+ def build_controllers
25
+ configuration.controllers.each do |controller|
26
+ unless Object.const_defined?(controller.class_name)
27
+ Sbmt::Strangler::ConstDefiner.call!(controller.class_name, Class.new(Sbmt::Strangler.action_controller_base_class))
28
+ end
29
+
30
+ controller.class_name.constantize.class_eval do
31
+ include Sbmt::Strangler::Mixin
32
+
33
+ controller.actions.each do |action|
34
+ define_method(action.name) do
35
+ @strangler_action = action
36
+
37
+ Sbmt::Strangler::ActionInvoker.new(
38
+ rails_controller: self,
39
+ strangler_action: action,
40
+ metric_tracker: Sbmt::Strangler::MetricTracker.new(self),
41
+ feature_flags: Sbmt::Strangler::FeatureFlags.new(
42
+ rails_controller: self,
43
+ strangler_action: action
44
+ )
45
+ ).call
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def add_feature_flags
53
+ configuration.controllers.each do |controller|
54
+ controller.actions.each do |action|
55
+ Sbmt::Strangler::FeatureFlags.new(strangler_action: action).add_all!
56
+ rescue => error
57
+ Sbmt::Strangler.logger.log_warn(
58
+ "Unable to add feature flags for action #{action.full_name}: #{error}",
59
+ error_class: error.class.name
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Strangler
5
+ module Configurable
6
+ def option(*hash)
7
+ case hash
8
+ in [*attributes, Hash => options]
9
+ in [*attributes]
10
+ options = {}
11
+ end
12
+
13
+ attributes.each do |attribute|
14
+ define_method :"#{attribute}=" do |value|
15
+ instance_variable_set(:"@#{attribute}", value)
16
+ end
17
+
18
+ define_method attribute.to_s do
19
+ value = instance_variable_get(:"@#{attribute}")
20
+ return value if value
21
+
22
+ if options[:default_from]
23
+ value = send(options[:default_from])&.public_send(attribute)
24
+ end
25
+
26
+ value || options[:default]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end