React 코드베이스에 기능 플래그 추가

안녕하세요 👋

🤔 소수의 사용자에게 기능을 출시한 다음 피드백/분석을 기반으로 100% 사용자에게 출시하고 싶었던 적이 있습니까? 또는 귀하의 팀에서 방금 대규모 기능을 개발했지만 마케팅/제품 팀에서 아직 출시하지 않겠다고 합니까?

😖 결국 별도의 기능 브랜치를 생성하고 메인 브랜치와 동기화를 유지하려고 합니다. 하지만 거기서 끝나지 않습니다. 몇 주 후에 해당 기능을 시작하려고 합니다. 이제 배포를 다시 트리거해야 합니다. 완전한 롤아웃이 2~4일이 걸리는 모바일 앱의 경우 상황이 훨씬 더 나쁩니다.

😭 오! 기다리다? 문제를 발견했습니다. 사용자가 해당 기능을 사용하지 못하도록 차단하려고 합니다. 행운을 빕니다!



👌 이와 같은 상황에서 개발자를 구하기 위해 기능 플래그가 있습니다! 개발자뿐만 아니라 마케팅, 제품, 영업팀에도 도움이 됩니다.


기능 플래그란 무엇입니까?



LaunchDarkly의 정의가 마음에 듭니다.

A feature flag is a software development process/pattern used to enable or disable functionality remotely without deploying code. New features can be deployed without making them visible to users. Feature flags help decouple deployment from release letting you manage the full lifecycle of a feature.



기능 플래그는 다음에 사용할 수 있습니다.
  • A/B 테스트를 실행 중입니다.
  • 베타 프로그램 관리.
  • 다중 배포 또는 롤백 감소.
  • 역할 기반 액세스를 제공합니다.
  • 먼저 소규모 그룹에 기능을 롤아웃하여 릴리스 실패를 최소화합니다.

  • 기능 플래그를 사용하기 시작하면 되돌릴 수 없습니다.


    React에 기능 플래그 추가



    이 구현은 ReactContextAPI를 사용합니다. 계속 진행하기 전에 기본 사항을 이해했는지 확인하십시오.

    예를 들어 보겠습니다.
    거대한 웹사이트/앱의 💰 결제 게이트웨이에서 작업한다고 상상해보세요. 최근 Apple Pay와 Google Pay라는 두 가지 새로운 결제 모드가 추가되었습니다.

    귀하는 10배 개발자로서 두 가지 통합을 매우 빠르게 완료했지만 마케팅팀은 Google Pay 출시를 몇 주 동안 보류하려고 합니다. Apple Pay가 내일 출시됩니다.

    별도의 브랜치를 유지하고 몇 주 후에 재배포하는 것을 원하지 않을 것입니다. 따라서 기능 플래그를 추가하도록 선택합니다. 😎

    먼저 기능 플래그에 대한 컨텍스트를 만드는 것으로 시작하겠습니다.

    // /contexts/FeatureFlags.js
    
    export const FeatureFlags = React.createContext({});
    


    이제 React DOM 트리를 래핑할 공급자를 생성해 보겠습니다.

    // /contexts/FeatureFlags.js
    
    export const FeatureFlags = React.createContext({});
    
    export const FeatureFlagsProvider = ({ children }) => {
      const [features, setFeatures] = React.useState({});
    
      return (
        <FeatureFlags.Provider value={{ features }}>
          {children}
        </FeatureFlags.Provider>
      );
    };
    


    우리의 컨텍스트는 몇 가지만 더 설정되어 있습니다. 바로 지금 이 공급자로 트리를 래핑할 수 있습니다.

    // index.js
    
    // ... imports here
    
    import App from "./App";
    import { FeatureFlagsProvider } from "./contexts/FeatureFlags";
    
    const rootElement = document.getElementById("root");
    const root = createRoot(rootElement);
    
    root.render(
      <StrictMode>
        <FeatureFlagsProvider>
          <App />
        </FeatureFlagsProvider>
      </StrictMode>
    );
    


    이제 우리가 해야 할 일은 기능을 가져오는 것입니다. fastify를 사용하여 더미 API를 만들었습니다. 이 부분은 무시하셔도 됩니다.

    // enabling cors for codesandbox
    fastify.register(require("fastify-cors"), {
      origin: /\.csb\.app$/
    });
    
    // feature flags route
    fastify.get("/feature-flags", function(request, reply) { 
    
      const features = {
        isGooglePayEnabled: true, 
        isApplePayEnabled: false
      }
    
      reply.send({ features });
    });
    


    컨텍스트 파일로 돌아가서 기능을 가져오겠습니다.

    // /contexts/FeatureFlags.js
    
    import { fetchFeatures } from 'api'
    
    export const FeatureFlags = React.createContext({});
    
    export const FeatureFlagsProvider = ({ children }) => {
       const [isLoading, setIsLoading] = React.useState(true);
       const [features, setFeatures] = React.useState({});
    
       React.useEffect(() => {
         (async () => {
           try {
             const data = await fetchFeatures();
             if (data.features) {
               setFeatures(data.features);
             }
           } catch (err) {
             console.log(err);
           } finally {
             setIsLoading(false);
           }
         })();
       }, []);
    
       return (
         <FeatureFlags.Provider value={{ features }}>
           {isLoading ? "Loading..." : children}
         </FeatureFlags.Provider>
       );
    };
    


    애플리케이션에 대한 useEffect 및 로드 상태를 추가했습니다.
    그리고 끝났습니다! 🎉

    마지막 단계는 컴포넌트에서 이것을 사용하는 것입니다.

    // components/PaymentOptions.js
    
    import { FeatureFlags } from "contexts/FeatureFlags";
    
    const PaymentOptions = () => {
      const { features } = React.useContext(FeatureFlags);
    
      const handleClick = () => alert("Payment successful!");
    
      return (
        <>
          <button className="btn" onClick={handleClick}>
            Credit Card
          </button>
          {features.isApplePayEnabled && (
            <button className="btn" onClick={handleClick}>
              Apple Pay
            </button>
          )}
          {features.isGooglePayEnabled && (
            <button className="btn" onClick={handleClick}>
              Google Pay
            </button>
          )}
        </>
      );
    };
    
    export default PaymentOptions;
    


    🚀 이제 새로 생성된 기능을 완벽하게 제어하여 이 앱을 시작할 수 있습니다.

    👏 우리는 원할 때마다 Google Pay를 활성화할 수 있으며 사용자는 즉시 볼 수 있습니다. 문제가 발생하면 두 결제 모드를 모두 비활성화할 수 있습니다.

    reply.send({ isGooglePayEnabled: false, isApplePayEnabled: false});
    



    떠나기 전에 마지막으로 이 구현은 최소한입니다. 팀의 필요에 맞게 확장할 수 있습니다. 내 마음에 드는 몇 가지 개선 사항은 다음과 같습니다.
  • 소품FeatureFlag을 가져오고 이를 기반으로 자식을 숨기거나 렌더링하는 구성 요소feature를 추가합니다.

  • <FeatureFlag feature="isGooglePayEnabled">
      <button onClick={handlePayment}>Google Pay</button>
    </FeatureFlag>
    


  • 캐싱 및 대체 메커니즘을 추가합니다. API 호출이 실패하면 어떻게 됩니까? 이러한 경우 캐시된 버전으로 대체할 수 있습니다.
    이것은 정말 흥미 롭습니다. 😉

  • 🔗 그리고 놀고 싶다면 여기 codesandbox link 가 있습니다.


    그게 다야! 👋

    이 기사가 어떤 식으로든 도움이 되었기를 바랍니다. 다른 사람들과 공유하는 것도 고려하십시오.

    🤙 무엇이든 이야기하고 싶다면 DM 또는 .

    좋은 웹페이지 즐겨찾기