<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://m1nsuppp.vercel.app/</id>
    <title>m1nsuppp의 블로그 Blog</title>
    <updated>2026-01-30T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://m1nsuppp.vercel.app/"/>
    <subtitle>m1nsuppp의 블로그 Blog</subtitle>
    <icon>https://m1nsuppp.vercel.app/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[AI가 환기한 엔지니어링의 본질]]></title>
        <id>https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering</id>
        <link href="https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering"/>
        <updated>2026-01-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[AI가 작성한 코드(구현)는 검증이 필요하다.]]></summary>
        <content type="html"><![CDATA[<p><strong>AI가 작성한 코드(구현)는 검증이 필요하다.</strong></p>
<p>이 말에는 <strong>사람의 코드는 신뢰할 수 있다</strong>는 전제가 붙는다. 하지만 정말 그랬을까?</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="솔직히-말하면">솔직히 말하면<a href="https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering#%EC%86%94%EC%A7%81%ED%9E%88-%EB%A7%90%ED%95%98%EB%A9%B4" class="hash-link" aria-label="솔직히 말하면에 대한 직접 링크" title="솔직히 말하면에 대한 직접 링크">​</a></h2>
<p>전통적인 코드 리뷰 프로세스를 떠올려보자.</p>
<p>PR이 올라오면 전체 diff를 훑어본다. 하지만 모든 줄을 정독했나? 동료가 작성한 코드의 구현이 정확한지 매번 검증했나?</p>
<p>솔직히, 아니었다.</p>
<p>주로 보는 건 이런 것들이었다.</p>
<ul>
<li>노출되는 인터페이스가 사용자 입장에서 합리적인가</li>
<li>테스트가 명세를 잘 표현하는가</li>
<li>설계 방향이 맞는가</li>
<li>팀의 코딩컨벤션(스타일)을 잘 따르는가</li>
</ul>
<p>내부 로직은 "맞겠지" 하고 넘어간 적이 많다. 모든 구현을 검증할 시간도 없었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="원래-중요했던-것">원래 중요했던 것<a href="https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering#%EC%9B%90%EB%9E%98-%EC%A4%91%EC%9A%94%ED%96%88%EB%8D%98-%EA%B2%83" class="hash-link" aria-label="원래 중요했던 것에 대한 직접 링크" title="원래 중요했던 것에 대한 직접 링크">​</a></h2>
<p>내가 가장 중요하게 생각하는 설계 원칙 중 하나는 **구현이 아닌 추상화에 의존하라.**는 원칙이다.</p>
<p>인터페이스와 구현을 분리한다. 중요한 것은 "어떻게"가 아니라 "무엇을"이다.</p>
<p>엔지니어링의 핵심은 구현이 아닌 추상화에 있다.</p>
<p>"어떻게 코드를 작성할 것인가"보다 "어떤 구조를 만들 것인가"가 훨씬 중요하다.</p>
<p>하지만 나는 늘 시간에 쫓겨 구조를 고민하기보다 코드를 채워 넣기에 급급했다.</p>
<p>AI 시대가 도래했음에도 여전히 <strong>코드 한 줄</strong>의 구현 자체에 집착하는 습관은 바로 여기에 있는 듯하다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="ai가-드러낸-것">AI가 드러낸 것<a href="https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering#ai%EA%B0%80-%EB%93%9C%EB%9F%AC%EB%82%B8-%EA%B2%83" class="hash-link" aria-label="AI가 드러낸 것에 대한 직접 링크" title="AI가 드러낸 것에 대한 직접 링크">​</a></h2>
<p>나는 AI가 내가 잊고 있던 본질을 강력하게 환기시켰다고 생각한다.</p>
<p>미시적인 영역(코드)에 신경쓰기보다는 거시적 관점에서 전체적인 설계를 보라는 것.</p>
<p>이것은 소프트웨어 엔지니어링이 원래 가르쳤던 본질이다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="마치며">마치며<a href="https://m1nsuppp.vercel.app/what-ai-reminded-me-about-software-engineering#%EB%A7%88%EC%B9%98%EB%A9%B0" class="hash-link" aria-label="마치며에 대한 직접 링크" title="마치며에 대한 직접 링크">​</a></h2>
<p>솔직히 말하면, 한동안은 마음이 헛헛했다.</p>
<p>내 머릿속 논리를 코드로 표현해가며 느끼던 그 코딩의 즐거움을 AI에게 빼앗긴 기분이 들었기 때문이다.</p>
<p>나는 이제 일의 재미를 어디서 찾아야하나 고민하기도 했다.</p>
<p>하지만 그 생각이 점차 바뀌고 있다. 좀 더 고수준의 일을 할 수 있게 됨으로써 엔지니어링의 재미가 점점 커지고 있다.</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Next.js에 대한 생각]]></title>
        <id>https://m1nsuppp.vercel.app/thoughts-on-nextjs</id>
        <link href="https://m1nsuppp.vercel.app/thoughts-on-nextjs"/>
        <updated>2025-11-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[그리고 프레임워크에 대한 생각]]></summary>
        <content type="html"><![CDATA[<p>그리고 프레임워크에 대한 생각</p>
<p>내가 가장 선호하는 웹 앱 프레임워크는 Next.js이다.</p>
<p>풀스택 웹 앱을 개발할 때, Next.js + Supabase 스택이 <strong>"가장 간단한 엔지니어링"</strong>
이라고 믿었기 때문이다.</p>
<p>참고로 나는 인스타그램 초기 엔지니어링 팀이 따랐던 핵심 소프트웨어 개발 3원칙을 굉장히 좋아한다.</p>
<div class="language-text codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-text codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token plain">1. 간단하게 유지할 것 (Keep things very simple)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">2. 바퀴를 재발명하지 말 것 (Don’t re-invent the wheel)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">3. 가능하면 입증된 견고한 기술을 사용할 것 (Use proven, solid technologies when possible)</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>나는 엔지니어링의 고통이 복잡함에서 발생한다고 생각하기 때문에, 이 3가지 원칙을 굉장히 중요하게 생각한다.</p>
<p>그런데 최근 Next.js 16 릴리즈를 보니 그들은 "엔지니어링 고통의 총량"을 줄이는 것에는 큰 관심이 없는 것 같다고 느꼈다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="nextjs가-아쉬운-지점">Next.js가 아쉬운 지점<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#nextjs%EA%B0%80-%EC%95%84%EC%89%AC%EC%9A%B4-%EC%A7%80%EC%A0%90" class="hash-link" aria-label="Next.js가 아쉬운 지점에 대��한 직접 링크" title="Next.js가 아쉬운 지점에 대한 직접 링크">​</a></h2>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="1-과도한-기능으로-인한-복잡도">1) 과도한 기능으로 인한 복잡도<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#1-%EA%B3%BC%EB%8F%84%ED%95%9C-%EA%B8%B0%EB%8A%A5%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EB%B3%B5%EC%9E%A1%EB%8F%84" class="hash-link" aria-label="1) 과도한 기능으로 인한 복잡도에 대한 직접 링크" title="1) 과도한 기능으로 인한 복잡도에 대한 직접 링크">​</a></h3>
<p>Next.js는 CSR, SSR, SSG, ISR 등 <strong>모든 렌더링 방법</strong>을 한 프레임워크에서 사용할 수 있도록 했지만, 이는 단순성을 잃게 했다고 본다.</p>
<p>개발자는 코드의 런타임이 <strong>빌드 시점</strong>인지, <strong>서버 사이드</strong>인지, <strong>클라이언트 사이드</strong>인지를 매번 고민해야 하기 떄문이다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="2-강결합">2) 강결합<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#2-%EA%B0%95%EA%B2%B0%ED%95%A9" class="hash-link" aria-label="2) 강결합에 대한 직접 링크" title="2) 강결합에 대한 직접 링크">​</a></h3>
<p>수십 년간 소프트웨어 공학에서 검증된 원칙은 <strong>느슨한 결합</strong>과 <strong>명확한 계층형 구조</strong>이다.</p>
<p>하지만 Next.js는 라우터, 번들러, 런타임이 <strong>강하게 결합</strong>되어 있다.</p>
<p>File system 기반 라우팅, Server component, Server action, Route segment config 등 너무나 많은 기능들이 Next.js에 종속적인 기능이다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="3-안정성">3) 안정성<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#3-%EC%95%88%EC%A0%95%EC%84%B1" class="hash-link" aria-label="3) 안정성에 대한 직접 링크" title="3) 안정성에 대한 직접 링크">​</a></h3>
<p>Next.js는 여러가지 문제 해결을 돕는 훌륭한 프레임워크다. 하지만 아직도 너무 많은 버그가 존재한다.</p>
<p>실제로 제품을 만드는 것보다 Next.js 버그를 해결하기 위한 시간이 더 많이 소비되는 일도 허다하다.</p>
<hr>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="맺으며">맺으며<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#%EB%A7%BA%EC%9C%BC%EB%A9%B0" class="hash-link" aria-label="맺으며에 대한 직접 링크" title="맺으며에 대한 직접 링크">​</a></h2>
<p>Next.js는 여전히 훌륭한 프레임워크이고, 그 영향력은 부정할 수 없다.</p>
<p>하지만 프레임워크가 더 많은 기능을 품을수록, 개발자가 짊어져야 할 복잡성의 무게도 커진다.</p>
<p>나는 프레임워크가 개발자의 자유를 제한하지 않고, 단순함을 유지하는 방향으로 진화하길 바란다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="참고한-글">참고한 글<a href="https://m1nsuppp.vercel.app/thoughts-on-nextjs#%EC%B0%B8%EA%B3%A0%ED%95%9C-%EA%B8%80" class="hash-link" aria-label="참고한 글에 대한 직접 링크" title="참고한 글에 대한 직접 링크">​</a></h3>
<ul>
<li><a href="https://blog.webf.zone/why-next-js-falls-short-on-software-engineering-d3575614bd08" target="_blank" rel="noopener noreferrer">https://blog.webf.zone/why-next-js-falls-short-on-software-engineering-d3575614bd08</a></li>
</ul>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[고객사별 맞춤형 B2B SaaS로 나아가기 위한 데이터 모델링 전략]]></title>
        <id>https://m1nsuppp.vercel.app/multi-tenant-configuration-model</id>
        <link href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model"/>
        <updated>2025-07-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Configurable Schema를 활용해 다양한 고객사의 요구사항을 만족시키기 위해 노력했던 고민]]></summary>
        <content type="html"><![CDATA[<p>Configurable Schema를 활용해 다양한 고객사의 요구사항을 만족시키기 위해 노력했던 고민</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="들어가며">들어가며<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0" class="hash-link" aria-label="들어가며에 대한 직접 링크" title="들어가며에 대한 직접 링크">​</a></h2>
<p>B2B SaaS 제품을 개발하다 보면 피할 수 없는 문제가 있다. 바로 고객사마다 다른 요구사항이다. 특히 데이터 구조, UI 표현, 그리고 비즈니스 로직에 있어서 각 고객사는 자신들만의 방식을 원한다. 이런 상황에서 고객사가 늘어날 때마다 새로운 코드를 작성하거나 데이터베이스 스키마를 변경하는 것은 지속 가능한 방법이 아니다.</p>
<p>개발자로서, 이 문제를 해결하기 위해 Configurable Schema와 메타데이터 기반 접근법을 구현했다. 이 글에서는 데이터 모델, UI 구성, 그리고 복잡한 비즈니스 로직까지 설정 기반으로 처리한 과정과 해결책을 공유하고자 한다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="직면했던-문제-다양한-도메인-다양한-요구사항">직면했던 문제: 다양한 도메인, 다양한 요구사항<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EC%A7%81%EB%A9%B4%ED%96%88%EB%8D%98-%EB%AC%B8%EC%A0%9C-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%8B%A4%EC%96%91%ED%95%9C-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD" class="hash-link" aria-label="직면했던 문제: 다양한 도메인, 다양한 요구사항에 대한 직접 링크" title="직면했던 문제: 다양한 도메인, 다양한 요구사항에 대한 직접 링크">​</a></h2>
<p>우리 서비스는 다양한 업종의 고객사를 대상으로 3D 제품 뷰어와 대시보드를 제공한다. 가장 많은 업종은 인테리어/가구 회사부터 아주 버티컬한 사업을 하는 자동차 특장차 제작업체까지, 각 업종마다 중요하게 생각하는 데이터가 달랐다.</p>
<ul>
<li>A 가구사는 프리셋의 총 치수, 재질, 조명의 총 전력량을 표시하길 원했다.</li>
<li>B 가구사는 선반이 2단, 3단, 5단인지를 표시하길 원했다.</li>
<li>자동차 특장차 제작업체는 하중과 같은 업계 특화 데이터를 표시하길 원했다.</li>
</ul>
<p>더 큰 문제는 같은 업종 내에서도 각 회사마다 사용하는 용어가 달랐다는 점이다. 이는 단순한 UI 문제가 아니라 데이터 모델링의 문제였다.</p>
<p>그리고 가장 복잡했던 부분은 비즈니스 로직이었다. 예를 들어, 가격 계산 방식이 고객사마다 완전히 달랐다.</p>
<ul>
<li>일부 가구사는 제품 너비에 따라 가격이 증가하는 방식을 사용했다.</li>
<li>다른 회사들은 재질과 옵션에 따른 복잡한 가격 계산 공식을 가지고 있었다.</li>
<li>특장차 제작업체는 하중 용량과 특수 기능에 따른 가격 체계를 원했다.</li>
</ul>
<p>이미 여러 고객사를 지원해야 했고, 앞으로도 늘어날 고객사를 생각하면, 고객사별로 데이터베이스를 설계하거나 API를 새로 구현하는 것은 현실적으로 불가능했다. 더구나 각 고객사별로 다른 비즈니스 로직을 하드코딩하는 것은 유지보수 악몽이 될 것이 분명했다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="해결책-configurable-schema">해결책: Configurable Schema<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%ED%95%B4%EA%B2%B0%EC%B1%85-configurable-schema" class="hash-link" aria-label="해결책: Configurable Schema에 대한 직접 링크" title="해결책: Configurable Schema에 대한 직접 링크">​</a></h2>
<p>이 문제를 해결하기 위해 나는 매우 유연하게 설정 가능한 스키마를 설계할 수 있게 하면 어떨까 생각했다. 그리고 핵심 아이디어는 간단했다.</p>
<ol>
<li>모든 가능한 필드를 정의하는 메타데이터 계층 생성</li>
<li>각 테넌트(고객사)별로 필요한 필드만 활성화하는 설정 계층 구현</li>
<li>실제 데이터는 Configurable Schema에 따라 저장 및 표시</li>
</ol>
<p>이를 Zod로 표현해보면 아래와 같다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> z </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"zod"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 필드 정의 스키마</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> fieldDefinitionSchema </span><span class="token operator">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  type</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">enum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"string"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"number"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"boolean"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"array"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"object"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"date"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  isRequired</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  defaultValue</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">optional</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  options</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">optional</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  validation</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">optional</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 테넌트별 설정 스키마</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tenantConfigSchema </span><span class="token operator">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  fields</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  labels</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  order</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">optional</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 전체 커스텀 데이터 스키마</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> customDataSchema </span><span class="token operator">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  version</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"1.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  fieldDefinitions</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">fieldDefinitionSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  values</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  tenantConfigs</span><span class="token operator">:</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">record</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tenantConfigSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">CustomData</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">infer</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">typeof</span><span class="token plain"> customDataSchema</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이 스키마의 각 부분은 다음과 같은 역할을 한다.</p>
<ul>
<li><code>fieldDefinitions</code>: 시스템에서 사용 가능한 모든 필드의 타입, 유효성 검사 규칙 등을 정의</li>
<li><code>values</code>: 실제 데이터 값을 저장</li>
<li><code>tenantConfigs</code>: 각 테넌트별로 어떤 필드를 사용할지, 어떤 레이블로 표시할지, 어떤 순서로 배치할지 정의</li>
</ul>
<p>그리고 이 스키마를 사용하여 각 테넌트별로 필요한 데이터만 추출하고, 해당 테넌트에 맞는 레이블과 순서로 표시할 수 있었다. 이를 통해 하나의 데이터 모델로 다양한 고객사의 요구사항을 충족시킬 수 있었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="server-driven-ui-백엔드에서-ui-구성-제어하기">Server Driven UI: 백엔드에서 UI 구성 제어하기<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#server-driven-ui-%EB%B0%B1%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-ui-%EA%B5%AC%EC%84%B1-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0" class="hash-link" aria-label="Server Driven UI: 백엔드에서 UI 구성 제어하기에 대한 직접 링크" title="Server Driven UI: 백엔드에서 UI 구성 제어하기에 대한 직접 링크">​</a></h2>
<p>다음 단계는 Server Driven UI 패턴 적용이었다.</p>
<p>우리의 3D 제품 뷰어에서는 다음과 같이 구현했다:</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 서버에서 전달되는 UI 구성 정의</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">UIConfiguration</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  components</span><span class="token operator">:</span><span class="token plain"> UIComponentDefinition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  layout</span><span class="token operator">:</span><span class="token plain"> LayoutDefinition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">UIComponentDefinition</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "select", "text", "filter" 등</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  properties</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 클라이언트에서 UI 구성을 렌더링하는 함수</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">renderUIComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  component</span><span class="token operator">:</span><span class="token plain"> UIComponentDefinition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  tenantId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ReactNode </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">switch</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">component</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"select"</span><span class="token operator">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">SelectComponent </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">...</span><span class="token plain">component</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">properties</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> tenantId</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token operator">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">TextComponent </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">...</span><span class="token plain">component</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">properties</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> tenantId</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"filter"</span><span class="token operator">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">FilterComponent </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">...</span><span class="token plain">component</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">properties</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> tenantId</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">tenantId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token operator">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이 접근법의 핵심은 UI 컴포넌트의 타입, 속성, 배치 등을 서버에서 JSON 형태로 전달받아 동적으로 렌더링하는 것이다. 이를 통해 코드 변경 없이도 다양한 고객사의 UI 요구사항을 충족시킬 수 있었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="고객사-별-비즈니스-로직은">고객사 별 비즈니스 로직은?<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EA%B3%A0%EA%B0%9D%EC%82%AC-%EB%B3%84-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EC%9D%80" class="hash-link" aria-label="고객사 별 비즈니스 로직은?에 대한 직접 링크" title="고객사 별 비즈니스 로직은?에 대한 직접 링크">​</a></h2>
<p>데이터 스키마와 UI를 설정 기반으로 만든 후, 다음 도전 과제는 복잡한 비즈니스 로직을 처리하는 것이었다. 특히 가격 계산이나 할인 규칙과 같은 로직은 고객사마다 크게 달랐다.</p>
<p>이를 위해 우리는 선언적 규칙 엔진(Declarative Rules Engine)을 설계했다. 이 접근법의 핵심은 비즈니스 로직을 코드가 아닌 데이터로 표현하는 것이다.</p>
<p>여기서는 TypeScript의 타입 시스템으로 예를 들어본다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 규칙 엔진 타입 정의</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">Rule</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  params</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  condition</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> Condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">Condition</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"and"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"or"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"not"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"eq"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gt"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lt"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gte"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lte"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Array</span><span class="token operator">&lt;</span><span class="token plain">Condition </span><span class="token operator">|</span><span class="token plain"> ConditionLeaf</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ConditionLeaf</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">RuleSet</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  description</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  rules</span><span class="token operator">:</span><span class="token plain"> Rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  version</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 테넌트별 규칙 저장소</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">TenantRuleRepository</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">getRuleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tenantId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ruleSetId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">RuleSet</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">saveRuleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tenantId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ruleSet</span><span class="token operator">:</span><span class="token plain"> RuleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 규칙 실행기</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">RuleEngine</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> ruleHandlers</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> RuleHandler</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> conditionEvaluator</span><span class="token operator">:</span><span class="token plain"> ConditionEvaluator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">constructor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">conditionEvaluator</span><span class="token operator">:</span><span class="token plain"> ConditionEvaluator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">conditionEvaluator </span><span class="token operator">=</span><span class="token plain"> conditionEvaluator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">registerHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> handler</span><span class="token operator">:</span><span class="token plain"> RuleHandler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ruleHandlers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">executeRuleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ruleSet</span><span class="token operator">:</span><span class="token plain"> RuleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    context</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain">context </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> rule </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">of</span><span class="token plain"> ruleSet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// 조건이 있으면 평가</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">condition </span><span class="token operator">&amp;&amp;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">!</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">conditionEvaluator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">evaluate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> handler </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ruleHandlers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">No handler registered for rule type: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">rule</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">// 규칙 실행 및 결과 업데이트</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> handler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 규칙 핸들러 인터페이스</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">RuleHandler</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    rule</span><span class="token operator">:</span><span class="token plain"> Rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    context</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token operator">&gt;&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이 추상화된 규칙 엔진을 사용하면 다양한 비즈니스 로직을 JSON 설정으로 표현할 수 있다. 예를 들어, 너비에 따른 가격 계산 규칙은 다음과 같이 표현할 수 있다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 너비 기반 가격 계산 규칙 예시 (JSON 형태)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> widthBasedPricingRuleSet</span><span class="token operator">:</span><span class="token plain"> RuleSet </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pricing-rules-tenant-a"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"너비 기반 가격 계산"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  description</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"제품 너비에 따라 가격을 계산하는 규칙"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  version</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  rules</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"min-width-price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"setField"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">640000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      condition</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lte"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"width"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"max-width-price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"setField"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">896000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      condition</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gte"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"width"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1200</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"incremental-price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"calculateField"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"price"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        formula</span><span class="token operator">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token string" style="color:rgb(255, 121, 198)">"basePrice + (Math.ceil((width - baseWidth) / incrementUnit) * pricePerUnit)"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        variables</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          basePrice</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">640000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          baseWidth</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">800</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          incrementUnit</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          pricePerUnit</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">12800</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      condition</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"and"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"width"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">600</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            operator</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            operands</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"width"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1200</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이제는 비즈니스 로직이 코드가 아닌 데이터로 표현되어 개발자가 아닌 사람도 이해하고 수정할 수 있다.</p>
<ul>
<li>사실 아직까지는 나만 수정하고 있다😂</li>
<li>가까운 미래에 기능으로 출시될 예정이다.</li>
</ul>
<p>이러한 규칙 엔진을 통해 우리는 다양한 고객사의 비즈니스 로직을 하나의 코드베이스로 처리할 수 있었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="실제-적용-사례">실제 적용 사례<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-%EC%82%AC%EB%A1%80" class="hash-link" aria-label="실제 적용 사례에 대한 직접 링크" title="실제 적용 사례에 대한 직접 링크">​</a></h2>
<p>현재 구현한 Configurable Schema와 Server Driven UI를 통해 다양한 고객사의 요구사항을 효과적으로 충족시킬 수 있었다. 다음은 각 고객사별로 어떻게 데이터와 UI를 구성할 수 있는지에 대한 예시다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="인테리어가구-회사-a">인테리어/가구 회사 A<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EC%9D%B8%ED%85%8C%EB%A6%AC%EC%96%B4%EA%B0%80%EA%B5%AC-%ED%9A%8C%EC%82%AC-a" class="hash-link" aria-label="인테리어/가구 회사 A에 대한 직접 링크" title="인테리어/가구 회사 A에 대한 직접 링크">​</a></h3>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// A 가구사 테넌트 설정</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tenantAConfig </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  fields</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"totalDimensions"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"material"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lightingPower"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  labels</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    totalDimensions</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"총 치수"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    material</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"재질"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    lightingPower</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"조명 전력량"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  order</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"material"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"totalDimensions"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"lightingPower"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 서버에서 전달하는 UI 구성</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tenantAUIConfig </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  components</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        content</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"제품 상세 정보"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        style</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"heading"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"select"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"material-selector"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"재질 선택"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        options</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"오크 원목"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"월넛"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"체리"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        defaultValue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"오크 원목"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"filter"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dimension-filter"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"치수 필터"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"totalDimensions"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        options</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"소형"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"중형"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"대형"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  layout</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"vertical"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    spacing</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"medium"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="자동차-특장차-제작업체">자동차 특장차 제작업체<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EC%9E%90%EB%8F%99%EC%B0%A8-%ED%8A%B9%EC%9E%A5%EC%B0%A8-%EC%A0%9C%EC%9E%91%EC%97%85%EC%B2%B4" class="hash-link" aria-label="자동차 특장차 제작업체에 대한 직접 링크" title="자동차 특장차 제작업체에 대한 직접 링크">​</a></h3>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 특장차 제작업체 테넌트 설정</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tenantCConfig </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  fields</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"loadCapacity"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dimensions"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"specialFeatures"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  labels</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    loadCapacity</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"최대 하중"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    dimensions</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"제원"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    specialFeatures</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"특수 기능"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 서버에서 전달하는 UI 구성</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tenantCUIConfig </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  components</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"title"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        content</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"특장차 사양"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        style</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"heading"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"select"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"load-capacity"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"하중 선택"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        options</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"1톤"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.5톤"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"5톤"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        defaultValue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2.5톤"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"filter"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      id</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"feature-filter"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      properties</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"특수 기능 필터"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        field</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"specialFeatures"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        options</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"냉동"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"리프트"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"크레인"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  layout</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"grid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    columns</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이러한 접근법을 통해 각 고객사는 자신들에게 필요한 필드와 UI 컴포넌트만 사용할 수 있게 되었으며, 하나의 코드베이스로 모든 고객사의 요구사항을 충족시킬 수 있었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="비즈니스-가치">비즈니스 가치<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EA%B0%80%EC%B9%98" class="hash-link" aria-label="비즈니스 가치에 대한 직접 링크" title="비즈니스 가치에 대한 직접 링크">​</a></h2>
<p>이러한 나의 고민들은 다음과 같은 비즈니스 가치를 가져왔다.</p>
<ol>
<li><strong>확장성</strong>: 새로운 고객사 온보딩 시 코드 변경 없이 설정만으로 요구사항 충족 가능</li>
<li><strong>비즈니스 민첩성</strong>: 새로운 필드나 UI 컴포넌트 추가가 간단해짐</li>
<li><strong>개발 효율성</strong>: 하나의 코드베이스로 모든 고객사 지원으로 개발 효율성 향상</li>
<li><strong>A/B 테스트 용이성</strong>: 서버에서 UI 구성을 변경하여 쉽게 다양한 버전 테스트 가능(TBD와 같이 적용해보고 있다)</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="여전히-존재하는-문제점">여전히 존재하는 문제점<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EC%97%AC%EC%A0%84%ED%9E%88-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-%EB%AC%B8%EC%A0%9C%EC%A0%90" class="hash-link" aria-label="여전히 존재하는 문제점에 대한 직접 링크" title="여전히 존재하는 문제��점에 대한 직접 링크">​</a></h2>
<p>물론 이 접근법에도 몇 가지 문제와 어려움이 남아있다.</p>
<ul>
<li>개발자 경험<!-- -->
<ul>
<li>디버깅이 어렵다...! toss의 useFunnel이 제공하는 시각화 툴을 만들어보는게 어떨까 싶기도 한데, 현재로서는 유지보수자가 나뿐이라 오버엔지니어링이라는 생각이 든다.</li>
</ul>
</li>
<li>코드베이스 복잡성<!-- -->
<ul>
<li>추상화 수준이 높아져서 코드의 이해도가 떨어진다.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/multi-tenant-configuration-model#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>멀티 테넌트 B2B SaaS 개발에서 고객사별 요구사항을 효과적으로 처리하는 것은 큰 도전이었다. 그리고 앞으로도 계속해서 도전이 될 것 같다.</p>
<p>그래도 이 설계와 아이디어가 나에게는 큰 도움이 되었다. 특히나 확장성과 유지보수성 간의 트레이드오프, 아키텍처 복잡성 고려 등 아주 중요한 부분들도 고민해보고, 2년이 약간 넘는 개발 경력 중 가장 큰 도전이었다고 생각한다.</p>
<p>그래도 나름 프론트엔드와 백엔드를 아우르는 통합적 시각이 이런 복잡한 문제를 해결하는 데 큰 도움이 된 것 같다.</p>
<p>성장... 하고 있는 것 같긴하다..!</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="data structure" term="data structure"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[의존성 주입을 활용하여 테스트 가능한 Zustand store 만들기]]></title>
        <id>https://m1nsuppp.vercel.app/testable-zustand-store</id>
        <link href="https://m1nsuppp.vercel.app/testable-zustand-store"/>
        <updated>2025-02-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[React Context API를 의존성 주입 도구로 활용하여 Mocking 없이 Zustand store 테스트하기]]></summary>
        <content type="html"><![CDATA[<p>React Context API를 의존성 주입 도구로 활용하여 Mocking 없이 Zustand store 테스트하기</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="들어가며">들어가며<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0" class="hash-link" aria-label="들어가며에 대한 직접 링크" title="들어가며에 대��한 직접 링크">​</a></h2>
<p>나는 전역 상태를 선호하지 않는다. 그 이유는 아래와 같다.</p>
<ul>
<li>컴포넌트 간의 의존성을 이해하고 관리하기 어렵다.</li>
<li>어디서 어떤 변경이 일어났는지 추적하기가 어렵다.</li>
<li>상태 변경이 여러 곳에서 일어날 수 있어서 상태를 예측하기 어렵다.</li>
</ul>
<p>그럼에도 불구하고 이미지 에디터를 개발하게 되면서, 전역 상태 관리 라이브러리를 사용할 수 밖에 없었다. 그 주요한 이유는 아래와 같다.</p>
<ul>
<li>UI와 Canvas가 서로 영향을 미쳐야한다.</li>
<li>성능 최적화를 꿰해야 한다.</li>
</ul>
<p>내 선호도와 관계없이, 전역 상태 관리 라이브러리가 가져다주는 이점이 크다고 생각했기 때문이다.</p>
<p>그리고 별 다른 문제없이 제품을 시장에 출시했고, 서비스 운영을 이어나갔다.</p>
<p>하지만 시간이 지남에 따라 코드베이스는 커져갔고, 이에 따라 엔지니어링적인 문제들이 수반되었다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="빠른-개발과-품질의-딜레마">빠른 개발과 품질의 딜레마<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EB%B9%A0%EB%A5%B8-%EA%B0%9C%EB%B0%9C%EA%B3%BC-%ED%92%88%EC%A7%88%EC%9D%98-%EB%94%9C%EB%A0%88%EB%A7%88" class="hash-link" aria-label="빠른 개발과 품질의 딜레마에 대한 직접 링크" title="빠른 개발과 품질의 딜레마에 대한 직접 링크">​</a></h2>
<p>제품의 개발기, 도입기를 거쳐 성장기에 진입하며 우리 팀은 좀 더 빠르게 움직일 수 있길 원했다.</p>
<p>개발자인 나는 더 짧은 주기로 배포 주기를 가져가야 했지만, 제약이 되는 걸림돌이 있었다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="1-수동-테스트에-의존적인-요구사항-검증">1. 수동 테스트에 의존적인 요구사항 검증<a href="https://m1nsuppp.vercel.app/testable-zustand-store#1-%EC%88%98%EB%8F%99-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90-%EC%9D%98%EC%A1%B4%EC%A0%81%EC%9D%B8-%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EA%B2%80%EC%A6%9D" class="hash-link" aria-label="1. 수동 테스트에 의존적인 요구사항 검증에 대한 직접 링크" title="1. 수동 테스트에 의존적인 요구사항 검증에 대한 직접 링크">​</a></h3>
<p>이미지 에디터 특성상 Canvas와 UI간 상호작용이 매우 복잡했다. 예를 들어 단축키로 UNDO/REDO를 수행한다고 해보면,</p>
<ul>
<li>Canvas 내 이미지가 이전/이후 상태로 변경되어야 한다.</li>
<li>UI의 모든 컨트롤(회전 각도, 밝기, 대비 등)이 해당 시점의 값으로 업데이트 되어야 한다.</li>
<li>여러 개의 이미지를 편집 중일 때에는 현재 선택된 이미지의 히스토리만 영향을 받아야 한다.</li>
</ul>
<p>이런 복잡한 상호작용은 수동 테스트로 검증하기에는 너무 많은 시간이 필요했고, 놓치기 쉬운 엣지 케이스도 많았다. 특히나 우리 팀은 별도의 QA 인력이 없기에 품질 검증이 더욱 힘들었다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="2-잦은-hotfix">2. 잦은 Hotfix<a href="https://m1nsuppp.vercel.app/testable-zustand-store#2-%EC%9E%A6%EC%9D%80-hotfix" class="hash-link" aria-label="2. 잦은 Hotfix에 대한 직접 링크" title="2. 잦은 Hotfix에 대한 직접 링크">​</a></h3>
<p>예상치 못한 버그가 프로덕션에서 발생하는 일이 잦았다. 이는 hotfix의 빈도를 증가시켰고, 계획된 작업 일정과 리소스 관리에 영향을 미치게 됐다.</p>
<hr>
<p>배포 주기에 영향을 주는 문제들을 해결해야겠다고 생각이 들었고, 버그 케이스를 커버할 수 있는 테스트라도 작성해야겠다고 생각했다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="테스트가-불가능한의미없는-코드">테스트가 불가능한(의미없는) 코드<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%ED%85%8C%EC%8A%A4%ED%8A%B8%EA%B0%80-%EB%B6%88%EA%B0%80%EB%8A%A5%ED%95%9C%EC%9D%98%EB%AF%B8%EC%97%86%EB%8A%94-%EC%BD%94%EB%93%9C" class="hash-link" aria-label="테스트가 불가능한(의미없는) 코드에 대한 직접 링크" title="테스트가 불가능한(의미없는) 코드에 대한 직접 링크">​</a></h2>
<p>버그 케이스를 커버하는 테스트 작성. 의의는 좋다.</p>
<p>문제는 Zustand에 의존적인 모듈들을 테스트하기 상당히 어려웠다. <a href="https://zustand.docs.pmnd.rs/guides/testing" target="_blank" rel="noopener noreferrer">공식 문서</a>는 zustand를 mocking하고 테스트 사이에 store를 reset하는 등에 관한 내용이 전부였다.</p>
<p>그리고 Zustand store가 전역 싱글톤으로 존재하기에 테스트마다 독립적인 환경을 만들기가 어려웠다.</p>
<ul>
<li>의존성 체이닝을 따라가며 문제를 해결하려고 하니, 테스트 환경 세팅만 한 세월이었다.</li>
</ul>
<p>테스트 작성 속도를 높이고 복잡한 의존성 작성을 피하고자 Mocking을 자주하게 되다보니 테스트 코드 작성의 의의가 흐려지게 됐다.</p>
<p>그래서 이 문제를 해결하기 위해 어떤 방법을 이용했는지 소개해보겠다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="의존성-주입-활용하기">의존성 주입 활용하기<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0" class="hash-link" aria-label="의존성 주입 활용하기에 대한 직접 링크" title="의존성 주입 활용하기에 대한 직접 링크">​</a></h2>
<p>말보다는 코드로 예를 들어보겠다. 에디터에서 사용될 이미지 상태가 있다고 해보자.</p>
<div class="language-ts codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-ts codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">ImageLayer</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  path</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  width</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  height</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  position</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    y</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  effect</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    brightness</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    contrast</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이제 실제로 사용하는 쪽에서 의존성을 주입할 수 있는 구조를 만들어가보겠다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="1-store-추상화">1. Store 추상화<a href="https://m1nsuppp.vercel.app/testable-zustand-store#1-store-%EC%B6%94%EC%83%81%ED%99%94" class="hash-link" aria-label="1. Store 추상화에 대한 직접 링크" title="1. Store 추상화에 대한 직접 링크">​</a></h3>
<p>먼저 Store에서 관리할 상태, 액션들에 대해 추상화했다.</p>
<div class="language-ts codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-ts codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">MyStore</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  imageLayers</span><span class="token operator">:</span><span class="token plain"> ImageLayer</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  actions</span><span class="token operator">:</span><span class="token plain"> MyStoreActions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="2-store-구현">2. Store 구현<a href="https://m1nsuppp.vercel.app/testable-zustand-store#2-store-%EA%B5%AC%ED%98%84" class="hash-link" aria-label="2. Store 구현에 대한 직접 링크" title="2. Store 구현에 대한 직접 링크">​</a></h3>
<p>실제 Store를 생성하는 구현체를 만든다.</p>
<div class="language-ts codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-ts codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> createStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">StoreApi</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"zustand"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">MyStoreState</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> Omit</span><span class="token operator">&lt;</span><span class="token plain">MyStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"actions"</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> defaultState</span><span class="token operator">:</span><span class="token plain"> MyStoreState </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  imageLayers</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">createMyStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  initialState</span><span class="token operator">:</span><span class="token plain"> MyStoreState </span><span class="token operator">=</span><span class="token plain"> defaultState</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> StoreApi</span><span class="token operator">&lt;</span><span class="token plain">MyStore</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">createStore</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">MyStoreState</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">...</span><span class="token plain">initialState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    actions</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">//...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>여기서 중요한 포인트는 Zustand의 <code>create</code> API가 아닌 <code>createStore</code> API를 사용했다는 것이다.</p>
<ul>
<li>그 이유는 <a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EC%99%9C-createstore-api%EC%9D%BC%EA%B9%8C">여기</a>에서 설명하겠다.</li>
</ul>
<p><a href="https://zustand.docs.pmnd.rs/apis/create-store" target="_blank" rel="noopener noreferrer">createStore API</a>는 더 낮은 수준의 API로 React에 의존하지 않으며, store 인스턴스를 직접 생성하고 관리할 때 사용된다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="3적용">3.적용<a href="https://m1nsuppp.vercel.app/testable-zustand-store#3%EC%A0%81%EC%9A%A9" class="hash-link" aria-label="3.적용에 대한 직접 링크" title="3.적용에 대한 직접 링크">​</a></h3>
<p>React의 Context API를 의존성 주입 도구로 활용하여 실제 store를 주입해보자.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// my-store-context.tsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> createContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> useContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">ReactNode</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"react"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> useStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">StoreApi</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"zustand"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">MyStoreContext</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">createContext</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">StoreApi</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">MyStore</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator">|</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name keyword" style="color:rgb(189, 147, 249);font-style:italic">undefined</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">undefined</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token maybe-class-name">MyStoreContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">displayName</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"MyStoreContext"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">MyStoreProviderProps</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  initialState</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">MyStoreState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  children</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">ReactNode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">MyStoreProvider</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  children</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  initialState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> </span><span class="token maybe-class-name">MyStoreProviderProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">JSX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Element</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 주입 받은 값으로 store 생성</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">StoreApi</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">MyStore</span><span class="token generic-function generic class-name operator">&gt;&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">createMyStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">initialState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreContext.Provider</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">value</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">store</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">children</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreContext.Provider</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이렇게 Zustand store를 사용하는 쪽에서 의존성을 주입할 수 있는 구조로 설계가 되었다.</p>
<p>그리고 이제 우리는 Zustand store의 상태를 Context에서 가져오고, 업데이트 하면 된다. 일반적인 Zustand Store를 사용하는 것과 다를 것이 없다.</p>
<div class="language-ts codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-ts codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useMyStore</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name constant" style="color:rgb(189, 147, 249)">S</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token function-variable function" style="color:rgb(80, 250, 123)">selector</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token operator">:</span><span class="token plain"> MyStoreState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">S</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">S</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> store </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">MyStoreContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그리고 이제 실제 사용해보자. 서버에서 받아온 데이터를 store에 초기화하는 예를 들어보겠다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 서버 데이터</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> remoteData </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchRemoteData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreProvider</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">initialState</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">remoteData</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">UI</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Canvas</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreProvider</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>서버 데이터를 Zustand store에 초기화 시도를 해본 개발자들이라면 어떤 점이 나아졌는지 눈에 보일 것이다(하지만 그 부분에 대한 설명은 생략한다).</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="4-테스트-코드-작성하기">4. 테스트 코드 작성하기<a href="https://m1nsuppp.vercel.app/testable-zustand-store#4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0" class="hash-link" aria-label="4. 테스트 코드 작성하기에 대한 직접 링크" title="4. 테스트 코드 작성하기에 대한 직접 링크">​</a></h3>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">it</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"UI에서 비율을 변경하면 캔버스의 크기가 변경된다."</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> userEvent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">render</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreProvider</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">initialState</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">        ratio</span><span class="token tag script language-javascript operator" style="color:rgb(255, 121, 198)">:</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">          width</span><span class="token tag script language-javascript operator" style="color:rgb(255, 121, 198)">:</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">1</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">          height</span><span class="token tag script language-javascript operator" style="color:rgb(255, 121, 198)">:</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">1</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">        </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">      </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">RatioMenu</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Canvas</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">MyStoreProvider</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">click</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> screen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">findByRole</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"button"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"16:9"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> canvas </span><span class="token operator">=</span><span class="token plain"> container</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">querySelector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"canvas"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">expect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">canvas</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">toHaveStyle</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    width</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"100%"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    height</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"56.25%"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이런 식으로 기대하는 동작만 검증할 수 있게 됐다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="맺으며">맺으며<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EB%A7%BA%EC%9C%BC%EB%A9%B0" class="hash-link" aria-label="맺으며에 대한 직접 링크" title="맺으며에 대한 직접 링크">​</a></h2>
<p>제품의 품질을 개선하기 위한 고민에서 시작한 테스트 코드 도입기는 많은 교훈, 생각을 안겨주었다.</p>
<ul>
<li>테스트 코드는 최고의 요구사항 문서다. 특히 구독 결제 비즈니스 특성상 이 규칙이 굉장히 복잡한데, 결제 테스트 코드가 항상 최신의 상태를 보장하는 비즈니스 요구사항 문서의 역할을 하고있다.</li>
<li>"처음부터 테스트 가능한 구조로 설계를 했더라면 어땠을까?" 생각하기도 했다. <del>물론 그 때는 아무 것도 몰랐다</del></li>
<li>"적정 엔지니어링에 대한 훈련은 어떻게 할 수 있을까?" 생각했다. <del>개발자 커리어가 끝나는 순간에도 모르지 않을까</del></li>
</ul>
<hr>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="왜-createstore-api일까">왜 createStore API일까?<a href="https://m1nsuppp.vercel.app/testable-zustand-store#%EC%99%9C-createstore-api%EC%9D%BC%EA%B9%8C" class="hash-link" aria-label="왜 createStore API일까?에 대한 직접 링크" title="왜 createStore API일까?에 대한 직접 링크">​</a></h4>
<p><code>create</code> API는 전역 싱글톤 스토어를 생성한다.</p>
<p>그런데 우리가 만약 여러개의 <code>MyStore</code>가 필요하다고 가정해보자.</p>
<p>의존성(props) 주입 시, 내 생각에는 두 가지 선택지가 있을 것 같다.</p>
<ol>
<li>props가 변경될 때마다 store를 새로 생성한다</li>
<li>props가 변경되어도 store는 유지하고, 필요한 부분만 업데이트한다</li>
</ol>
<p>그런데 <code>create</code> API를 사용하면 전역 싱글톤이기 때문에 1번 방식을 사용할 수 없다.</p>
<ul>
<li>store가 한 번 생성되면 그 상태를 계속 유지해야 하기 때문이다.</li>
</ul>
<div class="language-ts codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-ts codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// create API 사용 시</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> useStore </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">create</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Store</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ❌ props가 바뀌어도 store는 이미 생성되어 있어서 영향 없음</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  value</span><span class="token operator">:</span><span class="token plain"> props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">initialValue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>반면 <code>createStore</code> API를 사용하면 의존성(props) 변경에 따라 store를 자유롭게 초기화할 수 있다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> store </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">createStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token spread operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Context.Provider</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">value</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">store</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">...</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Context.Provider</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="testing" term="testing"/>
        <category label="zustand" term="zustand"/>
        <category label="react" term="react"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Supabase에서 자체 API 서버로, IoC와 의존성 제어 활용기]]></title>
        <id>https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api</id>
        <link href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api"/>
        <updated>2024-11-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Supabase에서 자체 API 서버로 전환하는 과정에서 제어의 역전(IoC)과 의존성 제어를 어떻게 활용했는지에 대한 경험]]></summary>
        <content type="html"><![CDATA[<p>Supabase에서 자체 API 서버로 전환하는 과정에서 제어의 역전(IoC)과 의존성 제어를 어떻게 활용했는지에 대한 경험</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="배경">배경<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#%EB%B0%B0%EA%B2%BD" class="hash-link" aria-label="��배경에 대한 직접 링크" title="배경에 대한 직접 링크">​</a></h2>
<p>처음에는 개발자가 2명뿐이었다. AI 엔지니어와 웹 개발자인 나.</p>
<p>대부분의 스타트업들이 그렇듯이 빠르게 시장에 던져보고, 반응을 살펴야했다. 그렇기 때문에 Supabase는 우리에게 최적의 솔루션이었다.</p>
<p>서비스 초기, 우리 팀은 제품 개발과 신규 기획, 마케팅 등 수많은 일들을 빠른 시간 안에 해내야했다.</p>
<p>하지만 동시에 제품의 엔지니어로서 기술 부채를 쌓지 않기 위해 외부 의존성을 제어할 수 있는 아키텍처에 대한 고민을 놓치지 않고싶었다.</p>
<p>사실 지금이나 그 때나 햇병아리 개발자인 것은 똑같지만, 그 당시에는 책에서 배운 개념들을 실무에 녹여내고 싶어했던 것 같다.</p>
<blockquote>
<ol>
<li>외부 의존성에 직접 의존하지 말 것</li>
<li>구현이 아닌 추상화에 의존할 것</li>
</ol>
</blockquote>
<p>돌이켜보면, 당장 내일 사라질지도 모르는 제품에 오버 엔지니어링을 한 게 아닐까 싶기도하다.</p>
<ul>
<li>아마 웹 개발자 팀원이 한 명이라도 더 있었다면, 왜 이런 구조를 택했냐고 했을 것 같다.</li>
</ul>
<p>하지만 마냥 근거 없는 선택은 아니었다. 판단 기준은 아래와 같았다.</p>
<ul>
<li>Supabase는 MVP에 쓰일 백엔드이다.</li>
<li>추상화로 인한 복잡성 비용 대비 이점이 있는가?</li>
<li>구독 결제와 같은 비즈니스 로직이 Supabase만으로는 힘들었다.<!-- -->
<ul>
<li>내가 백엔드 스킬이 부족해서 그럴 수도 있다.</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="외부-의존성-제어를-위한-고민">외부 의존성 제어를 위한 고민<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#%EC%99%B8%EB%B6%80-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A0%9C%EC%96%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B3%A0%EB%AF%BC" class="hash-link" aria-label="외부 의존성 제어를 위한 고민에 대한 직접 링크" title="외부 의존성 제어를 위한 고민에 대한 직접 링크">​</a></h2>
<p>개발 속도를 유지하면서도 시스템의 유연성을 확보하는 것은 쉽지 않았다. 나의 접근 방식은 <strong>필요할 때 확장 가능한 최소한의 추상화</strong>였다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="문제-httpclient-인터페이스와-supbase-sdk의-간극">문제: HTTPClient 인터페이스와 Supbase SDK의 간극<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#%EB%AC%B8%EC%A0%9C-httpclient-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EC%99%80-supbase-sdk%EC%9D%98-%EA%B0%84%EA%B7%B9" class="hash-link" aria-label="문제: HTTPClient 인터페이스와 Supbase SDK의 간극에 대한 직접 링크" title="문제: HTTPClient 인터페이스와 Supbase SDK의 간극에 대한 직접 링크">​</a></h3>
<p>Supabase SDK는 추상화된 <code>HTTPClient</code> 인터페이스와 완전히 호환되지 않았다. 그렇다고 SDK의 메서드들을 <code>HTTPClient</code> 인터페이스를 맞추는 것은 멍청한 행동이라고 생각했다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 이상적인 HTTPClient 인터페이스</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">HTTPClient</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">get</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Data </span><span class="token generic-function generic class-name operator">=</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">url</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> params</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> Record</span><span class="token operator">&lt;</span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">any</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">Data</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">post</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Data </span><span class="token generic-function generic class-name operator">=</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">url</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">unknown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">Data</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Supabase SDK를 통한 read</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">supabase</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'table'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">select</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'*'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">order</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'created_at'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> ascending</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Supabase SDK를 통한 create</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">supabase</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'table'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">insert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">select</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'*'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">single</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그렇다고 엄청난 생산성의 1등 공신 중 하나인 Supabase SDK를 쓰지 않겠다는 것은... 굳이 말을 이어나가지 않아도 될 것 같다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="bff-서버로-두-마리-토끼-잡기">BFF 서버로 두 마리 토끼 잡기<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#bff-%EC%84%9C%EB%B2%84%EB%A1%9C-%EB%91%90-%EB%A7%88%EB%A6%AC-%ED%86%A0%EB%81%BC-%EC%9E%A1%EA%B8%B0" class="hash-link" aria-label="BFF 서버로 두 마리 토끼 잡기에 대한 직접 링크" title="BFF 서버로 두 마리 토끼 잡기에 대한 직접 링크">​</a></h3>
<p>나의 경우, 제품의 코어 기술 스택이 Next.js이었기 때문에 가능한 선택이었다.</p>
<ul>
<li>바쁜 일정 속, 별도의 BFF 서버를 띄워야했다면, Supabase 의존성 제어를 고려하지도 않았을 것이다.</li>
</ul>
<p>Next.js API Route handler에서는 Supabase SDK를 이용하고, API 클라이언트 쪽에서는 <code>HTTPClient</code> 추상화에 의존하게 했다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// /app/api/projects/[projectId]route.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">GET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  request</span><span class="token operator">:</span><span class="token plain"> NextRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> projectId </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> projectId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">NextResponse</span><span class="token operator">&lt;</span><span class="token plain">FetchProjectResponseBody</span><span class="token operator">&gt;&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token operator">:</span><span class="token plain"> project</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    error</span><span class="token operator">:</span><span class="token plain"> fetchProjectError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    status</span><span class="token operator">:</span><span class="token plain"> fetchProjectStatus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> supabase</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'projects'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">select</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'*'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> count</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'exact'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">eq</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'id'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> projectId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">single</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> NextResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    code</span><span class="token operator">:</span><span class="token plain"> fetchProjectStatus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    message</span><span class="token operator">:</span><span class="token plain"> StatusMessages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">OK</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    result</span><span class="token operator">:</span><span class="token plain"> project</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// /services/project-service.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ProjectService</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">private</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">readonly</span><span class="token plain"> httpClient</span><span class="token operator">:</span><span class="token plain"> HTTPClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">constructor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">httpClient</span><span class="token operator">:</span><span class="token plain"> HTTPClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">httpClient </span><span class="token operator">=</span><span class="token plain"> httpClient</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">fetchProject</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">projectId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token generic-function function" style="color:rgb(80, 250, 123)">get</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Project</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">/api/projects/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">projectId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>하지만, 이대로는 좀 아쉬운 점이 있다고 느꼈다. <code>ProjectService</code>와 같은 API 클라이언트가 의존하는 서버가 BFF 서버에서 다른 API 서버로 변경될 때이다.</p>
<p>아래 코드를 보면 알겠지만, 엔드포인트가 <code>/api/projects/${projectId}</code>와 같이 직접 의존하고 있기 때문이다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">fetchProject</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">projectId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token generic-function function" style="color:rgb(80, 250, 123)">get</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Project</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">/api/projects/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">projectId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그래서 의존할 서버의 교체를 고려하여 엔드포인트 Record에 의존하게 했다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">API_SERVER_PATHS</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  projects</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/api/projects'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">fetchProject</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">projectId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">this</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token generic-function function" style="color:rgb(80, 250, 123)">get</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Project</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation constant" style="color:rgb(189, 147, 249)">API_SERVER_PATHS</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation">projects</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">projectId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이제 <code>ProjectService</code>와 같은 API 클라이언트들은 <strong>Supabase SDK에 의존하지 않는다.</strong> 나중에 BFF 마저도 사라지게 된다면, 엔드포인트들만 교체해주면 된다.</p>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="정말로-찾아온-자체-api-서버로의-전환">정말로 찾아온 자체 API 서버로의 전환<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#%EC%A0%95%EB%A7%90%EB%A1%9C-%EC%B0%BE%EC%95%84%EC%98%A8-%EC%9E%90%EC%B2%B4-api-%EC%84%9C%EB%B2%84%EB%A1%9C%EC%9D%98-%EC%A0%84%ED%99%98" class="hash-link" aria-label="정말로 찾아온 자체 API 서버로의 전환에 대한 직접 링크" title="정말로 찾아온 자체 API 서버로의 전환에 대한 직접 링크">​</a></h4>
<p>서비스의 성장과 함께 Supabase의 한계를 더욱 명확히 느끼게 되었다.
주요 전환 동기는 다음과 같았다.</p>
<ul>
<li>복잡해지는 비즈니스 로직</li>
<li>풀스택 앱이 갖는 복잡성</li>
<li>전문 백엔드 인력 채용</li>
</ul>
<p>그리고 앞서 구축한 추상화 계층 덕분에 전환 과정이 용이했다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/migrate-from-supabase-to-self-hosted-api#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>제어의 역전 달성과 최소한의 추상화는 우리 개발팀에 유연성을 제공했다.</p>
<p>최근 제품과 개발팀이 커지면서, 이런 경험들이 많아지고 있는데 혼자 학습하고 고민했던 것들이 헛되지 않았다는 게 느껴지기도 하고, 개발에 대한 성취감도 상당히 높아졌다.</p>
<p>이전에는 기술이나 코드로 문제를 해결하는 것보다는 그 외 방법으로 문제를 해결하는 게 더 재밌었는데, 지금은 개발이 역전한 것 같다.</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="ioc" term="ioc"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[서로 다른 인터페이스를 하나의 인터페이스로, 어댑터 패턴 실전 적용기]]></title>
        <id>https://m1nsuppp.vercel.app/adapter-pattern-in-react</id>
        <link href="https://m1nsuppp.vercel.app/adapter-pattern-in-react"/>
        <updated>2024-11-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[결제 수단 변경 기능을 구현하며 마주쳤던 컴포넌트 인터페이스 문제를 어댑터 패턴으로 해결한 경험]]></summary>
        <content type="html"><![CDATA[<p>결제 수단 변경 기능을 구현하며 마주쳤던 컴포넌트 인터페이스 문제를 어댑터 패턴으로 해결한 경험</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="배경">배경<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%EB%B0%B0%EA%B2%BD" class="hash-link" aria-label="배��경에 대한 직접 링크" title="배경에 대한 직접 링크">​</a></h2>
<p>사용자의 결제 경험을 개선하기 위한 가장 흔한 해결 방안 중 하나는 다양한 결제 수단을 제공하는 것이다. 그래서 우리 팀은 지속적으로 결제 수단을 늘려왔다.</p>
<p>그런데 며칠 전, 사용자로부터 결제 수단을 변경할 수 있게 해달라는 요청이 들어왔다. 이에 따라 우리 팀은 결제 수단 변경 기능을 개발하기로 했다.</p>
<p>API 서버에서는 기존 구독 API를 확장하여 결제 수단 변경 기능을 추가했다.</p>
<ul>
<li>같은 구독 플랜에 대해 결제 수단만 바꾸어 API 서버에 요청하는 것이었다.</li>
</ul>
<p>그리고 프론트엔드도 이에 맞추어 결제 수단 컴포넌트에 대한 핸들러를 수정하면 손 쉽게 해결할 수 있을 것이라고 생각했다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="문제">문제<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%EB%AC%B8%EC%A0%9C" class="hash-link" aria-label="문제에 대한 직접 링크" title="문제에 대한 직접 링크">​</a></h2>
<p>하지만, 언제나 그렇듯 가볍게 생각한 문제가 마냥 가볍지만은 않았다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 결제 페이지</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">PaymentPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token spread operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Layout</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">FirstEasyPaymentMethod</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">planType</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">planType</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">SecondEasyPaymentMethod</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">planType</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">planType</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">hasRegisteredCreditCard</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">RegisteredCreditCard</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">              </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">planType</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">planType</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">              </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onEditCreditCardButtonClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleEditCreditCardButtonClick</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">              </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">              </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handlePaymentFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">            </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">RegisterNewCreditCard</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">            </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onRegisterCreditCardSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleRegisterCreditCardSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">            </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onRegisterCreditCardFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleRegisterCreditCardFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Layout</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">  );</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="문제-1-서로-다른-인터페이스를-가진-결제-컴포넌트들">문제 1: 서로 다른 인터페이스를 가진 결제 컴포넌트들<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%EB%AC%B8%EC%A0%9C-1-%EC%84%9C%EB%A1%9C-%EB%8B%A4%EB%A5%B8-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EA%B0%80%EC%A7%84-%EA%B2%B0%EC%A0%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%93%A4" class="hash-link" aria-label="문제 1: 서로 다른 인터페이스를 가진 결제 컴포넌트들에 대한 직접 링크" title="문제 1: 서로 다른 인터페이스를 가진 결제 컴포넌트들에 대한 직접 링크">​</a></h3>
<p>각각의 결제 수단 컴포넌트들은 자신만의 인터페이스를 가지고 있었다.</p>
<p>이는 각 결제 수단의 특성과 개발 시점이 달랐기 때문이다. 어쩔 수 없다. 우리가 만든 레거시기 때문에 안고가야했다.</p>
<p>이렇게 서로 다른 인터페이스는 요구사항 변경에 대한 문제를 야기했다.</p>
<ul>
<li>컴포넌트마다 다른 props 네이밍으로 인한 일관성 부족</li>
<li>결제 성공/실패 핸들링 로직 중복</li>
<li>새로운 결제 수단 추가 시, 매번 다른 인터페이스 고려</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="문제-2-결제라는-행동에-종속된-인터페이스">문제 2: "결제"라는 행동에 종속된 인터페이스<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%EB%AC%B8%EC%A0%9C-2-%EA%B2%B0%EC%A0%9C%EB%9D%BC%EB%8A%94-%ED%96%89%EB%8F%99%EC%97%90-%EC%A2%85%EC%86%8D%EB%90%9C-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4" class="hash-link" aria-label="문제 2: &quot;결제&quot;라는 행동에 종속된 인터페이스에 대한 직접 링크" title="문제 2: &quot;결제&quot;라는 행동에 종속된 인터페이스에 대한 직접 링크">​</a></h3>
<p>기존 인터페이스는 "결제"라는 특정 행위에 종속되어 있었다.</p>
<p>하지만 이제는 "결제 수단 변경"이라는 새로운 유스케이스가 추가됐다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 결제 수단 변경 페이지</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">결제_수단_변경_페이지</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 🤔 기존 컴포넌트를 어떻게 재사용할까요?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Layout</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">FirstEasyPaymentMethod</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag comment" style="color:rgb(98, 114, 164)">// 💭 onPaymentSuccess라는 이름이 적절하지 않음</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleChangePaymentMethodSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onPaymentFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleChangePaymentMethodFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Layout</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="해결-방안-어댑터-패턴-적용">해결 방안: 어댑터 패턴 적용<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88-%EC%96%B4%EB%8C%91%ED%84%B0-%ED%8C%A8%ED%84%B4-%EC%A0%81%EC%9A%A9" class="hash-link" aria-label="해결 방안: 어댑터 패턴 적용에 대한 직접 링크" title="해결 방안: 어댑터 패턴 적용에 대한 직접 링크">​</a></h3>
<p>이러한 문제들을 어떻게 해결할 수 있을지 고민했고, 어댑터 패턴을 적용하기로 했다.</p>
<p>먼저 "결제 수단 변경"을 책임지는 컴포넌트들이 공통적으로 사용할 수 있는 통일된 인터페이스를 정의했다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">결제_수단_변경_Props</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">onSuccess</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">successResponse</span><span class="token operator">:</span><span class="token plain"> SuccessResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">onFail</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">failResponse</span><span class="token operator">:</span><span class="token plain"> FailResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>그 후, 각 결제 수단에 대해서 "변경"을 수행하는 컴포넌트들을 분리했다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">A</span><span class="token function" style="color:rgb(80, 250, 123)">로_결제_수단_변경</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">props</span><span class="token operator">:</span><span class="token plain"> 결제_수단_변경_Props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token spread operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">B</span><span class="token function" style="color:rgb(80, 250, 123)">로_결제_수단_변경</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">props</span><span class="token operator">:</span><span class="token plain"> 결제_수단_변경_Props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token spread operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">신용카드로_결제_수단_변경</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">props</span><span class="token operator">:</span><span class="token plain"> 결제_수단_변경_Props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    isEditCreditCardButtonClicked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    setIsEditCreditCardButtonClicked</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token spread operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">isEditCreditCardButtonClicked </span><span class="token operator">&amp;&amp;</span><span class="token plain"> hasRegisteredCreditCard</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">RegisteredCreditCard</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">        </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onEditButtonClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">setIsEditCreditCardButtonClicked</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript boolean" style="color:rgb(255, 121, 198)">true</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">RegisterNewCreditCard</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onSuccess</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">props</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">onSuccess</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">      </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onFail</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">props</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">onFail</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이렇게 "결제 수단 변경"을 책임지는 컴포넌트들을 따로 분리하고, 다음과 같이 페이지를 완성시켰다.</p>
<div class="language-tsx codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-tsx codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">결제_수단_변경_페이지</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> props</span><span class="token operator">:</span><span class="token plain"> 결제_수단_변경_Props </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    onSuccess</span><span class="token operator">:</span><span class="token plain"> handleSuccess</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    onFail</span><span class="token operator">:</span><span class="token plain"> handleFail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    planType</span><span class="token operator">:</span><span class="token plain"> currentUserPlanType</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token constant" style="color:rgb(189, 147, 249)">A</span><span class="token plain">로_결제_수단_변경</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token spread operator">...</span><span class="token plain">props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token comment" style="color:rgb(98, 114, 164)">/* ... */</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">신용카드로_결제_수단_변경</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token spread operator">...</span><span class="token plain">props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="회고-및-교훈">회고 및 교훈<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#%ED%9A%8C%EA%B3%A0-%EB%B0%8F-%EA%B5%90%ED%9B%88" class="hash-link" aria-label="회고 및 교훈에 대한 직접 링크" title="회고 및 교훈에 대한 직접 링크">​</a></h2>
<p>어댑터 패턴을 적용하면서 몇 가지 중요한 교훈을 얻을 수 있었던 것 같다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="1-인터페이스-설계의-중요성">1. 인터페이스 설계의 중요성<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#1-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%84%A4%EA%B3%84%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1" class="hash-link" aria-label="1. 인터페이스 설계의 중요성에 대한 직접 링크" title="1. 인터페이스 설계의 중요성에 대한 직접 링크">​</a></h3>
<ul>
<li>특정 행위에 종속적인 네이밍은 컴포넌트의 재사용성을 떨어뜨림</li>
<li>더 일반적이고 유연한 인터페이스 설계가 필요</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="2-점진적-리팩토링">2. 점진적 리팩토링<a href="https://m1nsuppp.vercel.app/adapter-pattern-in-react#2-%EC%A0%90%EC%A7%84%EC%A0%81-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81" class="hash-link" aria-label="2. 점진적 리팩토링에 대한 직접 링크" title="2. 점진적 리팩토링에 대한 직접 링크">​</a></h3>
<ul>
<li>한 번에 모든 컴포넌트를 수정하지 않고, 어댑터를 통해 점진적으로 개선</li>
<li>기존 코드의 동작을 유지하면서 안전하게 리팩토링 가능</li>
</ul>
<hr>
<p>이러한 경험을 통해, 어댑터 패턴이 단순히 이론적인 디자인 패턴이 아니라 실제 개발 현장에서 매우 유용한 도구라는 것을 깨달았다.
나와 비슷한 문제를 겪고 있다면, 어댑터 패턴의 적용을 고려해보는 것도 좋을 것 같다.</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="adapter pattern" term="adapter pattern"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[DIP 준수로 변화에 유연하게 대응하기]]></title>
        <id>https://m1nsuppp.vercel.app/dip</id>
        <link href="https://m1nsuppp.vercel.app/dip"/>
        <updated>2024-11-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[DIP 원칙을 통해 요구사항 변화에 유연하게 대응한 경험, feat: TanStack Query]]></summary>
        <content type="html"><![CDATA[<p>DIP 원칙을 통해 요구사항 변화에 유연하게 대응한 경험, feat: TanStack Query</p>
<div class="theme-admonition theme-admonition-note admonition_eWwA alert alert--secondary"><div class="admonitionHeading_Yqs_"><span class="admonitionIcon_641s"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>노트</div><div class="admonitionContent_KZHd"><p>실제 경험은 TanStack Query를 이용하지만, 이 글에서는 각색하여 다룬다.</p></div></div>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="배경">배경<a href="https://m1nsuppp.vercel.app/dip#%EB%B0%B0%EA%B2%BD" class="hash-link" aria-label="배경에 대한 직접 링크" title="배경에 대한 직접 링크">​</a></h2>
<p>최근 UI는 유지한 채, 페이지네이션 API를 교체해야할 일이 있었다.</p>
<p>이전 API는 데이터를 생성일순으로 정렬하여 페이지네이션 했었지만, 교체할 API는 개인화 추천 시스템이 적용된 데이터를 제공하였다. 이로 인해 두 API의 스펙이 상이했다.</p>
<p>그래서 FE에서는 관련 로직을 변경해야 했지만, UI는 그대로 유지해야했다.</p>
<p>이런 문제 속에서 DIP 원칙을 적용하여 어떻게 변화에 대응 했는지에 대한 경험을 이야기한다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="변경-이전의-컴포넌트">변경 이전의 컴포넌트<a href="https://m1nsuppp.vercel.app/dip#%EB%B3%80%EA%B2%BD-%EC%9D%B4%EC%A0%84%EC%9D%98-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8" class="hash-link" aria-label="변경 이전의 컴포넌트에 대한 직접 링크" title="변경 이전의 컴포넌트에 대한 직접 링크">​</a></h2>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">SomethingComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hasNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> isFetchingNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetchNextPage </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">InfiniteScroll</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      onEndReached</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">fetchNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">Layout</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">somethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">SomethingCard key</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">...</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">Layout</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">InfiniteScroll</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이 컴포넌트는 데이터를 무한 스크롤 방식으로 불러오는 컴포넌트이다. 그리고 이 컴포넌트는 <code>useFetchSomethings</code>라는 커스텀 훅에 직접 의존하고 있다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="컴포넌트에게-추상화-제공하기">컴포넌트에게 추상화 제공하기<a href="https://m1nsuppp.vercel.app/dip#%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EA%B2%8C-%EC%B6%94%EC%83%81%ED%99%94-%EC%A0%9C%EA%B3%B5%ED%95%98%EA%B8%B0" class="hash-link" aria-label="컴포넌트에게 추상화 제공하기에 대한 직접 링크" title="컴포넌트에게 추상화 제공하기에 대한 직접 링크">​</a></h2>
<p>그리고 이제 나는 위에서 언급한 것처럼 사용자에게 개인화된 데이터를 제공하기 위해 <code>useFetchSomethings</code>를 <code>useFetchPersonalizedSomethings</code>로 교체해야 했다.</p>
<p>두 hook의 내부 구현은 완전히 다를 것이다.</p>
<p>여기서 주목할 점은 <code>&lt;SomethingComponent&gt;</code>는 hook의 내부 구현에는 전혀 관심이 없다는 것이다. 컴포넌트는 오직 아래와 같은 인터페이스만 알면 된다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">FetchSomethings</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  data</span><span class="token operator">:</span><span class="token plain"> Something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  hasNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  isFetchingNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fetchNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> FetchSomethings </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchPersonalizedSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> FetchSomethings </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>즉, <code>&lt;SomethingComponent&gt;</code>가 <code>FetchSomethings</code>라는 추상화에 의존하는 결과를 반환하는 hook에 의존하게 하면, <code>useFetchSomethings</code>를 <code>useFetchPersonalizedSomethings</code>로 부품 교체 하듯이 교체하면 된다는 것이다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="실제-예시-구현체-전환하기">실제 예시: 구현체 전환하기<a href="https://m1nsuppp.vercel.app/dip#%EC%8B%A4%EC%A0%9C-%EC%98%88%EC%8B%9C-%EA%B5%AC%ED%98%84%EC%B2%B4-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0" class="hash-link" aria-label="실제 예시: 구현체 전환하기에 대한 직접 링크" title="실제 예시: 구현체 전환하기에 대한 직접 링크">​</a></h3>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">FetchSomethings</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  data</span><span class="token operator">:</span><span class="token plain"> Something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  hasNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  isFetchingNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fetchNextPage</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">void</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 조회순 API 구현</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> FetchSomethings </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Something</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fetchNextPage</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    somethingService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">fetchSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">then</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> hasNextPage </span><span class="token operator">=</span><span class="token plain"> page </span><span class="token operator">&lt;</span><span class="token plain"> totalPages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    hasNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    isFetchingNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    fetchNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 개인화된 API 구현</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchPersonalizedSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> FetchSomethings </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:rgb(80, 250, 123)">useState</span><span class="token generic-function generic class-name operator">&lt;</span><span class="token generic-function generic class-name">Something</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token generic-function generic class-name punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token generic-function generic class-name operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fetchNextPage</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    somethingService</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">fetchPersonalizedSeeds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">nextEndpoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">then</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> nextEndpoint</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      continuationToken</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">continuationToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">/api/somethings?continuationToken=</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">continuationToken</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">continuationToken</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> hasNextPage </span><span class="token operator">=</span><span class="token plain"> </span><span class="token operator">!</span><span class="token operator">!</span><span class="token plain">nextEndpoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    hasNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    isFetchingNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    fetchNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>위 두 구현체는 내부적으로 완전히 다른 방식으로 동작하지만, 동일한 인터페이스를 제공한다. 덕분에 컴포넌트는 어떤 구현체를 사용하든 변경할 필요가 없이, 아래와 같이 직접 의존할 hook만 교체해주면 된다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">SomethingComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useFetchSomethings();</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hasNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> isFetchingNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetchNextPage </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useFetchPersonalizedSomethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">InfiniteScroll</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      onEndReached</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">fetchNextPage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">Layout</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">somethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">SomethingCard key</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token operator">...</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">Layout</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">InfiniteScroll</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="추상화에-의존하는-것이-왜-좋은가">추상화에 의존하는 것이 왜 좋은가?<a href="https://m1nsuppp.vercel.app/dip#%EC%B6%94%EC%83%81%ED%99%94%EC%97%90-%EC%9D%98%EC%A1%B4%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B4-%EC%99%9C-%EC%A2%8B%EC%9D%80%EA%B0%80" class="hash-link" aria-label="추상화에 의존하는 것이 왜 좋은가?에 대한 직접 링크" title="추상화에 의존하는 것이 왜 좋은가?에 대한 직접 링크">​</a></h2>
<ol>
<li>유연성</li>
</ol>
<ul>
<li>API의 스펙이나 페이지네이션하는 방법이 변경되더라도 컴포넌트 코드는 변경할 필요가 없다. 동일한 인터페이스만 제공하면 된다.</li>
</ul>
<ol start="2">
<li>유지보수성</li>
</ol>
<ul>
<li>구현체 변경이 필요할 때 영향 범위가 최소화된다.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="solid-원칙과의-연관성">SOLID 원칙과의 연관성<a href="https://m1nsuppp.vercel.app/dip#solid-%EC%9B%90%EC%B9%99%EA%B3%BC%EC%9D%98-%EC%97%B0%EA%B4%80%EC%84%B1" class="hash-link" aria-label="SOLID 원칙과의 연관성에 대한 직접 링크" title="SOLID 원칙과의 연관성에 대한 직접 링크">​</a></h2>
<p>나의 이러한 경험은 SOLID 원칙 중 두 가지를 잘 보여준다고 생각했다.</p>
<ol>
<li><strong>의존관계 역전 원칙 (DIP)</strong></li>
</ol>
<ul>
<li>구체적인 구현이 아닌 추상화(인터페이스)에 의존한다.</li>
</ul>
<ol start="2">
<li><strong>인터페이스 분리 원칙 (ISP)</strong></li>
</ol>
<ul>
<li><code>&lt;SomethingComponent&gt;</code>는 정확히 필요한 인터페이스만 알고 있다.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/dip#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>추상화에 의존하는 것은 단순히 "좋은 프로그래밍 습관" 이상의 의미가 있다고 느낀다.</p>
<p>향후에도 커스텀 훅이나 컴포넌트를 설계할 때, "이 코드가 구체적인 구현에 의존하고 있지는 않은가?"라고 자문하는 습관을 들일 것이다.</p>
<p>하지만, 아직 나는 API 사용자에게 잘못된 추상화를 제공할 가능성이 너무나도 크다고 생각이 든다.</p>
<p>이번에는 좋은 사례로 남았지만, 안좋았던 사례가 더 많은 것 같다.</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="dip" term="dip"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[UI 상태 안전하게 관리하기]]></title>
        <id>https://m1nsuppp.vercel.app/discriminated-union</id>
        <link href="https://m1nsuppp.vercel.app/discriminated-union"/>
        <updated>2024-09-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[TypeScript의 Discriminated Union을 활용해서 UI 상태 안전하게 관리하기]]></summary>
        <content type="html"><![CDATA[<p>TypeScript의 Discriminated Union을 활용해서 UI 상태 안전하게 관리하기</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="배경">배경<a href="https://m1nsuppp.vercel.app/discriminated-union#%EB%B0%B0%EA%B2%BD" class="hash-link" aria-label="배경에 대한 직접 링크" title="배경에 대한 직접 링크">​</a></h2>
<p>최근 팀원분이 사용자의 연락처로 정보를 전달해야하는 기능을 구현하셨고, 그것을 리뷰해야했다.</p>
<p>그리고 팀원분께서 개발하신 UI 요구사항은 다음과 같았다.</p>
<p><img decoding="async" loading="lazy" alt="email-only" src="data:image/png;base64,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" width="250" height="149" class="img_AvkH"></p>
<p><img decoding="async" loading="lazy" alt="with-kakaotalk" src="https://m1nsuppp.vercel.app/assets/images/with-kakaotalk-c1fd0fd92a0770f212a84bcc5594c320.png" width="347" height="257" class="img_AvkH"></p>
<ul>
<li>이메일로 전달받기</li>
<li>이메일 + 카카오톡으로 전달 받기<!-- -->
<ul>
<li><code>&lt;input /&gt;</code>을 통해 전화번호를 입력 받는다.</li>
<li><code>&lt;input /&gt;</code>을 통해 약관 동의 여부를 입력 받는다.</li>
</ul>
</li>
</ul>
<p>개발자마다 다르겠지만, 이 요구사항을 만족하기 위한 type 또는 interface를 작성해달라고 <strong>ChatGPT와 Claude에게 질문했고, 다음과 같이 알려주었다.</strong>
그리고 팀원분 역시 이와 비슷한 구조를 선택하셨다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 폼 데이터 인터페이스 정의</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">Form</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  phoneNumber</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  agreeToTerms</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 폼 제출 방식 인터페이스 정의</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">FormSubmission</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  method</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"email"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"emailAndKakao"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  data</span><span class="token operator">?</span><span class="token operator">:</span><span class="token plain"> Form</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>음... 이 interface를 통해서도 충분히 구현이 가능하기는 하다. 하지만 내 생각에는 실수하기 쉬운 구조의 interface로 보인다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="발생할-수-있는-문제">발생할 수 있는 문제<a href="https://m1nsuppp.vercel.app/discriminated-union#%EB%B0%9C%EC%83%9D%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%AC%B8%EC%A0%9C" class="hash-link" aria-label="발생할 수 있는 문제에 대한 직접 링크" title="발생할 수 있는 문제에 대한 직접 링크">​</a></h2>
<p>위 interface는 왜 실수하기 쉬운 구조인지 생각해보자.</p>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="문제-1-타입-안정성-부족">문제 1. 타입 안정성 부족:<a href="https://m1nsuppp.vercel.app/discriminated-union#%EB%AC%B8%EC%A0%9C-1-%ED%83%80%EC%9E%85-%EC%95%88%EC%A0%95%EC%84%B1-%EB%B6%80%EC%A1%B1" class="hash-link" aria-label="문제 1. 타입 안정성 부족:에 대한 직접 링크" title="문제 1. 타입 안정성 부족:에 대한 직접 링크">​</a></h4>
<p><code>FormSubmission</code> interface는 <code>data</code>필드가 optional로 정의되어 있기 때문에 <code>method</code>가 <code>emailAndKakao</code>일 때 <code>data</code> 필드가 반드시 존재해야 한다는 것을 타입 시스템이 보장하지 못한다.</p>
<ul>
<li>요구사항에 따르면, <code>emailAndKakao</code>인 경우 사용자는 반드시 전화번호와 약관동의를 체크해야한다. 하지만 위 interface는 요구사항과 다르게 <code>method</code>가 <code>emailAndKakao</code>일 때, <code>phoneNumber</code>와 <code>agreeToTerms</code> 값을 비울 수 있다.</li>
</ul>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">setFormSubmission</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  method</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"emailAndKakao"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 요구사항과 다르게 data 필드를 비웠음에도 오류가 발생하지 않는다.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<blockquote>
<p><strong><em>실수를 안하면 되는 거 아닌가요?</em></strong> -&gt; 실수를 안하려고 노력하는 것보다는 "실수를 할 수 없게" 하는 것이 더 낫다고 생각한다.</p>
</blockquote>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="문제-2-불필요한-코드-추가">문제 2. 불필요한 코드 추가:<a href="https://m1nsuppp.vercel.app/discriminated-union#%EB%AC%B8%EC%A0%9C-2-%EB%B6%88%ED%95%84%EC%9A%94%ED%95%9C-%EC%BD%94%EB%93%9C-%EC%B6%94%EA%B0%80" class="hash-link" aria-label="문제 2. 불필요한 코드 추가:에 대한 직접 링크" title="문제 2. 불필요한 코드 추가:에 대한 직접 링크">​</a></h4>
<p>타입 시스템만으로는 충분하지 않기 때문에, 런타임 체크에 대한 코드나 유효하지 않은 상태들을 관리하기 위한 코드들이 추가된다.</p>
<p>이는 코드를 복잡하게 만들고, 개발자가 실수할 가능성을 높인다.</p>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="문제-3-가독성과-유지보수성-저하">문제 3. 가독성과 유지보수성 저하<a href="https://m1nsuppp.vercel.app/discriminated-union#%EB%AC%B8%EC%A0%9C-3-%EA%B0%80%EB%8F%85%EC%84%B1%EA%B3%BC-%EC%9C%A0%EC%A7%80%EB%B3%B4%EC%88%98%EC%84%B1-%EC%A0%80%ED%95%98" class="hash-link" aria-label="문제 3. 가독성과 유지보수성 저하에 대한 직접 링크" title="문제 3. 가독성과 유지보수성 저하에 대한 직접 링크">​</a></h4>
<p><code>method</code>가 하나 더 추가 된다면?.. 아마도 조건부 렌더링과 상태 관리가 더욱 복잡해지면서 코드의 가독성과 유지보수성이 저하된다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="개선안-discriminated-union을-활용하여-타입-안전하게-상태-관리하기">개선안: Discriminated Union을 활용하여 타입 안전하게 상태 관리하기<a href="https://m1nsuppp.vercel.app/discriminated-union#%EA%B0%9C%EC%84%A0%EC%95%88-discriminated-union%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-%ED%83%80%EC%9E%85-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0" class="hash-link" aria-label="개선안: Discriminated Union을 활용하여 타입 안전하게 상태 관리하기에 대한 직접 링크" title="개선안: Discriminated Union을 활용하여 타입 안전하게 상태 관리하기에 대한 직접 링크">​</a></h2>
<p>나는 리뷰이에게 다음과 같은 interface는 어떤지 조심스레 제안해보았다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">type</span><span class="token plain"> </span><span class="token class-name">NotificationConfig</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"emailOnly"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"emailAndKakao"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      phoneNumber</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      consent</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">boolean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>왜 나는 이 interface가 더 낫다고 생각했으며, 왜 리뷰이에게 변경을 제안했을까?</p>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="1-타입-안정성">1. 타입 안정성:<a href="https://m1nsuppp.vercel.app/discriminated-union#1-%ED%83%80%EC%9E%85-%EC%95%88%EC%A0%95%EC%84%B1" class="hash-link" aria-label="1. 타입 안정성:에 대한 직접 링크" title="1. 타입 안정성:에 대한 직접 링크">​</a></h4>
<p><code>NotificationConfig</code>의 타입에 따라 필요한 필드가 자동으로 체크된다. 예를 들어, <code>type</code>이 <code>emailAndKakao</code>일 때는 <code>phoneNumber</code>와 <code>consent(agreeToTerms)</code>가 반드시 존재해야 한다고 타입 시스템이 알려준다.</p>
<p><img decoding="async" loading="lazy" alt="type-error" src="https://m1nsuppp.vercel.app/assets/images/type-error-cbf489f94fe7453ff294f8d813ae350a.png" width="1242" height="210" class="img_AvkH"></p>
<h4 class="anchor anchorWithStickyNavbar_N2AM" id="2유지보수성-개선">2.유지보수성 개선<a href="https://m1nsuppp.vercel.app/discriminated-union#2%EC%9C%A0%EC%A7%80%EB%B3%B4%EC%88%98%EC%84%B1-%EA%B0%9C%EC%84%A0" class="hash-link" aria-label="2.유지보수성 개선에 대한 직접 링크" title="2.유지보수성 개선에 대한 직접 링크">​</a></h4>
<p>새로운 상태나 필드를 추가하기 쉬워지며, 타입 시스템이 컴파일 시점에 에러를 잡아내므로, 런타임 에러의 가능성이 줄어든다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/discriminated-union#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>Discriminated Union을 활용한 타입 설계는 UI 상태를 더 안전하고 효율적으로 관리할 수 있게 해준다.</p>
<ul>
<li>타입 안전성 향상<!-- -->
<ul>
<li>컴파일 시점에서 많은 오류를 잡아낼 수 있어, 런타임 오류의 가능성을 크게 줄일 수 있다.</li>
</ul>
</li>
<li>코드 간결성<!-- -->
<ul>
<li>불필요한 런타임 체크를 줄일 수 있어 코드가 더 간결해진다.</li>
<li>각 상태가 명확히 구분되어 있어, 코드의 의도를 더 쉽게 파악할 수 있다.</li>
</ul>
</li>
<li>확장성<!-- -->
<ul>
<li>새로운 상태나 필드를 추가하기 쉬워, 요구사항 변경에 유연하게 대응할 수 있다.</li>
</ul>
</li>
<li>자동완성 지원<!-- -->
<ul>
<li>IDE에서 각 상태에 맞는 필드를 자동으로 제안해주어 개발 생산성을 높일 수 있다.</li>
</ul>
</li>
</ul>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="discriminated union" term="discriminated union"/>
        <category label="typescript" term="typescript"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[비즈니스 로직과 햇감자 포카칩]]></title>
        <id>https://m1nsuppp.vercel.app/what-is-business-logic-in-fe</id>
        <link href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe"/>
        <updated>2024-08-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[포카칩으로 알아보는 비즈니스 로직]]></summary>
        <content type="html"><![CDATA[<p>포카칩으로 알아보는 비즈니스 로직</p>
<p>친구가 내게 물었다. <strong><em>"프론트엔드에서는 데이터를 요청하고 받고 조작하는 게 비즈니스 로직이라고 생각하는 건가?"</em></strong></p>
<p>이 질문은 비즈니스 로직에 대한 흔한 오해를 잘 보여준다고 생각이 들었다.</p>
<p>그래서 친구에게 어떻게 비즈니스 로직을 설명 해줄까 고민했다.</p>
<p>그러다 문득 어제 너무 맛있게 먹은 <strong>햇감자 포카칩</strong>이 생각나서 포스팅을 작성한다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="비즈니스-로직이란">비즈니스 로직이란<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EC%9D%B4%EB%9E%80" class="hash-link" aria-label="비즈니스 로직이란에 대한 직접 링크" title="비즈니스 로직이란에 대한 직접 링크">​</a></h2>
<blockquote>
<p>비즈니스 로직(Business logic)은 컴퓨터 프로그램에서 실세계의 규칙에 따라 데이터를 생성·표시·저장·변경하는 부분을 일컫는다. 이 용어는 특히 데이터베이스, 표시장치 등 프로그램의 다른 부분과 대조되는 개념으로 쓰인다. - <a href="https://ko.wikipedia.org/wiki/%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4_%EB%A1%9C%EC%A7%81" target="_blank" rel="noopener noreferrer">Wikipedia</a></p>
</blockquote>
<p>즉, 비즈니스 규칙 또는 가치를 실현하기 위한 로직이라고 할 수 있다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="포카칩으로-알아보는-비즈니스-로직">포카칩으로 알아보는 비즈니스 로직<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%ED%8F%AC%EC%B9%B4%EC%B9%A9%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81" class="hash-link" aria-label="포카칩으로 알아보는 비즈니스 로직에 대한 직접 링크" title="포카칩으로 알아보는 비즈니스 로직에 대한 직접 링크">​</a></h2>
<p>포카칩에는 재미있는 사실이 하나 있다.</p>
<p>포카칩을 생산 시기에 따라 <strong>국산 햇감자</strong>와 <strong>생감자</strong> 두 종으로 패키지를 구분해 출시하고 있다는 것이다.</p>
<p><img decoding="async" loading="lazy" alt="햇감자 포카칩과 생감자 포카칩" src="https://m1nsuppp.vercel.app/assets/images/pocachip-7131ea61eb1bb6a0271802c853bd3a2f.png" width="640" height="480" class="img_AvkH"></p>
<p>그리고 사람들은 두 제품의 맛을 비교하기도 한다.</p>
<p>두 제품의 차이를 가르는 건 원재료인 감자의 원산지이다.</p>
<blockquote>
<p>오리온에 따르면 국내에서 감자가 나는 매년 6월부터 11월까지는 국내산 감자로 제품을 생산해 <strong>국산 햇감자</strong>라는 문구와 함께 출시하는 반면,&nbsp;국내에서 감자가 나지 않는 12월~5월은 미국과 호주에서 수급한 감자를 사용해 제품을 만들며, 이 제품의 패키지에는 <strong>생감자</strong>가 표기된다고 한다. - <a href="https://news.sbs.co.kr/news/endPage.do?news_id=N1007321069" target="_blank" rel="noopener noreferrer">SBS News</a></p>
</blockquote>
<p>위 사실로부터 포카칩 제조사인 오리온에는 다음과 같은 비즈니스 규칙을 가지고 있다는 것을 알 수 있다.</p>
<div class="language-javascript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-javascript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">사용된_감자 </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"햇감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  포장지에_표시할_텍스트 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"햇감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  포장지에_표시할_텍스트 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"생감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이 간단한 조건문이 바로 비즈니스 로직의 한 예이다. 단순히 데이터를 표시하는 것이 아니라, 비즈니스 규칙에 따라 어떤 텍스트를 표시할지 결정하고 있기 때문이다.</p>
<ul>
<li>단순한 데이터 처리가 아닌 오리온의 패키징 정책을 구현하고 있으므로 비즈니스 로직이라고 할 수 있다.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="왜-비즈니스-로직과-ui-로직이-분리-되어야-한다고-할까">왜 비즈니스 로직과 UI 로직이 분리 되어야 한다고 할까?<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EC%99%9C-%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EA%B3%BC-ui-%EB%A1%9C%EC%A7%81%EC%9D%B4-%EB%B6%84%EB%A6%AC-%EB%90%98%EC%96%B4%EC%95%BC-%ED%95%9C%EB%8B%A4%EA%B3%A0-%ED%95%A0%EA%B9%8C" class="hash-link" aria-label="왜 비즈니스 로직과 UI 로직이 분리 되어야 한다고 할까?에 대한 직접 링크" title="왜 비즈니스 로직과 UI 로직이 분리 되어야 한다고 할까?에 대한 직접 링크">​</a></h2>
<p>대충 비즈니스 로직이 무엇인지는 알게 됐다. 그런데 대부분의 개발자들은 왜 비즈니스 로직과 UI 로직이 분리 되어야 한다고 주장 할까?</p>
<p>나는 그것을 SOLID 원칙의 관점에서 이야기 해볼 수 있을 것 같다.</p>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="단일-책임-원칙">단일 책임 원칙<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EB%8B%A8%EC%9D%BC-%EC%B1%85%EC%9E%84-%EC%9B%90%EC%B9%99" class="hash-link" aria-label="단일 책임 원칙에 대한 직접 링크" title="단일 책임 원칙에 대한 직접 링크">​</a></h3>
<ul>
<li>비즈니스 로직과 UI 로직을 분리함으로써 각 모듈이 단 하나의 책임만을 가지게 된다.<!-- -->
<ul>
<li>비즈니스 로직은 비즈니스 규칙을 처리하는 책임만, UI 로직은 사용자 인터페이스를 표현하는 책임만 갖게 된다.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="개방-폐쇄-원칙">개방 폐쇄 원칙<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99" class="hash-link" aria-label="개방 폐쇄 원칙에 대한 직접 링크" title="개방 폐쇄 원칙에 대한 직접 링크">​</a></h3>
<ul>
<li>비즈니스 로직을 UI와 분리함으로써, 비즈니스 로직의 변경 없이도 새로운 UI를 추가하거나 기존 UI를 수정할 수 있다.<!-- -->
<ul>
<li>반대로 UI 변경 없이 비즈니스 로직을 확장하거나 수정할 수 있다.</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="의존관계-역전-원칙">의존관계 역전 원칙<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%97%AD%EC%A0%84-%EC%9B%90%EC%B9%99" class="hash-link" aria-label="의존관계 역전 원칙에 대한 직접 링크" title="의존관계 역전 원칙에 대한 직접 링크">​</a></h3>
<ul>
<li>고수준 모듈(비즈니스 로직)이 저수준 모듈(UI 로직)에 의존하지 않고, 둘 다 추상화(인터페이스 또는 Prop)에 의존하게 만들 수 있다.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_N2AM" id="실제-적용-예시">실제 적용 예시<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EC%8B%A4%EC%A0%9C-%EC%A0%81%EC%9A%A9-%EC%98%88%EC%8B%9C" class="hash-link" aria-label="실제 적용 예시에 대한 직접 링크" title="실제 적용 예시에 대한 직접 링크">​</a></h3>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 비즈니스 로직</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">포카칩_패키징_결정</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">사용된_감자</span><span class="token operator">:</span><span class="token plain"> 감자</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">사용된_감자 </span><span class="token operator">===</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"햇감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"100% 국산 햇감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"100% 생감자"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// UI 로직</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">interface</span><span class="token plain"> </span><span class="token class-name">포카칩_Props</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  패키지_텍스트</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">포카칩_어니언</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> 패키지_텍스트 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> 포카칩_Props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">포카칩</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">포장지 색상</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"초록"</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">span</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">패키지_텍스트</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">span</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">포카칩</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">포장지</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 사용</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> month </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getMonth</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> 사용된_감자 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">월별_감자_가져오기</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">month</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> 패키지_텍스트 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">포카칩_패키징_결정</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">사용된_감자</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">포카칩_어니언 패키지_텍스트</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">패키지_텍스트</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>이렇게 분리함으로써, 비즈니스 로직의 변경(예: 새로운 종류의 감자 추가)이 UI에 직접적인 영향을 주지 않으며, UI의 변경(예: 패키지 디자인 수정)도 비즈니스 로직에 영향을 주지 않는다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/what-is-business-logic-in-fe#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>비즈니스 로직은 단순한 데이터 처리나 표시를 넘어서는 개념이다.</p>
<p>비즈니스 로직은 기업의 정책, 규칙, 그리고 의사결정 과정을 코드로 구현한 것이다.</p>
<p>그리고 프론트엔드에서의 비즈니스 로직도 이와 다르지 않다.</p>
<p>단순히 API를 호출하고 데이터를 받아오는 것만으로는 비즈니스 로직이라고 할 수 없는 것이 그 이유이기도 하다.</p>
<p>그러나 그 데이터를 어떻게 해석하고, 어떤 조건에 따라 사용자에게 어떻게 보여줄지를 결정하는 과정에서 비즈니스 로직이 발생한다.</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="business logic" term="business logic"/>
        <category label="frontend" term="frontend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[내가 Next.js SSG를 버린 이유, 빌드 시간을 15분에서 2분으로]]></title>
        <id>https://m1nsuppp.vercel.app/why-i-stopped-using-ssg</id>
        <link href="https://m1nsuppp.vercel.app/why-i-stopped-using-ssg"/>
        <updated>2023-06-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Static Site Generation(SSG)에서 콘텐츠가 늘어날수록 빌드 시간은 기하급수적으로 증가합니다. 특히 상세 페이지의 개수가 늘어날수록 빌드 시간이 15분까지 늘어나면서 개발 경험에 심각한 악영향을 미쳤습니다.]]></summary>
        <content type="html"><![CDATA[<p>Static Site Generation(SSG)에서 콘텐츠가 늘어날수록 빌드 시간은 기하급수적으로 증가합니다. 특히 상세 페이지의 개수가 늘어날수록 빌드 시간이 15분까지 늘어나면서 개발 경험에 심각한 악영향을 미쳤습니다.</p>
<p>이러한 비효율적인 개발 워크플로우를 개선하기 위해 고심한 끝에 App Router가 제공하는 SSR + 데이터 캐싱 패턴을 활용하여 빌드 시간을 2분까지 줄인 경험을 이야기하고자 합니다.</p>
<p>Next.js pages router에서 제공하는 SSG는 정적 웹 페이지를 생성하는 데 매우 효과적입니다. 특히 Vercel로 Next.js 애플리케이션을 관리하게 된다면, 캐시 정책과 같은 것들을 자동으로 설정해주어 사용자 네트워크 환경에 관계없이 굉장히 빠른 HTML 서빙 속도를 보장할 수 있었습니다.</p>
<p>그래서 저는 app router를 사용하면서도 SSG를 사용했는데요. app router에서 SSG를 사용하는 방법도 역시 간단합니다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// somethings/[somethingId]/page.tsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">generateStaticParams</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"https://.../somethings"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">throw</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> somethingIds </span><span class="token operator">=</span><span class="token plain"> somethings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> somethingId</span><span class="token operator">:</span><span class="token plain"> something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> somethingIds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> params </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> somethingId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token operator">...</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Next.js의 훌륭한 API 덕분에 개발자 경험도, 사용자 경험도 좋았습니다.</p>
<p>하지만, 마냥 좋기만 했다면 제가 이 글을 작성하지는 않았을 겁니다.</p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="문제-상세-페이지-개수에-비례하여-증가하는-빌드-시간">문제: 상세 페이지 개수에 비례하여 증가하는 빌드 시간<a href="https://m1nsuppp.vercel.app/why-i-stopped-using-ssg#%EB%AC%B8%EC%A0%9C-%EC%83%81%EC%84%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EA%B0%9C%EC%88%98%EC%97%90-%EB%B9%84%EB%A1%80%ED%95%98%EC%97%AC-%EC%A6%9D%EA%B0%80%ED%95%98%EB%8A%94-%EB%B9%8C%EB%93%9C-%EC%8B%9C%EA%B0%84" class="hash-link" aria-label="문제: 상세 페이지 개수에 비례하여 증가하는 빌드 시간에 대한 직접 링크" title="문제: 상세 페이지 개수에 비례하여 증가하는 빌드 시간에 대한 직접 링크">​</a></h2>
<p>Static Site Generation(SSG)을 사용하면서 가장 큰 문제는 데이터의 양이 증가함에 따라 빌드 시간이 비례하여 증가하는 것이었습니다.</p>
<p>특히, 생성할 상세 페이지의 개수가 10,000 여개로 증가하자 빌드 시간이 15분까지 늘어났습니다. 이로 인해 개발 주기가 길어지고, 팀원들 간의 협업에 차질이 생기면서 전체적인 개발 경험이 저하되었습니다. 빌드를 기다리는 시간이 많아지니 새로운 기능을 테스트하거나 버그를 수정하는 데 소요되는 시간이 늘어나는 악순환이 발생했습니다.</p>
<p><img decoding="async" loading="lazy" alt="15분까지 증가한 빌드 시간" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmQAAABLCAIAAABRFgwhAAABX2lDQ1BJQ0MgUHJvZmlsZQAAKJF1kD1IQmEUhh/LLNIgoqGhyLWwEBWKNtMwIcqsoB8qrlfTQO1yNcIlotmxobWtKagxggqiNSNobmssCFz6uZ2rlf3Q93F4H17OOd/5DtQ5FE1LW4FMNq9HQ8PO2bl5Z+M9dppokdulqDnNH4mMSQqf+vOUb7GYetNn9jovFePde8fnvUu2sH62/fg3/8dpjidyquiLhE/V9DxY3MKRjbxm8pZwuy5DCe+YnKzyvsmxKp9UcqajAeGScKuaUuLCd8Ku2Dc/+Y0z6XX1YwZzekciOzMl2iHRyQgBJnASIoiHAdxMMkrwnxpfpSbAGhoFdFZJkiIv9X5xNNIkhMNkUenHJeyRjh685q5/77DmbRZhqE2eOqh54wtw2At2W83reZDvX8BlQVN05WuzlrI1t+L1VNm+DA2LhvE0CLYrePMZxvORYbzuQn0ZTq/fAUB5Yui3uJTNAAAAOGVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAACoAIABAAAAAEAAAJkoAMABAAAAAEAAABLAAAAAEo2Ly0AAA3ASURBVHgB7Z13jBVVG4fZwiIsIrA0CVjAAjaMxA9FsSsaW9SoURSjMbGGRANqNLFHI2hiLMSuaLAr0ViJUfmwAbEQC9g7sn7KIrKUXWS/ZzkyuezdW/aODM7uM3/cPXP6PGdyfvO+58xsp04eEpCABCQgAQlIQAISkIAEJCABCUhAAhKQgAQkIAEJSEACEpCABDYdgTKa7tWrV/fu3auqqsrKmk89JCABCUhAAh2TQFNTExfe0NCwfPnyurq6CELZ4MGDV61atWLFCnKUl5erlxEaAxKQgAQk0NEIIIVr165FCqurq7t06bJ48eLGxkYgVBC1cuXKiooKlbKj3RNerwQkIAEJtCCAJgY1XL169Zo1a2pqapYtW0aeZo2srKxskdtTCUhAAhKQQEcmgDhiU2JJskaJ/1Wl7Mg3g9cuAQlIQAI5CWBJ1tfXs6eHHOU5c5kgAQlIQAIS6NgEWMLEsoSBYtmxbwSvXgISkIAEchPADRv2vSqWuSGZIgEJSEACElhHQLH0RpCABCQgAQkUIKBYFgBksgQkIAEJSECx9B6QgAQkIAEJFCCgWBYAZLIEJCABCUhAsfQekIAEJCABCRQgoFgWAGSyBCQgAQlIQLH0HpCABCQgAQkUIKBYFgBksgQkIAEJSECx9B6QgAQkIAEJFCCgWBYAZLIEJCABCUhAsfQekIAEJCABCRQgoFgWAGSyBCQgAQlIQLH0HpCABCQgAQkUIFBZIN1kCUhAAhKQwL+bQFWfUZ37juK34bc59LTxf3NC4B/stWL5D8K0KglIQAISSJRArzHTO/cZtb7Jps59/tOpU1mnYc0R9Qtvq19w2/qkuH8runTpErcOy0tAAhKQgASSJYAdWTN2VkW3QRnNljUr5fqDDNXDJxDRuM7cXB/d5r/du3dfsmSJYtlmcBaQgAQkIIFNSwAV7DFycjF9QDJj6mUQSzf4FEPbPBKQgAQk8G8hgFJWD5tQfG/I3CyZ8Q7FMh4/S0tAAhKQQLIE2qSUoWs9x0yP2cfSxXLIkCE33XQTvzF7YHEJSEACEmhPBPBbtno5FRUV3bp1azWp+MjmZciSDrYClVTu70Jxd8Oec845d9999zfffNPWTjzyyCO77rorpZqampYuXfr555/fc889H3zwQf56Zs6c+c4771x99dUMxltvvXXttdc+++yzLYrsvvvuDz300BlnnDF//vwbbrhht912O+qoo1rk8VQCEpBARyZwwAEHMIu+8MILQNh7772POOKITBo33njjypUrM2OKCVdWVlIPE3vPnj3ZEbNw4cKXX3559erVlCXmyCOP3HHHHdlS+vPPPzM5z5o1q5g6s/MUaVYumT+Fsr1HTIpqyNg0G8W1IVC6WCKQyCRiWZpe8nzx448/3nvvveXl5QMHDkTPkM+xY8f+9NNPebp/++23L168mAxlZWWbb755VVVVdmYqJIlfkp577rm33347O48xEpCABDomgREjRiBaO++880cffRQIDBgw4Msvv3zzzTcjIEHhotMiA6NHj952222nTZv266+/9uvX7+STT2Zif+aZZyh+zDHHNDY23nrrrfX19dtss83pp5/+xx9/RB0osn6yFbn0iFLWzb954GEzWtRM8ZLfvyxdLOlETL1E9p5//vlwMZ999tn999+/ww47IJZ77bXXlVdeefbZZy9atIhU7MiuXbteeumlhE844YQ5c+bMmzcvlIp+t9tuO6xMhv+LL754+umno/h99tmHpBdffLF///4PPvjgNddcc/7553Ov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" width="612" height="75" class="img_AvkH"></p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="데이터-캐싱-전략으로-해결하기">데이터 캐싱 전략으로 해결하기<a href="https://m1nsuppp.vercel.app/why-i-stopped-using-ssg#%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BA%90%EC%8B%B1-%EC%A0%84%EB%9E%B5%EC%9C%BC%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0" class="hash-link" aria-label="데이터 캐싱 전략으로 해결하기에 대한 직접 링크" title="데이터 캐싱 전략으로 해결하기에 대한 직접 링크">​</a></h2>
<p>어떻게 빌드 시간을 줄일 수 있을까 열심히 조사하던 중, <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching" target="_blank" rel="noopener noreferrer">Next.js의 공식문서</a>와 <a href="https://github.com/vercel/commerce" target="_blank" rel="noopener noreferrer">Vercel GitHub repo</a>를 찾았습니다.</p>
<p>공식 문서와 Vercel repo는 SSG 없이, fetch API를 이용하여 data caching 하는 기법을 적용하고 있었습니다. <a href="https://demo.vercel.store/" target="_blank" rel="noopener noreferrer">Vercel repo를 통해 운영 중인 웹사이트</a>를 이용해보니 퍼포먼스가 놀라울 정도로 훌륭했습니다. SSG가 아니라는 점이 상당히 놀라울 정도로.</p>
<p>제가 이전에 사용했던 Pages router의 SSR 경험은 그닥 좋지 못했습니다. 그 이유는 TTFB가 너무 느렸기 때문입니다.</p>
<p>그런데 SSR로 이런 퍼포먼스를 보여준다는 것이 너무 놀라웠습니다. 의심이 되어 HTTP 응답 헤더를 분석해보았는데, 캐싱이 적용되어있지 않았습니다.</p>
<p><img decoding="async" loading="lazy" alt="cache-control" src="https://m1nsuppp.vercel.app/assets/images/cache-control-6a330a9cf08e984ef141f33365c1c8bc.png" width="944" height="225" class="img_AvkH"></p>
<p>저는 이것을 확인하자마자 대박이라는 생각을 했고, 곧바로 제가 직면한 문제 상황에 대입해보기로 했습니다.</p>
<p>방법은 아주 간단했습니다. SSG 관련 코드를 모두 제거하고, <code>fetch</code>에 <code>force-cache</code>를 전달해주었습니다.</p>
<div class="language-typescript codeBlockContainer_JDzb theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_bb81"><pre tabindex="0" class="prism-code language-typescript codeBlock_EE1o thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_kgRx"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getSomething</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">somethingId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token operator">&lt;</span><span class="token plain">Something </span><span class="token operator">|</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">undefined</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">https://.../somethings/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">somethingId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    cache</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'force-cache'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// somethings/[somethingId]/page.tsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">Page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> somethingId </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  params</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> somethingId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> something </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getSomething</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">somethingId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">!</span><span class="token plain">something</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">notFound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token operator">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup_tpkI"><button type="button" aria-label="클립보드에 코드 복사" title="복사" class="clean-btn"><span class="copyButtonIcons_Xd7N" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_Rvbi"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_sL_5"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>위와 같이 데이터 캐싱을 활용하니 빌드 시간이 2분 대로 단축 됐을 뿐만 아니라, HTML 서빙 속도도 SSG와 동일한 성능을 보장할 수 있었습니다.</p>
<p><img decoding="async" loading="lazy" alt="after-build-time" src="data:image/png;base64,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" width="690" height="85" class="img_AvkH"></p>
<p><img decoding="async" loading="lazy" alt="after-data-caching" src="https://m1nsuppp.vercel.app/assets/images/after-data-caching-90a1ccadeca599b507c7b52aac955531.png" width="720" height="736" class="img_AvkH"></p>
<h2 class="anchor anchorWithStickyNavbar_N2AM" id="결론">결론<a href="https://m1nsuppp.vercel.app/why-i-stopped-using-ssg#%EA%B2%B0%EB%A1%A0" class="hash-link" aria-label="결론에 대한 직접 링크" title="결론에 대한 직접 링크">​</a></h2>
<p>데이터 캐싱 전략의 도입은 저에게 더 효율적인 개발 환경을 제공해주었습니다. 빌드 시간을 15분에서 2분으로 줄이는 것은 물론, SSG와 비슷한 성능을 보장받을 수 있었습니다.</p>
<p>하지만, Next.js가 <code>fetch</code> API를 커스텀한 것에 대해서는 부정적인 생각이 들었습니다. 물론 최종적으로는 그것을 받아들였지만요.</p>
<p>그리고 이렇게 좋은 경험을 하는 한 편, 내부에서 어떤 동작이 일어나는지 좀 더 빠삭하게 익혀놓아야 할 필요가 있다는 생각을 하기도 했습니다(기술에 잡아먹히면 안될테니까요).</p>]]></content>
        <author>
            <name>Minsup Lee</name>
            <uri>https://github.com/m1nsuppp</uri>
        </author>
        <category label="next.js" term="next.js"/>
        <category label="ssg" term="ssg"/>
        <category label="ssr" term="ssr"/>
        <category label="frontend" term="frontend"/>
    </entry>
</feed>