Feature Flag 基盤構築の一環として実装したサーバーサイドロジックの紹介です。 Ruby において include するだけですべての method をトラップして実装を差し込む手法を用いて、クライアントにサーバーサイドロジックの一部を確認/上書きできる用になります。
©2020 Wantedly, Inc.Wrap every methodWith just another lineMeguro.rb#30Jan 31, 2020 - Shimpei Otsubo
View Slide
©2020 Wantedly, Inc.Shimpei Otsubo @potsboDeveloper Experience Squad, Wantedly, Inc.KubernetesDev ToolsCI / CDAuthN / ZProductivity Dvorak
©2020 Wantedly, Inc.AB ςετ͕͍͠ςετ͕ͮ͠Β͘ͳΔࣗಈखಈޙΖଆͷϚΠΫϩαʔϏεͰߦ͏ͱཧෆೳҰ൪લͳΒport-forwardͱ͔Ͱߦ͚Δ͕ʜ˞αʔϏεϝογϡͰղܾ͍ͨ͠ϨΠϠͰ͋Δʜ$4͕ࠞཚਓʹΑͬͯڍಈ͕ҟͳΔࣄ͕͋ͬͯԿނ͔Θ͔Βͳ͍
©2020 Wantedly, Inc.AB ςετΛ؆୯ʹτάϧͰ͖Δextension࡞ͬͨ˞ը૾αϯϓϧͰ͢ͲΜͳflagͰࠓͷϖʔδ͕render͞Ε͔ͨΘ͔ΔͲΜͳflagͰࠓͷϖʔδΛrenderͯ͠΄͍͔͠ࢦఆͰ͖Δ
©2020 Wantedly, Inc.Thread.currentresp headermiddlewareͲΜͳΛ͔ͬͨʁΛfeedbackͲΜͳΛͬͯ΄͍͔͠ʁΛࢦఆThread.currentreq headerHeader Λհͯ͠ methodͷධՁΛ read /write)FBEFSͳͷͰNJDSPTFSWJDFͰൖՄೳcallreturn
©2020 Wantedly, Inc.ڵຯରͷNFUIPEʹͯ͢ΛIPPL͢Δඞཁ͕͋Δ͜ͷͨΊʹ
©2020 Wantedly, Inc.class Experimentdef self.enable?(name, target)# some consistent logic hereendendExperiment.enable?(:show_new_button, current_user)# => true or falseWantedly ʹ͓͚ΔABςετͷํ๏ಉҰͷuserʹରͯ͠ৗʹಉҰͷboolean͕ฦΔmethod
©2020 Wantedly, Inc.Thread.current͔Βuser͕ࢦఆ͕ͨ͋͠ΕͦΕΛར༻ͳ͚ΕThread.currentʹར༻ͨ͠Λ٧ΊΔclass Experimentdef self.enable?(name, target)# some consistent logic hereenddef self.wrapped_enable?(name, target)request_value = Thread.current[:request][name]return request_value unless request_value.nil?actual_value = enable?(name, target)Thread.current[:response][name] = actual_valueendend˞Thread.currentͱheaderͷؒΛͭͳ͙middlewareΛผ్༻ҙ
©2020 Wantedly, Inc.Renameͯͦ͠ͷ··͑ΔΑ͏ʹ͢Δclass Experimentdef self.real_enable?(name, target)# some consistent logic hereenddef self.enable?(name, target)request_value = Thread.current[:request][name]return request_value unless request_value.nil?actual_value = real_enable?(name, target)Thread.current[:response][name] = actual_valueendend
©2020 Wantedly, Inc.ҰൠԽͯ͠ΈΔclass FeatureFlagdef self.intercept(name)request_value = Thread.current[:request][name]return request_value unless request_value.nil?actual_value = yieldThread.current[:response][name] = actual_valueendendclass Experimentdef self.real_enable?(name, target)# some consistent logic hereenddef self.enable?(name, target)FeatureFlag.intercept(name) doreal_enable?(name, target)endendendϒϩοΫͰғΈ͑͢͞ΕͲ͜ͰPWFSSJEFՄೳʂ
©2020 Wantedly, Inc.ଞͷػೳͷग़͚͠ʹ͑Δͣ
©2020 Wantedly, Inc.Helperͷ͍ΖΜͳmethod ͰػೳΛग़͚͍ͯ͠͠Δॴ͕͋Δmodule I18nHelper# the new landing page currently supports only Japanese!!def show_new_landing_page?I18n.locale == :jaendend
©2020 Wantedly, Inc.ϒϩοΫͰғΉ͚ͩͰXSBQͰ͖Δclass FeatureFlagdef self.intercept(name)...endendmodule I18nHelper# the new landing page currently supports only Japanese!!def show_new_landing_page?FeatureFlag.intercept(:show_new_landing_page?) doI18n.locale == :jaendendend
©2020 Wantedly, Inc.ϒϩοΫͰғΉ͚ͩͰXSBQͰ͖Δclass FeatureFlagdef self.intercept(name)...endendmodule I18nHelper# the new landing page currently supports only Japanese!!def show_new_landing_page?FeatureFlag.intercept(:show_new_landing_page?) doI18n.locale == :jaendendendຖճॻ͘ͷྲྀੴʹ໘
©2020 Wantedly, Inc.class FeatureFlagdef self.intercept(name)...endendmodule I18nHelper# the new landing page currently supports only Japanese!!def show_new_landing_page?FeatureFlag.intercept(:show_new_landing_page?) doI18n.locale == :jaendendendखಈͰίʔυΛ͍͡ΔͷͰͳ͘
©2020 Wantedly, Inc.class FeatureFlagdef self.intercept(name)...endendmodule I18nHelperinclude FeatureFlagdef show_new_landing_page?I18n.locale == :jaendend*ODMVEF͚ͩͰղܾͰ͖ͨΒͤҰߦՃ͢Δ͚ͩʂʂ
©2020 Wantedly, Inc.Ͱ͖ΔΑ͏ʹͨ͠module FeatureFlagdef self.included(base)base.class_eval doextend ClassMethods@_already_patched = []@_patch_target = baseendendmodule ClassMethodsdef intercept(name)...enddef method_added(name)if [email protected]_already_patched.include?(name) && self == @_patch_targetorig_method_name = "_#{base}_orig".to_sym@_already_patched << name@_already_patched << orig_method_namealias_method orig_method_name, name
©2020 Wantedly, Inc.ॳظԽmodule FeatureFlagdef self.included(base)base.class_eval doextend ClassMethods@_already_patched = []@_patch_target = baseendendmodule ClassMethodsdef intercept(name)...enddef method_added(name)if [email protected]_already_patched.include?(name) && self == @_patch_targetorig_method_name = "_#{base}_orig".to_sym@_already_patched << name@_already_patched << orig_method_namealias_method orig_method_name, nameͲͷNPEVMFͰJODMVEFͨ͠ͷ͔͓֮͑ͯ͘
©2020 Wantedly, Inc.module ClassMethodsdef intercept(name)...enddef method_added(name)if [email protected]_already_patched.include?(name) && self == @_patch_targetorig_method_name = "_#{base}_orig".to_sym@_already_patched << name@_already_patched << orig_method_namealias_method orig_method_name, nameprivate orig_method_namedefine_method(name) do |*args|intercept(name) dosend(old_name, *args)endendendsuperendendend͢ͰʹXSBQ͍ͯ͠ͳ͍͔ͷ֬ೝ
©2020 Wantedly, Inc.module ClassMethodsdef intercept(name)...enddef method_added(name)if [email protected]_already_patched.include?(name) && self == @_patch_targetorig_method_name = "_#{base}_orig".to_sym@_already_patched << name@_already_patched << orig_method_namealias_method orig_method_name, nameprivate orig_method_namedefine_method(name) do |*args|intercept(name) dosend(old_name, *args)endendendsuperendendendطଘ࣮ͷୀආ
©2020 Wantedly, Inc.module ClassMethodsdef intercept(name)...enddef method_added(name)if [email protected]_already_patched.include?(name) && self == @_patch_targetorig_method_name = "_#{base}_orig".to_sym@_already_patched << name@_already_patched << orig_method_namealias_method orig_method_name, nameprivate orig_method_namedefine_method(name) do |*args|intercept(name) dosend(old_name, *args)endendendsuperendendend8SBQ࣮
©2020 Wantedly, Inc.αʔόʔͷڍಈ͕ݟ͑Δม͑ΒΕΔͱָ*ODMVEF͚ͩͰͯ͢ͷNFUIPEXSBQՄೳଞʹ͍Ζ͍Ζͳ֦ு͕Ͱ͖Δςετσόοά[email protected]ͰՃ͞ΕͨNFUIPEΛ͍͡Εྑ͍[email protected] CBSͱ͔࡞ΕΔ