<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://soongwonjun.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://soongwonjun.github.io/" rel="alternate" type="text/html" /><updated>2025-04-24T14:37:14+00:00</updated><id>https://soongwonjun.github.io/feed.xml</id><title type="html">Hive</title><subtitle>Post anything</subtitle><author><name>Soongwon Jun</name></author><entry><title type="html">Cassandra와 MongoDB의 인덱스에 대해서</title><link href="https://soongwonjun.github.io/cs/2025/04/19/Cassandra_N_Mongodb_Index.html" rel="alternate" type="text/html" title="Cassandra와 MongoDB의 인덱스에 대해서" /><published>2025-04-19T16:00:00+00:00</published><updated>2025-04-19T16:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2025/04/19/Cassandra_N_Mongodb_Index</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2025/04/19/Cassandra_N_Mongodb_Index.html"><![CDATA[<h1 id="서론에-비교를-담아서">서론에 비교를 담아서</h1>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>MongoDB</th>
      <th>Cassandra</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>CAP 특성</td>
      <td>CP</td>
      <td>AP</td>
    </tr>
    <tr>
      <td>인덱스 구조</td>
      <td>B-Tree</td>
      <td>LSM Tree + SSTable</td>
    </tr>
    <tr>
      <td>인덱스 정렬 단위</td>
      <td>전체 Collection(Global level)</td>
      <td>SSTable</td>
    </tr>
    <tr>
      <td>데이터 구조</td>
      <td>BSON</td>
      <td>Column-Based(Wide-column Store)</td>
    </tr>
    <tr>
      <td>데이터 분산</td>
      <td>Sharding</td>
      <td>Partition Key</td>
    </tr>
    <tr>
      <td>데이터 저장</td>
      <td>디스크 내에서 index를 기준으로 저장</td>
      <td>SSTable 내에서 Index를 기준으로 저장</td>
    </tr>
    <tr>
      <td>데이터 특성</td>
      <td>Mutable</td>
      <td>Immutable</td>
    </tr>
    <tr>
      <td>Transaction</td>
      <td>Document에 대한 Transaction 있음</td>
      <td>일부만 지원*</td>
    </tr>
    <tr>
      <td>Insert</td>
      <td>느림(위치 찾아서 저장 후 재정렬)</td>
      <td>빠름(MemTable에 저장 후 머지)</td>
    </tr>
    <tr>
      <td>Update</td>
      <td>기존 데이터 변경</td>
      <td>새 Row가 생성됨</td>
    </tr>
    <tr>
      <td>Select</td>
      <td>다양한 필터 조건(Equal, Range)에 대응되어 있음</td>
      <td>Parition Key 기반은 빠름, SAI는 비교적 느림</td>
    </tr>
  </tbody>
</table>

<ul>
  <li>Casssandra의 트랜잭션은 단일 row에 대한 처리 혹은 단일 파티션 내의 Batch정도만을 지원함</li>
</ul>

<h1 id="mongodb의-index에-대해서">MongoDB의 Index에 대해서</h1>

<p>MongoDB는 다양한 인덱스를 제공한다.</p>
<ul>
  <li>Default Index</li>
  <li>Single Field Index</li>
  <li>Compound Index</li>
  <li>Multikey Index</li>
  <li>Geospatial Index</li>
  <li>Text Index</li>
  <li>Hashed Index</li>
  <li>Clustered Index</li>
</ul>

<p>MongoDB에서의 모든 인덱스는 별도의 B-Tree로 구성되어 있으며, 데이터와는 별도의 자료구조로 저장된다.</p>

<h2 id="default-index">Default Index</h2>

<p><code class="language-plaintext highlighter-rouge">_id</code> 필드 인덱스. 모든 document에 생성되며 제거가 불가능하다. 이 <code class="language-plaintext highlighter-rouge">_id</code> 컬럼은 <code class="language-plaintext highlighter-rouge">ObjectId</code>라는 BSON이며, timestamp, 컴퓨터ID, 프로세서ID를 사용하여 고유함을 유지한다.</p>

<h2 id="sinle-field-index">Sinle Field Index</h2>

<p>단일 field를 사용하여 생성한 인덱스</p>

<h2 id="compound-index">Compound Index</h2>

<p>다수의 field를 사용하여 생성한 인덱스</p>

<h2 id="multikey-index">Multikey Index</h2>

<p>배열에 적용되는 인덱스. MongoDB에서는 document에 배열이 생성되면 자동으로 만들어진다.</p>

<h2 id="geospatial-index">Geospatial Index</h2>

<p>지리 공간을 처리하기 위해 특별히 만들어진 인덱스. GeoJSON 혹은 좌표(위도, 경도의 쌍) 정보를 저장하는 특별한 자료구조를 사용하여 기하학 계산을 지원한다.</p>

<h2 id="text-index">Text Index</h2>

<p>텍스트 필드를 위한 인덱스</p>

<h2 id="hashed-index">Hashed Index</h2>

<p>샤딩을 위한 해시된 값을 사용해 만드는 인덱스. <code class="language-plaintext highlighter-rouge">ObjectId</code> 혹은 <code class="language-plaintext highlighter-rouge">timestamp</code>같은 단순한 값을 사용하며, multikey는 지원하지 않는다. <code class="language-plaintext highlighter-rouge">convertShardKeyToHashed()</code> 함수를 사용해서 실제 해시된 값을 찾아낼 수 있다.<br />
데이터를 해시된 데이터로 저장하므로 데이터 위치 특정이 매우 쉽다. 때문에 <code class="language-plaintext highlighter-rouge">Equal</code>검색에 매우 강력하다. 하지만 이 Hashed Index는 정렬되어 있지 않기에 범위 검색에는 적합하지 않다. 무작위로 분포되는 이유는 가능한 한 고르게 분포 되어야 HotSpot을 방지할 수 있기 때문이다.</p>

<h2 id="clustered-index">Clustered Index</h2>

<p>MongoDB가 디스크에 데이터를 저장할 때 인덱스 순서대로 저장될 수 있도록 하는 인덱스. Mongodb가 구성하는 Index의 순서 그대로 디스크에 저장되기 때문에 물리 디스크에서 데이터를 가져올 때 상당히 빠른 처리가 가능하다. 단 하나의 document에서 하나의 clustered index만 지정할 수 있다.<br />
이 Clustered Index는 Sharding과는 다르다. Sharding은 노드의 분산을 의미하지만, Clustered Index는 샤딩된 후 개별 노드에 대해 적용된다. 때문에 Clustered Index를 쓰면서도 정렬된 Hashed Index를 사용하기 위해서는 Ranged Sharding을 사용해야 하는데 이는 HotSpot으로 인한 resharding 문제가 있어 성능에 이슈가 있을 수 있다.</p>

<h1 id="cassandra의-index-에-대해서">Cassandra의 Index 에 대해서</h1>

<p>Cassandra의 인덱스는 크게 3가지가 있다.</p>

<ul>
  <li>Primary Index
    <ul>
      <li>Cassandra의 Partition Key. 가장 빠르고 효과적임</li>
    </ul>
  </li>
  <li>Storage-attached indexing (SAI)
    <ul>
      <li>non-partition column을 위한 인덱스</li>
      <li>SSTable을 사용해서 데이터를 저장함</li>
      <li>cassandra 5.0부터 지원</li>
    </ul>
  </li>
  <li>Secondary indexing (2i)
    <ul>
      <li>Cassandra의 각 노드에 숨겨진 테이블로서 존재하는 로컬 인덱스</li>
      <li>Primary Index(Partition Key)와 함께 써야만 함</li>
      <li>성능상 이슈가 발생히가에 cassandra에서는 이 인덱스보다는 SAI를 쓰도록 가이드하고 있음</li>
    </ul>
  </li>
</ul>

<p>기본적으로 Cassandra는 Partition Key를 기반으로 데이터를 저장하므로 이 정보를 가진 Primary Key가 가장 빠르다. 하지만 5.x 부터 지원하는 SAI는 유연성에서 Primary Key를 대체할 수 있는 경우가 있다.<br />
Primary Index는 Hash로 저장되어 있고, 이 값은 equal/in에 대해 상당히 강력한 구조이다. 하지만 Between, GTE와 같은 범위 구조에 대해서는 사용할 수 없기 때문에 이러한 부분을 SAI가 보완해준다.</p>

<h2 id="primary-index">Primary Index</h2>

<p>Key 기반 인덱스
테이블에는 하나의 row를 식별하기 위해 1개의 Primary Key를 정의해야 하고, 보통은 1개 이상의 컬럼을 사용한다.
또한 Cassandra는 row 단위로 데이터를 찾는것이 아닌 partition 단위로 데이터를 찾는 구조이다.
때문에 Primary Key는 Partition Key와 Clustering Column으로 나뉘어 진다.</p>

<ul>
  <li>Partition Key
    <ul>
      <li>테이블에 지정하는 컬럼. 어떤 Partition을 사용할지를 결정한다. 이 Partition은 하나의 Node에 모이게 된다.</li>
      <li>즉 어느 node에 저장할지를 결정한다는 의미이기도 하다.</li>
    </ul>
  </li>
  <li>Clustering Column
    <ul>
      <li>Primary key를 정의할 때 Partition Key 뒤에 이어지는 컬럼으로, Partition 내의 정렬된 순서를 지정한다.</li>
    </ul>
  </li>
</ul>

<p>Cassandra는 이 데이터를 Hash로 만든 정렬된 데이터로 저장한다. 때문에 Primary 키를 사용하면 데이터가 저장된 노드를 바로 식별하고, 정렬된 범위 안에서 즉각적으로 데이터를 찾아내므로 굉장히 빠르다. 때문에 <code class="language-plaintext highlighter-rouge">Equal</code>/<code class="language-plaintext highlighter-rouge">In</code> 검색같이 위치가 특정되는 검색에 매우 강하다.</p>

<h3 id="partition">Partition</h3>

<p>카산드라는 데이터를 Partition Key 단위로 묶어두고, 이 데이터 묶음을 모두 같은 노드에 저장한다.
카산드라는 분산 시스템이여서, 데이터가 어디에 저장되는지를 알아야 데이터를 올바르게 찾고, 저장할 수 있다.
그리고 Partition Key를 통해 노드를 식별하면 효율적이고, 빠르다.
때문에 Partition Key의 정의에 따라 카산드라의 전체 효율이 결정된다.</p>

<h3 id="primary-index의-예제">Primary Index의 예제</h3>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">users</span> <span class="p">(</span>
  <span class="n">user_id</span> <span class="n">UUID</span><span class="p">,</span>
  <span class="n">created_at</span> <span class="nb">TIMESTAMP</span><span class="p">,</span>
  <span class="n">name</span> <span class="nb">TEXT</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">created_at</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<ul>
  <li>3개의 컬럼을 가진 users 테이블</li>
  <li>user_id는 Partition Key</li>
  <li>created_at은 Clustering Column</li>
</ul>

<p>데이터는 아래와 같이 저장된다</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>partition[userid=1001]
  └─ [created_at=2023-01-01] name = "Alice"
  └─ [created_at=2023-01-02] name = "Bob"
  └─ [created_at=2023-01-03] name = "Charlie"
partition[userid=1002]
  └─ [created_at=2023-01-01] name = "David"
  └─ [created_at=2023-01-02] name = "Eve"
</code></pre></div></div>

<p>user id로 node와 partition이 결정되고, 그 안에서 created_at 으로 정렬되어 있음
하지만 created_at 만으로 검색하는 경우, partition key를 알 수 없기에 full-scan을 해야한다.</p>

<h2 id="storage-attached-indexing-sai">Storage-attached indexing (SAI)</h2>

<ul>
  <li>분산 환경을 위한 높은 확장성을 가진 인덱스</li>
  <li>컬럼별로 따로 데이터를 저장하는 방식을 사용한다.</li>
  <li>MemTable을 사용하여 SSTable을 인덱싱하고, 데이터가 쓰여질 때 이 MemTable을 갱신하여 인덱스를 유지한다.</li>
  <li>read 시 사용과는 과정
    <ul>
      <li>cassandra는 query가 들어오면 적용 가능한 index를 검색한다.</li>
      <li>먼저 primary key를 사용해 partition을 찾고, partition에 해당되는 MemTable을, 결과가 없다면 sstable을 조회한다.</li>
      <li>이 작업은 병렬로 처리되고, 결과를 취합하여 response를 만들어 결과를 떨군다.</li>
    </ul>
  </li>
  <li>write의 과정
    <ul>
      <li>데이터 변경사항이 먼저 MemTable에 반영된다.</li>
      <li>MemTable이 flush될 때 sstable에 반영된다.</li>
      <li>sstable에 반영될 때 컬럼 단위 index가 생성된다. 이 인덱스가 SAI이다.</li>
    </ul>
  </li>
  <li>범위 검색이 가능한 구조
    <ul>
      <li>Primary Index는 <code class="language-plaintext highlighter-rouge">Equal</code>/<code class="language-plaintext highlighter-rouge">IN</code>이 아니면 제대로된 인덱스 검색이 불가능하다. 하지만 SAI는 유연한 구조를 위해 만들어졌기 때문에 <code class="language-plaintext highlighter-rouge">BETWEEN</code>같은 조건을 사용한 범위 필터링이 가능하다.</li>
    </ul>
  </li>
</ul>

<h2 id="secondary-indexing">Secondary Indexing</h2>

<p>Partition Key가 아닌 다른 컬럼에 인덱스를 만들어서 사용하는 방식
간단하게 인덱스를 구성할 수 있으나, 모든 노드에 개별 테이블로서 존재하기에 성능 이슈가 발생한다. 노드를 특정할 수 없기에 모든 노드를 뒤져야해서 full-scan 처럼 작동하기 때문이다.
매우 작은, 혹은 단일 노드에서는 사용할 수도 있으나, 클러스터 환경이나 큰 데이터를 다루어야 한다면 사용하지 않는 편이 좋다.</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="k">ON</span> <span class="n">users</span><span class="p">(</span><span class="n">name</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="번외">번외</h2>

<h3 id="sharding이-뭘까">Sharding이 뭘까?</h3>

<p>데이터를 특정 기준(Hashed된 키)를 사용하여 분산하여 데이터베이스의 수평적 확장(Scale-out/Horizontal partition)을 가능케 한다.</p>

<ul>
  <li>장점
    <ul>
      <li>읽기/쓰기 처리량 증가</li>
      <li>매우 쉬운 저장용량 증가</li>
      <li>고가용성. 샤딩을 구현하기 위해서는 데이터의 분산 및 복제를 전제로 처리되기 때문에 고가용성이 보장된다.</li>
    </ul>
  </li>
  <li>단점
    <ul>
      <li>데이터 조회의 오버헤드. 데이터 조회 시 마다 이를 조율할 수 있는 기능, 데이터를 취합할 수 있는 기능이 수행되어야 한다.</li>
      <li>관리의 복잡성. 관리 대상의 증가, 그리고 데이터의 분산 및 복제를 위한 처리, 이를 위한 백업 플랜 등 많은 기능이 요구된다.</li>
    </ul>
  </li>
</ul>

<h3 id="lsm-log-structured-merge-tree-memtable-sstable이-뭘까">LSM (Log Structured Merge Tree), MemTable, SSTable이 뭘까?</h3>

<p>key-value 쌍을 저장장치에 기록하기 위한 자료구조. 쓰기 성능에 최적화되어 있다. 메모리와 디스크 모두 쓰기 최적화를 위해 데이터를 메모리 -&gt; 디스크 순으로 저장하는 과정을 거치며, 이 과정에 있어 불필요한 데이터(deprecated된 데이터)는 디스크에 반영하지 않는다. 때문에 항상 메모리와 디스크의 데이터가 같다는 보장이 없다.<br />
LSM 은 MemTable, CommitLog, SSTable이라는 3개의 구성 요소를 가진다.</p>

<h3 id="memtable이-뭘까">MemTable이 뭘까?</h3>
<ul>
  <li>cassandra에서 데이터를 임시 저장하기 위한 메모리 내의 가상 테이블</li>
  <li>MemTable은 임시 테이블이어서, 이 데이터가 flush 되기 전에 서버가 죽으면 MemTable의 데이터는 소실된다.</li>
  <li>하지만 MemTable에 저장될 때 commitlog에 기록되기 때문에 이를 사용해서 데이터를 복구한다.</li>
  <li>데이터가 기록되는 과정
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user - (insert / update) -&gt; cassandra
                           └─ write commitlog (disk)
                           └─ write MemTable             - (flush) -&gt; sstable
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="sstable-이-뭘까">SSTable 이 뭘까?</h3>
<ul>
  <li>Sorted String Table의 약자</li>
  <li>ScyllaDB, Cassandra같은 NoSQL DB에서 사용되는 데이터 저장을 위한 파일 포멧</li>
  <li>빠른 읽기를 위해 정의된 순서대로 데이터를 정렬해서 저장하며, 이 데이터들은 불변 데이터이다.</li>
  <li>새로이 갱신되는 데이터는 새로운 MemTable에 저장되고, flush될 때 새로운 SSTable로 저장된다.</li>
</ul>

<h3 id="왜-cassandra는-sstable이-여러개-생길까">왜 Cassandra는 SSTable이 여러개 생길까?</h3>

<p>이는 카산드라의 데이터 저장 방식에 기인한다.</p>
<ul>
  <li>SSTable은 immutable한 구조로, 한번 생성되면 수정할 수 없다.</li>
  <li>새로 업데이트하는 내용도 계속 추가가 될 뿐, 기존 데이터가 바뀌지는 않는다.</li>
  <li>SSTable은 MemTable이 flush될 때 생성되는데, 이 타이밍에 따라 여러개의 SSTable이 생성될 수 있다.</li>
  <li>insert가 많아도 동일 MemTable에 쌓이면 1개의 SSTable이 생성되겠지만, 여러개의 MemTable이 사용되기에 꼭 1개만 생성되지는 않는다.</li>
  <li>한번 MemTable이 flush 된 이후에 기존 데이터가 업데이트 되면 다른 MemTable에 데이터가 저장되기에 새 SSTable이 생성된다.</li>
</ul>

<p>시나리오는 아래와 같다.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">users</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">"alpha"</span><span class="p">,</span> <span class="mi">30</span><span class="p">);</span>   <span class="c1">-- MemTable 1</span>

<span class="c1">-- MemTable full 에 의해 flush → SSTable A 생성</span>

<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">users</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nv">"beta"</span><span class="p">,</span> <span class="mi">31</span><span class="p">);</span>    <span class="c1">-- MemTable 2</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">users</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="nv">"challie"</span><span class="p">,</span> <span class="mi">22</span><span class="p">);</span> <span class="c1">-- MemTable 2</span>

<span class="c1">-- MemTable flush → SSTable B 생성</span>
</code></pre></div></div>

<p>이러한 문제로 인하여 카산드라는 <code class="language-plaintext highlighter-rouge">compaction</code>이라는 과정을 통해 여러개의 SSTable을 하나로 합쳐서 관리한다.</p>
<ul>
  <li>compaction은 여러개의 SSTable의 데이터를 하나의 완전한 row로 만드는 작업</li>
  <li>partition key를 기준으로 정렬 및 병합을 진행하여 1개의 SSTable을 생성한다.</li>
  <li>오래된 데이터를 담은 SSTable은 진행중인 읽기 과정이 끝나면 삭제된다.</li>
</ul>

<h3 id="lsm-tree-vs-b-tree를-비교해봅시다">LSM Tree vs B-Tree를 비교해봅시다.</h3>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>LSM Tree</th>
      <th>B-Tree</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>설계 목적</td>
      <td>쓰기 성능 최적화</td>
      <td>읽기 성능 최적화</td>
    </tr>
    <tr>
      <td>트리 구조</td>
      <td>다단계 파일 + 병합(compaction) 필요</td>
      <td>균형 트리</td>
    </tr>
    <tr>
      <td>읽기 구조</td>
      <td>Memtable 먼저 읽은 후 여러 SSTable을 병합 읽기</td>
      <td>B-Tree 탐색 후 디스크에서 읽기</td>
    </tr>
    <tr>
      <td>쓰기 구조</td>
      <td>메모리에 작성 후 디스크로 옮겨감</td>
      <td>디스크에 바로 쓰며 노드 분할/병합 수행</td>
    </tr>
    <tr>
      <td>삭제 구조</td>
      <td>메모리에 삭제마크 후 Compaction 시 삭제</td>
      <td>직접 해당 노드에서 키 제거</td>
    </tr>
    <tr>
      <td>업데이트 방식</td>
      <td>없음. 추가만 가능</td>
      <td>직접 수정 가능</td>
    </tr>
    <tr>
      <td>정렬 유지 방식</td>
      <td>flush 및 병합 시 정렬된 SSTable 생성</td>
      <td>트리 내에서 정렬된 상태 유지</td>
    </tr>
  </tbody>
</table>

<h1 id="참고">참고</h1>
<ul>
  <li><a href="https://cassandra.apache.org/doc/5.0/cassandra/developing/cql/indexing/indexing-concepts.html">Cassandra, Index에 대해서</a></li>
  <li><a href="https://www.scylladb.com/glossary/sstable/">ScyllaDB, SSTable에 대해서</a></li>
  <li><a href="https://www.scylladb.com/glossary/log-structured-merge-tree/">ScyllaDB, LSM Tree에 대해서</a></li>
  <li><a href="https://www.mongodb.com/ko-kr/docs/manual/indexes/">MongoDB, Index에 대해서</a></li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="dbms" /><summary type="html"><![CDATA[서론에 비교를 담아서]]></summary></entry><entry><title type="html">명령형 프로그래밍과 선언형 프로그래밍에 대한 정리</title><link href="https://soongwonjun.github.io/cs/2025/04/19/imperative_vs_declarative.html" rel="alternate" type="text/html" title="명령형 프로그래밍과 선언형 프로그래밍에 대한 정리" /><published>2025-04-19T16:00:00+00:00</published><updated>2025-04-19T16:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2025/04/19/imperative_vs_declarative</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2025/04/19/imperative_vs_declarative.html"><![CDATA[<p>조금 오래되었지만, 일전에 프로그램의 구조적 방법론에 대해 작성한 적이 있다. 이번에는 프로그래밍을 작성하는 스타일의 관점에 대한 정리를 해본다. 이번 주제는 <code class="language-plaintext highlighter-rouge">명령형 프로그래밍 vs 선언형 프로그래밍</code> 이다.<br />
코드는 실행될 때에는 기계어로 번역되어 명령형처럼 수행된다. 하지만 이 기계어는 프로그래머가 읽고 쓰기에는 매우 어렵다. 때문에 우리는 사람이 이해할 수 있는 방식과 언어로 프로그램을 작성하고 이를 기계어로 번역하여 실행한다. 여기서 사람이 작성하는 프로그램을 어떻게 생각하고 풀어내어 작성하는가에 대한 2가지 방법에 대해 정리해 본다.</p>

<h1 id="명령형-프로그래밍imperative-programming">명령형 프로그래밍(Imperative Programming)</h1>

<p>프로그램의 상태를 변경시키는 것을 구문 관점에서 써내려가는 프로그래밍을 의미힌다.
이를 위한 표현식은 매우 간단하며, 사람이 읽기에도 매우 편하다.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fun</span> <span class="nx">sumAll</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">res</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span><span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">res</span> <span class="o">+=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 예제는 전형적인 명령형 스타일의 프로그램 코드이다. 함수 sumAll에서는 arr의 길이만큼 반복(for 루프) 하도록 한다. 매 반복마다 누적 변수 res에 element를 더하도록 한다. 여기서 정의된 상태는 <code class="language-plaintext highlighter-rouge">현재 반복의 지점</code> 과 <code class="language-plaintext highlighter-rouge">누적변수 res</code>의 값으로 볼 수 있다.
이 프로그램은 구체적으로 <code class="language-plaintext highlighter-rouge">어떻게</code> 하도록 정의한다.</p>

<h1 id="선언형-프로그래밍declarative-programming">선언형 프로그래밍(Declarative Programming)</h1>

<p>목적(무엇을 해야하는지)에 대해 기술하고 이를 실행시킬 수 있도록 써내려가는 프로그래밍을 의미한다. 이 목적을 위해 함수의 흐름을 정의하며,</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fun</span> <span class="nx">sumAll</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 예제는 명령형 프로그램 코드와 같은 역할을 수행하지만, 선언형 스타일로 작성되어 있다.</p>

<p>함수 sumArr는 arr에 reduce 연산을 적용하여 모든 값을 합한 결과값을 반환한다.
reduce는 배열의 각 요소에 주어진 함수를 적용하여 하나의 결과값을 만들어내는 고차 함수이다.
reduce의 콜백 함수는 두 개의 인자를 더한 값을 반환하며, 초기값은 0이다.</p>

<p>이 함수를 설명하기 위해서만 명령형에 비해 많은 내용이 필요하다.
하지만 선언형 프로그래밍에서는 <code class="language-plaintext highlighter-rouge">추상화된 사고</code>과정이 필요하다.
선언형은 <code class="language-plaintext highlighter-rouge">결과를 어떻게 만들 것인가</code>이 아닌, <code class="language-plaintext highlighter-rouge">어떤 결과를 만들어야 하는가</code>에서 작성하는 프로그래밍 기법이기 때문이다. 즉 <code class="language-plaintext highlighter-rouge">과정</code> 보다는 <code class="language-plaintext highlighter-rouge">목적</code>을 의식하여야 한다.</p>

<ul>
  <li>명령형에서는 <code class="language-plaintext highlighter-rouge">모든 값을 합한 결과</code>를 만들기 위해 for 루프를 이용한 덧셈 연산, 즉 과정에 초점이 맞추어져 있다.</li>
  <li>선언형에서는 <code class="language-plaintext highlighter-rouge">모든 값을 합한다</code> 라는 목적을 맞추기 위해 고차 함수를 사용한다.</li>
</ul>

<h2 id="번외-명령형은-선언형으로-만들-수-있을까">번외. 명령형은 선언형으로 만들 수 있을까?</h2>

<p>가능하다. 우리가 작성한 코드들은 기계가 이해할 수 있는 언어로 변환된 후 명령형으로서 작동한다. 여기서 쓰고 있는 명령형, 선언형은 프로그래머가 프로그램을 작성하는 방법론에 대한 부분이기 때문이다.</p>

<p>아래의 <strong>선언형</strong> 프로그래밍의 예제를 보자</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">Array</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">reduce</span><span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="nx">initValue</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// 실제로는 native code가 들어가 있음</span>
  <span class="kd">const</span> <span class="nx">len</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">res</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">&lt;</span><span class="nx">len</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">res</span> <span class="o">+=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">res</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">fun</span> <span class="nx">sumAll</span><span class="p">(</span><span class="nx">arr</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">arr</span><span class="p">.</span><span class="nx">reduce</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">sumAll</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">])</span>
</code></pre></div></div>

<p>잘 보면 Array의 프로토타입으로 reduce가 명령형으로 작성되어 있다. 그리고 sumAll은 선언형으로 작성되어 있다. 이 프로그래밍은 sumAll을 호출하는데, 인자는 1~5까지의 숫자가 들어있는 Array이다. 명령형과 선언형이 섞여 있는데 왜 선언형이라고 이야기 할 수 있을까?<br />
호출하는 입장에서 보았을 때, 우리는 sumAll을 정의하고, 그 안에서 선언형으로 작성하였다. 그리고 명령형으로 작성된 reduce의 모든 내용을 <code class="language-plaintext highlighter-rouge">Array.prototype.reduce</code>로 추상화해두었다. 결국 sumAll에서는 <code class="language-plaintext highlighter-rouge">배열의 모든 인자의 합을 계산해줘</code> 라는 <code class="language-plaintext highlighter-rouge">목적</code>만이 명시되어 있고 그에 따라서 작성되어 있고, 다른 부분들은 추상화되어 외부에서는 보이지 않는다. 그리하여 이 코드는 선언형으로 불 수 있다.</p>

<h1 id="프로그램-패러다임에-대한-토막글">프로그램 패러다임에 대한 토막글</h1>

<p><a href="/cs/2022/03/14/FP_vs_OOP.html">함수형 vs 객체지향형</a><br />
<a href="/cs/2025/04/19/imperative_vs_declarative.html">명령형 vs 선언형</a><br />
<a href="/cs/2025/04/19/synchronous_vs_asynchronous.html">동기 vs 비동기 vs 반응형</a></p>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="article" /><summary type="html"><![CDATA[조금 오래되었지만, 일전에 프로그램의 구조적 방법론에 대해 작성한 적이 있다. 이번에는 프로그래밍을 작성하는 스타일의 관점에 대한 정리를 해본다. 이번 주제는 명령형 프로그래밍 vs 선언형 프로그래밍 이다. 코드는 실행될 때에는 기계어로 번역되어 명령형처럼 수행된다. 하지만 이 기계어는 프로그래머가 읽고 쓰기에는 매우 어렵다. 때문에 우리는 사람이 이해할 수 있는 방식과 언어로 프로그램을 작성하고 이를 기계어로 번역하여 실행한다. 여기서 사람이 작성하는 프로그램을 어떻게 생각하고 풀어내어 작성하는가에 대한 2가지 방법에 대해 정리해 본다.]]></summary></entry><entry><title type="html">동기형 프로그램과 비동기형, 그리고 반응형 프로그램에 대해서</title><link href="https://soongwonjun.github.io/cs/2025/04/19/synchronous_vs_asynchronous.html" rel="alternate" type="text/html" title="동기형 프로그램과 비동기형, 그리고 반응형 프로그램에 대해서" /><published>2025-04-19T16:00:00+00:00</published><updated>2025-04-19T16:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2025/04/19/synchronous_vs_asynchronous</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2025/04/19/synchronous_vs_asynchronous.html"><![CDATA[<p>다시금 프로그램의 이론에 대해서 정리하고 포스트를 남겨본다. 프로그램의 구조적인 방법론(FP/OOP)와 프로그램의 스타일(명령형/선언형)에 이어 이번에는 프로그램의 실행 방법에 대한 비교 및 정리를 해보려 한다. 바로 <code class="language-plaintext highlighter-rouge">동기형 vs 비동기형 프로그램</code>이다. 한발 더 나아가 지금은 많이 사용하고 있는 반응형 프로그램에 대해서도 같이 정리해보려 한다.</p>

<h1 id="동기형-프로그램-synchronous-program">동기형 프로그램 (Synchronous Program)</h1>

<p>하나의 작업이 시작되면 그 작업이 끝날 때 까지 다른 작업을 처리하지 않는다.<br />
작업이 호출되는 순간 처리가 시작되며, 모든 처리는 순차적으로 진행되며, caller는 함수를 호출하는 시점부터 종료 때 까지 호출한 함수의 작업이 끝날 때 까지 대기한다. 이를 blocking이라고 한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>caller - (call) -&gt; callee
                     |
                  (process)
                     |
caller &lt;- (return) --┘
</code></pre></div></div>

<p>아래와 같은 동기형 프로그램을 보자</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">a</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print A-1`</span><span class="p">);</span>
  <span class="nx">b</span><span class="p">();</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print A-2`</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">b</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print B`</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">a</span><span class="p">()</span>
</code></pre></div></div>

<p>당연하게도 출력 순서는 아래와 같다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>print A-1
print B
print A-2
</code></pre></div></div>

<p>실행 순서가 완벽하게 정해져 있으며, 눈으로 따라가며 흐름을 파악하기도 매우 쉽다. 직관성이 매우 높기 때문에 분석, 디버깅, 동작과 실행 결과를 예측하는 것도 매우 쉽다.</p>

<h1 id="비동기형-프로그램-asynchronous-program">비동기형 프로그램 (Asynchronous Program)</h1>

<p>하나의 작업이 다른 작업을 실행할 때 자신이 아닌 다른 실행자에게 위임하고 자신은 자신의 작업을 계속하는 프로그램을 의미한다.<br />
caller는 함수를 호출 하고 나서 처리 결과를 기다리지 않고 다시 자신의 작업을 실행한다. 이것을 non-blocking이라고 한다. 또한 실행했던 작업의 결과는 콜백 형태로 나중에 처리하게 된다. 물론 이 콜백을 처리하지 않아도 호출된 프로그램은 마지막까지 잘 처리된다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>caller - (call) -&gt; callee
   |                 |
(process)          (process)
   |                 |
   |               (callback) -&gt; (void / caller)
(process)
   |       
(terminate)
</code></pre></div></div>

<p>예를 들어 아래와 같은 비동기 프로그램을 작성했다고 하자.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">a</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print A-1`</span><span class="p">);</span>
  <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">b</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print A-2`</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">b</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`print B`</span><span class="p">);</span>
<span class="p">}</span>

<span class="nx">a</span><span class="p">()</span>
</code></pre></div></div>

<p>여기서 보장되는건 <code class="language-plaintext highlighter-rouge">print A-1</code>이 가장 먼저 출력된다는 것이다. 그리고 두 번째로 찍힐 문자열은 실행되는 환경과 타이밍에 따라 다르다. 비동기로 실행되는 경우 두 개의 프로그램이 보통 병렬적으로 처리될 수 있다. 때문에 컴퓨터의 처리에 따라 <code class="language-plaintext highlighter-rouge">print A-2</code>가 먼저 나올 수도 있고, <code class="language-plaintext highlighter-rouge">print B</code>가 먼저 나올 수 있다.<br />
하지만 본 예제는 NodeJS로 작성되었고, NodeJS의 비동기 프로그램은 아래와 같이 동작하므로, 결과를 예상할 수 있다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">a()</code> 함수가 이벤트루프의 큐에 등록된다.</li>
  <li>이벤트루프가 큐에 등록된 함수 <code class="language-plaintext highlighter-rouge">a()</code>를 꺼내 처리를 시작한다.</li>
  <li><code class="language-plaintext highlighter-rouge">a()</code> 함수가 실행되는 중에 <code class="language-plaintext highlighter-rouge">b()</code> 함수를 이벤트루프의 큐에 등록된다.</li>
  <li><code class="language-plaintext highlighter-rouge">a()</code> 함수의 마지막 라인까지 실행된 후 루프 1회가 종료된다.</li>
  <li>새 루프가 시작되면 현재 큐에 등록되어 있는 함수를 불러와 처리한다. <code class="language-plaintext highlighter-rouge">b()</code>가 등록되었으므로 이어서 <code class="language-plaintext highlighter-rouge">b()</code>의 내용을 처리한다.</li>
</ul>

<p>따라서 출력 결과는 아래와 같다</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>print A-1
print A-2
print B
</code></pre></div></div>

<p>여기서 중요한 점은 <code class="language-plaintext highlighter-rouge">a()</code> 함수가 실행될 때 <code class="language-plaintext highlighter-rouge">b()</code>함수를 실행하지 않고 예약만 한다는 점과, <code class="language-plaintext highlighter-rouge">a()</code> 함수에서 <code class="language-plaintext highlighter-rouge">b()</code> 함수를 호출함에도 <code class="language-plaintext highlighter-rouge">b()</code> 함수의 처리 결과를 기다리지 않고 <code class="language-plaintext highlighter-rouge">a()</code> 함수의 흐름을 중단시키지 않고 끝까지 실행한다는 점이다.<br />
이는 비동기 프로그램은 요청과 실행 사이가 완전히 분리되어 있다는 점이며, 이로 인하여 실행 순서가 완전히 달라질 수 있다는 점이다. 각 포인트마다 실행 순서와 결과가 다르기에 분석 및 디버깅도 동기형 프로그램에 비해 난도가 높다.</p>

<h2 id="oop에서-비동기-프로그램이-어려운-이유">OOP에서 비동기 프로그램이 어려운 이유</h2>
<ul>
  <li>OOP는 객체의 상태에 집중하며 객체에 정의된 상태에 따른 동작을 프로그램의 흐름을 작성한다.</li>
  <li>비동기 프로그램은 프로그램이 흐르는 순서를 예상하기가 어렵다. 잠깐의 시간 차이만으로도 객체의 상태가 변하기 때문에 같은 실행에 대해 같은 결과를 보장할 수가 없다. 동작이 실행되는 시점에 객체의 상태가 일관되지 않기 때문이다.</li>
  <li>이러한 이유로 비동기 프로그램을 구현할 때 OOP의 상태에 의존하여 프로그램을 작성할 수 없다. 프로그램의 실행을 프로그램의 실행 흐름 자체를 제어할 수 있는 별도의 상태를 새로이 정의해야 한다.</li>
  <li>하나의 흐름을 위해 하나의 상태 제어가 필요하며, 이 새로은 객체를 잘 관리해야 한다.</li>
  <li>문제는 이러한 흐름이 하나가 아니라는 점이며, 상태가 계속 추가 될 때 마다 상태를 제어하는 부분이 점점 커지며 프로그램은 기하급수적으로 복잡해진다.</li>
</ul>

<h1 id="반응형-프로그램-reactive-program">반응형 프로그램 (Reactive Program)</h1>

<p>반응형 프로그램은 비동기 프로그램의 한 가지로서, 지속적으로 발생하는 데이터 스트림을 어떻게 비동기적으로 처리할지를 다룬다. 여기에 3가지 주요한 관점을 이해해야 한다.</p>

<ul>
  <li>데이터 스트림</li>
  <li>publisher와 subscriber에 의한 데이터의 생성과 소비, 그리고 그로 인한 역할의 분리와 변경사항 전파</li>
  <li>비동기 프로그램과 동시성 제어</li>
</ul>

<p>여기서 말하는 데이터 스트림이란 시간에 따라 순차적으로 지속적으로 들어오는 데이터를 의미한다. 이것은 지금 당장은 데이터가 없지만 앞으로 계속해서 입력될 수 있음을 의미한다. 그리고 이 데이터는 단순한 상태 변화를 의미하는 것이 아닌, <code class="language-plaintext highlighter-rouge">상태 변화가 있었고 어디론가로 이 내용이 전달될거야</code>라는 의미이다. 이러한 데이터는 특정 모듈에서 발생하여 pipeline을 타고 전달되어 다른 모듈로 흘러들어가 소비된다.<br />
이 데이터는 publisher(생산자)에 의해 생성되고 pipeline에 전달된다. subscriber는 pipeline을 관찰하고 있다가 자신에게 필요한 이벤트가 흘러가는 것을 보면 그 이벤트를 구독하여 코드를 실행하게 된다. 이로 인하여 publisher와 subscriber간의 책임과 역할이 분리되며, 각 모듈이 비동기적으로 처리될 수 있게 되며 이에 따라 동시성 제어가 필요해진다. 또한 publisher와 subscriber간의 결합성을 낮추어 유연한 구조를 가져갈 수 있게 한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[publisher]      [pipeline]   [subscriber]
                     |&lt;------- (observe)
   run()             |             |
(process)            |             |
   |                 |             |
(publsh event) -----&gt;|
   |                 |&lt;------- (subscribe)
(return)             |          (process)
                     |             |
                     |&lt;-------  (publish)
</code></pre></div></div>

<p>따라서 구조를 작성해 보면 주요한 위와 같다. 데이터의 흐름을 정의하고 어디서 어떠한 처리를 통해 이벤트를 발행하고 소비할지를 정한다. 실제 흐름은 파이프라인을 통해 이루어지며 publisher와 subscriber가 누구인지는 특정되지 않는다. 중요한건 <code class="language-plaintext highlighter-rouge">누군가가 이벤트를 발행하면 누군가가 소비한다</code>를 시작으로, 어떠한 이벤트가 어떤 방식으로 전파되어 또다른 이벤트의 흐름이 만들어지는가에 대한 부분을 기술한다. 이 구조는 각 모듈 간 결합도를 낮추고 모듈들이 분산된 채로 독립적으로 반응한다는 형태를 만들 수 있다. 이를 통해 비동기 처리가 가능해지며 이벤트의 흐름과 처리를 조율해야만 pipeline에 이벤트가 대량으로 쌓이는 Backpressure 현상을 막을 수 있다.</p>

<h1 id="프로그램-패러다임에-대한-토막글">프로그램 패러다임에 대한 토막글</h1>

<p><a href="/cs/2022/03/14/FP_vs_OOP.html">함수형 vs 객체지향형</a><br />
<a href="/cs/2025/04/19/imperative_vs_declarative.html">명령형 vs 선언형</a><br />
<a href="/cs/2025/04/19/synchronous_vs_asynchronous.html">동기 vs 비동기 vs 반응형</a></p>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="article" /><summary type="html"><![CDATA[다시금 프로그램의 이론에 대해서 정리하고 포스트를 남겨본다. 프로그램의 구조적인 방법론(FP/OOP)와 프로그램의 스타일(명령형/선언형)에 이어 이번에는 프로그램의 실행 방법에 대한 비교 및 정리를 해보려 한다. 바로 동기형 vs 비동기형 프로그램이다. 한발 더 나아가 지금은 많이 사용하고 있는 반응형 프로그램에 대해서도 같이 정리해보려 한다.]]></summary></entry><entry><title type="html">Github actions를 원격으로 호출해보자</title><link href="https://soongwonjun.github.io/github/2024/02/07/github_remoteCallAction.html" rel="alternate" type="text/html" title="Github actions를 원격으로 호출해보자" /><published>2024-02-07T16:00:00+00:00</published><updated>2024-02-07T16:00:00+00:00</updated><id>https://soongwonjun.github.io/github/2024/02/07/github_remoteCallAction</id><content type="html" xml:base="https://soongwonjun.github.io/github/2024/02/07/github_remoteCallAction.html"><![CDATA[<h2 id="summary">Summary</h2>

<p>Github repository의 외부에서 action을 호출하고 싶을 때가 있다. 이를 위해 github actions의 trigger 중에는 <code class="language-plaintext highlighter-rouge">repository_dispatch</code>라는 webhook을 처리하는 event trigger가 있다.<br />
이를 사용해서 외부에서 actions를 호출하여 실행시켜보도록 하자.</p>

<h3 id="github-호출하기">github 호출하기</h3>

<p>TAG 정보를 <code class="language-plaintext highlighter-rouge">remote_actions/${date}.txt</code> 파일에 저장하는 action을 만든다. jobs에 들어가는 shell script는 아래의 과정을 따르도록 하였다.<br />
git actions에서 호출되는 이벤트는 <code class="language-plaintext highlighter-rouge">types</code>이며, 사용하는 데이터는 <code class="language-plaintext highlighter-rouge">payload</code>에 담긴다.</p>

<ul>
  <li>github checkout/commit을 위해 github token을 주입받아야 한다.</li>
  <li>git 설정정보 설정. 여기서는 github에서 제공하는 bot을 사용한다.</li>
  <li>tag 정보가 없으면 error를 발생시킨다.</li>
  <li>payload에 담겨온 tag 정보를 지정된 경로(<code class="language-plaintext highlighter-rouge">remote_actions/${date}.txt</code>)에 작성한다.</li>
  <li>git에 해당 파일을 commit/push 한다.</li>
</ul>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Remote call and write content to file</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">repository_dispatch</span><span class="pi">:</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">remote_call</span> <span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout repository</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">fetch-depth</span><span class="pi">:</span> <span class="m">0</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">$</span>
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">git config --global user.email "github-actions[bot]@users.noreply.github.com"</span>
          <span class="s">git config --global user.name "github-actions[bot]"</span>
          <span class="s">if [ -z "$TAG" ]; then</span>
            <span class="s">echo "No message found"</span>
            <span class="s">exit 1</span>
          <span class="s">fi</span>
          <span class="s">mkdir -p ./remote_actions</span>
          <span class="s">export FILENAME=./remote_actions/$(date +'%Y-%m-%d_%H:%M:%S').txt</span>
          <span class="s">export MESSAGE=$</span>
          <span class="s">echo $MESSAGE &gt; $FILENAME</span>
          <span class="s">git add $FILENAME</span>
          <span class="s">git commit -m "Remote called, saved to $FILENAME"</span>
          <span class="s">git push</span>
</code></pre></div></div>

<h3 id="만들어진-actions를-호출하기">만들어진 actions를 호출하기</h3>

<p>github에서는 webhook을 위한 RESTApi를 지원하고 있으며 당연하지만 이 내용은 curl을 통해 호출이 가능하다.
하지만 아무나 이 webhook을 호출할 수는 없고, 권한이 있는 사용자만이 이를 호출할 수 있다. 여기서는 github의 PAT token을 사용하여 인증을 처리하도록 하였다. 그리고 이 PAT 토큰은 본인의 github의 <code class="language-plaintext highlighter-rouge">developer settings</code>에서 만들 수 있다. 만드는 방법은 <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#about-personal-access-tokens">Github: PAT token 생성 방법</a> 를 참고하자.</p>

<h4 id="선행-조건">선행 조건</h4>

<ul>
  <li>PAT 토큰: github의 설정에서 외부 접속을 위해 만드는 토큰이다. repository 권한은 반드시 필요하다.</li>
  <li>X-Github-Api-Version: github에서 릴리즈한 api 버전을 의미한다. 해당 버전으로 api를 호출하게 되며, 현재 릴리즈는 <code class="language-plaintext highlighter-rouge">2022-11-28</code>이다.</li>
</ul>

<h4 id="github-이벤트-실행">Github 이벤트 실행</h4>

<p>위 github actions에서 작성한 이벤트는 <code class="language-plaintext highlighter-rouge">remote_call</code>이며 payload에는 <code class="language-plaintext highlighter-rouge">tag</code>가 필요하다. 해당 내용을 body의 <code class="language-plaintext highlighter-rouge">event_type</code>과 <code class="language-plaintext highlighter-rouge">client_payload</code>에 넣어 API를 호출한다.</p>

<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">curl -L \
  -X POST \
  -H "Accept: application/vnd.github+json" \
</span><span class="gp">  -H "Authorization: Bearer $</span><span class="o">{</span>여기에 본인의 github PAT를 넣어줍니다<span class="o">}</span><span class="s2">" </span><span class="se">\</span><span class="s2">
</span><span class="go">  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/soongwonjun/action-test/dispatches \
  -d '{"event_type":"remote_call","client_payload":{"tag": "1.0.1"}}'  
</span></code></pre></div></div>

<p>## Reference</p>
<ul>
  <li><a href="https://docs.github.com/ko/rest/about-the-rest-api/api-versions?apiVersion=2022-11-28">Github: API Version</a></li>
  <li><a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#repository_dispatch">Github: repository_dispatch</a></li>
  <li><a href="https://docs.github.com/en/free-pro-team@latest/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-dispatch-event">Github: create dispatch-event</a></li>
  <li><a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#about-personal-access-tokens">Github: PAT token 생성 방법</a></li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="github" /><category term="github" /><category term="cheatsheet" /><summary type="html"><![CDATA[Summary]]></summary></entry><entry><title type="html">Kotlin 테스트 코드에서 capture를 사용해보자</title><link href="https://soongwonjun.github.io/kotlin/2024/02/07/kotlin_argumentCapture.html" rel="alternate" type="text/html" title="Kotlin 테스트 코드에서 capture를 사용해보자" /><published>2024-02-07T16:00:00+00:00</published><updated>2024-02-07T16:00:00+00:00</updated><id>https://soongwonjun.github.io/kotlin/2024/02/07/kotlin_argumentCapture</id><content type="html" xml:base="https://soongwonjun.github.io/kotlin/2024/02/07/kotlin_argumentCapture.html"><![CDATA[<h2 id="kotlin--junit5--mockk">Kotlin + Junit5 + Mockk</h2>

<p>요즘에 Kotlin을 사용하면서, Junit5와 Mockk를 사용하여 테스트 코드를 작성하고 있다. 대부분은 쉽게 처리가 되었는데, 함수 처리에서 시간을 상당히 소모했다.  이를 해결하면서 <code class="language-plaintext highlighter-rouge">argumentCapture</code> 기능을 새로 알게 되었기에 잠깐 기록해본다.</p>

<h2 id="argumentcapture란">argumentCapture란?</h2>

<p>argument에 어떤 값이 들어가는지 확인하는 단계이며, 이 argument에 함수가 들어가는 부분도 캡쳐할 수 있다.<br />
테스트코드를 작성하면서 난감했던 함수 체크를 이 argumentCapture를 사용해 검증할 수 있게 된다.
이런 부분에 대해서는 역시 코드가 설명하기 더 쉬우니, 간단한 예제를 만들어 보자.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">TestClass</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">testFunc</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">calculator</span><span class="p">:</span> <span class="p">(</span><span class="n">v1</span><span class="p">:</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="nc">Int</span><span class="p">)</span> <span class="p">=</span>
        <span class="nf">calculator</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>간단한 테스트 함수를 작성해 보았다. value와 calculator를 인자로 받아서, calculator를 통해 value를 계산하는 함수이다.<br />
이제 이 함수에 대한 테스트 코드를 넣어본다.</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">CaptureTest</span> <span class="p">{</span>
    <span class="nd">@Test</span>
    <span class="k">fun</span> <span class="nf">`capture</span> <span class="nf">test`</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 캡쳐를 하기 위한 대상을 모킹한다.</span>
        <span class="c1">// 원래 테스트코드에서는 모킹한 대상을 직접 호출하는건 의미가 없지만, 여기서는 설명을 위해 중간 단계를 모두 건너뛰어 본다.</span>
        <span class="kd">val</span> <span class="py">t</span> <span class="p">=</span> <span class="n">mockk</span><span class="p">&lt;</span><span class="nc">TestClass</span><span class="p">&gt;()</span>

        <span class="c1">// calculator에 들어가는 함수를 확인하기 위해서 slot을 만들고 capture를 사용하여 argument를 확인한다.</span>
        <span class="kd">val</span> <span class="py">slot</span> <span class="p">=</span> <span class="n">slot</span><span class="p">&lt;</span><span class="nc">Int</span><span class="p">.()</span> <span class="p">-&gt;</span> <span class="nc">Int</span><span class="p">&gt;()</span>
        <span class="nf">every</span> <span class="p">{</span> <span class="n">t</span><span class="p">.</span><span class="nf">testFunc</span><span class="p">(</span><span class="nf">any</span><span class="p">(),</span> <span class="nf">capture</span><span class="p">(</span><span class="n">slot</span><span class="p">))</span> <span class="p">}</span> <span class="n">returns</span> <span class="mi">10</span>

        <span class="c1">// 여기서 설정되는 lambda 함수가 캡쳐된다.</span>
        <span class="n">t</span><span class="p">.</span><span class="nf">testFunc</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="n">it</span> <span class="p">+</span> <span class="mi">1</span> <span class="p">}</span>

        <span class="c1">// 함수가 캡쳐됬는지 확인하고, 실제 캡쳐된 함수가 어떻게 동작하는지 확인한다.</span>
        <span class="c1">// 여기서는 넘겨받은 인자에 +1을 해주는 함수이므로, 캡쳐된 함수에 들어가는 숫자가 1이 증가되는지 확인한다.</span>
        <span class="nf">assertTrue</span><span class="p">(</span><span class="n">slot</span><span class="p">.</span><span class="n">isCaptured</span><span class="p">)</span>
        <span class="nf">assertEquals</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">slot</span><span class="p">.</span><span class="nf">captured</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
        <span class="nf">assertEquals</span><span class="p">(</span><span class="mi">11</span><span class="p">,</span> <span class="n">slot</span><span class="p">.</span><span class="nf">captured</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>mockito의 홈페이지에서는 value 값, vararg 값을 체크하는 등 많은 방법으로 argumentCapture를 사용하는 방법을 예제로 만들어 두었다.</p>
<ul>
  <li><a href="https://www.javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/ArgumentCaptor.html">ArgumentCapture</a></li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="kotlin" /><category term="kotlin" /><category term="test" /><summary type="html"><![CDATA[Kotlin + Junit5 + Mockk]]></summary></entry><entry><title type="html">Javascript와 type에 대해서</title><link href="https://soongwonjun.github.io/cs/2023/09/13/Javascript_type_n_variable.html" rel="alternate" type="text/html" title="Javascript와 type에 대해서" /><published>2023-09-13T13:00:00+00:00</published><updated>2023-09-14T05:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2023/09/13/Javascript_type_n_variable</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2023/09/13/Javascript_type_n_variable.html"><![CDATA[<h2 id="javascript는-type을-지원할까">Javascript는 type을 지원할까?</h2>

<p>혹자는 Javascript에는 타입을 지원하지 않는다고 한다. 하지만 Javascript는 이미 여러가지 타입들을 지원하고 있으며, 이 타입을 판별하는 방법도 제공한다. 그리고 이 타입을 사용하여 value/function을 설정하고 프로그램을 작성하고 실행시킨다. 그런데 왜 javascript에는 타입이 없다고 하는 것일까?<br />
이것은 Javascript의 개념 중 <code class="language-plaintext highlighter-rouge">value</code>와 <code class="language-plaintext highlighter-rouge">variable</code>을 명확하게 구분하지 못하기 때문에 생기는 오해이다.</p>

<ul>
  <li>value: javascript의 identifier가 바라보는 값에 저장되어 있는 값 정보이다. 이 value에는 type 정보가 있다. type의 명세에 대해서는 <a href="https://262.ecma-international.org/10.0/#sec-type">이쪽</a>을 참고하자</li>
  <li>variable: identifier를 만들기 위한 property이며, type 정보가 없다. (var, let, const)</li>
</ul>

<p>이제 우리는 <code class="language-plaintext highlighter-rouge">Javascript는 type을 정의하고 지원한다. 그리고 Javascript의 variable에는 type이 할당되지 않는다</code> 라고 말할 수 있게 되었다. 어째서 이런 구조를 가지느냐에 대해서는 ECMAScript의 배경과 관련이 있다.</p>

<h3 id="ecmascript에-대해서">ECMAScript에 대해서</h3>

<p>ECMAScript는 Javascript와 같은 스크립트 언어의 표준화를 위해 만들어진 문서이다. 기본적으로 이 언어는 객체 기반이며 ECMAScript는 객체간의 커뮤니케이션의 모음이다. ECMAScript에서 Object는  attribute를 가진 0개 이상의 property의 모음이다. 또한 property는 다른 객체, primitive value, function을 가질 수 있다.</p>

<h3 id="ecmascript가-집중하는-부분-그리고-variable에-type이-없는-이유">ECMAScript가 집중하는 부분, 그리고 variable에 type이 없는 이유</h3>

<p>ECMAScript 문법은 상당히 사용하기 쉬운 스크립트 언어라는데 초점을 맞추고 있다. 변수는 타입을 정의할 필요가 없으며, property는 type 연결되지 않으며(value와 연결된다) 정의된 함수는 호출되기 전까지 그 내용이 표현될 필요가 없다는 부분이다.</p>

<h3 id="번외-ecmascript에서-변수가-선언되고-나서-어떻게-될까">번외. ECMAScript에서 변수가 선언되고 나서 어떻게 될까?</h3>

<p>Javascript를 실행하기 위해서 작성된 코드가 먼저 평가된다. 이 때 Lexical Environment가 생성되고, Initializer에 의해 Identifier가 만들어진다. 이 이후 Identifier의 값이 Lexical Environment 저장된 후 Lexical Binding 정보가 생성된다. Lexical Binding 정보는 Environment Record에 기록되고 이 때 identifier와 value가 매핑되어 Lexical Environment에 접근이 가능해진다. 예외적으로 Let 변수는 identifier에 undefined가 바인딩된다.
우리가 선언한 변수는 Lexical Environment의 어딘가를 바라보는 하나의 프로퍼티라고 할 수 있다. 조금 더 깊게 들어가면 코드에 대한 평가, Realm, Execution Context, Queue, Agent 등 많은 개념이 나오지만 이는 본 토막글과는 주제가 다르니 다른 토막글로 정리해보자.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/ECMAScript">Wiki, ECMAScript</a>
    <ul>
      <li><a href="https://ko.wikipedia.org/wiki/ECMA%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8">Wiki, ECMA스크립트</a></li>
    </ul>
  </li>
  <li><a href="https://262.ecma-international.org/10.0/#sec-ecmascript-overview">ECMAScript, Overview</a></li>
  <li><a href="https://262.ecma-international.org/10.0/#sec-type">ECMAScript, Type</a></li>
  <li><a href="https://262.ecma-international.org/10.0/#sec-declarations-and-the-variable-statement">ECMAScript, Variable statement</a></li>
</ul>

<h2 id="appendix">Appendix</h2>

<ul>
  <li>Lexical Environment(LE): identifier와 value/function의 관계를 정의하는 명세를 가진 <code class="language-plaintext highlighter-rouge">lexical nesting structure</code> 이다. LE는 Envorinment Record와 외부 LE 참조에 대한 null로 구성되어 있다. 매번 코드가 평가될 때 마다 새로운 Environment Record가 생성되고 평가된 코드를 위해 identifier가 바인딩된다.</li>
  <li>Lexical nesting structure: Execution Contexts에서 Scope와 Identifier를 관리하는 기능</li>
  <li>Environment Record(ER): identifier와 LE를 바인딩하는 record를 모아둔 것. 내부적으로 크게 3개의 파트로 나누어진다
    <ul>
      <li>Declarative Environment Records: ECMAScript의 함수선언, 블록구문, Try구문의 Catch와 같은 특별한 구문과 연결된다</li>
      <li>Object Environment Records: ECMAScript의 특정 객체에 바인딩되는 Identifier bindings를 저장한다.</li>
      <li>Global Environment Records: 함수 내에서 top-level 선언 혹은 전역 레벨 선언되었다고 평가된 항목들을 저장한다.</li>
    </ul>
  </li>
  <li>바인딩: identifier와 value를 서로 연결해 두는 것</li>
  <li>Execution Contexts: 함수가 실행될 때 마다 설정되는 컨텍스트. 관련 코드의 실행 상황을 추적하는데 필요한 모든 내용이 들어간다.</li>
  <li>코드의 평가: 구문과 표현식을 평가할 규칙을 작성해 두고, 그 규칙에 맞게 작성되었는지 확인하고 실행하는 작업</li>
  <li>top-level declaration: Global object의 property/method</li>
  <li>attribute: object를 구성하는 property의 특성을 나타내는 value를 나타낸다. ECMAScript에서는 method 또한 하나의 value이므로, javascript에서는 method 또한 attribute에 포함된다.</li>
  <li>method: object를 구성하는 property 중 function과 같이 특정 작업을 수행할 수 있는 attribute를 말한다.</li>
  <li>function: 기능을 수행하도록 구현된 문장의 집합</li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="article" /><summary type="html"><![CDATA[Javascript는 type을 지원할까?]]></summary></entry><entry><title type="html">MSA에 대한 내용을 정리하면서</title><link href="https://soongwonjun.github.io/cs/2023/09/10/MSA-101.html" rel="alternate" type="text/html" title="MSA에 대한 내용을 정리하면서" /><published>2023-09-10T13:00:00+00:00</published><updated>2023-09-10T13:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2023/09/10/MSA-101</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2023/09/10/MSA-101.html"><![CDATA[<h2 id="microservice-architecture">Microservice Architecture?</h2>

<p>마이크로서비스 아키텍쳐(MSA)의 기본적인 접근은 하나의 어플리케이션을 작은 서비스의 모음으로 개발하는 방법을 말한다. 각각의 작은 서비스들은 자체적인 프로세스로서 동작하며 서로 가볍고 투명한 매커니즘(http와 같은)으로 통신한다. 각각의 서비스는 비즈니스 기능을 중심으로 구축되며 가능한 한 자동화된 배포를 통해 독립적으로 배포가 가능하다.</p>

<h2 id="msa-vs-monolithic">MSA vs Monolithic</h2>

<p>Monolithic은 하나의 단단한게 뭉쳐진 어플리케이션으로 개발되고 배포된다. 각 기능들은 내부에서 class/package/namespace 등의 용어로 분리 개발되지만, 기본적으로는 하나의 어플리케이션을 구성한다. 단일 어플리케이션의 특성 상 모든 수정 및 업데이트내용은 서로에게 영향을 줄 수 있으며, 때문에 아주 작은 변경이라 할지라도 Monolithic에서는 서비스 전체에 반영을 해 주어야 한다. 이 내용은 모든 업데이트 내용이 어플리케이션 내부에서 서로 동기화되어 있다는 점이다.<br />
MSA 어플리케이션은 서비스를 중심으로 작은 단위로 개발 및 배포되며, 서비스간의 명확한 경계를 만들어 서로 독립적으로 개발 및 배포될 수 있도록 한다. 이는 작은 변경은 필요한 서비스만 변경하여 전달할 수 있도록 하며, 각 도메인간의 연관성은 최대한 느슨하게 하여 서로 영향력을 적게 받도록 한다. 하지만 이러한 내용은 동일한 내용이 다수의 도메인에 속할 수 있기 때문에, 데이터 혹은 로직의 중복을 만들 수 있다. 또한 MSA는 다수의 어플리케이션이 서로 잘 작동될 수 있도록 하기 위해서 많은 고민이 필요하다.</p>

<p class="image-caption"><img src="https://martinfowler.com/articles/microservices/images/sketch.png#center" alt="Monolithic vs MSA" />
<a href="https://martinfowler.com/articles/microservices">Monolithic vs MSA</a></p>

<h3 id="그래서-msa를-왜-써야하는데">그래서 MSA를 왜 써야하는데?</h3>

<p>MSA를 사용하는 이유는 매우 심플하다. 현재 개발하고 있는 비즈니스가 요구하고 있는가, 그리고 MSA로 개발했을 때 이득이 있는지를 확인해야 한다.<br />
모든 상황에서 MSA가 무조건 유리하지 않다. MSA보다 Monolithic이 더 나은 경우도 많이 있기 때문에 무조건적으로 MSA를 선택하는 것은 나쁜 방법이다. MSA는 Monolithic에 비해 정말 많은 리소스가 소모된다. 첫 설계를 시작할 때 비즈니스의 범위를 정의하는 과정, 서비스의 구성 및 서로의 연관관계를 고려해야 한다. CI/CD에서도 더 많은 리소스가 필요하다. 하나의 Monolithic을 배포하는 것 보다는 여러개의 분할된 서비스를 배포하는 MSA가 더 많은 작업과 트러블들을 마주하게 된다는건 자명하다. 문제가 발생한 경우에는 어떠한가? 추적, 감시, 로그 시스템이 잘 구축되어 있지 않다면 어디서 문제가 발생하였는지 알기 어려운 경우가 허다하다.<br />
여기까지만 보았을 때에는 왜 MSA를 쓰는가에 대해 회의감이 들 수 있다.</p>

<p>그럼에도 불구하고 우리가 MSA를 선택하는 경우에는 아래와 같은 이유가 있을 것이다.</p>

<ul>
  <li>서비스를 제공하는 조직에 대한 역할과 책임의 분리가 필요할 때</li>
  <li>컴포넌트화된 각 서비스들에 대한 유연한 변화가 요구될 때</li>
</ul>

<h3 id="번외-왜-msa와-ddd가-서로-접목되는가">번외. 왜 MSA와 DDD가 서로 접목되는가?</h3>

<p>상당히 다른 개념인 MSA와 DDD인데 왜 항상 두 가지를 같이 이야기하게 되는 것일까?
MSA의 목적은 서비스를 작은 서비스의 모음으로서 개발하고, 각각의 서비스는 비즈니스 기능을 구현하고, 구현된 서비스들은 유기적이고 느슨한 관계 속에서 본래의 목적을 다한다. DDD는 비즈니스를 코드로 설명할 수 있으며, 반대로 코드를 비즈니스로서 설명할 수 있는 것이라고 말할 수 있다. 즉 실제 비즈니스를 기반으로 도메인, BoundedContext, ContextMap을 잘 만들고 이를 전파하는것이 목표이다.<br />
자 그럼, MSA에서는 어떻게 서비스를 작은 서비스 모음으로 만들 수 있고 해당 내용을 모델링하여 전파할 수 있을까? DDD가 언급되는 이유는 여기에 있다. DDD의 목적을 다시 한번 잘 보면 비즈니스의 이해과 설계에 초점이 맞추어져 있다. 필요한 부분만을 모아서 정의한 도메인과 Boundary들, 그리고 BoundaryContext와 ContextMap은 MSA에서 서비스의 단위, 모델 설계, 서비스간의 관계와 필요한 기술을 설명할 수 있게 해주며, 나아가 서비스와 비즈니스간의 단단한 연결관계를 만들어주고 이를 비즈니스를 구성하는 구성원에게 전파하고 커뮤니케이션할 수 있는 수단(단어 혹은 상황들)을 제공해준다.</p>

<h2 id="msa의-특성들-그리고-고려해야-하는-점">MSA의 특성들, 그리고 고려해야 하는 점</h2>

<h3 id="서비스의-컴포넌트화">서비스의 컴포넌트화</h3>

<p>어플리케이션을 구성하는 각각이 서비스들을 단일 개발 유닛인 컴포넌트로서 개발하는 것이다. 이로서 컴포넌트들은 서로 독립적으로 버전업 혹은 수정내용의 반영 등이 가능하다.<br />
다만 각 서비스간의 커뮤니케이션을 위한 API 디자인이 가능한 한 세분화되어 있어야 하며, 이를 사용하는 서비스간 커뮤니케이션은 아무래도 Monolithic에 비해 비용이 더 들어간다. 또한 서비스가 책임지는 부분이 변경될 때에는 관련 서비스들에 대해 같이 변경되어야 하기 때문에 이 부분에 대한 어려움이 존재한다. 때문에 MSA에서는 서비스를 되도록 많이 나누는것이 능사가 아니다. 응집력이 있는 서비스를 만들고, 서비스간의 강한 관계성을 가능한한 낮추는 것이 여기서 말하는 컴포넌트화의 목적이다.</p>

<h3 id="비즈니스의-분리">비즈니스의 분리</h3>

<p>어쩌면 이 항목은 주제가 비즈니스에 초점이 맞추어져 있기 때문에 MSA보다는 DDD에 적합할지도 모른다. 하지만 MSA에도 적용되는 이야기가 있으니 작성해본다.</p>

<p>MSA는 <code class="language-plaintext highlighter-rouge">다수의 서비스가 유기적으로 작동하는 것</code> 에서부터 시작한다. 여기서 서비스는 어떻게 나누어지는가를 보면 비즈니스를 중심으로 나누어질 것이다. 비즈니스는 목표에 따라 하나 또는 여러 팀으로 나뉘게 된다. 그리고 여러 개의 팀은 각자의 역할, 책임을 가지게 된다. 그리고 이 말은 각자의 서비스 내에서 역할과 책임을 다해야 한다는 의미이기도 하다. 이 내용은 밑에서 나오는 <code class="language-plaintext highlighter-rouge">오류가 발생할 것을 전제로 만들어지는 구조</code> 와 <code class="language-plaintext highlighter-rouge">데이터와 트랜잭션에 관해서</code> 에 이어진다.</p>

<h3 id="오류가-발생할-것을-전제로-만들어지는-구조">오류가 발생할 것을 전제로 만들어지는 구조</h3>

<p>MSA의 각 서비스들은 서로 연관관계가 있으면서도 독립적으로 작동해야만 한다. 서비스들이 서로의 작동에 영향을 줄 수 있지만, 장애 혹은 문제가 전파되어서는 안된다. 때문에 MSA에서의 서비스들은 모두 에러에 대한 내용을 전달하거나, 에러가 날 것을 전제로 작성해야만 한다. 그렇지 않으면 문제가 발생하였을 때 어디서부터 발생하였는지, 그리고 그 오류가 어디까지 전파되는지 알 수 없게 된다.<br />
Monolithic의 경우에는 모든 로직이 서비스 내에 존재한다. 서비스 내에서의 오류는 개발자가 직접 테스트하는 것 만으로도 잡을 수 있다. 하지만 MSA는 서비스 간 통신이 기본 전제로 만들어지며, 이 통신이 문제가 발생하는 경우는 많다. 단순히 네트워크 장애 혹은 일부 서비스의 버전업 같은 경우도 여기에 속한다. 이러한 문제를 극복하기 위해 서비스의 감시와 복구에 대한 부분이 대두된다. 서비스는 언제든지 장애가 발생하여 정지될 수 있지만, 이것을 빠르게 인지하고 복구할 수 있는 시스템이 필요하다. 이를 위해서 우리는 실시간 모니터링 시스템을 구축하고, 가능한 경우에는 자동 복구 시스템을 구축하기도 한다.</p>

<h3 id="데이터와-트랜잭션에-관하여">데이터와 트랜잭션에 관하여</h3>

<p>MSA하면 항상 트랜잭션의 문제에 대해 언급하곤 한다. 우리가 작성하던 수많은 Monolithic 서비스에서는 데이터 일관성을 위해서 트랜잭션을 매우 많이 사용한다. 하지만 MSA에서는 서비스간에 트랜잭션이 묶이는 경우도 있으며, 이를 위해 많은 서비스들이 하나의 트랜잭션에 묶이곤 한다.<br />
계속 나오는 이야기이지만 MSA는 다수의 서비스가 관계를 통해 동작하는 하나의 큰 서비스이다. 그리고 이 서비스들의 역할은 너무나도 명확하다. 그리고 당연하지만 각각의 서비스는 자신이 관리하는 데이터를 각각 가지게 되고, 이 데이터들은 분산된 데이터 소스에 저장될 수 있다. 그리고 MSA에서는 여러 개의 데이터가 한번에 처리되어야 하는 경우도 물론 생긴다.<br />
서비스에서 데이터를 관리할 때는 보통 ACID를 많이 지키려고 한다. 우리에게는 친근하면서도 엄격한 모델이지만, 문제는 이 ACID는 여러개의 boundedContext 안에서는 지켜지기가 쉽지 않다. 때문에 MSA에서는 최종 일관성이라는 개념을 도입하고, 이를 지키기 위해서 Two-Phase commit 혹은 SAGA 와 같은 <code class="language-plaintext highlighter-rouge">분산 서비스 환경에서의 데이터 처리를 위한 패턴</code>을 사용한다.</p>

<h2 id="참고문헌">참고문헌</h2>

<ul>
  <li><a href="https://martinfowler.com/articles/microservices.html">Martin Fowler, Microservices</a></li>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice">Microsoft, Design a DDD-oriented microservice</a></li>
  <li><a href="https://developers.redhat.com/articles/2021/09/21/distributed-transaction-patterns-microservices-compared">RedHat, Distributed transaction pattern</a></li>
  <li><a href="https://microservices.io/patterns/microservices.html">Microservice Architecture Pattern</a></li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="article" /><summary type="html"><![CDATA[Microservice Architecture?]]></summary></entry><entry><title type="html">NodeJS의 메모리 영역</title><link href="https://soongwonjun.github.io/cs/2023/09/05/nodejs_memory.html" rel="alternate" type="text/html" title="NodeJS의 메모리 영역" /><published>2023-09-05T13:00:00+00:00</published><updated>2023-09-05T13:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2023/09/05/nodejs_memory</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2023/09/05/nodejs_memory.html"><![CDATA[<h2 id="nodejs의-메모리-영역을-알아두어야-할까">NodeJS의 메모리 영역을 알아두어야 할까?</h2>

<p>NodeJS로 개발을 할 때에는 이벤트 루프와 함께 메모리 영역과 작업의 처리 과정에 대해서는 알아두면 상당히 좋다.<br />
NodeJS에서는 이벤트루프와 메모리의 Stack/Heap을 사용해서 함수를 처리하고 데이터를 쌓는다. 이 과정중에 원치 않는 순서대로 작업이 수행되거나, 원하는 함수가 전혀 실행되지 않기도 한다. 의도대로 프로그램이 실행되지 않아 곤혹스러운 때를 회피할 수 있게 된다.</p>

<h3 id="nodejs에서의-작업을-처리할-때-사용하는-메모리-영역">NodeJS에서의 작업을 처리할 때 사용하는 메모리 영역</h3>

<p>NodeJS에서는 작업을 처리하기 위해 2개의 공간을 사용한다.</p>

<ul>
  <li>Queue: 요청된 작업이 쌓이는 공간</li>
  <li>Stack: Queue에서 실제 처리될 작업들의 Callback이 등록되는 공간</li>
</ul>

<p>NodeJS에서 js파일을 읽으면, 라인 단위로 작업을 해체해서 작업 queue에 등록한다. NodeJS는 이 작업queue에 등록된 작업을 하나씩 빼내어 처리하게 된다.<br />
너무나도 억지스럽지만, <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code>, <code class="language-plaintext highlighter-rouge">c</code> 순서대로 콘솔에 찍기 위한 아래 예제를 참조해보자. 하지만 실행 결과는 <code class="language-plaintext highlighter-rouge">setTimeout</code>에 의해 원하는 내용과 다르게 나온다.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 소스코드</span>
<span class="kd">function</span> <span class="nx">print</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span> <span class="p">}</span>

<span class="nx">print</span><span class="p">(</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">print</span><span class="p">(</span><span class="dl">'</span><span class="s1">b</span><span class="dl">'</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="nx">print</span><span class="p">(</span><span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">// 실행 결과</span>
<span class="dl">'</span><span class="s1">a</span><span class="dl">'</span>
<span class="dl">'</span><span class="s1">c</span><span class="dl">'</span>
<span class="kc">undefined</span> <span class="c1">// 안나오는 경우도 있음</span>
<span class="dl">'</span><span class="s1">b</span><span class="dl">'</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">b</code> 가 뒤늦게 콘솔에 찍히게 되는데, 이유는 NodeJS의 event loop에서 아래와 같은 순서대로 작업을 처리하기 때문이다.</p>

<ul>
  <li>step #1
    <ul>
      <li>print, setTimeout, print를 queue에 등록</li>
    </ul>
  </li>
  <li>step #2
    <ul>
      <li>pool 단계에서 print(‘a’) 처리</li>
      <li>pool 단계에서 setTimeout( … ) 의 내용을 loop에 등록하여 event loop의 timer 단계까지 대기한다. setTimeout의 결과인 undefined가 콘솔에 찍힐 때가 있다.</li>
      <li>pool 단계에서 print(‘c’) 처리</li>
    </ul>
  </li>
  <li>step #3
    <ul>
      <li>timer 단계에서 step #2에서 등록된 setTimeout 함수를 처리. delay가 0이니 해당 함수의 콜백 print(‘b’)를 queue에 등록</li>
      <li>poll 단계에서 queue에 등록된 print(‘b’)를 처리</li>
    </ul>
  </li>
</ul>

<p>만약 함수 내에서 다른 함수를 계속 호출하는 아래와 같은 모양이라면 stack은 이렇게 생기게 되며, heap 영역에 저장되는 데이터는 없다.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 코드</span>
<span class="kd">function</span> <span class="nx">print</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">text</span><span class="p">);</span> <span class="p">}</span>
<span class="kd">function</span> <span class="nx">calla</span><span class="p">()</span> <span class="p">{</span> <span class="nx">print</span><span class="p">(</span><span class="dl">'</span><span class="s1">call - a</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span>
<span class="kd">function</span> <span class="nx">callb</span><span class="p">()</span> <span class="p">{</span> <span class="nx">calla</span><span class="p">();</span> <span class="p">}</span>


<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">callb</span><span class="p">()</span> <span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

<span class="cm">/*
// 메모리 영역
--- stack ---
|   print   |
|   calla   |
|   callb   |
| anonymous |
-------------
--- heap  ---
--- young --- | ---  old  ---
|             |             |
-----------------------------
*/</span>
</code></pre></div></div>

<p>혹시 이 과정이 어떻게 처리되는지 동적인 그림으로 보고 싶다면 이 조각글 하단 <code class="language-plaintext highlighter-rouge">Reference</code>의 <a href="https://www.jsv9000.app/">Javascript Visualizer 9000</a> 에서 한번 실행해보는것도 좋다.</p>

<h2 id="nodejs에서의-데이터를-저장할-때-사용하는-메모리-영역">NodeJS에서의 데이터를 저장할 때 사용하는 메모리 영역</h2>

<p>NodeJS에서는 데이터 저장을 위해 Stack과 Heap을 사용하며, Heap은 GC를 위해 여러 개의 영역으로 나뉘어 관리된다.</p>

<ul>
  <li>Stack
    <ul>
      <li>로컬 변수(argument, return value) 및 객체에 대한 포인터 정보를 저장한다.  여기서 말하는 포인터는 단순 객체에 대한 포인터와 Global scope(= Global frame)에 대한 포인터를 모두 포함한다.</li>
      <li>OS에서 관리되고 처리된다.</li>
    </ul>
  </li>
  <li>Heap
    <ul>
      <li>Stack의 포인터에 의해 참조되는 객체 정보(object type, function)를 저장한다.  primitive type(int, string과 같은)은 포인터에 의해 참조되는 객체 정보가 아니기 때문에 Heap이 아닌 Stack에 저장된다.</li>
      <li>V8 엔진이 관리하고 처리하며, 이를 위해 2가지 GC를 사용한다.</li>
      <li>Young Generation과 Old Generation으로 나뉜다.
 Young Generation: nursey/intermediate 로 나뉘며, 새 객체는 nursey에 생성되어 저장되고, intermediate는 Scavenge 때 활용된다.
        <ul>
          <li>old generation: Young Generation에서 GC가 2번 진행될 동안 객체의 포인터가 살아있는 경우, 이 객체들이 이동되는 장소이다.</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h2 id="gc-garbage-collection">GC (Garbage Collection)</h2>

<p>NodeJS에서 GC의 경우에는 개발자가 이를 손댈 수 없다. V8 엔진에서 모든 GC작업을 알아서 처리하기 때문이다. 그럼에도 우리는 언제 GC가 실행되고 이로 인하여 우리의 어플리케이션에 어떠한 영향을 주는지 기억해두는게 좋다.<br />
GC에는 2가지가 있는데, Young Generation만을 처리하는 Scavenge와 전체 Heap 영역을 처리하는 Major GC로 나뉜다.</p>

<h3 id="scavengeminor-gc-의-동작-과정">Scavenge(Minor GC) 의 동작 과정</h3>

<p class="image-caption"><img src="https://v8.dev/_img/trash-talk/02.svg#center" alt="V8 Memory" />
<a href="https://v8.dev/_img/trash-talk">V8 Scavenge</a></p>

<ul>
  <li>새 객체들이 생성되면 nursey에 할당된다.</li>
  <li>GC가 시작될 때, 모든 객체의 포인터를 확인하고, 포인터가 살아있는 모든 객체들을 마킹한다.</li>
  <li>마킹된 모든 객체들을 immediate로 이동시킨다.
    <ul>
      <li>이 때 sweeping이 되면서 모든 객체 정보가 가지런히 저장된다. 파편화 제거가 목적이다.</li>
      <li>옮겨지는 모든 객체들에는 한번 GC가 되었음을 마킹해둔다.</li>
    </ul>
  </li>
  <li>참조하고 있는 pointer정보가 유효하지 않으니 모든 pointer의 주소를 업데이트한다.</li>
  <li>immediate의 모든 내용을 nursey로 옮긴다.</li>
  <li>두번의 GC(GC가 한번 되었음에도 불구하고 살아남은 모든 객체의 데이터)는 old-generation으로 이동되며, 이후 Major GC가 이루어질 때 까지 heap에 계속 존재하게 된다.</li>
  <li>만약 scavenge가 실행되는 과정중에 새로운 객체가 생성된다면 이 객체는 nursey에 저장된다.</li>
</ul>

<h3 id="major-gc">Major GC</h3>

<p>전체 heap 영역을 처리한다.<br />
heap에 저장되는 모든 데이터는 tree구조로 만들어져있다. heap에 데이터가 저장될 때 루트에서 뻗어나간 어딘가의 리프로서 저장된다.
GC가 시작될 때 루트를 시작으로 DFS 방식으로 모든 객체들을 탐색하며 포인터가 있는 모든 객체들에 마킹을 하게 된다.
그 후에 모든 메모리영역을 순회하며 마킹되지 않은 모든 객체들에 대해 메모리를 해제하고 필요에 따라 파편화 방지를 위해 메모리영역을 압축한다.</p>

<h2 id="reference">Reference</h2>

<ul>
  <li><a href="https://v8.dev/blog/trash-talk">V8: trash-talk</a></li>
  <li><a href="https://v8.dev/blog/orinoco-parallel-scavenger">V8: Orinoco: Young Generation garbage collection</a></li>
  <li><a href="https://www.jsv9000.app/">Javascript Visualizer 9000</a></li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="nodejs" /><category term="article" /><summary type="html"><![CDATA[NodeJS의 메모리 영역을 알아두어야 할까?]]></summary></entry><entry><title type="html">Domain Driven Design을 정리하면서</title><link href="https://soongwonjun.github.io/cs/2023/08/21/DDD_101.html" rel="alternate" type="text/html" title="Domain Driven Design을 정리하면서" /><published>2023-08-21T13:00:00+00:00</published><updated>2023-08-27T13:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2023/08/21/DDD_101</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2023/08/21/DDD_101.html"><![CDATA[<h2 id="서두에-결론을-담아서">서두에 결론을 담아서</h2>

<p>DDD는 단순히 어플리케이션을 프로그램 코드로 작성하는 것만을 다루지 않는다. DDD의 내용들을 살펴보면 분명 어플리케이션 작성에 대한 내용을 담고 있다. 하지만 다시 한번 잘 살펴보면 이 부분은 프로젝트를 운영하는 방법에 대하여 이야기하는 부분이 더 크다는 것을 알 수 있다. 때문에 DDD를 시작하고자 한다면 내가 속한 조직 내에서 어떠한 부분들에 대해 적용할 지에 대해 진지한 고민이 필요할 것이다. 개인 프로젝트라면 어플리케이션 내의 수많은 조각들을 만드는 부분에서 시작할 수 있지만, 팀 단위 혹은 조직이 커진다면 진지하게 어디서부터 어떻게 적용할지에 대해 고려하고 구성원들과 많은 소통을 통해 나아가고자 하는 방향을 설득해야 할 수 있다.</p>

<h3 id="소프트웨어-엔지니어의-역할">소프트웨어 엔지니어의 역할</h3>

<p>DDD를 먼저 들어가기전에 우리는 소프트웨어 엔지니어에 대해 잠시 고민해봐야 한다. 소프트웨어 엔지니어는 무엇을 하는 사람일까? IT프로젝트 진행을 위해 무언가 프로그램을 만드는 사람들이라고 할 수 있겠다. 그럼 이걸 어떻게 만들어야 할까?<br />
처음 우리가 소프트웨어 엔지니어로 일을 시작하게 되면, 프로그램을 다루게 된다. 내용은 <code class="language-plaintext highlighter-rouge">프로젝트 진행자가 요청한 내용을 처리할 수 있는 프로그램</code> 을 작성하는 것 에서부터 시작한다. 그리고 시간이 지남에 따라 영역은 점점 넓어진다. 다른 영역에 눈을 돌릴 수 있게 될 때 부터는 요구사항에 맞춰서 질문도 하고, 좋은 아이디어가 있다면 제안도 할 수 있을 것이다. 그리고 프로그램이 잘 돌아가는지, 그리고 견고하게 만들었는지 테스트고 해야하며 서비스 인프라를 만들어 서비스도 해야 한다.<br />
컴퓨터 프로그램 구현에서 시작해서 기획, 품질관리, 설치 및 배포 등 다양한 영역으로 작업 영역이 늘어나게 된다.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>소프트웨어 엔지니어는 프로젝트에 참여해서 프로젝트의 목표에 맞는 프로그램을 만들어야 하는 사람이다.
</code></pre></div></div>

<h3 id="domain-driven-design">Domain Driven Design</h3>

<p>요구사항을 잘 반영하는 domain model을 정의하는 일에 대한 중요성은 아무리 강조해도 모자람이 없다. 이는 프로젝트 결과물을 결정짓고 나아가 프로젝트가 목표로 하는 방향성에도 영향을 주기 때문이다. 이는 domain model이 프로젝트 혹은 어플리케이션을 구성하는 내용 그 자체이기 때문이다.<br />
Domain model을 잘 만들지 못했다면 비즈니스상의 요구사항을 잘 이해하지 못함에 의한 문제들이 발생한다. 프로젝트가 다른 방향으로 전개되거나, 예정보다 늦게까지 개발되거나, 필요 이상의 개발 시간이 필요해 지기도 한다. 또한 실제 개발내용과 전혀 다른 내용이 개발될 수도 있고, 개발팀 간의 이해 정도에서도 차이가 발생하기에 소통도 시간이 오래걸리고 문제가 발생한다. 그리고 이러한 문제를 수정하는데 시간을 들일 수도 있지만, 프로젝트의 방향성 자체가 틀어질 수도 있다.<br />
DDD에서 tactical design tools 혹은 strategic design tools을 제안하는 이유도 이 domain model에 요구사항을 정확하게 반영하고 그 내용을 모두가 쉽게 이해하고 공유할 수 있도록 하기 위함이다.</p>

<h4 id="goal">Goal</h4>

<p>우리가 프로젝트를 진행할 때 가장 먼저 해야하는 것은 당연히 프로젝트에 대한 이해일 것이다. 어플리케이션을 구성하는 모듈들은 각자의 목적에 맞게 설계되어 있을 것이고 이 모듈들은 design pattern에 따라 잘 추상화되어 있을 것이다. 이것을 읽는 소프트웨어 엔지니어들은 프로젝트에 대한 이해와 프로그램의 구성에 대한 지식을 기반으로 프로젝트를 이해하고 개선하고 관리해나갈 것이다. 요구사항을 최대한 명확하게 만들어 코드로 작성하고, 이에 대한 내용을 문서에 작성된 용어를 사용하여 커뮤니케이션 시에 사용할 것이다. DDD는 이를 돕기 위한 수단이다.</p>

<h3 id="그럼-ddd를-할-때-소프트웨어-엔지니어가-가져가야-할-자세는-무엇일까">그럼 DDD를 할 때 소프트웨어 엔지니어가 가져가야 할 자세는 무엇일까?</h3>

<p>소프트웨어 엔지니어는 프로젝트의 목표를 코드화할 수 있고, 코드화된 프로젝트를 읽고 이해할 수 있어야 한다.<br />
우리가 도메인을 이해하고 이를 코드화하기 위해서는 최대한 자세히, 그리고 세밀하게 알아야 한다. 이를 위해 소프트웨어 엔지니어는 도메인의 단위를 가장 작은 것 부터 정의할 수 있도록 요구사항을 잘 분석하고 나누고 정리할 수 있어야 한다. 여기까지 진행하면 도메인에 대한 영향 범위, 관계성 등을 이해하고 도식화할 수 있다. 이 과정을 통해 우리는 프로젝트를 구성하는 어플리케이션의 구성 요소, 작동 방식 등을 설계하고 구현할 수 있게 된다.<br />
요약하자면,</p>

<ul>
  <li>Core Domain에 집중하고 이해하고 설계할 것</li>
  <li>커뮤니케이션을 할 때에는 Ubiquitous Language를 사용할 것</li>
  <li>Domain model 설계를 위해 Domain Expert(기획, PO 등)의 지속적인 참여를 유도하고, 엔지니어는 이를 지속적으로 표현 및 교차검증할 것</li>
  <li>개발자가 개발/문제해결을 할 때 Domain에 입각해서 생각할 것</li>
</ul>

<h3 id="그럼-ddd를-어떻게-프로젝트-운영에-접목시킬-수-있을까">그럼 DDD를 어떻게 프로젝트 운영에 접목시킬 수 있을까?</h3>

<p>당연하게도 DDD의 가장 첫 시작은 비즈니스, 즉 프로젝트에 대한 이해이다. 비즈니스를 잘 아는 것 만큼 DDD를 성공적으로 운영시킬 수 있는 방법은 없다고 할 수 있겠다. 비즈니스를 구성하는 Domain을 구성하기 위해서는 당연히 비즈니스를 잘 알고 있어야 하기 때문이다. 그리고 domain을 잘 정의하여야 domain model들에 대한 형태와 관계, 작동 방식에 대해 이해할 수 있게 되며, 이것을 기반으로 프로젝트를 어떻게 구성해야 하는지 볼 수가 있다. 반대로, 비즈니스에 대한 전체적인 그림을 확인하고 그에 대한 흐름을 파악한 후, 세세한 domain을 정의하고 그에 대한 유기적인 비즈니스로직을 확인 및 보완하는 방법으로 다가갈 수도 있다. 중요한건 두 가지 방법 모두 비즈니스에 대한 이해가 기본이 되어야 한다는 것이다.<br />
그리고 비즈니스는 상황에 따라서 지속적으로 변화한다. 아키텍트가 이미 모든 내용을 디자인한 후에도 우리는 이 domain model이 지속적으로 변화하는 것을 매우 자주 경험한다. Domain model이 잘 작성되어 있고 그만큼 추상화가 잘 되어 있다면 비교적 적은 공수로도 충분히 리펙토링이 가능하게 된다. 아키텍트가 domain model과 BoundedContext를 최대한 정확하게 디자인하였다면 서비스들이 각자 필요한 정도로 파티셔닝되어 있을 것이고 이는 boundedContext의 경계와 domain model의 관계가 매우 명확하게 정의되었음을 의미한다. 그리고 이는 domain model이 이해하기 쉽고 그만큼 쉽게 분할/병합되기 좋은 형태, 즉 변화에 대응하기 매우 유연한 상태가 되어 있음을 말한다.<br />
이를 통해 우리는,</p>

<ul>
  <li>비즈니스를 보다 명확하게 인지하고 코드화할 수 있다.</li>
  <li>코드가 아닌 프로젝트 구성원간의 상호 커뮤니케이션이 보다 수월하고 오류가 적어진다.</li>
  <li>비즈니스의 변화에 보다 쉽게 대응할 수 있는 준비를 할 수 있다.</li>
</ul>

<h2 id="ddd와-관련된-용어들">DDD와 관련된 용어들</h2>

<h3 id="domain--domain-model">Domain &amp; Domain Model</h3>

<p>Domain은 유저의 행동 혹은 비즈니스, 혹은 목표 그 자체이다. 목표로 하는 그 무엇이든 domain이 될 수 있다. 수학적 모델, 혹은 단순한 계산, BM모델들도 domain이 될 수 있다. 이 domain의 목적이 반영될 수 있도록 잘 추상화하여 코드로 작성하여 프로그램 혹은 어플리케이션 모델을 만들게 되면 이 코드가 domain model이 된다.</p>

<h3 id="context--principle">Context (= Principle)</h3>

<p>Domain을 정의하는 <code class="language-plaintext highlighter-rouge">단어, 문장, 상황</code>. 모델이 크거나 작고, 복잡하거나 단순한 것이 아닌 Domain model 그 자체에 대한 정의를 말하며 model이 전체적으로 일관적이며 논리적이어야 함을 의미한다.</p>

<h3 id="ubiquitous-language">Ubiquitous Language</h3>

<p>Domain/Domain Model을 정의하고 적용하기 위해 개발자를 포함한 프로젝트 관계자들 모두가 사용하는 같은 의미를 가지는 용어를 일컫는다. 이 내용에는 애매모호한 내용은 절대라고 할 정도로 포함되어서는 안된다. Domain을 이해하기 어렵거나 Domain의 표현이 어색해지는 용어는 지양해야 한다. 또한 Domain을 디자인하는 데 있어 방해되는 모호한 표현 혹은 Domain과의 불일치를 만드는 표현을 지양해야 한다. 이렇게 구축된 Ubiquitous Language는 도메인을 이해하기 쉽게 만들며, 보다 명확한 디자인을 만들고, 만들어진 디자인에 대해 원활한 소통을 제공하여 불필요한 커뮤니케이션 비용을 줄여준다.</p>

<h3 id="boundary---bounded-context--context-map">Boundary &amp;  Bounded Context &amp; Context Map</h3>

<p>boundary는 domain을 다루는 팀 조직 구성, 어플리케이션의 일부(sub system), 물리 장비 혹은 db 스키마 등의 일부로 들어갈 수도 있다.<br />
DDD는 거대한 모델을 나누어 명확한 경계를 만들고, 상호간의 명확한 연관성을 만들어주는 작업이다. 이 경계와 경계를 이루는 domain(하나 혹은 다수의)의 상호작용을 boundary라 부른다. 그리고 이 boundary를 설명할 때에 그것을 BoundedContext라고 한다. boundary를 설명하는 context로서 Domain을 정의할 때 <code class="language-plaintext highlighter-rouge">단어, 문장, 상황</code>을 사용해 설명하듯, bounded context는 boundary가 어떤 것인지, 혹은 어떻게 동작하는지 설명할 수 있는 <code class="language-plaintext highlighter-rouge">단어, 문장, 상황</code>을 말한다. 그리고 여러 개의 bounded context간의 관계를 묘사한 <code class="language-plaintext highlighter-rouge">도식도, 문서</code> 를 Context Map이라고 한다</p>

<p><img src="https://martinfowler.com/bliki/images/boundedContext/sketch.png#center" alt="BoundedContext &amp; ContextMap" /></p>

<p class="image-caption"><a href="https://martinfowler.com/bliki/BoundedContext.html">BoundedContext &amp; ContextMap</a></p>

<p>위의 그림에서는 하나의 큰 어플리케이션 모델을 2개의 boundedContext(Sales Context와 Support Context)로 나누고, 각각의 boundary 안에는 다수의 모델이 들어 있다. 그리고 두개의 boundary는 서로의 커뮤니케이션을 위해 Customer/Product model을 가지고 있다.<br />
여기에서는 단지 2개로 BoundedContext를 나누었을 뿐이지만, 사실 이 boundedContext를 나누는 기준은 매우 다양하다. model간의 상호작용, 다루는 팀의 상황 등 어플리케이션의 영역 이외에도 이 내용을 정의하는데 주는 요소는 너무나도 많기 때문에 같은 모델일지어도 상황에 따라 다른 boundedContext를 정의할 수 있으며, 이 때 특별한 이유(타 boundedContext와의 커뮤니케션과 같은)로 boundedContext와 전혀 관계가 없어 보이는 domain model이 포함될 수도 있다. 이는 전혀 이상한 것이 아니다. 우리의 상황을 만드는 것은 어플리케이션이나 프로젝트의 목표가 아닌 우리가 일하는 방향에서 비롯되며, 이는 프로젝트 구성원 간의 커뮤니케이션과 상호작용에서부터 시작함을 기억해야 한다.</p>

<h3 id="continuous-integration">Continuous Integration</h3>

<p>지금은 프로젝트를 진행할 때 자주 거론되는 그 CI가 맞다. 주기적/지속적으로 코드 혹은 관련된 문서, 요소들을 자동적으로 통합 및 테스트를 진행하여 요소들의 파편화를 방지하려는 수단이다. 더불어 이러한 작업을 통해 당사자들간의 이해의 차이를 최소한으로 좁히려는 노력이 포함된다.</p>

<h3 id="layered-architecture">Layered Architecture</h3>

<p>DDD는 매우 복잡한 구조를 처리하기 위해 만들어졌다. 그리고 이것을 한번에 처리하기에는 너무나도 버겁기 때문에 이를 위해 4가지 계층적 구조를 제안한다. 프로그램을 몇몇 레이어로 나누어 설계토록 한다. 각각 레이어는 레이어 내부에서만 응집력을 가지도록 하고, 레이어 간에는 느슨하게 관계를 가질 수 있도록 하는 것이 목표이다.<br />
domain model과 business logic을 서로 격리시키고 infrastructure, user interface, application logic을 business logic에서 격리시킨다. 도메인 모델을 하나의 레이어에 격리되도록 하고, user interface, application, infrastructure code와 격리시킨다. Domain model은 application에서 관리될 수는 있지만 저장 혹은 관계에 대해서는 도메인 객체 내부에서 처리하도록 한다. 이 과정을 통해서 도메인을 더 명확하게 만들 수 있고 비즈니스의 목적을 잘 구현할 수 있게 된다.</p>

<ul>
  <li>User Interface Layer: 유저의 요청이 처리되고 정보를 표현할 수 있는 부분. Contoller, Listener 등</li>
  <li>Application Layer: transaction, dto, domain에 대한 처리 및 관리가 이루어지는 부분. Service/Business Logic 등</li>
  <li>Domain Layer: domain에 대한 상태 및 정보를 담아두는 부분. DTO, Domain-model, Schema 등</li>
  <li>Infrastructure Layer: 위 3개 레이어를 서포트할 수 있는 부분. Repository, Adapter, Framework, Library 등</li>
</ul>

<h3 id="reference">Reference</h3>

<ul>
  <li><a href="https://martinfowler.com/bliki/BoundedContext.html">Figure #1, Martin Fowler: Bounded Context</a></li>
  <li><a href="https://product.kyobobook.co.kr/detail/S000001514402">Erick Evans: 도메인 주도 설계</a></li>
</ul>

<h3 id="appendix">Appendix</h3>

<ul>
  <li>Design Pattern: 프로그램에 대한 추상화된 설명. 그리고 프로그램을 더 구조화된 형태로 작성할 수 있는 방법론</li>
  <li>Module: 소프트웨어의 복잡한 구조를 구성할 수 있는 독립적인 혹은 표준적인 단위. 우리 엔지니어들은 이 모듈을 design pattern을 사용해 추상화시키고 설명한다.</li>
</ul>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="ddd" /><category term="article" /><summary type="html"><![CDATA[서두에 결론을 담아서]]></summary></entry><entry><title type="html">네트워크 장비에 대해서</title><link href="https://soongwonjun.github.io/cs/2023/08/01/Network.html" rel="alternate" type="text/html" title="네트워크 장비에 대해서" /><published>2023-08-01T13:00:00+00:00</published><updated>2023-08-01T13:00:00+00:00</updated><id>https://soongwonjun.github.io/cs/2023/08/01/Network</id><content type="html" xml:base="https://soongwonjun.github.io/cs/2023/08/01/Network.html"><![CDATA[<h2 id="l1-l2-l이-무엇일까">L1? L2? L이 무엇일까?</h2>

<p>네트워크 이론에서 가장 많이 사용되고 적용되는 이론이 OSI 7계층에 대한 내용이다. 컴퓨터 통신을 위한 과정을 7가지 계층에 의해 어떻게 이루어지는지에 대한 설명이 담겨 있으며 이는 하드웨어에도 그대로 녹아 있다. 이제부터 메모해둘 L2 Switch, L3 Router, L4/L7 Loadbalancer에 붙는 L은 모두 OSI 7계층의 몇 번째 계층인지를 의미한다.</p>

<h2 id="l1">L1</h2>

<p>물리 장비를 의미한다. 구리 동축케이블, 광랜 케이블, 그리고 장비들을 연결하는 리피터, 허브 장비를 통칭한다.</p>

<h2 id="l2-switch">L2 Switch</h2>

<p>OSI 7계층의 네트워크 계층에 적용되는 스위치 장비를 의미한다.<br />
스위치의 경우에는 데이터를 보낼 대상을 <code class="language-plaintext highlighter-rouge">MAC Address(Media Access Control Address)</code> 로 판단하며, 각 포트에 할당되어 있는 주소를 사용하여 여러 장비를 거쳐 단말간 통신이 될 수 있도록 한다. 또한 패킷의 내용을 확인은 하지만 내용을 업데이트하지는 않는다.
허브의 경우에는 패킷이 들어왔을 때 자신에게 연결된 모든 네트워크 노드들에 대해 패킷을 전달하지만, 스위치의 경우에는 가야 할 목적지를 명확하게 알 수 있기 때문에 (L3에서 전달되어야 하는 대상을 패킷 프레임 안에 저장해둔다) 대상이 되는 하나의 포트에만 패킷이 전달된다.</p>

<h2 id="l3-router">L3 Router</h2>

<p>OSI 7계층의 네트워크 계층에 적용되는 라우팅 장비를 의미한다.<br />
네트워크 구성을 위해 라우터는 패킷을 전달하고 정보의 가장 효율적인 경로를 선택하는 역할을 한다. 노드들을 연결해 소규모 네트워크를 만드는 스위치들을 연결하여 더 거대한 인터넷 네트워크를 만들어낸다. 이를 위해서 패킷을 목적지까지 전달할 수 있도록 전달 경로를 계산(라우팅)하며, 패킷을 안전하게 전달하기 위해서 에러체크를 위한 기능을 제공한다.<br />
또한 우리가 흔히 사용하는 IP 주소를 인식할 수 있게 되며 ARP를 사용하여 IP 주소와 MAC Address를 연결하여 실제 전송되어야 하는 대상을 확인하여 패킷 프레임 안에 저장해 두는 역할도 수행한다. 이를 위해서 전달되는 패킷의 프레임을 확인하고 내용을 업데이트하기도 한다.</p>

<h2 id="l4l7-loadbalancer">L4/L7 Loadbalancer</h2>

<p>Loadbalancer는 서버에 가해지는 부하를 분산시키는 역할을 하는 하드웨어/소프트웨어이다. 서버와 클라이언트 사이에 위치하고 있으며, Round-Robin, Hash, 접속 수, 다양한 변수의 가중치를 이용한 분산 등 다양한 분산 방식이 존재한다.
L4 Loadbalancer는 OSI 7계층의 전송 계층에서 작동하며, TCP/UDP 프로토콜을 사용한 분산을 지원한다. 가장 기본적인 방법으로 IP단위의 로드밸런싱이 있다.<br />
L7 Loadbalancer는 OSI 7계층의 어플리케이션 계층에서 작동하며, 다양한 옵션들을 적용할 수 있다. 이중 한 가지 예제로 HTTP 요청에 따른 로드밸런싱이 가능하다. 이유는 L7 Loadbalancer는 어플리케이션 레벨의 정보를 처리하는데 이 정보들은 HTTP, SMTP 등 우리가 어플리케이션에서 사용하는 많은 내용들이 포함되기 때문이다.</p>]]></content><author><name>Soongwon Jun</name></author><category term="cs" /><category term="cs" /><category term="network" /><summary type="html"><![CDATA[L1? L2? L이 무엇일까?]]></summary></entry></feed>