<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>API Design · 인재덕</title>
    <link>https://jared.lynskey.co.nz/ko/tags/api-design/</link>
    <description>소프트웨어 엔지니어링과 개발 조직 리더십에 대한 이야기. 성과 리뷰, 채용과 팀 확장, CI/CD, 데브옵스 모니터링, 에이전틱 AI. 서울에서 씁니다.</description>
    <generator>Hugo</generator>
    <language>ko</language>
    <copyright>© 2026 인재덕</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ko/tags/api-design/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>API 설계 우선(Design-First) 워크플로우: 스프린트마다 OpenAPI 계약 정하기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-planning-an-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-planning-an-api/</guid>
      <pubDate>Mon, 09 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>API</category>
      <category>OpenAPI</category>
      <category>API Design</category>
      <category>Planning</category>
      <category>Mock Server</category>
      <description>스프린트마다 돌리는 API 설계 우선 개발 프로세스. 백엔드와 프론트엔드 담당자가 OpenAPI 계약에 합의하면 CI가 목 서버를 띄우고, 양쪽이 동시에 개발합니다. YAML 예제 포함.</description>
      <content:encoded><![CDATA[<p>얼마 전 <a href="https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-open-api/" >OpenAPI를 활용한 API 우선 개발</a>에 대해 일반론을 썼는데, 이번 글은 그 실전편입니다. 우리 팀에서 시험 운영 중인 계획 워크플로우와, 거기에 딸린 YAML 예제를 소개합니다.</p>
<p>요약하면, 이 설계 우선 프로세스는 스프린트마다 다음 순서로 돌아갑니다.</p>
<ol>
<li>그 스프린트의 API 작업을 맡을 담당자를 백엔드와 프론트엔드에서 한 명씩 정합니다.</li>
<li>두 사람이 스프린트 요구사항에 맞춰 OpenAPI 스키마를 작성(또는 수정)하고, 양쪽이 동의할 때까지 리뷰합니다.</li>
<li>스키마를 저장소에 커밋합니다. 이제부터 이게 계약입니다.</li>
<li>커밋된 스키마로 CI가 목 서버를 띄웁니다.</li>
<li>프론트엔드는 목 서버를 상대로 개발하고, 그동안 백엔드는 실제 엔드포인트를 구현합니다.</li>
<li>실제 서비스가 배포되면, 거기서 생성된 스펙이 다음 스프린트 스키마의 출발점이 됩니다.</li>
</ol>

<h2 class="relative group">어쩌다 시작했나
    <div id="어쩌다-시작했나" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ec%96%b4%ec%a9%8c%eb%8b%a4-%ec%8b%9c%ec%9e%91%ed%96%88%eb%82%98" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>발단은 회고였습니다. API 계획이 얼마나 안 보이는지에 대해 피드백이 꽤 직설적이었거든요. 백엔드는 요구사항을 자기들 나름대로 해석해서 엔드포인트를 만들고, 프론트엔드는 응답 형태를 나중에야 알게 되고, 어긋난 부분은 통합 시점에, 그러니까 가장 비싼 순간에 터져 나왔습니다. 그래서 나온 제안이 양쪽을 계획 단계부터 끌어들이고, OpenAPI 스키마를 모든 것의 기준점으로 삼자는 것이었습니다.</p>

<h2 class="relative group">프로세스
    <div id="프로세스" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ed%94%84%eb%a1%9c%ec%84%b8%ec%8a%a4" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>매주 백엔드 한 명, 프론트엔드 한 명, 이렇게 대표 두 명을 뽑습니다. 이들의 임무는 스프린트 요구사항을 이해하고 계획용 OpenAPI 스키마를 거기에 맞추는 것입니다. 둘이 함께 스키마를 리뷰하고 안 맞는 부분을 고치다 보면, 만들기 전에 양쪽 모두 정확히 뭘 만들지 알게 됩니다.</p>
<p>수정이 마무리되면 스키마를 저장소에 커밋하고, CI/CD가 그 yaml로 목 API를 생성합니다. 스프린트가 끝나면 다음 스프린트의 API 요구사항을 담은 새 yaml을 만들고, 같은 사이클을 반복합니다.</p>

<h2 class="relative group">도구 관련 메모
    <div id="도구-관련-메모" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%eb%8f%84%ea%b5%ac-%ea%b4%80%eb%a0%a8-%eb%a9%94%eb%aa%a8" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>yaml 시각화는 <a href="https://editor.swagger.io/"  target="_blank" rel="noreferrer">Swagger Editor</a>면 충분합니다. 파일을 올리면 제대로 된 Swagger 문서로 렌더링해서 보여 줍니다.</p>
<p>CI/CD 파이프라인은 yaml을 받아서 목 서버를 띄울 수 있어야 합니다. Stoplight의 <a href="https://github.com/stoplightio/prism"  target="_blank" rel="noreferrer">Prism</a>이 널리 쓰이는 선택지이고, OpenAPI 3.1도 지원합니다.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -g @stoplight/prism-cli
</span></span><span class="line"><span class="cl">prism mock openapi.yaml</span></span></code></pre></div></div>
<p>이렇게 하면 스키마의 모든 경로가 <code>http://127.0.0.1:4010</code>에서 서비스되고, 응답으로는 스키마의 <code>examples</code>가 그대로 돌아옵니다. 예를 들어 <code>GET /lists/10</code>을 호출하면 부록에 있는 예시 리스트가 옵니다. 계약도 제대로 지키게 합니다. 예제에서 API 키를 선언해 뒀기 때문에 <code>X-API-Key</code> 헤더가 없는 요청에는 401을 돌려주는데, 프론트엔드가 맞춰 개발해야 할 동작이 바로 이거죠. 컨테이너 안에서 돌릴 때는 바깥에서 접근할 수 있도록 <code>-h 0.0.0.0</code>을 붙이세요. <a href="https://github.com/muonsoft/openapi-mock"  target="_blank" rel="noreferrer">openapi-mock</a>은 응답을 무작위 데이터로 채워 주는, 좀 더 가벼운 대안입니다.</p>
<p>목 서버를 띄우기 전에 스키마를 린트해 두는 것도 좋습니다. 계약이 깨져 있으면 프론트엔드를 헷갈리게 하는 대신 빌드가 실패하니까요. <a href="https://github.com/stoplightio/spectral"  target="_blank" rel="noreferrer">Spectral</a>은 한 줄짜리 룰셋만 있으면 바로 돌아갑니다.</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install -g @stoplight/spectral-cli
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;extends: [&#34;spectral:oas&#34;]&#39;</span> &gt; .spectral.yaml
</span></span><span class="line"><span class="cl">spectral lint openapi.yaml</span></span></code></pre></div></div>
<p>마지막으로, 이 스키마는 어디까지나 계획용 산출물입니다. 실제 API가 배포되면 각 서비스가 자기 openapi.json을 생성해야 하고, 그걸 내려받아 다음 계획에 다시 활용하면 됩니다.</p>

<h2 class="relative group">왜 이 수고를 하나
    <div id="왜-이-수고를-하나" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ec%99%9c-%ec%9d%b4-%ec%88%98%ea%b3%a0%eb%a5%bc-%ed%95%98%eb%82%98" aria-label="앵커">#</a>
    </span>
    
</h2>
<p>논쟁이 가장 싼 곳으로 옮겨가기 때문입니다. 스키마를 쓰는 자리에 프론트엔드와 백엔드 엔지니어가 같이 앉아 있으면, 오해는 코드가 되기 전에 잡힙니다. API 요구사항은 스프린트 중간에 재협상되는 대신 한 번에 합의되고, 목 서버 덕분에 프론트엔드는 기다리지 않고 바로 시작하고, 중요한 디테일이 빠져나갈 틈도 훨씬 줄어듭니다. 두 쌍의 눈과 문서화된 계약을 다 통과해야 하니까요.</p>
<p>혁명적인 이야기는 하나도 없습니다. 그저 기계가 목으로 되돌려줄 수 있을 만큼 계획을 구체적으로 만들었을 뿐인데, 알고 보니 계획에 필요한 구체성이란 딱 그 정도더군요.</p>

<h2 class="relative group">부록 A - yaml 예제
    <div id="부록-a---yaml-예제" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%eb%b6%80%eb%a1%9d-a---yaml-%ec%98%88%ec%a0%9c" aria-label="앵커">#</a>
    </span>
    
</h2>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">openapi</span><span class="p">:</span><span class="w"> </span><span class="m">3.1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">info</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">MyApp Sprint 3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Planning schema for sprint 3. Drives the mock server only.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">servers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="l">https://api-mock.example.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">lists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Saved lists feature (PROJ-123)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">security</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">api_key</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">/lists</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">lists]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="l">Return the current user&#39;s saved lists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l">getLists</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;200&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">content</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">application/json</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">items</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#/components/schemas/List&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">/lists/{listId}:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">lists]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="l">Find a list by ID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l">getListById</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">listId</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="l">path</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;200&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">OK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">content</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">application/json</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;#/components/schemas/List&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">&#39;404&#39;</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">List not found</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">components</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">schemas</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">List</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">id, title]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">10</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">title</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">Weekend reading]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">itemCount</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">integer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">examples</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="m">7</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">securitySchemes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">api_key</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">apiKey</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">X-API-Key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="l">header</span></span></span></code></pre></div></div>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-planning-an-api/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>API 우선 개발(API-First)과 OpenAPI: 프론트엔드와 백엔드를 병렬로 개발하기</title>
      <link>https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-open-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ko/posts/2023/2023-10-08-open-api/</guid>
      <pubDate>Sun, 08 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>OpenAPI</category>
      <category>backend</category>
      <category>frontend</category>
      <category>API design</category>
      <category>software development</category>
      <category>team collaboration</category>
      <description>API 우선 개발을 실무에서 하는 법: OpenAPI 계약에 먼저 합의하고 목 서버를 띄우면, 프론트엔드가 더 이상 백엔드를 기다리지 않아도 됩니다.</description>
      <content:encoded><![CDATA[<p>지금까지 본 것 중 일정을 가장 확실하게 망치는 건 어려운 엔지니어링이 아닙니다. &ldquo;거의 다 됐다&quot;는 엔드포인트를 프론트엔드가 하염없이 기다리는 상황이죠. 회사에서 정착시킨 해법은 지루하지만 잘 듣습니다. 코드를 쓰기 전에 OpenAPI로 API 계약부터 합의하는 겁니다.</p>

<h3 class="relative group">기다림의 게임
    <div id="기다림의-게임" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ea%b8%b0%eb%8b%a4%eb%a6%bc%ec%9d%98-%ea%b2%8c%ec%9e%84" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>전통적인 흐름은 직렬입니다. 백엔드가 API를 설계하고 구현한 뒤, 엔드포인트가 실제로 동작해야 프론트엔드가 통합을 시작합니다. 백엔드의 지연은 전부 프론트엔드 일정에 그대로 얹히는데, 릴리스 날짜가 적혀 있는 곳이 대개 프론트엔드 일정표입니다.</p>

<h3 class="relative group">코드보다 계약이 먼저
    <div id="코드보다-계약이-먼저" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ec%bd%94%eb%93%9c%eb%b3%b4%eb%8b%a4-%ea%b3%84%ec%95%bd%ec%9d%b4-%eb%a8%bc%ec%a0%80" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>스키마 우선 흐름에서는 양쪽이 거의 동시에 출발합니다. 코드가 한 줄도 없는 시점에 API 구조를 OpenAPI 스키마로 적어 둡니다. 모든 엔드포인트, 요청·응답 모델, 인증 방식, 에러 코드까지요. 이 문서가 프론트엔드와 백엔드 사이의 계약서가 됩니다.</p>
<p>효과는 바로 나타납니다. 만들기 전에 모두가 기능을 같은 모양으로 이해하게 됩니다. 백엔드가 구현 중인 동안 프론트엔드는 UI를 만들 수 있고요. 합의된 단일 진실 공급원 하나만 고치면 되니 변경도 빨리 돕니다. 게다가 스키마에서 바로 목 서버를 생성할 수 있어서, 진짜 엔드포인트가 하나도 없는 단계부터 프론트엔드 테스트가 시작됩니다.</p>

<h3 class="relative group">실제로 돌리는 순서
    <div id="실제로-돌리는-순서" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ec%8b%a4%ec%a0%9c%eb%a1%9c-%eb%8f%8c%eb%a6%ac%eb%8a%94-%ec%88%9c%ec%84%9c" aria-label="앵커">#</a>
    </span>
    
</h3>
<ol>
<li>요구사항을 모읍니다. 뭘 만들든 똑같죠.</li>
<li>스키마를 설계합니다. 엔드포인트, 요청/응답 모델, 인증, 에러 코드.</li>
<li>프론트엔드와 백엔드 양쪽이 함께 리뷰합니다. 계약이 제값을 하는 순간이 바로 여기입니다.</li>
<li>목 서버를 생성하고(Swagger, Postman 둘 다 됩니다) 프론트엔드는 그걸 상대로 통합을 진행합니다.</li>
<li>백엔드는 같은 스키마에 맞춰 진짜 엔드포인트를 구현합니다.</li>
<li>완성되는 대로 목을 진짜 엔드포인트로 갈아 끼우고, 계약이 지켜지는지 계속 테스트합니다.</li>
<li>대화를 이어갑니다. 스키마는 살아 있는 문서지 석판이 아니니까요.</li>
</ol>

<h3 class="relative group">걸리는 부분
    <div id="걸리는-부분" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%ea%b1%b8%eb%a6%ac%eb%8a%94-%eb%b6%80%eb%b6%84" aria-label="앵커">#</a>
    </span>
    
</h3>
<p>공짜는 아닙니다. 꼼꼼한 스키마를 미리 쓰는 데는 실제로 시간이 듭니다. 요구사항이 바뀌면 스키마부터 고쳐야 하고, 그 파장은 두 팀 모두에 미칩니다. 그리고 전원이 OpenAPI와 주변 도구에 최소한의 감은 갖고 있어야 하죠.</p>
<p>그래도 이 선행 투자가, 그 덕에 막을 수 있는 통합 지옥에 지는 걸 본 적이 없습니다. 프론트엔드는 첫날부터 달리기 시작하고, 논쟁은 깨진 빌드가 아니라 문서 위에서 벌어지게 됩니다.</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/featured.jpg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
