<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Dongjun's Blog]]></title><description><![CDATA[AI Solutions Architect & Full-stack Engineer의 기술 블로그.
AI/LLM 엔지니어링, RAG 시스템 설계, 바이브코딩, 그리고 실전에서 배운 문제 해결 경험을 공유합니다.]]></description><link>https://blog.dongjun.win</link><image><url>https://cdn.hashnode.com/uploads/logos/6889d261565bc76a2da0e4d8/00f6ab62-a04e-48d1-bb7f-50ab415aafad.png</url><title>Dongjun&apos;s Blog</title><link>https://blog.dongjun.win</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 23:39:28 GMT</lastBuildDate><atom:link href="https://blog.dongjun.win/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[법률 AI 검색 실험기 (3) — 복수 정답 문제와 LLM Selector 모델 비교]]></title><description><![CDATA[검색 결과에서 정답을 "선택"하는 것도 문제다
법률 QA 시스템에서 검색(retrieval) 품질은 기본 전제다. 검색이 어느 정도 궤도에 오르자, 다음 병목이 드러났다. Top-50 검색 결과 안에 정답 근거가 들어 있는데도 최종 답변에서 빠지는 경우가 생긴 것이다.
예를 들어 "택배 배송 중 물건이 파손되었을 때 누구에게 책임을 물을 수 있는가?"라는 질문에 대해, 검색 결과에는 민법 제756조(사용자책임)가 포함되어 있었다. 그런데 LLM...]]></description><link>https://blog.dongjun.win/legal-ai-search-03-llm-selector-benchmark</link><guid isPermaLink="true">https://blog.dongjun.win/legal-ai-search-03-llm-selector-benchmark</guid><category><![CDATA[AI]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[llm]]></category><category><![CDATA[RAG ]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Tue, 07 Apr 2026 23:31:17 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-6rka7iojioqysoqzvoyxkoyencdsojxri7xsnyqgiuyeoo2dnsltlzjripqg6rkd64eiousuoygnoulpa">검색 결과에서 정답을 "선택"하는 것도 문제다</h2>
<p>법률 QA 시스템에서 검색(retrieval) 품질은 기본 전제다. 검색이 어느 정도 궤도에 오르자, 다음 병목이 드러났다. Top-50 검색 결과 안에 정답 근거가 들어 있는데도 최종 답변에서 빠지는 경우가 생긴 것이다.</p>
<p>예를 들어 "택배 배송 중 물건이 파손되었을 때 누구에게 책임을 물을 수 있는가?"라는 질문에 대해, 검색 결과에는 민법 제756조(사용자책임)가 포함되어 있었다. 그런데 LLM이 답변을 생성하면서 이 조문을 근거로 선택하지 않았다. 50개 후보 중에서 어떤 것이 진짜 근거인지 "골라내는" 단계, 즉 selector가 별도로 필요했다.</p>
<p>이 글은 selector 구조를 설계하고, 여러 LLM 모델을 비교 실험한 과정을 정리한 기록이다.</p>
<h2 id="heading-single-selector-vs-citation-selector">두 가지 접근법: Single Selector vs Citation Selector</h2>
<p>처음에는 selection planner라는 구조를 시도했다. 검색 결과 전체를 보고 어떤 조문이 왜 필요한지를 장문으로 설명하게 하는 방식이었다. 방향은 맞았지만, 출력이 길고 파싱이 불안정했다. 그래서 출력을 직접 근거, 보조 근거, 누락 포인트 세 필드로 줄인 citation selector로 전환했다.</p>
<p>citation selector는 안정적이었다. 전체 11문항을 파싱 오류 없이 완주했고, Selection Recall 27/31(87.1%)을 기록했다. 하지만 여전히 selection miss가 남았다. 이를 해결하기 위해 두 가지 구조를 실험했다.</p>
<ul>
<li><strong>2-pass selector</strong>: coverage pass와 completion pass를 나눠서 2회 호출. 1차에서 넓게 훑고, 2차에서 빠진 것을 보완한다.</li>
<li><strong>single selector</strong>: 1회 호출로 선택과 보완을 동시에 처리. 속도를 줄이는 대신 품질 손실이 있을 수 있다.</li>
</ul>
<p>2-pass selector는 Gemini 3 Flash Preview 기준으로 Selection Recall을 29/31(93.5%)까지 끌어올렸다. Q2(사용자책임)와 Q11(이자소득)의 selection miss가 모두 해결되었다. 다만 질문당 2회 호출이라 latency가 19~39초 수준으로 늘어났다.</p>
<h2 id="heading-66qo642467oeiou5hoq1kdog7ian64e7jmaio2sioynioydmcdtirjroijsnbtrk5zsmkttliq">모델별 비교: 속도와 품질의 트레이드오프</h2>
<p>selector 구조가 잡히자 다음 질문은 자연스럽게 "어떤 모델이 가장 나은가"였다. OpenAI의 GPT-5 mini, GPT-5 nano와 Google의 Gemini 3 Flash Preview, Gemini 2.5 Flash Lite를 비교했다.</p>
<h3 id="heading-2-pass-selector-q2-q11">2-pass selector 비교 (Q2, Q11 기준)</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>Selection Recall</td><td>pass1 평균 latency</td><td>pass2 평균 latency</td></tr>
</thead>
<tbody>
<tr>
<td>GPT-5 mini</td><td>6/6</td><td>4,399ms</td><td>4,956ms</td></tr>
<tr>
<td>GPT-5 nano</td><td>4/6</td><td>2,568ms</td><td>3,207ms</td></tr>
<tr>
<td>Gemini 2.5 Flash Lite</td><td>5/6</td><td>-</td><td>-</td></tr>
<tr>
<td>Gemini 3 Flash Preview</td><td>6/6 (전체 11문항 29/31)</td><td>-</td><td>-</td></tr>
</tbody>
</table>
</div><p>GPT-5 mini는 Q2와 Q11을 모두 해결했지만, GPT-5 nano는 Q11에서 무너졌다. 속도는 빠르지만 품질 손실이 컸다.</p>
<h3 id="heading-single-selector-q2-q11">single selector 비교 (Q2, Q11 기준)</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>Selection Recall</td><td>평균 selector latency</td></tr>
</thead>
<tbody>
<tr>
<td>Gemini 3 Flash Preview</td><td>5/6</td><td>12,011ms</td></tr>
<tr>
<td>Gemini 2.5 Flash Lite</td><td>5/6</td><td>1,659ms</td></tr>
<tr>
<td>GPT-5 mini</td><td>4/6</td><td>3,173ms</td></tr>
<tr>
<td>GPT-5 nano</td><td>3/6</td><td>2,297ms</td></tr>
</tbody>
</table>
</div><p>GPT-5 mini는 2-pass에서 6/6이었지만 single selector에서는 4/6으로 떨어졌다. 반면 Gemini 2.5 Flash Lite는 single selector에서도 5/6을 유지하면서 latency가 1.6초로 가장 빨랐다.</p>
<h3 id="heading-holdout-16-single-selector-answer">holdout 16문항 일반화 검증 (single selector + answer)</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>도메인 커버</td><td>평균 총 시간</td></tr>
</thead>
<tbody>
<tr>
<td>Gemini 2.5 Flash Lite</td><td>3/3</td><td>4.5~5.0초</td></tr>
<tr>
<td>GPT-5 mini</td><td>3/3</td><td>8.8~11.6초</td></tr>
<tr>
<td>GPT-5 nano</td><td>2/3</td><td>5.5~9.6초</td></tr>
<tr>
<td>Gemini 3 Flash Preview</td><td>3/3</td><td>16.8~25.3초</td></tr>
</tbody>
</table>
</div><p>Gemini 2.5 Flash Lite가 속도와 품질의 균형에서 가장 현실적인 결과를 보였다. 5초 이내에 답변이 나오면서 holdout 도메인도 모두 커버했다.</p>
<h2 id="heading-all-in-one-selector-answer">All-in-One: selector와 answer를 합치면?</h2>
<p>selector와 answer를 분리하면 호출이 최소 2회다. 이걸 1회로 합칠 수 있다면? "근거 선택 + 최종 답변 생성"을 한 번에 처리하는 all-in-one 방식을 실험했다.</p>
<h3 id="heading-core-11">Core 11문항 결과</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>조건</td><td>Selection Recall</td><td>Full Recall</td><td>평균 생성 시간</td></tr>
</thead>
<tbody>
<tr>
<td>Gemini 3 Flash Preview (no-web)</td><td>28/31 (90.3%)</td><td>8/11</td><td>14.7초</td></tr>
<tr>
<td>GPT-5.4 (no-web)</td><td>29/31 (93.5%)</td><td>9/11</td><td>11.7초</td></tr>
<tr>
<td>GPT-5.4 (web)</td><td>27/31 (87.1%)</td><td>7/11</td><td>129.1초</td></tr>
</tbody>
</table>
</div><p>GPT-5.4 no-web이 가장 좋았다. 2-pass selector의 최고 성적(29/31, 9/11)과 동일한 recall을 1회 호출로 달성했고, 평균 11.7초였다. 남은 miss는 Q6, Q7뿐이었는데, 이 둘은 애초에 Top-50 검색 결과에 정답이 없는 retrieval miss였다.</p>
<h3 id="heading-holdout-16">Holdout 16문항 결과</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>조건</td><td>도메인 커버</td><td>평균 생성 시간</td></tr>
</thead>
<tbody>
<tr>
<td>GPT-5.4 (no-web)</td><td>14/16</td><td>9.1초</td></tr>
<tr>
<td>Gemini 3 Flash Preview (no-web)</td><td>14/16</td><td>12.7초</td></tr>
<tr>
<td>Gemini 3 Flash Preview (web)</td><td>14/16</td><td>13.8초</td></tr>
<tr>
<td>GPT-5.4 (web)</td><td>14/16</td><td>114.9초</td></tr>
</tbody>
</table>
</div><p>Holdout에서도 GPT-5.4 no-web이 속도와 품질 모두에서 가장 나은 균형을 보였다.</p>
<h2 id="heading-7ju5ioqygoydisdrs7tsobdsnzgg7zqo6ro8oidquldrjidsmyag64uk66w4ioqysoqzva">웹 검색 보조의 효과: 기대와 다른 결과</h2>
<p>웹 검색을 붙이면 retrieval miss를 보완할 수 있을 거라 기대했다. 결과는 정반대였다.</p>
<p>GPT-5.4에 웹 검색을 붙이자 Core 성능이 오히려 떨어졌다. Selection Recall이 93.5%에서 87.1%로 하락했고, latency는 11.7초에서 129.1초로 11배 증가했다. Q2와 Q11에서 새로운 miss가 발생했다. Gemini 3 Flash Preview의 웹 검색 버전은 Q5에서 반복적으로 timeout이 발생해 전체 결과를 안정적으로 수집하지도 못했다.</p>
<p>Holdout에서도 웹 검색 유무에 관계없이 도메인 커버는 14/16으로 동일했다. 웹 검색이 retrieval miss를 자동으로 메우지 않았다.</p>
<h2 id="heading-gpt-54-thinking-perplexity-sonar">GPT-5.4 Thinking과 Perplexity Sonar 추가 실험</h2>
<p>추가로 두 가지 변형을 더 시도했다.</p>
<p><strong>GPT-5.4 reasoning high</strong>: 품질은 유지되었다(Core Q2, Q11 모두 해결). 하지만 평균 생성 시간이 116.1초로 치솟았다. 기존 no-web(11.7초) 대비 품질 이득은 거의 없으면서 latency만 10배 증가했다.</p>
<p><strong>Perplexity Sonar (web)</strong>: Core 평균 9.4초, Holdout 평균 6.2초로 속도는 빨랐다. 하지만 Q2에서 selection miss가 남았다. 빠른 web-assist 후보로는 가치가 있지만, hard case 품질은 GPT-5.4 no-web에 못 미쳤다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>조건</td><td>Core (Q2, Q11)</td><td>Holdout (H2, H8, H16)</td><td>평균 생성 시간</td></tr>
</thead>
<tbody>
<tr>
<td>GPT-5.4 no-web</td><td>6/6</td><td>3/3</td><td>11.7초</td></tr>
<tr>
<td>GPT-5.4 reasoning high</td><td>6/6</td><td>3/3</td><td>116.1초</td></tr>
<tr>
<td>Perplexity Sonar web</td><td>5/6</td><td>3/3</td><td>6.2~9.4초</td></tr>
</tbody>
</table>
</div><h2 id="heading-selector">결론: Selector 실험에서 확인한 것</h2>
<p>이 실험의 결론을 정리한다.</p>
<p><strong>구조가 모델보다 먼저다.</strong> 2-pass selector는 약한 모델(Gemini 2.5 Flash Lite)에서도 selection miss를 줄여줬다. 반면 single selector는 강한 모델(GPT-5 mini)에서도 품질이 떨어졌다. 호출 구조를 어떻게 설계하느냐가 모델 선택보다 영향이 컸다.</p>
<p><strong>강한 모델은 구조를 단순화할 수 있다.</strong> GPT-5.4는 all-in-one 1회 호출로 2-pass selector의 최고 성적을 재현했다. 모델이 충분히 강하면 복잡한 다단계 구조 없이도 같은 품질을 낼 수 있다.</p>
<p><strong>웹 검색은 만능이 아니다.</strong> 검색 보조를 붙인다고 retrieval miss가 해결되지 않았다. 오히려 latency만 크게 늘고 기존에 잘 되던 것까지 흔들렸다. 웹 검색은 별도 경로로 분리해서, 정말 필요한 경우에만 선택적으로 태워야 한다.</p>
<p><strong>실전 기본값은 단순하게.</strong> 최종적으로 실전 기본값 후보는 GPT-5.4 no-web all-in-one이 되었다. Core 29/31(93.5%), Holdout 14/16, 평균 11초 내외. 남은 miss는 selector가 아니라 retrieval 축에서 풀어야 할 문제였다.</p>
<p>selector는 RAG 파이프라인에서 흔히 간과되는 단계다. 검색만 잘 되면 된다고 생각하기 쉽지만, 50개 후보에서 진짜 근거를 골라내는 일은 그 자체로 독립적인 문제다. 그리고 그 문제를 푸는 방법은 모델을 바꾸는 것만이 아니라, 호출 구조를 설계하는 것이다.</p>
]]></content:encoded></item><item><title><![CDATA[법률 AI 검색 실험기 (2) — 임베딩 모델 5종 벤치마크: 법률 도메인 실전 비교]]></title><description><![CDATA[법률 RAG 시스템에서 가장 먼저 결정해야 하는 것은 "어떤 임베딩 모델을 쓸 것인가"다. MTEB 리더보드 점수가 높다고 해서 우리 도메인에서도 잘 동작하리라는 보장은 없다. 한국 법률 조문이라는 특수한 코퍼스 위에서, 실제 질문셋으로 직접 비교하는 것이 유일한 방법이다.
이 글에서는 임베딩 모델 5종을 동일 조건에서 평가한 과정과 결과를 공유한다. 모델 선택 하나가 retrieval 성능의 천장을 결정한다.

평가 대상: 임베딩 모델 5종
...]]></description><link>https://blog.dongjun.win/legal-ai-search-02-embedding-model-benchmark</link><guid isPermaLink="true">https://blog.dongjun.win/legal-ai-search-02-embedding-model-benchmark</guid><category><![CDATA[AI]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[#Embeddings]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 06 Apr 2026 06:29:58 GMT</pubDate><content:encoded><![CDATA[<p>법률 RAG 시스템에서 가장 먼저 결정해야 하는 것은 "어떤 임베딩 모델을 쓸 것인가"다. MTEB 리더보드 점수가 높다고 해서 우리 도메인에서도 잘 동작하리라는 보장은 없다. 한국 법률 조문이라는 특수한 코퍼스 위에서, 실제 질문셋으로 직접 비교하는 것이 유일한 방법이다.</p>
<p>이 글에서는 임베딩 모델 5종을 동일 조건에서 평가한 과정과 결과를 공유한다. 모델 선택 하나가 retrieval 성능의 천장을 결정한다.</p>
<hr />
<h2 id="heading-5">평가 대상: 임베딩 모델 5종</h2>
<p>평가에 사용한 모델은 다음 5종이다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>파라미터</td><td>벡터 차원</td><td>개발사</td><td>특징</td></tr>
</thead>
<tbody>
<tr>
<td>BGE-M3</td><td>568M</td><td>1024</td><td>BAAI</td><td>다국어, dense/sparse/multi-vector 지원</td></tr>
<tr>
<td>pplx-embed-v1-0.6b</td><td>0.6B</td><td>1024</td><td>Perplexity</td><td>경량 임베딩, INT8 네이티브</td></tr>
<tr>
<td>pplx-embed-v1-4b</td><td>4B</td><td>2560</td><td>Perplexity</td><td>대형 임베딩, MTEB 최상위권</td></tr>
<tr>
<td>pplx-embed-context-v1-0.6b</td><td>0.6B</td><td>1024</td><td>Perplexity</td><td>문서 문맥 인식 경량 모델</td></tr>
<tr>
<td>pplx-embed-context-v1-4b</td><td>4B</td><td>2560</td><td>Perplexity</td><td>문서 문맥 인식 대형 모델</td></tr>
</tbody>
</table>
</div><p>BGE-M3는 BAAI에서 공개한 다국어 임베딩 모델로, XLM-RoBERTa 기반이며 8192 토큰까지 처리할 수 있다. dense, sparse, multi-vector 세 가지 retrieval 방식을 동시에 지원하는 것이 특징이다.</p>
<p>Perplexity의 pplx-embed 시리즈는 2종 x 2사이즈, 총 4개 모델로 구성된다. <code>pplx-embed-v1</code>은 표준 dense retrieval용이고, <code>pplx-embed-context-v1</code>은 문서 수준 문맥을 반영하여 청크를 임베딩하는 contextual embedding 모델이다. context 모델은 인덱싱 시에만 사용하고, 쿼리 임베딩에는 표준 v1을 사용하는 비대칭 구조를 갖는다. Qwen3 기반으로 학습되었으며, quantization-aware training을 통해 INT8 임베딩을 네이티브로 생성한다.</p>
<hr />
<h2 id="heading-7yj6rcaio2zmoqyvq">평가 환경</h2>
<h3 id="heading-7l2u7y287iqk">코퍼스</h3>
<p>한국 주요 기본법(민법, 형법, 상법, 민사소송법, 형사소송법, 헌법, 행정소송법 등)에서 추출한 <strong>약 2,300여 개 조문</strong>을 코퍼스로 사용했다. 5개 모델 모두 정확히 동일한 문서 세트로 벡터 DB 컬렉션을 구성했다. 데이터 정합화 작업을 거쳐, 모든 컬렉션의 문서 수와 구조가 일치하는 것을 사전 검증했다.</p>
<h3 id="heading-7kei66y47iwl">질문셋</h3>
<p>총 84개 질문을 다섯 유형으로 나누어 평가했다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>질문 유형</td><td>문항 수</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>단일정답 직접 질문</td><td>20</td><td>특정 조문을 직접 묻는 질문</td></tr>
<tr>
<td>단일정답 시나리오 질문</td><td>20</td><td>실생활 시나리오로 우회하여 묻는 질문</td></tr>
<tr>
<td>단일정답 세법 질문</td><td>17</td><td>세법 도메인 특화 질문</td></tr>
<tr>
<td>복수정답 질문</td><td>11</td><td>여러 조문이 정답인 질문 (정답 조문 31개)</td></tr>
<tr>
<td>Holdout 질문</td><td>16</td><td>도메인 커버리지 확인용</td></tr>
</tbody>
</table>
</div><p>복수정답 질문이 가장 중요한 평가 축이다. 단일정답은 대부분의 모델이 쉽게 맞추지만, 여러 조문을 빠짐없이 찾아와야 하는 복수정답에서 모델 간 차이가 극명하게 드러났다.</p>
<h3 id="heading-67me6rwqioyhsoqxta">비교 조건</h3>
<p>두 가지 모드로 비교했다.</p>
<ul>
<li><strong>raw</strong>: 사용자 질문 원문 그대로 검색. vector hybrid (dense + BM42 sparse) 사용.</li>
<li><strong>prerewrite</strong>: LLM 프리라이터로 질문을 변환한 뒤 검색. vector + graph 검색을 RRF로 병합.</li>
</ul>
<p>추가로, graph 검색을 완전히 제외한 <strong>vector-only</strong> 조건도 별도 실험했다.</p>
<h3 id="heading-66mu7yq466at">메트릭</h3>
<ul>
<li><strong>@K recall</strong>: K개 결과 안에 정답 조문이 몇 개 포함되었는가</li>
<li><strong>Full match</strong>: 복수정답 질문에서, 한 질문의 정답 조문을 모두 찾았는가 (11문항 중 몇 문항 완전 적중)</li>
<li>K = 10, 20, 50으로 측정</li>
</ul>
<hr />
<h2 id="heading-1-hybrid-dense-bm42-graph">결과 1: Hybrid 검색 (dense + BM42 + graph)</h2>
<p>복수정답 @50 기준이 모델 선택의 핵심 지표였다.</p>
<h3 id="heading-50">복수정답 @50 비교</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>Raw Recall</td><td>Raw Full</td><td>Raw 속도</td><td>Pre Recall</td><td>Pre Full</td><td>Pre 속도</td></tr>
</thead>
<tbody>
<tr>
<td>BGE-M3</td><td>28/31</td><td>8/11</td><td>103ms</td><td>29/31</td><td>9/11</td><td>1,536ms</td></tr>
<tr>
<td>pplx-embed-v1-0.6b</td><td>28/31</td><td>8/11</td><td>322ms</td><td>29/31</td><td>9/11</td><td>1,700ms</td></tr>
<tr>
<td><strong>pplx-embed-v1-4b</strong></td><td><strong>30/31</strong></td><td><strong>10/11</strong></td><td>319ms</td><td><strong>30/31</strong></td><td><strong>10/11</strong></td><td>1,708ms</td></tr>
<tr>
<td>pplx-embed-context-v1-0.6b</td><td>26/31</td><td>6/11</td><td>323ms</td><td>29/31</td><td>9/11</td><td>1,712ms</td></tr>
<tr>
<td>pplx-embed-context-v1-4b</td><td>28/31</td><td>8/11</td><td>332ms</td><td><strong>30/31</strong></td><td><strong>10/11</strong></td><td>1,714ms</td></tr>
</tbody>
</table>
</div><p><code>pplx-embed-v1-4b</code>가 raw 모드에서 이미 30/31 recall, 10/11 full match를 달성했다. 프리라이터를 붙여도 결과가 동일했다. 즉 이 모델은 질문 원문만으로도 충분히 강력한 retrieval을 보여준 것이다.</p>
<p>반면 BGE-M3와 0.6b 모델들은 @50에서 28/31에 머물렀고, 프리라이터를 적용해야 29/31까지 올라갔다.</p>
<h3 id="heading-64uo7j287kcv64u1ioyaloyvvq">단일정답 요약</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>질문 유형</td><td>핵심 결과</td></tr>
</thead>
<tbody>
<tr>
<td>직접 질문 20</td><td>전 모델 raw @10 = 20/20. 프리라이터 적용 시 오히려 @10이 19/20으로 하락하는 경우 발생</td></tr>
<tr>
<td>시나리오 질문 20</td><td>4b 모델 2종은 raw @10 = 20/20. BGE-M3 raw @10 = 18/20</td></tr>
<tr>
<td>세법 질문 17</td><td>BGE-M3만 raw @10 = 16/17, 나머지 4종은 17/17</td></tr>
</tbody>
</table>
</div><p>단일정답에서는 대부분의 모델이 높은 성능을 보였지만, 시나리오 질문과 세법 질문에서 4b 모델의 우위가 확인되었다.</p>
<hr />
<h2 id="heading-2-vector-only-graph">결과 2: Vector-Only 검색 (graph 제외)</h2>
<p>graph 검색 없이 순수 벡터 검색만으로 평가한 결과도 동일한 결론을 가리켰다.</p>
<h3 id="heading-vector-only-k">복수정답 Vector-Only 비교 (주요 K 값)</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>Mode</td><td>@20</td><td>F@20</td><td>@30</td><td>F@30</td><td>@50</td><td>F@50</td><td>속도</td></tr>
</thead>
<tbody>
<tr>
<td>BGE-M3</td><td>raw</td><td>21/31</td><td>4/11</td><td>23/31</td><td>5/11</td><td>28/31</td><td>8/11</td><td>113ms</td></tr>
<tr>
<td>BGE-M3</td><td>pre</td><td>21/31</td><td>5/11</td><td>22/31</td><td>6/11</td><td>27/31</td><td>8/11</td><td>1,291ms</td></tr>
<tr>
<td>pplx-v1-0.6b</td><td>raw</td><td>24/31</td><td>5/11</td><td>26/31</td><td>7/11</td><td>28/31</td><td>8/11</td><td>336ms</td></tr>
<tr>
<td><strong>pplx-v1-4b</strong></td><td><strong>raw</strong></td><td><strong>25/31</strong></td><td><strong>7/11</strong></td><td><strong>26/31</strong></td><td><strong>7/11</strong></td><td><strong>30/31</strong></td><td><strong>10/11</strong></td><td><strong>323ms</strong></td></tr>
<tr>
<td>pplx-v1-4b</td><td>pre</td><td>24/31</td><td>5/11</td><td>27/31</td><td>7/11</td><td>29/31</td><td>9/11</td><td>1,501ms</td></tr>
<tr>
<td>pplx-ctx-v1-0.6b</td><td>raw</td><td>23/31</td><td>6/11</td><td>24/31</td><td>6/11</td><td>26/31</td><td>6/11</td><td>319ms</td></tr>
<tr>
<td>pplx-ctx-v1-4b</td><td>raw</td><td>26/31</td><td>7/11</td><td>27/31</td><td>7/11</td><td>28/31</td><td>8/11</td><td>326ms</td></tr>
</tbody>
</table>
</div><p>여기서 주목할 점이 두 가지 있다.</p>
<p>첫째, <strong>vector-only에서도 <code>pplx-embed-v1-4b raw</code>가 @50 = 30/31로 최고 성능</strong>이었다. graph 검색 없이도 이 모델의 벡터 품질 자체가 우수하다는 뜻이다.</p>
<p>둘째, <strong>프리라이터가 vector-only에서는 오히려 성능을 깎는 경우가 있었다</strong>. <code>pplx-embed-v1-4b</code>는 프리라이터 적용 시 30/31에서 29/31로 하락했고, BGE-M3도 28/31에서 27/31로 떨어졌다. 프리라이터가 graph 검색과 결합될 때는 도움이 되지만, 벡터 단독 검색에서는 오히려 원래 질문의 의미를 왜곡할 수 있다는 신호였다.</p>
<h3 id="heading-k">K값에 따른 성능 변화</h3>
<p><code>pplx-embed-v1-4b raw</code>의 복수정답 recall 변화를 K값별로 보면:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>K</td><td>Recall</td><td>Full Match</td></tr>
</thead>
<tbody>
<tr>
<td>10</td><td>19/31</td><td>2/11</td></tr>
<tr>
<td>20</td><td>25/31</td><td>7/11</td></tr>
<tr>
<td>30</td><td>26/31</td><td>7/11</td></tr>
<tr>
<td>40</td><td>28/31</td><td>8/11</td></tr>
<tr>
<td>50</td><td>30/31</td><td>10/11</td></tr>
</tbody>
</table>
</div><p>K20과 K50 사이에 정보 손실이 상당히 크다. K20에서 25/31이던 recall이 K50에서 30/31까지 올라간다. "프리라이터로 K를 줄일 수 있다"는 가설은 이번 실험에서 지지되지 않았고, 복수정답 시나리오에서는 충분한 K가 확보되어야 한다는 결론에 이르렀다.</p>
<hr />
<h2 id="heading-7ian64eiou5hoq1ka">속도 비교</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>모델</td><td>Raw 평균</td><td>Prerewrite 평균</td></tr>
</thead>
<tbody>
<tr>
<td>BGE-M3</td><td>~103ms</td><td>~1,536ms</td></tr>
<tr>
<td>Perplexity 계열 4종</td><td>~309-342ms</td><td>~1,400-1,714ms</td></tr>
</tbody>
</table>
</div><p>BGE-M3가 raw 기준 약 3배 빠르다. 다만 prerewrite를 적용하면 LLM 호출 비용이 지배적이 되면서 모델 간 속도 차이가 줄어든다. raw 모드에서 Perplexity 계열은 약 320ms 수준으로, 실서비스에서 충분히 사용 가능한 범위다.</p>
<hr />
<h2 id="heading-pplx-embed-v1-4b">최종 선택: pplx-embed-v1-4b</h2>
<p>종합적으로 <code>pplx-embed-v1-4b</code>를 retrieval 기본 모델로 선택했다. 이유는 다음과 같다.</p>
<p><strong>1. 복수정답 retrieval 최고 성능</strong></p>
<p>가장 까다로운 복수정답 @50에서 30/31 recall, 10/11 full match. hybrid든 vector-only든 동일하게 최상위였다.</p>
<p><strong>2. 프리라이터 없이도 강력한 성능</strong></p>
<p>다른 모델들은 프리라이터를 붙여야 성능이 올라갔지만, 이 모델은 raw 질문만으로 이미 최고 수준에 도달했다. 이는 파이프라인을 단순하게 유지할 수 있다는 실질적인 장점이다.</p>
<p><strong>3. 단일정답에서도 회귀 없음</strong></p>
<p>직접 질문, 시나리오 질문, 세법 질문 모두에서 최상위 또는 동률. 복수정답에서 강하다고 해서 단일정답이 약해지는 트레이드오프가 없었다.</p>
<p><strong>4. 허용 가능한 지연 시간</strong></p>
<p>raw 기준 약 320ms. BGE-M3보다는 느리지만, 법률 QA 서비스의 응답 시간 예산 안에 충분히 들어온다.</p>
<hr />
<h2 id="heading-7j20ioylpo2xmoyxkoyencdtmzxsnbjtlzwg6rkd">이 실험에서 확인한 것</h2>
<ul>
<li><strong>벤치마크 점수와 도메인 성능은 다르다.</strong> MTEB 리더보드에서의 순위가 한국 법률 도메인에서의 순위를 보장하지 않는다. 직접 평가 외에 지름길은 없다.</li>
<li><strong>데이터 정합화가 공정한 비교의 전제 조건이다.</strong> 5개 컬렉션의 문서 수가 불일치하는 상태에서는 비교 자체가 무의미하다. 2,336개로 맞추는 작업이 평가보다 먼저 수행되어야 한다.</li>
<li><strong>프리라이터는 만능이 아니다.</strong> "질문을 다듬으면 무조건 좋아진다"는 직관과 달리, 특정 모델/조건에서는 오히려 성능이 하락했다. 특히 vector-only 검색에서 이 현상이 두드러졌다.</li>
<li><strong>복수정답이 진짜 변별력이다.</strong> 단일정답은 대부분의 모델이 쉽게 맞추므로 모델을 구분하기 어렵다. 여러 조문을 동시에 찾아야 하는 복수정답 시나리오가 실질적인 벤치마크다.</li>
<li><strong>K값은 넉넉하게.</strong> 복수정답에서 K20과 K50 사이의 정보 손실이 크다. K50을 유지한다.</li>
</ul>
<p>법률 RAG의 핵심 파이프라인에서, 임베딩 모델 선택은 모든 후속 단계의 성능 상한을 결정하는 첫 번째 관문이다. 이 단계에서의 비교 실험은 생략할 수 없다.</p>
]]></content:encoded></item><item><title><![CDATA[법률 AI 검색 실험기 (1) — 벡터 검색이 실패하는 이유]]></title><description><![CDATA[도입: 법률 QA를 만들면서 마주한 첫 번째 벽
법률 질의응답 시스템을 만드는 일은, 처음에는 RAG(Retrieval-Augmented Generation)의 교과서적 응용처럼 보였습니다. 법 조문을 임베딩해서 벡터 DB에 넣고, 사용자 질문과 유사한 조문을 검색한 뒤, LLM이 답변을 생성하면 되니까요.
실제로 단일 정답 질문 -- "주택임대차보호법상 대항력은 언제 취득하나요?" 같은 -- 에는 이 방식이 잘 작동했습니다. 해당 조문과 질문...]]></description><link>https://blog.dongjun.win/legal-ai-search-01-why-vector-search-fails</link><guid isPermaLink="true">https://blog.dongjun.win/legal-ai-search-01-why-vector-search-fails</guid><category><![CDATA[AI]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[search]]></category><category><![CDATA[Vector Search]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 06 Apr 2026 06:25:56 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-qa">도입: 법률 QA를 만들면서 마주한 첫 번째 벽</h2>
<p>법률 질의응답 시스템을 만드는 일은, 처음에는 RAG(Retrieval-Augmented Generation)의 교과서적 응용처럼 보였습니다. 법 조문을 임베딩해서 벡터 DB에 넣고, 사용자 질문과 유사한 조문을 검색한 뒤, LLM이 답변을 생성하면 되니까요.</p>
<p>실제로 단일 정답 질문 -- "주택임대차보호법상 대항력은 언제 취득하나요?" 같은 -- 에는 이 방식이 잘 작동했습니다. 해당 조문과 질문의 텍스트 유사도가 높기 때문입니다.</p>
<p>문제는 현실의 법률 질문이 그렇게 단순하지 않다는 데서 시작됩니다. "부당해고 당했는데 어떻게 하나요?"라는 질문에 정확히 답하려면 근로기준법 23조(해고제한), 26조(해고예고), 28조(구제신청) 세 조문이 모두 필요합니다. 그런데 벡터 검색은 보통 28조(구제신청)만 찾고, 나머지 두 조문은 Top-50에도 들어오지 않았습니다.</p>
<p>이 글은 저희가 법률 QA 시스템을 만들면서 벡터 검색의 구조적 한계를 발견하고, 그 원인을 분석한 과정을 다룹니다.</p>
<hr />
<h2 id="heading-66y47kccioygleydmdog67o17iiyioygleultsdsp4jrrljsnbtrnoa">문제 정의: 복수 정답 질문이란</h2>
<p>법률 상담에서 사용자가 던지는 질문은 대부분 하나의 조문으로 답할 수 없습니다. 하나의 상황에 여러 법률 조항이 얽혀 있기 때문입니다.</p>
<p>저희는 이런 질문을 "복수 정답 질문"이라고 정의하고, 정답이 2~4개 조문인 11개 평가 문항을 설계했습니다. 예를 들면 이런 것들입니다.</p>
<ul>
<li>"부당해고 당했는데 어떻게 하나요?" -- 정답: 해고제한(23조), 해고예고(26조), 구제신청(28조)</li>
<li>"세금을 과다하게 부과한 것 같아요" -- 정답: 경정청구(45조의2), 불복(55조), 청구기간(61조)</li>
<li>"양도차익이 4억인데 세금이 얼마나?" -- 정답: 양도소득 범위(94조), 장기보유공제(95조), 세율(104조)</li>
</ul>
<p>이 질문들의 공통점은, 사용자가 명시적으로 언급하지 않은 조문이 정답에 포함되어 있다는 점입니다. "부당해고 어떻게 하나요"라는 질문에는 "해고예고"라는 단어가 없고, "세금이 과다하다"는 말에는 "심판청구"라는 단어가 없습니다.</p>
<hr />
<h2 id="heading-11">실제 데이터: 복수 정답 11문항 평가 결과</h2>
<p>저희는 여러 임베딩 모델을 비교 평가했습니다. 핵심 지표는 두 가지입니다.</p>
<ul>
<li><strong>Article Recall@K</strong>: 전체 정답 조문 31개 중 Top-K 안에 들어온 수</li>
<li><strong>Question Full Recall@K</strong>: 11개 질문 중 모든 정답 조문이 Top-K 안에 들어온 질문 수</li>
</ul>
<p>초기 평가에서 가장 성능이 좋았던 bge-m3 모델의 결과입니다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>지표</td><td>@5</td><td>@10</td></tr>
</thead>
<tbody>
<tr>
<td>Article Recall</td><td>-</td><td>14/31 (45%)</td></tr>
<tr>
<td>Question Full Recall</td><td>0/11</td><td>1/11 (9%)</td></tr>
</tbody>
</table>
</div><p>Top-10 기준으로 정답 조문의 절반도 찾지 못했고, 11개 질문 중 모든 정답을 다 찾은 문항은 단 1개뿐이었습니다.</p>
<p>이후 임베딩 모델을 교체하고 Top-K를 50까지 확대한 최고 baseline(pplx-embed-v1-4b)에서는 상황이 많이 개선되었습니다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>지표</td><td>@50</td></tr>
</thead>
<tbody>
<tr>
<td>Article Recall</td><td>30/31 (97%)</td></tr>
<tr>
<td>Question Full Recall</td><td>10/11 (91%)</td></tr>
</tbody>
</table>
</div><blockquote>
<p><strong>참고</strong>: 이 수치는 초기 평가 기준 기준이다. 이후 multi-2 문항의 정답 정의를 재검토하면서(민법 766조 → 756조) benchmark를 수정했고, 최종적으로는 graph 검색과 결합하여 31/31, 11/11을 달성했다. 이 과정은 이후 "1차 아키텍처 확정: 실험에서 운영으로" 편에서 자세히 다룬다.</p>
</blockquote>
<p>Top-50까지 확대하니 대부분의 조문을 회수할 수 있었습니다. 하지만 여전히 놓치는 조문이 있었고, 더 중요한 것은 Top-50이라는 범위 자체가 실용적이지 않다는 점입니다. 50개 조문을 LLM에 넘기는 것은 비용과 레이턴시 면에서 부담이 크고, 노이즈가 많아지면 LLM의 선별 정확도도 떨어집니다.</p>
<p>End-to-end 기준 최고 성능은 <code>gpt-5.4 no-web all-in-one</code> 조합으로, Selection Recall 29/31, Full Recall 9/11을 기록했습니다. 하지만 이 수치도 결국 retrieval 단계에서 후보군에 포함되지 않은 조문은 아무리 좋은 LLM을 써도 찾을 수 없다는 한계를 보여줍니다.</p>
<p><strong>핵심 병목은 LLM 선별(selection)이 아니라 검색(retrieval) 자체</strong>였습니다.</p>
<hr />
<h2 id="heading-67kh7yswioqygoydieydgcdsmzwg7iuk7yyo7zwy64qu6rca">벡터 검색은 왜 실패하는가</h2>
<p>벡터 검색의 원리는 "텍스트가 의미적으로 비슷하면 벡터 공간에서 가까이 위치한다"는 전제에 기반합니다. 이 전제는 많은 경우에 유효하지만, 법률 도메인에서는 구조적으로 맞지 않는 상황이 자주 발생합니다.</p>
<h3 id="heading-7j2y6647kcbioycooycrouphoyzgcdrhbzrpqzsoieg6rsa6roe7j2yioq0toumra">의미적 유사도와 논리적 관계의 괴리</h3>
<p>질문별로 벡터 검색이 놓친 조문을 분석하면 명확한 패턴이 보입니다.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>질문</td><td>찾은 조문</td><td>놓친 조문</td><td>놓친 이유</td></tr>
</thead>
<tbody>
<tr>
<td>계약해제 원상회복+손배</td><td>548조(해제효과)</td><td>551조(해제와 손배)</td><td>같은 제도의 다른 효과</td></tr>
<tr>
<td>불법행위+사용자배상</td><td>750조(불법행위)</td><td>756조(사용자책임)</td><td>같은 불법행위의 책임 귀속</td></tr>
<tr>
<td>대항력+소액임차인</td><td>8조(소액임차인)</td><td>3조(대항력)</td><td>전제조건</td></tr>
<tr>
<td>해고사유+예고+구제</td><td>28조(구제신청)</td><td>23조, 26조</td><td>절차 체인</td></tr>
<tr>
<td>경정청구+이의+심판</td><td>45의2(경정청구)</td><td>55조, 61조</td><td>불복 절차 체인</td></tr>
<tr>
<td>양도소득+공제+세율</td><td>95조(장기보유공제)</td><td>94조, 104조</td><td>계산 체인</td></tr>
</tbody>
</table>
</div><p>벡터 검색이 놓치는 조문들은 질문과 <strong>텍스트가 안 닮았지만 논리적으로는 반드시 필요한</strong> 조문들입니다. "부당해고 당했는데 어떻게 하나요"라는 질문과 "해고예고" 조문 사이에는 텍스트 유사도가 낮습니다. 하지만 해고 절차를 이해하는 사람이라면 이 둘이 연결되어 있다는 걸 압니다.</p>
<p>임베딩 모델은 아무리 좋아도 텍스트를 고차원 벡터로 압축하는 과정에서 이런 논리적 관계 정보를 잃어버립니다. 이것은 모델의 성능 문제가 아니라 bi-encoder 아키텍처의 구조적 한계입니다.</p>
<h3 id="heading-miss">다섯 가지 miss 패턴</h3>
<p>놓친 조문들을 관통하는 관계 유형을 정리하면 다섯 가지로 분류됩니다.</p>
<ol>
<li><strong>절차 체인(PROCEDURE_CHAIN)</strong>: 같은 법적 절차의 단계들. 해고 제한 -&gt; 해고 예고 -&gt; 구제 신청처럼, 하나의 절차를 구성하는 조문들이 흩어져 있는 경우.</li>
<li><strong>계산 체인(CALCULATION_CHAIN)</strong>: 같은 세금이나 금액 계산의 구성요소. 소득 정의 -&gt; 공제 -&gt; 세율처럼, 하나의 계산 흐름에 속하지만 각각 별도의 조문인 경우.</li>
<li><strong>전제조건(PREREQUISITE)</strong>: A 권리가 성립하려면 B 조건이 필요한 경우. 갱신청구권을 행사하려면 먼저 대항력을 갖추어야 하는 것처럼.</li>
<li><strong>같은 원인의 다른 효과(EFFECT_OF)</strong>: 계약 해제라는 같은 원인에서 원상회복과 손해배상이라는 다른 효과가 나오는 경우.</li>
<li><strong>기간/제한(LIMITATION)</strong>: 권리에 딸린 기간 제한. 임차권의 존속기간이나 갱신청구 기한처럼.</li>
</ol>
<p>이 다섯 패턴은 모두 <strong>"텍스트로는 안 닮았지만 논리적으로 연결된"</strong> 관계입니다. 벡터 검색이 원리적으로 포착하기 어려운 종류의 관계이며, 이것이 법률 도메인에서 벡터 검색만으로는 충분하지 않은 근본적인 이유입니다.</p>
<hr />
<h2 id="heading-7jef6roe7jeq7isc64eioqwmeydgcdrrljsojzrpbwg6rkq6rogioyeioulpa">업계에서도 같은 문제를 겪고 있다</h2>
<p>이 문제는 저희만 겪는 것이 아닙니다. 최근 RAG 연구에서 벡터 검색의 멀티홉 추론 한계는 핵심 연구 주제로 부상하고 있습니다.</p>
<p>2025년 ACM TKDD에 게재된 멀티홉 QA 연구에서는 기존의 반복적 검색(iterative retrieval) 방법이 검색 횟수가 늘어날수록 원래 추론 경로에서 벗어나는 "쿼리 드리프트" 문제를 보고했습니다. 저희가 쿼리 분해(query decomposition)를 시도했을 때 경험한 것과 정확히 같은 현상입니다 -- 분해된 서브쿼리가 원래 의미에서 드리프트하면서 단일 정답 문항의 성능이 20/20에서 16/20으로 급락했습니다.</p>
<p>2025년 발표된 HopRAG 논문(arXiv:2502.12442)은 벡터 유사도 기반 검색이 논리적 관계를 포착하지 못하는 한계를 지적하며, 그래프 기반 검색으로 멀티홉 추론을 지원하는 방법을 제안했습니다. 또한 인도의 법률 AI를 다룬 Domain-Partitioned Hybrid RAG 연구(arXiv:2602.23371)는 법률 코퍼스를 판례, 법령, 헌법으로 분리한 뒤 Neo4j 기반 법률 지식 그래프로 관계형 쿼리와 멀티홉 추론을 지원하는 아키텍처를 제안했는데, 이는 저희가 독립적으로 도달한 결론과 놀라울 정도로 유사합니다.</p>
<p>결국 업계 전반에서 "벡터 검색만으로는 부족하다"는 공감대가 형성되고 있으며, 특히 법률처럼 조문 간 논리적 관계가 핵심인 도메인에서는 그래프 기반 확장이 사실상 필수적인 보완재로 논의되고 있습니다.</p>
<hr />
<h2 id="heading-7iuc64e7zai7kea66emiou2goyhse2wiounmcdqsoprk6q">시도했지만 부족했던 것들</h2>
<p>벡터 검색의 한계를 우회하기 위해 몇 가지 방법을 시도했습니다.</p>
<h3 id="heading-query-decomposition">쿼리 분해 (Query Decomposition)</h3>
<p>질문을 여러 서브쿼리로 분해한 뒤 각각 검색하고 RRF(Reciprocal Rank Fusion)로 병합하는 방식입니다. 결과는 복수 정답에서 소폭 개선(+2~3 Q.Full)이 있었지만, <strong>단일 정답 문항에서 심각한 성능 저하</strong>가 발생했습니다. pplx-embed-v1-4b 모델은 세법 문항에서 17/17이 0/17으로 완전히 무너졌습니다. 분해된 서브쿼리가 원래 맥락에서 벗어나는 쿼리 드리프트가 원인이었습니다.</p>
<h3 id="heading-agentic-rag">Agentic RAG</h3>
<p>LLM이 검색 결과를 보고, 부족하다고 판단하면 재검색하는 방식입니다. 이론적으로는 매력적이지만 근본적인 순환 논리가 있습니다. "결과가 충분한지" 판단하려면 이미 법률 지식이 있어야 합니다. 게다가 비용과 레이턴시가 3~10배 증가합니다.</p>
<h3 id="heading-co-citation">Co-citation 기반 확장</h3>
<p>판례에서 함께 인용된 조문을 확장하는 방식입니다. 저희 MongoDB에는 판례 73,032건에서 추출한 158,152건의 법조문 참조 데이터가 이미 있었습니다. 실제로 근로기준법 28조(구제신청)에서 출발하면, 관련 판례 177건을 거쳐 23조(해고제한)가 148회 공출현하는 것을 확인할 수 있었습니다.</p>
<p>하지만 데이터 폭발 문제가 심각했습니다. 민법 750조(불법행위)처럼 범용 조항 하나만 걸리면 관련 판례가 2,173건, 공출현 조문이 1,307개로 폭발합니다. 또한 co-citation은 통계적 상관관계이지 논리적 관계가 아닙니다. 28조와 26조(해고예고)는 판례에서 함께 인용되는 빈도가 낮지만, 논리적으로는 해고 절차의 핵심 구성요소입니다.</p>
<hr />
<h2 id="heading-64uk7j2mioq4gcdsmijqs6a">다음 글 예고</h2>
<p>이 글에서 확인한 것은 명확합니다. 법률 QA에서 벡터 검색은 필요하지만 충분하지 않습니다. 텍스트 유사도로는 포착할 수 없는 논리적 관계 -- 절차 체인, 계산 체인, 전제조건, 기간 제한 -- 가 법률 질의응답의 정확도를 결정합니다.</p>
<p>이 <strong>법률 AI 검색 실험기</strong> 시리즈에서는 이 문제를 실제로 어떻게 풀어갔는지를 다룹니다. 임베딩 모델 벤치마크, LLM selector 비교, query rewriting, Graph RAG 도입, 멀티 컬렉션 라우팅까지 — 한국 법률 도메인에서 RAG를 운영 가능한 수준까지 끌어올리는 과정을 한 편씩 기록할 예정입니다.</p>
<p>다음 편에서는 법률 도메인에 맞는 임베딩 모델을 어떻게 골랐는지, 5종 모델을 직접 비교한 실험 결과를 다룹니다.</p>
<p>법률 QA를 만들면서 배운 것은, 검색 시스템의 정확도는 임베딩 모델의 성능이 아니라 "어떤 종류의 관계를 포착할 수 있느냐"에 달려 있다는 것입니다. 벡터 검색이 잘하는 것(텍스트 유사도)과 법률 도메인이 요구하는 것(논리적 관계) 사이의 간극을 메우는 것이 이 시리즈의 주제입니다.</p>
<hr />
<p><strong>참고 자료:</strong></p>
<ul>
<li><a target="_blank" href="https://arxiv.org/abs/2502.12442">HopRAG: Multi-Hop Reasoning for Logic-Aware Retrieval-Augmented Generation</a></li>
<li><a target="_blank" href="https://arxiv.org/html/2602.23371v1">Domain-Partitioned Hybrid RAG for Legal Reasoning</a></li>
<li><a target="_blank" href="https://dl.acm.org/doi/10.1145/3789506">Retrieval-Augmented Generation for Multi-Hop Question Answering Based on Structured Planning (ACM TKDD)</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-solve-5-common-rag-failures-with-knowledge-graphs/">How to Solve 5 Common RAG Failures with Knowledge Graphs</a></li>
<li><a target="_blank" href="https://towardsdatascience.com/vector-search-is-not-all-you-need-ecd0f16ad65e/">Vector Search Is Not All You Need (Towards Data Science)</a></li>
<li><a target="_blank" href="https://neo4j.com/blog/genai/advanced-rag-techniques/">Advanced RAG Techniques (Neo4j)</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[CTO로 한해를 보내며.. (2019 회고)]]></title><description><![CDATA[2020년 설이 지나서야 2019년 회고의 글을 씁니다. 목차를 만들어서 하나씩 회고를 하면서 써나갈까 합니다. 2019년은 정말 다사다난했습니다. 큰일들을 위주로 회고를 시작할까 합니다.
CTO가 되다..
 ITAM GAMES는 2018년에 시니어 개발자로 입사하게 되었습니다. 개발자로서 만족하면서 개발 일을 프런트, 백앤드를 가리지 않고 개발을 했습니다. 2018년 말 전 CTO님께서 회사를 퇴사하면서 저희 대표님은 저에게 CTO 직을 제시...]]></description><link>https://blog.dongjun.win/cto-2019</link><guid isPermaLink="true">https://blog.dongjun.win/cto-2019</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Tue, 28 Jan 2020 05:19:00 GMT</pubDate><content:encoded><![CDATA[<p> 2020년 설이 지나서야 2019년 회고의 글을 씁니다. 목차를 만들어서 하나씩 회고를 하면서 써나갈까 합니다. 2019년은 정말 다사다난했습니다. 큰일들을 위주로 회고를 시작할까 합니다.</p>
<h1 id="heading-cto">CTO가 되다..</h1>
<p> ITAM GAMES는 2018년에 시니어 개발자로 입사하게 되었습니다. 개발자로서 만족하면서 개발 일을 프런트, 백앤드를 가리지 않고 개발을 했습니다. 2018년 말 전 CTO님께서 회사를 퇴사하면서 저희 대표님은 저에게 CTO 직을 제시하였고, 저는 거절했습니다. 하지만 결국은 2019년 1월.. CTO를 맡게 되었습니다. 총인원 약 60명 되는 회사의 CTO는 저에게 있어서 아직 부담으로 다가왔었습니다. 물론 개발팀만 50~60명은 아닙니다.
 CTO를 하면서 많은 일이 있었고, <code>이불킥</code>을 할 정도로 부족했던 나의 모습, 하나하나의 결정에 따른 책임, 회사를 퇴사하겠다는 직원, 항상 무언가 새로운 것만 추구했던 사업부.. CTO는 정말 쉬운 직책이 아니고 편한 직책이 아님을 경험하고 있습니다. (현재도 CTO로 재직 중에 있습니다.)</p>
<h2 id="heading-64ky7j2yioucuumgouhncdrkjjsp4ag7jwk64quiouqqoutocdqsomu">나의 뜻대로 되지 않는 모든 것.</h2>
<p> 2019년 초에는 모든 것이 나의 뜻대로 되지 않았습니다. 팀원들은 나를 신뢰하지 않은 듯한 느낌을 받았고, 저의 진심을 알아주지 않은 팀원들에게 서운한 감정도 많이 들었습니다. 또한 개발일 정도 충분히 가능하리라 봤던 것들도 안되었습니다. 팀원들은 무언가 불만이 많았던 것처럼 보였으며, 나를 무시하는 느낌까지 받았었습니다. 사업부나 기획팀도 일정에 따른 압박을 주기 시작했고, 저는 CTO로서 첫 번째 고비가 다가왔었습니다.</p>
<h4 id="heading-66qo65ogioyemouquydgcdsoidrozzrtodthlaulg">모든 잘못은 저로부터..</h4>
<p> 2019년 초기에는 위에 써놓은 것처럼 모든 것이 마음대로 되지 않았고 그로 인한 쌈닭이 되어 가는 모습을 보게 되었습니다. 저를 바라보고 함께 일하고 싶어서 왔던 동료들인데 제가 변해 가는 모습을 몇 개월 후에나 알게 되었을 때 정말 고개를 들지 못할 정도로 미안함을 느끼게 되었습니다. 모든 것이 제가 미숙했던 거였습니다.</p>
<ol>
<li>왜 나는 동료들에게 다가가려 하지 않았을까?</li>
<li>먼저 신뢰를 얻으려고 하지 않을까?</li>
<li>일에 대한 욕심을 왜 이렇게까지 냈을까..</li>
<li>개발팀의 동료를 생각하지 않고 왜 회사의 입장만 고수하고 강요했을까?</li>
<li>모든 기준을 나로 두고 생각했을까..</li>
<li>팀원 각자가 특출난 부분이 있는데 왜 그걸 알아내지 못했으며, 알면서도 그에 맞게 활용을 못 했을까?</li>
<li>나 스스로 노력하지 않으면서, 나를 따라와 주고 믿어주며, 함께 할 거라 생각을 했을까?</li>
<li>동료의 말을 들으려고 하지 않을까?</li>
<li>한쪽 말만 듣고 판단하려고 했을까?</li>
<li>왜 한 달에 한 번씩 회고를 하지 못했을까..?</li>
<li>감정적으로 행동을 했으며, 말을 함부로 했을까..?</li>
</ol>
<p>위의 11가지 말고도 더 많은 잘못이 있었음을 너무 늦게 깨달았습니다. '그때 그렇게가 아니고 이렇게 했으면 조금 더 좋은 결과가 있었을 텐데..'라는 생각을 지금도 합니다.
그래도 늦게라도 회고하고 반성하며 아래와 같은 생각을 가지고 회사 생활을 하게 되었습니다.</p>
<ol>
<li>팀원들과 함께 하는 시간을 늘려보자. (함께 산책하기, 농담 따먹기 하기 등등)</li>
<li>내가 말하는 시간보다 듣는 시간을 늘려보자.</li>
<li>무언가 일을 진행할 때 욕심을 부리지 말자.</li>
<li>팀원 각자의 특출난 부분에 맞게 업무를 분배하고 나아가자.</li>
<li>감정적으로 행동하지 말고, 말을 함부로 하지 말자.</li>
<li>한쪽의 의견만 듣지 말고 양쪽의 의견을 듣고 판단하자.</li>
<li>팀원들을 신뢰하고 일을 맡기자.</li>
<li>업무에 대한 부족함이 있을 시에는 부족한 부분을 채워주도록 하는 게 나의 역활 중 하나라 생각하자.</li>
<li>새롭게 알게 된 지식은 공유하자.</li>
<li>한 달에 한 번쯤은 스스로 돌아보고 회고하자.</li>
<li>짧은 인생, 안 좋은 소리보다 좋은 소리를 하자.</li>
</ol>
<p>위의 11가지를 생각하면서 변화를 하게 되었고 지금 이 순간은 저희 개발팀은 서로 함께 늙어 가면서 평생 같이 보는 사이가 되었습니다. 더 크게 깨달은 것 중의 하나는 저는 정말 멘탈이 강한 줄 알았는데 맨탈이 강한 게 아니었다는 것을 느끼게 되는 2019년이었습니다.</p>
<blockquote>
<p>지금은 서로 술도 한잔하고 함께 진솔한 애기를 많이 합니다. 팀원들은 이렇게 얘기합니다. 2019년 초기만 해도 머 저런 미친X.. 쌍또라... 등으로 저를 얘기했는데 어떻게 이렇게 웃고 서로 힘이 되어 주고 도와주는 사이가 됐는지 신기해합니다. (자슥들아 너희와 내가 함께 노력한 거다 ㅋㅋㅋ) 그중 한 명은 진짜로 코드에 심각한 오류를 심어 놓고 퇴사할까 라는 생각도 했다고 합니다.</p>
</blockquote>
<h2 id="heading-64si66y0iounjuydgcdsl4xrrltrn4kulg">너무 많은 업무량..</h2>
<p> CTO가 되고 나서 정말 사업부는 너무 많은 업무와 짧고 짧은 기간을 주었습니다. 저는 이 부분을 어떻게 풀어 나가야 할지에 대해서 고민을 했습니다.</p>
<blockquote>
<p>저는 정말 야근이 싫었습니다. 그래서 팀원들에게 야근하게 만들고 싶지 않았습니다.</p>
</blockquote>
<h4 id="heading-1">1. 서버 관리는 하지 말자.</h4>
<p> 미친 듯한 업무량을 제한된 시간에 팀원들과 함께 풀어나가기에는 개발에만 집중하기에도 부족했습니다. 여기에 있어서 서버 관리까지 한다고 생각하니 답이 나오지 않았습니다. 그래서 선택을 아래와 같이 했습니다.</p>
<ol>
<li>Lambda를 사용해서 서버를 관리하지 말자.</li>
<li>S3, Route53, Cloudfront를 사용해서 SPA를 서비스하자</li>
<li>Codepipeline을 이용해서 자동 배포 시스템을 구축하자.</li>
<li>Mongodb를 사용하면서 직접 관리하지 말고 Mongodb Atlas를 사용하자.</li>
</ol>
<p>위와 같은 선택은 정말 신의 한 수였습니다. 선택에 따른 결과는 아래와 같았습니다.</p>
<ol>
<li>서버 관리가 더 이상 필요하지 않았습니다.</li>
<li>Mongodb의 모든 설정 및 관리가 필요하지 않았습니다.</li>
<li>자동 배포로 인해 배포에 대한 부담감이 적어졌습니다.</li>
<li>모니터링 툴이 필요하지 않았습니다. (Cloud Watch를 사용했으며, 더 필요한 부분은 log를 남겼습니다)
결론적으로 개발에 집중 할 수 있는 환경을 만들었습니다. 지금도 그때의 선택은 정말 잘했다고 생각합니다.</li>
</ol>
<h4 id="heading-2">2. 급하게 처리할 수밖에 없던 업무들..</h4>
<p> 짧은 일정은 충분히 잘 만들 수 있던 서비스를 일정에 치여서 아쉬운 상태로 완료된 경우가 너무 많았습니다. 대표님과도 해당 이슈에 대한 부분에 대해 많은 얘기를 했지만, 항상 결과는 회사가 나아가기 위해서는 어쩔 수 없이 그 기간 안에 서비스가 나와야 된다는 애기로 끝났습니다.
 지금 생각해보면 정말 어리석은 결정이었습니다. 제가 조금 더 강하게 의견을 말했으면 어땠을까 라는 생각을 많이 합니다.</p>
<p> 위와 같은 결정에 따른 결과는 아래와 같은 부정적으로 나타나기 시작했습니다.</p>
<ol>
<li>완성도의 하락.</li>
<li>그때 그 순간만을 위한 개발.</li>
<li>개발자의 의욕 상실.</li>
<li>기술의 부채.</li>
<li>엄청난 기회비용의 낭비.</li>
<li>효율적이지 못한 업무.</li>
</ol>
<p>위와 같은 상황은 개발팀뿐만 아니라 회사 전체에 악영향을 주었다고 생각합니다. 지금은 이와 같은 상황을 만들지 않기 위해서 노력하고 있습니다. (부채를 열심히 갚고 있고요..)</p>
<h2 id="heading-7zqo7jyo7kcb7j24ioyxheustcdrsknsi50">효율적인 업무 방식</h2>
<p> CTO가 되면서 욕심을 부리고 싶었던 부분은 효율적인 업무 방식이었습니다. 그에 따라서 정말 다양한 툴을 사용했었습니다.</p>
<ol>
<li>트렐로 + 슬랙</li>
<li>테스크월드 + 슬랙</li>
<li>github(저장소마다 있는 project) + 슬랙</li>
<li>먼데이 + 슬랙</li>
<li>노션 + 텔레그램(현재)</li>
</ol>
<p>위의 5가지를 써보았고, 효율적인 업무를 위해서 애자일 방법론들을 짬뽕하고 조합해서 다양하게 적용을 해봤습니다. 하지만 결과적으로는 실패하게 되었습니다. 그에 따른 경험을 공유할까 합니다.</p>
<h3 id="heading-1-1">1. 모두가 함께 노력해야 된다.</h3>
<p> 모든 업무를 Task 화 하고 그에 맞게 스프린트를 단위로 개발을 하자라는 목표로 시작을 했습니다. 먼저 바로 모든 것을 바꾸기에는 무리가 있었다고 판단하였고 작은 거부터 시작하자는 목표로 진행했습니다. 하지만 쉽지 않았습니다. 누구에게는 이 방식이 편했고 누구에게는 이 방식이 불편했습니다.
업무수행 방식에 대한 약속 또한 지키지 못하는 경우가 자주 발생하였습니다. 업무수행 방식의 변화는 어느 순간부터 팀원들에게 스트레스가 되었고, 개발팀을 제외한 다른 팀, 대표님한테도 불필요한 일처럼 느끼게 만들어졌습니다. 또한 많은 업무량에 이와 같은 업무수행 방식의 변화는 더욱더 안 좋은 인식을 가져다주었습니다. 결론적으로 업무수행 방식의 변화는 회사 전체가 서로 도와줘야 된다는 것을 느끼게 되었습니다. 무엇보다도 대표님이 이해해주고 도와줘야 된다는 것을 절실하게 느꼈습니다. 그리고 엄청난 업무량과 짧은 기간에는 그냥 폭포수처럼 할 수밖에 없나? 정말 방법이 없을까? 라는 생각을 많이 하게 되었습니다. 이와 같은 경험은 2019년에는 아직 업무수행 방식을 변경하기에는 시기상조라는 결론을 내리게 되었고, 2020년에는 꼭 여유를 가지고 지금보다 나은 효율적인 업무수행 방식을 도입하겠다는 다짐을 했습니다.</p>
<h3 id="heading-2-1">2. 인식의 변화가 필요</h3>
<p> 효율적인 업무수행 방식은 회사에서 일하는 모두가 인식의 변화가 필요합니다. 그리고 이것을 적용하고 효율을 보기 위해서는 많은 시간과 노력이 필요하다는 것도 알아야 됩니다. 기존에 일했던 방식과 매우 다를 수도 있기 때문에 주변에서도 많이 도와줘야 되고 여유를 가지고 적용해야 된다는 것을 느꼈습니다.</p>
<h3 id="heading-3">3. 효율적인 업무수행 방식은 편하지 않다.</h3>
<p> 편하다는 것과 효율적이라는 말은 같지 않습니다. 효율적으로 업무를 하기 위해서는 불편함을 느낄 수밖에 없습니다. 가끔 효율적으로 업무 수행하는 게 편하게 일하는 거 아니에요? 라고 하지만 절대 아님을 알고 있어야 됩니다.</p>
<h3 id="heading-4">4. 업무수행 방식의 시스템화</h3>
<p> 확고한 체계를 가지고 시스템화 해야 됩니다. 아마 처음부터 확실한 시스템화는 하기 힘듭니다. 계속 보안하고 발전해 나가서 하나의 시스템으로 자리 잡게 해야 된다고 생각을 합니다. 그 누군가 새로 오든 혹은 누군가 회사를 퇴사하든가 문제없어야 되니깐요..</p>
<h3 id="heading-5">5. 대표님의 도움(?)</h3>
<p> 실패의 가장 큰 요인은 대표님이 불편해했고 불필요하다고 느꼈다는 것입니다. 그 누구보다 앞장서서 도와주셨으면 반은 성공하지 않을까 생각도 합니다.</p>
<p>효율적인 업무수행 방식을 도입하기 위해서는 많은 어려움이 있음을 경험하고 알게 되었습니다. 하지만 절대 포기할 수 없기도 하고요. 효율적으로 업무를 하는 회사가 있으면 경험도 하고 싶습니다. 그리고 구글이나 아마존 등등에서는 어떻게 업무를 하고 있는지도 궁금하기도 하고요. 2020년에는 조금 더 효율적인 업무수행 방식에 대해서 경험도 하고 싶고 도입을 해야 한다는 다짐을 합니다.</p>
<h2 id="heading-7ye07iks66w8ioybko2vmouklcdsp4hsm5a">퇴사를 원하는 직원</h2>
<p> 직원들 모두가 한 회사에서 평생을 함께하지 않습니다. 특히나 스타트업에서는 있을 수 없는 일입니다. 우리 회사도 퇴사를 한 직원이 있었으며, 퇴사를 생각하다가 지금까지 함께 일하는 직원도 있습니다.
 제가 CTO가 아닌 개발자로 있었을 때는 크게 생각을 하지 않았습니다. 하지만 CTO가 되고 나서부터는 누군가가 퇴사한다고 할 때마다 가슴이 철렁합니다. 많지 않은 인원으로 개발팀을 꾸려가고 있는 상황에서 누군가 퇴사하면 모든 부분에 대해서 타격이 입기 때문입니다. 이와 같은 경험을 통해서 느낀 점은 아래와 같습니다.</p>
<ol>
<li>그 누구도 그만둘 수 있다. 준비하자.</li>
<li>퇴사를 막기 위해서 희망 고문 하지 말자.</li>
<li>퇴사라는 결정을 하기 전에 미리 방지할 수 있어야된다.</li>
<li>좋은 곳으로 이직을 하게 되면 진심으로 축하하자.</li>
<li>퇴사하는 직원이 있으면 다른 직원들도 동요하게 된다. 주의하자.</li>
<li>모든 계정 및 비밀번호를 쉽게 변경 할 수 있게 준비하자</li>
<li>항상 문서화를 해서 퇴사를 해도 영향력이 적게 처리하자.</li>
<li><p>누구보다 나 자신의 멘탈 관리를 하자.</p>
<p>함께 하다가 퇴사를 한다는 얘기를 들으면 정말 서운한 감정이 먼저 앞서게 됩니다. 그다음이 업무에 대해서 생각을 하게 되고요. 솔직히 처음에는 맨탈도 많이 흔들리기도 했습니다. 맨탈 관리도 중요합니다. 하지만 언제든 함께할 수 없다는 것을 알고 준비해야 되고 좋은 곳으로 이직을 했을 시에는 진심으로 축하해야 된다고 생각합니다. 나아가서 누군가 퇴사한다는 것은 회사 전체 분위기에도 좋지 않은 영향을 미칠 수 있습니다. 그렇기 때문에 꼭 주의를 해야 됩니다.</p>
<blockquote>
<p>그 무엇이 됐든 미리미리 준비하는 것이 최고의 방법이라 생각합니다.</p>
</blockquote>
</li>
</ol>
<h2 id="heading-64z6riwiou2goyxra">동기 부여</h2>
<p> 동기부여는 정말 어렵습니다. 지금도 어렵고 앞으로도 어렵습니다. 솔직히 어떻게 해야 될지도 모르겠다고 표현하는 게 맞는 거 같습니다. CTO로서 개발팀의 동기부여를 하고 번아웃 되지 않게 잘 해야 되다는 얘기를 들었습니다. 동기부여 어떻게 해야 될까요?? 우선, 제가 직접 해본 방식은 아래와 같습니다.</p>
<ol>
<li>너는 혼자가 아니야 함께 하고 있어.</li>
<li>많은 얘기를 듣고 함께 하는 시간이 필요합니다. (가끔은 형으로써 동생으로서 조언도 하고요)</li>
<li>공감대 형성도 아주 중요하다고 생각합니다.</li>
<li>회사는 일만 하는 곳이 아니야.</li>
<li>하루에 회사에 있는 시간이 많습니다. 일만 하는 곳으로 생각이 들게 하는 게 아니라 가끔은 게임도 하고 떠들고 놀 수도 있게 만들었습니다.</li>
<li>흥미로운 기술 적용</li>
<li>정해진 기술만 가지고 개발을 한다면 본인 스스로가 정체된다는 느낌을 받을 수 있습니다. 팀원이 적용하고 싶은 기술이 있으면 언제든 오픈되어있고 적용도 할 수 있게 했습니다.</li>
<li>충분한 휴식을 주기</li>
<li>많은 업무량을 소화했으면 그것에 맞게 휴식도 주었습니다.</li>
<li>희망 고문은 하지 말자</li>
<li>처음에는 희망고문적인 말을 해봤습니다. 우리가 이렇게 하면 우리는 정말 잘될 거다 같은 느낌으로요. 하지만 순간순간은 동기부여가 될 수 있지만, 장기적으로 봤을 때는 좋지 않은 결과로 다가왔습니다.</li>
</ol>
<p>위와 같은 노력에도 불구하고 동기부여는 아직도 어렵습니다. 그리고 제가 열심히 노력한다고 해도 회사에서 그것에 맞게 따라주지 않으면 동기부여가 무산되기도 합니다. 누군가 동기부여에 대해서 강의를 하면 꼭 돈을 줘서라도 듣고 싶습니다.</p>
<h2 id="heading-67aa7kcv7kcb7j24iou2hoychoq4soyxkcdso7zsnzjtlzjsnpa">부정적인 분위기에 주의하자</h2>
<p> 재미있는 건 긍정적인 분위기보다는 부정적인 분위기가 쉽게 퍼집니다. 부정적인 분위기는 다들 아시겠지만 업무 효율부터 모든 것에 마이너스가 됩니다. 아래와 같은 상황을 주의해야 됩니다.</p>
<ol>
<li>누굴 탓하는 분위기</li>
<li>일하기 싫다는 분위기</li>
<li>퇴사하는 사람한테 나오는 무언가 퇴사해서 좋다는 분위기</li>
</ol>
<p>글 쓰는 이 순간에는 저 위의 3가지만 생각이 납니다. 부정적인 분위기는 막는다고 막히는 게 아니라고 생각은 합니다. 하지만 최대한 주의를 하고 미리 예방한다면 좋지 않을까 합니다.</p>
<h2 id="heading-66ei7kea66ej7jy866gcli4">마지막으로..</h2>
<p> 너무 두서없이 주저리주저리 쓴 거 같습니다. 마지막으로 위의 글 말고도 생각나는 걸 그냥 막 나열해볼까 합니다.</p>
<ol>
<li>맨탈 관리 잘하자.</li>
<li>왜 CTO를 뽑을 때 경험이 있는 사람을 우대하는지 알겠다.</li>
<li>건강 관리하자.</li>
<li>사람은 누구나 특출나게 잘하는 곳이 있다. 그것에 맞게 업무 분배가 중요하다.</li>
<li>나 혼자 아무리 해보려고 해도 팀원이나 회사가 도와주지 않으면 아무것도 못 한다. 함께하자.</li>
<li>효율적인 업무수행 방식은 못하더라도 문서화는 꼭 하자.</li>
<li>욕심을 버리고 내려놓자.</li>
<li>누군가 나에 대해서 얘기를 하는 거에 예민하게 반응하지 말자.</li>
<li>말하기보다는 듣기를 조금 더 하자.</li>
<li>부족한 부분을 탓하기보다는 채워주기 위해서 노력하자.</li>
<li>말을 할 때 10번은 생각하고 말하자.</li>
<li>생각 할 수 있는 여유를 가지고 개발을 할 수 있는 환경을 만들자. (급하게 해서는 절대 안 된다)</li>
<li>진솔한 사람이 되자.</li>
</ol>
<p>아직도 많이 부족하기만 하다는 것을 글을 쓰면서 다시 한번 느끼게 됩니다. 2020년 제가 어떻게 될지는 모르겠지만 지금보다 조금 더 발전하고 나아가야겠습니다. 저와 함께 일한 동료들이 다시 또 함께 할 수 있게 그리고 개발자로서도 발전해 나가야겠다고 다짐을 합니다.</p>
]]></content:encoded></item><item><title><![CDATA[Serverless를 선택한 이유(Lambda, Altas)]]></title><description><![CDATA[CTO를 맡으면서 제가 선택하고 실무에 적용하면서 경험한 Serverless에 대해서 글을 남기려고 합니다.
Serverless?
 여기에 와서 글을 읽으시는 분들은 Serverless가 무엇인지 충분히 알고 있을 거라고 생각합니다. 그래도 간단하게만 얘기한다면 진짜 Server가 없는 것은 아니고 Server를 신경 쓰지 않아도 서비스를 할 수 있게 하는 기술이라고 보면 됩니다.
 조금 더 있어 보이게 얘기한다면 애플리케이션 개발자가 서버를 ...]]></description><link>https://blog.dongjun.win/lambda</link><guid isPermaLink="true">https://blog.dongjun.win/lambda</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Fri, 17 Jan 2020 03:25:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/images/tyle-bfw-02.png" alt />
 CTO를 맡으면서 제가 선택하고 실무에 적용하면서 경험한 Serverless에 대해서 글을 남기려고 합니다.</p>
<h2 id="heading-serverless">Serverless?</h2>
<p> 여기에 와서 글을 읽으시는 분들은 Serverless가 무엇인지 충분히 알고 있을 거라고 생각합니다. 그래도 간단하게만 얘기한다면 진짜 Server가 없는 것은 아니고 Server를 신경 쓰지 않아도 서비스를 할 수 있게 하는 기술이라고 보면 됩니다.
 조금 더 있어 보이게 얘기한다면 <strong><em>애플리케이션 개발자가 서버를 프로비저닝하거나 애플리케이션의 확장을 관리할 필요가 없는 클라우드 컴퓨팅 모델을 가리킵니다.</em></strong>
 여기에서 저는 실무에 적용하고 경험한 Lambda와 Mongodb Atlas 등 Serverless를 선택한 이유와 그에 대한 글을 남길까 합니다.</p>
<h2 id="heading-lambda">Lambda</h2>
<p> 람다는 AWS에서 만든 <strong><em>서버를 프로비저닝하거나 관리하지 않고도 코드를 실행할 수 있게 해주는 컴퓨팅 서비스</em></strong>입니다. 솔직히 말이 조금 어렵지만 단순하게 서버 관리가 필요 없이 함수를 실행해 주는 놈? 이라고 생각하는 게 편합니다.</p>
<p><strong>람다를 사용했을 시 장점은 무엇이 있을까?</strong></p>
<ol>
<li>개발에만 집중 할 수 있는 환경이 됩니다.</li>
<li>서버에 대해서 고민을 할 필요가 없습니다. (오토스캐일링부터 다양한 관점에서)</li>
<li>비용이 저렴합니다.</li>
<li>서버뿐만 아니라 AWS의 기능에 대한 트리거 및 스케줄로 등등으로 사용이 가능합니다. (예로 code pipeline에서 s3 배포를 완료 후 Cloud front 캐시 초기화할 때도 사용됩니다)</li>
</ol>
<p><strong>장점만 있으면 좋겠지만 단점도 존재합니다.</strong></p>
<ol>
<li>콜드 스타트 부분입니다. 최근에 동시성이라는 기능이 추가되어서 많이 좋아지긴 했습니다. 하지만 디비 커넥션과 같은 부분은 콜드 스타트 부분에서 초기화되기 때문에 생각을 해줘야 됩니다.</li>
<li>로그 보는 부분이 매우 불편합니다. (AWS의 CloudWatch를 통해서 볼 수 있지만 불편합니다)</li>
<li>동시 실행에 대한 제한이 있습니다.</li>
</ol>
<p><strong>단점을 극복 한 저의 경험은?</strong></p>
<ol>
<li>콜드 스타트<ul>
<li>해당 부분은 현재 15분마다 해당 function을 호출해 주는 방법이 가장 좋다는 생각은 듭니다. 하지만 MSA로 구현이 되어 있다면 수많은 function을 호출하는 게 비용적인 측면에서 문제가 있을 수 있습니다. 저는 Lambda의 사용을 꼭 MSA로 구현하지 않아도 된다고 생각하기 때문에 해당 부분은 어떻게 설계하냐에 따라 달라질 거라 생각합니다.</li>
</ul>
</li>
<li>로그 문제<ul>
<li>lambda를 호출하는 부분에 공통으로 로그를 남기는 부분을 만들어 놓았습니다. 물론 x-ray와 같은 서비스를 사용해도 좋겠지만 그거보다는 직접 호출이 되었을 시 그리고 오류가 났을 시 등등을 전부 에러 로그 처리해 놓고 확인하게 하였습니다. 다음에는 ELK를 이용하여 로그 분석 관련해서 작업을 진행할까 생각도 하고 있습니다.</li>
</ul>
</li>
<li>동시 실행에 대한 제한<ul>
<li>이 부분은 방법이 없습니다. 미리미리 AWS에 동시 실행에 대한 제한 부분을 풀어 놓으면 됩니다.</li>
</ul>
</li>
<li>제한된 모니터링 툴<ul>
<li>Lambda에 대한 모니터링 툴은 많이 부족하고 찾기 힘듭니다. 물론 CloudWatch가 있긴하지만 뭔가 부족함이 있다는 것을 느낄 수 있습니다. 아직 이 부분은 정도 CloudWatch를 확인하네요.</li>
</ul>
</li>
</ol>
<p><strong>Lambda를 직접 운영/개발을 하면서 알게 된 팁.</strong></p>
<ol>
<li>Lambda layer를 꼭 사용해야 합니다.<ul>
<li>layer 같은 경우는 외부 코드나 라이브러리, 모듈 등을 사전에 압축하여 하나의 큰 모듈처럼 사용 할 수 있게 해줍니다.</li>
<li>layer를 사용하면 deploy 부분에 대한 속도 부분이 극명하게 차이 날 정도로 빨라집니다.</li>
</ul>
</li>
<li>Serverless Framework 사용 추천<ul>
<li>이 부분을 팁으로 놓아야 하나 고민은 많이 되었습니다. 하지만 다른 프레임워크보다는 다양한 플러그인 지원, 그리고 방대한 커뮤니티 등등이 Lambda 혹은 Serverless를 사용하면서 큰 도움이 되었다고 생각합니다.</li>
</ul>
</li>
<li>Codepipeline 사용한 자동배포<ul>
<li>AWS를 사용해서 배포를 자동화하면 좋습니다. 보안에 민감할 수 있으니 해당 배포에 대한 권한을 개발자에게 주기보다는 AWS IAM을 이용해서 자동 배포 할시에만 주는걸 추천해 드립니다.</li>
</ul>
</li>
<li>MSA?? 굳이... 상황에 맞게 처리.<ul>
<li>Lambda를 사용하면 MSA를 해야 될 거 같은 느낌을 받을 수 있지만, 굳이 그렇게 할 필요가 없습니다. Monolithic과 비슷하게 1개의 endpoint에 1개의 function으로 해도 됩니다. 제가 추천하는 건 적당한 선에서 상황에 맞게 처리하는 것을 추천합니다.</li>
<li>MSA를 했을 때에는 DB 커젝션 수부터 로그 부분 그리고 function이 많아질 경우 그거에 대한 관리 등등을 고민해야 됩니다. 또한 콜드 스타트 때문에 매번 lambda 호출 시 드는 비용에 대해서도 고민할 필요가 있습니다.</li>
<li>Monolithic으로 했을 때에는 너무 방대해진 코드의 용량에 대해 고민을 할 필요가 있습니다. 또한 동시 실행의 제한도 미리미리 신청해서 늘려 놓아야 됩니다.</li>
</ul>
</li>
</ol>
<p><strong> Lambda를 선택한 이유</strong></p>
<p>위의 다양한 의견을 내긴 했지만 제가 Lambda를 선택한 가장 큰 이유는 서버 관리가 필요하지 않고 개발에 집중 할 수 있는 환경을 구성 할 수 있기 때문입니다. Lambda에서 나오는 단점은 충분히 커버도 가능할 거라 생각하고요. 비용 부분도 저렴합니다. 우선 프리티어와 관계없이 월 1백만 호출까지는 무료로 알고 있습니다. 호출된 횟수에 맞게 돈을 지불하기 때문에도 저렴하다고 말 할 수 있습니다. 트레픽이 많아지면 많이 비싸진다는 애기도 있긴 합니다. 이 부분도 말씀드리고 싶은것은 우선 Lambda가 아닌 다른 것을 사용해도 트레픽이 많아지면 가격이 비싸집니다. 물론 딱 물리적인 가격만 비교하면 Lambda가 비싸게 느껴질 수 있지만 트레픽 대응에 대한 서버 관리, 인력 고용 등등을 생각도 해야 됩니다.(대용량 트레픽에 따른 서버 관리하는 사람을 고용하는 것도 어렵고 급여도 많이 높을 거라 생각합니다) 이 모든 비용을 바라본다면 Lambda가 저렴하다고 생각합니다.</p>
<h2 id="heading-mongodb-atlas">Mongodb Atlas</h2>
<p> Mongodb Atlas는 Mongodb management service(MMS)입니다. 말이 어렵게 느껴질 수 있지만 결국 serverless로 mongodb에 대한 모든 관리는 Atlas에서 해준다고 생각하면 편합니다. Lambda랑 비슷합니다. 한때는 Mlab이 가장 유명했지만 mongodb에서 인수를 하면서 서비스가 종료되었습니다.</p>
<p><strong> Mongodb Atals의 장점 </strong></p>
<ol>
<li>가장 좋은 건 Mongodb에 대한 관리가 필요 없습니다. Server부터 모든 기능 전부(리플리카셋, 샤딩 등등)</li>
<li>모니터링 툴을 따로 쓸 이유가 없습니다.</li>
<li>알람 또한 너무 잘되어 있어서 빠르게 확인 할 수 있습니다.</li>
<li>Performance Advisor라는 기능을 제공하여 쿼리 속도부터 index가 필요한 부분까지 체크해줍니다.</li>
<li>멀티 리전을 사용할 수 있습니다.</li>
<li>스토리지에 대한 Auto Scaling을 제공합니다.</li>
<li>백업에 대해서도 지원합니다. (실시간 백업도 가능, 4.2버전 이상은 아직 미지원)</li>
</ol>
<p><strong> 단점은? </strong></p>
<ol>
<li>비용이 저렴하진 않습니다.</li>
</ol>
<p>단점 부분은 솔직히 비용을 적긴 했지만 저는 합리적이라고 생각합니다. 단점을 찾기가 쉽지 않네요..</p>
<p><strong> Mongodb Atals를 사용하면서 알게 된 팁 </strong></p>
<ol>
<li>같은 리전, 같은 클라우드<ul>
<li>당연한 소리이지만 같은 리전 그리고 같은 클라우드 서비스로 만드는 것을 추천합니다. (이유는 굳이 설명 안 하겠습니다.)</li>
</ul>
</li>
<li>Database Access 권한<ul>
<li>Database Access 권한인 경우는 모든 클러스트 공통으로 적용이 됩니다. 저희 같은 경우 개발 클러스트와 실 클러스트를 운영하는데 Database Access 권한을 주면 둘 다 동일하게 적용되었습니다. (다른 방법이 있는데 제가 모르는 거 일 수도 있습니다) 그렇기 때문에 해당 부분을 확인하는 게 좋습니다.</li>
</ul>
</li>
<li>Network Access 권한<ul>
<li>Network Access 같은 경우 보통 0.0.0.0/0으로 세팅 하는 경우가 있는데 보안상 추천하지 않습니다. 꼭 화이트 리스트로 하는 것을 추천합니다.</li>
</ul>
</li>
<li>vpc peering<ul>
<li>VPC peering을 한다고 해서 체감상 속도가 빨라지진 않습니다. 다만 이걸 하는 것을 추천하는 이유는 보안상의 이유가 크다고 볼 수 있습니다.</li>
</ul>
</li>
<li>Performance Advisor 기능<ul>
<li>Performance Advisor 기능을 적극적으로 활용해야 됩니다. 정말 신기할 정도로 잘 추천해주고 그것에 맞게 처리하면 성능 개선을 확실히 볼 수 있습니다. 다만 초기에 데이터가 없을 시에는 확인이 되지 않을 수 있습니다.</li>
</ul>
</li>
<li>스케일 업<ul>
<li>mongodb 스케일 업 시에는 간단하게 버튼만으로 추가 할 수 있습니다. 다만 스케일업 하는 동안은 서비스가 되지 않기 때문에 충분한 공지를 통해서 하시는걸 추천해 드립니다.</li>
</ul>
</li>
</ol>
<p><strong> Mongodb Atlas를 선택한 이유</strong></p>
<p> 똑같은 소리를 반복하는 거일 수 있지만 서버 관리, 몽고디비의 다양한 기능, 설정 등등을 직접 관리할 필요가 없고 개발에 집중 할 수 있는 환경을 조성 할 수 있기 때문입니다. 초기에는 Atlas가 아닌 직접 mongodb를 운영도 해보았습니다. EC2 3대와 글로벌 서비스를 위한 버지니아에 read 전용 EC2 2대까지 총 5대를 세팅하고 운영을 하면서 다양한 버그, 에러 그리고 모니터링 툴의 필요성, 알람의 필요성, 백업 계획 등등을 느끼고 있었지만, 개발을 할 수 있는 인력 및 여건이 되지 않아 Atlas로 옮기는 결정을 하고 작업을 했습니다. 옮기고 나서 만족도는 1,000,000%입니다. 다만 단점이라고 하면 비용에 있습니다. 하지만 위의 부가적인 작업 및  Mongodb에 문제가 있을 시 등등의 기회비용 인력 비용 모든 것을 다 합한다고 하면 합리적이라고 생각합니다.</p>
<h3 id="heading-serverless-1">Serverless 왜 선택했냐?</h3>
<p> Serverless를 선택한 이유는 아래와 같습니다.</p>
<ol>
<li>개발에 집중하는 환경<ul>
<li>많은 스타트업이 그렇겠지만 개발자는 뽑기 힘들고 많이 있지 않습니다. (어디 있나요..?) 많지 않은 인력으로 서비스 개발만 해도 시간이 부족합니다. 그렇기 때문에 개발에 집중할 수 있는 환경을 조성하기 위한 하나의 선택지라 생각합니다.</li>
</ul>
</li>
<li>Serverless에서 제공하는 기능들<ul>
<li>Lambda, Atlas에서 제공하는 기능들은 정말 꿀과 같습니다. (특히 Atlas) 이런 기능들은 직접 만들고 유지하고 운영하기에는 저희는 인력도 부족하고 시간도 부족합니다.</li>
</ul>
</li>
<li>합리적인 비용<ul>
<li>물리적인 하드웨어를 따지고 보면 비싸게 느껴질 수 있지만 모든 기회비용까지 생각하면 오히려 저렴하다고 생각합니다. (개발자 몸값만 생각해도...)</li>
</ul>
</li>
<li>전문 지식<ul>
<li>Serverless를 이용하지 않고 직접 운영을 한다면 해당 기술에 대한 전문지식이 필요합니다. 다양한 버그, 상황에 맞는 설정 등 경험을 해보지 못하는 경우가 많고 아무리 많이 안다고 해도 전문적으로 해당 기술에 대한 Serverless 하는 회사에 보다는 많이 부족함도 사실입니다.</li>
</ul>
</li>
</ol>
<h3 id="heading-66ei66y066as7zwy66mwlg">마무리하며.</h3>
<p> 다양한 서비리스 중 2가지만 애기하기 했지만 다른 좋은 서버리스도 많습니다. 상황에 맞게 그리고 비즈니스에 맞게 잘 활용한다면 아주 큰 도움이 될 수 있다고 생각합니다.</p>
]]></content:encoded></item><item><title><![CDATA[Frontend 개발 후 AWS에 서비스 배포하기]]></title><description><![CDATA[Frontend(vue, react, angular 등등)를 개발을 하고 서비스를 하기 위한 AWS 환경 설정 및 배포에 대해서 글을 남깁니다.
아키텍처
단순하게 SSR이 아니기 때문에 따로 서버를 두고 관리하기 보다는 S3에 파일을 올리고 그에 맞게 CDN인 Coundfront와 연결 후 Route53을 이용해서 도메인까지 연결하는 구조로 생각을 했습니다. 그리고 배포 시스템으로는 Codepipeline을 이용해서 배포 하는 방법을 채택했습니...]]></description><link>https://blog.dongjun.win/fronend-aws-s3-cloudfront-route-53-vue-angular-react</link><guid isPermaLink="true">https://blog.dongjun.win/fronend-aws-s3-cloudfront-route-53-vue-angular-react</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 13 Jan 2020 02:47:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/images/tyle-blo-02.png" alt />
Frontend(vue, react, angular 등등)를 개발을 하고 서비스를 하기 위한 AWS 환경 설정 및 배포에 대해서 글을 남깁니다.</p>
<h2 id="heading-7jwe7ykk7ywn7lky">아키텍처</h2>
<p>단순하게 SSR이 아니기 때문에 따로 서버를 두고 관리하기 보다는 S3에 파일을 올리고 그에 맞게 CDN인 Coundfront와 연결 후 Route53을 이용해서 도메인까지 연결하는 구조로 생각을 했습니다. 그리고 배포 시스템으로는 Codepipeline을 이용해서 배포 하는 방법을 채택했습니다.</p>
<p><strong>개발자</strong></p>
<ol>
<li>개발 소스 GitHub 배포</li>
<li>CodePipeline에서 Webhook을 이용 Github 확인</li>
<li>CodeBuild를 이용하여 빌드(ex. npm run build와 같은 것 등등)</li>
<li>CodeDeploy를 이용해서 S3 배포</li>
<li>S3를 배포가 완료 되면 Lambda를 실행 시켜 CloudFront 캐시 초기화</li>
</ol>
<p><strong>고객</strong></p>
<ol>
<li><p>www.example.com 접속시 DNS 접속(Route53)</p>
</li>
<li><p>Route53에 연결된 CNS(CloudFront) 호출</p>
</li>
<li><p>캐싱이 되어 있으면 캐싱된 부분으로 리턴 안되어 있으면 S3 접근 후 리턴</p>
</li>
</ol>
<h2 id="heading-7jwe7ykk7ywn7lky7jeqiouusoulucdtmzjqsr0g7isk7kcv">아키텍처에 따른 환경 설정</h2>
<p>CloudFormation을 이용해서 배포 하는 방법이 있지만 아직 그 부분은 익숙하지 않아서 아래와 같이 직접 배포를 하게 되었습니다.</p>
<h3 id="heading-1-s3">1. S3</h3>
<h4 id="heading-1">1) 저장소 생성</h4>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/%EB%B2%84%ED%82%B7%EC%83%9D%EC%84%B1.png?raw=true" alt /></p>
<p>위의 이미지의 버킷 만들기를 클릭 후 배킷을 생성 합니다.</p>
<blockquote>
<p>생성시 꼭 퍼블릭 액세스 차단은 비활성화를 하셔야됩니다.</p>
</blockquote>
<h4 id="heading-2">2) 정책 설정</h4>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/%EB%B2%84%ED%82%B7%EC%A0%95%EC%B1%85.png?raw=true" alt /></p>
<p>만든 버킷에 접속 후에 해당 권한 탭 -&gt; 버킷 정책을 클릭후 아래와 같은 정책을 입력 합니다. 그냥 해당 정책은 읽기 권한을 퍼블릭하게 오픈 한다는 의미 입니다.</p>
<pre><code class="lang-JSON">{
 <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2008-10-17"</span>,
 <span class="hljs-attr">"Statement"</span>: [
 {
 <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"Stmt1484315864175"</span>,
 <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
 <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
 <span class="hljs-attr">"Action"</span>: <span class="hljs-string">"s3:GetObject"</span>,
 <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:s3:::버킷명/*"</span>
 }
 ]
}
</code></pre>
<h4 id="heading-3">3) 정책 설정</h4>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/%EC%86%8D%EC%84%B1%EC%84%A4%EC%A0%95.png?raw=true" alt /></p>
<p>속성 탭에 있는 정적 웹사이트 호스팅을 위와 같이 설정합니다.</p>
<h3 id="heading-2-cloudfront">2. CloudFront 설정</h3>
<h4 id="heading-1-1">1) 생성</h4>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-1.png?raw=true" alt /></p>
<p>위의 Create Distribution을 클릭합니다.</p>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-2.png?raw=true" alt /></p>
<p>Web 부분의 Get Started를 클립 합니다. (저희는 Web이니깐요)</p>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-3.png?raw=true" alt /></p>
<ol>
<li><strong>Origin Domain Name</strong>
여기에 위의 S3 속성 설정의 앤드포인트(URL주소)를 가지고 와서 붙여넣기 합니다. (url 주소를 넣는 거지 버킷 아이디를 넣으면 안 됩니다.)
Ex) http://dev-front-itam.store-admin.s3-website.ap-northeast-2.amazonaws.com/</li>
<li>다른 부분은 굳이 입력하지 않아도 됩니다.</li>
</ol>
<blockquote>
<p>여기에서 버킷을 안 넣고 주소를 넣는 웹서비스이기 때문입니다. 특히 권한 문제.</p>
</blockquote>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-4.png?raw=true" alt /></p>
<ol>
<li><strong>Viewer Protocol Policy</strong>
<strong>Redirect HTTP to HTTPS</strong> 을 선택해주세요.</li>
<li><strong>Object Caching</strong>
caching에 대한 설정을 다르게 하고 싶으시면 이 부분을 Customize로 설정후 아래의 활성화된 값을 넣으면 됩니다. 굳이 안 하려면 위의 사진과 같이 확인하시면 되요.</li>
</ol>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-5.png?raw=true" alt /></p>
<ol>
<li><strong>Price Class</strong>
기본값인 Use All Edge Locations를 선택합니다. 물론 나의 타깃은 확고하고 정해져 있다고 하면 다른 값으로 설정하셔도 됩니다.</li>
<li><strong>Alternate Domain Names (CNAMEs)</strong>
서비스할 도메인을 넣으면 됩니다. 복수도 가능합니다. 여러 개의 도메인을 사용 시 한 줄 씩 쓰면 됩니다.
Ex) dev.example.com</li>
<li>서비스할 도메인이 있을시 <strong>SSL Certificate</strong>의 Custom SSL Certificate (example.com) 을 선택 후 인증서를 넣으면 됩니다. (인증서는 바로 아래의 버튼을 클릭 후 설정이 가능합니다.)</li>
</ol>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-6.png?raw=true" alt /></p>
<p>여기까지 왔으면 다 온 거라고 보시면 돼요. 여기서는 간단하게 Comment만 적어 두고 끝내면 됩니다. (관리하기 편하게 해당 서비스명을 넣는걸 추천해 드립니다.)</p>
<h4 id="heading-2-spa">2) SPA 관련 세팅</h4>
<p> SPA는 말 그대로 싱글 페이지 애플리케이션이기 때문에 모든 부분을 index.html 가게 해야 됩니다. 지금 이 설정을 하지 많으면 404 에러가 뜨게 됩니다.</p>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-error.png?raw=true" alt /></p>
<p>위의 Create Custom Error Response를 클릭 해주세요.</p>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/CF-error-2.png?raw=true" alt /></p>
<p>위의 사진과 같이 404 관련된 에러는 전부 /index·html로 가게 해주면 됩니다. 여기에서 주의 깊게 볼 부분은 꼭 TTL은 0으로 세팅해주셔야 됩니다. 안 그러면 기다림이 발생합니다.</p>
<h3 id="heading-route53">Route53</h3>
<p> 여기에서는 DNS 설정이 대한 부분은 제외하고 설명하겠습니다. 되게 간단합니다.</p>
<p><img src="https://github.com/mayajuni/images/blob/master/blog/aws/route53.png?raw=true" alt /></p>
<ol>
<li>이름에 서브 도메인을 입력합니다(CloudFront에서 넣은 도메인명).</li>
<li>별칭을 클릭하면 Cloudfront영역에 선택 할 수 있는 주소가 생성되어 있습니다. 해당 부분을 선택합니다.</li>
</ol>
<p>위와 같이 하면 Route 53은 설정이 끝납니다.</p>
<h2 id="heading-66ei7lmy66mw">마치며</h2>
<p>처음 하는 입장이면 생각보다 어렵다고 느껴지실 수 있습니다.</p>
<p>직접 해보고 익숙해지면 쉽게 하실 수 있다는 생각이 듭니다. 위의 CloudFront 부분에서 도메인 인증서 하는 부분이 빠지긴 했지만, 그 부분은 그냥 버튼을 클릭 한 후에 하라는 대로 따라 하면 됩니다.</p>
]]></content:encoded></item><item><title><![CDATA[EosJS API 사용]]></title><description><![CDATA[EosJS API 사용
안녕하세요. 권동준 입니다.
 이전에 EOSJS 시작하기에서 간단하게 EOSJS를 사용하는 방법을 해봤습니다. 이번에는 EosJs에서 제공하는 api 중에 자주 쓰는 api를 소개하고 테스트 할 수 있게 진행을 하려고 합니다. 

api 목록을 보기를 원하시면 여기를 확인해 보시면 됩니다. 

시작하기에 앞서 준비하기
모든 코드를 직접 사용 해볼 수 있게 할 예정입니다. 그렇게 하기 위해서는 준비가 필요합니다. 
준비 사...]]></description><link>https://blog.dongjun.win/eosjs-api-1</link><guid isPermaLink="true">https://blog.dongjun.win/eosjs-api-1</guid><category><![CDATA[eosjs]]></category><category><![CDATA[Eos]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Wed, 01 Aug 2018 01:32:00 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-eosjs-api">EosJS API 사용</h1>
<p>안녕하세요. 권동준 입니다.
 이전에 EOSJS 시작하기에서 간단하게 EOSJS를 사용하는 방법을 해봤습니다. 이번에는 EosJs에서 제공하는 api 중에 자주 쓰는 api를 소개하고 테스트 할 수 있게 진행을 하려고 합니다. </p>
<blockquote>
<p>api 목록을 보기를 원하시면 <a target="_blank" href="https://github.com/EOSIO/eosjs-api/blob/master/docs/api.md#eos--object">여기</a>를 확인해 보시면 됩니다. </p>
</blockquote>
<h4 id="heading-7iuc7j6r7zwy6riw7jeqioyvnuyencdspidruyttlzjqula">시작하기에 앞서 준비하기</h4>
<p>모든 코드를 직접 사용 해볼 수 있게 할 예정입니다. 그렇게 하기 위해서는 준비가 필요합니다. </p>
<p>준비 사항은 아래와 같습니다.</p>
<ol>
<li>nodeJs</li>
<li>eosJs</li>
</ol>
<p>위의 2개를 설치하고 javascript 파일 가장 위에 아래와 같이 넣어주세요.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Eos = <span class="hljs-built_in">require</span>(<span class="hljs-string">'eosjs'</span>);

<span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">expireInSeconds</span>: <span class="hljs-number">60</span>,
    <span class="hljs-attr">broadcast</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">debug</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">sign</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-comment">// mainNet bp endpoint</span>
    <span class="hljs-attr">httpEndpoint</span>: <span class="hljs-string">'https://api.eosnewyork.io'</span>,
    <span class="hljs-comment">// mainNet chainId</span>
    <span class="hljs-attr">chainId</span>: <span class="hljs-string">'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906'</span>,
};

<span class="hljs-keyword">const</span> eos = Eos(config);
</code></pre>
<p>이렇게 넣고 나서 아래의 api 예제를 직접 코딩하고 nodeJs로 javascript를 실행하면 값이 나옵니다.</p>
<blockquote>
<p>Bp Endpoint마다 응답속도 혹은 신뢰도가 각각 다르게 때문에 본인에 가장 맞는 bp를 사용하기를 권장합니다.</p>
</blockquote>
<h4 id="heading-getblockblocknumorid">getBlock(blockNumOrId)</h4>
<p>해당 블록의 정보를 가지고 올 수 있습니다.</p>
<p>params:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>param</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>block_num_or_id</td><td>블록의 아이디나 number</td></tr>
</tbody>
</table>
</div><p>Code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Promise</span>
eos.getBlock(<span class="hljs-number">1</span>).then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result)).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));

<span class="hljs-comment">// callback</span>
eos.getBlock(<span class="hljs-number">1</span>, <span class="hljs-function">(<span class="hljs-params">error, result</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error, result));

<span class="hljs-comment">// Parameters object</span>
eos.getBlock({<span class="hljs-attr">block_num_or_id</span>: <span class="hljs-number">1</span>}).then(<span class="hljs-built_in">console</span>.log);
</code></pre>
<p>결과 값: </p>
<pre><code class="lang-json">{ timestamp: '<span class="hljs-number">2018</span><span class="hljs-number">-06</span><span class="hljs-number">-08</span>T08:<span class="hljs-number">08</span>:<span class="hljs-number">08.500</span>',
  producer: '',
  confirmed: <span class="hljs-number">1</span>,
  previous:
   '<span class="hljs-number">0000000000000000000000000000000000000000000000000000000000000000</span>',
  transaction_mroot:
   '<span class="hljs-number">0000000000000000000000000000000000000000000000000000000000000000</span>',
  action_mroot:
   'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
  schedule_version: <span class="hljs-number">0</span>,
  new_producers: <span class="hljs-literal">null</span>,
  header_extensions: [],
  producer_signature:
   'SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne',
  transactions: [],
  block_extensions: [],
  id:
   '<span class="hljs-number">00000001405147477</span>ab2f5f51cda427b638191c66d2c59aa392d5c2c98076cb0',
  block_num: <span class="hljs-number">1</span>,
  ref_block_prefix: <span class="hljs-number">4126519930</span> }
</code></pre>
<p>해당 블록에서 어떠한 일을 했는지 보기 위해서는 transactions를 보면 됩니다. </p>
<p>transactions를 보기 위해 아래와 같이 한번 같이 해보시죠.</p>
<pre><code class="lang-javascript">[ { <span class="hljs-attr">status</span>: <span class="hljs-string">'executed'</span>,
    <span class="hljs-attr">cpu_usage_us</span>: <span class="hljs-number">1170</span>,
    <span class="hljs-attr">net_usage_words</span>: <span class="hljs-number">40</span>,
    <span class="hljs-attr">trx</span>:
     { <span class="hljs-attr">id</span>:
        <span class="hljs-string">'8a29bfa66850b7d4a2b0b62173a24c5dfe4dbd7b39c211df6309d02a85374960'</span>,
       <span class="hljs-attr">signatures</span>: [<span class="hljs-built_in">Array</span>],
       <span class="hljs-attr">compression</span>: <span class="hljs-string">'none'</span>,
       <span class="hljs-attr">packed_context_free_data</span>: <span class="hljs-string">''</span>,
       <span class="hljs-attr">context_free_data</span>: [],
       <span class="hljs-attr">packed_trx</span>:
        <span class="hljs-string">'9051595bad38f016a289000000000100a6823403ea3055000000572d3ccdcd0110e0a53cab294d7600000000a8ed3232dd0110e0a53cab294d76a0986af64b96bc65010000000000000004454f5300000000bb01496e74726f647563696e67204954414d204e6574776f726b2c20616e20454f532d426173656420444150502050726f6a656374206f6e20426c6f636b636861696e2047616d696e6720506c6174666f726d20666f722061205472616e73706172656e742047616d696e672045636f73797374656d2e202d2d576562736974653a2068747470733a2f2f6974616d2e67616d65732f656e202d2d54656c656772616d3a2068747470733a2f2f742e6d652f6974616d6e6574776f726b00'</span>,
       <span class="hljs-attr">transaction</span>: [<span class="hljs-built_in">Object</span>] } }
 ]
</code></pre>
<p>위와 같은 값으로 주며 저기에서도 transaction를 보면 actions가 있으며 그걸 보면 이 블록에서 어떤일들을 했는지 더욱 깊게 볼 수 있습니다.</p>
<h3 id="heading-getaccountaccountname">getAccount(accountName)</h3>
<p>Eos계정의 정보를 가지고 올때 사용합니다.</p>
<p>Params:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Param</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>account_name</td><td>eos 계정의 이름</td></tr>
</tbody>
</table>
</div><p>Code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Promise</span>
eos.getAccount(<span class="hljs-string">'itamnetwork1'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result))
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));

<span class="hljs-comment">// callback</span>
eos.getAccount(<span class="hljs-string">'itamnetwork1'</span>, <span class="hljs-function">(<span class="hljs-params">error, result</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error, result));

<span class="hljs-comment">// Parameters object</span>
eos.getAccount({<span class="hljs-attr">account_name</span>: <span class="hljs-string">'itamnetwork1'</span>})
    .then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result))
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));
</code></pre>
<p>결과 값</p>
<pre><code class="lang-json">{ account_name: 'itamnetwork1',
  head_block_num: <span class="hljs-number">8516805</span>,
  head_block_time: '<span class="hljs-number">2018</span><span class="hljs-number">-07</span><span class="hljs-number">-30</span>T07:<span class="hljs-number">34</span>:<span class="hljs-number">52.500</span>',
  privileged: <span class="hljs-literal">false</span>,
  last_code_update: '<span class="hljs-number">1970</span><span class="hljs-number">-01</span><span class="hljs-number">-01</span>T00:<span class="hljs-number">00</span>:<span class="hljs-number">00.000</span>',
  created: '<span class="hljs-number">2018</span><span class="hljs-number">-07</span><span class="hljs-number">-09</span>T02:<span class="hljs-number">24</span>:<span class="hljs-number">58.500</span>',
  core_liquid_balance: '<span class="hljs-number">12.6131</span> EOS',
  ram_quota: <span class="hljs-number">14976</span>,
  net_weight: <span class="hljs-number">201000</span>,
  cpu_weight: <span class="hljs-number">10401000</span>,
  net_limit: { used: <span class="hljs-number">1679786</span>, available: <span class="hljs-number">11108657</span>, max: <span class="hljs-number">12788443</span> },
  cpu_limit: { used: <span class="hljs-number">7950353</span>, available: <span class="hljs-number">6356380</span>, max: <span class="hljs-number">14306733</span> },
  ram_usage: <span class="hljs-number">10934</span>,
  permissions:
   [ { perm_name: 'active', parent: 'owner', required_auth: [Object] },
     { perm_name: 'owner', parent: '', required_auth: [Object] } ],
  total_resources:
   { owner: 'itamnetwork1',
     net_weight: '<span class="hljs-number">20.1000</span> EOS',
     cpu_weight: '<span class="hljs-number">1040.1000</span> EOS',
     ram_bytes: <span class="hljs-number">14976</span> },
  self_delegated_bandwidth:
   { from: 'itamnetwork1',
     to: 'itamnetwork1',
     net_weight: '<span class="hljs-number">0.1000</span> EOS',
     cpu_weight: '<span class="hljs-number">0.1000</span> EOS' },
  refund_request: <span class="hljs-literal">null</span>,
  voter_info:
   { owner: 'itamnetwork1',
     proxy: '',
     producers: [],
     staked: <span class="hljs-number">4000</span>,
     last_vote_weight: '<span class="hljs-number">0.00000000000000000</span>',
     proxied_vote_weight: '<span class="hljs-number">0.00000000000000000</span>',
     is_proxy: <span class="hljs-number">0</span> } }
</code></pre>
<p>위의 결과값중에 다 중요하지만 몇개만 설명을 하려 합니다.</p>
<ol>
<li><p>account_name
누구나 다 알다 싶이 eos account name 입니다.</p>
</li>
<li><p>ram_quota</p>
<p>내가 보유한 RAM 입니다. 단위는 byte입니다.</p>
</li>
<li><p>net_limit</p>
<p>해당 계정이 가지고 있는 총 net, 사용 가능한 net, 사용한 net을 나타냅니다. 단위는 byte입니다.</p>
</li>
<li><p>cpu_limit</p>
<p>해당 계정이 가지고 있는 총 cpu, 사용 가능한 cpu, 사용한 cpu을 나타냅니다. 단위는 us 입니다.</p>
</li>
<li><p>ram_usage</p>
<p>해당 계정이 사용한 RAM 입니다 단위는 byte입니다.</p>
</li>
<li><p>total_resources
나에게 할당된 리소스의 eos를 보여줍니다. (누군가가 나에게 delegated한 것도 포함됩니다.)</p>
</li>
<li><p>self_delegated_bandwidth
내가 내 자신에게 delegated한 정보 입니다.</p>
</li>
<li><p>voter_info</p>
<p>투표에 대한 정보입니다. 여기에서 눈여겨 봐야될 부분은 staked입니다. 이부분은 현재 내가 staked 한 부분인데요. 좀더 자세히 설명한다면 내가 스스로 내 자신에게 delegated한 부분과 누군가에서 delegated한 부분을 포함한 값입니다.</p>
</li>
</ol>
<h3 id="heading-getkeyaccountspublickey">getKeyAccounts(publicKey)</h3>
<p>public key에 해당하는 account들을 가지고 옵니다.</p>
<p>Params:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Param</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>public_key</td><td>EOS의 public key</td></tr>
</tbody>
</table>
</div><p>Code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Promise</span>
eos.getKeyAccounts(<span class="hljs-string">'EOS6S6C5ExCM7VHGdmG5h6VREVJEC33bpMJtLucwhyByPmzB58KW5'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result))
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));

<span class="hljs-comment">// callback</span>
eos.getKeyAccounts(<span class="hljs-string">'EOS6S6C5ExCM7VHGdmG5h6VREVJEC33bpMJtLucwhyByPmzB58KW5'</span>,
    <span class="hljs-function">(<span class="hljs-params">error, result</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error, result));

<span class="hljs-comment">// Parameters object</span>
eos.getKeyAccounts({<span class="hljs-attr">public_key</span>: <span class="hljs-string">'EOS6S6C5ExCM7VHGdmG5h6VREVJEC33bpMJtLucwhyByPmzB58KW5'</span>})
    .then(<span class="hljs-built_in">console</span>.log);
</code></pre>
<p>결과값:</p>
<pre><code class="lang-json">{ account_names: [ 'itamnetwork1' ] }
</code></pre>
<p>EOS의 public key 한개로 여러 account를 만들수 있습니다. 그렇게 때문에 account_name의 값이 string으로 이루어진 array 입니다.</p>
<h3 id="heading-getcurrencybalancecode-account-symbol">getCurrencyBalance(code, account, symbol)</h3>
<p>code의 symbol에 해당하는 Token을 가지고 옵니다. </p>
<p>Params:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Param</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>code</td><td>컨트렉트 명 혹은 해당 컨트렉트가 있는 account명을 말합니다.<br />ex) eosio.token, therealkarma 등등</td></tr>
<tr>
<td>account</td><td>조회할 EOS의 계정명 입니다.</td></tr>
<tr>
<td>symbol</td><td>Token의 symbol 입니다. 이부분은 필수값이 아닌 옵션 값입니다.</td></tr>
</tbody>
</table>
</div><p>Code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Promise</span>
eos.getCurrencyBalance(<span class="hljs-string">'eosio.token'</span>, <span class="hljs-string">'itamnetwork1'</span>, <span class="hljs-string">'EOS'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result))
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));

<span class="hljs-comment">// callback</span>
eos.getCurrencyBalance(<span class="hljs-string">'eosio.token'</span>, <span class="hljs-string">'itamnetwork1'</span>, <span class="hljs-string">'EOS'</span>,
    <span class="hljs-function">(<span class="hljs-params">error, result</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error, result));

<span class="hljs-comment">// Parameters object</span>
eos.getCurrencyBalance({<span class="hljs-attr">account</span>: <span class="hljs-string">'itamnetwork1'</span>, <span class="hljs-attr">code</span>: <span class="hljs-string">'eosio.token'</span>, <span class="hljs-attr">symbol</span>: <span class="hljs-string">'EOS'</span>})
    .then(<span class="hljs-built_in">console</span>.log);
</code></pre>
<p>결과값:</p>
<pre><code class="lang-json">[ '<span class="hljs-number">12.6131</span> EOS' ]
</code></pre>
<p>결과 값을 보면 string형식의 array가 나옵니다. 이유는 해당 컨트렉트안에 여러 symbol을 가진 token들이 있을수 있기 때문입니다. EOS 테스트넷인 정글넷을 보면 symbol을 제외하고 eosio.token을 조회하면 2개의 token들을 볼수 있습니다.</p>
<h3 id="heading-getcurrencystatscode-symbol">getCurrencyStats(code, symbol)</h3>
<p>symbol에 해당하는  Token의 정보를 가지고 옵니다.</p>
<p>Params:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Param</td><td>설명</td></tr>
</thead>
<tbody>
<tr>
<td>code</td><td>컨트렉트 명 혹은 해당 컨트렉트가 있는 account명을 말합니다.<br />ex) eosio.token, therealkarma 등등</td></tr>
<tr>
<td>symbol</td><td>Token의 symbol 입니다.</td></tr>
</tbody>
</table>
</div><p>Code:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Promise</span>
eos.getCurrencyStats(<span class="hljs-string">'eosio.token'</span>, <span class="hljs-string">'EOS'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(result))
    .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(error));

<span class="hljs-comment">// callback</span>
eos.getCurrencyStats(<span class="hljs-string">'eosio.token'</span>, <span class="hljs-string">'EOS'</span>,
    <span class="hljs-function">(<span class="hljs-params">error, result</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error, result));

<span class="hljs-comment">// Parameters object</span>
eos.getCurrencyStats({<span class="hljs-attr">code</span>: <span class="hljs-string">'eosio.token'</span>, <span class="hljs-attr">symbol</span>: <span class="hljs-string">'EOS'</span>})
    .then(<span class="hljs-built_in">console</span>.log);
</code></pre>
<p>결과값:</p>
<pre><code class="lang-json">{ EOS:
   { supply: '<span class="hljs-number">1006148640.3388</span> EOS',
     max_supply: '<span class="hljs-number">10000000000.0000</span> EOS',
     issuer: 'eosio' } }
</code></pre>
<p>결과값에 대한 설명은 아래와 같습니다.</p>
<ol>
<li><p>supply
현재 공급된 토큰의 갯수 입니다.</p>
</li>
<li><p>max_supply</p>
<p>총 토큰의 갯수 입니다.</p>
</li>
<li><p>issuer
발행자 입니다.</p>
</li>
</ol>
<h2 id="heading-66ei66y066as7zwy66mw">마무리하며</h2>
<p>자주 쓰는 api들중 5개를 소개하는 시간을 가지게 되었습니다. 아직 더 많은 api들이 있고 다음 블로그에 이어서 많이 쓰는 api들에 대해서 연재할 계획입니다. 감사합니다.</p>
<blockquote>
<p>해당 예제는 <a target="_blank" href="https://github.com/ITAMNETWORK/eosjs-api-example">github</a>에서 확인 하실 수 있습니다.</p>
</blockquote>
<p>해당 게시글은 저의 블로그 혹은 itamnetwork 블로그에서 동일하게 확인 하실수 있습니다.</p>
]]></content:encoded></item><item><title><![CDATA[eosJs 시작하기]]></title><description><![CDATA[EOSJS 시작하기
EOSJS란?
EOS 블록체인을 javascript로 좀더 편하게 컨트롤 할 수 있게 만들어 놓은 라이브러리라고 생각하면 편하다. 살짝만 깊게 들어가면 EOS에서 제공하는(nodeos) HTTP API를 이용하게 편하게 해 놓은 거라고 말 할 수 있다. (이더리움에는 web3.js, 네오에는 neon-js) 

EOSJS도 EOS에서 제공하는 HTTP API를 사용하기 때문에 BP들을 잘 선택해야된다.(응답속도, 제공여부 등...]]></description><link>https://blog.dongjun.win/eosjs</link><guid isPermaLink="true">https://blog.dongjun.win/eosjs</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 16 Jul 2018 05:19:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-eosjs">EOSJS 시작하기</h2>
<h4 id="heading-eosjs-1">EOSJS란?</h4>
<p>EOS 블록체인을 javascript로 좀더 편하게 컨트롤 할 수 있게 만들어 놓은 라이브러리라고 생각하면 편하다. 살짝만 깊게 들어가면 EOS에서 제공하는(nodeos) HTTP API를 이용하게 편하게 해 놓은 거라고 말 할 수 있다. (이더리움에는 <a target="_blank" href="https://github.com/ethereum/web3.js">web3.js</a>, 네오에는 <a target="_blank" href="https://github.com/CityOfZion/neon-js">neon-js</a>) </p>
<blockquote>
<p>EOSJS도 EOS에서 제공하는 HTTP API를 사용하기 때문에 BP들을 잘 선택해야된다.(응답속도, 제공여부 등등)</p>
</blockquote>
<h4 id="heading-7isk7lmy">설치</h4>
<p>설치 방법으로는 2가지가 있다.</p>
<ol>
<li><p>NPM을 통해서 간단하게 설치 할 수 있다.</p>
<pre><code class="lang-bash">&gt; npm install eosjs
</code></pre>
</li>
<li><p>CNS를 이용하기</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/eosjs@15.0.3/lib/eos.min.js"</span>
        <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-QX0dPq5pyX33coEuy5x1UqKHFDeveQYMp7Sz+qOUwRL9mol4QDvViU+QAjd+k6P7QjPjrDCoyhK1kz2GDxCP9A=="</span>
        <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
</li>
</ol>
<h4 id="heading-eos-connect">EOS Connect</h4>
<ol>
<li><p>EOSJS를 이용한 EOS Connect</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Eos = <span class="hljs-built_in">require</span>(<span class="hljs-string">'eosjs'</span>);

<span class="hljs-comment">// 아래와 같이 하면 localhost Testnet에 접근한다.</span>
<span class="hljs-keyword">const</span> eos = Eos();
</code></pre>
<p>localhost에 EOS가 구동 되어있지 않는다면 위의 코드를 실행하면 아래와 같은 에러가 난다. (당연히 connect하는 EOS의 httpEndpoint가 틀려도 아래와 같은 에러가 나온다.)</p>
<pre><code class="lang-json">{ FetchError: request to http:<span class="hljs-comment">//127.0.0.1:8888/v1/chain/get_info failed, reason: connect ECONNREFUSED 127.0.0.1:8888</span>
    at ClientRequest.&lt;anonymous&gt; (/Users/mayajuni/Projects/eos-scan/node_modules/node-fetch/index.js:<span class="hljs-number">133</span>:<span class="hljs-number">11</span>)
    at ClientRequest.emit (events.js:<span class="hljs-number">182</span>:<span class="hljs-number">13</span>)
    at Socket.socketErrorListener (_http_client.js:<span class="hljs-number">382</span>:<span class="hljs-number">9</span>)
    at Socket.emit (events.js:<span class="hljs-number">182</span>:<span class="hljs-number">13</span>)
    at emitErrorNT (internal/streams/destroy.js:<span class="hljs-number">82</span>:<span class="hljs-number">8</span>)
    at emitErrorAndCloseNT (internal/streams/destroy.js:<span class="hljs-number">50</span>:<span class="hljs-number">3</span>)
    at process._tickCallback (internal/process/next_tick.js:<span class="hljs-number">63</span>:<span class="hljs-number">19</span>)
  name: 'FetchError',
  message:
   'request to http:<span class="hljs-comment">//127.0.0.1:8888/v1/chain/get_info failed, reason: connect ECONNREFUSED 127.0.0.1:8888',</span>
  type: 'system',
  errno: 'ECONNREFUSED',
  code: 'ECONNREFUSED' }
</code></pre>
</li>
<li><p>Mainnet connect 하기</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Eos = <span class="hljs-built_in">require</span>(<span class="hljs-string">'eosjs'</span>);

<span class="hljs-keyword">const</span> eos = Eos({<span class="hljs-attr">httpEndpoint</span>: <span class="hljs-string">'mainnet httpEndpoint'</span>});
</code></pre>
<p>아마 위와 같이 connect를 시도 한다면 아래와 같은 에러 메시지가 나온다.
 <img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/images/error.png" alt="connect-error" /></p>
<p>에러가 나더라도 getInfo는 가능하네 이걸 통해서 chainId를 확인해서 넣자. </p>
<blockquote>
<p>Mainnet인 경우는 aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906 이것이 공통으로 쓰는 chainId이기 때문에 넣으면 된다.</p>
</blockquote>
</li>
</ol>
<p>위와 같은 방법으로 connect를 하면 아래와 같이 최초 1회 get_info api를 호출한다. (처음에 이것도 모르고 매번 커넥트를 줬더니 모바일에서 데이터 사용량이 아주 높게 나온적이 있다.)</p>
<p><img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/images/net.png" alt="network-image" /></p>
<blockquote>
<p>메인넷 주소는 https://api.eosnewyork.io 여기에서 확인 해 볼 수 있다. BP들마다 제공하는 정보의 양, 응답속도 등등이 다르기 때문에 확인을 해서 본인에게 가장 잘 맞는 BP를 찾는 것이 좋다.(혹은 본인이 직접 full node를 받아서 연결하는 방법도 있다.)</p>
</blockquote>
<h6 id="heading-connect-config">Connect Config</h6>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Eos = <span class="hljs-built_in">require</span>(<span class="hljs-string">'eosjs'</span>);

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">chainId</span>: <span class="hljs-literal">null</span>, <span class="hljs-comment">// 32 byte (64 char) hex string</span>
  <span class="hljs-attr">keyProvider</span>: [<span class="hljs-string">'PrivateKeys...'</span>], <span class="hljs-comment">// WIF string or array of keys..</span>
  <span class="hljs-attr">httpEndpoint</span>: <span class="hljs-string">'http://127.0.0.1:8888'</span>,
  <span class="hljs-attr">expireInSeconds</span>: <span class="hljs-number">60</span>,
  <span class="hljs-attr">broadcast</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">verbose</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// API activity</span>
  <span class="hljs-attr">sign</span>: <span class="hljs-literal">true</span>
};

<span class="hljs-keyword">const</span> eos = Eos(config);
</code></pre>
<p>이부분의 자세한 내용은 github에 있으니 여기에서 확인하면 된다.(<a target="_blank" href="https://github.com/EOSIO/eosjs#configuration">github-configuration</a>)</p>
<h4 id="heading-getinfo-api">getInfo - api</h4>
<p>EOS의 기본 네트워크 정보를 가지고 올 수 있다. 코드는 아래와 같다.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> eos = Eos().getInfo(<span class="hljs-function">(<span class="hljs-params">error, info</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(error, info);
});
</code></pre>
<blockquote>
<p>재미 있는 것은 다른 api들은 전부 Promise를 기본으로 하는데 getInfo만 callback방식이다.</p>
</blockquote>
<p>리턴된 값은 아래와 같다.</p>
<pre><code class="lang-json">{
    server_version: '<span class="hljs-number">36</span>a043c5',
    chain_id:
        'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906',
    head_block_num: <span class="hljs-number">5469047</span>,
    last_irreversible_block_num: <span class="hljs-number">5468711</span>,
    last_irreversible_block_id:
        '<span class="hljs-number">00537227</span>a657d1f4fd74de877e9ad1a3839540ece45eedeaf177b20e51b2da1b',
    head_block_id:
        '<span class="hljs-number">005373770504</span>c59e992214b3056c7bdabb07c53c5a9c4785909b90fe595a119f',
    head_block_time: '<span class="hljs-number">2018</span><span class="hljs-number">-07</span><span class="hljs-number">-12</span>T08:<span class="hljs-number">58</span>:<span class="hljs-number">05.500</span>',
    head_block_producer: 'helloeoscnbp',
    virtual_block_cpu_limit: <span class="hljs-number">200000000</span>,
    virtual_block_net_limit: <span class="hljs-number">1048576000</span>,
    block_cpu_limit: <span class="hljs-number">199900</span>,
    block_net_limit: <span class="hljs-number">1048576</span>
}
</code></pre>
<p>위에서(EOS Connect) 애기 했던 chainId를 모를 경우 getInfo를 통해서 chainId를 얻어서 쓸 수 있다.</p>
<p>이번 글에서는 설치, connect, getInfo만 첫 걸음이라는 생각으로 가볍게 알아보았다.</p>
]]></content:encoded></item><item><title><![CDATA[RethinkDB 소개]]></title><description><![CDATA[RethinkDB란?
Real-Time에 최적화된 오픈소스 데이터베이스라고한다. 그리고 확장 가능한 JSON 데이터 베이스이며, 전통적인 데이터베이스 아키텍처를 바꾸어 변경 사항을 폴링하는 대신 업데이트 된 쿼리 결과를 실시간, 지속적으로 push 할 수 있다고 한다.
특징을 설명하자면 아래와 같을꺼 같다.

실시간에 최적화 되어 있다.
JSON 기반의 데이터베이스이다.
업데이트가 발생 되었을 시 지속적/실시간으로 push를 해준다.
확장이 쉬...]]></description><link>https://blog.dongjun.win/rethinkdb</link><guid isPermaLink="true">https://blog.dongjun.win/rethinkdb</guid><category><![CDATA[NoSQL]]></category><category><![CDATA[RethinkDB]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 27 Mar 2017 04:18:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://i1.wp.com/developer.ibm.com/clouddataservices/wp-content/uploads/sites/47/2017/07/rethinkdb.png" alt /></p>
<h1 id="heading-rethinkdb">RethinkDB란?</h1>
<p>Real-Time에 최적화된 오픈소스 데이터베이스라고한다. 그리고 확장 가능한 JSON 데이터 베이스이며, 전통적인 데이터베이스 아키텍처를 바꾸어 변경 사항을 폴링하는 대신 업데이트 된 쿼리 결과를 실시간, 지속적으로 push 할 수 있다고 한다.
특징을 설명하자면 아래와 같을꺼 같다.</p>
<ol>
<li>실시간에 최적화 되어 있다.</li>
<li>JSON 기반의 데이터베이스이다.</li>
<li>업데이트가 발생 되었을 시 지속적/실시간으로 push를 해준다.</li>
<li>확장이 쉬운 분산 데이터베이스</li>
<li>실시간 웹 애플리케이션 구죽을 위한 <code>오픈소스</code> 데이터 베이스</li>
<li>웹UI 관리 콘솔을 제공한다.(서버 성능 확인, 쿼리테스트 데이터 테이블과 샤드 등등을 관리하는 도구이다.)</li>
</ol>
<h2 id="heading-rethinkdb-1">RethinkDb와 실시간 동기화 서비스의 차이점은 무엇을까?</h2>
<p>RethinkDb는 Firebase, pubNub, pusher와 같은 실시간 API와 근본적으로 다른 3가지가 있다. </p>
<ol>
<li>실시간 동기화 API는 클라우드 서비스이고 RethinkDB는 오픈소스 프로젝트이다. </li>
<li>실시간 동기화 API는 문서 동기화에만 국한되며, RethinkDB는 범용 데이터베이스 시스템이다. 테이블 조인, 하위쿼리, 지형공간 쿼리 등등을 포함한 쿼리를 실행 할 수 있다. </li>
<li>실시간 동기화 API는 브라우저에서 직접 액세스하도록 설계되어 있다. 이러면 기본 앱을 쉽게 실행 할 수 있지만 앱이 확장되면 유연성이 제한된다. RethinkDB는 기존 데이터베이스와 같이 응용 프로그램 서버에서 엑세스 할 수 있도록 설계되어 있다. 쉽게 말해 많은 유연성을 가지고 있다.</li>
</ol>
<h2 id="heading-rethinkdb-mongodb">RethinkDB와 MongoDB의 차이점은 무엇일까?</h2>
<p>RethinkDB를 살펴 보면 Mongodb의 oplog가 생각이 든다. 물론 그거 말고도 비슷한 점이 많긴 하다. 하지만 기본적으로 다른 아키텍처를 기반으로 되어 있다. 개발자는 변경상항을 폴링하는 대신 실시간으로 업데이트 된 쿼리 결과를 계속 푸쉬하도록 RethinkDB에서 할 수 있다. 예로 들어 쿼리를 본다면 아래와 같다.</p>
<pre><code class="lang-javascript">r.table(<span class="hljs-string">'users'</span>).get(<span class="hljs-string">'coffeemug'</span>).changes().run()
</code></pre>
<p> 위에서 언급했지만 몽고디비의 oplog와 비교 될 수 있지만 oplog보다는 훨씬 노은 수준의 추상화를 제공한다.  RethinkDB의 피드는 쿼리 계산 엔진과 완벽하게 통합되므로 원시 복제 데이터뿐만 아니라 쿼리 결과의 변경 내용을 구독 할 수 있다. 이 아키텍처는 확장 가능한 실시간 응용 프로그램을 구축하는 데 필요한 시간과 노력을 크게 줄일 수 있다. </p>
<p>이외에도 MongoDB에 비해 여러가지 장점을 제공한다.</p>
<ul>
<li>테이블 조인, 하위 쿼리 및 대규모 병렬 분산 계산을 지원하는 고급 쿼리 언어다.</li>
<li>우아하고 강력한 연산 및 모니터링 API로 쿼리 언어와 통합되며 RethinkDB를보다 쉽게 확장 할 수 있다.</li>
<li>몇 번의 클릭만으로 샤드하고 복제 할 수있는 간단하고 아름다운 관리 UI 및 온라인 문서 및 쿼리 언어 제안을 제공한다.</li>
</ul>
<h2 id="heading-7iuc7iqk7ywcioyaloq1rcdsgqztla0">시스템 요구 사항</h2>
<p>RethinkDB 서버는 C++로 작성되었으며, 32비트 및 64비트 리눅스 시스템과 OS X 10.7이상에서 실행 할 수 있다. 
최소 2기가 이상의 램을 권장하지만 업격한 하드웨어 요구 사항은 없다.(다른 블로그를 보니 램이 부족해서 몽고디비로 이전했다는 사람도 있긴했다.) </p>
<h2 id="heading-65287j207is87iqk64quioustoyxhydhoq5jd8">라이센스는 무엇을까?</h2>
<p>RethinkDB 서버 및 클라이언트 드라이버는 <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache License 버전 2.0</a> 에 따라 사용이 허가됩니다 .</p>
<p>다음에는 간단한 설치부터 쿼리하는거까지 진행을 해보도록 하겠다. </p>
<p>참고사항</p>
<ol>
<li><a target="_blank" href="https://www.rethinkdb.com/faq/">Rethink FAQ</a></li>
<li><a target="_blank" href="https://www.rethinkdb.com">RethinkDB</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[2016년 회고]]></title><description><![CDATA[블로그를 운영 아닌 운영을 하면서 올해부터는 새롭게 회고를 해볼까 한다.막상 회고를 한다고 하니 어떻게 글을 시작해야될지 머리속이 정리가 안된다. 또한 좋지 못하는 기억력을 다시한번 실감하게 되기도 하다.시작을 어떻게 할까 고민을 하다가 카테고리를 나누어 써볼까 한다. 
여행
1. 일본(오키나와)
 1월에 전에 다녔던 회사(컴팔)에서 처음으로 해외 워크샵(이라고 부르지만 그냥 놀러, 술마시러)을 갔다왔다. 오키나와는 일본의 휴양도시고 아주 좋다...]]></description><link>https://blog.dongjun.win/2016</link><guid isPermaLink="true">https://blog.dongjun.win/2016</guid><category><![CDATA[2016]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Fri, 30 Dec 2016 02:41:00 GMT</pubDate><content:encoded><![CDATA[<p>블로그를 운영 아닌 운영을 하면서 올해부터는 새롭게 회고를 해볼까 한다.<br />막상 회고를 한다고 하니 어떻게 글을 시작해야될지 머리속이 정리가 안된다. 또한 좋지 못하는 기억력을 다시한번 실감하게 되기도 하다.<br />시작을 어떻게 할까 고민을 하다가 카테고리를 나누어 써볼까 한다. </p>
<h2 id="heading-7jes7zaj">여행</h2>
<h5 id="heading-1">1. 일본(오키나와)</h5>
<p> 1월에 전에 다녔던 회사(컴팔)에서 처음으로 해외 워크샵(이라고 부르지만 그냥 놀러, 술마시러)을 갔다왔다. 오키나와는 일본의 휴양도시고 아주 좋다는 애기를 많이 들어 되게 기대했다. 하지만 정말 실망적이였다. 비싼 가격, 같이간 사람들, 아름답지 않은 날씨 등등이 나에게 있어서는 아주 매력적이지 않았다. 그 돈이면 차라리 동남아를 가고 말지 라는 생각도 많이 들었다. 음식도 비싸기만 했지 별로.... 내가 이렇게 실망한 가장 큰 이유는 여행 스타일때문인거 같기도 하다. 회사에서 간거라 그런지 계속 차타고 이동 내리고 구경 저녁에 술... 역시 나는 배낭을 매고 이리저리 내 발로 걸어 다니면서 다니는 여행이 나에게 맞는거 같다. 아니면 아에 휴양이거나, 같이 간 사람들도 회사 사람들이다 보니 더욱 별로였던거 같다.(이 회사는 사내정치가 심한 회사라 직원들끼리 파가 나누어져있었다) 위안으로 삼은건 내돈 주고 온게 아니고 회사돈으로 와서 다행이다(?)이정도...</p>
<h5 id="heading-2">2. 인도(+ 말레시아 잠시)</h5>
<p> 2014년도에 만난 인도 친구에게 결혼하면 꼭 결혼식에 참석하겠다라는 약속을 지키기 위해 떠난 여행이다. 다시 한번 인도에 대해 느끼게 되는 여행이기도 하다. 인도는 너무너무 힘들다..하하.... 내 여행인생 처음으로 비행기도 놓쳐보고(이때 진짜 인도가 싫었다. 그놈의 퍼스트서비스가 먼지 어떻게든 돈을 더 받아 먹을려고...에흉) 잠도 많이 못자기도 했다. 하지만 정말 좋은 점들도 있었다. 우선 정말 가족같은 우리 누나와 형님을 오랫만에 만난점.. 그리고 인도 친구의 결혼식(힌두결혼식)을 경험해 본것. 말레이시아에서 회사 직원과 함께 구경하고 밥먹었던 점은 정말 소중한 추억이고 기억이다. 아 특히 힌두 결혼식은 정말 특별하고 힘들었다. 이게 짧은거라고 하지만 나에게는 정말 하하... 결혼식 전날 신랑쪽(우리는 신부쪽이다)에서 마련한 공간에서 같이 춤추고 밥먹고 즐기는 것도 너무 재미 있었지만.. 결혼식 당일 새벽 4시에 일어나서 기도로 시작하고 결혼식장까지 가서 기도로 끝나는 모습은 정말... 하하... 종교가 대단하다는 생각도 많이 했다. 특히 인도 정통의상을 선물해 주고 그걸 입고 참석한 일, 외국인이라서 특히 2년전 약속을 지키기위해 온 우리를 좋게 생각해줘서 그런지 정말 이리저리 불려 많이 다녔다.. 5분도 못쉬고 정말 이리저리..(나의 사촌오빠의 엄마의 할아버지 등등) 그때 생각하면 참 힘들었지만 너무 즐거웠다.(하지만 힌두 결혼식은 2번 참석하기 힘들꺼 같다..하하) 마지막 돌아오는 하루는 말레이시아에 들렸다. 거기에서는 말레이시아 회사 직원이 차로 우리를 관광도 시켜줬다. 너무너무 고맙고 좋았던 기억이다. 거기서 잘때 호스텔에서 잤는데 특별했다 옥상에 올라가는 바가 있었는데 바로 쌍둥이 빌딩도 보이고 음악도 너무 좋고, 거기다 우리는 간단하게 맥주 마시는데 역시 배낭여행객이 많아서 그런데 너무 자연스럽게 외국인(우리도 외국인이긴하지만 하하)들과 함께 조인되어서 짧은 영어로 이리저리 애기하는게 너무 재미 있었다.</p>
<h5 id="heading-3">3. 베트남</h5>
<p>회사를 그만두기로 하고 시간이 비어 떠난 베트남 여행이다. 하노이, 다낭, 호이안, 사파까지 비자기간인 2주를 거의 꽉 채웠다. 처음에는 혼자 먼저 떠나서 하노이에서 머물다가 와이프랑 와이프친구 만나고 와이프가 먼저 귀국하면 그다음에는 친동생같은 동생이 와서 같이 여행하는 좀 특이한 여행일정이였다. 하노이 생각하면 내가 지냈던 호스텔이 아직도 생각난다. 하룻밤에 6불인데 조식도 주고 밤에는 맥주도 무제한으로 줬었다..(남는게있어???) 특히 맥주 무제한 시간에는 이런 저런 사람들과 함께 애기하고 놀고 게임하는게 아주아주 재미있었다. 이때 영국남자 미국남자 미국커플 한국인들이랑 친해져서 같이 밥도 먹고.. 게임도 옆에 하노이 놀러온 중국인들과도 함께 했는데 너무너무 즐거웠다. 역시 우린 위아더 월드!!<br />와이프와서 같이간 다낭, 호이안 여기도는 하노이와 또 다른 매력을 지닌 곳이다. 여기에서 인생 조개요리를 먹었다. 로컬식당인데 영어를 못해서 서로 몸짓으로 음식 시키고 대화 했던거 생각하면 너무 재미있었다. 또 바로 옆 테이블 로컬 사람들이랑 같이 조인되서 영어를 못해 몸짓으로 서로 대화하고 맥주를 짝으로 마신 기억은 진짜 아우 ㅎㅎㅎ 너무 특별하다. 호이안은 너무 아름다운 도시였다. 하지만 여기서 진행했던 호핑투어는 진짜 아주 안좋은 기억이었다. 다행히 호텔에 컴플레인을 했더니 죄송하다고 하며, 돈을 깍아주고 공항까지 가는 택시를 지원해줬다. 나는 이런걸 원하는게 아니라서 거절을 막 했더니 이걸 안받으면 자신들이 혼난다고 하여 어쩔수 없이 받았다. 이 호텔은 정말 좋았다.. 다만 호핑투어가 안좋았을뿐...<br />다시 하노이 와서 와이프가 떠나고 동생이 왔다. 베트남에서 가장 좋았던 도시 <code>사파</code>로 떠났다. 하노이는 너무나 더웠는데 사파는 아주 시원했다. 그리고 산에 있어서 그런지 뷰가 아주 그냥... 음식도 너무 맛나고.. 진짜 천국(?) 여기에서도 나의 오지랍(?)으로 현지인 베트남 사람과 식당에서 함께 음식을 먹고 술도 한잔하면서 놀았다..하하.... 
바디랭기쥐는 영어도 필요 없게 한다... 영어 따윈 훗! 베트남은 사랑이다!!!</p>
<h5 id="heading-4">4. 체코</h5>
<p>어머니 환갑으로 떠나게 된 여행이다. 이 여행에서는 와이프에게 너무 고맙다. 시어머니랑 가는것도 쉽지 않은데 시어머니의 친구분(70대의 언니분이다. 이모라 부른다.)까지 함께 했으니 얼마나 힘들었을까... 모스크바를 들렸다가 체코 프라하를 갔다.
비행기는 러시아 항공을 이용했는데 만족스러웠다. 짐도 잃어버리지 않고.. 대망의 첫 모스크바.. 나는 정말 붉은성의 야경을 보고 싶었는데 못본게 가장 아쉽다. 하지만 모스크바(러시아)는 2번가진 않을꺼 같다. 물가도 비싸다능... 체코는 너무나 좋았다 특히 맥주는 아주 그냥.. 하루를 맥주로 시작하고 맥주로 끝냈다. 도시들도 너무나 아름답고 물가도 너무 싸다.. 만약 디지털 노마드를 한다고 하면 체코에서 살고 싶은 생각이다.. 아 첫 에어비앤비도 너무 저렴하고 좋았다. </p>
<h5 id="heading-5">5. 종합</h5>
<p>올해만 4번이나 여행을 갔다 왔지만 항상 또 가고 싶다.. 그리고 귀국하면 느낌은 꼭 갔다오지 않은 듯한 느낌이다. 동내 근처 놀러갔다온 느낌?? 그리고 항상 영어를 잘하고 싶다라고 생각해도 작심 3일... 2017년에는 꼭 영어 공부를 잘 해야겠다. 앞으로 2017년에 이미 잡혀 있는 10월(보라카이)여행을 말고는 자제를 해야겠다.. 돈도 모아서 집을 사야지.. 아시아나 마일리지가 동남아 왕복권이 있어서 내년에 한번 더 갈 가능성은 있긴하다..하하..</p>
<h2 id="heading-6rca7kgx">가족</h2>
<p>기억에 나는건 역시나 우리 할아버지께서 돌아가신거다. 할아버지를 생각하면 말이 없으시지만 우리를 챙겨주시는 모습, 할머니가 잔소리 할때마다 은근슬쩍 피하는 모습.. 그리고 뇌졸증으로 쓰러지시고나서 회복후에는 할머니와 함께 손잡고 다니는 모습.. 할머니도 걱정되면서 어머니도 걱정 되었다. 하지만 걱정과 다르게 이겨내시는 모습에 안심이 된다. </p>
<blockquote>
<p>매년 경험하는 죽음.. 무언가 나에게 먼 느낌이면서도 아주 가까운 곳에 있는거 같다.. 여기서 다시 한번 생각한다. 나의 삶의 목표에 대해.. 그리고 중요한게 무엇인지 다시 한번 되세김 해본다.</p>
</blockquote>
<h2 id="heading-7zqm7iks">회사</h2>
<h5 id="heading-7lu07yyu">컴팔</h5>
<p>2년 넘게 함께한 그리고 아주 많은 일들이 있어던 회사(컴팔)에서 이직을 했다. 컴팔에서는 너무 편했다. 할일도 많지 않았고 내가 하고싶은데로 하면 되었기 때문이다. 하지만 나에게 있어서 너무 나태하게 하는 곳이기도 했다. 내가 짠 소스가 정확히 맞는 소스인지도 모르겠고, 누군가와 함게 개발을 하고 싶었다. 코드 리뷰도 하고 싶었고 개발적인 토론도 하고 싶었다. 컴팔은 나에게 아주 큰 공부도 되고 배움도 되는 곳이였지만 슬슬 도전을 해야 된다고 생각이 되는 시기였다. </p>
<h5 id="heading-7yq466ci7ys7yq4">트레포트</h5>
<p>스카웃(면접제의)가 와서 면접을 보고 입사를 했다. 트레포트에서는 나를 포함한 우리 팀은 6명이였다. 신규 프로젝트를 진행했는데 내가 공부를 하고 있었던 angular2로 진행하였다. 여기서 문제는 나를 제외하고 전부 angular2를 몰랐다.. 한달간 리딩을 하고 공부를 진행하면서 프로젝트를 진행하였는데. 너무나도 고맙게 팀원들이 아주 잘 따라와 주었고 내가 만족할 정도로 포퍼먼스도 내주었다.(우리 팀원은 4년차 1명 2년차 1명 남어지는 신입이다..) rxjs와 data flow 기반의 코딩은 역시나 어려웠다. 나조차도 확실히 알지 못하는 상황에서 누군가를 가르친다는건 참 모순적이기도 했다. 그래도 팀원들이 아주 잘 따라와주었지만 아쉽게도 회사 사정상 첫번째 프로젝트는 중단하게 되었다. 이어서 한 두번째 프로젝트 기간은 길지 않았고 빠르게 진행하게 되었다. 그래서 우리는 이미 만들어진 컴포넌트를 찾게 되었고, 우리는 아이오닉을 기반으로 진행하게 되었다. 하지만 아이오닉은 자체 라우터를 제공하였고 우리는 그걸 사용하지 못했다. 이유는 아이오닉 자체 라우터는 url 방식이 아니기 때문이다. angular2 라우터를 붙이게 되었고 리덕스를 포함하여 프로젝트를 설계하고 진행하게 되었다. 하지만 트레포트와 인연이 여기까지였다. 나의 의지와 상관 없이 회사에서의 일방적인 통보.. 팀원들과 헤어진다는게 너무 아쉬웠고 팀원들도 같이 화를 내주었다.(2016년 말에 같이 한잔을 했다능..그리고 그 팀원들중에서도 회사를 나온 사람이 좀 있더라...)</p>
<blockquote>
<p>내가 만약 40대 50대 아이가 있는데 회사에서 갑자기 퇴사 통보를 한다고 하면.. 얼마나 당황하고 힘들까 라는 생각이 많이 들었다. 회사의 의존된 내가 되지 말고 회사와 관계 없이 나의 가치를 더 올려야겠다.</p>
</blockquote>
<h5 id="heading-o2palm">o2palm</h5>
<p>트레포트를 들어가기전에 스카웃 제의를 받았던 곳이다. 트레포트를 뒤로 두고 입사하게 되었다. 여기는 아에 첫 스타트 업으로 아직 캐쉬카우가 없는 곳이여서 엄청 고민을 했던 곳이였다. 여기 회사는 되게 자유도가 높은 회사이다. 출퇴근 시간이 정해져 있지 않으며, 자택근무를 원하면 언제든 자택근무도 가능하다. 그리고 1년이 지나지 않아도 휴가를 묻지 않고 마음대로 쓸수 있는 곳이다. 원하면 굳이 한국에서 일 안해도 된다고 한다.. 역시 자유에는 그만큼의 책임이 있다. 오투팜은 철저한 성과위주이다. 성과가 없으면 연봉이 감봉된다. 역시나 성과가 좋으면 연봉이 상승되는 구조로 되어 있다. 아직 체계를 잡아가는 중이지만 많이 잡혀가고 있다. 모두다가 지라를 사용하고 있고(개발 뿐만 아니라 모든 직원 대표님까지), 슬랙을 이용하며, 리모트 환경을 잡아가고 있다. 현재 nodeJs와 angular2 그리고 아이오닉으로 개발을 진행하고 있다. 내가 항상 해왔던 것들.. 열심히 해야지..
다만 개발팀에서 가장 아쉬운건 신입 2명이다. 올해에는 솔찍히 실망이 컸다. 트레포트의 신입들과 비교가 많이 된다. 내년에는 좀더 잘할꺼라 생각을 하고 다시한번 기대를 해본다.</p>
<h2 id="heading-6rcc67cc">개발</h2>
<p>2016년도는 기존 angular1에서 angular2로 바꾸기 시작했고 실제 프로젝트에서도 사용하기 시작했다. 그리고 스터디도 가장 많이 하게 되는 년도이기도 하다. 많은 선배님들이 도와주었고 특히 유자소프트에 합류하게 된것은 너무나도 좋은 기회였고 많은 것들을 배우고 있다.<br />프론트엔드 개발자로써 좀더 나아가기 위해서 어떻게 해야될까 라는 생각을 다시 한번 하게 되었다. javascript에 대해 공부를 좀더 하게 되었고, 오픈소스도 만들게 되었고 그리고 첫 오픈소스 첫 이슈도 등록 되었다.(아직 이슈 처리를 못했다. 얼렁해야지) 많은 글은 등록하지 못했지만 블로그도 시작하게 되었다. </p>
<p><img src="http://i64.tinypic.com/2ljrvo9.png" alt /></p>
<h2 id="heading-66ei7kea66ej7jy866gcli4">마지막으로..</h2>
<p>2016년도는 많은 일들이 있었다. 가장 힘든일은 커뮤니케이션에 대한 것이었다. 모든걸 솔찍하게 애기하는건 정말 좋지 않구나 라는 생각도 하게 되었고 말을 많이 안하는게 좀더 좋은 커뮤니케이션인가 라는 생각도 하게 되었다. 솔찍히 아직도 그건 나에게 하나의 숙제로 남아있다. 2017년에도 아마 이건 나의 고민이지 않을까 싶다. 그리고 2016년도에 나를 많이 도와주신 분들이 있는데 너무너무 감사하다고 전달하고 싶다. 특히 자존감에 대해 한번더 생각하게 해준 피터, 항상 내 옆에서 인생의 선배로 그리고 개발자의 방향으로 도움을 주는 동재형님, 나에게 항상 힘이 되는 자성이, 새로운 시각을 가지게 해준 마이클, 항상 개발자의 모습을 보여주고 조언을 해주는 성균형님 너무너무 감사하다.<br />그리고 항상 내 옆에서 응원해주고 힘이 되어 준 우리 와이프 너무 사랑한다. </p>
<blockquote>
<p>항상 조금씩 0.000001미리라도 나아가야겠다. 자신감보다 자존감을 키우자.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[es2015 요약]]></title><description><![CDATA[ES2015
  ES2015는 Javascript의 버전이다.
 Ecma라는 단체에서 기존의 결점을 보완한 표준 자바스크립트 버전을 매년 발표한다.
 ES는 EcamaScript의 줄임말이다.
let

블록 스코프 변수(block scoped variable)이다.
var가 함수 스코프 변수 이라는 점에서 대비된다.
블록 단위안에서도 hoisting 되지 않는다.
같은 스코프에서 재선언이 불가능하다.

ex:

블록 스코프 변수 / 함수 스코프...]]></description><link>https://blog.dongjun.win/es2015</link><guid isPermaLink="true">https://blog.dongjun.win/es2015</guid><category><![CDATA[es2015]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 05 Dec 2016 05:59:02 GMT</pubDate><content:encoded><![CDATA[<h1 id="heading-es2015">ES2015</h1>
<p>  ES2015는 Javascript의 버전이다.
 Ecma라는 단체에서 기존의 결점을 보완한 표준 자바스크립트 버전을 매년 발표한다.
 ES는 EcamaScript의 줄임말이다.</p>
<h1 id="heading-let">let</h1>
<ul>
<li>블록 스코프 변수(block scoped variable)이다.</li>
<li>var가 함수 스코프 변수 이라는 점에서 대비된다.</li>
<li>블록 단위안에서도 hoisting 되지 않는다.</li>
<li>같은 스코프에서 재선언이 불가능하다.</li>
</ul>
<p>ex:</p>
<ol>
<li>블록 스코프 변수 / 함수 스코프 변수 예제<pre><code class="lang-javascript"><span class="hljs-keyword">if</span>(<span class="hljs-literal">true</span>) {
    <span class="hljs-keyword">var</span> a = <span class="hljs-string">'a'</span>;
    <span class="hljs-keyword">let</span> b = <span class="hljs-string">'b'</span>;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a =&gt; '</span>, a); <span class="hljs-comment">// a</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b =&gt; '</span>, b); <span class="hljs-comment">// b</span>
}
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a =&gt; '</span>, a); <span class="hljs-comment">// a</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b =&gt; '</span>, b); <span class="hljs-comment">// b is not defined</span>
</code></pre>
</li>
<li>hoisting 예제<pre><code class="lang-javascript"><span class="hljs-keyword">if</span>(<span class="hljs-literal">true</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a =&gt; '</span>, a); <span class="hljs-comment">// undefined</span>
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'b =&gt; '</span>, b); <span class="hljs-comment">// b is not defined</span>
    <span class="hljs-keyword">var</span> a = <span class="hljs-string">'a'</span>;
    <span class="hljs-keyword">let</span> b = <span class="hljs-string">'b'</span>;
}
</code></pre>
</li>
<li>같은 스코프에서 재선언 불가능 예제<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> a = <span class="hljs-string">'a'</span>;  <span class="hljs-comment">// a = 'a'</span>
<span class="hljs-keyword">var</span> a = <span class="hljs-string">'aa'</span>; <span class="hljs-comment">// a = 'aa'</span>
<span class="hljs-keyword">let</span> b = <span class="hljs-string">'b'</span>;  <span class="hljs-comment">// b = 'b'</span>
<span class="hljs-keyword">let</span> b = <span class="hljs-string">'bb'</span>; <span class="hljs-comment">// Identifier 'b' has already been declared</span>
</code></pre>
</li>
</ol>
<h1 id="heading-const">const</h1>
<ul>
<li>읽기 전용 상수</li>
<li>객체를 할당하면 참조값이 상수에 할당되므로 객체의 프로퍼티는 변경 가능</li>
<li>const 또한 block scoping을 따르며 hoisting 되지 않는다.</li>
</ul>
<p>ex:</p>
<ol>
<li>읽기 전용 예제<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> a = <span class="hljs-string">'a'</span>; <span class="hljs-comment">// a = 'a'</span>
a = <span class="hljs-string">'b'</span>        <span class="hljs-comment">// Assignment to constant variable.</span>
</code></pre>
</li>
<li>프로퍼티 변경 예제<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> a = {<span class="hljs-attr">name</span>: <span class="hljs-string">'a'</span>};
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a.name =&gt; '</span>, a.name); <span class="hljs-comment">// a</span>
a.name = <span class="hljs-string">'b'</span>;
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a.name =&gt; '</span>, a.name); <span class="hljs-comment">// b</span>
</code></pre>
</li>
</ol>
<h1 id="heading-set-collection">Set - Collection</h1>
<ul>
<li>Set은 유일한 값들로 구성된 Collection</li>
<li>add한 순서대로 원소를 가지고 있다.</li>
<li>중복된 값은 자동 삭제된다.</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> a = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();
a.add(<span class="hljs-number">1</span>).add(<span class="hljs-number">2</span>).add(<span class="hljs-number">1</span>); <span class="hljs-comment">// Set {1, 2}</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'a size: '</span>, a.size); <span class="hljs-comment">// 2</span>
a.delete(<span class="hljs-number">2</span>); <span class="hljs-comment">// Set {1}</span>
a.forEach(<span class="hljs-function"><span class="hljs-params">f</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(f)); <span class="hljs-comment">// 1</span>
</code></pre>
<h1 id="heading-weakset-collection">WeakSet - Collection</h1>
<ul>
<li>weakly하게 값을 참조한다는 뜻인데 WeakSet이 갖는 객체의 참조값이 다른곳에서 참조되지 않으면 객체는 garbage collect 대상</li>
<li>객체 참조값만 가진다.</li>
<li>iterable 객체가 아니다.</li>
<li>.has(), .get(), .set(), .delete()만 지원</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> ws = <span class="hljs-keyword">new</span> <span class="hljs-built_in">WeakSet</span>();
<span class="hljs-keyword">const</span> value = {<span class="hljs-attr">a</span>: <span class="hljs-string">'a'</span>};
ws.add(value).add({<span class="hljs-attr">b</span>: <span class="hljs-string">'moon'</span>});
<span class="hljs-built_in">console</span>.log(ws); <span class="hljs-comment">// WeakSet {Object {b: "moon"}, Object {a: "a"}}</span>
<span class="hljs-comment">/* after the garbage Collection has run */</span>
<span class="hljs-built_in">console</span>.log(ws); <span class="hljs-comment">// WeakSet {Object {a: "a"}}</span>
</code></pre>
<h1 id="heading-map-collection">Map - Collection</h1>
<ul>
<li>key-value로 이루어진 Collection</li>
<li>iterable 객체</li>
<li>삽입한 순서대로 원소를 가진다.</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> map = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
<span class="hljs-keyword">const</span> obj = {<span class="hljs-attr">a</span>: <span class="hljs-number">2</span>};
map.set(obj, <span class="hljs-number">2</span>).set(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>).set(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'map has 1: '</span>, map.has(<span class="hljs-number">1</span>)); <span class="hljs-comment">// map has 1:  true</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'value of 1: '</span>, map.get(<span class="hljs-number">1</span>)); <span class="hljs-comment">// value of 1: 2</span>
map.delete(<span class="hljs-number">1</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'map has 1: '</span>, map.has(<span class="hljs-number">1</span>)); <span class="hljs-comment">// map has 1: false</span>
</code></pre>
<h1 id="heading-weakmap-collection">WeakMap - Collection</h1>
<ul>
<li>WeakSet 과 비슷한 개념으WeakMap 의 key 가 약하게 참조된다.</li>
<li>key는 객체참조 값만을가진다.</li>
<li>iterable 객체가 아니다.</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> ws = <span class="hljs-keyword">new</span> <span class="hljs-built_in">WeakMap</span>();
<span class="hljs-keyword">const</span> obj = {<span class="hljs-attr">a</span>: <span class="hljs-string">'a'</span>};
ws.set(obj, <span class="hljs-number">1</span>).set({<span class="hljs-attr">b</span>: <span class="hljs-number">2</span>}, <span class="hljs-number">2</span>);
<span class="hljs-built_in">console</span>.log(ws); <span class="hljs-comment">// WeakMap {Object {a: "a"} =&gt; 1, Object {b: 2} =&gt; 2}</span>
<span class="hljs-comment">/* after the garbage Collection has run */</span>
<span class="hljs-built_in">console</span>.log(ws); <span class="hljs-comment">// WeakMap {Object {a: "a"} =&gt; 1}</span>
</code></pre>
<h1 id="heading-arrow-function">arrow function</h1>
<ul>
<li>보다 간결한 구문을 지닌 익명함수이다.</li>
<li>자기 고유의 this 를 갖지 않고, 외부 스코프의 this 를 그대로 가진다 (lexical
binding)</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> func = <span class="hljs-function">(<span class="hljs-params">x, y</span>) =&gt;</span> { <span class="hljs-keyword">return</span> x + y; };
<span class="hljs-comment">/* json을 리턴할 때에는 괄호를 넣어준다. */</span>
<span class="hljs-keyword">const</span> func2 = <span class="hljs-function">() =&gt;</span> ({ <span class="hljs-attr">foo</span>: <span class="hljs-number">1</span> });
</code></pre>
<h1 id="heading-class">class</h1>
<ul>
<li>기존모델에 단지 새롭게 추가된 구문일 뿐,전혀 새로운 객체지향 모델이 아니다.</li>
<li>생성자나 상속의 좀 더 간단하고 명확한 구문이 제공된다.</li>
<li>생성자는 constructor 로 표현된다.</li>
<li>클래스 바디는 중괄호 안에 두고 여기에 메소드를 function 키워드 없이 정의한다.</li>
<li>메소드는 prototype 프로퍼티에 추가된다.</li>
<li>class는 프로퍼티와 메소드로 이루어져 있다.</li>
<li>class 는 extends 구문으로 다른 class 를 상속한다.</li>
<li>자식 클래스에 constructor 가 없으면 부모의 그것이 자동으로 호출된다.</li>
<li>생성자에서 super 키워드를 통해 상속 계층을 구현한다. this 보다 먼저 사용하지 않으면 예외가 발생한다.</li>
<li>static 키워드를 통해 정적 메소드를 만들 수 있다. 이 메소드는 클래스 prototype 프로퍼티가 아닌 클래스 자체 메소드
다. (유틀리티 함수 작성에 쓰인다.)</li>
</ul>
<p>ex:</p>
<ol>
<li>일반 예제<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Point</span> </span>{
    <span class="hljs-keyword">constructor</span>(x, y) {
        <span class="hljs-built_in">this</span>.x = x;
        <span class="hljs-built_in">this</span>.y = y;
    }
    <span class="hljs-keyword">static</span> distance(a, b) {
        <span class="hljs-keyword">const</span> dx = a.x - b.x;
        <span class="hljs-keyword">const</span> dy = a.y - b.y;
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.sqrt(dx*dx + dy*dy);
    }
}
<span class="hljs-keyword">const</span> p1 = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">5</span>, <span class="hljs-number">5</span>);
<span class="hljs-keyword">const</span> p2 = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">10</span>, <span class="hljs-number">10</span>);
<span class="hljs-built_in">console</span>.log(Point.distance(p1, p2)); <span class="hljs-comment">// 7.0710678118654755</span>
</code></pre>
</li>
<li>상속<pre><code class="lang-javascript"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Cat</span> </span>{
    <span class="hljs-keyword">constructor</span>(name) {
        <span class="hljs-built_in">this</span>.name = name;
    }
    speak() {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.name + <span class="hljs-string">' makes a noise.'</span>);
    }
}
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Lion</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Cat</span> </span>{
    speak() {
        <span class="hljs-built_in">super</span>.speak();
        <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.name + <span class="hljs-string">' roars.'</span>);
    }
}
<span class="hljs-keyword">const</span> lion = <span class="hljs-keyword">new</span> Lion(<span class="hljs-string">'happy'</span>);
lion.speak();
</code></pre>
</li>
</ol>
<h1 id="heading-template-string">Template string</h1>
<ul>
<li>백틱(Backtick)을 이용해 문자열을 만드는 새로운 방법</li>
<li>코드의 가독성을 높여준다.</li>
<li>${} 표현식을 사용하여 변수, 함수, 연산식 등을 표현 할 수 있다.</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> name = <span class="hljs-string">'dowon'</span>;
<span class="hljs-keyword">const</span> myStr = <span class="hljs-string">`Hi <span class="hljs-subst">${name}</span>. Have a great day!`</span>;
<span class="hljs-built_in">console</span>.log(myStr); <span class="hljs-comment">// Hi dowon. Have a great day!</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"string text line 1\n"</span>+
<span class="hljs-string">"string text line 2"</span>);
<span class="hljs-comment">/* "string text line 1
 string text line 2" */</span>
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`string text line 1
string text line 2`</span>);
<span class="hljs-comment">/* "string text line 1
 string text line 2" */</span>
<span class="hljs-keyword">const</span> a = <span class="hljs-number">5</span>;
<span class="hljs-keyword">const</span> b = <span class="hljs-number">10</span>;
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Fifteen is <span class="hljs-subst">${a + b}</span> and not <span class="hljs-subst">${<span class="hljs-number">2</span> * a + b}</span>.`</span>); <span class="hljs-comment">// Fifteen is 15 and not 20.</span>
<span class="hljs-keyword">const</span> a = <span class="hljs-function">() =&gt;</span> <span class="hljs-string">"test"</span>;
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`a: =&gt; <span class="hljs-subst">${a()}</span>`</span>);
</code></pre>
<h1 id="heading-forof">for~of 구문</h1>
<ul>
<li>for~in 문은 객체의 열거가능한 모든 속성에 대해 반복했다면, for ~ of 문은 컬렉션의 요소를 반복.</li>
<li>forEach(), for in 구문과 달리, break, continue, 그리고 return 구문과 함께 사용할 수 있습니다.</li>
<li>for~of 루프 구문은 data를 순회하기 위한 구문</li>
<li>배열 뿐만 아니라 Collection 객체, DOM NodeList 등등 을 다를수 있다.</li>
</ul>
<p>ex)</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> chr <span class="hljs-keyword">of</span> <span class="hljs-string">"12"</span>) {
<span class="hljs-built_in">console</span>.log(chr);
}
<span class="hljs-comment">//1</span>
<span class="hljs-comment">//2</span>
<span class="hljs-keyword">let</span> iterable = [<span class="hljs-number">10</span>, <span class="hljs-number">20</span>, <span class="hljs-number">30</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> value <span class="hljs-keyword">of</span> iterable) {
    <span class="hljs-built_in">console</span>.log(value);
}
<span class="hljs-keyword">let</span> iterable = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>([<span class="hljs-number">0x00</span>, <span class="hljs-number">0xff</span>]);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> value <span class="hljs-keyword">of</span> iterable) {
    <span class="hljs-built_in">console</span>.log(value);
}
<span class="hljs-comment">// 0</span>
<span class="hljs-comment">// 255</span>
<span class="hljs-keyword">let</span> iterable = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>([[<span class="hljs-string">"a"</span>, <span class="hljs-number">1</span>], [<span class="hljs-string">"b"</span>, <span class="hljs-number">2</span>], [<span class="hljs-string">"c"</span>, <span class="hljs-number">3</span>]]);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> entry <span class="hljs-keyword">of</span> iterable) {
    <span class="hljs-built_in">console</span>.log(entry);
}
<span class="hljs-comment">// [a, 1]</span>
<span class="hljs-comment">// [b, 2]</span>
<span class="hljs-comment">// [c, 3]</span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> [key, value] <span class="hljs-keyword">of</span> iterable) {
    <span class="hljs-built_in">console</span>.log(value);
}
<span class="hljs-comment">// 1</span>
<span class="hljs-comment">// 2</span>
<span class="hljs-comment">// 3</span>
<span class="hljs-keyword">let</span> iterable = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>([<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">3</span>]);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> value <span class="hljs-keyword">of</span> iterable) {
    <span class="hljs-built_in">console</span>.log(value);
}
<span class="hljs-comment">// 1</span>
<span class="hljs-comment">// 2</span>
<span class="hljs-comment">// 3</span>
</code></pre>
<h1 id="heading-iterator">Iterator</h1>
<ul>
<li>Iterator 는 새로운 문법이나 built-in 이 아니라 프로토콜(약속)이다.</li>
<li>간단한 약속만 지키면 누구나 만들수 있다.</li>
<li>Set Map Array 등이 Iterator 객체이다.</li>
<li>Symbol.iterator 를 Key 로 갖는 속성이 반드시 존재해야 한다.</li>
<li>다음 규칙에 따라 next() 메서드를 구현한 객체를 iterator 라고 한다:
```bash
아래의 두 속성을 가지는 객체를 리턴하며 인자가 없는 함수:</li>
<li>done (boolean)<ul>
<li>iterator 가 순회를 모두 마쳤을 경우 true</li>
<li>iterator 가 순회할 다음 value 가 존재할 경우 false</li>
</ul>
</li>
<li>value - iterator 에 의해 리턴될 값. done 이 true 일 경우 생략 가능
```</li>
</ul>
<p>ex:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> iterable = {
  [<span class="hljs-built_in">Symbol</span>.iterator]() {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">i</span>: <span class="hljs-number">0</span>,
      next() {
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.i &lt; <span class="hljs-number">3</span>) {
          <span class="hljs-keyword">return</span> { <span class="hljs-attr">value</span>: <span class="hljs-built_in">this</span>.i++, done: <span class="hljs-literal">false</span> };
        }
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">value</span>: <span class="hljs-literal">undefined</span>, <span class="hljs-attr">done</span>: <span class="hljs-literal">true</span> };
      }
    };
  }
};
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> value <span class="hljs-keyword">of</span> iterable) {
  <span class="hljs-built_in">console</span>.log(value);
}
<span class="hljs-comment">// 0</span>
<span class="hljs-comment">// 1</span>
<span class="hljs-comment">// 2</span>
</code></pre>
<h1 id="heading-rest-parameter-default-parameter">Rest Parameter 와 Default Parameter</h1>
<p>이건 코드로 확인하는게 가장 편하다.</p>
<ol>
<li><p>rest parameter</p>
<pre><code class="lang-javascript"> <span class="hljs-comment">// 사용법은 아래와 같다.</span>
 <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">a, b, ...theArgs</span>) </span>{
    <span class="hljs-comment">// ...</span>
 }
 <span class="hljs-comment">// ex</span>
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">restParam</span>(<span class="hljs-params">...arg</span>) </span>{
     <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> val <span class="hljs-keyword">of</span> arg) <span class="hljs-built_in">console</span>.log(val);
 }
 restParam(<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>);
 <span class="hljs-comment">// es2</span>
 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">restParam2</span>(<span class="hljs-params">first, ...arg</span>) </span>{
     <span class="hljs-built_in">console</span>.log(first); <span class="hljs-comment">// 시작</span>
     <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> val <span class="hljs-keyword">of</span> arg) <span class="hljs-built_in">console</span>.log(val);
     <span class="hljs-comment">// 1</span>
     <span class="hljs-comment">// 2</span>
     <span class="hljs-comment">// 3</span>
     <span class="hljs-comment">// 4</span>
     <span class="hljs-comment">// 5</span>
 }
 restParam(<span class="hljs-string">'시작'</span>, <span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>);
</code></pre>
</li>
<li><p>Default Parameter</p>
<ul>
<li>parameter 값이 없을 시 Default로 값을 넣어준다.<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defaultParam</span>(<span class="hljs-params">a = <span class="hljs-string">'test'</span>, b = <span class="hljs-string">'done'</span></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`a: <span class="hljs-subst">${a}</span>`</span>);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`b: <span class="hljs-subst">${b}</span>`</span>);
}
defaultParam();
<span class="hljs-comment">//</span>
</code></pre>
</li>
</ul>
</li>
</ol>
<h1 id="heading-6riw7yoa">기타</h1>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> obj = {<span class="hljs-attr">a</span>:<span class="hljs-number">1</span>, <span class="hljs-attr">b</span>:<span class="hljs-number">2</span>};
<span class="hljs-keyword">const</span> {a,b} = obj;
<span class="hljs-built_in">console</span>.log(a,b); <span class="hljs-comment">// 12</span>
<span class="hljs-keyword">const</span> a = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>];
<span class="hljs-keyword">const</span> aCopy = [...a];
<span class="hljs-built_in">console</span>.log(aCopy);
</code></pre>
]]></content:encoded></item><item><title><![CDATA[angular2 실무 프로젝트 회고]]></title><description><![CDATA[이것은 내가 실무로 프로젝트 리더이였을 때 느꼈던 회고를 기록으로 남긴다.
대화형 커머스 - 총 6명
Angular2 로 하는 첫 실무  프로젝트이며, 이 프로젝트는 angular2-seed 를 사용하여   진행하였습니다.

생각외로 typescript 의 진입장벽은 높지 않았고 팀원(신입도)들이 잘 받아드리고 편하게 사용했다.
rxJs, redux 에 대해 팀원들을 이해시키기까지 어려웠다.(결국 이해한 팀원만 해당 기술 개발을 하고 남어지 팀...]]></description><link>https://blog.dongjun.win/angular2</link><guid isPermaLink="true">https://blog.dongjun.win/angular2</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Wed, 30 Nov 2016 12:04:00 GMT</pubDate><content:encoded><![CDATA[<p>이것은 내가 실무로 프로젝트 리더이였을 때 느꼈던 회고를 기록으로 남긴다.</p>
<h3 id="heading-6">대화형 커머스 - 총 6명</h3>
<p>Angular2 로 하는 첫 실무  프로젝트이며, 이 프로젝트는 angular2-seed 를 사용하여   진행하였습니다.</p>
<ul>
<li>생각외로 typescript 의 진입장벽은 높지 않았고 팀원(신입도)들이 잘 받아드리고 편하게 사용했다.</li>
<li>rxJs, redux 에 대해 팀원들을 이해시키기까지 어려웠다.(결국 이해한 팀원만 해당 기술 개발을 하고 남어지 팀원은 사용법만 익히게 하였다. 이와 같이 하니 어려움 없이 프로젝트 진행이 가능했다)</li>
<li>Rc4 으로 시작해서 정식  버전까지 마이그레이션을  했었는데 가장 힘들었을 때가   ngModule 나올 때였다.(Rc5)</li>
<li>ngModule 을 잘이해하고  설계를 잘해고 개발하면 아주 좋다.(이해하기 편할려면   angular1 의 모듈을 생각하면 될 듯하다.)</li>
<li>개발하면서 redux 를 사용하였지만 이게 꼭 필요한가에 대해서는    아직도 의문점이다.(있으면 편하긴하다)</li>
<li>JIT 컴파일보다 AOT 컴파일이 확실히 체감을 느낄  정도로 빠르다. 무조건   필수다.</li>
<li>Tpyescript 의 타입과 인터페이스는 정말 개발자가 놓치고 있는 부분을 잡아주며, 오류도 적게 내면서 개발속도 즉 생산성도 높혀준다.(typescript 는 정말 좋다)</li>
<li>Control flow 기반의 코딩이 너무 익숙하다 보니 Data flow 기반의 코딩이 쉽지 않다.(우린 둘다 잘해야된다.)</li>
<li>rxJs 를 잘 알고 이해하면 angular2 의 코드의 질이   좋아진다.</li>
<li>zoneJs 도 공부하면 좀더 품질의 코드가 나오지만 자료가 많지    않다.</li>
</ul>
<h3 id="heading-3">일정표 - 총 3명</h3>
<p>대화형 커머스가 우선순위에서 밀리면서 시작한 새로운 프로젝트 입니다. 일정이 길지 않아서 ionic2 를 검토하고 잘 맞는다면 그것을 통해 진행을 하자고 했습니다. 확실히 ionic2 는 하이브리드 앱에 적합하였지만 모바일웹으로 사용하기에도 무리가 없었습니다. 그에 맞혀  바로 ionic2 를 가지고 요구  사항을 분석하고 설계하여 첫 번째 angular2 프로젝트의 회고를 토대로 개발을 진행하였고 현재도 진행하고 있습니다.</p>
<ul>
<li>Ionic2 에는 라우터가 포함되어 있지 않다.(하지만 angular2 기반이기 때문에 포함 시킬 수 있다.)</li>
<li>Router 를 썼을씨 html5 base url 을 설정해도 #가 붙는다.(ex.  domail.com/#/login)</li>
<li>Angular2-seed 보다 설정이 간편하다.(설치만 하고 따로 셋팅을 하지 않아도 된다.)</li>
<li>Ionic2 serve 는 많이 무겁다(메모리 릭이 잘난다)</li>
<li>지원하는 component 가 많다.</li>
<li>Service worker 를 사용한다.(주석 처리되어 있어서 주석만 풀면된다. 지원하지 않는 브라우저는 그냥 패스된다).</li>
</ul>
<h3 id="heading-6-1">코드리뷰 - 총 6명</h3>
<ul>
<li>서로의 실력 향상에 가장  좋은  방법이다.</li>
<li>리뷰를 진행하는 사람의 역할이 크다.(리뷰를 하는 사람만 말하는 코드리뷰는  효과가    반감된다.)</li>
<li>신입들은 꼭 1 질문씩 하는 것으로 진행하였다.(가만히  있는 것은 알고 있다고  생각하여    물어보았다)</li>
<li>꼭 tsLint 나 jsLint 를 사용하여 코드리뷰 때 컨벤션에 대한 애기가    안나오게  해야된다.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[nodeJs mysql (async/await를 이용한 mySql 모듈 만들기)]]></title><description><![CDATA[nodeJs를 이용하여 mysql 혹은 mariaDB 등 RDB를 사용하는 경우가 많다. es7에 제안된 async/await를 사용하여 mysql 모듈을 만들어 볼까 한다.(모듈이라고 하지만 그저 wrapping 한거다.)
기존 사용 했던 mysql 코드
처음 mysql을 썼었을때 pool을 이용하여, 매번 connection을 맺고 끊어주고, 또 트랜젝션을 맺고 롤백과 commit을 해주는 코드를 썼다.
아마 대부분이 아래와 같을 것이다.:...]]></description><link>https://blog.dongjun.win/typescript-nodejs-mysql</link><guid isPermaLink="true">https://blog.dongjun.win/typescript-nodejs-mysql</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 11 Jul 2016 23:53:11 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/_posts/typescript-nodejs-mysql/node.js-with-mysql.png" alt />
nodeJs를 이용하여 mysql 혹은 mariaDB 등 RDB를 사용하는 경우가 많다. es7에 제안된 async/await를 사용하여 mysql 모듈을 만들어 볼까 한다.(모듈이라고 하지만 그저 wrapping 한거다.)</p>
<h2 id="heading-mysql">기존 사용 했던 mysql 코드</h2>
<p>처음 mysql을 썼었을때 pool을 이용하여, 매번 connection을 맺고 끊어주고, 또 트랜젝션을 맺고 롤백과 commit을 해주는 코드를 썼다.</p>
<p>아마 대부분이 아래와 같을 것이다.:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mysql = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mysql'</span>);
<span class="hljs-keyword">const</span> DBpool  = mysql.createPool({
  <span class="hljs-attr">connectionLimit</span> : <span class="hljs-number">10</span>,
  <span class="hljs-attr">host</span>            : <span class="hljs-string">'example.org'</span>,
  <span class="hljs-attr">user</span>            : <span class="hljs-string">'bob'</span>,
  <span class="hljs-attr">password</span>        : <span class="hljs-string">'secret'</span>,
  <span class="hljs-attr">database</span>        : <span class="hljs-string">'my_db'</span>
});

<span class="hljs-keyword">const</span> get = <span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> {
    DBpool.getConnection(<span class="hljs-function">(<span class="hljs-params">err, con</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
            <span class="hljs-keyword">throw</span> err;
        }
        con.query(<span class="hljs-string">'select * from user where id= ?'</span>, [id], <span class="hljs-function">(<span class="hljs-params">err, data</span>) =&gt;</span> {
            con.release();
            ...
        });
    });
}
</code></pre>
<p>트랜젝션을 사용:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pool은 생략</span>
<span class="hljs-keyword">const</span> insert = <span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> {
    DBpool.getConnection(<span class="hljs-function">(<span class="hljs-params">err, con</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
            <span class="hljs-keyword">throw</span> err;
        }
        con.beginTransaction(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
            <span class="hljs-keyword">if</span> (err) {
                con.release();
                <span class="hljs-keyword">throw</span> err;
            }

            con.query(<span class="hljs-string">'select * from user where id = ?'</span>, [id], <span class="hljs-function">(<span class="hljs-params">err, data</span>) =&gt;</span> {
                <span class="hljs-keyword">if</span>(err) {
                    <span class="hljs-keyword">return</span> con.rollback(<span class="hljs-function">() =&gt;</span> {
                        con.release();
                        <span class="hljs-keyword">throw</span> err;
                    });
                }

                con.query(<span class="hljs-string">'insert into user (name) values (?)'</span>, [data[<span class="hljs-number">0</span>].name], <span class="hljs-function">(<span class="hljs-params">err, data</span>) =&gt;</span> {
                    <span class="hljs-keyword">if</span>(err) {
                        <span class="hljs-keyword">return</span> con.rollback(<span class="hljs-function">() =&gt;</span> {
                            con.release();
                            <span class="hljs-keyword">throw</span> err;
                        });
                    }

                    con.commit(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
                        <span class="hljs-keyword">if</span> (err) {
                            <span class="hljs-keyword">return</span> con.rollback(<span class="hljs-function">() =&gt;</span> {
                                con.release();
                                <span class="hljs-keyword">throw</span> err;
                            });
                        }
                        <span class="hljs-keyword">return</span> con.release();
                    });
                });
                ...
            });
        });
    });
}
</code></pre>
<p>이렇게 되면 매번 db 작업을 할때마다 connection 맺어주고 끊어주는 중복된 코드를 작성해야되며, 트렌젝션을 사용할 때는 콜백헬과 좀더 더 긴 코드를 매번 처리해줘야된다.</p>
<p>필자는 이렇게 하는 것이 너무나도 마음에 안들었고 매번 중복된 코드를 쓰는게 너무너무 귀찮아서 아래와 같이 만들어서 사용했다.</p>
<h2 id="heading-1">1. 시작하기</h2>
<p>async/await를 사용하기 위해서는 <a target="_blank" href="https://babeljs.io/">Babel</a>을 사용하거나 <a target="_blank" href="https://www.typescriptlang.org/">Typescript</a> 같은 것을 사용해야된다. 필자는 Typescript를 사용하기 때문에 Typescript로 진행 하겠다.</p>
<p>기본 설정:</p>
<ol>
<li>NodeJs 설치</li>
<li>Typescript 설치</li>
<li>Typings 설치</li>
</ol>
<blockquote>
<p>자세한 설정은 <a target="_blank" href="https://mayajuni.github.io/2016/06/30/typescript-express_%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0/">Typescript + ExpressJs 시작하기</a>를 참고하여 진행하면 된다.</p>
</blockquote>
<h2 id="heading-2-promise-mysql">2. promise-mysql</h2>
<p>async/await는 전에 <a target="_blank" href="https://mayajuni.github.io/2016/07/04/expressJs-error/">ExpressJs Error</a>에서 설명 했듯이 모든 리턴은 promise로 받아야된다. 그래서 기존 mysql은 callback 기반이기 때문에 사용하지 못하고 npm에 있는 <a target="_blank" href="https://www.npmjs.com/package/promise-mysql">promise-mysql</a> 모듈을 사용한다.</p>
<pre><code class="lang-bash">npm install --save promise-mysql
</code></pre>
<p>promise-mysql모듈은 typings에 없기 때문에 설치를 하지 않고 진행한다.</p>
<h2 id="heading-3-module">3. Module 만들기</h2>
<p>기존에는 모든 함수에 connection 맺고 끊는 혹은 콜백하고 커밋하는 코드를 넣어줬다. 이제 그부분을 분리하여, 모듈로 만들 것이다.</p>
<h4 id="heading-1-connection">1) connection</h4>
<p>내가 생각하는 순서는 다음과 같다.:</p>
<ol>
<li>function을 받는다.</li>
<li>받은 function의 paramter들을  <a target="_blank" href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments">"...args"</a>를 사용하여 args에 담는다.</li>
<li>connection을 맺고 connection 객체를 생성한다.</li>
<li>받은 function을 connection객체와 함께 기존 paramter(args)를 넘겨주어 실행 시킨다.</li>
<li>catch를 통해 error가 있을시 connection을 닫아주고 throw error을 해준다.</li>
<li>error가 없을시에는 connection을 닫아주고 실행된 function을 값을 넘겨준다.</li>
</ol>
<p>위와 같이 생각을 했으면, 아마 아래와 같은 코드가 나올 것이다.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * 기존 import 하는 방식이 아닌 이유는 promise-mysql은
 * 정의 파일(typings)이 없기 때문에 아래와 같이 쓴다.
 */</span>
<span class="hljs-keyword">const</span> promiseMysql = <span class="hljs-built_in">require</span>(<span class="hljs-string">'promise-mysql'</span>);

<span class="hljs-keyword">const</span> pool  = promiseMysql.createPool({
  <span class="hljs-attr">connectionLimit</span> : <span class="hljs-number">10</span>,
  <span class="hljs-attr">host</span>: <span class="hljs-string">'example.org'</span>,
  <span class="hljs-attr">user</span>: <span class="hljs-string">'bob'</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">'secret'</span>,
  <span class="hljs-attr">database</span>: <span class="hljs-string">'my_db'</span>
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> connect = <span class="hljs-function"><span class="hljs-params">fn</span> =&gt;</span> <span class="hljs-keyword">async</span> (...args) =&gt; {
    <span class="hljs-comment">/* DB 커넥션을 한다. */</span>
    <span class="hljs-keyword">let</span> con: any = <span class="hljs-keyword">await</span> pool.getConnection();
    <span class="hljs-comment">/* 로직에 con과 args(넘겨받은 paramter)를 넘겨준다. */</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> fn(con, ...args).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
        <span class="hljs-comment">/* 에러시 con을 닫아준다. */</span>
        con.connection.release();
        <span class="hljs-keyword">throw</span> error;
    });
    <span class="hljs-comment">/* con을 닫아준다. */</span>
    con.connection.release();
    <span class="hljs-keyword">return</span> result;
};
</code></pre>
<h4 id="heading-2">2) 트렌젝션 모듈</h4>
<p>트렌젝션 모듈도 위의 connection모듈과 크게 다르지 않을것이다. 그저 롤백과 커밋이 들어간것이다.</p>
<ol>
<li>function을 받는다.</li>
<li>받은 function의 paramter들을  <a target="_blank" href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments">"...args"</a>를 사용하여 args에 담는다.</li>
<li>connection을 맺고 connection 객체를 생성한다.</li>
<li>트렌젝션을 시작하는 코드를 넣는다.</li>
<li>받은 function을 connection객체와 함께 기존 paramter(args)를 넘겨주어 실행 시킨다.</li>
<li>catch를 통해 error가 있을시 rollback과 connection을 닫아주고 throw error을 해준다.</li>
<li>error가 없을시에는 commit과 connection을 닫아주고 실행된 function을 값을 넘겨준다.</li>
</ol>
<p>위와 같이 생각을 했으면, 아마 아래와 같은 코드가 나올 것이다.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// pool 생략</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> transaction = <span class="hljs-function"><span class="hljs-params">fn</span> =&gt;</span> <span class="hljs-keyword">async</span> (...args) =&gt; {
    <span class="hljs-comment">/* DB 커넥션을 한다. */</span>
    <span class="hljs-keyword">const</span> con: any = <span class="hljs-keyword">await</span> pool.getConnection();
    <span class="hljs-comment">/* 트렌젝션 시작 */</span>
    <span class="hljs-keyword">await</span> con.connection.beginTransaction();
    <span class="hljs-comment">/* 비지니스 로직에 con을 넘겨준다. */</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> fn(con, ...args).catch(<span class="hljs-keyword">async</span> (error) =&gt; {
        <span class="hljs-comment">/* rollback을 진행한다. */</span>
         <span class="hljs-keyword">await</span> con.rollback();
        <span class="hljs-comment">/* 에러시 con을 닫아준다. */</span>
        con.connection.release();
        <span class="hljs-keyword">throw</span> error;
    });
    <span class="hljs-comment">/* commit을 해준다. */</span>
    <span class="hljs-keyword">await</span> con.commit();
    <span class="hljs-comment">/* con을 닫아준다. */</span>
    con.connection.release();
    <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p>위와 같이 만든 모듈을 하나로 합치고 mysql모듈이라고 명칭하면 아래와 같다.
```javascript mysql.ts
/**</p>
<ul>
<li>기존 import 하는 방식이 아닌 이유는 promise-mysql은</li>
<li>정의 파일(typings)이 없기 때문에 아래와 같이 쓴다.
<em>/
const promiseMysql = require('promise-mysql');
import </em> as dotenv from 'dotenv';</li>
</ul>
<p>dotenv.config({
    silent: true,
    path: '.env'
});</p>
<p>const pool = promiseMysql.createPool({
    connectionLimit : 10,
    host: process.env.MYSQL_HOST,
    user: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DB
});</p>
<p>export module mysql {
    export const connect = fn =&gt; async (...args) =&gt; {
        /<em> DB 커넥션을 한다. </em>/
        const con: any = await pool.getConnection();
        /<em> 로직에 con과 args(넘겨받은 paramter)를 넘겨준다. </em>/
        const result = await fn(con, ...args).catch(error =&gt; {
            /<em> 에러시 con을 닫아준다. </em>/
            con.connection.release();
            throw error;
        });
        /<em> con을 닫아준다. </em>/
        con.connection.release();
        return result;
    };</p>
<p>    export const transaction = fn =&gt; async (...args) =&gt; {
        /<em> DB 커넥션을 한다. </em>/
        const con: any = await pool.getConnection();
        /<em> 트렌젝션 시작 </em>/
        await con.connection.beginTransaction();
        /<em> 비지니스 로직에 con을 넘겨준다. </em>/
        const result = await fn(con, ...args).catch(async (error) =&gt; {
            /<em> rollback을 진행한다. </em>/
             await con.rollback();
            /<em> 에러시 con을 닫아준다. </em>/
            con.connection.release();
            throw error;
        });
        /<em> commit을 해준다. </em>/
        await con.commit();
        /<em> con을 닫아준다. </em>/
        con.connection.release();
        return result;
    }
}</p>
<pre><code>이렇게 하면 mysql 모듈이 완성이다.

## <span class="hljs-number">4.</span> 사용법
일반 connection 사용:
<span class="hljs-string">``</span><span class="hljs-string">`javascript
/* 위에 만든 mysql 모듈이다. */
import {mysql} from "mysql"

const get = mysql.connect((con: any, id: string) =&gt; con.query('select * from user', [id]));</span>
</code></pre><p>너무 간단하게 한줄로 끝내버렸다. 물론 단순 select한 값을 리턴했기 때문에 위와 같이 한줄로 나올수 있는 것이다. 만약 다른 비지니스 로직이 있다고 하면 아래와 같다.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/* 위에 만든 mysql 모듈이다. */</span>
<span class="hljs-keyword">import</span> {mysql} <span class="hljs-keyword">from</span> <span class="hljs-string">"mysql"</span>

<span class="hljs-keyword">const</span> get = mysql.connect(<span class="hljs-keyword">async</span> (con: any, <span class="hljs-attr">id</span>: string) =&gt; {
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> con.query(<span class="hljs-string">'select * from user'</span>, [id]);

        <span class="hljs-comment">// ...비지니스로직...</span>

        <span class="hljs-keyword">return</span> result
    });
</code></pre>
<p>굳이 동기로 할 필요 없을시에는 async를 빼도 된다.</p>
<p>트랜젝션을 사용:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/* 위에 만든 mysql 모듈이다. */</span>
<span class="hljs-keyword">import</span> {mysql} <span class="hljs-keyword">from</span> <span class="hljs-string">"mysql"</span>

<span class="hljs-keyword">const</span> insert = mysql.transaction(<span class="hljs-keyword">async</span> (con: any, <span class="hljs-attr">id</span>: string) =&gt; {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> con.query(<span class="hljs-string">'select * from user where id = ?'</span>, [id]);
    <span class="hljs-keyword">await</span> con.query(<span class="hljs-string">'insert into user (name) values (?)'</span>, [user[<span class="hljs-number">0</span>].name]);
    <span class="hljs-comment">/* 리턴할 값이 없을시 그냥 return만 써도 된다. */</span>
    <span class="hljs-keyword">return</span> user;
});
</code></pre>
<p>트랜젝션을 사용하는 코드는 더욱더 짧아진 코드량을 볼 수 있다.</p>
<blockquote>
<p>이 모듈에 대한 예제를 <a target="_blank" href="https://github.com/mayajuni/async-await-mysql">github</a>에 올렸다. 한번 보면 좀더 이해하기 편할 것이다. 도움이 되었다면 위의 별도 한번 눌러 주는 센스!</p>
</blockquote>
<p><code>반말로 블로그를 작성하였는데 이해해주시기 바랍니다. 문의 및 수정 사항은 댓글이나 mayajuni10@gmail.com으로 이메일 보내주시기 바랍니다.</code></p>
]]></content:encoded></item><item><title><![CDATA[ExpressJs Error]]></title><description><![CDATA[저번 포스트에서는 서버 구동에 대해서 포스팅했다. 이번에는 Error 처리에 대해 포스팅 하겠다.
기본적인 에러처리
app.get('/', (req, res) => {
    throw new Error('에러 발생')
})
app.use((err, req, res, next) => {
    console.log(err.message)
})

위와 같이 처리를 하면 큰 문제가 생긴다. 아래와 같이 callback을 받아 error 처리를 할 ...]]></description><link>https://blog.dongjun.win/expressjs-error</link><guid isPermaLink="true">https://blog.dongjun.win/expressjs-error</guid><category><![CDATA[Express.js]]></category><category><![CDATA[router]]></category><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Mon, 04 Jul 2016 00:25:35 GMT</pubDate><content:encoded><![CDATA[<p>저번 포스트에서는 서버 구동에 대해서 포스팅했다. 이번에는 Error 처리에 대해 포스팅 하겠다.</p>
<h2 id="heading-6riw67o47kcb7j24ioyxkoufroyymoumra">기본적인 에러처리</h2>
<pre><code class="lang-bash">app.get(<span class="hljs-string">'/'</span>, (req, res) =&gt; {
    throw new Error(<span class="hljs-string">'에러 발생'</span>)
})
app.use((err, req, res, next) =&gt; {
    console.log(err.message)
})
</code></pre>
<p>위와 같이 처리를 하면 큰 문제가 생긴다. 아래와 같이 callback을 받아 error 처리를 할 시 절대로 에러를 잡지 못한다.</p>
<pre><code class="lang-bash">app.get(<span class="hljs-string">'/'</span>, (req, res) =&gt; {
    callback(error =&gt; {
        throw new Error(<span class="hljs-string">'에러 발생'</span>)
    });
})

app.use((err, req, res, next) =&gt; {
    console.log(err.message)
})
</code></pre>
<p>그래서 하는 방식이 next를 통한 에러를 전달하는 방식이다.</p>
<pre><code class="lang-bash">app.get(<span class="hljs-string">'/'</span>, (req, res, next) =&gt; {
    callback(error =&gt; {
        <span class="hljs-keyword">if</span>(error) <span class="hljs-built_in">return</span> next(error);

        callback2(error =&gt; {
            <span class="hljs-keyword">if</span>(error) <span class="hljs-built_in">return</span> next(error);
        });
    });
})
app.use((err, req, res, next) =&gt; {
    console.log(err.message)
})
</code></pre>
<p>이렇게 처리시 2가지의 문제점을 가지고 있다.</p>
<ul>
<li>로직 및 모든 부분에 Error 처리를 해줘야 한다.
 (이거 의외로 되게 유지보수하기 힘들고, 귀찮은 작업이다. 깜빡 한번하면 그냥 죽어버린다.)</li>
<li>내가 Error 처리를 하지 못하는 부분에서는 Error처리를 할 수 없다.(모듈 안에서 에러가 났던가, 기타 등등)</li>
</ul>
<blockquote>
<p>위와 같은 문제 때문에 필자는 <a target="_blank" href="https://github.com/brianc/node-domain-middleware">node-domain-middleware</a> 미들웨어를 사용하여 에러처리를 했다.(편하고 좋았다.) 하지만 <a target="_blank" href="http://expressjs.com/ko/advanced/best-practice-performance.html#section-8">expressJs의 성능우수 사례</a>를 보면 도메인 사용을 권장하지 않고 더이상 사용되지 않는 모듈이라고 되어 있다.</p>
</blockquote>
<h2 id="heading-7zse66gc6647iqk66w8ioydtoyaqe2vncdsl5drn6wg7lky66as">프로미스를 이용한 에러 처리</h2>
<p>es2015에 있는 <a target="_blank" href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>를 이용하여 Error 처리 하는 방식이다.</p>
<pre><code class="lang-bash">app.get(<span class="hljs-string">'/'</span>, (req, res, next) =&gt; {
    callback()
        .<span class="hljs-keyword">then</span>(_ =&gt; {
            // 로직1
        })
        .<span class="hljs-keyword">then</span>(_ =&gt; {
            // 로직2
        })
        .catch(next);
})
app.use((err, req, res, next) =&gt; {
    console.log(err.message)
})
</code></pre>
<p>위와 같이 하면 catch를 통해 promise로 처리하는 부분의 모든 error를 처리 할 수 있다. 위에 단점으로 적었던 2가지 전부를 해결 할 수 있다. 나의 생각 일 수 있지만 코드도 좀더 간결해 보인다(아닐수도 있다.)
하지만 여기서 코드를 좀더 간결하게 해보도록 하겠다. es7 스팩인 async/await를 이용할 것이다.</p>
<h3 id="heading-asyncawait">async/await 이용한 에러 처리</h3>
<p> async/await가 다소 생소 할 수 있다. 비동기 코드를 동기화 간편하게 해주는 것이다. es7에 제안된 스팩이며, 자세한 내용은 <a target="_blank" href="https://blogs.msdn.microsoft.com/typescript/2015/11/03/what-about-asyncawait/">이곳</a>을 보자. 구글 검색해도 많이 나온다.</p>
<blockquote>
<p>async/await 사용하기 위해서는 <a target="_blank" href="https://babeljs.io/docs/usage/cli/">Babel</a> 혹은 <a target="_blank" href="https://www.typescriptlang.org/">typescript</a> 등을 사용해야된다. 필자는 typescript를 사용한다.</p>
</blockquote>
<ol>
<li><p>처음에는 우선 아래와 같이 (req, res, next) 부분을 감싸도록 하겠다.</p>
<pre><code class="lang-bash">const wrap = fn =&gt; (...args) =&gt; fn(...args).catch(args[2]);
</code></pre>
<p><a target="_blank" href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments">"...args"</a>는 es2015 문법이다. 이렇게 사용하면 args안에 parameter 값들이 순차적으로 들어가게된다.
wrap은 함수를 받아서 그 함수를 실행 하고 catch를 통해 error가 발생시 next(error)을 해주는 역활이다. router에서는 기본적으로 paramter를 req, res, next를 주기 때문에 args[2]는 next 이다.</p>
<p>이걸 풀어서 아래와 같이 할 수 있다.</p>
<pre><code class="lang-bash">const wrap = fn =&gt; (req, res, next) =&gt; fn(req, res, next).catch(next);
</code></pre>
</li>
<li>만든 wrap을 아래와 같이 한다.<pre><code class="lang-bash">app.get(<span class="hljs-string">'/'</span>, wrap(async (req, res, next) =&gt; {
<span class="hljs-built_in">let</span> data = await callback();
<span class="hljs-built_in">let</span> data2 = await 로직1();
/* 리턴 값이 없음 아래와 같이 써도 된다. */
await 로직2();
}))
app.use((err, req, res, next) =&gt; {
console.log(err.message)
})
</code></pre>
코드를 보면 짐작하시겠지만 async/await를 통해 비동기 로직을 동기식으로 간편하게 로직처리 한다.</li>
</ol>
<blockquote>
<p>여기에도 유의점이 있다. 이것을 사용하기 위해서는 return promise 이어야된다. 단순 callback에 대한 처리를 할 수 없다. 하지만 많은 모듈 혹은 미들웨어가 promise를 제공(?)하기 때문에 사용하기에는 불편함이 없다.(기존 로직은 async/await로 처리하면되며, mongoose나 mysql 같은경우 이미  promise를 사용 할 수 있어 큰 불편이 없다.)</p>
</blockquote>
<h2 id="heading-6rkw66gg">결론</h2>
<p>직접 이렇게 하고 사용을 하면 생산성이 확실히 빨라진다. 코드도 짧아지고 가독성도 좋아 진다. 이렇게 한번 쓰기 시작하면서 모든 노드 프로젝트는 이 방식으로 개발하고 있다. 필자는 이 방식(방법)을 추천한다.</p>
<h5 id="heading-7lc46rog">참고</h5>
<ul>
<li><a target="_blank" href="https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/">Asynchronous Error Handling in Express with Promises, Generators and ES7</a></li>
<li><a target="_blank" href="http://expressjs.com/ko/advanced/best-practice-performance.html">프로덕션 우수 사례: 성능 및 신뢰성</a></li>
</ul>
<p><code>반말로 블로그를 작성하였는데 이해해주시기 바랍니다. 문의 및 수정 사항은 댓글이나 mayajuni10@gmail.com으로 이메일 보내주시기 바랍니다.</code></p>
]]></content:encoded></item><item><title><![CDATA[Typescript + ExpressJs 시작하기]]></title><description><![CDATA[최근 Angular2.0 을 스터디 하면 Typescript를 알게 되었다. 사용하면서 모든 javascript에 적용을 시키면 정말 편할꺼 같아서 개인적으로 Restful Api 서버를 만들어 보고, 그걸을 토대로 기록을 남긴다.
시작하기 및 설정
시작하기 전에 먼저 설치를 해야된다.

NodeJs 6버젼 이상을 추천한다.(es2015지원이 빵빵하다!)
]]></description><link>https://blog.dongjun.win/typescript-express</link><guid isPermaLink="true">https://blog.dongjun.win/typescript-express</guid><dc:creator><![CDATA[권동준]]></dc:creator><pubDate>Thu, 30 Jun 2016 00:07:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/_posts/typescript-express/typescript-express-nodejs.jpg" alt="" /></p>
<p>최근 <a href="https://angular.io/">Angular2.0</a> 을 스터디 하면 <a href="http://www.typescriptlang.org/">Typescript</a>를 알게 되었다. 사용하면서 모든 javascript에 적용을 시키면 정말 편할꺼 같아서 개인적으로 Restful Api 서버를 만들어 보고, 그걸을 토대로 기록을 남긴다.</p>
<h1>시작하기 및 설정</h1>
<p>시작하기 전에 먼저 설치를 해야된다.</p>
<ul>
<li><a href="https://nodejs.org/en/">NodeJs</a> 6버젼 이상을 추천한다.(es2015지원이 빵빵하다!)</li>
<li><a href="http://www.typescriptlang.org/">Typescript</a></li>
</ul>
<p>NodeJs는 해당 홈페이지 들어가서 다운로드를 받고 설치하면 문제 없지 진행 할 수 있다.
Typescript 설치는 터미널을 열고 아래와 같이 npm으로 간편하게 설치가 가능하다.</p>
<pre><code class="language-bash">npm install -g typescript
</code></pre>
<h2>1. 프로젝트 설정</h2>
<pre><code class="language-bash">mkdir myapp
cd myapp
npm init
</code></pre>
<p>npm init을 했을시 package.json을 생성시켜주지만 직접 파일로 만들어도 된다.</p>
<blockquote>
<p>package.json
 : 필요한 노드 모듈을 정의하고 프로젝트 설명이 기록되어 있다. 또한 npm 실행 script도 사용할수 있다.</p>
</blockquote>
<pre><code class="language-javascript">{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1"
  },
  "author": "",
  "license": "ISC"
}
</code></pre>
<p>초기 셋팅을 하면 위와 같이 된다.</p>
<h2>2. expressJs 설치</h2>
<pre><code class="language-bash">npm install --save express
</code></pre>
<p>위와 같이 express 설치를 하면 package.json에 아래와 같이 추가가 된다.</p>
<pre><code class="language-bash">"dependencies": {
    "express": "^4.14.0"
  }
</code></pre>
<h2>3. typings</h2>
<blockquote>
<p>typings
 : 타입스크립트에서 사용되는 모듈 혹은 라이브러리 등등의 정의가 있는 파일이다.(쉽게 말해 자동완성 기능을 해준다.) 기능과 사용법 자세한 설명은 <a href="https://github.com/typings/typings">Typings</a>에서 보자</p>
</blockquote>
<p>설치와 설정은 아래와 같다.</p>
<pre><code class="language-bash">npm install -g typings
typings init
</code></pre>
<p>typings init을 하면 typings.json이 생성된다. 여기에 우리가 설치한 Definition File들이 기록된다.</p>
<blockquote>
<p>NodeJs를 통해 사용되는 모듈뿐만 아니라 그 이외의 수많은 Definition이 있기 때문에 검색 후 설치하는 것을 권장한다. typings search [모듈이름] 으로 찾을 수 있으며, typings install로 설치가 가능하다.</p>
</blockquote>
<p>typings.json이 만들어졌으면, 우리가 사용한 모듈이랑 노드에 대해 설치를 하자.</p>
<pre><code class="language-bash">typings install env~node --save --global
typings install dt~express --save --global
</code></pre>
<p>위의 문법은 <a href="https://github.com/typings/typings">Typings</a>에 가면 설명나와 있다.</p>
<blockquote>
<p>아마 위에 2개만 설치하고 타입스크립트 컴파일을 하면 에러가 떨어질 것이다. 이유는 express 정의 파일안에 serve-static, express-serve-static-core 파일을 import 하는 부분이 있다. 또 serve-static 안에 mime라는 정의를 임포트 하기 때문에 같이 설치한다.</p>
</blockquote>
<pre><code class="language-bash">typings install dt~serve-static --save --global
typings install dt~express-serve-static-core --save --global
typings install dt~mime --save --global
</code></pre>
<p>설치가 완료 되면 typings.json을 보면 아래와 같이 되어 있다.</p>
<pre><code>{
  "name": "myapp",
  "dependencies": {},
  "globalDependencies": {
    "express": "registry:dt/express#4.0.0+20160317120654",
    "express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160625155614",
    "node": "registry:env/node#6.0.0+20160622202520",
    "mime": "registry:dt/mime#0.0.0+20160316155526",
    "serve-static": "registry:dt/serve-static#0.0.0+20160606155157"
  }
}
</code></pre>
<p>먼저 파일을 만들고 위와 같이 작성후 <code>typings install</code>로 한꺼번에 설치가 가능하다.</p>
<blockquote>
<p>모든 모듈, 라이브러리 등등에 정의 파일이 존재하지 않는다. 그래서 정의 파일을 사용하지 않아도 오류 없이 사용이 가능하다.<code>const redisStore = require("connect-redis");</code> 이와 같이 선언하면 정의 파일 없이도 에러 없이 사용 가능하다.</p>
</blockquote>
<h2>4. typescript 설정</h2>
<p>typescript를 사용하면 tsconfig.json라는 파일을 만들어서 설정을 진행 할 수 있다. 자세한 설명은 <a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html">공식홈페이지</a>에서 확인 할 수 있다.</p>
<pre><code class="language-bash">{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "typings",
    "node_modules"
  ]
}
</code></pre>
<p>위의 내용으로 파일을 만든다.</p>
<p>이제 거의 완성이 되었다. 이렇게 되면 아래와 같은 구조가 된다.(하위 폴더는 생략)</p>
<pre><code class="language-text">├── node_modules
├── typings
├── package.json
├── typings.json
└── tsconfig.json
</code></pre>
<h1>코딩 시작</h1>
<p>여기까지 오셨으면 설치 및 설정까지 완료된 것이다. 이제부턴 코드를 작성하겠다.
es2015를 기반으로 사용할것이며, 기본적으로 es2015를 공부하면 좀더 좋다. 물론 es5로 코딩도 가능하다.
post, get, delete, put 메소드를 사용하여 {result : Hello world}를 리턴을 목표로 한다</p>
<h2>1. 테스트 코드 만들기</h2>
<p>우리가 만든 예제가 잘 돌아가는지 테스트를 하기 위해 mocha를 이용하여 테스트 코드를 만든다. 테스트 코드에 대해서는 설명을 하진 않겠다.</p>
<pre><code class="language-bash">npm install -g mocha
npm install --save-dev should
npm install --save-dev supertest

typings install dt~mocha --save --global
typings install dt~should --save --global
</code></pre>
<p>위에 것들을 다 설치하면 바로 test 폴더를 만들고 그안에 app.spec.ts 파일을 만든다.</p>
<pre><code class="language-javascript">const request = require('supertest');
require('should');

const server: any = request.agent('http://localhost:3000');

describe('테스트 시작', () =&gt; {
    it('GET', done =&gt; server.get('/').expect(200).expect("Content-type",/json/)
        .end((err, res) =&gt; {
            if(err) throw err;
            res.body.should.be.a.Object();
            res.body.should.have.property('result');
            res.body.result.should.equal('Hello World');
            done();
        }));
    it('POST', done =&gt; server.post('/').expect(200).expect("Content-type",/json/)
        .end((err, res) =&gt; {
            if(err) throw err;
            res.body.should.be.a.Object();
            res.body.should.have.property('result');
            res.body.result.should.equal('Hello World');
            done();
        }));
    it('DELETE', done =&gt; server.delete('/').expect(200).expect("Content-type",/json/)
        .end((err, res) =&gt; {
            if(err) throw err;
            res.body.should.be.a.Object();
            res.body.should.have.property('result');
            res.body.result.should.equal('Hello World');
            done();
        }));
    it('PUT', done =&gt; server.put('/').expect(200).expect("Content-type",/json/)
        .end((err, res) =&gt; {
            if(err) throw err;
            res.body.should.be.a.Object();
            res.body.should.have.property('result');
            res.body.result.should.equal('Hello World');
            done();
        }));
});
</code></pre>
<p>이제 테스트 코드도 만들었겠다. 슬슬 본격적인 코딩에 들어가겠다.</p>
<h2>2. app.ts</h2>
<p>서버에 대한 설정을 하는 역활을 한다. 이 글에서는 간단하게 router랑 기본 설정말 할것이며, 이후 logging, db(mongo,mysql etc)설정, session(redis, cookie)등의 설정은 다루지 않겠다.</p>
<p>완성까지는 아니지만 express + typescript + mongodb 를 활용하여 만든 <a href="https://github.com/mayajuni/blog/tree/master/server-node">github</a>를보면 알 수 있다. 그안에 logging부터 restapi 테스트까지 전부 있다.</p>
<p>코딩하는 방법은 여러가지가 있겠지만 es2015의 <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes">Class</a>를 사용하겠다.</p>
<pre><code class="language-javascript">import * as express from "express";

export class Server {
    /* app에 대한 타입 설정 */
    public app: express.Application;

    constructor() {
        /* express 설정을 위한 express 선언 */
        this.app = express();
        /* 라우터 */
        this.router();

        /* Not Foud */
        this.app.use((req: express.Request, res: express.Response, next: Function) =&gt; {
            /**
             *  Error이라는 정의가 있지만 Error에는 status라는 정의가 없어서 any 설정
             *  (아마 typescript로 개발하다보면 any를 많이 쓰게된다)
             */
            const err: any = new Error('not_found');
            err.status = 404;
            next(err);
        });

        /* 에러 처리 */
        this.app.use((err: any, req: express.Request, res: express.Response) =&gt; {
            err.status  = err.status || 500;
            console.error(`error on requst \({req.method} | \){req.url} | ${err.status}`);
            console.error(err.stack || `${err.message}`);

            err.message = err.status  == 500 ? 'Something bad happened.' : err.message;
            res.status(err.status).send(err.message);
        });
    }

    private router() {
        /**
         * 에러 처리를 좀더 쉽게 하기 위해서 한번 감싸준다.
         * es7에 제안된 async await를 사용하여 에러처리시 catch가 되기 편하게 해준 방식이다.
         * http://expressjs.com/ko/advanced/best-practice-performance.html#section-10 을 참고하면 좋다.
         */
        const wrap = fn =&gt; (req, res, next) =&gt; fn(req, res, next).catch(next);
        //get router
        const router: express.Router = express.Router();

        //get
        router.get("/", wrap(async (req, res) =&gt; {
            res.status(200).json({result: "Hello World"})
        }));

        //post
        router.post("/", wrap(async (req, res) =&gt; {
            res.status(200).json({result: "Hello World"})
        }));

        //put
        router.put("/",  wrap(async (req, res) =&gt; {
            res.status(200).json({result: "Hello World"})
        }));

        //delete
        router.delete("/",  wrap(async (req, res) =&gt; {
            res.status(200).json({result: "Hello World"})
        }));

        this.app.use(router);
    }
}
</code></pre>
<p>위의 라우터 부분은 추후 한번 더 블로깅 하겠다. 자세하게 보고 싶으면 <a href="http://expressjs.com/ko/advanced/best-practice-performance.html#section-10">expressJs 성능 우수 사례의 올바른 예외처리(프로미스 사용)</a>를 참고하면 된다.</p>
<h2>3. server.ts</h2>
<p>app.ts에 설정된 내용을 가지고 서버를 만들고 스타트 하는 역활을 한다. 물론 app.ts에서 해도 되지만 확정성을 고려하여 따로 분리한다.</p>
<pre><code class="language-javascript">import {Server} from './app';
import * as express from "express";

/* 따로 설정하지 않았으면 3000 port를 사용한다. */
const port: number = process.env.PORT || 3000;
const app: express.Application = new Server().app;
app.set('port', port);

app.listen(app.get('port'), () =&gt; {
   console.log('Express server listening on port ' + port);
}).on('error', err =&gt; {
   console.error(err);
});
</code></pre>
<h2>4. server run</h2>
<p>타입 스크립트는 한번 컴파일을 하지 않으면 js 파일이 생성되지 않는다 그렇게 때문에 꼭 컴파일을 해야된다.</p>
<pre><code class="language-bash">tsc --p tsconfig.json
</code></pre>
<p>이렇게 하면 ts파일 이외의 js 파일과 js.map 파일이 생성된다.</p>
<blockquote>
<p>ts 파일이 위치한 곳에 생성되기 때문에 안좋아 보일수 있다. gulp나 grunt를 사용하면 해결 할 수 있다.</p>
</blockquote>
<pre><code class="language-bash">node server
</code></pre>
<p>위와 같이 하면 서버가 구동된다.
<img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/_posts/typescript-express/%EC%84%9C%EB%B2%84%EA%B5%AC%EB%8F%99.PNG" alt="" /></p>
<p>구동 까지 완료 되었으면, 처음에 만든 테스트를 실행 하여, 제대로 되는지 확인한다.</p>
<pre><code class="language-bash">mocha
</code></pre>
<p>mocha만 치면 프로젝트의 test폴더 안에 있는 모든 테스트 파일을 구동한다.
이제 결과는 아래와 같다.
<img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/_posts/typescript-express/%ED%85%8C%EC%8A%A4%ED%8A%B8%EA%B2%B0%EA%B3%BC.PNG" alt="" /></p>
<p>물론 웹으로 요청 한 것도 볼 수 있다.
<img src="https://raw.githubusercontent.com/mayajuni/hexo-blog/master/source/_posts/typescript-express/%EC%9B%B9%EA%B2%B0%EA%B3%BC.PNG" alt="" /></p>
<p>마지막으로 package.json에 script 추가한다.</p>
<pre><code class="language-bash">{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "npm run tsc &amp; mocha",
    "start": "npm run tsc &amp;&amp; node server",
    "tsc": "tsc --p tsconfig.json"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.14.0"
  },
  "devDependencies": {
    "mocha": "^2.5.3",
    "should": "^9.0.2",
    "supertest": "^1.2.0"
  }
}
</code></pre>
<p>아주 기본적인 구동 및 테스트만 했다.
언제든 궁금한 사항이나 버그, 오류가 있을 시 <a href="mailto:mayajuni10@gmail.com">mayajuni10@gmail.com</a>으로 이메일 주시거나 혹은 아래의 댓글로 남겨주시면 수정 및 최대한 아는 범위에서 답변 하겠다.</p>
<p>테스트로 만든 예제 또한 <a href="https://github.com/mayajuni/myapp">github</a>에 공개되어 있어 볼수 있다.</p>
<p><code>반말로 블로그를 작성하였는데 이해해주시기 바랍니다.</code></p>
]]></content:encoded></item></channel></rss>