<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ted_0527.log</title>
        <link>https://velog.io/</link>
        <description>자기계발 중인 신입 개발자</description>
        <lastBuildDate>Tue, 14 Jun 2022 02:31:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ted_0527.log</title>
            <url>https://velog.velcdn.com/images/_ted_0527_/profile/40d60d08-7121-4f09-a265-d6e7bb3fc588/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ted_0527.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_ted_0527_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[ TIL ] 데이터 엔지니어링 (3)]]></title>
            <link>https://velog.io/@_ted_0527_/TIL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4-3</link>
            <guid>https://velog.io/@_ted_0527_/TIL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4-3</guid>
            <pubDate>Tue, 14 Jun 2022 02:31:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사내 M/L 개발자 &#39;고강빈&#39;님의 강의자료 입니다.</p>
</blockquote>
<h1 id="람다-아키텍처-배치-프로세싱">람다 아키텍처: 배치 프로세싱</h1>
<h2 id="1-데이터-용어-해설">1. 데이터 용어 해설</h2>
<blockquote>
<p><strong>Data Lakes and hybrid Data Warehouses are certainly a wonderful tool to make the company data-driven and to bring it forward. 
However, such a Data Lake must be managed and maintained, otherwise it degenerates into a Data Swamp. This often leads to the fact that information is wrong and users do not use it at all, then Data Lakes do not create any advantages but only produce costs.</strong></p>
</blockquote>
<p><strong><em>- What is Data Swamp? -</em></strong></p>
<blockquote>
</blockquote>
<hr>
<h3 id="data-warehouse">Data Warehouse</h3>
<ul>
<li><p>대표적인 데이터 저장소</p>
<p>  → 대량의 데이터를 장기 보관 (보통 3개월~1년)</p>
</li>
<li><p>다양한 source의 데이터를 <strong>분석 가능하고 구조화된 형식</strong>으로 저장
→ 구조화된 정형 데이터를 담는 repository
→ Data-driven 의사결정을 도움 (For Analytics and Reporting)
→ 다양한 source에서 필요한 데이터를 그대로 가져오기도 하지만, 보통 ETL 과정 거침</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/9993ac60-8aa8-4d87-9881-49c6c9b1a1ca/image.png" alt=""></p>
<ul>
<li>기본적인 RDB와 달리 주로 columnar (column-oriented) 형태로 관리됨<ul>
<li>주제 지향적 (subjectoriented)</li>
<li>통합적 (integrated)</li>
<li>시계열적 (timevarient)</li>
<li>비휘발적 (nonvolatile)</li>
</ul>
</li>
</ul>
<hr>
<h3 id="data-mart">Data Mart</h3>
<ul>
<li>특정 분야의 데이터를 정제, 집계해서 따로 담고 있는 데이터 저장소<ul>
<li>팀, 부서별로 구축 및 관리</li>
<li>데이터 웨어하우스보다 최종 사용자에 근접한 데이터스토어</li>
<li>최종 사용자가 필요로 하는 속성을 갖고 있는 작은 데이터 집합</li>
<li>비교적 소량의 데이터를 가지며 전체 컬럼에 대한 조회가 잦음
→ RDB 형태를 주로 채택</li>
</ul>
</li>
<li>소수의 소스로부터 or 데이터 웨어하우스로부터 ETL 프로세싱하여 구성
<img src="https://velog.velcdn.com/images/_ted_0527_/post/3adce552-eedc-4f3d-9f7d-40bd34489ddf/image.png" alt=""></li>
</ul>
<ul>
<li><p>용도에 따라 나누어놓고 <a href="https://www.notion.so/d8b77b9d4e284fb08c30313c63315db4">OLAP</a> 작업 통해 BI(Business Intelligence) 실현</p>
<p>  → 시각화 등 BI 툴을 추가하여 사용하기도 한다.</p>
</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/03ad73ea-384d-443c-b09b-074109f9640d/image.png" alt=""></p>
<hr>
<h3 id="data-lake">Data Lake</h3>
<blockquote>
<p><strong>“일단 저장하고 필요할 때 꺼내쓴다!”</strong></p>
</blockquote>
<ul>
<li>정형 / 비정형 데이터 모두를 저장하고 관리하는 저장소</li>
<li>최근 클라우드 및 대용량 분산처리의 등장과 함께 떠오른 기술
→ 정의된 목적이 없는, 정형화나 정규화를 하지 않고 원시 데이터를 그대로 저장</li>
<li>DB보다는 대용량 분산 스토리지 (S3, HDFS, …)에 가까운 형태</li>
<li>not table / no schema / schema on read ⇒ 읽어들일 때 스키마를 결정</li>
<li><strong>ELT</strong>의 중간 저장소(Load) 역할</li>
</ul>
<p><a href="https://www.notion.so/d8b77b9d4e284fb08c30313c63315db4">잘 관리되지 않은 데이터 레이크는 데이터 늪(Data Swamp)라고도 부른다.</a></p>
<hr>
<h3 id="olap">OLAP</h3>
<ul>
<li>OnLine Analysis Processing ↔ OLTP</li>
<li>OnLine Transaction Processing</li>
<li>사용자가 대화형 쿼리 통해 다차원 데이터 분석을 하고 이를 의사결정에 참고하는 과정 또는 그 과정에 사용되는 DB 엔진</li>
<li>주로 대량 <strong>읽기</strong> 워크로드 담당</li>
<li>분석을 위해 만들어진 다수의 다차원 데이터(OLAP cube)를 aggregate &amp; query</li>
<li>Columnar</li>
</ul>
<hr>
<h3 id="row-oriented-vs-column-oriented">Row-Oriented v.s. Column-Oriented</h3>
<ul>
<li><p><strong>Row-oriented == 일반적인 RDB(관계형 데이터베이스)</strong>
→ OLTP</p>
</li>
<li><p>행(row)별로 데이터를 관리하고 조회</p>
<ul>
<li>특정 row 검색, 삭제, 업데이트, 추가 효율적</li>
<li>트랜잭션 처리</li>
<li>적은 양의 데이터를 자주 읽고 쓴다.</li>
</ul>
</li>
<li><p><strong>Column-oriented(Columnar) : 열별로 데이터를 저장 ⇒ 데이터 엔지니어링 세계에선 대부분</strong>
→ OLAP</p>
</li>
<li><p>열(column)별로 데이터를 저장하고 관리</p>
<ul>
<li>Delete/Update가 비효율적이거나 불가능 (Only by Batch Processing)</li>
<li>같은 형식의 데이터를 관리하기 때문에 압축이나 보관이 우세</li>
<li>특정 칼럼의 데이터만 가져오거나 대량의 데이터에 대한 쿼리, 같은 자료형의 데이터가 모여있다</li>
<li>많은 양의 데이터를 종종 읽고 쓴다.
<img src="https://velog.velcdn.com/images/_ted_0527_/post/d29a5f99-8556-466b-8357-1133a22b5cd9/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-batch-processing">2. Batch Processing</h2>
<blockquote>
<p><strong>“Just because you have a hammer doesn’t mean that’s the right tool for every job.”</strong></p>
</blockquote>
<p><strong><em>- Mark Balkenende, Director of Technical Product Marketing -</em></strong></p>
<blockquote>
</blockquote>
<hr>
<h3 id="복습-batch-processing">복습) Batch Processing</h3>
<ul>
<li>일괄 처리</li>
<li>데이터를 일정 기간동안 수집하고 나서 하나의 슬롯으로 처리하는 것</li>
<li>Bounded data processing</li>
<li>For large volumes of data 연산량이 많은 CPU 집약적인 작업 (ex: ML/DL)</li>
<li>중요도 : latency &lt; throughput</li>
</ul>
<hr>
<h3 id="배치-처리의-pain-point">배치 처리의 Pain point</h3>
<ul>
<li><p>불규칙 데이터 사이즈
→ 배치 처리의 컴퓨팅 리소스와 배치 작업 주기를 산정하기 어려움
→ 안정화가 되어 있는 경우는 뭐 괜찮지만.. 스타트업은 크리티컬</p>
</li>
<li><p>연산/처리 속도와 배치 주기
→ 컴퓨팅 리소스 산정 
→ 비즈니스 및 DS의 요구 사항 고려</p>
<blockquote>
<p><strong>”한 시간마다 데이터 주세요”</strong></p>
</blockquote>
</li>
<li><p>재처리 방식
→ 재처리용 리소스를 따로 할당 or 기존 리소스를 나눠서 사용</p>
</li>
<li><p>태스크 스케쥴링(워크플로 스케쥴링)
→ 배치 작업을 정해진 시간에 실행하고 그 상태 / 결과를 모니터링 할 수 있어야 함
⇒ A가 끝나면 B를 수행한다. A가 실패할 경우 C를 실행한다
⇒ 뭔가 문제가 생기면 즉각 대응이 가능해야한다.</p>
</li>
</ul>
<hr>
<h3 id="빅데이터의-왕-hadoop">빅데이터의 왕: Hadoop</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/e5bb6565-39e5-4361-99a0-712615bded9c/image.png" alt=""></p>
<ul>
<li>대표적인 빅데이터 프레임워크
→ 주로 배치 처리 담당</li>
<li>HDFS, MapReduce, HBase, Spark, Presto, YARN 등 Hadoop Ecosystem<ul>
<li>너무 방대한 양과 다양한 에코시스템….🥲</li>
</ul>
</li>
<li>대체 가능<ul>
<li>Data Lake (HDFS) → AWS S3</li>
<li>Hive → Amazon Athena</li>
<li>Hadoop → AWS EMR</li>
</ul>
</li>
</ul>
<hr>
<h2 id="3-etl과-elt">3. ETL과 ELT</h2>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/60446f43-4d1a-481e-a792-e4a972ff7057/image.png" alt=""></p>
<hr>
<h3 id="etl">ETL</h3>
<ul>
<li><strong>Extract-Transform-Load</strong>
→ 비즈니스 데이터를 활용 가능한 형태로 추출(E), 변환(T), 저장(L)하여 이후 활용할 수 있도록 하는 작업, 또는 그러한 데이터 파이프라인
<img src="https://velog.velcdn.com/images/_ted_0527_/post/515001de-ab58-4e5f-b74f-e79e0f1260fd/image.png" alt=""></li>
</ul>
<ul>
<li>주로 OLTP(OnLine Transaction Processing) 
→ DB를 source로 하는 ETL 파이프라인을 만들고 Data warehouse와 Data mart로 저장</li>
<li>Transform (= filter, sort, aggregate, join, deduplicate, validate, missing value, etc)</li>
<li>주로 일괄 처리 방식, 주로 정형 데이터</li>
</ul>
<hr>
<h3 id="etl의-단점">ETL의 단점</h3>
<ul>
<li>추출해야 하는 데이터 스토어가 백엔드나 데이터 엔지니어링의 영역
→ 데이터 사이언티스트나 BI 담당자는 원하는 데이터를 즉각적으로 얻기가 어려움 (민첩성 결여)</li>
<li>비정제, 비정형 데이터를 담아서 활용할 저장소의 부재</li>
<li>기존 DB 구조에서 크게 벗어나지 못함<ul>
<li>스키마 종속적</li>
<li>제한적인 데이터 타입</li>
<li>쿼리 등의 성능 때문에 DB 모델링이 중요</li>
</ul>
</li>
</ul>
<hr>
<h3 id="데이터-필드의-변화-elt의-등장">데이터 필드의 변화: ELT의 등장</h3>
<blockquote>
<p><strong>“원하는 데이터만 추려서 저장!” ⇒ “뭐가 필요할지 모르니까 일단 다 저장!”</strong></p>
</blockquote>
<ul>
<li>데이터 니즈의 다양성
→ 데이터를 다루는 직군이 늘어남: 데이터 기반 의사결정
→ 데이터 소스의 다양화</li>
<li>데이터 통합</li>
<li>Computing resource / Storage resource 발전<ul>
<li>쉽고 저렴하게 담아서 원하는 시점에 원하는 형식으로 꺼내서 사용할 수 있게 됨</li>
<li>데이터 레이크의 발전</li>
</ul>
</li>
</ul>
<hr>
<h3 id="what-is-elt">What is ELT?</h3>
<blockquote>
<p><strong>“데이터를 자주 그리고 다양하게 뽑아서 사용하겠다!”</strong></p>
</blockquote>
<p>“데이터의 목적지에서 원하는대로 변환해서 사용하겠다!”</p>
<blockquote>
</blockquote>
<p>“데이터 과학자와 분석가가 편하게, 원하는 데이터를, 원하는 방식으로 
변환, 모델링, 쿼리할 수 있도록 해야겠다!”**</p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/f2b6d649-6b8c-42dd-9376-c8aceda3dc1e/image.png" alt=""></p>
<ul>
<li><p>다양한 데이터 소스로부터 원시 데이터를 가져와서 데이터 레이크에 저장</p>
</li>
<li><p>구조화된 데이터 모델이 불필요</p>
<p>  → 미리 모델링 고려할 필요 X</p>
</li>
</ul>
<hr>
<h3 id="is-elt-silver-bullet">Is ELT silver bullet?</h3>
<ul>
<li>개인정보, 보안, 암호화 등의 요구 사항에선 ETL이 필요</li>
<li>데이터의 전초기지(Outpost)가 필요할 때 ETL로 데이터를 뽑아서 DW, DM 구축</li>
<li>원천 데이터가 너무 많을 경우 시스템 및 비즈니스 요구사항에 따라 좀 잘라내서 저장</li>
</ul>
<p>⇒ ELT가 좋긴해도 ETL이 사라지진 않을 것 (여전히 배치에 의한 ETL가 70% 이상)</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ TIL ] 데이터 엔지니어링 (2)]]></title>
            <link>https://velog.io/@_ted_0527_/TIL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-2</link>
            <guid>https://velog.io/@_ted_0527_/TIL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-2</guid>
            <pubDate>Mon, 13 Jun 2022 11:23:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사내 M/L 개발자 &#39;고강빈&#39;님의 강의자료 입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/85663ded-43a6-4ec9-9394-3a2aa5a45c19/image.png" alt=""></p>
<ul>
<li>데이터 수집 엔진<ul>
<li>점차 확대되어 파이프라인 툴로 자리잡고 있음</li>
<li>다양한 소스에서 데이터를 수집하여 변환한 후 저장소로 전달</li>
</ul>
</li>
<li>플러그인을 통해 원하는 방식으로 parsing 가능</li>
<li>데이터 수집 &amp; 전달 (데이터 파이프라인 역할)</li>
<li>경량 수집기로부터 전달받은 데이터를 aggregation 하는 역할
<img src="https://velog.velcdn.com/images/_ted_0527_/post/1081cc2c-80b7-4dfa-9a09-f6d736bc2861/image.png" alt=""></li>
</ul>
<p><strong>⇒ Server Cluster → Aggregator → Store</strong></p>
<hr>
<h3 id="다양한-input-filter-output-plugin-지원">다양한 input, filter, output plugin 지원</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/c32be0df-f44b-4d43-a8d7-10c5c3cea954/image.png" alt=""></p>
<hr>
<h2 id="1-aws-서버-구축">1. AWS 서버 구축</h2>
<p>아마존 웹서비스 (AWS)에서 EC2 우분투 서버를 구축하고 해당 서버 위에서 데이터 수집 및 전달을 실습해보자.</p>
<hr>
<h3 id="aws-→-ec2-→-인스턴스-시작"><strong>AWS → EC2 → 인스턴스 시작</strong></h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/f774f2c9-982b-4585-9ceb-42f5503efe1c/image.png" alt=""></p>
<hr>
<h3 id="ubuntu-2004-lts-→-t2large">Ubuntu 20.04 LTS → t2.large</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/6d29a492-3425-4007-b06f-db145ab03b3c/image.png" alt=""></p>
<hr>
<h3 id="새-키-페어-생성">새 키 페어 생성</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/7f5262e6-a77c-4eea-8a1b-cb37b1568a31/image.png" alt=""></p>
<hr>
<h3 id="ec2-인스턴스-연결">EC2 인스턴스 연결</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/a6c8b79c-f6e1-423d-888a-37abea7973b0/image.png" alt=""></p>
<hr>
<h2 id="2-데이터-수집-및-전달">2. 데이터 수집 및 전달</h2>
<p>로그스태시(logstash)와 비츠(beats - filebeat)를 이용하여 데이터를 수집하고 이를 서버에 전달해보자.</p>
<hr>
<h3 id="download-logstash--filebeat">Download logstash &amp; filebeat</h3>
<pre><code class="language-powershell"># install java
sudo apt update
sudo apt install openjdk-11-jdk -y

# download &amp; unzip logstash
wget https://artifacts.elastic.co/downloads/logstash/logstash-7.16.2-linux-x86_64.tar.gz
tar -zxvf logstash-7.16.2-linux-x86_64.tar.gz

# download &amp; unzip filebeat
wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.16.2-linux-x86_64.tar.gz
tar -zxvf filebeat-7.16.2-linux-x86_64.tar.gz</code></pre>
<hr>
<h3 id="실습용-파일-다운로드--단순-로그-생성-및-적재">실습용 파일 다운로드 &amp; 단순 로그 생성 및 적재</h3>
<pre><code class="language-powershell"># clone repository &amp; copy files
git clone https://github.com/sangyun-han/aws-based-data-engineering
cp aws-based-data-engineering/week-1/logstash/logstash-config-* logstash-7.16.2/config/

# check logstash-config-generator.conf
cat logstash-7.16.2/config/logstash-config-generator.conf

# run it!
./logstash-7.16.2/bin/logstash -f logstash-7.16.2/config/logstash-config-generator.conf</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/1452822c-1504-4d1d-9d60-9b21764e7249/image.png" alt=""></p>
<p>⇒ 임의 데이터를 count만큼 반복 생산해주는 input plugin이 내용을 path의 파일로 작성
<img src="https://velog.velcdn.com/images/_ted_0527_/post/5052e962-2f6c-489f-bbe6-e22dd42c7f3c/image.png" alt=""></p>
<hr>
<h3 id="log-파일-읽어서-저장">log 파일 읽어서 저장</h3>
<pre><code class="language-powershell"># reset output file
rm logstash-output-*

# check logstash-config-file.conf
cat logstash-7.16.2/config/logstash-config-file.conf

# run it
./logstash-7.16.2/bin/logstash -f logstash-7.16.2/config/logstash-config-file.conf</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/334c0b51-aee7-4531-a614-c17fc6d19e0e/image.png" alt=""></p>
<p>⇒ input.file의 내용을 읽어서 path의 파일로 저장</p>
<pre><code class="language-powershell"># open new tab
# write data into input.file
echo &quot;1&quot; &gt;&gt; input.file
echo &quot;blahblah&quot; &gt;&gt; input.file

# check output
cat logstash-output-YY_MM_dd_HH.log</code></pre>
<ul>
<li>logstash가 꺼진 상태에서 input.file에 기록된 내용도 logstash 재실행시 업데이트 된다.</li>
</ul>
<hr>
<h3 id="filter-plugin-테스트">filter plugin 테스트</h3>
<pre><code class="language-powershell"># reset input, output file
rm input.file
rm logstash-output-*

# check logstash-config-filter.conf
cat logstash-7.16.2/config/logstash-config-filter.conf

# check sample log
cat aws-based-data-engineering/week-1/apache-sample-log

# run it!
./logstash-7.16.2/bin/logstash -f logstash-7.16.2/config/logstash-config-filter.conf</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/da70c231-40b2-47ee-bec3-1446df48529c/image.png" alt=""></p>
<p><strong>Filter plugin</strong></p>
<ul>
<li>grok</li>
<li>prune</li>
<li>mutate</li>
</ul>
<p><a href="https://www.elastic.co/guide/en/logstash/current/filter-plugins.html">더 많은 플러그인이 알고 싶다면</a></p>
<pre><code class="language-powershell"># 다른 필터
vi logstash-7.16.2/config/logstash-config-filter.conf</code></pre>
<hr>
<h3 id="filebeat-→-logstash-연동">filebeat → logstash 연동</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/5e132201-bad1-4cf0-834c-1c36a490183b/image.png" alt=""></p>
<pre><code class="language-powershell"># check logstash-config-filebeat.conf
cat logstash-7.16.2/config/logstash-config-filebeat.conf

# run logstash
cd logstash-7.16.2/config
~/logstash-7.16.2/bin/logstash -f logstash-config-filebeat.conf</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/cd029207-2007-4e25-baca-bdc108cc56a4/image.png" alt=""></p>
<p>⇒ port 5044번으로 logstash를 열었다. output path는 따로 지정하지 않았기에 저장되진 않는다.</p>
<pre><code class="language-powershell"># check filebeat.yml &amp; copy
cat aws-based-data-engineering/week-1/filebeat/filebeat.yml
cp aws-based-data-engineering/week-1/filebeat/filebeat.yml filebeat-7.16.2-linux-x86_64
chmod go-w aws-based-data-engineering/week-1/filebeat/filebeat.yml

# run it
cd filebeat-7.16.2-linux-x86_64
./filebeat run -e filebeat.yml</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/2445778e-a1d2-471d-b1f6-eb1cb786048d/image.png" alt=""></p>
<p>⇒ home에 있는 input.file을 읽어서 logstash가 있는 localhost:5044로 output</p>
<hr>
<h3 id="지속적인-로그-수집-실습">지속적인 로그 수집 실습</h3>
<p>⇒ 실제로 로그가 계속 찍히는 환경을 가정하기 위해, 무한루프문으로 문자열을 계속 input.file로 날려주자</p>
<pre><code class="language-powershell">for ((i=0; ;i+=1 )); do echo $i &gt;&gt; input.file;sleep 1; done</code></pre>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/2c0be566-292a-4644-9001-1ce6ff41d920/image.png" alt=""></p>
<hr>
<aside>
💡 **실습 종료 후엔 반드시 AWS 자원 정리할 것!**

</aside>]]></description>
        </item>
        <item>
            <title><![CDATA[[ TIL ] 데이터 엔지니어링 (1)]]></title>
            <link>https://velog.io/@_ted_0527_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4</link>
            <guid>https://velog.io/@_ted_0527_/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4</guid>
            <pubDate>Sat, 04 Jun 2022 12:14:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>사내 M/L 개발자 &#39;고강빈&#39;님의 강의자료 입니다.</p>
</blockquote>
<h2 id="1-데이터-엔지니어링">1. 데이터 엔지니어링</h2>
<blockquote>
<p>“우리의 온라인 활동에서 나오는 데이터는 그냥 사라지는 것이 아니다.
이러한 디지털 흔적들을 모으고 분석하면 매년 1조 달러 규모의 가치를 창출하는 산업이 된다.”</p>
</blockquote>
<p>다큐멘터리 영화, &lt;거대한 해킹 (The Great Hack)&gt; 중 </p>
<hr>
<h3 id="데이터-엔지니어링이란">데이터 엔지니어링이란?</h3>
<ul>
<li>데이터를 수집, 저장하고 처리하기 위한 시스템을 구축하고 운영하는 일</li>
<li>이를 위한 데이터 플랫폼을 개발하고 운영하는 일</li>
<li>데이터 파이프라인을 개발 / 구축 / 운영하는 일</li>
<li>ETL (Extract-Transform-Load)</li>
</ul>
<p>⇒ 데이터 기반 의사결정을 주도하는 데이터 플랫폼과 관련된 모든 일</p>
<hr>
<h3 id="데이터-엔지니어가-되려면">데이터 엔지니어가 되려면?</h3>
<p>   1) CS Fundamental</p>
<pre><code>- 자료구조 &amp; 알고리즘
- 수학 &amp; 통계학
- 컴퓨터 구조
- 네트워크
- Linux (OS)
    - CLI, Vim
    - Shell Scripting
    - Cronjobs
- 기타 개발 지식
    - 터미널 사용법
    - REST API
    - Git Version Control
    - …</code></pre><p>   2) Programming Language</p>
<pre><code>- Python (떠오르는 신예)
- Java (전통의 강자)
- Scala
- Go</code></pre><p>   3) Testing</p>
<pre><code>- Unit testing
- Integration testing
- Functional testing</code></pre><p>   4) Database Fundamental</p>
<pre><code>- SQL
- Normalization
- ACID transaction</code></pre><p>   5) DBMS</p>
<pre><code>- MySQL, PostgreSQL, MariaDB, Amazon Aurora, …
- MongoDB, Elasticsearch, Apache Casandra, Neo4j, Redis, …</code></pre><p>   6) Data warehouse &amp; Object Storage</p>
<pre><code>- Snowflake, Presto, Apache Hive, Google BigQuery, …
- AWS S3, Azure Blob Storage, Google Cloud Storage</code></pre><p>   7) Cluster computing fundamentals</p>
<pre><code>- Apache Hadoop, HDFS, Managed Hadoop
- MapReduce
- Lambda &amp; Kappa Architectures
- Amazon EMR, Google Dataproc, Azure Data Lake</code></pre><p>   8) Data Processing</p>
<pre><code>- Batch
- Streaming
- Hybrid</code></pre><p>   9) Messaging</p>
<pre><code>- Amazon SNS &amp; SQS, Azure Service Bus, Google PubSub
- RabbitMQ, Apache ActiveMQ</code></pre><p>   10) Workflow scheduling</p>
<pre><code>- Apache Airflow, Google Composer, Apache Oozie, Luigi</code></pre><p>   11) Monitoring pipelines</p>
<pre><code>- Prometheus
- Datadog
- Sentry
- StatsD</code></pre><p>   12) Infrastructure</p>
<pre><code>- Containers: Docker
- Orchestration: Kubernetes, Docker Swarm, GKE
- Provisioning: Terraform, AWS CDK</code></pre><p>   13) CI/CD</p>
<pre><code>- GitHub Actions, Jenkins</code></pre><p>   14) Data Security &amp; Privacy</p>
<hr>
<h3 id="데이터-파이프라인">데이터 파이프라인</h3>
<ul>
<li>데이터를 순차적으로 전달/처리하는 시스템</li>
<li>데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 구조</li>
<li>SW 기반 <strong>자동화된</strong> 데이터 처리 시스템</li>
<li>ETL</li>
<li>데이터 생성/수집/가공/저장 등 일련의 작업들</li>
<li>사람이 보기도 편하고 쓰기도 편하게끔 만드는 것이 궁극적 목표</li>
</ul>
<hr>
<h3 id="전통적인-데이터-파이프라인-etl">전통적인 데이터 파이프라인: ETL</h3>
<p>로그 스토리지, 거래정보 DB, 사용자 정보 DB 등에서 <strong>추출(Extract)</strong>해서 분석하기 편한 스키마로 <strong>변환(Transform)</strong>한 후 분석 및 시각화용 DB 또는 스토리지에 <strong>적재(Load)</strong>하는 프로세스</p>
<p>→ 실제 서비스 데이터가 수집되는 DB ≠ ETL에 의한 분석 DB
<img src="https://velog.velcdn.com/images/_ted_0527_/post/17b12b0b-db03-4d9c-a1f1-9d608535b16f/image.png" alt="프로세스 과정">
<img src="https://velog.velcdn.com/images/_ted_0527_/post/111787f8-ac37-4bb4-8a70-ac951f4ff59e/image.png" alt="">
<img src="https://velog.velcdn.com/images/_ted_0527_/post/810e8fec-9c7d-4388-b3f2-7acffeedb977/image.png" alt="각종 툴의 종류"></p>
<p>수집 → <strong>저장 → 처리</strong> ⇒ ELT</p>
<hr>
<h2 id="2-빅데이터-아키텍처">2. 빅데이터 아키텍처</h2>
<blockquote>
<p>“몇 년 동안 대중을 몰래 감시해온 저희로서는 수 많은 사람들이 자발적으로 자신의 거주지와 종교적 정치적 견해, 순서대로 정리한 친구 목록, 이메일 주소, 전화번호, 자신이 찍힌 수백 장의 사진, 현재 하고 있는 활동 정보를 공개하고 있다니 놀랍기 그지 없습니다. CIA로서는 꿈에 그리던 일이지요.”</p>
</blockquote>
<p>크리스토퍼 사르틴스키, CIA 부국장</p>
<hr>
<h3 id="데이터-처리-방식">데이터 처리 방식</h3>
<ol>
<li>Batch Processing (배치 처리, 일괄 처리)</li>
<li>Stream Processing (스트림 처리, 실시간 처리)</li>
</ol>
<hr>
<h3 id="batch-processing">Batch Processing</h3>
<ul>
<li>일괄처리<ul>
<li>특정 시간 동안 데이터를 모았다가 처리하는 방식 
→ 유한한 데이터셋 단위</li>
</ul>
</li>
<li>대용량 데이터 / 장기간 데이터 처리에 적합</li>
<li>분산 스토리지에 저장된 데이터를 정기적으로 추출해서 처리<ul>
<li>영속성 덕분에 언제든지 재처리 가능 → 이전 데이터에서 문제가 발생했을 때 다시 처리 가능</li>
<li>실시간성에 대한 요구사항이 없을 때 이용 → 사후분석</li>
</ul>
</li>
</ul>
<hr>
<h3 id="stream-processing">Stream Processing</h3>
<ul>
<li>실시간 처리<ul>
<li>데이터를 piece-by-piece로 들어오는 즉시 처리 
→ 무한 데이터셋 (주기도 없고 정해진 크기도 없고)</li>
<li>Unbounded data processing</li>
</ul>
</li>
<li>실시간 처리가 필요할 때 사용 (ex: 카카오 택시 매칭 , 자율주행)</li>
<li>과거 데이터나 재처리를 크게 고려하지 않음 
→ 최근 재처리 고려할 수 있는 기술이 개발되고 있긴함</li>
<li>Approximate result<ul>
<li>유실데이터에 robust</li>
</ul>
</li>
</ul>
<hr>
<h3 id="batch-vs-stream">Batch v.s. Stream</h3>
<p><strong>스트림 처리가 더 좋은가?</strong></p>
<ul>
<li>틀린 결과, 오류, 누락을 어떻게 보완할 것인가</li>
<li>늦게 전송된 데이터를 어떻게 처리할 것인가 
→ 시계열성이 중요한 경우 보완이 필요함</li>
</ul>
<p><strong>그럼 배치 처리?</strong></p>
<ul>
<li>실시간성의 부족</li>
</ul>
<p>⇒ 두 아키텍처는 상호 보완적 관계</p>
<hr>
<h3 id="lambda-architecture-layer">Lambda Architecture: Layer</h3>
<blockquote>
<p><strong>스트림 처리의 문제점을 장기 저장소와 안정적인 배치 처리로 보완하는 것이 핵심</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/16d63455-bac3-4abb-8838-457bc8edc93b/image.png" alt="">
<img src="https://velog.velcdn.com/images/_ted_0527_/post/d415d421-9815-478e-9dd2-c954a60c95b0/image.png" alt=""></p>
<ul>
<li><p>원본 데이터는 장기 저장소(S3, HDFS 등)에 저장 중</p>
</li>
<li><p><strong>스트림 처리: 스피드 레이어</strong></p>
<ul>
<li>실시간 처리가 가능해서 처리 결과를 빠르게 확인 가능
→ Real-time View</li>
<li>데이터 보관 기간이 짧아서 일정 시간이 지난 데이터는 삭제됨</li>
<li>정확도가 배치에 비해 떨어짐</li>
</ul>
</li>
<li><p><strong>배치 처리: 배치 레이어</strong></p>
<ul>
<li><p>과거의 데이터를 장기 저장소에 축적하고 여러 번 다시 집계 가능 
→ 재처리 가능</p>
</li>
<li><p>대용량 데이터를 처리할 수 있지만 1회 처리에 소요되는 시간이 길다</p>
</li>
<li><p>데이터가 쌓이고 배치 작업을 기다리는 동안 데이터를 파악할 수 없음</p>
<p>  → 서빙 레이어를 통해 배치 처리 결과물 조회 가능</p>
<p>  → 스피드 레이어에서 실시간으로 조회 가능 (배치 뷰 업데이트 전까지)</p>
<p>  → 두 가지 뷰를 조합하여 조회</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="람다-아키텍처의-문제점">람다 아키텍처의 문제점</h3>
<ul>
<li>데이터 자체를 병합하거나 각각 쿼리를 하는 등의 방식으로 결과물을 사용<ul>
<li>데이터가 너무 크면 병합도 큰 리소스</li>
</ul>
</li>
<li>독립된 두 버전의 파이프라인을 모두 개발/운영해야 하기 때문에 효율이 떨어짐</li>
<li>두 파이프라인에서 나온 결과를 병합하거나 양쪽에 쿼리를 해야 했기에 비효율적</li>
</ul>
<p>⇒ 카파 아키텍처의 등장으로 이어짐</p>
<hr>
<h3 id="kappa-architecture">Kappa Architecture</h3>
<p><img src="https://velog.velcdn.com/images/_ted_0527_/post/fc123e0e-c395-4595-afed-d020ffafaea7/image.png" alt=""></p>
<ul>
<li>카프카 개발자 Jay Kreps가 제안: <a href="https://www.oreilly.com/radar/questioning-the-lambda-architecture/">참고자료</a></li>
<li>스피드 레이어만 존재
→ 배치 레이어와 서빙 레이어 제거</li>
<li>스트림 처리로만 데이터 파이프라인 단순화
<img src="https://velog.velcdn.com/images/_ted_0527_/post/b3cfb7e2-9aca-478e-8392-a57bfc5b6c0c/image.png" alt=""></li>
</ul>
<p>→ 두 처리 방식의 결과물을 병합하기 위한 리소스 절약</p>
<p>→ 단일 파이프라인과 단일 뷰로 개발/운영 효율화</p>
<hr>
<h3 id="왜-이제서야-등장했나">왜 이제서야 등장했나?</h3>
<ul>
<li>발전된 스트림 처리 엔진<ul>
<li>초기 람다 아키텍처가 소개될 당시 스트림처리 엔진의 신뢰성이 낮았음</li>
<li>지금은 at-leat-once뿐만 아니라 exactly-once까지 지원<ul>
<li>at-most-once: 최대 한번, 메시지 유실 가능성 있음</li>
<li>at-least-once: 최소 한번, 메시지 유실 가능성 없음, 중복 가능성 있음</li>
<li><strong>exactly-once: 전달 보장, 유실 가능성 없음, 중복 가능성 없음</strong></li>
</ul>
</li>
</ul>
</li>
<li>심하게 과장된 스트리밍의 한계</li>
<li>재현 가능한(replayable) 메시지 브로커: Kafka<ul>
<li>메시지 브로커의 데이터 보관기간이 충분히 길다면 과거의 데이터를 다시 스트림 파이프라인에 흘려서 재처리 가능해짐</li>
</ul>
</li>
<li><strong>무엇보다 실시간성이 중요해짐</strong>
→ 되니까.. 보다는 필요하니까! (ex: 우버 매칭)</li>
</ul>
<hr>
<h3 id="카파-아키텍처의-문제점">카파 아키텍처의 문제점</h3>
<ul>
<li>높은 부하<ul>
<li>재처리 시에는 수 배 ~ 수 십 배의 리소스 필요</li>
</ul>
</li>
<li>실시간성에 완벽하게 의존하기 때문에 Computation-intensive한 작업이 어려움<ul>
<li>ML/DL 등 계산량이 많이 필요한 경우 계산 속도가 이를 따라가지 못함</li>
</ul>
</li>
</ul>
<p>⇒ 분산처리, Scale-Out, <strong>Cloud Computing</strong></p>
<hr>
<h2 id="3-데이터-엔지니어링-on-cloud">3. 데이터 엔지니어링 on Cloud</h2>
<blockquote>
<p>“넷플릭스 서비스 역시 빠른 속도로 진화함에 따라 많은 리소스를 차지하는 새로운 기능이 다수 도입되고 데이터 사용량도 지속적으로 증가해 왔습니다. 넷플릭스의 기존 데이터 센터가 이러한 급성장을 지원하기란 매우 어려웠지요. 그러나 클라우드의 탄력성 덕분에 이제 수천 개의 가상 서버와 페타바이트급(PB) 저장 용량을 불과 몇 분 내에 추가할 수 있게 되었습니다. 이에 전 세계에 분산된 AWS 클라우드 지역을 기반으로 글로벌 인프라를 유연하게 활용하고 그 역량을 확대할 수 있게 되었으며, 언제 어디서나 더 편안하고 즐겁게 콘텐츠를 스트리밍할 수 있는 환경이 조성되었지요.”</p>
</blockquote>
<p>유리 이즈라일예브스키, 넷플릭스 클라우드&amp;플랫폼 엔지니어링 부사장</p>
<hr>
<h3 id="why-cloud">Why Cloud?</h3>
<ul>
<li>뛰어난 확장성<ul>
<li>늘어나는 데이터를 손쉽게 대응 (컴퓨팅 자원, 스토리지, 신규 기능)</li>
</ul>
</li>
<li>효율적인 자원 활용</li>
<li>뛰어난 비용 효율성</li>
<li>빠른 리소스 구축</li>
<li>현대 데이터 엔지니어링과 클라우드는 뗄 수 없는 사이<ul>
<li>Public Cloud든 on-prem이든</li>
</ul>
</li>
</ul>
<hr>
<h3 id="데이터를-위한-수많은-서비스">데이터를 위한 수많은 서비스</h3>
<ul>
<li>개발/구축할 인력 대신 만들어진 서비스를 이용</li>
<li>일부 대체 불가능한 서비스들(Bigquery, S3, …)</li>
<li>법적 제도적 이슈 (클라우드 및 개인정보 관련 인증과 제도)</li>
<li>손쉽게 사용 가능한 ML/DL 솔루션</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GroupBy] 개발 그리고 문화]]></title>
            <link>https://velog.io/@_ted_0527_/GroupBy-%EA%B0%9C%EB%B0%9C-%EA%B3%BC-%EB%AC%B8%ED%99%94</link>
            <guid>https://velog.io/@_ted_0527_/GroupBy-%EA%B0%9C%EB%B0%9C-%EA%B3%BC-%EB%AC%B8%ED%99%94</guid>
            <pubDate>Thu, 19 May 2022 05:49:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>좋은 개발 문화란 결국 다양한 개별의지가 충돌하는 답 없는 문제로 귀결된다.</strong></p>
</blockquote>
<h1 id="keyword">Keyword</h1>
<p><em>[Agile Process, Code Review, Scrum, Sprint, 회고, 각종 툴, 개인의 성장을 위한 활동, Pair programming &amp; blogging, Tech blog, etc]</em>
<img src="https://velog.velcdn.com/images/_ted_0527_/post/e86770f8-e37e-4ab8-afe7-ab2e996bd0d6/image.jpeg" alt=""></p>
<h1 id="issue">Issue</h1>
<ul>
<li><p>본인은 현재 <a href="https://groupby.super.site/">GroupBy</a> 라는 스타트업에 입사한지 한 달차 인 백엔드 개발자이다. 아직은 플랫폼 개발팀이 없는 상태이고, 웹 개발자도 5개월차 백엔드인 본인뿐이다.</p>
</li>
<li><p>이전 개발자 분들(프1, 백1)이 사용하다가 남겨놓은 레거시들이 있지만, 각자가 혼자서 개발을 하셨다보니 솔플(solo play)의 흔적이 많이 남아 있는 상태다.</p>
</li>
<li><p>당연히 이렇다 할 개발 문화는 존재하지 않았고, 앞으로 꾸려질 개발팀에 도입하고픈 개발 문화에 대해 내가 직접 고민을 해야하는 상황이다.</p>
</li>
<li><p>여러가지 개발 문화에 대해 리서치하고 생각들을 정리하는 것이 이 포스팅의 목적이다.</p>
</br>

</li>
</ul>
<h1 id="think-hard">Think hard</h1>
<p><a href="https://megazonedsg.github.io/agile-failed-history/">어느 데브옵스 분의 개발 문화 정착 실패담</a></p>
<p>개발 문화를 도입해야한다 라는 의견을 들었을 때, 아래와 같은 생각들이 머리를 스쳐갔다. 그리고 리서치를 하면서 생각에 변화가 일어났다.</p>
<ul>
<li><p><strong>좋은게 좋은거지. 괜찮은 문화 가져다 쓰자!</strong>
문화는 상징적인 것이다. 개개인이 다른 만큼 원하는 문화도 다양하다.
남들이 좋다는 문화를 그냥 가져다 쓴다면 맞지 않는 옷을 억지로 입은 것처럼 어색하고 불편하기만 할 뿐이다.</p>
</li>
<li><p><strong>개발팀이 이제야 꾸려지는데, 이거 맞아?</strong>
스타트업, 그 중에서도 IT나 소프트웨어 개발이 목적이 아닌 일반 서비스에 종사하는 스타트업은 개발팀이 열악하거나 아예 없는 무주공산일 가능성이 크다.
이런 경우, 혼자서 할 수 있는 것이 많지 않다. 그러나 작은 일이라도 하나씩 모아놓고, 정리하고, 파악해 둔다면 2명이 되는 그 순간부터 팀의 문화가 시작 될 수 있을 것이다.</p>
</li>
<li><p><strong>경력직(사수) 있으니까 괜찮겠지. 알아서 하실테니, 따라만 가자.</strong>
시니어 or 리드급 개발자에 의해 문화가 만들어질 수도, 아닐 수도 있다. 리더가 자기성장욕구도 강하고 후배 개발자들에게 관심이 많다면 자연스레 다양한 방법들로 문화 정착에 힘 쓸 확률이 높다. 혹은 실험이나 도전의지가 강한 사람이라면 이 또한 여러 방법들을 시도해보고 싶어 할지도 모른다. 개발팀이 완전 새로이 만들어 졌거나, 열악하다면 여러가지 시도를 해보는 것만으로도 좋은 경험이 된다. 그러나 최악의 경우, 너는 너, 나는 나 식 대로 회사일만 하고 따로 국밥이 되는 경우도 생길 수 있을 것이다. 주니어에겐 난감한 상황일 뿐만 아니라, 퇴사 의지를 불러 일으킬지도 모른다.</p>
</li>
<li><p><strong>개발 문화? 그냥 코드 리뷰하고 데일리 스크럼하고 그런거 아닌가?</strong>
기업에 따라 개발 문화는 해당 조직의 문화가 될 수도 있다. 대부분의 기업에는 개발팀 뿐만 아닌 여러 개발과 무관한 팀들이 존재한다. 예를 들어 &#39;코드 리뷰&#39;는 개발팀만 수행 할 수 있지만, &#39;독서 모임&#39;은 아무나 참여가 가능하다. 이처럼 문화는 어떠한 프로세스가 아닌 목적에 따른 구성원들의 참여 그 자체인 것이다. 설령 &#39;독서 모임&#39;이 개발자들 사이에서 시작 했을지라도 다른 팀원들이 합류하고 책의 종류가 다양해 지면서 모임의 규모가 전사적으로 커진다면 충분히 &#39;조직 문화&#39;, &#39;기업 문화&#39; 로 불리어 질 수 있는 것이다.</p>
</br>

</li>
</ul>
<h1 id="pathfinding">Pathfinding</h1>
<blockquote>
<p><strong>문화는 2명 이상이어야 존재 할 수 있다.</strong></p>
</blockquote>
<p><a href="https://www.abctech.software/2012/09/13/good-startup/">바람직한 스타트업 개발문화의 조건
</a></p>
<p>개발 문화에 대해 리서치한 이유에는 앞으로 꾸려질 개발팀에 도입하면 좋을 듯한 것들을 미리 물색하기 위함과 동시에 현재 우리 회사에 필요한 것은 없는지 알아보기 위한 것도 포함되어 있다. 허나, 내가 근무하고 있는 조직에는 이미 좋은 문화들이 도입되었고 정착되어 있었다.</p>
<ul>
<li><p><strong>데일리 스크럼</strong>
매일 아침 10시에 이루어지는 전체 미팅. 전체 회의이지만 어제 한 일, 오늘 할 일, 추가적인 의논이 필요한 일들에 대해서 간단히 대화하는 시간이다. </p>
</li>
<li><p><strong>월요일 오지랖 회의</strong>
매주 월요일 오전에 실시하는 전체 회의. 지난주에 대한 간단한 회고를 하고 이번 주에 진행 할 일에 대해서 부연 설명과 함께 팀원들에게 알리는 시간이다.</p>
</li>
<li><p><strong>간트 차트</strong>
경영팀에서 선 도입 후 전사적으로 확대시킨 스케줄 차트. 우리 팀 뿐만 아니라 다른 팀원들의 주간, 월간 계획을 한 곳에서 살펴 볼 수 있도록 돕는다.</p>
</li>
<li><p><strong>업무의 투명화</strong>
슬랙의 오픈 채널들을 통해 경영팀은 무엇을 진행중인지, 마케팅은 어떤 형태로 이루어 졌는지, 컨택한 회사는 어디이고 미팅을 진행할 회사는 어디인지 등등. 갖가지 이벤트들을 당연한 듯이 투명하게 공개하고 의견을 묻고 답하는 행위가 자연스럽다.</p>
</li>
<li><p><strong>수평적 관계</strong>
&#39;~님&#39; 으로 호칭이 통일 되고, 지시하는 형태의 업무 방식이 아닌 필요에 의한 원하는 업무를 스스로 택하는 방식을 취하고 있다.</p>
</li>
</ul>
<p>언뜻 보기엔 당연하기도하고 쉬워보이기도 한 방식들이지만, 의외로 많은 기업들이 흐지부지 되고 의미가 퇴색되는 등 유지에 애를 먹는다고 한다. 그렇게 되지 않기 위해 인원이 늘고 규모가 커지면 좀 더 디테일한 관리가 필요하겠지만, 이 정도를 유지하는 것만으로도 성공적이라 할 수 있겠다.</p>
<p>앞으로 탄생할 개발팀에도 조직 문화와 더불어 개발자들 만의 좋은 문화가 생길 텐데, 이것만큼은 꼭 있었으면 하는 세 가지 문화를 추려보았다.</p>
<ul>
<li><p><strong>Code Review</strong>
코드 리뷰는 성장을 바라는 개발자라면 누구나 바라는 문화가 아닐까 싶다. 본인의 코드 뿐만 아니라 타인의 코드에도 열린 마음을 갖고 코드를 분석하는 것은 팀 전체가 성장하는 알찬 시간이 될 것이다.</p>
</li>
<li><p><strong>Pair Coding</strong>
페어 코딩은 두 명이 짝을 이루어 각자의 역할을 갖고 코딩을 하는 형태를 말한다. 대개 한 명은 리드를 다른 한 명은 배우는 역할인 경우가 많으며, 다른 기업에서도 신입을 가르치거나 키울 때 흔히 사용하는 방법이라고 한다.</p>
</li>
<li><p><strong>포스트모템 문화</strong>
특정 과제 완료 후 그 프로젝트 전 과정을 되돌아보면서 잘된 점이 무엇인지, 잘못된 점이 무엇인지를 살펴보는 작업을 의미한다. 개발자에게 회고는 새로운 것을 학습 하는 것 만큼이나 중요하다고 생각한다. 개인이 혼자서 회고를 하는 것보다 팀 전체의 시각에서 회고를 한다면 훨씬 더 많은 것들이 보이고 다양한 해결책들이 나올 수 있을 것이다.</p>
</br>
# Conclusion

</li>
</ul>
<blockquote>
<p><strong>누가 뭐래도 최고의 복지는 언제나 최고의 동료다.</strong></p>
</blockquote>
<ul>
<li>처음에도 언급했지만, 좋은 개발 문화라는 것은 특정한 답이 정해져 있는 것이 아니다. 이번 프로젝트에서 도입했던 문화가 회고 후 사라질 수도 있고 누군가의 반대 의견에 의해 도입되지 않을 수도 있는 것이다. 개개인의 성향, 의지, 공통의 목표와 같이 다양한 의지들이 모인 상태에서 수 많은 대화와 협력을 통해 조금씩 만들어져 가는 것이 문화다. 
개인적으로, 무엇보다 중요한 것은 해당 팀 구성원의 자유의지가 아닐까 한다. 자율적인 의지가 수반되어야 본인 스스로도 스트레스 받지 않고 프로세스를 받아 들일 수 있을 것이고, 마주앉는 팀원과도 그 프로세스를 같이 함에 따라 신뢰가 생김으로써 좋은 효과와 성과를 얻을 수 있는 문화가 정착 될 수 있을 것이다.</br>

</li>
</ul>
<h1 id="ref">Ref.</h1>
<p><a href="https://daco2020.tistory.com/74">여러 기업의 개발 문화 참고 블로그</a></p>
<h3 id="독특한-개발-문화들">독특한 개발 문화들</h3>
<ul>
<li><p><strong>오늘의 집</strong>
칸반 보드 및 아이데이션 보드 운영. 전사 모든 팀원이 아이디어를 공유할 수 있는 현황판을 운영한다.
개발팀을 나누고 도식화가 잘 되어 있어 각 팀이 어떤 역할을 하는지 파악이 쉽다.</p>
</li>
<li><p><strong>마켓컬리</strong>
몹 프로그래밍(몹 프로그래밍은 한 명의 드라이버와 여러 명의 프로그래머가 하나의 PC로 코딩 또는 문서화 작업을 진행하는 개발 방식으로 1:1방식인 페어 프로그래밍을 1:n으로 확장시킨 형태이다. 여기서 n은 에자일 개발팀 전체 인원수로 팀 전원이 참여한다는 것이 특징이다). 페어코딩을 활성화 하여 적용함.</p>
</li>
<li><p><strong>우아한 형제들</strong>
KPT 툴을 사용하여 의견을 나누고 대안을 찾거나, 새로운 방향으로 나아가는 방식을 취했다.
K: Keep(잘하고 있는 점, 계속 했으면 좋겠다 싶은 점)
P: Problem(뭔가 문제가 있다 싶은 점, 변화가 필요한 점)
T: Try(잘하고 있는 것을 더 잘하기 위해서, 문제가 있는 점을 해결하기 위해서 우리가 시도해 볼 것들)</p>
</li>
<li><p><strong>요기요</strong>
&#39;천하제일 망함대회&#39; 달에 한 번 전 직원이 모인 자리에서 자신이 실수했거나 망한 작업을 발표하고 그 문제에 대해 해결하는 대회가 있다. 실패를 공유하면서 실패에 대한 두려움을 없애는 데에 효과적.</p>
</li>
<li><p><strong>리디북스</strong>
포스트모템 프로세스 도입. 프로젝트를 팀 전체가 회고하면서 모든 팀원이 완전 습득에 가깝게 프로젝트를 이해하는 구도를 만듦.</p>
</li>
<li><p><strong>토스</strong>
본 것중 가장 독특한 팀 구조를 가지고 있다.
<img src="https://velog.velcdn.com/images/_ted_0527_/post/dcfb99cf-2acf-4ff0-8759-2c71f664893b/image.png" alt=""></p>
</li>
</ul>
<p>Silo(사일로) : Product Owner, Designer, Developer 등 약 8-9명의 메이커로 구성된 조직
Chapter(챕터) : Frontend Chapter, Backend chapter, Designer Chapter 등 같은 종류의 일을 하는 팀원들이 모인 조직
Guild(길드) : 특정 과제를 진행/해결하기 위해 결성하는 모임</p>
<ul>
<li><strong>스포티파이</strong>
토스와 유사하지만 한 단계 더 많은 구조를 가지고 있다.
<img src="https://velog.velcdn.com/images/_ted_0527_/post/5a018fb8-6fe7-4dff-a84e-137fa173fca3/image.jpeg" alt=""></li>
</ul>
<br>
</br>

<blockquote>
<p>Agile Manifesto - 애자일 선언문</p>
</blockquote>
<p>우리는 소프트웨어를 개발하고, 
또 다른 사람의 개발을 도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고 있다. 
이 작업을 통해 우리는 다음을 가치 있게 여기게 되었다.</p>
<blockquote>
</blockquote>
<p>공정과 도구보다 개인과 상호작용을
포괄적인 문서보다 작동하는 소프트웨어를
계약 협상보다 고객과의 협력을
계획을 따르기보다 변화에 대응하기를
가치 있게 여긴다. </p>
<blockquote>
</blockquote>
<p>이 말은, 왼쪽에 있는 것들도 가치가 있지만,
우리는 오른쪽에 있는 것들에 더 높은 가치를 둔다는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GroupBy] 개발 문화 그리고 협업]]></title>
            <link>https://velog.io/@_ted_0527_/GroupBy-%EA%B0%9C%EB%B0%9C%EB%AC%B8%ED%99%94-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%98%91%EC%97%85</link>
            <guid>https://velog.io/@_ted_0527_/GroupBy-%EA%B0%9C%EB%B0%9C%EB%AC%B8%ED%99%94-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%98%91%EC%97%85</guid>
            <pubDate>Fri, 13 May 2022 02:35:14 GMT</pubDate>
            <description><![CDATA[<h1 id="개발-문화에-대한-사색">개발 문화에 대한 사색</h1>
<ul>
<li><p>각 개발팀마다 고유의 개발 문화가 존재한다. 문화는 둘 이상의 사람이 만나야 만들어 진다고 한다. 혼자서 행하는 것은 문화라기보단 습관이나 개인학습에 가깝다고 본다.</p>
</li>
<li><p>개인적으로는 코드 리뷰도 있었으면 좋겠고, 똑똑한 사람들만 한다는 TDD 방식도 도입했으면 좋겠고, 주 1회 스터디도 있었으면 좋겠고, 등등 이지만, 사실 본인이 주니어 개발자이다 보니, 이것 저것 경험 해보고 싶은 욕심이 커서 바라는 것만 많을 뿐이다.</p>
</li>
<li><p>문화는 원한다고 다 만들 수 있는게 아니다. 주위 환경에 가장 큰 영향을 받고 한정된 리소스에 두번째 영향을 받을 것이며, 함께하는 구성원들이 마지막 영향을 미칠 것이다. 윗사람이 그거 필요없다 라고 하면 얄쨜없다는 것이다.</p>
</li>
<li><p>그럼에도 불구하고 코드 리뷰와 스터디 정도는 생겼으면 좋겠다는 바람이다. 혼자서 학습하는게 효율이 영 좋지않은 나로써는 반강제로라도 의미를 부여해야 발전이 있을 것이니까.</p>
</li>
<li><p>오늘은 곧 개발팀이 만들어질 회사와 더 정확히는 나를 위해 어떤 협업툴이 있는지, 백엔드라면 꼭 필요한 API 문서화 툴에는 어떤 것이 있는지 한번 찾아보자. 그리고 어떠한 개발문화가 우리 회사에 적합하고 앞으로 만들어질 팀원들은 어떤 것을 원하는지 들어본 뒤에 결정해도 늦지 않을 것이다.</p>
</li>
</ul>
<h2 id="협업툴">협업툴</h2>
<h3 id="github">Github</h3>
<ol>
<li>issue 탭에서 현재 발생한 문제점들을 등록하고 관리 할 수 있다.</li>
<li>Project 탭에서 issue 와 연결하여 업무를 세분화 하고 분배가 가능하다. 유사한 협업툴로는 Trello 가 있는데, Trello는 티켓 내부에 체크리스트를 만들 수 있는 기능도 존재한다.</li>
</ol>
<p>장점 : 깃헙의 기능들이 늘어남에 따라 단순히 코드만 관리하던 공간에서 협업을 용이케하는 기술을 갖춤. 깃헙 하나에 툴이 몰빵되어 있으므로 이곳 저곳 돌아다니며 프로젝트 관리를 따로 하지 않아도 됨.</p>
<p>단점 : 기능들이 하나씩은 부족한 부분들이 존재함. 또한 혹시라도 깃헙이 다운되어 버린다면 멘붕이 올 수도 있음.</p>
<h3 id="trello">Trello</h3>
<ol>
<li>티켓을 생성하고 티켓 내부에 체크리스트 및 task 관리가 가능한 툴</li>
<li>직관적이고 누가 담당하고 있는지 쉽게 알아볼 수 있음.</li>
<li>티켓 내부에 노션만큼 다양한 마크다운 기능들이 있음</li>
</ol>
<p>장점 : 티켓을 잘 만들어 두면 스크럼이나 스프린트, 회고와 같은 미팅들에서 티켓만 열어서 회의 가능. 티켓 오픈 방식이 팝업형이라서 시각적으로도 편리함.</p>
<p>단점 : 프로젝트외의 용도로는 사용하기 어려움. 회의록이라던지 레퍼런스 및 스터디 관련 공유를 위해서는 다른 툴 사용이 불가피. 규모가 커지면 유료.</p>
<h3 id="jira">Jira</h3>
<ol>
<li>노션과 트렐로가 적절히 섞여 있는 느낌. 인터페이스는 생각보다 난잡함.</li>
<li>사용법을 잘 알고 있다면 세세하고 보다 꼼꼼하게 프로젝트 관리가 용이함.</li>
<li>10명 이상부터 유료</li>
</ol>
<p>장점 : 많은 개발팀들이 지라를 활용하여 프로젝트 관리를 하고 있음. 인터페이스는 처음보기에 복잡해 보이지만 사용법이 직관적이고 쉬움. 무엇보다 일정을 분기 및 일별, 시간별로도 나눌 수 있어서 철저한 계획성을 지켜나갈 수 있음.</p>
<p>단점 : 사람에 따라 굉장히 귀찮은 부분이 될 수 도 있다. 사용법에 대한 학습 시간 필요. 구성원이 많아지면 유료.</p>
<h3 id="notion">Notion</h3>
<ol>
<li>문서 작성, TASK 관리, 일정 관리, 레퍼런스 공유 등등 활용도와 방식에 따라 자유자재로 사용이 가능함.</li>
<li>개발자라면 누구나 한번쯤 써봤을 법 할 정도로 대중적이고 잘 알려져 있어서 따로 학습의 기간 불필요.</li>
<li>기업형은 유료</li>
</ol>
<p>장점 : 친숙하고 문서 작성만큼은 노션을 따라 올 툴이 없는 듯. 캘린더 기능도 있고 체크리스트, 토글 기능까지 없는게 없는 수준.</p>
<p>단점 : 생각보다 최적화가 좋지 않음. 전체 너비로 바꾸면 전체 페이지로 하지 않는 이상 문단들이 시각적으로 보기 좋지 않음. 개인별 일정 관리 힘듦. 프로젝트용 협업툴 이라기 보다는 전사적인 공유 페이지 역할이 더 잘 어울림.</p>
<h2 id="api-문서-작성용-툴">API 문서 작성용 툴</h2>
<h3 id="swagger">swagger</h3>
<ol>
<li>DRF 에 더없이 친숙한 API 전용 툴</li>
<li>라이브러리는 다소 무겁지만 코드 내부에 몇 줄을 추가하여 자동화 할 수 있음</li>
<li>자칫 코드가 지저분해 보일 수 있음</li>
<li>회사에 이미 관련 코드가 존재함. 다만 모든 api에 적용되어 있는지는 추가 확인 필요</li>
</ol>
<h3 id="postman">Postman</h3>
<ol>
<li>mock 서버를 만들 수 있는 기능 존재</li>
<li>api test 에 거의 필수적인 프로그램</li>
<li>api 문서화 시 별도의 코드 작업 불필요. 프로그램 내부에서 클릭 몇 번으로 문서 생성 및 배포도 가능.</li>
<li>불-편 (올 수작업)</li>
<li>포스트맨 자체가 겁나 무거움(다량의 리소스 쪽쪽)</li>
</ol>
<h3 id="slate">Slate</h3>
<ol>
<li>깃헙 돌아다니다가 찾은 api 문서화 툴</li>
<li>api 에 대한 설명을 붙이기 좋음. 마크다운 지원.</li>
<li>html 파일 하나만 수정함으로써 문서화 가능. 사용법에 대한 예제도 제공하므로 학습에 용이함.</li>
<li>사용을 위해서는 루비를 설치 해야함.</li>
</ol>
<h3 id="api-blueprint">API Blueprint</h3>
<ol>
<li><p>Slate와 비슷한 인터페이스와 결과물</p>
</li>
<li><p>문서를 세련되게 꾸미고 표현 할 수 있음</p>
</li>
<li><p>렌더러 없음. 단, Third-party 렌더러 사용이 필요함 (ex: Aglio)
Aglio 사용 시 5개 테마 &amp; 2, 3패널 디자인 제공</p>
</li>
<li><p>학습난이도 약간 높음</p>
</li>
</ol>
<h3 id="gitbook">GitBook</h3>
<ol>
<li><p>깔끔한 UI 제공</p>
</li>
<li><p>Git의 commit / merge가 존재 -&gt; 문서 버전 관리 가능 (=업데이트 내역 확인 용이)</p>
</li>
<li><p>테스트 요청 / 응답 사용 불가</p>
</li>
<li><p>팀이 사용할 경우 유료 (개인은 무료)</p>
</li>
</ol>
<h1 id="ref">Ref.</h1>
<p><a href="https://tech.kakaoenterprise.com/127">API 문서 작성이란(카카오)</a>
<a href="https://minkukjo.github.io/opensource/2020/10/28/Infra-24/">Slate 관련 블로그</a>
<a href="https://devwaffle.tistory.com/48">GitBook 관련 블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] EC2, ssh online - timeout, port 22 problem]]></title>
            <link>https://velog.io/@_ted_0527_/AWS-EC2-ssh-online-timeout-port-22-problem</link>
            <guid>https://velog.io/@_ted_0527_/AWS-EC2-ssh-online-timeout-port-22-problem</guid>
            <pubDate>Thu, 12 May 2022 05:42:50 GMT</pubDate>
            <description><![CDATA[<h2 id="issue">Issue</h2>
<ul>
<li>회사에서 잘만 접속되던 ec2 서버.
재택근무 하는 날 집에서 접속을 했더니 왠걸, 
<code>port 22 : Operation timeout</code> 
에러가 날 반긴다. 집에서 일을 하지 말라는 계시인가</li>
<li>말 같잖은 상상은 접어두고 원인을 찾아 본다.</li>
</ul>
<h2 id="struggling">Struggling</h2>
<ul>
<li>Google 은 방대하지만 초보에겐(나에겐) 10층짜리 중고서점 이나 진배없다. <code>timeout</code> 을 키워드로 잡고 구글링을 했더니, 먼저 AWS의 답변이 눈에 들어왔다.
<img src="https://velog.velcdn.com/images/_ted_0527_/post/0c8fa1d8-0515-49bf-907f-445dc0e53916/image.png" alt=""></li>
</ul>
<ol>
<li>연결을 차단하는 방화벽이 없습니다.</li>
</ol>
<p>-&gt; 연결을 차단하는 방화벽이 있는지 확인하고 해제하세요. (라는 거겠지?)
2. SSH 서비스가 인스턴스에서 실행 중입니다.
-&gt; 중복 실행인거 아니냐?
3. SSH TCP 포트 22가 수신 대기 상태에 있습니다.
-&gt; 포트 22 가 사용중 이거나 뭔일 있는거 아니여?
4. 서버의 IP 주소 또는 호스트 이름 입니다.
-&gt; 서버의 IP 주소 또는 호스트 이름 을 확인 하세요. (번역 실화냐)
5. 보안 그룹 및 네트워크 ACL이 TCP 포트 22에서 수신되는 트래픽을 허용합니다.
-&gt; ACL(접근 제어 목록) 이나 보안 그룹에서 포트 22 오픈 하세요.</p>
<ul>
<li>번역이 심상치 않지만, 하나하나 verify 해보자.</li>
</ul>
<h2 id="checking">Checking</h2>
<ul>
<li>1번 방화벽 문제 : 방화벽 전부 꺼봤지만 failed</li>
<li>2번 인스턴스 중복 실행 : 회사에 ssh 쓰는 사람 나 밖에 없음. 내가 안썼으니 이것도 failed</li>
<li>3번 포트 22가 수신 대기 중 : 잘 모르겠음. 사용중인 포트 확인법 같은걸 검색해야 하나?</li>
<li>4번 IP or 호스트 이름 확인 : Ctrl+C &amp; V 는 틀리지 않는다. failed</li>
<li>5번 ACL or 보안그룹에서 포트 22 오픈 : 보안그룹은 모든 IP 와 포트를 열었다. ACL?<del>(뭐야 이건)</del></li>
</ul>
<h2 id="tracking">Tracking</h2>
<ul>
<li>사실 입사한 첫 주에 집에서 ssh 접속을 했을 땐 접속이 잘 되었었다. 한달이 지난 이 시점에서 갑자기 접속이 안되니 &#39;내가 뭘 잘못 건드렸나&#39; 싶은 불안감이 스멀거리며 피어 오르는 것이다.</li>
<li>그때와 지금이 다른 부분은 인터넷의 설치 유무 뿐이다.
원룸으로 막 이사를 했을 당시에는 건물에 개방된 KT 공짜 와이파이로 연결을 했었는데, 이후 SK 회선을 새로 설치를 했고 와이파이 역시 나만의 SK 와이파이를 쓰게 된 것이다.</li>
<li>설마 SK 가 문제겠어?</li>
</ul>
<h2 id="gaha">G.A.H.A</h2>
<blockquote>
<p>Google always has the answer.</p>
</blockquote>
<p><a href="https://chaarles.tistory.com/25">SK 브로드밴드가 쓰레기인 이유</a></p>
<ul>
<li>전부 다 그런것인지는 확인 할 수 없지만 SK 측 공유기 중 대다수가 port 22 를 막아둔다고 한다.</li>
<li>열려있는 포트에다가 인스턴스를 다시 생성하던지 하면 되지 않느냐라고 할 수 있는데, 지금은 그게 안되는 경우다 보니 다른 방법을 찾아야 했다.</li>
<li>블로그에 나와 있는데로 SK 측에 문의를 했더니, &#39;난 잘 모르겠고 기사님 보내줄게&#39; 하길래 OK 했다.</li>
</ul>
<h2 id="solution">Solution</h2>
<ul>
<li>결론적으로 기사님도 내가 EC2 어쩌고 port 22 저쩌고 했지만 잘 모르겠고 구형 공유기로 바꿔줄게라며 공유기를 냅다 교체 하셨다.</li>
<li>Wow.. 거짓말처럼 ssh 접속에 성공했다.</li>
<li>특정 년도에 만들어진 공유기만 그런건지, 내가 모든 종류의 공유기로 실험을 해 볼 순 없었기에 딱 이건 됩니다 라고 할 수는 없지만, 만약 SK 쓰시는 분들중에 EC2 접속이 안되시는 분들은 인터넷 회사를 교체하시던지 공유기를 교체해보시길 권장한다.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<ul>
<li>SK 쓰지 말자.</li>
<li>KT 쓰자.</li>
<li>애먼 보안그룹 디비쪼지 말자.</li>
<li>인터넷, 공유기 둘 다 현재 교체하기 어렵고 급하다 하시는 분들은 휴대폰 테더링으로 접속해보자. 블로그에도 쓰여 있지만 핫스팟으로 접속했더니 되더라 하는 글이 꽤 있었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Network] 쿠키, 세션 그리고 캐시]]></title>
            <link>https://velog.io/@_ted_0527_/Network-%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%BA%90%EC%8B%9C</link>
            <guid>https://velog.io/@_ted_0527_/Network-%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%BA%90%EC%8B%9C</guid>
            <pubDate>Mon, 14 Feb 2022 10:03:07 GMT</pubDate>
            <description><![CDATA[<p>출처: <a href="https://interconnection.tistory.com/74">https://interconnection.tistory.com/74</a> [Ryan Server]</p>
<h1 id="1-http의-특징과-쿠키-세션을-사용하는-이유">1. HTTP의 특징과 쿠키, 세션을 사용하는 이유</h1>
<p>쿠키를 발급받고 사용하는 과정
<img src="https://images.velog.io/images/_ted_0527_/post/0ef6d129-3da4-47f5-bb0b-69012dc21309/cookie,%20session.png" alt=""></p>
<p>기본적으로 HTTP 프로토콜 환경은 &quot;connectionless, stateless&quot;한 특성을 가지기 때문에 서버는 클라이언트가 누구인지 매번 확인해야하는데, 이 특성들을 보완하기 위해서 쿠키와 세션을 사용하게 됨</p>
<ul>
<li><p><strong>connectionless</strong>
클라이언트가 요청을 한 후 응답을 받으면 그 연결을 끊어 버리는 특징</p>
<p>HTTP는 먼저 클라이언트가 request를 서버에 보내면, 서버는 클라이언트에게 요청에 맞는 response를 보내고 접속을 끊는 특성이 있다.</p>
<p>헤더에 keep-alive라는 값을 줘서 커넥션을 재활용하는데 HTTP1.1에서는 이것이 디폴트다.</p>
<p>HTTP가 tcp위에서 구현되었기 때문에 (tcp는 연결지향, udp는 비연결지향) 네트워크 관점에서 keep-alive는 옵션으로 connectionless의 연결비용을 줄이는 것을 장점으로 비연결지향이라 한다.</p>
</li>
<li><p><strong>stateless</strong>
통신이 끝나면 상태를 유지하지 않는 특징</p>
<p>연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있다.</p>
</br>

</li>
</ul>
<p>예를 들어, 쿠키와 세션을 사용하지 않으면 쇼핑몰에서 물건을 구매하려고 로그인을 했음에도, 페이지를 이동할 때 마다 계속 로그인을 해야 한다.</p>
<p>쿠키와 세션을 사용했을 경우, 한 번 로그인을 하면 어떠한 방식에 의해서 그 사용자에 대한 인증을 유지하게 된다.</p>
<h1 id="2-쿠키--cookie-">2. 쿠키 ( Cookie )</h1>
<p>쿠키는 클라이언트(브라우저) 로컬에 저장되는 키와 값이 들어있는 작은 데이터 파일이다.
사용자 인증이 유효한 시간을 명시할 수 있으며, 유효 시간이 정해지면 브라우저가 종료되어도 인증이 유지된다는 특징이 있음.</p>
<p>쿠키는 클라이언트의 상태 정보를 로컬에 저장했다가 참조함.
클라이언트에 300개까지 쿠키저장 가능, 하나의 도메인당 20개의 값만 가질 수 있음, 하나의 쿠키값은 4KB까지 저장.</p>
<p>Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있다.
쿠키는 사용자가 따로 요청하지 않아도 브라우저가 Request시에 Request Header를 넣어서 자동으로 서버에 전송함.
</br></p>
<h3 id="쿠키의-구성-요소">쿠키의 구성 요소</h3>
<blockquote>
<p>이름 : 각각의 쿠키를 구별하는 데 사용되는 이름
값 : 쿠키의 이름과 관련된 값
유효시간 : 쿠키의 유지시간
도메인 : 쿠키를 전송할 도메인
경로 : 쿠키를 전송할 요청 경로</p>
</blockquote>
</br>

<h3 id="쿠키의-동작-방식">쿠키의 동작 방식</h3>
<blockquote>
<ol>
<li>클라이언트가 페이지를 요청</li>
<li>서버에서 쿠키를 생성</li>
<li>HTTP 헤더에 쿠키를 포함 시켜 응답</li>
<li>브라우저가 종료되어도 쿠키 만료 기간이 있다면 클라이언트에서 보관하고 있음</li>
<li>같은 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄</li>
<li>서버에서 쿠키를 읽어 이전 상태 정보를 변경 할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답</br>
</li>
</ol>
</blockquote>
<h3 id="쿠키의-사용-예">쿠키의 사용 예</h3>
<blockquote>
<ol>
<li>방문 사이트에서 로그인 시, &quot;아이디와 비밀번호를 저장하시겠습니까?&quot;</li>
<li>쇼핑몰의 장바구니 기능</li>
<li>자동로그인, 팝업에서 &quot;오늘 더 이상 이 창을 보지 않음&quot; 체크, 쇼핑몰의 장바구니</li>
</ol>
</blockquote>
</br>

<h1 id="3-세션--session-">3. 세션 ( Session )</h1>
<p>세션은 쿠키를 기반하고 있지만, 사용자 정보 파일을 브라우저에 저장하는 쿠키와 달리 세션은 서버 측에서 관리한다. 서버에서는 클라이언트를 구분하기 위해 세션 ID를 부여하며 웹 브라우저가 서버에 접속해서 브라우저를 종료할 때까지 인증 상태를 유지한다. 물론 접속 시간에 제한을 두어 일정 시간 응답이 없다면 정보가 유지되지 않게 설정이 가능하다.</p>
<p>사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 된다. 즉, 동접자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 되는 것.</p>
<p>클라이언트가 Request를 보내면, 해당 서버의 엔진이 클라이언트에게 유일한 ID를 부여하는 데 이것이 세션 ID 라고 한다.
</br></p>
<h3 id="세션의-동작-방식">세션의 동작 방식</h3>
<blockquote>
<p>클라이언트가 서버에 접속 시 세션 ID를 발급 받음
클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있음
클라리언트는 서버에 요청할 때, 이 쿠키의 세션 ID를 같이 서버에 전달해서 요청
서버는 세션 ID를 전달 받아서 별다른 작업없이 세션 ID로 세션에 있는 클라언트 정보를 가져와서 사용
클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답</p>
</blockquote>
</br>

<h3 id="세션의-특징">세션의 특징</h3>
<blockquote>
<p>각 클라이언트에게 고유 ID를 부여
세션 ID로 클라이언트를 구분해서 클라이언트의 요구에 맞는 서비스를 제공
보안 면에서 쿠키보다 우수
사용자가 많아질수록 서버 메모리를 많이 차지하게 됨</p>
</blockquote>
</br>

<h3 id="세션의-사용-예">세션의 사용 예</h3>
<blockquote>
<p>로그인 같이 보안상 중요한 작업을 수행할 때 사용</p>
</blockquote>
</br>

<h1 id="4-쿠키와-세션의-차이">4. 쿠키와 세션의 차이</h1>
<ul>
<li><p>사용자의 정보가 저장되는 위치 </p>
</li>
<li><blockquote>
<p>쿠키는 서버의 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용합니다.</p>
</blockquote>
</li>
<li><p>보안 면에서 세션이 더 우수</p>
</li>
<li><blockquote>
<p>쿠키는 클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있어서 보안에 취약</p>
</blockquote>
</li>
<li><blockquote>
<p>세션은 쿠키를 이용해서 session_id 만 저장하고 그것으로 구분해서 서버에서 처리하기 때문에 비교적 보안성이 좋음</p>
</blockquote>
</li>
<li><p>요청 속도는 쿠키가 세션보다 더 빠름</p>
</li>
<li><blockquote>
<p>세션은 서버의 처리가 필요하기 때문</p>
</blockquote>
</li>
<li><p>(중요)라이프 사이클</p>
</li>
<li><blockquote>
<p>쿠키도 만료시간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속해서 정보가 남아 있을 수 있다. 또한 만료기간을 넉넉하게 잡아두면 쿠키삭제를 할 때 까지 유지될 수도 있음</p>
</blockquote>
</li>
<li><blockquote>
<p>세션도 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제됨</p>
</blockquote>
</li>
<li><blockquote>
<p>예를 들어, 크롬에서 다른 탭을 사용해도 세션을 공유함.</p>
</blockquote>
</li>
<li><p>속도</p>
</li>
<li><blockquote>
<p>쿠키에 정보가 있기 때문에 서버에 요청시 속도가 빠름</p>
</blockquote>
</li>
<li><blockquote>
<p>세션은 정보가 서버에 있기 때문에 처리가 요구되어 비교적 속도가 느림</p>
</blockquote>
</li>
</ul>
</br>

<h1 id="5-세션과-쿠키-동시에-사용-하는-이유">5. 세션과 쿠키 동시에 사용 하는 이유?</h1>
<p>세션은 서버의 자원을 사용하기 때문에 무분별하게 만들다보면 서버의 메모리가 감당할 수 없어질 수가 있고 속도가 느려질 수 있기 때문에 쿠키가 유리한 경우가 있음.
</br></p>
<h1 id="6-쿠키세션--캐시">6. 쿠키&amp;세션 != 캐시</h1>
<p>캐시는 이미지나 css, js파일 등을 브라우저나 서버 앞 단에 저장해놓고 사용하는 것을 말한다.</p>
<p>한번 캐시에 저장되면 브라우저를 참고하기 때문에 서버에서 변경이 되어도 사용자는 변경되지 않게 보일 수 있는데, 이런 부분을 캐시를 지워주거나 서버에서 클라이언트로 응답을 보낼 때 header에 캐시 만료시간을 명시하는 방법 등을 이용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 함수형 프로그래밍]]></title>
            <link>https://velog.io/@_ted_0527_/CS-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@_ted_0527_/CS-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Fri, 11 Feb 2022 07:22:52 GMT</pubDate>
            <description><![CDATA[<h1 id="프로그래밍-패러다임">프로그래밍 패러다임</h1>
<ul>
<li><p>프로그래밍을 작성할 때의 관점 및 방법론</p>
</li>
<li><p>프로그래밍 언어별로 지원하는 프로그래밍 패러다임이 다르다.</p>
</li>
<li><p>하지만 최근 대부분의 프로그래밍 언어는 여러개의 패러다임을 갖는데, 이를 &quot;멀티 패러다임 언어&quot;라고 부른다.</p>
</li>
<li><p>파이썬은 세 가지 패러다임(함수형, 명령형, 객체지향 (클래스기반))을 지원하는 언어이다.</p>
</li>
</ul>
<p><a href="https://ko.wikipedia.org/wiki/%EB%8B%A4%EC%A4%91_%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D_%EC%96%B8%EC%96%B4#%EB%8B%A4%EC%A4%91_%ED%8C%A8%EB%9F%AC%EB%8B%A4%EC%9E%84_%EC%96%B8%EC%96%B4%EC%9D%98_%EC%98%88">다중 패러다임 프로그래밍 언어 - 위키백과</a>
<a href="https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS6330645435">멀티패러다임 언어를 사용해야하는 이유</a></p>
<blockquote>
<p>※ (참고)대표적인 프로그래밍 패러다임과 언어</p>
</blockquote>
<p>OOP(Object-Oriented Programming) - 객체지향(파이썬, 자바...)
IP(Imperative Programming) - 명령형(C, C++...)
DP(Declarative Programming) - 선언형(SQL...)
FP(Functional Programming) - 함수형(하스칼, 스칼라...)
RP(Reactive Programming) - 반응형(RxJava...)
※ RP 는 선언형 방식을 지향하고 함수형 언어의 도구들을 활용한다.
등등등</p>
<br>

<h1 id="함수형-프로그래밍이란">함수형 프로그래밍이란?</h1>
<p>함수형 프로그래밍은 하나의 프로그래밍 패러다임으로 정의되는 일련의 코딩 접근 방식이며, 자료처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임을 의미한다. <a href="https://jongminfire.dev/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80">함수형 프로그래밍이란?</a>
함수형 프로그래밍은 프로그래밍 언어나 방식을 배우는것이 아니라 함수로 프로그래밍하는 사고를 배우는것이라고 할 수 있다. 즉, 새로운 계산방법을 배우는 것처럼 사고의 전환을 필요로 한다. 다양한 사고방식으로 프로그래밍을 바라보면 더욱 유연한 문제해결이 가능해진다.</p>
<br>

<h1 id="함수형-프로그래밍의-특징">함수형 프로그래밍의 특징</h1>
<br>
1. 순수함수 (Pure function)

<ul>
<li>동일한 입력에는 항상 같은 값을 반환해야 하는 함수</li>
<li>함수의 실행이 프로그램의 실행에 영향을 미치지 않아야 하는 함수</li>
<li>함수 내부에서 인자의 값을 변경하거나 프로그램 상태를 변경하는 Side Effect가 없는 것</li>
</ul>
<br>
2. 비상태, 불변성 (Stateless, Immutability)

<ul>
<li>함수형 프로그래밍에서의 데이터는 변하지 않는 불변성을 유지해야 한다.</li>
<li>데이터의 변경이 필요한 경우, 원본 데이터 구조를 변경하지 않고 그 데이터의 복사본을 만들어서 그 일부를 변경하고, 변경한 복사본을 사용해 작업을 진행한다.</li>
</ul>
<br>
3. 선언형 함수(Expressions)(feat. 선언형 프로그래밍)

<ul>
<li>명령형 프로그래밍은 무엇을 어떻게 할 것인가에 주목하고, 선언형 프로그래밍은 무엇을 할 것인가에 주목한다.</li>
</ul>
<p>※ 명령형과 선언형의 프로그래밍 비교</p>
<ul>
<li>명령형: 알고리즘을 명시하고 목표는 명시 안함.</li>
<li>선언형: 알고리즘 명시하지 않고 목표만 명시.</li>
</ul>
<blockquote>
</blockquote>
<p>Q. 여기가 선릉역인데 위코드까지 어떻게 가야할까요?</p>
<blockquote>
</blockquote>
<p>(명령형)A. 10번 출구로 나와서 직진하세요. 세 번째 코너에서 스타벅스를 끼고 좌회전 하세요. 첫 번째 골목길에서 우회전 하세요. 다음 신호등을 지나 직진하세요.</p>
<blockquote>
</blockquote>
<p>(선언형)A. 위코드 주소는 강남구 테헤란로 427 입니다.
※ 어떻게(HOW)에 대한 것은 추상화 하고 무엇을(WHAT)에 집중하는 방식</p>
<blockquote>
<p><a href="https://codechaser.tistory.com/81">선언형 프로그래밍이란?</a></p>
</blockquote>
<br>
4. 1급 객체 및 고차함수(각 개념은 추가 설명 참조)

<ul>
<li>함수형 프로그래밍에서는 함수가 1급 객체가 된다.</li>
<li>고차 함수는 함수를 인자로 전달 받거나, 함수를 반환 하는 함수를 의미한다.</li>
<li>함수의 반환 값으로 다른 함수를 사용하거나, 함수의 반환 값으로 또 다른 함수를 사용 할 수 있어야 한다.</li>
</ul>
<br>

<h1 id="함수형-프로그래밍의-장단점">함수형 프로그래밍의 장단점</h1>
<ul>
<li><p>장점</p>
<ul>
<li>높은 수준의 추상화를 제공한다.</li>
<li>함수 단위의 코드 재사용이 수월하다.</li>
<li>불변성을 지향하기 때문에 프로그램의 동작을 예측하기 쉬워진다.</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>순수함수를 구현하기 위해서는 코드의 가독성이 좋지 않을 수 있다.</li>
<li>함수형 프로그래밍에서는 반복이 for문이 아닌 재귀를 통해 이루어지는데 (deep copy), 재귀적 코드 스타일은 무한 루프에 빠질 수 있다.</li>
<li>순수함수를 사용하는 것은 쉬울 수 있지만 조합하는 것은 쉽지 않다.</li>
</ul>
</li>
</ul>
<br>

<h1 id="ref">Ref.</h1>
<p><a href="https://velog.io/@keywookim/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%ED%95%A8%EC%88%98%ED%98%95%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%A7%9C%EA%B8%B0">파이썬으로 함수형프로그래밍 하기</a>
<a href="https://www.youtube.com/watch?v=UPmQHHpS3cw&amp;ab_channel=PyConKorea">파이썬에서 함수형으로 프로그래밍하기(영상) - 하원호</a></p>
<hr>
<h2 id="고차-함수">고차 함수</h2>
<ul>
<li>고차 함수는 람다 계산법에서 만들어진 용어로 불변성(Immutability)을 지향하는 함수형 프로그래밍에 기반을 두고 있다.</li>
<li>대표 언어는 Javascript, React<blockquote>
<p>고차 함수는 1급 함수의 부분 집합(Subset)이다.
리액트의 고차 컴포넌트(HOC)는 컴포넌트를 사용하여 위의 조건을 만족하는 컴포넌트를 말한다.</p>
</blockquote>
</li>
</ul>
<ol>
<li>함수를 인자로써 전달 할 수 있어야 한다.</li>
<li>함수의 반환 값으로 또 다른 함수를 사용 할 수 있다.</li>
</ol>
<br>

<h2 id="1급-객체1급-함수란">1급 객체(1급 함수)란?</h2>
<ul>
<li>1급 객체는 아래의 조건을 충족하는 객체를 말한다.</li>
</ul>
<ol>
<li>변수나 데이터에 할당 할 수 있어야 한다.</li>
<li>객체의 인자로 넘길 수 있어야 한다.</li>
<li>객체의 반환값으로 반환할 수 있어야 한다.</li>
<li>할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.</li>
<li>동적으로 프로퍼티 할당이 가능하다.<pre><code class="language-python"># 변수나 데이터에 할당 할 수 있어야 한다.
def print_hello():
 print(&quot;hello!&quot;)
</code></pre>
</li>
</ol>
<p>a = print_hello  # print_hello 함수를 a라는 변수에 저장
a()</p>
<h1 id="hello">hello!</h1>
<pre><code>```python
# 객체의 인자로 넘길 수 있어야 한다.
def introduce(name):
    return &quot;hello, my name is&quot; + name

def goodbye(name):
    return &quot;bye bye, &quot; + name


def who_i_am(func, name):
    return func(name)

who_i_am(introduce, &quot;hanbin&quot;)
# hello, my name is hanbin
who_i_am(goodbye, &quot;hanbin&quot;)
# bye bye, hanbin</code></pre><pre><code class="language-python"># 객체의 반환값으로 반환할 수 있어야 한다.
def test_hasher(data):
    return &quot;hashed&quot; + data

def get_hasher():
    return test_hasher  # test_hasher함수를 반환한다.

hasher = get_hasher()
hashed_data = hasher(&quot;hanbin&quot;)
print(hashed_data)
# hashed hanbin</code></pre>
<br>

<h2 id="함수의-추상화--추상화-수준">함수의 추상화 &amp; 추상화 수준</h2>
<p><a href="https://lordofkangs.tistory.com/m/127">클린코드 - 추상화와 추상화 수준</a></p>
<ul>
<li>하나의 기능을 하나의 함수로 만드는 작업을 &#39;추상화&#39;라 부른다. </li>
<li>로버트 마틴은 하나의 함수는 한 가지 추상화를 담으므로 한 번의 들여쓰기로 표현하기를 추천한다.</li>
</ul>
<pre><code class="language-python">@Override
public void mainButtonAction() throws IOException  {
    if(!isEmptyUserName()) doJoinProcess(duplicateCheck());
    else setUserNameCheck(&quot;닉네임이 공백입니다.&quot;);    
}</code></pre>
<p>위 코드는 함수로만 코드가 구성되어있다. 추상화 수준이 높다고 할 수 있다.</p>
<pre><code class="language-python">// 관심사 : Peer의 주소생성
private String getLocalhost() {
    return &quot;localhost:&quot;+ (5500 + (int)(Math.random()*100));
}</code></pre>
<p>위 코드는 &#39;+&#39; 같은 연산기호나 (int) 형변환이나 Math.random() 같은 API 사용을 담고 있다. 구체적인 세부 구현사항을 담은 것이다. 이런 함수는 추상화 수준이 낮다고 말할 수있다.  </p>
<p>mainButtonAction() 함수는 추상화 수준이 높다. 반면 getLocalhost() 함수는 추상화 수준이 낮다. </p>
<p>이렇듯, 하나의 함수는 하나의 추상화 수준을 가져야 한다.</p>
<p>하나의 함수 안에 높은 추상화 수준과 낮은 추상화 수준이 공존해서는 안 된다. 추상화 수준이 다른 코드가 같은 함수 안에 사용되면 혼란스러워진다. </p>
<p>또한, 추상화 수준이 높은 함수는 위에, 추상화 수준이 낮은 함수는 아래에 작성해야 한다. 이를 <strong>내려가기 규칙</strong>이라 부른다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQL] SQL 의 View]]></title>
            <link>https://velog.io/@_ted_0527_/SQL-SQL-%EC%9D%98-View</link>
            <guid>https://velog.io/@_ted_0527_/SQL-SQL-%EC%9D%98-View</guid>
            <pubDate>Tue, 08 Feb 2022 08:48:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>####</p>
</blockquote>
<h2 id="1-view">1. View</h2>
<ul>
<li>FROM 구에 기술된 서브쿼리에 이름을 붙이고 DB를 객체화하여 쓰기 쉽게 한 것</li>
<li>데이터베이스 객체란 테이블이나 인덱스 등 DB안에 정의하는 모든 것</li>
<li>View 도 데이터베이스 객체 중 하나이나, SELECT문은 객체가 아님</li>
<li><blockquote>
<p>이름을 지정할 수도 없고 DB에 등록되지도 않기 때문</p>
</blockquote>
</li>
<li>즉, View 는 SELECT 명령을 기록하는 데이터베이스 객체</li>
</ul>
<p><code>SELECT * FROM (SELECT * FROM sample01) sq;</code> | 서브쿼리를 뷰 객체로 만들면,
<code>SELECT * FROM sample_view_01;</code> | SELECT 문이 &quot;view_01&quot; 이라는 객체로 명명됨
-&gt; 뷰를 작성하는 것으로 복잡한 SELECT 명령을 간략하게 표현할 수 있다.
-&gt; WHERE, GROUP BY 등과 같이 복잡한 명령에서도 사용 가능</p>
<ul>
<li>뷰는 실체가 존재하지 않는 테이블이라는 의미로 &#39;가상 테이블&#39;이라 불림</li>
<li>저장공간을 가지지않기 때문에 &#39;SELECT&#39;문 에서만 사용하는 것을 권장함
</br></br></li>
</ul>
<h2 id="2-view-의-생성과-삭제">2. View 의 생성과 삭제</h2>
<ul>
<li>CREATE VIEW (viewname) AS SELECT (추가 명령문)   | AS 생략 불가능(별명이 아니기 때문)</li>
<li>DROP VIEW (viewname)
</br></br></li>
</ul>
<h2 id="3-view-의-약점과-보완책">3. View 의 약점과 보완책</h2>
<p>1) 저장공간을 필요로 하지 않는 대신 CPU 자원을 사용함
2) 보관하는 데이터양이 많은 경우, 집계처리를 할 때도 뷰가 사용된다면 처리속도가 많이 저하됨
3) 뷰를 구성하는 SELECT 명령은 단독으로도 실행할 수 있어야 하지만, 부모 쿼리와 어떤 식으로든 연관된 서브쿼리의 경우에는 뷰의 SELECT 명령으로 사용할 수 없음</p>
<h4 id="materialized-view">Materialized View</h4>
<ul>
<li>1, 2번 약점을 회피하기 위해 사용할 수 있는 뷰(But, Only use Oracle &amp; DB2)</li>
<li>데이터를 일시적으로 저장해 사용하는 것이 아닌, 테이블처럼 저장장치에 저장해두고 사용함</li>
<li>처음 참조되었을 때 데이터를 저장해두고 이후 다시 참조할 때 그대로 사용하는 방식</li>
<li>다만, 데이터가 변경된 경우 SELECT 명령을 재실행하여 데이터를 다시 저장함</br>
#### 함수 테이블</li>
<li>3번째 약점을 회피하기 위해 함수 테이블을 사용함</li>
<li>함수 테이블은 테이블을 결괏값으로 반환해주는 사용자정의 함수</li>
<li>함수에는 인수를 지정할 수 있기 때문에 인수의 값에 따라 WHERE조건을 붙여 결괏값을 바꿀 수 있으므로 상관 서브쿼리처럼 동작할 수 있게 됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 25- (End)]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-25-End</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-25-End</guid>
            <pubDate>Thu, 20 Jan 2022 00:14:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="이번-포스팅에는-코드가-없습니다">이번 포스팅에는 코드가 없습니다.</h4>
</blockquote>
<h1 id="마지막-날">마지막 날</h1>
<h4 id="d--대단원의-막을-내리는-날이다-그리고">D : 대단원의 막을 내리는 날이다. 그리고</h4>
<h4 id="r--retrospect회고를-해야-진짜-끝난-기분이-든다">R : Retrospect(회고)를 해야 진짜 끝난 기분이 든다.</h4>
<h4 id="f--피날레는-언제나-음주가-최고다">F : 피날레는 언제나 음주가 최고다(?!)</h4>
<p>기업협업에서 가장 좋았고, 감사했던 부분은 직원분들이 다들 너무나 친절하고 우리를 존중해주셨다는 점이다.
DRF에 대해 기본부터 다질 수 있도록 사수님이 유데미 계정을 빌려주시기도 하고,
장고로 간단한 기능을 구현하게 한 뒤, DRF로 리팩토링 해보는 식으로 접근하며 생소한 지식을 쉽게 적응시켜주셨다. 화려한 고급 기술이나 메소드가 난무하는 프로젝트를 하진 않았지만,
진짜 우리 수준에 맞게 기초 기능부터 구현해볼수있도록 회사 프로젝트도 조율해 주셨다.
덕분에 각자가 API 2, 3개정도를 DRF로 구현할 수 있었고, 회사의 깃헙에 남겨두고 올 수 있었다.
프론트 역시 사수님이 혼자셨음에도 불구하고 매번 &#39;더 자세히 못봐드려서 죄송하다&#39;,
&#39;이런 이런 쪽으로 더 공부해보시면 도움이 될거다&#39;, &#39;나는 이렇게 했었다, 저렇게 했었다&#39; 하시면서
아낌없이 경험과 지식을 말씀해주셨다고 한다. 세상 어느 자료나 책으로도 만날 수 없는 개인의
경험을 공유한다는 것은 실력위주의 직업군에서 너무나 귀중한 것임을 잘 알고 있기에, 이 분들이
얼마나 진심으로 대해 주시는지 잘 느낄 수 있었다.</p>
<p>그 가운데, 우리가 맡은 파트는 관리자 페이지의 기능들 이었다.
기본적으로 CRUD를 전부 사용 해야하는 페이지였고, 전혀 개발이 되어있지 않은 
무주공산 이었기에, 오히려 작업하기에는 편한 환경이었다. 때문에 merge 할 때 컨플릭트에 대한
걱정도 적었고, git을 다룰 때는 사수님이 1:1 맨투맨으로 붙어서 지도해주셔서 정말 아무런 문제없이 진행되었다. </p>
<p>가장 기억에 남는 경험 중 하나는, 우리가 직접 회사 프로젝트의 전체 회의에서 의견을 
자유롭게 나누고 피드백 받았던 것이다. 실제로 마케팅팀에서 보낸 피드백을 같이 공유하면서
해결책을 찾아가는 그 모습이 말로만 듣던 개발자들의 회의 답다고 생각했다.</p>
<p>사수님은 퇴근 후 저녁에도, 주말에도 항상 질문하면 빠르게 답변해 주시고, 
협업이 끝난 후에도 질문있으면 해도 된다시면서 먼저 연락처 알려주시는 등, 칭찬하자면
하룻밤으로는 모자랄 듯 하기에, 진심을 담아 이 자리를 빌어 다시 한번 감사하다는 말을 전하고 싶다.</p>
</br>


<p>기술 스택이 어마무시 하다거나, 경력이 화려한 사수, 선배 혹은 동료 개발자도 좋지만,
사람으로부터 시작해서 사람으로 끝이나는 개발 문화에서 가장 중요하고 우선시 되어야 할
덕목은 &#39;마음부터 따르게 만드는 사람&#39; 이어야 하는게 아닐까.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 24-
]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-24-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-24-</guid>
            <pubDate>Wed, 19 Jan 2022 08:47:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>####</p>
</blockquote>
<h1 id="d">D</h1>
<p>Developer 로서의 첫 기업이 끝나갈 무렵, 사수님이 넌지시 과제를 하나 주셨는데,
&#39;이번에 포인트 기능을 추가로 구현 할 생각인데, 모델링부터 로직까지 한번 생각해보라&#39;
하시면서 참고할 자료로 &#39;우아한 형제들 기술 블로그&#39;를 추천하셨다.</p>
<p><a href="https://techblog.woowahan.com/2587/">우아한 형제들 기술 블로그 : 신규 포인트 시스템 전환기</a></p>
<p>기술스택부터 화려해 보였다. Java, Spring, AWS Redis, SQS, Jenkins 등..
초보, 비전공자가 시작하기에 어려운 기술 목록에 있던 기술들만 있는 것 같았다.
처음부터 끝까지 찬찬히 읽어 보았지만, 역시나 무슨 소리를 하시는 건지 모르겠더라.
그나마 눈에 들어온 몇 가지 문구만 기록해두고 넘어가겠다.</p>
<ul>
<li>이번 포인트 시스템의 아키텍처 기조는 DB가 죽어도 문제 없는 서비스였습니다.</li>
<li>테스트 코드를 굉장히 빡빡하게 작성하였습니다. (말만 들어도 겁남)</li>
<li>특히 HTTP API 테스트를 코드로 남긴다는 점이 중요합니다. Postman을 선택할 이유가 하나도 없었습니다.<ul>
<li><a href="https://jojoldu.tistory.com/266">Postman 대신 IntelliJ</a></li>
</ul>
</li>
<li>API 문서 자동화 : 수동으로 작성하는 것은 언젠간 코드와 문서간에 간격이 발생</li>
<li>테스트 코드를 작성하면 문서로 자동 완성(Spring Rest Docs 선택)</li>
<li>단일 브랜치 전략을 선택했습니다. master 브랜치만 유지하고 모든 커밋을 master에만 하였습니다.</li>
</ul>
<h1 id="r">R</h1>
<p>Roundly(솔직히), 중요한 문구들은 더 많았지만, 기술적인 단어들이 망라되다 보니 
읽으면서도 고개를 젓게 되었다. 아무튼, 간단하게라도 코드가 치고 싶어져서 바로 vscode 를 켰다.</p>
<pre><code class="language-python">models.py
class User(models.Model):
    name = models.CharField(max_length=50)

class Point(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    point = models.BigIntegerField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class PointInfo(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    point_log = models.CharField(max_length=50)
    status = models.CharField(max_length=50)
    description = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)</code></pre>
<p>초간단 모델 3가지와,</p>
<pre><code class="language-python">class UserViewSet(ModelViewSet):
    permission_classes = [AllowAny]
    serializer_class = UserSerializer
    queryset = User.objects.all()

class PointViewSet(ModelViewSet):
    permission_classes = [AllowAny]
    serializer_class = PointSerializer
    queryset = Point.objects.all()

class PointInfoViewSet(ModelViewSet):
    permission_classes = [AllowAny]
    serializer_class = PointInfoSerializer
    queryset = PointInfo.objects.all()</code></pre>
<p>초초간단 ModelViewSet,</p>
<pre><code class="language-python">class UserSerializer(serializers.ModelSerializer):
    point = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = &#39;__all__&#39;

    def get_point(self, obj):
        return Point.objects.filter(user_id=obj).values(&#39;point&#39;)

class PointSerializer(serializers.ModelSerializer):

    class Meta:
        model = Point
        fields = &#39;__all__&#39;

class PointInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = PointInfo
        fields = &#39;__all__&#39;</code></pre>
<p>기본 그 자체의 serializer 까지 만드는데에 20분도 안걸렸지 싶다. 거의 모든 것이 고려되지 않고
CRUD의 기능만 가진 베이스 코드이기에 가능한 부분이다. 이제 여기에 살을 좀 붙여볼 차례.
User 를 조회하면 해당 User가 가진 Point 도 같이 나와야 한다.</p>
<pre><code class="language-python">    point = serializers.SerializerMethodField()

    def get_point(self, obj):
        return Point.objects.filter(user_id=obj).values(&#39;point&#39;)</code></pre>
<p>User Serializer 에 이 로직을 추가하면 된다. 잊지말고 url 도 미리 추가해 주자.</p>
<pre><code class="language-python">router = DefaultRouter()
router.register(&#39;users&#39;, UserViewSet, basename=&#39;users&#39;)
router.register(&#39;points&#39;, PointViewSet, basename=&#39;points&#39;)
router.register(&#39;pointinfo&#39;, PointInfoViewSet, basename=&#39;pointinfo&#39;)</code></pre>
<h1 id="f">F</h1>
<p>Factorials(계승, 상속)의 개념이 난무하는 DRF에서 추가 기능을 구현하고 싶다면 
view or serializer 중 한 곳에 로직을 추가 하면 된다. 혹은 Service Layer를 구성해서
따로 관리하는 것도 가능하다.
<a href="https://jay-ji.tistory.com/74">참고 블로그</a>
serializer 는 validation 이 목적인 클래스이기때문에 일반적으로 비지니스 로직과 혼합되어 있는 것은
그리 보기 좋은 코드가 아니라고 할 수 있겠다. 그렇지만 view 에만 로직을 몰빵한다면 view가
지저분해지고 유지보수에도 불편하다는 단점이 존재한다. &#39;사람이 보기 편하고 누구나 보아도 편한 코드&#39;를
지향하는 것이 내가 바라는 개발자의 덕목 중 하나인만큼, 어느 방법을 쓸 것인지는 앞으로 많은 경험을 통해
체득 해 나갈 것이다.</p>
<p>다음으로 생각해볼 기능은 포인트의 &#39;유효기간 에 대한 로직&#39;이다.</p>
<p><a href="https://github.com/Ted0527/DjangoRestFramework/tree/main/MovieListAPI-Django/watchlist_app/api">Point 기능 구현 repo</a></p>
<p><strong>기업 협업에 대한 포스팅은 다음 편이 마지막입니다. 추가로 구현 될 기능들은</strong>
<strong>github에 올려두고 지속적으로 업데이트 할 예정입니다. 감사합니다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 23-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-23-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-23-</guid>
            <pubDate>Wed, 19 Jan 2022 00:53:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>####</p>
</blockquote>
<h1 id="d">D</h1>
<p>D-2.
한 달이었던 기업협업도 벌써 막바지다. 개발자의 삶이 원래 이렇게 빠르게 지나가는 것인지, 
아니면 나이를 먹었기 때문에 더 빠르게 지나가는 것처럼 느끼는 것인지 잘은 모르겠지만,
무의미하게 흘려보낸 시간들은 아니었기에 마음이 무겁지만은 않다. 남은 인생을 걸었던 
큰 꿈이 좌절되고 그저 흘려보내기만 한 그 몇 개월에 비하면 지금의 나는 얼마나 값진
시간들을 쌓고 있는지. 오늘도 &#39;살아 있음&#39;이 아닌 &#39;살고 있음&#39;을 쌓는다.</p>
<h1 id="r">R</h1>
<p>Remote. github의 Repository 와 연결하는 통로를 설정한다. 통로명은 내 마음대로 정할 수 있지만,
통념상 origin 과 upstream 으로 많이들 사용하고 있다고 한다.
github 에서 fork를 떠오게 되면 내 repo 는 더 이상 최신화 되지 않는다. 이때 remote path 를
잘 지정해두면 명령문 몇 줄로 repo 를 최신화 할 수 있다.</p>
<p><a href="https://dev200ok.blogspot.com/2020/09/git-git-upstream-origin.html">LEEO 님의 블로그</a>
<a href="https://pers0n4.io/github-remote-repository-and-upstream/">Hack It 님의 블로그</a></p>
<p>remote 등록 명령어
<code>git remote add origin (repo http or ssh)</code>
<code>git remote add upstream (repo http or ssh)</code>
<code>git remote add (이름 아무거나 가능) (repo http or ssh)</code></p>
<p>등록 된 remote path 확인
<code>git remote -v</code></p>
<p>등록 된 remote 삭제
<code>git remote remove (삭제 하려는 remote 명)</code>
ex&gt; <code>git remote remove origin</code></p>
<p><a href="https://ifuwanna.tistory.com/263">git remote 명령어 모음</a></p>
<h1 id="f">F</h1>
<p>Front 분과 오전에 최종적으로 통신을 해서 작업한 모든 API가 문제 없이 작동하는 것을 확인하고,
push &amp; merge 까지 끝마쳤다. 오후부터는 특별히 할당된 과제가 없어서 블로그를 다듬고 
이력서를 준비하고 있었는데, 사수님이 깃헙을 잘 꾸며보라고 귀띔해주셨다.</p>
<p>다양한 테마, 기능들로 깃헙을 꾸밀 수 있는데, 우선은 프로필 dashboard 에 바로 보여지는
&#39;profile readme&#39; 를 먼저 만들었다. 이제부터는 Readme 를 계속해서 입맛에 따라 수정하면 된다.</p>
<p>참고한 블로그 &gt; <a href="http://blog.cowkite.com/blog/2102241544/">소연님의 블로그</a></p>
<h1 id="teds-github">Ted&#39;s Github</h1>
<ul>
<li><p>기술 스택 뱃지</p>
<ul>
<li><a href="https://shields.io/">뱃지 생성 사이트</a></li>
<li><a href="https://simpleicons.org/?q=python">뱃지 로고 사이트</a></li>
</ul>
</li>
<li><p>방문자 수 카운팅 뱃지</p>
<ul>
<li><a href="https://hits.seeyoufarm.com/">hit 뱃지 생성 사이트</a></li>
</ul>
</li>
<li><p>github stats 박스</p>
<ul>
<li><a href="https://github.com/anuraghazra/github-readme-stats">stats box repo</a></li>
<li><a href="https://github.com/anuraghazra/github-readme-stats/blob/master/themes/README.md">stats box all themes</a><br>

</li>
</ul>
</li>
</ul>
<p>현재 나의 github profile</p>
<p><a href="https://github.com/Ted0527">Ted&#39;s Github</a>
<img src="https://images.velog.io/images/_ted_0527_/post/986417b2-9958-4ea8-9c26-fe899ab5927a/github1.png" alt="">
<img src="https://images.velog.io/images/_ted_0527_/post/9ec6d7d9-42b6-497e-a4a3-d60a11ff4617/github2.png" alt=""></p>
<p>역시, 스킨은 진리다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 22-
]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-22-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-22-</guid>
            <pubDate>Tue, 18 Jan 2022 00:34:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id=""></h4>
</blockquote>
<h1 id="d">D</h1>
<p>돌파구가 보이지 않는 테스트 코드 정복은 목적지를 수정하여 우선적으로 status_code 와 Object 단위에 대한 테스트만 먼저 완성하기로 했다. 다시 말해, 실제 출력되는 값이 아닌, <code>.count()</code> 메소드를 사용해서 생성이 되는지만 테스트 하기로 한 것이다. 테스트를 해야하는 API는 즐비했고 사수님 포함 우리가 너무 많은 시간을 투자했기 때문에 이 이상은 낭비 할 수 없었다.</p>
<p>먼저 깔끔하게 Git 정리를 하고 최신 버전으로 pull 을 받은 후 각자가 구현한 다른 API로 넘어가서 테스트 코드를 적용했다.
이때 push &amp; pull 하는 과정에서 <code>remote origin &amp; upstream</code> 에 대한 궁금증이 생겨서 알아보았었다. 이에 관한 것은 다음 포스팅에서 다루기로 한다. 
아래는 완성된 테스트 코드 전문이다.</p>
<pre><code class="language-python">class SchoolGradesTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;, is_staff=True)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)
        self.serializer = GradesSerializer
        school = School.objects.create(school_name=&#39;test&#39;)
        Grade.objects.create(school_id=school.id, grade=&quot;1등급&quot;)

    def test_schoolgrade_viewset(self):
        data = {
            &quot;school_id&quot;: 1,
            &quot;grade&quot; : &quot;1등급&quot;
            &quot;others&quot;: &quot;texttexttext&quot;,
            &quot;school_info&quot;: [
                {
                    &quot;school_name&quot;: &quot;test&quot;,
                }
            ]
        }

        patch_data = {
            &quot;grade_id&quot; : 1,
            &quot;grade&quot; : &quot;2등급&quot;
        }

        response = self.client.get(&#39;/grade/&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(Grade.objects.count(), 1)
        self.assertEqual(Grade.objects.get().school_name, &#39;test&#39;)

        response = self.client.patch(&#39;/grade/1/&#39;, patch_data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.client.delete(&#39;/grade/1/&#39;, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)</code></pre>
<h1 id="r">R</h1>
<p><code>response</code> 부분을 살펴보면, (&#39;url&#39;, &#39;(입력 데이터)&#39;, &#39;(데이터의 형식)&#39;) 과 같은 형태로 되어있다. 그리고 테스트에 사용하려는 요청 메소드(get, post, patch, delete)에 따라 이 형태도 바뀔 수 있다. <code>get</code>의 경우에는 <code>body</code>가 없고, <code>list</code> 의 형태로 데이터를 요청하고 출력하기 때문에 입력 데이터와 데이터의 형식을 넣지 않아도 된다. 여담으로 <code>format</code> 같은 경우는 <code>settings.py</code> 에서 default 값을 지정해두고 사용하는 것도 가능하다. 물론, setting 을 건드릴때는 항상 동료들과 상의 후 결정해야하는 것도 잊지 말아야 한다.</p>
<pre><code class="language-python">response = self.client.get(&#39;/grade/&#39;)
※ 전체 리스트를 불러오는 get 요청

response = self.client.patch(&#39;/grade/1/&#39;, patch_data, format=&#39;json&#39;)
※ patch_data 를 받아서 값을 수정하는 patch 요청</code></pre>
<h1 id="f">F</h1>
<p><code>format</code> 의 종류에는 <code>multipart</code> 와 <code>json</code> 두 가지를 Django 에서 기본적으로 지원한다. 특히나 <code>factory</code> 를 사용할 경우, <code>multipart</code> 형식이 디폴트 값이기 때문에 <code>json</code> 으로 작업하고자 한다면 따로 지정해주어야 한다.</p>
<p>이전 테스트와 이번 테스트간의 다른점은 정참조하는 테이블에서 데이터를 추가로 가져오는 로직이 포함된 API를 테스트 하는 것이다. Grade 라는 테이블은 School 을 정참조 하고 있고 Serializer 에서 school_name 을 추가로 가져오는 로직이 구현되어 있다.</p>
<pre><code class="language-python">school = School.objects.create(school_name=&#39;test&#39;)
Grade.objects.create(school_id=school.id, grade=&quot;1등급&quot;)</code></pre>
<p>setUp 에서 Grade 테이블의 FK 값에 해당하는 school_id 가 필요하게 때문에 School의 Object 를 먼저 생성해주고, Grade Object 를 생성해준다.</p>
<pre><code class="language-python">self.assertEqual(Grade.objects.get().school_name, &#39;test&#39;)</code></pre>
<p>추가로 포함된 school_name 을 제대로 가져왔다면 school_name 으로 필터링을 해도 Grade 가 출력되어야 하는 테스트를 진행한다.</p>
<p>여기까지 테스트 코드를 마무리하고 모든 코드를 싹 정리한 뒤 PR을 올렸다.</p>
<h1 id="til">TIL</h1>
<h4 id="format--multipart--json">format = multipart &amp; json</h4>
<ul>
<li>multipart
: 웹 클라이언트가 요청을 보낼 때, http 프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것. 웹 클라이언트가 서버에게 파일을 업로드할 때, http 프로토콜의 바디 부분에 파일정보를 담아서 전송을 하는데, 파일을 한번에 여러개 전송을 하면 body 부분에 파일이 여러개의 부분으로 연결되어 전송된다. 이렇게 여러 부분으로 나뉘어서 전송되는 것을 Multipart data라고 한다. 보통 파일을 전송할 때 사용한다.
<a href="https://codingnotes.tistory.com/73">출처 : 코딩수첩 블로그</a><br>

</li>
</ul>
<h4 id="multipart-vs-json">multipart vs json</h4>
<ul>
<li>application/json<ul>
<li>장점 : 파일과 함께 전달된 데이터의 연관 관계를 표현하기 용이하다.</li>
<li>단점<ul>
<li>인코딩 방식의 특성으로 인해 바이너리 데이터보다 33-37 % 많은 용량을 차지한다.
(base64 인코딩 방식이 인코딩 시 바이트 당 8 비트를 차지하는 바이너리 방식과 달리,
6 비트를 차지하여 이론상 8/6 = 1.33, 약 33 % 많은 용량을 차지하지만 실제로는 
패딩 등의 이유로 3~4 % 더 차지함)</li>
<li>서버는 디코딩, 클라이언트는 인코딩을 위한 프로세싱 오버헤드가 부가된다.</li>
<li>인코딩한 파일 컨텐츠의 문자열이 길어져 서버측에서 파일을 읽지 못하는 경우가 발생할 수 있다.<br></li>
</ul>
</li>
</ul>
</li>
<li>multipart/form-data<ul>
<li>장점 : 파일 업로드 시 application/json 방식에서 발생하는 단점을 감수하지 않을 수 있다.</li>
<li>단점 : 파일과 함께 전달된 데이터의 연관 관계 표현이 제한된다.</li>
</ul>
</li>
</ul>
<p><a href="https://velog.io/@ryan-son/multipartform-data-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-vs-applicationjson">출처 : &lt;Ryan (Geonhee) Son&gt; 님의 벨로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 20~21-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-2021-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-2021-</guid>
            <pubDate>Mon, 17 Jan 2022 00:49:10 GMT</pubDate>
            <description><![CDATA[<h4 id="이번-포스팅의-코드들은-성공하지-못한-코드들임을-미리-밝힙니다">!이번 포스팅의 코드들은 성공하지 못한 코드들임을 미리 밝힙니다!</h4>
<blockquote>
<p>####</p>
</blockquote>
<h1 id="d">D</h1>
<p>Dictionary 의 다른 모습인 OrderedDict에 잡혀서 금요일에 이어 주말에도 문제 해결을 위한 노력은 계속 됐다.
완성된 샘플의 모양새에 맞추려고만 하다보니 오히려 생각이 갇혀버려서 마치 눈 앞의 벽만 보고서 이 길은 막혀있다고 단정짓는 모양새가 되어 버린듯 했다. 해서, 다른 방법들로 시도해보기로 마음을 바꿨다. 이번에 시도해볼 것은 <code>APIRequestFactory</code> 이다.</p>
<ul>
<li>APIRequestFactory(정리글 펌)
개발자 차원에서의 테스트. 단위 테스트 뷰를 함수단위로 테스트 하고 싶을 때 사용 한다. 장고는 웹 애플리케이션이 켜져 있고 사용자가 접속하기를 기다렸다가 요청에 대한 응답을 돌려 준다. 그래서 웹 서버만 켜져 있고 장고는 해당 요청에 대한 아웃풋만 주는 것. 웹 애플리케이션 이라는 것은 어떤 인풋이 들어왔을 때 아웃풋을 보내는 함수에 불과하다. APIRequestFactory는 뷰를 하나의 함수로 놓고 함수에 대해서 테스트를 할 수 있게 한다. API단위 자체를 함수로 구분 하는 것.
request객체를 만들어 view(request)에 던지면 response 가 오는 형태인데, 이 view는 함수의 형태로 request를 받게 작성 되어 있기 때문에 해당 함수를 테스트 할 수 있게 된다. 그래서 하나 하나의 단위로 테스트 하고 싶을 경우 APIRequestFactory를 사용한다.
<a href="https://himanmengit.github.io/restful/2018/03/26/Restful-framework-Tesing.html">출처: Django Restful framework Testing</a></li>
</ul>
<h1 id="r">R</h1>
<p><code>RequestFactory</code>는 Django 에서 지원하는 UnitTest 관련 모듈이다. 이에 대한 설명은 Django 공식문서에 잘(?) 나와있다.</p>
<blockquote>
<p>The RequestFactory shares the same API as the test client. However, instead of behaving like a browser, the RequestFactory provides a way to generate a request instance that can be used as the first argument to any view. This means you can test a view function the same way as you would test any other function – as a black box, with exactly known inputs, testing for specific outputs.</p>
</blockquote>
<p>The API for the RequestFactory is a slightly restricted subset of the test client API:</p>
<blockquote>
</blockquote>
<ul>
<li>It only has access to the HTTP methods get(), post(), put(), delete(), head(), options(), and trace().</li>
<li>These methods accept all the same arguments except for follow. Since this is just a factory for producing requests, it’s up to you to handle the response.</li>
<li>It does not support middleware. Session and authentication attributes must be supplied by the test itself if required for the view to function properly.<blockquote>
<p><a href="https://docs.djangoproject.com/ko/4.0/topics/testing/advanced/">Django 공식문서</a></p>
</blockquote>
<h4 id="요약하자면-requestfactory는-요청-데이터를-생성해주는-공장이다-정확히-알려진-입력을-사용하여-특정-출력을-테스트-한다-정도가-되겠다">요약하자면, &quot;RequestFactory는 요청 데이터를 생성해주는 공장이다, 정확히 알려진 입력을 사용하여 특정 출력을 테스트 한다&quot; 정도가 되겠다.</h4>
</li>
</ul>
<h1 id="f">F</h1>
<p>framework 들이 대개 그렇듯, DRF도 Django 의 거의 모든 부분에서 확장된 기능을 제공하고 있다. APIRequestFactory 는 다음과 같이 설명된다.</p>
<blockquote>
<p>The APIRequestFactory class supports an almost identical API to Django&#39;s standard RequestFactory class. This means that the standard .get(), .post(), .put(), .patch(), .delete(), .head() and .options() methods are all available.
<a href="https://www.django-rest-framework.org/api-guide/testing/">DRF 공식문서</a></p>
</blockquote>
<h4 id="간단하게-requestfactory-가-할-수-있는건-거의-다-할-수-있습니다-라는-뜻이다">간단하게, &quot;RequestFactory 가 할 수 있는건 거의 다 할 수 있습니다&quot; 라는 뜻이다.</h4>
<p>친절하게 git repo 까지 제공하고 있지만, 너무 어려워서 미간을 좁히던 중, 이전 포스팅에서 좀 더 쉬운 예제로 되어있는 repo의 링크가 있단걸 기억하고 열심히 참고해 보았다. 먼저 <code>factories.py</code>를 만들고 <code>tests.py</code> 로 넘어가야한다.
<a href="https://github.com/sp41mer/drf-test-examples/blob/master/lecture_api_testing/tests.py">sp41mer/drf-test-examples</a></p>
<h1 id="til">TIL</h1>
<h4 id="닉값-하는-진짜-심플한-apisimpletestcase-doesnt-work-with-db">닉값 하는 진짜 심플한 APISimpleTestCase (doesn&#39;t work with DB)</h4>
<pre><code class="language-python">factories.py
from factory.django import DjangoModelFactory

from .models    import School, Teacher

class SchoolFactory(DjangoModelFactory):
    class Meta:
        model = School

class TeacherFactory(DjangoModelFactory):
    class Meta:
        model = Teacher

tests.py
from django.contrib.auth.models import User
from django.contrib.auth     import get_user_model
from django.urls         import reverse

from rest_framework              import status
from rest_framework.test          import APIRequestFactory, APISimpleTestCase, APITestCase
from rest_framework.authtoken.models import Token

from apps     import serializers, models
from .factories import SchoolFactory, TeacherFactory
from .views     import SchoolViewSet

class TestCaseSchoolSimple(APISimpleTestCase):

    def test_create_school_request_factory(self):
        school = SchoolFactory.build(school_name=&#39;Test&#39;)

        self.assertEqual(school.school_name, &#39;Test&#39;)</code></pre>
<p>설명에도 나와 있듯, 이 테스트는 DB 근처도 가지 않는다. 지정된 Factory class 에서 생성된 값을 곧바로 비교하는 원리이다.
도데체 이걸 어디다 써먹어야 되는지 잘 모르겠고, 짐작으로는 정말 간단한 200_OK 로직 같은 것을 테스트 할 때 사용 할 수 있을 것 같다. 당연하겠지만, <code>build</code> 값과 <code>assertEqual</code> 값이 다르면 테스트가 실패한다. 간단한 만큼 테스트 속도는 아주 빠르다.
<br></p>
<h4 id="apirequestfactory-apitestcase-그리고-setup">APIRequestFactory, APITestCase 그리고 setUp</h4>
<p>SimpleTest 로 기분좋게 출발했으니, 본론으로 가보자. 참고 Repo에 있는 코드를 잘 대입해서 <code>get</code>요청에 대한 테스트를 짜 보았다.</p>
<pre><code class="language-python">    def test_get_school_request_factory(self):
        school = SchoolFactory(name=&quot;Test&quot;)
        request_factory = APIRequestFactory()
        request = request_factory.get(&quot;/schools&quot;)
        view = SchoolViewSet.as_view({&quot;get&quot;: &quot;list&quot;})
        response = view(request)
        self.assertEqual(response.status_code, status.HTTP_200_OK)</code></pre>
<p>일단 결과는 OK, 그러나 잘 살펴보니 허점이 군데 군데 발견되었다.</p>
<ul>
<li><p>인증/인가에 대한 로직 없음
: 지금까지 해왔던 테스트에선 항상 <code>setUp</code>을 만들어서 <code>User</code>를 생성하고, 인증을 fake로 통과시킨 <code>Token</code>도 생성시킨 뒤 테스트 함수가 작동되는 형태였다. 이 작업들이 없음에도 OK가 뜬 이유를 살펴보니, 나의 view에 <code>permission_class</code>가 빠져있었다. 또한, 참조 Repo 에도 설정이 되어있지 않았기 때문에 굳이 Token 이 없어도 로직이 작동 되었던 것이다.</p>
</li>
<li><p>Factory 를 쓰나, objects.create()를 쓰나 다른점이 무엇일까?
: 일단 알아낸 것은 <code>SchoolFactory()</code> 괄호안에 아무것도 입력하지 않아도 model에 설정되어 있는 field 속성에 따라 빈 값들이 입력된 채로 Object 1개가 생성이 되는 것을 확인했다. <code>.create()</code>의 경우 빈 값으로 활용 할 수 없으므로 차이점이라고 할 수 있겠다.</p>
</li>
</ul>
<p>OK가 떴으나, 전혀 신뢰가 가지 않는 테스트 코드이기 때문에 실패한 코드인 것이다.
Factory 에 대해 좀 더 깊게 알아볼 필요가 있겠다. 무언가 다른 방향으로 조금 더 활용도 높게 사용 할 수 있을 것 같은 느낌이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 19-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-19-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-19-</guid>
            <pubDate>Mon, 17 Jan 2022 00:45:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<h1 id="d">D</h1>
<p>Delay 되었던 유닛 테스트를 오늘은 어느정도 마무리를 해야만 했다. 다음주 목요일에 협업이 마무리되기 때문에 현재까지 작성된 API에 대한 테스트 코드도 작성해야하고, 프론트와 최종확인까지 마쳐야 하기 때문이다. 사용된 테스트 코드는 다음과 같다.</p>
<pre><code class="language-python">class SchoolTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)

    def test_school_viewset(self):
        data ={
            &#39;school_name&#39; : &#39;test&#39;,
        }

        response = self.client.post(&#39;/school/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    response = self.client.get(&#39;/school/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)</code></pre>
<p>여기에서 <code>get</code>에 추가된 &#39;검색&#39; 기능을 사용했을 때, 그리고 <code>serializer</code>에서 추가 된 정보에 대한 결과값을 확인하기 위한 테스트 코드를 작성하는 것이 목표다.</p>
<h1 id="r">R</h1>
<p><code>response</code> 를 한 줄 더 추가하기 전에 먼저 <code>view</code> 와 <code>serializer</code>를 먼저 살펴 보자.</p>
<pre><code class="language-python">views.py
class SchoolGradesViewSet(ModelViewSet):
    permission_classes = [IsAdminUser]
    serializer_class = SchoolGradesSerializer
    pagination_class = SchoolGradesPagination

def get_queryset(self):
   school_name = self.request.query_params.get(&#39;school_name&#39;)

   if school_name:
      queryset = SchoolGrades.objects.annotate(school_name=F(&#39;school__school_name&#39;)).filter(school_name__icontains=school_name)

   else:
      queryset = SchoolGrades.objects.all()

   return queryset


serializers.py
class SchoolGradesSerializer(serializers.ModelSerializer):
    school_info = serializers.SerializerMethodField()

    class Meta:
        model = SchoolGrades
        fields = [&#39;id&#39;, &#39;school_id&#39;, &#39;school_grade&#39;, &#39;school_info&#39;]
        ※ &#39;school_info&#39;는 serializer 에서 추가된 정보이므로 나중에 다룬다.
        ※ 이번 포스팅에서는 &#39;검색&#39; 기능 결과에 대한 실패 경험만을 다룬다.

    def get_school_info(self, obj):
        return School.objects.filter(id=obj.school_id).values(&#39;school_name&#39;)</code></pre>
<p>SchoolGrades 테이블은 School 테이블을 정참조 하고 있다. 이제 &#39;school_name&#39;으로 검색 했을 때의 테스트 코드를 덧붙이자.</p>
<pre><code class="language-python">response = self.client.get(&#39;/school/?school_name=te&#39;, format=&#39;json&#39;)
self.assertEqual(response.status_code, status.HTTP_200_OK)</code></pre>
<p>이렇게만 쓰면 깔끔하게 OK가 뜰 것이다. 하지만 내가 하고자 하는 건 이게 아니다. 단순히 &#39;status_code&#39; 만으로 테스트를 하면 빈 값이 나오더라도 200_OK가 되기 때문에 의미가 없다. 그래서 실제로 출력되는 값을 비교해보아야 한다.</p>
<pre><code class="language-python">self.assertEqual(response.data, {&#39;id&#39;:1, &#39;school_id&#39;:1, &#39;school_grade&#39;:&#39;상&#39;, &#39;school_info&#39;=[{etc..}]}</code></pre>
<h1 id="f">F</h1>
<p>From now on(지금부터) 문제는 발생한다. <code>response.data</code> 를 <code>print</code> 해보면 다음과 같은 결과값이 등장한다.</p>
<p><code>OrderedDict([(&#39;count&#39;, 1), (&#39;next&#39;, None)[332 chars])])])</code></p>
<p>이 <code>OrderedDict</code>가 내 금요일을 소멸시킨 장본인 되시겠다. 이녀석은 오늘의 TIL에서 더 알아보도록 하고 다시 코드로 돌아가자. <code>OrderedDict</code>를 만난 후로 다양한 시도를 해보았다.</p>
<pre><code class="language-python">1. self.assertEqual(response.data[&#39;id&#39;, &#39;shop_id&#39;...], {...})
2. self.assertEqual(response.data.first, {...})
3. self.assertEqual(response, {...})
4. self.assertEqual(response.data, ([(...)]))
5. response_data = json.loads[&#39;data&#39;]
   self.assertEqual(response_data, {...})
6. self.assertEqual(response.data, set({...}))
7. self.assertEqual(**response.data, {...})
......</code></pre>
<p>당연히 모조리 실패 했다. 심지어 입력값을 최고한으로 줄여서 print된 출력값을 토씨하나 안빠지고 똑같이 복붙을 해봤는데도 일치하지 않는다는 에러가 떴다(이건 진짜 이상했음). 결국 퇴근시간이 될 때까지 해결하지 못했고, stackoverflow에 질문을 남겨두고 금요일을 마무리 했다.</p>
<h1 id="til">TIL</h1>
<h4 id="서순하는-dict-순서를-아는-ordereddict">서순하는 Dict, 순서를 아는 OrderedDict</h4>
<p>알다시피 파이썬에서 dict 형태는 순서(index)가 정해져 있지 않다. 그래서 아래와 같은 로직도 성립을 한다.</p>
<pre><code class="language-python">A = {&#39;a&#39;:1, &#39;b&#39;:2, &#39;c&#39;:3}
B = {&#39;a&#39;:1, &#39;c&#39;:3, &#39;b&#39;:2}
A == B
True</code></pre>
<p>하지만 순서를 지키는 <code>OrderedDict</code>를 사용하면 순서까지 맞춰야 True를 반환한다.
파이썬의 <code>collactions</code> 모듈에서 임포트 해야하는 <code>OrderedDict</code>를 단순히 순서가 있는 dict 형식으로만 사용할 생각이면 파이썬 3.6버전 보다 상위버전을 쓰고 있을 시, 그냥 dict 를 사용해도 된다. 기존 dict에 순서를 포함하는 기능을 업그레이드 해 준 것이다. 하위 버전의 파이썬을 쓰거나, 조금 더 엄격한 동등성 비교를 필요로 할 때에는 <code>OrderedDict</code> 를 사용 할 수 있다.</p>
<p><code>a = OrderedDict({&#39;a&#39;:1})</code>
</br></p>
<h4 id="serializer-의-field-추가-기능">serializer 의 field 추가 기능</h4>
<p>DRF에 대한 초기 포스팅에서 <code>serializer</code> 에 추가적으로 정보를 찾아서 <code>field</code>에 추가해주는 로직을 구현한 적이 있다. 그때와 지금이 달라진 점은 class Meta 의 field 에서 더 이상 <code>__all__</code> 을 쓰지 않고 각각의 필요한 field 명을 일일이 작성해준다는 점이다. 만약 MethodField 를 지정해 놓고 함수처리를 하지 않는다 거나, 함수 처리까지 다 했지만, field 에 해당 field 명을 넣지 않는 다거나, 일치하지 않는 field 명이 존재 한다면 무조건 에러가 발생한다. 그래서 추가 정보를 출력하는 로직이 구현된 <code>serializer</code>의 모습은 항상 다음과 같은 포맷을 유지한다.</p>
<pre><code class="language-python">class SchoolGradesSerializer(serializers.ModelSerializer):
    school_info = serializers.SerializerMethodField()

    class Meta:
        model = SchoolGrades
        fields = [&#39;id&#39;, &#39;school_id&#39;, &#39;school_grade&#39;, &#39;school_info&#39;]

    def get_school_info(self, obj):
        return School.objects.filter(id=obj.school_id).values(&#39;school_name&#39;)</code></pre>
<p>알고 있기로는 함수명이나 클래스명은 컨벤션 때문이지 사실상 코드를 작성하는 개발자의 마음대로 지정할 수 있다고 배웠으나, DRF에서는 그럴 수 없다. 만약 <code>def get_school_info</code> 에서 한 글자라도 빠지거나 오타가 발생하면 함수가 작동하지 않는다. 이는 MethodField 나 fields=[] 도 마찬가지다. 세 부분이 똑같이 일치해야 로직이 정상적으로 작동하는 것이다.</p>
<p>편의와 편리 두 마리 토끼를 다 잡고자 하는 DRF 이지만, 그 모든 혜택을 누리려면 토끼를 잡으려 하는 사용자의 경험치가 상당히 많이 요구되는 프레임워크 인 것 같다.</p>
<ul>
<li>참고할만한 포스팅 
<a href="https://djangostars.com/blog/rest-apis-django-development/">DRF 기초와 Testing example</a></li>
</ul>
<blockquote>
<h4 id=""></h4>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 18-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-18-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-18-</guid>
            <pubDate>Fri, 14 Jan 2022 00:45:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="내-두눈-밤이면-별이-되지-나의-집은-뒷골목-달과-별이-뜨지요">내 두눈 밤이면 별이 되지, 나의 집은 뒷골목 달과 별이 뜨지요.</h4>
<p>[ 체리필터, &quot;낭만고양이&quot; ]</p>
</blockquote>
<h1 id="d">D</h1>
<p>더딘 작업속도에 점점 초조해져 갈 무렵, 사수님이 작업중이시던 기능 구현을 끝내고 합류하셨는데, 사수님도 DRF로 유닛 테스트를 만들어 본 적 없다고 하시면서 같이 공부하면서 만들어 보자고 하셨다. 또, 예제로 사용된 테스트 코드 샘플을 보여주시면서 이걸로 응용해 보자고도 하셨다.</p>
<pre><code class="language-python">class StreamPlatformTestCase(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)
    def test_stream(self):
        data = {
            &#39;name&#39; : &#39;tving&#39;,
            &#39;about&#39; : &#39;test&#39;,
            &#39;website&#39; : &#39;http://tving.co.kr&#39;
        }
        response = self.client.post(&#39;/watch/stream/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response = self.client.get(&#39;/watch/stream/&#39;, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)</code></pre>
<p>이것은 Udemy 강의에서 사용된 학습자료를 바탕으로 만들어진 유닛 테스트인데, &#39;setUp&#39;의 모양새는 내가 만든 것과 같았지만, <code>response</code>부분이 달랐다.</p>
<pre><code class="language-python">    def test_school_create(self):
        data = {
        &#39;school_name&#39; : &#39;서울대&#39; 
        }
        response = self.client.post(reverse(&#39;/school/&#39;, data))
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)</code></pre>
<h1 id="r">R</h1>
<p><code>reverse</code> 는 Django 에서도 사용이 가능한데, 그 역할은 다음과 같다.</p>
<ul>
<li>장고는 urls.py 변경을 통해 ‘각 뷰에 대한’ url이 변경되는 유연한 시스템을 갖고 있다.</li>
<li>url이 변경 되더라도 url reverse가 변경된 url을 추적한다. (누락의 위험이 적다)</li>
</ul>
<p><a href="https://wayhome25.github.io/django/2017/05/05/django-url-reverse/">초보몽키님의 블로그</a>
그래서 DRF공식문서 예시에 나온 것처럼 url 과 <code>basename</code>을 사용해보았지만, 어째선지 자꾸만 에러가 났다. 이것 때문에 거진 하루를 소비했지만, 결국 방법은 알아내지 못했고 한숨만 쉬던 중 사수님이 주신 예제로 바꾸어보았더니, 바로 통과가 되었다.</p>
<pre><code class="language-python">class SchoolTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)

    def test_school_viewset(self):
        data ={
            &#39;school_name&#39; : &#39;test&#39;,
        }

        response = self.client.post(&#39;/school/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)</code></pre>
<h1 id="f">F</h1>
<p>Fail 이 뜨지 않는게 오히려 불안하다는 말을 들은 적이 있는데, 당시에는 개발자 전용 개그인줄로만 알았다. 그런데 단박에 OK가 뜨자, 묘한 위화감이 들면서 &#39;과연 이게 정말 맞는 테스트 코드여서 OK가 뜬 걸까?&#39; 하는 의문이 자리잡았다. 하지만 일주일이 거의 다 지나가고 있었고, 다른 API에 대한 테스트도 작성해야 했기에, 의문은 잠시 접어두고 기본적인 CRUD에 대한 테스트를 완성했다.</p>
<pre><code class="language-python">class SchoolTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)

    def test_school_viewset(self):
        data ={
            &#39;school_name&#39; : &#39;test&#39;,
        }

        patch_data = {
            &#39;school_name&#39; : &#39;TESTname&#39;
        }

        response = self.client.post(&#39;/school/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(School.objects.count(), 1)
        self.assertEqual(School.objects.get().school_name, &#39;test&#39;)

        response = self.client.get(&#39;/school/&#39;, data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.client.patch(&#39;/school/&#39;, patch_data, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.client.delete(&#39;/school/&#39;, format=&#39;json&#39;)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)</code></pre>
<p>드디어. 일주일을 통으로 갈아넣으면서 테스트 코드 한 토막을 완성했다(일단은 그렇게 보였다). <code>post</code>부분에 두 줄을 추가한 이유는 <code>create</code>가 제대로 적용되었다면, 1개의 데이터가 카운팅 될 것이고, <code>name</code>이 &#39;test&#39;인 데이터여야 할 것이기에 검증 차원에서 추가해보았다. 테스트 결과도 모두 통과가 되었고, 사수님도 OK사인을 보내셨다. 이제 남은 것은 추가 된 기능에 대한 테스트 인데, &#39;검색&#39;과 <code>serializer</code>에서 추가로 찾은 결과값에 대한 테스트가 필요했다. 과연 내일안에 성공할 수 있을지.</p>
<h1 id="til">TIL</h1>
<h4 id="가정-설정문-assert-함수">가정 설정문, <code>assert</code> 함수</h4>
<p><code>reverse</code>에 대해 찾아보다가 우연찮게 <code>assert</code> 함수에 대해 알게 되었다. 파이썬 함수인 <code>assert</code>는 <code>assertEqual</code>처럼 메소드 형식으로 사용하는 경우도 있지만, 특정한 의미로써 <code>assert</code> 만으로도 사용하기도 한다. 이에 대해서 다음과 같은 설명이 있다.</p>
<blockquote>
<p>예외를 발생시키는 예외처리랑 비슷하지만, 예외처리는 에러가 발생했을때 어떤 처리를 하기위한 코드이고, 이 assert (가정 설정문)은 어떤 조건이 True임을 보증하기 위해서 사용하는 것 입니다.</p>
</blockquote>
<p>오류를 발생시키는 raise와 비슷하지만 다릅니다. raise에 대한 자세한 설명이 필요하다면 [바로가기]</p>
<blockquote>
</blockquote>
<p>raise는 만약에 오류가 발생했을때 &quot;except 와 함께 이렇게 처리해라&quot; 는 뜻이고</p>
<blockquote>
</blockquote>
<p>assert는 이 조건이 참일때 코드는 내가 보장한다. 이 조건은 올바르다!
하지만 이 조건이 거짓이라는 것은 내가 보증하지 않은 동작이다. 그러니 AssertionError를 발생해라.</p>
<blockquote>
<p>이런 식의 흐름입니다.
<a href="https://blockdmask.tistory.com/553">출처: [개발자 지망생]</a></p>
</blockquote>
<p>참초한 블로그에 예시들도 자세히 나와있으므로 참고하면 좋을 듯 하다.</p>
<p>추가로 유닛 테스트에서 자주 사용되는 8가지 정도의 <code>assert--</code> 함수 종류에 대해서도 간략히 알아보자.</p>
<ul>
<li>assertEqual()
assertEqual(결과 값, 기대한 값)
실제 데이터 값과 기대한 값이 동일한지 확인합니다.
결과 값과 기대한 값이 같다면 (결과 값 == 기대한 값) Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertNotEqual()
assertNotEqual(결과 값, 기대한 값)
실제 데이터 값과 기대한 값이 다른지 확인합니다.
결과 값과 기대한 값이 다르다면 (결과 값 != 기대한 값) Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertTrue()
assertTrue(결과 값)
결과 값이 True인지 확인합니다.
결과 값이 True라면 Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertFalse()
assertFalse(결과 값)
결과 값이 False인지 확인합니다.
결과 값이 False라면 Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertGreater()
assertGreater(결과 값, 기대한 값)
결과 값이 기대한 값보다 큰지 확인합니다.
결과 값이 기대한 값보다 크다면 (결과 값 &gt; 기대한 값) Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertGreaterEqual()
assertGreaterEqual(결과 값, 기대한 값)
결과 값이 기대한 값보다 크거나 같은지 확인합니다.
결과 값이 기대한 값보다 크거나 같다면 (결과 값 &gt;= 기대한 값) Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertLess()
assertLess(결과 값, 기대한 값)
결과 값이 기대한 값보다 작은지를 확인합니다.
결과 값이 기대한 값보다 작다면 (결과 값 &lt; 기대한 값) Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertLessEqual()
assertLessEqual(결과 값, 기대한 값)
결과 값이 기대한 값보다 작거나 같은지를 확인합니다.
결과 값이 기대한 갑보다 작거나 같다면 (결과 값 &lt;= 기대한 값) Pass 입니다</li>
</ul>
<hr>
<ul>
<li>assertIn()
assertIn(찾으려는 문장, 전체 문장)
전체 문장에서 내가 찾으려는 문장이 들어 포함되었는지 확인합니다.
전체 문장에서 내가 찾으려는 문장이 있다면 Pass 입니다.</li>
</ul>
<hr>
<ul>
<li>assertNotIn()
assertNotIn(찾으려는 문장, 전체 문장)
전체 문장에서 내가 찾으려는 문장이 들어 포함되지 않았는지 확인합니다.
전체 문장에서 내가 찾으려는 문장이 없다면 Pass 입니다.</li>
</ul>
<p><a href="https://dejavuqa.tistory.com/200">출처: 자주 사용되는 assert 함수 정리</a></p>
<blockquote>
<h4 id="이젠-바다로-떠날-거예요-거미로-그물-쳐서-물고기-잡으러">이젠 바다로 떠날 거예요, 거미로 그물 쳐서 물고기 잡으러</h4>
<p>[ 체리필터, &quot;낭만고양이&quot; ]</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 17-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-17-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-17-</guid>
            <pubDate>Thu, 13 Jan 2022 01:06:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="형도-그랬단다-죽고-싶었지만-견뎌보니-괜찮더라">형도 그랬단다 죽고 싶었지만 견뎌보니 괜찮더라</h4>
<p>[ 노라조, &quot;형&quot; ]</p>
</blockquote>
<h1 id="d">D</h1>
<p>되돌아 온 위코드 데이. 속절없이 날짜는 지나가고 그보다 시간은 더 빠르게 지나갔다. 진전이 없는 코드는 쌓여만 가는 페이지 방문 기록을 무색하게 만들고, 투자한 시간마저도 빛 바랜 기억의 편린으로 만들어 버렸다. 오늘로써 3일째, 출근도 안하는데 코드마저 발전이 없다면 내가 사장이라도 월급 주기 싫을 것 같기에, 닥치는대로 코드를 밀어 넣어 보기로 했다. </p>
<h1 id="r">R</h1>
<p><code>RequestFactory</code> 모듈은 Django에서도 제공하는 TestCode 관련 지원 모듈이지만, 써본 적은 없었다. 사실상 <code>TestCase</code> 말고는 써보지 않았으니, 모르는게 더 많을 수 밖에. DRF에서는 대부분 Django에 존재하는 모듈에서 확장된 모듈을 제공하는데, <code>TestCase =&gt; APITestCase, RequestFactory =&gt; APIRequestFactory, Client =&gt; APIClient</code> 처럼 <code>API</code>가 다 붙어있다. 여하튼, 코드란 자고로 글만 읽어서는 알 수가 없다. 써보자, 일단.</p>
<h1 id="f">F</h1>
<p>(First)먼저, <code>TestCase</code>의 확장 모듈인 <code>APITestCase</code>부터 적용해 보았다.</p>
<pre><code class="language-python">tests.py

class TestSchool(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user(username=&#39;testuser&#39;, password=&#39;password@123&#39;)
        self.token = Token.objects.get(user__username=self.user)
        self.client.credentials(HTTP_AUTHORIZATION=&#39;Token&#39; + self.token.key)
        self.school = models.School.objects.create(school_name=&#39;서울대&#39;,(etc..))

    def test_school_create(self):
        data = {&#39;school_name&#39; : &#39;서울대&#39; }
        response = self.client.post(reverse(&#39;/school/&#39;, data))
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)</code></pre>
<p><code>setUp</code>부터 난관 이었는데, 나의 <code>model</code>에는 존재하지 않는 <code>User</code> 와 <code>Token</code>은 어디서 가져오는지, <code>credentials</code>는 무엇이고, <code>response</code>의 <code>reverse</code>는 또 무엇인지 쉽게 이해할 수 없었다. 해서, 오늘은 &#39;User&#39; 와 &#39;Token&#39;, 그리고 &#39;credentials&#39;까지 이 세 가지부터 알아보려고 한다.</p>
<h1 id="til">TIL</h1>
<h3 id="존재하지-않는-모델-가짜user-token-테이블">존재하지 않는 모델, 가짜<code>User</code>, <code>Token</code> 테이블</h3>
<h4 id="user">User</h4>
<p>분명 <code>models.py</code>에는 이 두 가지 모델명은 존재하지 않는다. 그래서 위의 테스트를 그대로 실행시키게 되면,</br>
<code>AttributeError: Manager isn&#39;t available; &#39;auth.User&#39; has been swapped for &#39;schools.School&#39;</code></br>
이런 에러를 만나게 된다. 회사의 기존 코드를 클론 받아서 이어나가고 있지만, 전체 코드를 살펴보지는 못했기에, 어디엔가 내가 모르는 설정이 되어 있는듯 했다. 
<code>auth.User</code>를 찾아 헤맨 끝에, <code>settings.py</code>에서 <code>auth.User</code>를 사용하기 위해 설정해 주는 코드가 있다는 것을 알아냈고 찾아보았더니 역시나, 다음과 같은 설정이 되어있었다.</br>
<code>AUTH_USER_MODEL = &#39;schools.School&#39;</code></br>
이것을 &#39;Custom user model&#39;이라고 하고 이 경우에는 다음의 코드를 &#39;setUp&#39;에 추가로 작성해 주어야 한다.</br>
<code>from django.contrib.auth import get_user_model</code>
<code>User = get_user_model()</code></br>
커스텀 된 유저 모델을 가져와서 적용해주는 모듈 &#39;get_user_model&#39;을 import 하고 &#39;User&#39; 를 변수로 지정해서 사용하려는 모델을 담아준다. 
<a href="https://stackoverflow.com/questions/17873855/manager-isnt-available-user-has-been-swapped-for-pet-person?rq=1">StackOverflow - 관련 답변</a></p>
<h4 id="token">Token</h4>
<p>&#39;Token&#39; 역시 테이블이 존재하지 않으며, 때문에 추가 설정을 필요로 한다.</p>
<pre><code class="language-python">from rest_framework_simplejwt.tokens import AccessToken

self.token = Token.objects.get(user__username=self.user)
(위의 코드를 아래의 코드로 바꿔줘야 한다.)
self.access = AccessToken.for_user(self.user)</code></pre>
<p>현재 회사에서 &#39;simplejwt&#39;라는 모듈을 사용하고 있어서 적용하는 방법을 알아보았더니, &#39;AccessToken&#39;을 임포트 하면 된다는 것을 알아냈다. 이렇게 하면 굳이 &#39;Token&#39; 가짜 테이블을 &#39;auth.token&#39;으로 설정해주지 않아도 알아서 테스트용 토큰이 생성되고 사용 할 수 있게 된다.</p>
<h4 id="credentials">credentials</h4>
<p>이 메소드에 대한 해답은 공식문서에 나와있다.</p>
<blockquote>
<p>credentials 메소드는 테스트 클라이언트가 모든 후속 요청에 포함 할 헤더를 설정하는데 사용할 수 있습니다.</p>
</blockquote>
<p>credentials를 다시 호출하면 기존 credentials을 덮어 씁니다. 
인수없이 메서드를 호출하여 기존 credentials의 설정을 해제할 수 있습니다.
credentials 방법은 기본인증, OAuth1a과 OAuth2 인증 및 간단한 토큰 인증스키마와 같은 인증 헤더가 필요한 API를 테스트하는데 적합합니다.</p>
<blockquote>
</blockquote>
<p>&lt;<code>.force_authenticate(user=None, token=None)</code>&gt;
때로는 인증을 생략하고 테스트 클라이언트의 모든 요청을 인증 된 것으로 자동처리하도록 할 수 있습니다. 이는 API를 테스트하고 있지만 테스트 요청을 하기 위해 유효한 자격 증명을 작성하지 않으려는 경우 유용한 단축키입니다.</p>
<blockquote>
<p>&lt;사용예시&gt;
user = User.objects.get(username=&#39;lauren&#39;)
client = APIClient()
client.force_authenticate(user=user)</p>
</blockquote>
<p>후속 요청을 인증 해제하려면 force_authenticate를 호출하여 사용자/토큰을 None으로 설정하세요.
<a href="https://kimdoky.github.io/django/2018/08/10/drf-Testing/">DRF 공식문서 번역본</a></p>
<p>즉, 테스트 실행 시, 후속으로 실행 될 모든 테스트 함수에 포함할 헤더를 설정하는 메소드이다. 인증을 통해 &#39;Token&#39;을 발급하면 인가된 API를 사용할 수 있는데, 이때 &#39;Token&#39;을 헤더에 담아서 매 API마다 로그인을 하지 않아도 되게 하는 것이 인증&amp;인가의 기본 개념인것 처럼, <code>credentials</code>메소드가 자동으로 헤더에 &#39;Token&#39;을 담아 주는 것이다.
이제 실행이 가능하도록 정제된 &#39;setUp&#39; 코드를 보도록 하자.</p>
<pre><code class="language-python">class ProposeGoodShopTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;test&#39;, password=&#39;Password!&#39;)
        self.access = AccessToken.for_user(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f&#39;Bearer {self.access}&#39;)</code></pre>
<p>이로써 <code>APITestCase</code>의 &#39;setUp&#39;이 설정 되었다.
내일은 1차로 완성된 테스트 코드를 살펴 보겠다.</p>
<blockquote>
<h4 id="살다보면-살아가다보면-웃고-떠들며-이-날을-넌-추억할테니">살다보면 살아가다보면 웃고 떠들며 이 날을 넌 추억할테니</h4>
<p>[ 노라조, &quot;형&quot; ]</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 16-]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-15-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-15-</guid>
            <pubDate>Tue, 11 Jan 2022 14:11:33 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="쉽게-쉽게-생각해-깊게-깊겐-피곤해">쉽게 쉽게 생각해, 깊게 깊겐 피곤해</h4>
<p>[ Clover, &quot;An Oppa I Know&quot; ]</p>
</blockquote>
<h1 id="d">D</h1>
<p>드높게 늘어선 빌딩숲 사이로 다시금 눈꽃이 흩날렸다. 서울와서 벌써 두번째 눈이다. 부산에선 몇 년에 한번도 보기 힘든 눈이지만, 지금은 반가워할 겨를이 없는게 문제다. 협업 3주차, 평일로만 따진다면 출근일이 6일밖에 안남았다. 한달동안 DRF에 대해서 확실하게 기초 개념을 잡을 수 있겠지라고 생각했던게 2주전이란 뜻인데... 이쯤에서 스멀스멀 피어오르는 자기비판을 억누르며 출근길에 올랐다.</p>
<h1 id="r">R</h1>
<p>RoadPic. 현재 우리가 개발중이고 베타버전으로 배포중인 앱 이름이다(앱 이름 정도는 홍보차원에서 말해도 괜찮지 않을까). 오후에는 이 앱에 대해서 전체 회의가 있었는데, 영업팀에서 보내준 피드백 자료도 같이 보면서 자유롭게 의견을 나눴다. 직원분들이 착하셔서 그런건지 진짜 의미가 있는 의견이었는지는 모르지만, 우리들이 낸 의견도 잘 경청해주시고 좋은 의견이다라거나 전에 한번 나왔다가 보류중인 건이다 라는 식으로 적극 소통해주셨다. 마치 1, 2차 프로젝트 때처럼 편안한 분위기에서 자유롭게 의견을 주고받는게 신기하면서도 상당히 재밌는 시간이었다.</p>
<h1 id="f">F</h1>
<p>Figma 라는 그래픽툴로 여러가지 형태의 레이아웃과 추가될 기능들의 위치를 대략적으로 잡은 뒤 회의내용을 요약한 후 2시간에 걸친 회의는 끝이 났다. 회의가 있기전까지 프론트와 통신을 하면서 postman으로 필요한 키 값들을 정리한 API문서를 각자 만들고 통합했다. 그리고 어제부터 매달린 Test Code 짜기에 남은 시간을 모두 투자했는데, 도무지 진전이 없었다. 사수님도 여기서 Test Code를 만들어 본 적이 없으셨고, 레거시 코드마저도 없는 상황이라 꽤나 막막했다. 하루 종일 구글을 헤엄쳐다니고, 여러 깃헙들을 파고들었지만 어제보다 코드가 나아지질 않았다. 그래서 사수님과 긴급회의를 한 결과, 위코드데이인 내일까지 한번 연구해보고 정 안된다면 다른 기능을 해보는 걸로 하자고 결론을 내렸다.</p>
<h1 id="til">TIL</h1>
<p>아래는 온라인강의를 들으면서 참고한 Testcode 이다. DRF에서 제공하는 APITestCase를 사용했고, Factoryboy 나 Faker, Baker와 같은 라이브러리는 사용하지 않았다. 특이한 점으로는 &#39;token&#39;을 위한 fake model을 만든 것인데, 아직 &#39;auth.user&#39; 와 &#39;auth.token&#39;의 개념이나 사용법을 정확히 몰라서 완전히 이해하지는 못했다. 또한 <code>settings.py</code>에 <code>&#39;rest_framework.authtoken&#39;</code>,을 <code>INSTALLED_APPS</code>에 추가해 줘야한다. 참고한 StackOverflow 에서는 migration도 해야한다고 했지만, 안해도 딱히 상관은 없었다.</p>
<pre><code class="language-python">models.py
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

Tests.py
class StreamPlatformTestCase(APITestCase):

    def setUp(self):
        self.user = User.objects.create_user(username=&quot;example&quot;, password=&quot;Password@123&quot;)
        self.token = Token.objects.get(user__username=self.user)
        self.client.credentials(HTTP_AUTHORIZATION=&#39;Token &#39; + self.token.key)

        self.stream = models.StreamPlatform.objects.create(name=&quot;Netflix&quot;, 
                                about=&quot;#1 Platform&quot;, website=&quot;https://www.netflix.com&quot;)

    def test_streamplatform_create(self):
        data = {
            &quot;name&quot;: &quot;Netflix&quot;,
            &quot;about&quot;: &quot;#1 Streaming Platform&quot;,
            &quot;website&quot;: &quot;https://netflix.com&quot;
        }
        response = self.client.post(reverse(&#39;streamplatform-list&#39;), data)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_streamplatform_list(self):
        response = self.client.get(reverse(&#39;streamplatform-list&#39;))
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_streamplatform_ind(self):
        response = self.client.get(reverse(&#39;streamplatform-detail&#39;, args=(self.stream.id,)))
        self.assertEqual(response.status_code, status.HTTP_200_OK)</code></pre>
<p>아래는 어제와 같은 실패한 테스트 코드이다.</p>
<pre><code class="language-python">def test_school_create(self):
        data = {
            &#39;school_name&#39; : &#39;서울대&#39;,
            &#39;address&#39; : &#39;서울시 강남구&#39;,
            &#39;description&#39; : &#39;서울대입구 역에서 3Km&#39;
        }
        response = self.client.post(reverse(&#39;institution/school&#39;), data)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)</code></pre>
<p>APITestCase를 사용할 때 APIRequestFactory 를 사용하는 방법도 있어서 한참 RequestFactory에 대해 찾아보다가 회의에 들어가게 되어서 마무리는 짓지 못했다. 게다가 <code>reverse</code> 메소드에서 발목이 단단히 잡혔는데, 우선 이 메소드의 역할과 기본 형태에 대해서부터 알아봐야 할 것 같다. 참고하고 있는 사이트 몇 가지를 남긴다. 내일의 나는 좀 더 낫기를.</p>
<p><a href="https://tech.ashe.kr/26">APITestCase 에서 API 를 요청하는 두가지 방법</a></p>
<p><a href="https://github.com/encode/django-rest-framework/tree/master/rest_framework">endcode github: DRF의 각종 기능별 예제 코드가 많은 곳 </a></p>
<p><a href="https://github.com/erkarl/django-rest-framework-oauth2-provider-example/blob/master/apps/users/tests.py">APITestCase 사용 예제 github</a></p>
<p><a href="https://github.com/sp41mer/drf-test-examples/blob/master/lecture_api_testing/tests.py">APIRequestFactory 사용 예제 github</a></p>
<blockquote>
<h4 id="우리-사이는-특별해-스타들보다-스페셜해">우리 사이는 특별해, 스타들보다 스페셜해</h4>
<p>[ 클로버, &quot;An Oppa I Know&quot; ]</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 15-
]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-14-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-14-</guid>
            <pubDate>Mon, 10 Jan 2022 15:10:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="어딜-쳐다보는-거냐고-솔직히-너-그래-너-생판-처음-만난-너">어딜 쳐다보는 거냐고, 솔직히 너 그래 너 생판 처음 만난 너</h4>
<p>[ PSY, &quot;New_Face&quot; ]</p>
</blockquote>
<h1 id="d">D</h1>
<p>Determined(결연한) 한 표정으로 월요일 아침 출근을 했는데, 평소에는 막히지 않던 길이 꽉꽉 막히더니 평소보다 30분이나 늦게 도착했다. 9시면 도착할걸 9시30분에 도착하다니.. 어쨌거나, 10시에 출근한 사수님들과 3주차 첫 스크럼을 진행했다.</p>
<h1 id="r">R</h1>
<p>Remind 할 내용으로는 저번주에 push했던 API 기능중 url부분에 에러가 있었고, 큰 문제는 아니어서 사수님이 수정 후 재 push 하셨다고 말씀하시면서 오늘 작업하기전에 다시 pull 받으라고 하셨다. 그리고 주말간에 완성한 API는 스크럼에 이어서 곧바로 확인하고 push 하기로 했다. 3주차의 주요 과제는 바로 &#39;Test Code 작성&#39; 이었다.</p>
<h1 id="f">F</h1>
<p>피로감이 절로 몰려드는 TestCode, 하지만 &#39;테스트가 없는 코드는 실패한 코드이다&#39;라는 말도 있듯이, 반드시 작성해야 하는 부분이기도 하다. 우선 DRF에서는 &#39;UnitTest&#39;를 어떻게 하는지 부터 알아 보았다. <a href="https://www.django-rest-framework.org/api-guide/testing/">DRF 공식문서(Testing)</a>
프로젝트때 맛보았던, 내가 알던 그 &#39;UnitTest&#39;가 아니었다. 종류도 &#39;DB를 거치는 테스트&#39;, &#39;DB를 거치지 않는 테스트&#39; 로 나뉘어 있었고, Django에서 썼던 &#39;TestCase&#39;보다 좀 더 확장된 클래스 모듈을 사용하고 있었다. 자료를 찾던 중 유용해 보이는 자료를 오늘의 TIL에 첨부해 놓으려 한다.</p>
<h1 id="til">TIL</h1>
<pre><code class="language-python">from django.contrib.auth.models import User
from django.contrib.auth     import get_user_model
from django.urls import reverse

from rest_framework              import status
from rest_framework.test             import APITestCase
from rest_framework.authtoken.models import Token

from notice import serializers, models


class SchoolTestCase(APITestCase):

    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(username=&#39;testuser&#39;, password=&#39;password@123&#39;)
        self.token = Token.objects.get(user__username=self.user)
        self.client.credentials(HTTP_AUTHORIZATION=&#39;Token&#39; + self.token.key)

        self.school = models.School.objects.create(school_name=&#39;서울대&#39;, address=&#39;서울시 관악구&#39;, description=&#39;서울대입구 역에서 3Km&#39;)


    def test_school_create(self):
        data = {
            &#39;school_name&#39; : &#39;서울대&#39;,
            &#39;address&#39; : &#39;서울시 강남구&#39;,
            &#39;description&#39; : &#39;서울대입구 역에서 3Km&#39;
        }
        response = self.client.post(reverse(&#39;institution/school&#39;), data)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)</code></pre>
<h4 id="실패-코드403-에러">실패 코드(403 에러)</h4>
<p>위의 TestCode는 오류때문에 실행되지 않는 코드이다. DRF에서 제공하는 <code>APITestCase</code>라는 클래스가 있는데, 이 TestCase는 DB에 직접 도달하여 테스트를 수행한다. 또한 Django의 <code>RequestFactory</code> 라는 테스트 클래스의 확장버전이기도 한데, Factory를 써본적이 없어서 비교하지는 못했다. 검색해봐도 Django에 대한 설명보다 SpringFramework, Pytest 에서 사용한 설명이 더 많았다. 다시 한번 공식문서들과 친해져야 할 때다.</p>
<h4 id="test-를-조금이라도-더-쉽게-해주는-third-party-packages">Test 를 조금이라도 더 쉽게 해주는 Third party packages</h4>
<ul>
<li>Factoryboy - 임시 데이터 생성용 라이브러리</li>
<li>Faker - 무작위 값을 생성해주는 라이브러리<br><a href="https://www.44bits.io/ko/post/faker-and-factory-boy-for-clean-code-on-python-test">Factoryboy &amp; Faker</a></li>
</ul>
<h4 id="도움이-될-만한-페이지">도움이 될 만한 페이지</h4>
<p><a href="https://hyun-am-coding.tistory.com/entry/13-Testing">현암 코딩</a> - DRF 공식문서를 번역하여 한줄 한줄 잘 설명되어 있는데, 개인적으로 가장 꼼꼼한 자료인 것 같음.</p>
<p><a href="https://github.com/sp41mer/drf-test-examples/tree/master/lecture_api_testing">sp41mer/drf-test-examples</a> - 어느 외국인의 github 인데, DRF에서 제공하는 TestCode를 종류별로 예시와 함께 구현되어있다. 기본 코드 블록은 이곳에서 참조하는 것이 좋을 것 같다.</p>
<p><a href="https://velog.io/@gogimon/python-fakerlibrary">개발 동기의 벨로그</a> - Faker 라이브러리 사용법에 대해 찾아보다가 발견한 동기의 벨로그. 개발 실력이 뛰어난 친구이니 신뢰도는 높은 편.</p>
<blockquote>
<h4 id="왜-널-쳐다보는-거냐고-궁금해서-설레서-낯설어서">왜 널 쳐다보는 거냐고, 궁금해서 설레서 낯설어서</h4>
<p>[ 싸이, &quot;New-Face&quot; ]</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기업협업] 퀀텀AI -Day 12~14-
]]></title>
            <link>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-11-</link>
            <guid>https://velog.io/@_ted_0527_/%EA%B8%B0%EC%97%85%ED%98%91%EC%97%85-%ED%80%80%ED%85%80AI-Day-11-</guid>
            <pubDate>Mon, 10 Jan 2022 13:44:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="lookin-for-a-better-way-to-get-up-out-of-bed">Lookin&#39; for a better way to get up out of bed</h4>
<p>(침대에서 일어나기 위한 좀 더 나은 방법을 찾고 있어)
[ 맥클모어 앤 라이언 루이스, &quot;Can&#39;t Hold Us&quot; ]</p>
</blockquote>
<h1 id="d">D</h1>
<p>Damn... 목요일 하루종일 으슬으슬하고 두통이 미약하게나마 유지되더니, 금요일 아침엔 도저히 움직이기 힘든 상태가 되었다. 이미 3차까지 맞았으니 걱정을 별로 안하지만 혹시나 하는 생각에 사수님께 보고를 하고 재택으로 하기로 했다. 하지만 약 사먹고 기절한 후 온종일 골골거리느라 컴퓨터는 켜지도 못했다.ㅠ</p>
<h1 id="r">R</h1>
<p>Restfull 한 금요일을 보내고 조금 정신을 차린 토요일에 겨우 맡은 API를 마무리 할 수 있었다.(TIL 참고) 이번 API의 주요 기능은 검색과 &#39;status&#39; 값에 따른 데이터 변환 이었다. 검색의 경우에는 1) 이름만 검색 했을 때, 2) 상태만 검색 했을 때, 3) 둘 모두를 검색 했을 때, 이렇게 3가지 조건이 필요해서 Q객체를 사용하여 복합 조건문을 해결했다.</p>
<h1 id="f">F</h1>
<p>F객체처럼 Q객체도 쿼리가 사용되는 횟수를 줄여주기도 하지만, <code>.filter()</code>를 사용함에 있어서 복합적이고 다중적인 조건 연산도 가능하게 해준다.(AND 조건과 OR 조건 두 가지를 섞어서 사용하는 것도 가능하다) </p>
<h1 id="til">TIL</h1>
<pre><code class="language-python">def get_queryset(self):
    school_name = self.request.query_params.get(&#39;school_name&#39;)
    status = self.request.query_params.get(&#39;status&#39;)

    q = Q()

    if school_name:
        q &amp;= Q(school_name__icontains = school_name)
    if status:
        q &amp;= Q(status__icontains = status)

    queryset = School.objects.filter(q)
    return queryset</code></pre>
<h3 id="q-객체">Q 객체</h3>
<p>Django 에서 ORM을 사용할 때, 쿼리문의 OR 조건이나 다중 복합 조건문을 사용하고자 할 때 유용하다.</p>
<pre><code class="language-python">- SQL Query
select * from school where category=&#39;1학년&#39; or sub_category=&#39;1반&#39;

- Django ORM
School.objects.filter(Q(category=&#39;1학년&#39;) | Q(sub_category=&#39;1반&#39;))

&amp; =&gt; where 절의 AND 조건(교집합)
| =&gt; where 절의 OR 조건(합집합)</code></pre>
<h4 id="q">Q()</h4>
<p>School.objects.all() 의 역할을 한다. 테이블 전체를 변수에 담아두고, q 안에 조건들을 추가하는 것이다. 
<code>q &amp;= Q(sub_category__category__name=category)</code> 는 &#39;q&#39;라는 변수안에 조건들을 넣어주고 마지막 <code>.filter</code>에 &#39;q&#39;를 넣어서 쿼리를 한번만 날리도록 해준다.</p>
<p>몸이 아프기도 했고 일요일에는 Docker를 연습하긴 했지만, 왠지 기업협업을 시작하기 전보다 느슨해진것 같아서 불안하다. 2주 밖에 안남았지만 다시금 정신차려서 DRF를 최대한 많이 배워야 하겠다.</p>
<blockquote>
<h4 id="learn-from-that-failure-gain-humility-and-then-we-keep-marching-ourselves">Learn from that failure, gain humility and then we keep marching ourselves</h4>
<p>실패로부터 배웠고, 겸손함을 얻었지. 그리고 우린 계속해서 앞으로 갈거야</p>
</blockquote>
<p>[ 맥클모어 앤 라이언 루이스, &quot;Can&#39;t Hold Us&quot; ]</p>
]]></description>
        </item>
    </channel>
</rss>