Flask/Python에서 자체 기능 플래그 관리자 구축

소프트웨어 개발 모델에 이렇게 열정적인 것은 아마도 이상한 일일 것이다. 그러나 내가 가장 좋아하는 방식은 기능 로고를 사용하여 새로운 기능을 선택하는 것이다.나는 그것들이 없는 상황에서 어떠한 합리적인 크기의 응용 프로그램도 구축할 수 없다고 상상할 수 없다.
특성 표지는 본질적으로 좋은 if/else 검사 방법으로 사용자에게 어떤 코드 경로를 사용할지 선택할 수 있지만 그것들을 관리하는 약간의 도구라도 개발 속도를 완전히 높일 수 있다.너무 좋아요!이것은 사용자가 더 많은 유용한 제품을 더 빨리 얻을 수 있다는 것을 의미하기를 바란다.
특성 표지의 첫 번째 큰일은 사용자가 새로운 특성의 증량 개발을 할 수 있다는 것이다. 단독의 특성 지점에 대해 논쟁할 필요가 없다는 것이다.부분적으로 완성된 작업을 특성 표지 뒤에 숨기면main을 수시로 제출할 수 있습니다.만약 네가 다른 사람과 합작한다면, 이 점은 특히 유용하다.
if feature_flag('NEW_FEATURE', 'ENABLED'):
    # show new feature
새로운 기능, 디자인, 변체를 발표하려고 준비할 때, 선택적으로 이렇게 할 수도 있습니다. 변체 그룹에 인원을 추가하여 기능 로고가 그것을 보기 시작하도록 합니다.특성 로고가 있어 새로운 특성의 출시는 코드 배치와 분리될 수 있어 더욱 안전하고 문제가 발생하면 복구하기 쉽다.
if feature_flag('MY_EXPERIMENT', 'VARIANT_A'):
    # show variant a
elif feature_flag('MY_EXPERIMENT', 'VARIANT_B'):
    # show variant b
else:
    # show control

사든지 만들든지
구매와 건설 결정에 있어서 사람들은 나보다 더 똑똑하고 경험이 많다. 예를 들어 라는 책의 저자인 카밀 포니어(Camille Fournier)의 이 우수한 문장analysis이다.나는 여기서 전체 주제를 반복하지는 않지만, 엔지니어들이 일반적으로 그것을 선택하는 경향이 있는 것보다 정확한 결정은 구매라는 것을 말하고 싶다.적어도 어떤 규모의 생산 시스템에서도 공사팀은 마찬가지다.
본고를 작성할 때까지 2021년 동안 LaunchDarkly는 선도적인 기능 표지인 SaaS 솔루션이었다.그들의 업무 규모를 바탕으로 많은 개발자들이 좋은 시스템에서 그들의 기능 표지를 관리하는 가치를 발견한 것 같다.사용자당 10달러의 가격으로 기존의 해결 방안을 사용하는 것은 공사 시간을 소비하는 것보다 훨씬 싸다(비용이 훨씬 높다!)자신의 집을 짓다.
그럼에도 불구하고 기능 로고의 기본 버전은 상대적으로 실현하기 쉽다. 만약에 당신이 새로운 프로젝트에 종사하고 있다면 외부 서비스와 통합하는 번거로움을 원하지 않을 수도 있고 개발자마다 매달 비용을 지불해서 블러드 값을 검사할 수 있기를 원하지 않을 수도 있다.당신이나 기능 로고 관리자의 실현을 궁금하게 생각하고 있다면 계속 읽어 주십시오.

자체 기능 플래그 관리자 구축
나는 줄곧 기능 표지 관리자의 추상화를'문지기'라고 불렀는데 페이스북과 많은 전 페이스북 사용자들에게서 일했다. 왜냐하면 이것은 그들의 기능 표지와 실험의 내부 서비스 이름framework이기 때문이다.이것은 기억하기 쉬운 이름이며 코드에서 쉽게 줄여서 gk라고 할 수 있다.나도 여기서 쓸 거야.
gatekeeper 자체를 제외하고는 기능 표지가 무엇인지, 사용자나 세션을 다른 변체에 넣는 논리를 지정하기 위해gatekeeper 설정이 필요합니다.가장 전문적인 기능 로고 솔루션은 코드에 독립된 프로필을 사용하지만, 우리가 개발한 첫 번째 버전의gatekeeper에 대해 프로필을 코드에 넣는 것이 가장 간단하고, 작업도 충분하다.
마지막으로gatekeeper를 초기화하고 등록해서 개발자에게 사용하도록 하는 것이 개발자 도구에 있어서 매우 관건적입니다!일을 가능한 한 쉽게 사용하기 위해서, 나는 핵심 추상적인 또 다른 추가 기능이 매우 유용하다는 것을 발견했다. 그것은 바로 브라우저 덮어쓰기를 지원하는 것이다. 이것은 관리 계기판을 통해 설정할 수 있다는 것이다.
어쨌든 당신이 필요로 하는 것은:
  • 기능 표지 관리자
  • 기능 플래그 구성(보안 구성)
  • 물건을 가지런히 연결하여 사용하기 쉽게
  • 다음 코드는 Flask 프레임워크를 사용하는 웹 응용 프로그램에 사용되는 파이톤으로 작성되었습니다.여기에서 읽을 수 있고 다른 언어와 프레임워크로 확장될 수 있기를 바랍니다!
    브라우저 덮어쓰기를 지원하는 코드는 포함되어 있지만 덮어쓰기를 설정하고 읽을 수 있는 관리 대시보드 코드는 포함되어 있지 않습니다.

    골키퍼py
    from gatekeeper_config import FF_CONFIG_MAP
    
    
    class Gatekeeper(object):
        def __init__(self, user_id=None,
                     app=None,
                     request=None,
                     session=None,
                     config_map=None):
    
            self.user_id = user_id
            self.app = app
            self.request = request
            self.session = session
    
            self.config_map = config_map if config_map else FF_CONFIG_MAP
    
        def ff(self, *args, **kwargs):
            '''Shorthand wrapper for `feature_flag`.'''
            return self.feature_flag(*args, **kwargs)
    
        def feature_flag(self, flag, variant):
            return self.get_feature_flag_variant(flag) == variant
    
        def ff_variant(self, *args, **kwargs):
            '''Shorthand wrapper for `get_feature_flag_variant`.'''
            return self.get_feature_flag_variant(*args, **kwargs)
    
        def get_feature_flag_variant(self, flag, user_id_override):
            config = self.config_map.get(flag)
            if not config:
                return None
    
            variant = config.get_variant(
                user_id=user_id_override or self.user_id,
                app=self.app,
                request=self.request,
                session=self.session)
            return variant.name
    
        def get_config_map(self):
            return self.config_map
    
        def get_browser_override_variants(self, request):
            return json.loads(request.cookies.get('gatekeeper', '{}'))
    
        def set_browser_override_variant(self, request, flag, variant):
            config = self.config_map.get(flag)
            if not config:
                return None
    
            return config.set_browser_override_variant(request, variant)
    
    
    def initialize_gatekeeper(user_id=None, app=None, config_map=None):
        from flask import current_app
        from flask import request
        from flask import session
        from flask_login import current_user
    
        if not app:
            app = current_app
    
        if user_id is None and current_user and not current_user.is_anonymous:
            user_id = current_user.id
    
        gk = Gatekeeper(
            user_id=user_id,
            app=app,
            request=request,
            session=session,
            config_map=config_map)
        return gk
    
    gatekeeper_config.py
    from abc import ABC
    from abc import abstractmethod
    from enum import Enum
    import json
    
    import flask
    
    
    class FeatureFlagConfig(ABC):
        FLAG_NAME: str
        VARIANTS_ENUM_STR: str
    
        def __init__(self, overrides=None):
            self.variants_enum = Enum(
                f'{self.__class__.__name__}Variants', self.VARIANTS_ENUM_STR)
            self.overrides = overrides
    
        def get_variants(self):
            return list(self.variants_enum.__members__.keys())
    
        @abstractmethod
        def _get_variant(
                self,
                user_id: Optional[int] = None,
                app: Optional[Flask] = None,
                request: Optional[Request] = None,
                session: Optional[Any] = None):
            pass
    
        def get_variant(self, user_id: Optional[int] = None, app: Optional[Flask] = None,
                        request: Optional[Request] = None, session: Optional[Any] = None):
            override_variant = self.get_override_variant(
                user_id=user_id, app=app, request=request, session=session)
            if override_variant:
                return override_variant
            else:
                return self._get_variant(
                    user_id=user_id, app=app, request=request, session=session)
    
        def get_override_variant(
                self, user_id=None, app=None, request=None, session=None):
            if request:
                browser_override_variant = self.get_browser_override_variant(request)
                if browser_override_variant:
                    return browser_override_variant
            if self.overrides:
                for variant, user_ids in self.overrides.items():
                    if user_id in user_ids:
                        return self.variants_enum[variant]
            return None
    
        def set_browser_override_variant(self, request, variant):
            browser_override_variants = self.get_browser_override_variants(request)
            if variant == '':
                browser_override_variants.pop(self.FLAG_NAME, None)
            else:
                browser_override_variants[self.FLAG_NAME] = variant
    
            response = flask.make_response()
            response.set_cookie(
                'gatekeeper',
                json.dumps(browser_override_variants))
            return response
    
        def get_browser_override_variants(self, request):
            return json.loads(request.cookies.get('gatekeeper', '{}'))
    
        def get_browser_override_variant(self, request):
            browser_override_variants = self.get_browser_override_variants(request)
            browser_override_variant = browser_override_variants.get(self.FLAG_NAME)
            if browser_override_variant:
                return self.variants_enum[browser_override_variant]
            else:
                return None
    
    
    class MyNewFeatureFFConfig(FeatureFlagConfig):
        FLAG_NAME = 'MY_NEW_FEATURE'
        VARIANTS_ENUM_STR = 'VISIBLE NOT_VISIBLE'
        DESCRIPTION = 'Gate visibility of my new feature during development'
    
        def _get_variant(self, user_id=None, app=None, request=None, session=None):
            if app and app.config.get('IS_DEV'):
                return self.variants_enum.VISIBLE
            elif user_id and user_id in [1, 2, 3]:  # Team user ids
                return self.variants_enum.VISIBLE
            else:
                return self.variants_enum.NOT_VISIBLE
    
    
    FF_CONFIG_MAP: FFConfigMap = {
        'MY_NEW_FEATURE': MyNewFeatureFFConfig()
    }
    

    app.py
    # All the usual app setup stuff here... 
    
    app = create_app()
    
    @app.before_request
    def register_gatekeeper():
        # Put imports here to avoid circular import issues.
        from flask import request
    
        import gatekeeper
    
        request.gk = gatekeeper.initialize_gatekeeper(app=app)
    

    골키퍼를 사용하다
    이 모든 설정이 있으면, 현재gatekeeper를 검색해서 특성 표지와 변체를 얻는 것은 매우 간단합니다.
    예를 들어, Python 웹 프로세서 코드에서는 다음과 같이 보일 수 있습니다.
    if request.gk.ff('MY_NEW_FEATURE', 'VISIBLE'): 
        # Show new feature
    
    또는 Jinja 템플릿 코드에서는 다음과 같이 보일 수 있습니다.
    {% if request.gk.ff('MY_NEW_FEATURE', 'VISIBLE') %}
        {# Show new feature #}
    {% endif %} 
    

    마지막 생각
    덮어쓰기, 특히 브라우저 덮어쓰기를 지원하기 위해 코드를 꺼내면, 기능적인 기능 로고 관리자를 만들 필요가 거의 없다는 것을 알게 될 것이다.제가 덮어쓰기 지원을 포함하는 이유는 개발자의 용이성에 매우 중요하고 예상보다 중요하다는 것을 발견했기 때문입니다.
    만약 기능 로고를 사용하지 않았다면, 이 글은 개발과 발표 작업 흐름에서 이 로고를 사용하도록 설득할 수 있기를 바랍니다. 만약 다른 SaaS 솔루션을 만들고 싶지 않다면, 아마도 여기에 공유된 코드는 사용자가 자신의 기능 로고 관리자를 사용하기 시작하는 데 도움을 줄 수 있을 것입니다.즐거운 코딩!

    좋은 웹페이지 즐겨찾기