<?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/">
  <channel>
    <title>Aws on Devlog in the SKY</title>
    <link>https://skyoo2003.github.io/ko/tags/aws/</link>
    <description>Recent content in Aws on Devlog in the SKY</description>
    <generator>Hugo</generator>
    <language>ko</language>
    <lastBuildDate>Sun, 19 Apr 2026 00:00:00 +0900</lastBuildDate>
    <atom:link href="https://skyoo2003.github.io/ko/tags/aws/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Smithy 모델로 AWS 서비스를 자동 생성하는 방법</title>
      <link>https://skyoo2003.github.io/ko/posts/2026/04/19/smithy-codegen-aws-services/</link>
      <pubDate>Sun, 19 Apr 2026 00:00:00 +0900</pubDate>
      <guid>https://skyoo2003.github.io/ko/posts/2026/04/19/smithy-codegen-aws-services/</guid>
      <description>&lt;h2 id=&#34;들어가며&#34;&gt;들어가며&lt;/h2&gt;
&lt;p&gt;AWS는 200개가 넘는 서비스를 제공하며, 각 서비스는 고유한 API 프로토콜과 요청/응답 구조를 가지고 있다. 이들을 하나하나 수동으로 구현하는 것은 현실적으로 불가능에 가깝다. &lt;a href=&#34;https://github.com/skyoo2003/devcloud&#34;&gt;DevCloud&lt;/a&gt;에서는 AWS의 내부 모델링 언어인 &lt;strong&gt;Smithy&lt;/strong&gt;를 리버스 엔지니어링하여 거의 전체 AWS 서비스의 Go 코드를 자동 생성하는 파이프라인을 구축했다.&lt;/p&gt;
&lt;p&gt;이 포스트에서는 Smithy 모델을 파싱하여 Go 코드를 생성하는 전체 흐름과, 자동 생성된 코드를 기반으로 로컬 AWS 에뮬레이터를 구동하는 방법을 살펴본다. 하지만 더 근본적인 질문은 이것이다: &lt;strong&gt;왜 IDL에서 코드를 생성하는가?&lt;/strong&gt; 단순한 &amp;ldquo;생산성 자동화&amp;rdquo; 이상의 의미를 파악하려면, 먼저 이 접근이 해결하려는 근본적인 문제를 이해해야 한다.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>AWS는 200개가 넘는 서비스를 제공하며, 각 서비스는 고유한 API 프로토콜과 요청/응답 구조를 가지고 있다. 이들을 하나하나 수동으로 구현하는 것은 현실적으로 불가능에 가깝다. <a href="https://github.com/skyoo2003/devcloud">DevCloud</a>에서는 AWS의 내부 모델링 언어인 <strong>Smithy</strong>를 리버스 엔지니어링하여 거의 전체 AWS 서비스의 Go 코드를 자동 생성하는 파이프라인을 구축했다.</p>
<p>이 포스트에서는 Smithy 모델을 파싱하여 Go 코드를 생성하는 전체 흐름과, 자동 생성된 코드를 기반으로 로컬 AWS 에뮬레이터를 구동하는 방법을 살펴본다. 하지만 더 근본적인 질문은 이것이다: <strong>왜 IDL에서 코드를 생성하는가?</strong> 단순한 &ldquo;생산성 자동화&rdquo; 이상의 의미를 파악하려면, 먼저 이 접근이 해결하려는 근본적인 문제를 이해해야 한다.</p>
<h2 id="근본적인-문제-수동-api-구현의-한계">근본적인 문제: 수동 API 구현의 한계</h2>
<p>클라우드 에뮬레이터를 구현하는 작업은 본질적으로 <strong>프로토콜 변환기 구현</strong>이다. AWS SDK가 JSON 바디를 보내면 에뮬레이터는 Go 구조체로 변환해야 하고, XML 응답을 반환해야 하며, 에러 코드를 SDK가 기대하는 형식으로 맞춰야 한다.</p>
<p>이 작업을 수동으로 하는 데에는 세 가지 근본적인 한계가 있다.</p>
<p>첫째, <strong>언어(spec)과 구현(impl)의 불일치</strong>다. AWS가 S3에 새 오퍼레이션을 추가하면, 문서를 읽고 Go 구조체를 추가하고 직렬화 코드를 수정해야 한다. 이 과정에서 언제든 누락이 발생한다. 두 번째, <strong>5가지 프로토콜의 직렬화 규칙을 모두 외우는 것은 인간의 인지 능력의 한계를 넘는다</strong>. REST-XML의 XML 네임스페이스, Query 프로토콜의 ECMAScript 날짜 포맷, JSON 프로토콜의 <code>X-Amz-Target</code> 헤더 형식 — 이 모든 것을 동시에 정확히 구현하는 것은 실제로 시도해 본 사람만이 얼마나 어려운지 안다. 세 번째, <strong>확장성</strong>이다. 6개 핵심 서비스를 구현하는 것은 현실적이지만, 그것을 90개 서비스로 확장하는 것은 전혀 다른 문제다.</p>
<p>이 문제를 해결하는 열쇠는 단순하다: <strong>AWS가 이미 모델로 정의해놓으니까, 그 모델을 파싱해서 코드를 생성하면 된다.</strong> 언어와 구현 사이의 간극을 모델이라는 중간 표현으로 메우는 것이다. 이것이 모델 기반 코드 생성의 본질이다.</p>
<h2 id="smithy란-무엇인가">Smithy란 무엇인가</h2>
<p>Smithy는 AWS가 내부적으로 사용하는 **인터페이스 정의 언어(IDL)**다. Protocol Buffers나 OpenAPI와 같은 역할을 하지만, 몇 가지 결정적인 차이가 있다.</p>
<table>
  <thead>
      <tr>
          <th>특징</th>
          <th>OpenAPI</th>
          <th>Protocol Buffers</th>
          <th>Smithy</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>정의 대상</td>
          <td>REST API</td>
          <td>메시지 포맷</td>
          <td>API + 프로토콜 + 에러</td>
      </tr>
      <tr>
          <td>프로토콜</td>
          <td>REST에 내장</td>
          <td>별도 정의 필요</td>
          <td>모델에 명시</td>
      </tr>
      <tr>
          <td>서버 코드 생성</td>
          <td>공식 지원 안 함</td>
          <td>protoc (Go)</td>
          <td>지원 안 함 (직접 구현)</td>
      </tr>
      <tr>
          <td>SDK 생성</td>
          <td>공식 지원</td>
          <td>protoc (다양 언어)</td>
          <td>공식 지원 (다양 언어)</td>
      </tr>
      <tr>
          <td>에러 모델</td>
          <td>RFC 7807</td>
          <td>별도 정의</td>
          <td>내장 (httpBinding, retryConfig)</td>
      </tr>
  </tbody>
</table>
<p>Smithy의 핵심 차이점은 <strong>프로토콜이 모델에 명시된다</strong>는 점이다. OpenAPI는 REST에 국한되며, Protocol Buffers는 직렬화만 정의한다. 반면 Smithy는 서비스, 오퍼레이션, 구조체, 에러, 프로토콜 바인딩, HTTP 엔드포인트, 재시도 정책, 페이지네이션 등 API의 모든 측면을 하나의 모델에 정의한다.</p>
<p>Smithy 모델은 JSON 형태로 배포되며, AWS SDK Go V2 저장소에서 찾을 수 있다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;smithy&#34;</span>: <span style="color:#f1fa8c">&#34;2.0&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">&#34;shapes&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;com.amazonaws.s3#PutObjectRequest&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;type&#34;</span>: <span style="color:#f1fa8c">&#34;structure&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;members&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;Bucket&#34;</span>: { <span style="color:#ff79c6">&#34;target&#34;</span>: <span style="color:#f1fa8c">&#34;com.amazonaws.s3#BucketName&#34;</span> },
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;Key&#34;</span>: { <span style="color:#ff79c6">&#34;target&#34;</span>: <span style="color:#f1fa8c">&#34;com.amazonaws.s3#ObjectKey&#34;</span> }
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#ff79c6">&#34;traits&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;aws.api#http&#34;</span>: { <span style="color:#ff79c6">&#34;method&#34;</span>: <span style="color:#f1fa8c">&#34;PUT&#34;</span>, <span style="color:#ff79c6">&#34;uri&#34;</span>: <span style="color:#f1fa8c">&#34;/{Bucket}/{Key+}&#34;</span> },
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;aws.protocols#restXml&#34;</span>: {}
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>이 하나의 Shape 정의에서 우리가 필요한 모든 정보를 추출할 수 있다: 구조체 필드(<code>Bucket</code>, <code>Key</code>), HTTP 바인딩(<code>PUT /{Bucket}/{Key+}</code>), 프로토콜(<code>rest-xml</code>).</p>
<h2 id="코드-생성-파이프라인-구조">코드 생성 파이프라인 구조</h2>
<p>DevCloud의 코드 생성 파이프라인은 네 단계로 구성된다:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 384 601"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>┌</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'>┌</text>
<text text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='196' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='212' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='244' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='260' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='276' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='292' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='308' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='324' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='340' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='356' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='372' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='16' y='420' fill='currentColor' style='font-size:1em'>┌</text>
<text text-anchor='middle' x='16' y='436' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='452' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='468' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='484' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='16' y='532' fill='currentColor' style='font-size:1em'>┌</text>
<text text-anchor='middle' x='16' y='548' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='564' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='580' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='244' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='40' y='276' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='40' y='308' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='40' y='324' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='40' y='340' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='356' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='40' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='436' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='40' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='548' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='40' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='48' y='212' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='48' y='324' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='340' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='48' y='356' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='48' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='436' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='48' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='548' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='48' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='196' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='56' y='212' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='308' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='324' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='56' y='340' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='356' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='56' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>중</text>
<text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='64' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='324' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='340' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='64' y='356' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='436' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='64' y='452' fill='currentColor' style='font-size:1em'>필</text>
<text text-anchor='middle' x='64' y='468' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='64' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='548' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='64' y='564' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='64' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>간</text>
<text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='308' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='324' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='340' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='356' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='72' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='436' fill='currentColor' style='font-size:1em'>발</text>
<text text-anchor='middle' x='72' y='452' fill='currentColor' style='font-size:1em'>요</text>
<text text-anchor='middle' x='72' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='548' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='72' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='80' y='212' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='80' y='308' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='324' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='80' y='340' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='80' y='356' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='436' fill='currentColor' style='font-size:1em'>자</text>
<text text-anchor='middle' x='80' y='452' fill='currentColor' style='font-size:1em'>한</text>
<text text-anchor='middle' x='80' y='468' fill='currentColor' style='font-size:1em'>인</text>
<text text-anchor='middle' x='80' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='548' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='80' y='564' fill='currentColor' style='font-size:1em'>변</text>
<text text-anchor='middle' x='80' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>표</text>
<text text-anchor='middle' x='88' y='196' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='308' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='88' y='324' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='88' y='340' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='88' y='356' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='88' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='436' fill='currentColor' style='font-size:1em'>가</text>
<text text-anchor='middle' x='88' y='468' fill='currentColor' style='font-size:1em'>증</text>
<text text-anchor='middle' x='88' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='88' y='548' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='88' y='564' fill='currentColor' style='font-size:1em'>경</text>
<text text-anchor='middle' x='88' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>현</text>
<text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='96' y='308' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='96' y='324' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='96' y='340' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='96' y='356' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='452' fill='currentColor' style='font-size:1em'>오</text>
<text text-anchor='middle' x='96' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='96' y='548' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='308' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='104' y='324' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='340' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='356' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='104' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='436' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='104' y='452' fill='currentColor' style='font-size:1em'>퍼</text>
<text text-anchor='middle' x='104' y='468' fill='currentColor' style='font-size:1em'>없</text>
<text text-anchor='middle' x='104' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='548' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='104' y='564' fill='currentColor' style='font-size:1em'>감</text>
<text text-anchor='middle' x='104' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>*</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='308' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='112' y='356' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='112' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='436' fill='currentColor' style='font-size:1em'>텁</text>
<text text-anchor='middle' x='112' y='452' fill='currentColor' style='font-size:1em'>레</text>
<text text-anchor='middle' x='112' y='468' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='112' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='564' fill='currentColor' style='font-size:1em'>지</text>
<text text-anchor='middle' x='112' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>J</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='120' y='308' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='356' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='120' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='436' fill='currentColor' style='font-size:1em'>을</text>
<text text-anchor='middle' x='120' y='452' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='120' y='468' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='120' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='120' y='548' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='120' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='128' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='128' y='308' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='128' y='356' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='452' fill='currentColor' style='font-size:1em'>션</text>
<text text-anchor='middle' x='128' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='128' y='548' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='128' y='564' fill='currentColor' style='font-size:1em'>시</text>
<text text-anchor='middle' x='128' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='308' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='136' y='356' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='136' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='436' fill='currentColor' style='font-size:1em'>임</text>
<text text-anchor='middle' x='136' y='452' fill='currentColor' style='font-size:1em'>만</text>
<text text-anchor='middle' x='136' y='468' fill='currentColor' style='font-size:1em'>비</text>
<text text-anchor='middle' x='136' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='136' y='548' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>┬</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='308' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='144' y='356' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='144' y='372' fill='currentColor' style='font-size:1em'>┬</text>
<text text-anchor='middle' x='144' y='388' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='144' y='404' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='144' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='436' fill='currentColor' style='font-size:1em'>베</text>
<text text-anchor='middle' x='144' y='468' fill='currentColor' style='font-size:1em'>즈</text>
<text text-anchor='middle' x='144' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='500' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='144' y='516' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='144' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='144' y='548' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='144' y='564' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='144' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='152' y='308' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='356' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='152' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='436' fill='currentColor' style='font-size:1em'>딩</text>
<text text-anchor='middle' x='152' y='452' fill='currentColor' style='font-size:1em'>오</text>
<text text-anchor='middle' x='152' y='468' fill='currentColor' style='font-size:1em'>니</text>
<text text-anchor='middle' x='152' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='152' y='548' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='564' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='152' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='160' y='244' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='160' y='260' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='160' y='276' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='160' y='356' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='160' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='436' fill='currentColor' style='font-size:1em'>하</text>
<text text-anchor='middle' x='160' y='452' fill='currentColor' style='font-size:1em'>버</text>
<text text-anchor='middle' x='160' y='468' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='160' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='160' y='548' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='212' fill='currentColor' style='font-size:1em'>서</text>
<text text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'>요</text>
<text text-anchor='middle' x='168' y='244' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='260' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='276' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='292' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='308' fill='currentColor' style='font-size:1em'>응</text>
<text text-anchor='middle' x='168' y='324' fill='currentColor' style='font-size:1em'>오</text>
<text text-anchor='middle' x='168' y='340' fill='currentColor' style='font-size:1em'>에</text>
<text text-anchor='middle' x='168' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='436' fill='currentColor' style='font-size:1em'>여</text>
<text text-anchor='middle' x='168' y='452' fill='currentColor' style='font-size:1em'>라</text>
<text text-anchor='middle' x='168' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='168' y='548' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='168' y='564' fill='currentColor' style='font-size:1em'>자</text>
<text text-anchor='middle' x='168' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>중</text>
<text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='176' y='212' fill='currentColor' style='font-size:1em'>비</text>
<text text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'>청</text>
<text text-anchor='middle' x='176' y='308' fill='currentColor' style='font-size:1em'>답</text>
<text text-anchor='middle' x='176' y='324' fill='currentColor' style='font-size:1em'>퍼</text>
<text text-anchor='middle' x='176' y='340' fill='currentColor' style='font-size:1em'>러</text>
<text text-anchor='middle' x='176' y='356' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='176' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='452' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='176' y='468' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='176' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='176' y='548' fill='currentColor' style='font-size:1em'>가</text>
<text text-anchor='middle' x='176' y='564' fill='currentColor' style='font-size:1em'>동</text>
<text text-anchor='middle' x='176' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>간</text>
<text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>일</text>
<text text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'>구</text>
<text text-anchor='middle' x='184' y='212' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='184' y='244' fill='currentColor' style='font-size:1em'>J</text>
<text text-anchor='middle' x='184' y='260' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='184' y='276' fill='currentColor' style='font-size:1em'>Q</text>
<text text-anchor='middle' x='184' y='292' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='184' y='324' fill='currentColor' style='font-size:1em'>레</text>
<text text-anchor='middle' x='184' y='356' fill='currentColor' style='font-size:1em'>텁</text>
<text text-anchor='middle' x='184' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='436' fill='currentColor' style='font-size:1em'>구</text>
<text text-anchor='middle' x='184' y='452' fill='currentColor' style='font-size:1em'>드</text>
<text text-anchor='middle' x='184' y='468' fill='currentColor' style='font-size:1em'>직</text>
<text text-anchor='middle' x='184' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='184' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='196' fill='currentColor' style='font-size:1em'>조</text>
<text text-anchor='middle' x='192' y='228' fill='currentColor' style='font-size:1em'>역</text>
<text text-anchor='middle' x='192' y='244' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='192' y='260' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='192' y='276' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='192' y='292' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='192' y='308' fill='currentColor' style='font-size:1em'>직</text>
<text text-anchor='middle' x='192' y='324' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='192' y='340' fill='currentColor' style='font-size:1em'>타</text>
<text text-anchor='middle' x='192' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='436' fill='currentColor' style='font-size:1em'>현</text>
<text text-anchor='middle' x='192' y='468' fill='currentColor' style='font-size:1em'>에</text>
<text text-anchor='middle' x='192' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='192' y='548' fill='currentColor' style='font-size:1em'>매</text>
<text text-anchor='middle' x='192' y='564' fill='currentColor' style='font-size:1em'>생</text>
<text text-anchor='middle' x='192' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>표</text>
<text text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='164' fill='currentColor' style='font-size:1em'>템</text>
<text text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'>체</text>
<text text-anchor='middle' x='200' y='212' fill='currentColor' style='font-size:1em'>인</text>
<text text-anchor='middle' x='200' y='228' fill='currentColor' style='font-size:1em'>직</text>
<text text-anchor='middle' x='200' y='244' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='200' y='260' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='200' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='200' y='292' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='200' y='308' fill='currentColor' style='font-size:1em'>렬</text>
<text text-anchor='middle' x='200' y='324' fill='currentColor' style='font-size:1em'>션</text>
<text text-anchor='middle' x='200' y='340' fill='currentColor' style='font-size:1em'>입</text>
<text text-anchor='middle' x='200' y='356' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='200' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='200' y='548' fill='currentColor' style='font-size:1em'>주</text>
<text text-anchor='middle' x='200' y='564' fill='currentColor' style='font-size:1em'>성</text>
<text text-anchor='middle' x='200' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>현</text>
<text text-anchor='middle' x='208' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='164' fill='currentColor' style='font-size:1em'>플</text>
<text text-anchor='middle' x='208' y='212' fill='currentColor' style='font-size:1em'>터</text>
<text text-anchor='middle' x='208' y='228' fill='currentColor' style='font-size:1em'>렬</text>
<text text-anchor='middle' x='208' y='244' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='208' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='208' y='308' fill='currentColor' style='font-size:1em'>화</text>
<text text-anchor='middle' x='208' y='356' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='208' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='468' fill='currentColor' style='font-size:1em'>집</text>
<text text-anchor='middle' x='208' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='208' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='164' fill='currentColor' style='font-size:1em'>릿</text>
<text text-anchor='middle' x='216' y='196' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='216' y='212' fill='currentColor' style='font-size:1em'>페</text>
<text text-anchor='middle' x='216' y='228' fill='currentColor' style='font-size:1em'>화</text>
<text text-anchor='middle' x='216' y='260' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='216' y='276' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='216' y='292' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='216' y='324' fill='currentColor' style='font-size:1em'>라</text>
<text text-anchor='middle' x='216' y='340' fill='currentColor' style='font-size:1em'>정</text>
<text text-anchor='middle' x='216' y='356' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='216' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='468' fill='currentColor' style='font-size:1em'>중</text>
<text text-anchor='middle' x='216' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='216' y='548' fill='currentColor' style='font-size:1em'>모</text>
<text text-anchor='middle' x='216' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='196' fill='currentColor' style='font-size:1em'>요</text>
<text text-anchor='middle' x='224' y='212' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='224' y='244' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='224' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='224' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='224' y='308' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='224' y='324' fill='currentColor' style='font-size:1em'>우</text>
<text text-anchor='middle' x='224' y='340' fill='currentColor' style='font-size:1em'>의</text>
<text text-anchor='middle' x='224' y='356' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='224' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='224' y='548' fill='currentColor' style='font-size:1em'>델</text>
<text text-anchor='middle' x='224' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='164' fill='currentColor' style='font-size:1em'>렌</text>
<text text-anchor='middle' x='232' y='196' fill='currentColor' style='font-size:1em'>청</text>
<text text-anchor='middle' x='232' y='212' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='232' y='228' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='232' y='244' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='232' y='260' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='232' y='276' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='232' y='292' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='232' y='308' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='232' y='324' fill='currentColor' style='font-size:1em'>팅</text>
<text text-anchor='middle' x='232' y='356' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='232' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='232' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='84' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='164' fill='currentColor' style='font-size:1em'>더</text>
<text text-anchor='middle' x='240' y='196' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='240' y='228' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='240' y='244' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='240' y='260' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='240' y='276' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='240' y='292' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='240' y='308' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='240' y='356' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='240' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='240' y='548' fill='currentColor' style='font-size:1em'>동</text>
<text text-anchor='middle' x='240' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='164' fill='currentColor' style='font-size:1em'>링</text>
<text text-anchor='middle' x='248' y='196' fill='currentColor' style='font-size:1em'>응</text>
<text text-anchor='middle' x='248' y='228' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='248' y='244' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='248' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='248' y='308' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='248' y='324' fill='currentColor' style='font-size:1em'>테</text>
<text text-anchor='middle' x='248' y='356' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='248' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='436' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='248' y='452' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='248' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='248' y='548' fill='currentColor' style='font-size:1em'>기</text>
<text text-anchor='middle' x='248' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'>답</text>
<text text-anchor='middle' x='256' y='228' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='256' y='260' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='256' y='276' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='256' y='292' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='256' y='308' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='256' y='324' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='256' y='356' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='256' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='256' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='196' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='264' y='228' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='264' y='244' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='264' y='260' fill='currentColor' style='font-size:1em'>싱</text>
<text text-anchor='middle' x='264' y='276' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='264' y='292' fill='currentColor' style='font-size:1em'>싱</text>
<text text-anchor='middle' x='264' y='308' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='264' y='324' fill='currentColor' style='font-size:1em'>블</text>
<text text-anchor='middle' x='264' y='356' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='264' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>모</text>
<text text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='164' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='272' y='228' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='272' y='244' fill='currentColor' style='font-size:1em'>싱</text>
<text text-anchor='middle' x='272' y='276' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='272' y='308' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='272' y='356' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='272' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='272' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>델</text>
<text text-anchor='middle' x='280' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='228' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='280' y='308' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='280' y='356' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='280' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='468' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='280' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='280' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='276' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='288' y='308' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='288' y='356' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='288' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='288' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>파</text>
<text text-anchor='middle' x='296' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='228' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='296' y='276' fill='currentColor' style='font-size:1em'>싱</text>
<text text-anchor='middle' x='296' y='324' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='296' y='356' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='296' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='296' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>일</text>
<text text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='356' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='304' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='304' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='312' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='196' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='312' y='356' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='312' y='372' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='312' y='564' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='312' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='320' y='84' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>┘</text>
<text text-anchor='middle' x='320' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='356' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='320' y='372' fill='currentColor' style='font-size:1em'>┘</text>
<text text-anchor='middle' x='320' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='320' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='328' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='212' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='328' y='228' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='328' y='308' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='328' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='328' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='84' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='336' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='292' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='336' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='336' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='84' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='344' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='340' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='344' y='420' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='484' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='532' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='344' y='548' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='344' y='580' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='352' y='52' fill='currentColor' style='font-size:1em'>┐</text>
<text text-anchor='middle' x='352' y='148' fill='currentColor' style='font-size:1em'>┐</text>
<text text-anchor='middle' x='352' y='356' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='352' y='420' fill='currentColor' style='font-size:1em'>┐</text>
<text text-anchor='middle' x='352' y='484' fill='currentColor' style='font-size:1em'>┘</text>
<text text-anchor='middle' x='352' y='532' fill='currentColor' style='font-size:1em'>┐</text>
<text text-anchor='middle' x='352' y='580' fill='currentColor' style='font-size:1em'>┘</text>
<text text-anchor='middle' x='360' y='180' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='360' y='244' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='360' y='260' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='360' y='276' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='368' y='84' fill='currentColor' style='font-size:1em'>│</text>
</g>

    </svg>
  
</div>
<p>이 구조의 핵심 아이디어는 <strong>관심사의 분리</strong>다. 모델 파싱(1단계)은 AWS의 도메인 지식이 필요하고, 템플릿 렌더링(2단계)은 Go 템플릿과 직렬화 라이브러리에 대한 이해가 필요하다. 하지만 <strong>서비스 구현(3단계)은 프로토콜 세부사항을 전혀 몰라도 된다</strong>. 직렬화/역직렬화는 코드 생성기가 처리하므로, 개발자는 &ldquo;PutObject는 파일에 저장한다&quot;라는 비즈니스 로직만 구현하면 된다.</p>
<h2 id="1단계-smithy-json-파싱의-핵심">1단계: Smithy JSON 파싱의 핵심</h2>
<p>Parser의 임무는 AWS의 형식으로 인코딩된 JSON을 프로그램이 다룰 수 있는 구조체로 변환하는 것이다. 이 과정에서 세 가지 주요한 결정을 내렸다.</p>
<h3 id="원시-json을-중간-표현으로">원시 JSON을 중간 표현으로</h3>
<p>Smithy JSON의 모든 값이 <code>json.RawMessage</code>로 들어있다. 이는 각 Shape의 타입(<code>service</code>, <code>operation</code>, <code>structure</code> 등)에 따라 파싱 방법이 다르기 때문이다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> rawModel <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>    Smithy <span style="color:#8be9fd">string</span>                     <span style="color:#f1fa8c">`json:&#34;smithy&#34;`</span>
</span></span><span style="display:flex;"><span>    Shapes <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]json.RawMessage <span style="color:#f1fa8c">`json:&#34;shapes&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> rawShape <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>    Type       <span style="color:#8be9fd">string</span>                     <span style="color:#f1fa8c">`json:&#34;type&#34;`</span>
</span></span><span style="display:flex;"><span>    Operations []rawTarget                <span style="color:#f1fa8c">`json:&#34;operations&#34;`</span>
</span></span><span style="display:flex;"><span>    Input      <span style="color:#ff79c6">*</span>rawTarget                 <span style="color:#f1fa8c">`json:&#34;input&#34;`</span>
</span></span><span style="display:flex;"><span>    Output     <span style="color:#ff79c6">*</span>rawTarget                 <span style="color:#f1fa8c">`json:&#34;output&#34;`</span>
</span></span><span style="display:flex;"><span>    Errors     []rawTarget                <span style="color:#f1fa8c">`json:&#34;errors&#34;`</span>
</span></span><span style="display:flex;"><span>    Members    <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]rawMember       <span style="color:#f1fa8c">`json:&#34;members&#34;`</span>
</span></span><span style="display:flex;"><span>    Member     <span style="color:#ff79c6">*</span>rawMember                 <span style="color:#f1fa8c">`json:&#34;member&#34;`</span>
</span></span><span style="display:flex;"><span>    Traits     <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]json.RawMessage <span style="color:#f1fa8c">`json:&#34;traits&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>Traits</code> 맵이 핵심이다. 프로토콜 정보(<code>aws.protocols#restXml</code>), HTTP 바인딩(<code>aws.api#http</code>), 문서화(<code>smithy.api#documentation</code>) 등이 모두 Trait으로 표현된다. Parser는 Trait의 키 접두사로 프로토콜을 감지한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">detectProtocol</span>(s <span style="color:#ff79c6">*</span>rawShape) <span style="color:#8be9fd">string</span> {
</span></span><span style="display:flex;"><span>    traits <span style="color:#ff79c6">:=</span> s.Traits
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> traits[<span style="color:#f1fa8c">&#34;aws.protocols#restXml&#34;</span>] <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-xml&#34;</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> traits[<span style="color:#f1fa8c">&#34;aws.protocols#awsJson1_0&#34;</span>] <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;json-1.0&#34;</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> traits[<span style="color:#f1fa8c">&#34;aws.protocols#awsJson1_1&#34;</span>] <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;json-1.1&#34;</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> traits[<span style="color:#f1fa8c">&#34;aws.protocols#awsQuery&#34;</span>] <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;query&#34;</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> traits[<span style="color:#f1fa8c">&#34;aws.protocols#restJson1&#34;</span>] <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-json&#34;</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="오퍼레이션-참조-해석">오퍼레이션 참조 해석</h3>
<p>Smithy의 오퍼레이션은 다른 오퍼레이션이나 구조체를 참조할 때 <code>#</code>로 구분된 식별자를 사용한다: <code>com.amazonaws.s3#PutObjectInput</code>. Parser는 이 식별자에서 짧은 이름(<code>PutObjectInput</code>)만 추출한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">shortName</span>(ref <span style="color:#8be9fd">string</span>) <span style="color:#8be9fd">string</span> {
</span></span><span style="display:flex;"><span>    parts <span style="color:#ff79c6">:=</span> strings.<span style="color:#50fa7b">Split</span>(ref, <span style="color:#f1fa8c">&#34;#&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> parts[<span style="color:#8be9fd;font-style:italic">len</span>(parts)<span style="color:#ff79c6">-</span><span style="color:#bd93f9">1</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>smithy.api#Unit</code>은 입력이 없는 오퍼레이션을 나타내는 Smithy의 빌트인 타입이다. DeleteBucket, ListBuckets 같은 오퍼레이션이 이 타입을 입력으로 사용한다.</p>
<h3 id="http-바인딩-추출">HTTP 바인딩 추출</h3>
<p>REST 프로토콜 서비스(S3, Route53 등)의 경우, HTTP 메서드와 URI 패턴이 <code>aws.api#http</code> Trait에 포함된다. 이 정보는 라우터 생성에 필수적이다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#f1fa8c">&#34;traits&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&#34;aws.api#http&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;method&#34;</span>: <span style="color:#f1fa8c">&#34;PUT&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&#34;uri&#34;</span>: <span style="color:#f1fa8c">&#34;/{Bucket}/{Key+}&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>{Key+}</code>의 <code>+</code>는 &ldquo;하나 이상의 경로 세그먼트&quot;를 의미하는 Smithy의 URI 템플릿 문법이다. 라우터는 이 패턴을 정규식으로 변환하여 HTTP 요청을 매칭한다.</p>
<h2 id="2단계-go-템플릿-렌더링">2단계: Go 템플릿 렌더링</h2>
<p>Generator는 파싱된 중간 표현을 기반으로 Go 템플릿을 렌더링한다. 각 서비스마다 7개의 Go 파일이 생성되며, 각 파일이 해결하는 문제가 다르다.</p>
<h3 id="typesgo--다중-프로토콜-구조체">types.go — 다중 프로토콜 구조체</h3>
<p>가장 까다운 템플릿이다. 하나의 구조체가 JSON과 XML 양쪑의 태그를 가져야 하므로, 템플릿은 Smithy Trait에서 양쪀 프로토콜의 직렬화 지시자를 읽어 태그를 생성한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>{{ <span style="color:#ff79c6">range</span> .Structures <span style="color:#ff79c6">-</span>}}
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> {{ .Name }} <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>{{ <span style="color:#ff79c6">range</span> .Members <span style="color:#ff79c6">-</span>}}
</span></span><span style="display:flex;"><span>    {{ .Name }} {{ .GoType }} <span style="color:#f1fa8c">`json:&#34;</span><span style="color:#ff79c6">{{</span> <span style="color:#50fa7b">.JSONTag</span> <span style="color:#ff79c6">}}</span><span style="color:#f1fa8c">&#34; xml:&#34;</span><span style="color:#ff79c6">{{</span> <span style="color:#50fa7b">.XMLTag</span> <span style="color:#ff79c6">}}</span><span style="color:#f1fa8c">&#34;`</span>
</span></span><span style="display:flex;"><span>{{ end <span style="color:#ff79c6">-</span>}}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>{{ end <span style="color:#ff79c6">-</span>}}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>json:&quot;bucketName&quot;</code>와 <code>xml:&quot;BucketName&quot;</code>처럼 필드명이 다를 수 있다는 점에 주의해야 한다. AWS SDK는 XML 요소명을 camelCase로 변환하지만, 일부 구형 서비스는 원본 케이스를 유지한다. 이 미세한 차이가 호환성 실패의 흔한적인 원인이 된다.</p>
<h3 id="interfacego--오퍼레이션-계약">interface.go — 오퍼레이션 계약</h3>
<p>서비스의 전체 오퍼레이션을 인터페이스로 정의한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>{{ <span style="color:#ff79c6">range</span> .Operations }}
</span></span><span style="display:flex;"><span>    {{ .Name }}(ctx context.Context, input <span style="color:#ff79c6">*</span>{{ .InputName }}) (<span style="color:#ff79c6">*</span>{{ .OutputName }}, <span style="color:#8be9fd">error</span>)
</span></span><span style="display:flex;"><span>{{<span style="color:#ff79c6">-</span> end }}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>이 인터페이스는 <strong>서비스 구현체와 코드 생성기 사이의 계약</strong>이다. 인터페이스가 정의되어 있으므로, 구현체가 컴파일 타임에 모든 오퍼레이션을 구현했는지 확인할 수 있다.</p>
<h3 id="routergo--http-기반-라우팅">router.go — HTTP 기반 라우팅</h3>
<p>REST 프로토콜 서비스에서는 HTTP 메서드와 URI 패턴으로 오퍼레이션을 결정한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">var</span> OperationRoutes = []OperationRoute{
</span></span><span style="display:flex;"><span>    {Method: <span style="color:#f1fa8c">&#34;PUT&#34;</span>, Pattern: <span style="color:#f1fa8c">&#34;/{Bucket}/{Key+}&#34;</span>, Operation: <span style="color:#f1fa8c">&#34;PutObject&#34;</span>},
</span></span><span style="display:flex;"><span>    {Method: <span style="color:#f1fa8c">&#34;GET&#34;</span>, Pattern: <span style="color:#f1fa8c">&#34;/{Bucket}&#34;</span>, Operation: <span style="color:#f1fa8c">&#34;ListObjectsV2&#34;</span>},
</span></span><span style="display:flex;"><span>    {Method: <span style="color:#f1fa8c">&#34;DELETE&#34;</span>, Pattern: <span style="color:#f1fa8c">&#34;/{Bucket}/{Key+}&#34;</span>, Operation: <span style="color:#f1fa8c">&#34;DeleteObject&#34;</span>},
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>JSON 프로토콜 서비스(DynamoDB, Lambda)에서는 라우터가 필요 없다 — <code>X-Amz-Target</code> 헤더가 오퍼레이션을 지정하므로.</p>
<h3 id="base_providergo--구현-강제-메커니즘">base_provider.go — 구현 강제 메커니즘</h3>
<p>모든 오퍼레이션에 대해 <code>NotImplementedError</code>를 반환하는 스텁을 생성한다. 이 스텁은 Go의 **임베딩(embedding)**과 결합하여 강력한 구현 강제 메커니즘을 만든다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (p <span style="color:#ff79c6">*</span>S3BaseProvider) <span style="color:#50fa7b">PutObject</span>(ctx context.Context, req <span style="color:#ff79c6">*</span>PutObjectInput) (<span style="color:#ff79c6">*</span>PutObjectOutput, <span style="color:#8be9fd">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>, <span style="color:#ff79c6">&amp;</span>NotImplementedError{
</span></span><span style="display:flex;"><span>        Service: <span style="color:#f1fa8c">&#34;Amazon S3&#34;</span>,
</span></span><span style="display:flex;"><span>        Operation: <span style="color:#f1fa8c">&#34;PutObject&#34;</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>이 패턴의 장점: 구현하지 않은 오퍼레이션은 <strong>컴파일 타임에 에러가 아닌 런타임 에러</strong>가 된다. 버그가 아니라 미구현이라는 것이 명확하다. 반면, Go의 컴파일러는 구현체가 인터페이스를 만족하는지 검증하므로, 실수 누락을 방지할 수 있다.</p>
<h2 id="3단계-스텁-임베딩으로-구현하기">3단계: 스텁 임베딩으로 구현하기</h2>
<p>자동 생성된 코드를 기반으로 실제 서비스를 구현하는 방법을 S3를 예시로 살펴본다.</p>
<h3 id="임베딩으로-전체-오퍼레이션-차단">임베딩으로 전체 오퍼레이션 차단</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> S3Provider <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">*</span>generated.S3BaseProvider  <span style="color:#6272a4">// 자동 생성된 스텁 — 미구현 오퍼레이션 자동 차단</span>
</span></span><span style="display:flex;"><span>    fileStore  <span style="color:#ff79c6">*</span>FileStore
</span></span><span style="display:flex;"><span>    metaStore  <span style="color:#ff79c6">*</span>MetadataStore
</span></span><span style="display:flex;"><span>    serverPort <span style="color:#8be9fd">int</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (p <span style="color:#ff79c6">*</span>S3Provider) <span style="color:#50fa7b">ServiceID</span>() <span style="color:#8be9fd">string</span>     { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;s3&#34;</span> }
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (p <span style="color:#ff79c6">*</span>S3Provider) <span style="color:#50fa7b">ServiceName</span>() <span style="color:#8be9fd">string</span>   { <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;Amazon S3&#34;</span> }
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (p <span style="color:#ff79c6">*</span>S3Provider) <span style="color:#50fa7b">Protocol</span>() plugin.ProtocolType {
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> plugin.ProtocolRESTXML
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>*generated.S3BaseProvider</code>를 임베딩하면, 오버라이드하지 않은 모든 메서드가 자동으로 <code>NotImplementedError</code>를 반환한다. Go의 임베딩이 이 패턴을 가능하게 하는데, 핵심은 <strong>명시적 오버라이드가 암시적 오버라이드보다 우선된다</strong>는 Go의 메서드 해석 규칙 때문이다.</p>
<h3 id="경로-트래버설-보호">경로 트래버설 보호</h3>
<p>로컬 파일시스템을 사용하므로 보안이 중요하다. <code>FileStore</code>는 모든 경로에 대해 트래버설 검사를 수행한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (fs <span style="color:#ff79c6">*</span>FileStore) <span style="color:#50fa7b">safePath</span>(parts <span style="color:#ff79c6">...</span><span style="color:#8be9fd">string</span>) (<span style="color:#8be9fd">string</span>, <span style="color:#8be9fd">error</span>) {
</span></span><span style="display:flex;"><span>    joined <span style="color:#ff79c6">:=</span> filepath.<span style="color:#50fa7b">Join</span>(<span style="color:#8be9fd;font-style:italic">append</span>([]<span style="color:#8be9fd">string</span>{fs.baseDir}, parts<span style="color:#ff79c6">...</span>)<span style="color:#ff79c6">...</span>)
</span></span><span style="display:flex;"><span>    cleaned <span style="color:#ff79c6">:=</span> filepath.<span style="color:#50fa7b">Clean</span>(joined)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> !strings.<span style="color:#50fa7b">HasPrefix</span>(cleaned, fs.baseDir<span style="color:#ff79c6">+</span><span style="color:#8be9fd;font-style:italic">string</span>(filepath.Separator)) <span style="color:#ff79c6">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>       cleaned <span style="color:#ff79c6">!=</span> fs.baseDir {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;&#34;</span>, fmt.<span style="color:#50fa7b">Errorf</span>(<span style="color:#f1fa8c">&#34;path traversal detected: %s&#34;</span>, cleaned)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> cleaned, <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>filepath.Clean</code>이 <code>../../etc/passwd</code> 같은 악의적인 경로를 정규화하고, <code>strings.HasPrefix</code>로 정규화된 경로가 여전히 baseDir 아래에 있는지 검증한다. 이 두 단계 검사 없이는 안전하지 않다.</p>
<h2 id="4단계-주간-자동-동기와-자동화-선순환">4단계: 주간 자동 동기와 자동화 선순환</h2>
<p>AWS는 수시로 새 서비스를 출시하고 기존 서비스에 오퍼레이션을 추가한다. 이를 추적하는 것이 코드 생성 파이프라인의 마지막 단계다.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#6272a4"># .github/workflows/smithy-sync.yml</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">schedule</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ff79c6">cron</span>: <span style="color:#f1fa8c">&#34;0 0 * * 1&#34;</span>  <span style="color:#6272a4"># 매주 월요일 자정 UTC</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">workflow_dispatch</span>:       <span style="color:#6272a4"># 수동 실행도 가능</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#ff79c6">sync</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">runs-on</span>: ubuntu-latest
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">uses</span>: actions/checkout@v4
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">name</span>: Download latest Smithy models
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">run</span>: bash scripts/download-smithy-models.sh
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">name</span>: Run code generation
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">run</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          CGO_ENABLED=1 go run ./cmd/codegen \
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            -models ./smithy-models \
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            -output ./internal/generated \
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            -templates ./internal/codegen/templates</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">name</span>: Check for changes
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">id</span>: changes
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">run</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          if git diff --quiet internal/generated/; then
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            echo &#34;changed=false&#34; &gt;&gt; $GITHUB_OUTPUT
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          else
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            echo &#34;changed=true&#34; &gt;&gt; $GITHUB_OUTPUT
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">          fi</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#ff79c6">name</span>: Create Pull Request
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span>: steps.changes.outputs.changed == &#39;true&#39;
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">uses</span>: peter-evans/create-pull-request@v8
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">commit-message</span>: <span style="color:#f1fa8c">&#34;chore: sync Smithy models and regenerate code&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">title</span>: <span style="color:#f1fa8c">&#34;chore: weekly Smithy model sync&#34;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">body</span>: |<span style="color:#f1fa8c">
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            Automated weekly sync of AWS Smithy models.
</span></span></span><span style="display:flex;"><span><span style="color:#f1fa8c">            Changes detected in generated code.</span>
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">branch</span>: smithy-sync/weekly
</span></span><span style="display:flex;"><span>          <span style="color:#ff79c6">delete-branch</span>: <span style="color:#ff79c6">true</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>이 워크플로우의 핵심은 <strong>변경이 있을 때만 PR을 생성</strong>한다는 점이다. AWS가 모델을 변경하지 않은 주에는 아무 일도 일어나지 않는다. 변경이 감지되면 PR이 자동으로 생성되어 리뷰 후 병합할 수 있다.</p>
<p>이것이 코드 생성의 진정 가치다: <strong>자동화의 선순환</strong>. AWS가 모델을 변경 → 코드가 자동 재생성 → 호환성 테스트가 자동 실행 → 문제가 없으면 병합. 인간 개발자는 비즈니스 로직 구현에만 집중하면 된다.</p>
<h2 id="5가지-프로토콜-자동-감지">5가지 프로토콜 자동 감지</h2>
<p>런타임임에 들어오는 요청의 헤더만으로 프로토콜을 판별한다. 이 감지 로직은 에뮬레이터의 진입점에서 가장 중요한 코드 중 하나다.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">DetectProtocol</span>(r <span style="color:#ff79c6">*</span>http.Request) (protocol <span style="color:#8be9fd">string</span>, serviceID <span style="color:#8be9fd">string</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 1. JSON 프로토콜: X-Amz-Target 헤더</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> target <span style="color:#ff79c6">:=</span> r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;X-Amz-Target&#34;</span>); target <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        contentType <span style="color:#ff79c6">:=</span> r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>        proto <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">jsonProtocolFromContentType</span>(contentType)
</span></span><span style="display:flex;"><span>        service <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">serviceFromTarget</span>(target)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> proto, service
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 2. Query 프로토콜: form-encoded body에 Action= 파라미터</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> strings.<span style="color:#50fa7b">Contains</span>(r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;Content-Type&#34;</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#f1fa8c">&#34;application/x-www-form-urlencoded&#34;</span>) {
</span></span><span style="display:flex;"><span>        bodyBytes, _ <span style="color:#ff79c6">:=</span> io.<span style="color:#50fa7b">ReadAll</span>(r.Body)
</span></span><span style="display:flex;"><span>        r.Body = io.<span style="color:#50fa7b">NopCloser</span>(bytes.<span style="color:#50fa7b">NewReader</span>(bodyBytes))
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> strings.<span style="color:#50fa7b">Contains</span>(<span style="color:#8be9fd;font-style:italic">string</span>(bodyBytes), <span style="color:#f1fa8c">&#34;Action=&#34;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;query&#34;</span>, <span style="color:#50fa7b">serviceFromQueryRequest</span>(r, <span style="color:#8be9fd;font-style:italic">string</span>(bodyBytes))
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 3. SigV4 서명에서 REST 스타일 서비스 추출</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> svc <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">serviceFromSigV4</span>(r); svc <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;&#34;</span> <span style="color:#ff79c6">&amp;&amp;</span> svc <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;s3&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-json&#34;</span>, <span style="color:#50fa7b">normalizeServiceID</span>(svc)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 4. 기본: REST-XML (S3)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-xml&#34;</span>, <span style="color:#f1fa8c">&#34;s3&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>감지 순서가 중요하다. <code>X-Amz-Target</code>을 먼저 확인해야 JSON 프로토콜 서비스(DynamoDB, Lambda)가 Query 프로토콜로 오인되지 않는다. SQS는 JSON과 Query 프로토콜을 모두 지원하는 특이한 서비스로, <code>X-Amz-Target</code> 헤더 유무로 구분한다.</p>
<table>
  <thead>
      <tr>
          <th>프로토콜</th>
          <th>Content-Type</th>
          <th>오퍼레이션 지정 방식</th>
          <th>대표 서비스</th>
          <th>복잡도</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>REST-XML</td>
          <td><code>application/xml</code></td>
          <td>HTTP 메서드 + URI</td>
          <td>S3, Route53, CloudFront</td>
          <td>매우 높음</td>
      </tr>
      <tr>
          <td>JSON 1.0</td>
          <td><code>application/x-amz-json-1.0</code></td>
          <td><code>X-Amz-Target</code> 헤더</td>
          <td>DynamoDB, SQS</td>
          <td>중간</td>
      </tr>
      <tr>
          <td>JSON 1.1</td>
          <td><code>application/x-amz-json-1.1</code></td>
          <td><code>X-Amz-Target</code> 헤더</td>
          <td>ECS, Lambda, CloudWatch</td>
          <td>중간</td>
      </tr>
      <tr>
          <td>Query</td>
          <td><code>application/x-www-form-urlencoded</code></td>
          <td><code>Action=</code> 파라미터</td>
          <td>IAM, STS, SNS, RDS</td>
          <td>높음</td>
      </tr>
      <tr>
          <td>REST-JSON</td>
          <td><code>application/json</code></td>
          <td>HTTP 메서드 + URI</td>
          <td>ACM, API Gateway</td>
          <td>중간</td>
      </tr>
  </tbody>
</table>
<p>REST-XML이 가장 복잡한 이유는 HTTP 경로 기반 라우팅, XML 직렬화, 멀티파트 업로드, presigned URL 등 S3 특유의 기능들이 많기 때문이다. 반면 JSON 프로토콜은 오퍼레이션이 헤더에 명시되므로 라우팅이 단순하다.</p>
<h2 id="서비스-구현-현황과-우선순위">서비스 구현 현황과 우선순위</h2>
<p>전체 서비스는 세 단계로 분류되며, 각 단계의 목표가 다르다:</p>
<ul>
<li><strong>Tier 1 (핵심 6개)</strong>: S3, SQS, DynamoDB, Lambda, IAM, STS — 124개 이상의 오퍼레이션, 완전 구현. 대부분의 클라우드 네이티브 애플리케이션이 이 서비스들만으로 동작한다.</li>
<li><strong>Tier 2 (통합 8개)</strong>: EventBridge, SNS, CloudWatch, KMS, Secrets Manager, SSM, ECR — 157개 이상의 오팼레이션. 마이크로서비스 아키텍처에서 필수적인 서비스들.</li>
<li><strong>Tier 3 (확장 40+개)</strong>: EC2, EFS, EBS, Route53, ACM, ECS, EKS 등 — 스텁 생성 완료, 점진적 구현 예정.</li>
</ul>
<p>우선순위를 정하는 기준은 <strong>사용 빈도 × 구현 난이도</strong>다. S3, DynamoDB, SQS는 거의 모든 클라우드 애플리케이션에서 사용되므로 가장 먼저 완전 구현한다. EC2는 사용 빈도는 높지만 구현 난이도가 높으므로 나중에 스텁 단계에서 시작한다.</p>
<h2 id="핵심-인사이트">핵심 인사이트</h2>
<p>이 파이프라인에서 얻은 교훈은 &ldquo;코드를 자동 생성했다&quot;라는 기술적 사실이 아니다. 더 깊은 통찰이 가능하다.</p>
<h3 id="1-모델이-소스-of-truth가-되면-유지보수가-사라진다">1. 모델이 소스 of truth가 되면 유지보수가 사라진다</h3>
<p>96개 서비스 × 평균 10개 오퍼레이션 = 약 960개의 오퍼레이션. 이 중 AWS가 주간에 변경하는 것은 극소수다. 코드 생성 파이프라인이 이를 자동으로 감지하고 PR을 생성하므로, 인간 개발자는 &ldquo;어떤 오퍼레이션이 추가되었나?&ldquo;라는 질문에 답할 필요가 없다. <strong>모델이 바뀐면 코드가 따라간다</strong> — 이게 모델 기반 접근의 진정 가치다.</p>
<h3 id="2-인터페이스가-개발자와-코드-생성기-사이의-언어가-된다">2. 인터페이스가 개발자와 코드 생성기 사이의 언어가 된다</h3>
<p><code>ServicePlugin</code> 인터페이스는 두 가지 역할을 동시에 수행한다. 개발자에게는 &ldquo;이 메서드를 구현하면 된다&quot;는 명확한 계약을 제공하고, 코드 생성기에게는 &ldquo;이 메서드의 직렬화는 내가 처리한다&quot;라고 선언한다. 양쪀이 서로 다른 언어를 사용하더라도, 인터페이스라는 공통 언어가 되어 통신이 가능하다.</p>
<h3 id="3-스텁은-시작점이지-끝점이-아니다">3. 스텁은 시작점이지 끝점이 아니다</h3>
<p>전체 오퍼레이션의 스텁을 자동 생성하면 &ldquo;아무것도 구현하지 않은 빈 프로젝트&quot;가 된다. 하지만 이 빈 프로젝트는 <strong>컴파일이 되고 테스트가 실행되고, 대시보드에 표시된다</strong>. 이것이 시작점이다. 실제 사용 빈도에 따라 점진적으로 구현하면 되고, 구현이 누락되면 에러가 즉시 드러난다.</p>
<h3 id="4-임베딩이-구성composition보다-유리한-경우가-있다">4. 임베딩이 구성(composition)보다 유리한 경우가 있다</h3>
<p>Go에서 인터페이스를 만족시키는 방법은 두 가지다 — 구성(composition)과 임베딩(embedding). 구성은 모든 메서드를 수동으로 위임해야 하지만, 임베딩은 명시적으로 오버라이드한 메서드만 실제 구현이 호출되고 나머지는 임베디드 타입의 기본 동작을 따른다. 수백 개의 오퍼레이션 중 몇 개만 구현할 때 임베딩이 훨씬 실용적이다.</p>
<h2 id="마치며-이-접근이-의미하는-것">마치며: 이 접근이 의미하는 것</h2>
<p>Smithy 모델에서 Go 코드를 자동 생성하는 것은 기술적으로 흥미로운 접근이지만, 그 이면의 진정 가치는 <strong>자동화의 선순환</strong>에 있다. AWS가 모델을 변경하면 코드가 자동으로 재생성되고, 호환성 테스트가 자동으로 실행된다. 인간 개발자는 비즈니스 로직에만 집중하면 된다.</p>
<p>이 패턴은 AWS에 국한되지 않는다. Azure의 REST API는 OpenAPI로 정의되어 있고, GCP의 gRPC 서비스는 Protocol Buffers로 정의되어 있다. 각 플랫폼의 IDL을 파싱하여 코드를 생성하는 파이프라인은 멀티클라우드 에뮬레이터를 구축하는 범용적인 접근법이 될 수 있다.</p>
<p>전체 소스 코드는 <a href="https://github.com/skyoo2003/devcloud">github.com/skyoo2003/devcloud</a>에서 확인할 수 있다.</p>
]]></content:encoded>
    </item>
    <item>
      <title>로컬 AWS 에뮬레이터에서 boto3 호환성 달성하기</title>
      <link>https://skyoo2003.github.io/ko/posts/2026/04/19/local-aws-emulator-boto3-compatibility/</link>
      <pubDate>Sun, 19 Apr 2026 00:00:00 +0900</pubDate>
      <guid>https://skyoo2003.github.io/ko/posts/2026/04/19/local-aws-emulator-boto3-compatibility/</guid>
      <description>&lt;h2 id=&#34;들어가며&#34;&gt;들어가며&lt;/h2&gt;
&lt;p&gt;클라우드 네이티브 애플리케이션을 개발하다 보면 느끼는 불편함이 있다. CI 파이프라인에서 AWS를 호출하면 비용이 청구되고, VPN이 없으면 개발이 멈추며, 온보딩에는 자격 증명 설정이 필요하다. &lt;a href=&#34;https://github.com/skyoo2003/devcloud&#34;&gt;DevCloud&lt;/a&gt;는 이 문제를 로컬에서 해결하는 AWS 에뮬레이터다.&lt;/p&gt;
&lt;p&gt;boto3 호환성 테스트에서 **671/699 케이스 통과 (96%)**를 달성했다. 이 숫자는 단순한 테스트 통과율이 아니라, 프로토콜 설계가 얼마나 정확한지를 보여주는 지표다.&lt;/p&gt;
&lt;h2 id=&#34;왜-aws-에뮬레이션이-어려운가&#34;&gt;왜 AWS 에뮬레이션이 어려운가&lt;/h2&gt;
&lt;p&gt;해결책을 설명하기 전에, AWS 에뮬레이션이 왜 근본적으로 어려운지 이해할 필요가 있다.&lt;/p&gt;
&lt;p&gt;대부분의 API는 프로토콜이 하나다. gRPC 서비스는 protobuf를 쓰고, REST API는 JSON을 쓰며, GraphQL은 자체 쿼리 언어를 사용한다. AWS는 &lt;strong&gt;서로 다른 다섯 가지 프로토콜&lt;/strong&gt;을 서비스별로 사용하며, 각 프로토콜마다 직렬화 규칙, 에러 포맷, 인증 방식이 다르다. 단일한 &amp;ldquo;AWS API&amp;quot;는 존재하지 않는다 — 사실상 하나의 서비스 모델을 공유하는 다섯 개의 프로토콜 구현체를 만드는 셈이다.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>클라우드 네이티브 애플리케이션을 개발하다 보면 느끼는 불편함이 있다. CI 파이프라인에서 AWS를 호출하면 비용이 청구되고, VPN이 없으면 개발이 멈추며, 온보딩에는 자격 증명 설정이 필요하다. <a href="https://github.com/skyoo2003/devcloud">DevCloud</a>는 이 문제를 로컬에서 해결하는 AWS 에뮬레이터다.</p>
<p>boto3 호환성 테스트에서 **671/699 케이스 통과 (96%)**를 달성했다. 이 숫자는 단순한 테스트 통과율이 아니라, 프로토콜 설계가 얼마나 정확한지를 보여주는 지표다.</p>
<h2 id="왜-aws-에뮬레이션이-어려운가">왜 AWS 에뮬레이션이 어려운가</h2>
<p>해결책을 설명하기 전에, AWS 에뮬레이션이 왜 근본적으로 어려운지 이해할 필요가 있다.</p>
<p>대부분의 API는 프로토콜이 하나다. gRPC 서비스는 protobuf를 쓰고, REST API는 JSON을 쓰며, GraphQL은 자체 쿼리 언어를 사용한다. AWS는 <strong>서로 다른 다섯 가지 프로토콜</strong>을 서비스별로 사용하며, 각 프로토콜마다 직렬화 규칙, 에러 포맷, 인증 방식이 다르다. 단일한 &ldquo;AWS API&quot;는 존재하지 않는다 — 사실상 하나의 서비스 모델을 공유하는 다섯 개의 프로토콜 구현체를 만드는 셈이다.</p>
<p>두 번째 난제는 **동작 충실도(behavioral fidelity)**다. 올바른 JSON 구조를 반환하는 것만으로는 충분하지 않다. 타임스탬프는 정해진 정밀도의 ISO 8601 포맷이어야 하고, 에러 코드는 AWS 특정 문자열과 정확히 일치해야 하며, 페이지네이션 토큰은 SDK가 파싱할 수 있는 형태여야 하고, XML 응답은 boto3가 기대하는 정확한 네임스페이스 선언을 가져야 한다. 이 디테일 각각이 잠재적 호환성 실패 원인이다.</p>
<h2 id="5가지-프로토콜-하나의-게이트웨이">5가지 프로토콜, 하나의 게이트웨이</h2>
<p>DevCloud는 다섯 가지 AWS 프로토콜을 단일 HTTP 게이트웨이에서 처리한다:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 640 329"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='0' y='228' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='8' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='132' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='196' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='212' fill='currentColor' style='font-size:1em'>▼</text>
<text text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='16' y='244' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='16' y='260' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='16' y='276' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='16' y='292' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='16' y='308' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='260' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='276' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='292' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='308' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='40' y='260' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='40' y='276' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='40' y='292' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='40' y='308' fill='currentColor' style='font-size:1em'>기</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='260' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='48' y='276' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='292' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='48' y='308' fill='currentColor' style='font-size:1em'>본</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='56' y='260' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='56' y='276' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='56' y='292' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='64' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='64' y='292' fill='currentColor' style='font-size:1em'>V</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='72' y='260' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='72' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='292' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='260' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='80' y='276' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'>q</text>
<text text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'>q</text>
<text text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='260' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='88' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='292' fill='currentColor' style='font-size:1em'>서</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='276' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='96' y='292' fill='currentColor' style='font-size:1em'>명</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='260' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='104' y='276' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='104' y='292' fill='currentColor' style='font-size:1em'>에</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='112' y='260' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='276' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>크</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='120' y='260' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='276' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='120' y='292' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='128' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='128' y='260' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='136' y='276' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='136' y='292' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='144' y='260' fill='currentColor' style='font-size:1em'>헤</text>
<text text-anchor='middle' x='144' y='292' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>오</text>
<text text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='260' fill='currentColor' style='font-size:1em'>더</text>
<text text-anchor='middle' x='152' y='276' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='152' y='292' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>요</text>
<text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>리</text>
<text text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='160' y='276' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='160' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>청</text>
<text text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'>진</text>
<text text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='168' y='260' fill='currentColor' style='font-size:1em'>존</text>
<text text-anchor='middle' x='168' y='276' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='176' y='180' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='176' y='260' fill='currentColor' style='font-size:1em'>재</text>
<text text-anchor='middle' x='176' y='276' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='176' y='292' fill='currentColor' style='font-size:1em'>경</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='184' y='116' fill='currentColor' style='font-size:1em'>크</text>
<text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>처</text>
<text text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='184' y='180' fill='currentColor' style='font-size:1em'>대</text>
<text text-anchor='middle' x='184' y='276' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='184' y='292' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>패</text>
<text text-anchor='middle' x='192' y='116' fill='currentColor' style='font-size:1em'>기</text>
<text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>리</text>
<text text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='192' y='164' fill='currentColor' style='font-size:1em'>구</text>
<text text-anchor='middle' x='192' y='180' fill='currentColor' style='font-size:1em'>시</text>
<text text-anchor='middle' x='192' y='276' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'>닉</text>
<text text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='200' y='164' fill='currentColor' style='font-size:1em'>조</text>
<text text-anchor='middle' x='200' y='180' fill='currentColor' style='font-size:1em'>보</text>
<text text-anchor='middle' x='200' y='276' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'>제</text>
<text text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='208' y='164' fill='currentColor' style='font-size:1em'>화</text>
<text text-anchor='middle' x='208' y='180' fill='currentColor' style='font-size:1em'>드</text>
<text text-anchor='middle' x='208' y='276' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'>복</text>
<text text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'>한</text>
<text text-anchor='middle' x='216' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='216' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'>구</text>
<text text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'>q</text>
<text text-anchor='middle' x='224' y='164' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='224' y='180' fill='currentColor' style='font-size:1em'>실</text>
<text text-anchor='middle' x='224' y='276' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='232' y='148' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='232' y='164' fill='currentColor' style='font-size:1em'>깅</text>
<text text-anchor='middle' x='232' y='180' fill='currentColor' style='font-size:1em'>시</text>
<text text-anchor='middle' x='232' y='276' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='232' y='292' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='240' y='148' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='240' y='164' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='240' y='180' fill='currentColor' style='font-size:1em'>간</text>
<text text-anchor='middle' x='240' y='260' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='240' y='276' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='248' y='148' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='248' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='248' y='292' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='256' y='148' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='256' y='180' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='256' y='260' fill='currentColor' style='font-size:1em'>J</text>
<text text-anchor='middle' x='256' y='276' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='256' y='292' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='256' y='308' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='264' y='148' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='264' y='180' fill='currentColor' style='font-size:1em'>그</text>
<text text-anchor='middle' x='264' y='260' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='264' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='264' y='292' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='272' y='148' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='272' y='180' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='272' y='260' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='272' y='276' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='272' y='292' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='272' y='308' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='280' y='148' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='280' y='260' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='280' y='276' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='280' y='292' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='280' y='308' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='288' y='148' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='288' y='276' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='292' fill='currentColor' style='font-size:1em'>J</text>
<text text-anchor='middle' x='288' y='308' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='296' y='260' fill='currentColor' style='font-size:1em'>프</text>
<text text-anchor='middle' x='296' y='276' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='296' y='292' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='296' y='308' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='304' y='260' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='304' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='304' y='292' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='304' y='308' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='312' y='260' fill='currentColor' style='font-size:1em'>토</text>
<text text-anchor='middle' x='312' y='276' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='312' y='292' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='312' y='308' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>K</text>
<text text-anchor='middle' x='320' y='260' fill='currentColor' style='font-size:1em'>콜</text>
<text text-anchor='middle' x='320' y='308' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='328' y='276' fill='currentColor' style='font-size:1em'>+</text>
<text text-anchor='middle' x='328' y='292' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='328' y='308' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='336' y='260' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='336' y='292' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='344' y='260' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='344' y='276' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='344' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='344' y='308' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='352' y='260' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='352' y='276' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='352' y='292' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='352' y='308' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='360' y='260' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='360' y='276' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='360' y='292' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='360' y='308' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='368' y='260' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='368' y='276' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='292' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='368' y='308' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='376' y='260' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='376' y='276' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='376' y='292' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='384' y='260' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='276' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='384' y='292' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='392' y='260' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='392' y='276' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='400' y='260' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='408' y='260' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='408' y='276' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='424' y='260' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='424' y='276' fill='currentColor' style='font-size:1em'>Q</text>
<text text-anchor='middle' x='432' y='260' fill='currentColor' style='font-size:1em'>Q</text>
<text text-anchor='middle' x='432' y='276' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='440' y='260' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='440' y='276' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='448' y='276' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='456' y='260' fill='currentColor' style='font-size:1em'>J</text>
<text text-anchor='middle' x='456' y='276' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='464' y='260' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='472' y='260' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='472' y='276' fill='currentColor' style='font-size:1em'>프</text>
<text text-anchor='middle' x='480' y='260' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='480' y='276' fill='currentColor' style='font-size:1em'>로</text>
<text text-anchor='middle' x='488' y='260' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='488' y='276' fill='currentColor' style='font-size:1em'>토</text>
<text text-anchor='middle' x='496' y='276' fill='currentColor' style='font-size:1em'>콜</text>
<text text-anchor='middle' x='512' y='276' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='520' y='276' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='528' y='276' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='536' y='276' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='544' y='276' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='560' y='276' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='568' y='276' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='576' y='276' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='584' y='276' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='600' y='276' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='608' y='276' fill='currentColor' style='font-size:1em'>Q</text>
<text text-anchor='middle' x='616' y='276' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='624' y='276' fill='currentColor' style='font-size:1em'>)</text>
</g>

    </svg>
  
</div>
<h3 id="프로토콜-자동-감지">프로토콜 자동 감지</h3>
<p>들어오는 요청의 헤더만으로 프로토콜을 판별한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#6272a4">// internal/gateway/protocol.go</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> <span style="color:#50fa7b">DetectProtocol</span>(r <span style="color:#ff79c6">*</span>http.Request) (protocol <span style="color:#8be9fd">string</span>, serviceID <span style="color:#8be9fd">string</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 1. JSON 프로토콜: X-Amz-Target 헤더 존재</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> target <span style="color:#ff79c6">:=</span> r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;X-Amz-Target&#34;</span>); target <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        contentType <span style="color:#ff79c6">:=</span> r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>        proto <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">jsonProtocolFromContentType</span>(contentType)
</span></span><span style="display:flex;"><span>        service <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">serviceFromTarget</span>(target)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> proto, service
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 2. Query 프로토콜: form-encoded body에 Action= 파라미터</span>
</span></span><span style="display:flex;"><span>    contentType <span style="color:#ff79c6">:=</span> r.Header.<span style="color:#50fa7b">Get</span>(<span style="color:#f1fa8c">&#34;Content-Type&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> strings.<span style="color:#50fa7b">Contains</span>(contentType, <span style="color:#f1fa8c">&#34;application/x-www-form-urlencoded&#34;</span>) {
</span></span><span style="display:flex;"><span>        bodyBytes, err <span style="color:#ff79c6">:=</span> io.<span style="color:#50fa7b">ReadAll</span>(r.Body)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">==</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>            r.Body = io.<span style="color:#50fa7b">NopCloser</span>(bytes.<span style="color:#50fa7b">NewReader</span>(bodyBytes))
</span></span><span style="display:flex;"><span>            <span style="color:#ff79c6">if</span> strings.<span style="color:#50fa7b">Contains</span>(<span style="color:#8be9fd;font-style:italic">string</span>(bodyBytes), <span style="color:#f1fa8c">&#34;Action=&#34;</span>) {
</span></span><span style="display:flex;"><span>                service <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">serviceFromQueryRequest</span>(r, <span style="color:#8be9fd;font-style:italic">string</span>(bodyBytes))
</span></span><span style="display:flex;"><span>                <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;query&#34;</span>, service
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 3. SigV4 서명에서 REST 스타일 서비스 추출</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> svc <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">serviceFromSigV4</span>(r); svc <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;&#34;</span> <span style="color:#ff79c6">&amp;&amp;</span> svc <span style="color:#ff79c6">!=</span> <span style="color:#f1fa8c">&#34;s3&#34;</span> {
</span></span><span style="display:flex;"><span>        normalized <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">normalizeServiceID</span>(svc)
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-json&#34;</span>, normalized
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4">// 4. 기본: REST-XML (S3)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> <span style="color:#f1fa8c">&#34;rest-xml&#34;</span>, <span style="color:#f1fa8c">&#34;s3&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>감지 순서가 중요하다. <code>X-Amz-Target</code>을 먼저 확인해야 JSON 프로토콜 서비스(DynamoDB, Lambda)가 Query 프로토콜로 오인되지 않는다. SQS는 JSON과 Query 프로토콜을 모두 지원하는 특이한 서비스로, <code>X-Amz-Target</code> 헤더 유무로 구분한다.</p>
<h3 id="서비스-이름-정규화">서비스 이름 정규화</h3>
<p>게이트웨이는 100개 이상의 AWS 서비스 이름을 내부 ID로 매핑하는 광범위한 테이블을 유지한다. 특수 케이스가 많다:</p>
<ul>
<li>SES → <code>sesv2</code> (Query가 아닌 REST-JSON)</li>
<li><code>opensearch</code> vs <code>elasticsearchservice</code> (경로 기반 구분)</li>
<li>서비스 이름 정규화 (공백 제거, 소문자 변환)</li>
</ul>
<p>SDK가 서비스를 참조하는 방식에 관계없이 올바른 플러그인에 도달하도록 보장한다.</p>
<h2 id="프로토콜별-직렬화-상세">프로토콜별 직렬화 상세</h2>
<p>각 프로토콜의 직렬화 방식은 완전히 다르다. 에뮬레이터에서 가장 까다로운 부분이 바로 이 직렬화/역직렬화 계층이다. 구체적인 예시를 살펴보자.</p>
<h3 id="rest-xml-s3">REST-XML (S3)</h3>
<p>S3는 가장 복잡한 프로토콜이다. 오퍼레이션은 HTTP 메서드와 경로로 결정되며, 응답은 특정 네임스페이스 선언이 있는 유효한 XML이어야 한다.</p>
<p><strong>요청 라우팅:</strong></p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 536 89"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>?</text>
<text text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>?</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='216' y='68' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='224' y='20' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='256' y='36' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='344' y='20' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='344' y='52' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>→</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='360' y='36' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='360' y='52' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='368' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='368' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='376' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='376' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='384' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='384' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='384' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='392' y='20' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='392' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='392' y='52' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='400' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='400' y='52' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='408' y='36' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='408' y='52' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='416' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='416' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='424' y='36' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='424' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='424' y='68' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='432' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='440' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='440' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='448' y='20' fill='currentColor' style='font-size:1em'>V</text>
<text text-anchor='middle' x='448' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='448' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='456' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='472' y='68' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='480' y='68' fill='currentColor' style='font-size:1em'>다</text>
<text text-anchor='middle' x='488' y='68' fill='currentColor' style='font-size:1em'>중</text>
<text text-anchor='middle' x='504' y='68' fill='currentColor' style='font-size:1em'>객</text>
<text text-anchor='middle' x='512' y='68' fill='currentColor' style='font-size:1em'>체</text>
<text text-anchor='middle' x='520' y='68' fill='currentColor' style='font-size:1em'>)</text>
</g>

    </svg>
  
</div>
<p><strong>응답 XML 구조:</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#ff79c6">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">&lt;ListObjectsV2Output&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;Name&gt;</span>my-bucket<span style="color:#ff79c6">&lt;/Name&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;Prefix&gt;&lt;/Prefix&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;KeyCount&gt;</span>2<span style="color:#ff79c6">&lt;/KeyCount&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;MaxKeys&gt;</span>1000<span style="color:#ff79c6">&lt;/MaxKeys&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;IsTruncated&gt;</span>false<span style="color:#ff79c6">&lt;/IsTruncated&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;Contents&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&lt;Key&gt;</span>hello.txt<span style="color:#ff79c6">&lt;/Key&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&lt;LastModified&gt;</span>2026-04-19T12:00:00.000Z<span style="color:#ff79c6">&lt;/LastModified&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&lt;ETag&gt;</span>&#34;d41d8cd98f00b204e9800998ecf8427e&#34;<span style="color:#ff79c6">&lt;/ETag&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&lt;Size&gt;</span>13<span style="color:#ff79c6">&lt;/Size&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">&lt;StorageClass&gt;</span>STANDARD<span style="color:#ff79c6">&lt;/StorageClass&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">&lt;/Contents&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff79c6">&lt;/ListObjectsV2Output&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>boto3는 서비스 모델의 <code>xmlNamespace</code> 트레이트를 사용하여 이 XML을 파싱한다. 네임스페이스 선언이나 엘리먼트 순서가 틀리면, 역직렬화가 조용히 실패하거나 잘못된 값을 생성한다.</p>
<h3 id="json-1011-dynamodb-lambda">JSON 1.0/1.1 (DynamoDB, Lambda)</h3>
<p>JSON 프로토콜은 라우팅이 단순하지만 자체적인 뉘앙스가 있다:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 592 73"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>B</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>_</text>
<text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>K</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='224' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='232' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='296' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='312' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='320' y='52' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='328' y='52' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='352' y='52' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='360' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='368' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='376' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='384' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='392' y='52' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='400' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='408' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='416' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='424' y='52' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='432' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='52' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='448' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='456' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='464' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='472' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='480' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='488' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='496' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='504' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='512' y='52' fill='currentColor' style='font-size:1em'>"</text>
<text text-anchor='middle' x='520' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='536' y='52' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='544' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='552' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='560' y='52' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='568' y='52' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='576' y='52' fill='currentColor' style='font-size:1em'>}</text>
</g>

    </svg>
  
</div>
<p>JSON 1.0은 <code>application/x-amz-json-1.0</code>을 사용하고, 특정 <code>X-Amz-Target</code> 포맷을 따른다: <code>ServiceName_APIVersion.OperationName</code>. JSON 1.1은 <code>application/x-amz-json-1.1</code>과 약간 다른 타겟 포맷을 사용한다.</p>
<p>에러 응답은 특정 구조를 따른다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{<span style="color:#ff79c6">&#34;__type&#34;</span>: <span style="color:#f1fa8c">&#34;ResourceNotFoundException&#34;</span>, <span style="color:#ff79c6">&#34;message&#34;</span>: <span style="color:#f1fa8c">&#34;Requested resource not found&#34;</span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="query-iam-sts">Query (IAM, STS)</h3>
<p>Query 프로토콜은 모든 파라미터를 form-urlencoded 형식으로 인코딩한다:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 392 25"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>&amp;</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>V</text>
<text text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>5</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>&amp;</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>e</text>
</g>

    </svg>
  
</div>
<p>이 프로토콜은 몇 가지 독특한 직렬화 과제를 가지고 있다:</p>
<ul>
<li><strong>ECMAScript 날짜 포맷</strong>: 타임스탬프가 <code>20260419T120000Z</code> (하이픈과 콜론 없음)으로 인코딩되며, ISO 8601이 아니다</li>
<li><strong>평탄화 리스트</strong>: <code>member.1=Value1&amp;member.2=Value2</code> — JSON 배열이 아닌 인덱스 기반 표현</li>
<li><strong>구조화 맵 키</strong>: <code>AttributeName.1.Name=id&amp;AttributeName.1.Value=userId</code> — 맵 키도 인덱스로 인코딩</li>
<li><strong>부울린 인코딩</strong>: <code>true</code>와 <code>false</code>를 소문자 문자열로 표현</li>
</ul>
<h2 id="96-호환성의-의미">96% 호환성의 의미</h2>
<p>boto3 호환성 테스트는 실제 AWS SDK가 보내는 요청을 에뮬레이터에 재생하고, 응답이 SDK가 기대하는 구조와 일치하는지 검증한다. 이것은 단위 테스트가 아니라 <strong>와이어 프로토콜 수준의 통합 테스트</strong>다.</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 368 153"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='24' y='84' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='24' y='116' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>7</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>테</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>페</text>
<text text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'>타</text>
<text text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'>예</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>트</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>실</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'>임</text>
<text text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'>외</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>통</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>패</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>지</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>케</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>과</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>네</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>탬</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>메</text>
<text text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>N</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>프</text>
<text text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'>밀</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>시</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>션</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'>리</text>
<text text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'>지</text>
<text text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>9</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>포</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>초</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>실</text>
<text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>6</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>엣</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>맷</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>텍</text>
<text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>행</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>지</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>정</text>
<text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>미</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>밀</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>트</text>
<text text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'>정</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>%</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>케</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>세</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>도</text>
<text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>확</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>차</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>스</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'>차</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>문</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'>이</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>타</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>구</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>포</text>
<text text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'>:</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>임</text>
<text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>와</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>맷</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>존</text>
<text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'>8</text>
<text text-anchor='middle' x='176' y='116' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>불</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'>개</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>처</text>
<text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>일</text>
<text text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'>리</text>
<text text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'>치</text>
<text text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='216' y='68' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>K</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>처</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>리</text>
</g>

    </svg>
  
</div>
<p>실패한 28개의 대부분은 기능적 오류가 아니라 <strong>포맷 미세 차이</strong>다:</p>
<ol>
<li>
<p><strong>페이지네이션 (12개 실패)</strong>: boto3는 한 응답의 <code>NextToken</code>을 다음 요청의 입력으로 사용한다. 토큰 포맷이 AWS와 다르면 페이지네이션이 깨진다. 우리 토큰은 base64 인코딩 포인터인 반면, AWS는 불투명한 암호화 토큰을 사용한다. SDK는 파싱할 수 있지만, 일부 엣지 케이스에서 라운드트립이 실패한다.</p>
</li>
<li>
<p><strong>타임스탬프 (8개 실패)</strong>: AWS는 가변 정밀도의 타임스탬프를 반환한다 — 밀리초(<code>2026-04-19T12:00:00.123Z</code>)가 있고, 없는 것(<code>2026-04-19T12:00:00Z</code>)도 있다. SDK는 필드별로 특정 포맷을 기대한다. 우리 타임스탬프는 항상 밀리초 정밀도인데, 일부 필드는 소수점이 없는 것을 기대한다.</p>
</li>
<li>
<p><strong>에러 메시지 (8개 실패)</strong>: <code>The bucket you are attempting to access must be addressed using the specified endpoint.</code> 같은 에러 메시지는 AWS의 정확한 문구와 일치해야 한다. 우리 메시지는 동일한 의미를 전달하지만 다른 표현을 사용한다.</p>
</li>
</ol>
<p>이 실패들은 실제 애플리케이션 동작에 영향을 주지 않는다. 애플리케이션은 에러 메시지 텍스트가 아닌 에러 코드(예: <code>NoSuchBucket</code>)를 확인하며, 우리 에러 코드는 정확히 일치한다.</p>
<h2 id="서비스-플러그인-아키텍처">서비스 플러그인 아키텍처</h2>
<p>모든 서비스는 <code>ServicePlugin</code> 인터페이스를 구현한다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> ServicePlugin <span style="color:#8be9fd;font-style:italic">interface</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">ServiceID</span>() <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">ServiceName</span>() <span style="color:#8be9fd">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">Protocol</span>() ProtocolType
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">Init</span>(config PluginConfig) <span style="color:#8be9fd">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">Shutdown</span>(ctx context.Context) <span style="color:#8be9fd">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">HandleRequest</span>(ctx context.Context, op <span style="color:#8be9fd">string</span>, req <span style="color:#ff79c6">*</span>http.Request) (<span style="color:#ff79c6">*</span>Response, <span style="color:#8be9fd">error</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">ListResources</span>(ctx context.Context) ([]Resource, <span style="color:#8be9fd">error</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#50fa7b">GetMetrics</span>(ctx context.Context) (<span style="color:#ff79c6">*</span>ServiceMetrics, <span style="color:#8be9fd">error</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="플러그인-레지스트리">플러그인 레지스트리</h3>
<p>서비스는 중앙 레지스트리를 통해 등록된다:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">type</span> Registry <span style="color:#8be9fd;font-style:italic">struct</span> {
</span></span><span style="display:flex;"><span>    mu        sync.RWMutex
</span></span><span style="display:flex;"><span>    factories <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]PluginFactory
</span></span><span style="display:flex;"><span>    active    <span style="color:#8be9fd;font-style:italic">map</span>[<span style="color:#8be9fd">string</span>]ServicePlugin
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (r <span style="color:#ff79c6">*</span>Registry) <span style="color:#50fa7b">Register</span>(serviceID <span style="color:#8be9fd">string</span>, factory PluginFactory) {
</span></span><span style="display:flex;"><span>    r.mu.<span style="color:#50fa7b">Lock</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">defer</span> r.mu.<span style="color:#50fa7b">Unlock</span>()
</span></span><span style="display:flex;"><span>    r.factories[serviceID] = factory
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">func</span> (r <span style="color:#ff79c6">*</span>Registry) <span style="color:#50fa7b">Init</span>(serviceID <span style="color:#8be9fd">string</span>, cfg PluginConfig) (ServicePlugin, <span style="color:#8be9fd">error</span>) {
</span></span><span style="display:flex;"><span>    factory, ok <span style="color:#ff79c6">:=</span> r.factories[serviceID]
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> !ok {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>, fmt.<span style="color:#50fa7b">Errorf</span>(<span style="color:#f1fa8c">&#34;unknown service: %s&#34;</span>, serviceID)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    p <span style="color:#ff79c6">:=</span> <span style="color:#50fa7b">factory</span>(cfg)
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">if</span> err <span style="color:#ff79c6">:=</span> p.<span style="color:#50fa7b">Init</span>(cfg); err <span style="color:#ff79c6">!=</span> <span style="color:#ff79c6">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#ff79c6">return</span> <span style="color:#ff79c6">nil</span>, fmt.<span style="color:#50fa7b">Errorf</span>(<span style="color:#f1fa8c">&#34;init %s: %w&#34;</span>, serviceID, err)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    r.active[serviceID] = p
</span></span><span style="display:flex;"><span>    <span style="color:#ff79c6">return</span> p, <span style="color:#ff79c6">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>이 분리 덕분에 새 서비스를 추가하려면:</p>
<ol>
<li>코드 생성기를 실행하여 타입과 스텁 생성</li>
<li><code>ServicePlugin</code> 인터페이스 구현</li>
<li>팩토리에 등록</li>
</ol>
<p>프로토콜 처리, 직렬화, 라우팅은 자동 생성 코드가 전부 처리한다.</p>
<h2 id="실제-사용-예시-코드-변경-제로">실제 사용 예시: 코드 변경 제로</h2>
<p>DevCloud를 띄우면 기존 코드 변경 없이 엔드포인트만 바꾸면 된다:</p>
<h3 id="python-boto3">Python (boto3)</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff79c6">import</span> boto3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 프로덕션</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># s3 = boto3.client(&#39;s3&#39;, endpoint_url=&#39;https://s3.amazonaws.com&#39;)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># DevCloud 로컬</span>
</span></span><span style="display:flex;"><span>s3 <span style="color:#ff79c6">=</span> boto3<span style="color:#ff79c6">.</span>client(<span style="color:#f1fa8c">&#39;s3&#39;</span>, endpoint_url<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;http://localhost:4747&#39;</span>,
</span></span><span style="display:flex;"><span>                  aws_access_key_id<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;test&#39;</span>,
</span></span><span style="display:flex;"><span>                  aws_secret_access_key<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;test&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>s3<span style="color:#ff79c6">.</span>create_bucket(Bucket<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;my-bucket&#39;</span>)
</span></span><span style="display:flex;"><span>s3<span style="color:#ff79c6">.</span>put_object(Bucket<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;my-bucket&#39;</span>, Key<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;hello.txt&#39;</span>, Body<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">b</span><span style="color:#f1fa8c">&#39;Hello, DevCloud!&#39;</span>)
</span></span><span style="display:flex;"><span>response <span style="color:#ff79c6">=</span> s3<span style="color:#ff79c6">.</span>get_object(Bucket<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;my-bucket&#39;</span>, Key<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;hello.txt&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">print</span>(response[<span style="color:#f1fa8c">&#39;Body&#39;</span>]<span style="color:#ff79c6">.</span>read())  <span style="color:#6272a4"># b&#39;Hello, DevCloud!&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="docker">Docker</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run -p 4566:4566 skyoo2003/devcloud
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="terraform">Terraform</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">export</span> <span style="color:#8be9fd;font-style:italic">AWS_ENDPOINT_URL</span><span style="color:#ff79c6">=</span>http://localhost:4747
</span></span><span style="display:flex;"><span>terraform apply
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="aws-cli">AWS CLI</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>aws --endpoint-url http://localhost:4747 s3 ls
</span></span></code></pre></td></tr></table>
</div>
</div><p>AWS SDK를 사용하는 모든 도구가 수정 없이 동작한다. DevCloud는 AWS와 동일한 와이어 프로토콜을 구현하므로, SDK 입장에서는 진짜 AWS인지 로컬 에뮬레이터인지 구별할 수 없다.</p>
<h2 id="핵심-인사이트">핵심 인사이트</h2>
<h3 id="1-프로토콜이-복잡성의-핵심이다">1. 프로토콜이 복잡성의 핵심이다</h3>
<p>비즈니스 로직보다 직렬화/역직렬화가 구현 난이도를 결정한다. 96개 서비스에 대해 5개 프로토콜 직렬화기를 수동으로 구현하는 것은 유지보수 악몽이다. 코드 생성으로 자동화하는 것이 올바른 접근이었다.</p>
<h3 id="2-호환성은-포맷-정밀도에서-갈린다">2. 호환성은 포맷 정밀도에서 갈린다</h3>
<p>96%에서 99.9%로 가는 길은 기능 추가가 아니라 타임스탬프 포맷, 에러 메시지 문구, 페이지네이션 토큰 같은 디테일에 있다. 이 디테일은 지루하지만 필수적이다. 96%를 넘어선 각 퍼센트 포인트마다 점점 더 구체적인 포맷 매칭이 필요하다.</p>
<h3 id="3-플러그인-아키텍처로-관심사를-분리하라">3. 플러그인 아키텍처로 관심사를 분리하라</h3>
<p>프로토콜 처리와 서비스 로직을 분리하면 양쪽을 독립적으로 발전시킬 수 있다. 코드 생성 쪽에서 프로토콜 처리를 개선해도 서비스 구현에 영향이 없고, 서비스 개발자는 직렬화 세부사항을 몰라도 기능을 추가할 수 있다.</p>
<h3 id="4-sdk-관점에서-테스트하라">4. SDK 관점에서 테스트하라</h3>
<p>호환 가능한 에뮬레이터를 구축하는 가장 빠른 방법은 서버 관점이 아니라 SDK 관점에서 테스트하는 것이다. 실제 SDK 요청을 녹화하고, 에뮬레이터에 재생한 뒤 응답을 비교한다. 서버 중심 테스트가 놓치는 포맷 이슈를 잡을 수 있다.</p>
<h2 id="마치며">마치며</h2>
<p>96% boto3 호환성은 로컬 개발 환경에서 AWS SDK를 그대로 사용할 수 있다는 것을 의미한다. CI/CD 파이프라인의 클라우드 비용을 없애고, 오프라인 개발(비행기, 제한된 네트워크 환경)을 가능하게 하며, 새 팀원의 온보딩을 <code>docker run</code> 하나로 줄일 수 있다.</p>
<p>남은 4%는 포맷 정밀도 문제다 — 페이지네이션 토큰, 타임스탬프 포맷, 에러 메시지 문구. 이것도 매주 Smithy 모델 동기와 함께 점진적으로 개선되고 있다.</p>
<p>전체 소스 코드는 <a href="https://github.com/skyoo2003/devcloud">github.com/skyoo2003/devcloud</a>에서 확인할 수 있다.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
