<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jiseong_98.log</title>
        <link>https://velog.io/</link>
        <description>나 혼자 공부하고, 끄적이는 공간. (Node.JS / Back-End Developer)</description>
        <lastBuildDate>Mon, 26 May 2025 14:18:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jiseong_98.log</title>
            <url>https://velog.velcdn.com/images/jiseong_98/profile/68d18d32-79d3-4832-807c-ebf86f0f14d7/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jiseong_98.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jiseong_98" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[SQLD] 1과목. 데이터 모델과 성능]]></title>
            <link>https://velog.io/@jiseong_98/SQLD-1%EA%B3%BC%EB%AA%A9.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EC%84%B1%EB%8A%A5</link>
            <guid>https://velog.io/@jiseong_98/SQLD-1%EA%B3%BC%EB%AA%A9.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EA%B3%BC-%EC%84%B1%EB%8A%A5</guid>
            <pubDate>Mon, 26 May 2025 14:18:45 GMT</pubDate>
            <description><![CDATA[<h2 id="part-1-성능-데이터-모델링의-개요">PART 1. 성능 데이터 모델링의 개요</h2>
<h3 id="✅-성능-데이터-모델링">✅ 성능 데이터 모델링</h3>
<p>: DB 성능향상을 목적으로 설계 단계의 데이터 모델링 때부터 정규화, 반정규화, 테이블 통합, 테이블 분할, 조인구조, PK, FK 등 여러가지 성능과 관련된 사항이 데이터 모델링에 반영될 수 있도록 하는 것.</p>
<p><strong>🔹 수행 시점</strong>
: 분석/설계 단계에서 데이터 모델에 성능을 고려한 데이터 모델링을 수행할 경우 성능 저하에 따른 재업무 비용을 최소화 할 수 있는 기회를 가지게 된다.
데이터의 증가가 빠를수록 성능 저하에 따른 성능개선비용은 기하급수적으로 증가하게 된다.
⚠️ 성능 데이터 모델링 시점이 늦어질수록 재업무 비용이 증가한다.</p>
<p><strong>🔹 성능 데이터 모델링 고려사항 순서</strong></p>
<ol>
<li>데이터 모델링을 할 때 정규화를 정확하게 수행해야 한다.</li>
<li>DB 용량산정을 수행한다.</li>
<li>DB에 발생되는 트랜잭션의 유형을 파악한다.</li>
<li>용량과 트랜잭션의 유형에 따라 반정규화를 수행한다.</li>
<li>이력 모델의 조정, PK/FK 조정, 슈퍼/서브타입을 조정해야 한다.</li>
<li>성능 관점에서 데이터 모델을 검증한다.</li>
</ol>
<p>→ 기본적으로 데이터는 속성간의 함수종속성에 근거하여 정규화되어야 한다. 정규화는 선택이 아니라 필수사항이다.</p>
<p><strong>🔹 함수적 종속성 (FD, Functional Dependency)</strong></p>
<ul>
<li>데이터들이 어떤 기준 값에 의해 종속되는 현상</li>
<li>결정자와 종속자의 관계이다.</li>
<li>결정자의 값으로 종속자의 값을 알 수 있다.</li>
</ul>
<p><strong>✔️ 다치 종속(MVD, Multivalued Dependency)</strong>: 여러 컬럼이 동일한 결정자의 종속자일 때</p>
<hr>
<h2 id="part-2-정규화와-성능">PART 2. 정규화와 성능</h2>
<h3 id="✅-정규화-normalization">✅ 정규화 (Normalization)</h3>
<p>: 반복적인 데이터를 분리하고 각 데이터가 종속된 테이블에 적절하게 배치되도록 하는 것.
컬럼에 의한 반복, 중복적인 속성 값을 갖는 형태는 1차 정규화의 대상이다.
이상형상(anomaly)를 제거한다.</p>
<p><strong>✔️ 정규형 (NF, Normal Form)</strong>: 정규화로 도출된 데이터 모델이 갖춰야 할 특성</p>
<h3 id="✅-정규화-이론">✅ <strong>정규화 이론</strong></h3>
<ol>
<li>1차, 2차, 3차, 보이스코드 정규화는 함수적 종속성에 근거한다.</li>
<li>4차 정규화는 다치 종속을 제거한다.</li>
<li>5차 정규화는 조인에 의한 이상현상을 제거하여 정규화를 수행한다.</li>
</ol>
<p><strong>🔹 1차 정규화</strong></p>
<ul>
<li>속성의 원자성을 확보한다.</li>
<li>다중값 속성을 분리한다.</li>
</ul>
<p><strong>🔹 2차 정규화</strong></p>
<ul>
<li>부분 함수 종속성을 제거한다.</li>
<li>일부 기본키에만 종속된 속성을 분리한다.</li>
<li>기본키가 하나의 컬럼일 때 생략 가능하다.</li>
</ul>
<p><strong>🔹 3차 정규화</strong> </p>
<ul>
<li>이행 함수 종속성을 제거한다. 
→ 기본키 이외에 다른 컬럼이 그 외에 다른 컬럼을 결정할 수 없다는 것을 의미한다.</li>
<li>서로 종속관계가 있는 일반속성을 분리한다.</li>
<li>주식별자와 관련성이 가장 낮다.</li>
</ul>
<p><strong>🔹 보이스코드 정규화(BCNF, Boyee-codd Normal Form)</strong></p>
<ul>
<li>후보키가 기본키 속성 중 일부에 함수적 종속일 때 다수의 주식별자를 분리한다.
→ 3차 정규화를 진행한 테이블에 대해 모든 결정자가 테이블을 분해하는 것</li>
</ul>
<p><strong>🔹 4차, 5차 정규화</strong> : 다치 종속 분리, 결합 종속 분리</p>
<h3 id="✅-정규화와-성능">✅ <strong>정규화와 성능</strong></h3>
<p>: 정규화는 입출력 데이터의 양을 줄여 성능을 향상시킨다.</p>
<p><strong>🔹 정규화로 인한 성능 향상</strong> 
    : 입력 / 수정 / 삭제 시 성능은 항상 향상된다.</p>
<ul>
<li>유연성 증가: High Cohesion &amp; Coupling 원칙에 충실해진다.<ul>
<li>응집도(Cohesion): 테이블 내부의 속성들이 얼마나 하나의 주제를 잘 나타내고 서로 밀접하게 관련되어 있는지를 의미한다.
→ 즉, 하나의 테이블이 얼마나 잘 정의된 단일 목적을 가지는지에 대한 척도이다.</li>
<li>결합도(Coupling): 테이블들이 서로 얼마나 강하게 의존하고 있는지를 나타낸다.
→ 외래키(Foreign Key)를 통한 참조 관계의 정도와 유형으로 평가될 수 있다.</li>
</ul>
</li>
<li>재활용 가능성 증가: 개념이 세분화 된다.</li>
<li>데이터 중복 최소화</li>
</ul>
<p><strong>🔹 정규화로 인한 성능 저하</strong> 
      : 조회 시 처리 조건에 따라 성능 저하가 발생할 수도 있다.</p>
<ul>
<li>데이터 조회 시 조인을 유발하여 CPU와 메모리를 많이 사용하게 된다.<ul>
<li>반정규화로 해결 가능하다.</li>
<li>조인이 발생하더라도 인덱스를 사용하여 조인 연산을 수행하면 성능 상 단점이 거의 없고, 정규화를 통해 필요한 인덱스의 수를 줄일 수 있다.</li>
<li>정규화를 통해 소량의 테이블이 생성된다면 성능 상 유리할 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="part-3-반정규화와-성능">PART 3. 반정규화와 성능</h2>
<h3 id="✅-반정규화-denormalization">✅ 반정규화 (Denormalization)</h3>
<p>: 정규화된 엔터티, 속성, 관계에 대해 시스템의 성능향상과 개발과 운영의 단순화를 위해 중복, 통합, 분리 등을 수행하는 데이터 모델링의 기법이다.
→ 일반적으로 정규화시 입력 / 수정 / 삭제 성능이 향상되며 반정규화시 조인 성능이 향상된다.</p>
<p>: 데이터 중복을 허용하여 조인을 중리는 DB 성능 향상 방법이다.
→ 데이터의 무결성을 희생하고 조회 성능을 향상시킨다.</p>
<h3 id="✅-반정규화-절차">✅ 반정규화 절차</h3>
<p>🔹 <strong>반정규화 대상 조사</strong> (데이터 처리 범위, 처리 빈도수, 통계성 등)</p>
<ol>
<li>자주 사용되는 테이블에 접근하는 프로세스의 수가 많고 항상 일정한 범위만을 조회하는 경우</li>
<li>테이블의 대량의 데이터가 있고 대량의 데이터 범위를 자주 처리하는 경우에 처리범위를 일정하게 줄이지 않으면 성능을 보장할 수 없는 경우</li>
<li>통계성 프로세스에 의해 통계 정보를 필요로 할 때 별도의 통계 테이블을 생성한다.</li>
<li>테이블에 지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 기술적으로 어려울 경우</li>
</ol>
<p>SORTING, ORDER BY는 반정규화 대상 ❌</p>
<p>🔹 <strong>다른 방법 유도 검토</strong> (뷰, 클러스터링, 인덱스, 애플리케이션)</p>
<ol>
<li><p>지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 기술적으로 어려울 경우 VIEW를 사용한다.</p>
</li>
<li><p>대량의 데이터처리나 부분처리에 의해 성능이 저하되는 경우 클러스터링을 적용하거나 인덱스를 조정한다.</p>
<p> ✔️ 클러스터링(Clustering): 물리적인 저장 효율성과 조회(SELECT) 성능 향상을 목적으로 사용되는 기법.
 물리적인 저장 구조를 변경하여 관련있는 데이터를 디스크 상에서 가깝게 배치함으로써 I/O(입출력) 효율을 높이는 전략. </p>
</li>
<li><p>대량의 데이터는 PK의 성격에 따라 부분적인 테이블로 분리할 수 있다. (파티셔닝 기법)</p>
</li>
<li><p>응용 애플리케이션에서 로직을 구사하는 방법을 변경함으로써 성능을 향상시킬 수 있다.</p>
</li>
</ol>
<p>🔹 <strong>반정규화 적용</strong> (정규화 수행 후 반정규화 수행 → 테이블, 속성, 관계 반정규화)</p>
<h3 id="✅-반정규화-기법테이블-컬럼-관계">✅ 반정규화 기법(테이블, 컬럼, 관계)</h3>
<p><strong>🔶 테이블 반정규화</strong></p>
<p>  <strong>🔹 테이블 병합</strong> ( 1:1 관계, 1:M 관계, 슈퍼/서브타입 )</p>
<ol>
<li>1:1 관계를 통합하여 성능 향상</li>
<li>1:M 관계를 통합하여 성능 향상 - 많은 데이터 중복 발생</li>
<li>슈퍼/서브 관계를 통합하여 성능 향상</li>
</ol>
<p>  <strong>🔹 테이블 분할</strong> ( 수직 분할, 수평 분할 )</p>
<ol>
<li><strong>수직 분할</strong> : 컬럼 단위 테이블을 디스크 I/O를 분산처리하기 위해 테이블을 1:1로 분리하여 성능 향상</li>
<li><strong>수평 분할</strong> : 로우 단위로 집중 발생되는 트랜잭션을 분석하여 디스크 I/O 및 데이터 접근의 효율성을 높여 성능을 향상하기 위해 로우 단위로 테이블을 쪼갠다.</li>
</ol>
<p>  <strong>🔹 테이블 추가</strong> ( 중복, 통계, 이력, 부분 테이블 추가 )</p>
<ol>
<li><strong>중복</strong> : 다른 업무이거나 서버가 다른 경우 <strong>동일한 테이블 구조를 중복</strong>하여 원격 조인을 제거하여 성능을 향상시킨다.</li>
<li><strong>통계</strong> : SUM, AVG 등을 미리 수행하여 계산해 둠으로써 조회 시 성능을 향상시킨다.</li>
<li><strong>이력</strong> : 이력 테이블 중에서 마스터 테이블에 존재하는 레코드를 중복하여 이력 테이블에 존재시켜 성능을 향상시킨다.</li>
<li><strong>부분</strong> : 하나의 테이블의 전체 컬럼 중 자주 이용하는 집중화된 컬럼들이 있을 때 디스크 I/O를 줄이기 위해 해당 컬럼들을 모아놓은 별도의 반정규화된 테이블을 생성한다.
→ 즉, 자주 이용하는 컬럼으로 구성된 테이블을 생성한다.</li>
</ol>
<p><strong>🔶 컬럼 반정규화</strong> ( 중복, 파생, 이력, PK, 오작동 )</p>
<ol>
<li><strong>중복</strong> : 조인에 의해 처리할 때 성능저하를 예방하기 위해 중복된 컬럼을 위치시킨다.</li>
<li><strong>파생</strong> : 트랜잭션이 처리되는 시점에 계산에 의해 발생되는 성능저하를 예방하기 위해 미리 값을 계산하여 컬럼에 보관한다.
→ 즉, 필요한 값 미리 계산한 컬럼을 추가한다.</li>
<li><strong>이력테이블</strong> : 대량의 이력데이터를 처리할 때 불특정 날 조회나 최근 값을 조회할 때 나타날 수 있는 성능저하를 예방하기 위해 이력테이블에 가능성 칼럼(최근값 여부, 시작과 종료일자 등)을 추가한다.</li>
<li><strong>PK에 의한 컬럼 추가</strong> : 이미 PK안에 데이터가 존재하지만 성능향상을 위해 일반속성으로 포함하는 방법</li>
<li><strong>응용시스템 오작동을 위한 컬럼 추가</strong> : 업무적으로는 의미가 없지만 사용자의 실수로 원래 값으로 복구하기 원하는 경우 이전 데이터를 임시적으로 중복하여 보관하는 기법이다.</li>
</ol>
<p><strong>🔶 관계 반정규화</strong> : 데이터 무결성 보장 가능</p>
<ul>
<li><strong>중복관계 추가</strong>
: 데이터를 처리하기 위한 여러 경로를 거쳐 조인이 가능하지만 이 때 발생할 수 있는 성능저하를 예방하기 위해 추가적인 관계를 맺는 방법</li>
</ul>
<hr>
<h2 id="part-4-대용량-데이터에-따른-성능">PART 4. 대용량 데이터에 따른 성능</h2>
<p>: 테이블 반정규화 중 테이블 분할 관련
🔹 블록 : 테이블의 데이터 저장 단위</p>
<h3 id="✅-대량-데이터-발생으로-인한-현상">✅ 대량 데이터 발생으로 인한 현상</h3>
<p>: 블록 I/O 획수 증가 → 디스크 I/O 가능성 상승 (디스크 I/O시 성능 저하)</p>
<p><strong>🔹 로우 체이닝 (Row Chaining)</strong>
: 로우의 길이가 너무 길어서 데이터 블록 하나에 데이터가 모두 저장되지 않고 두 개 이상의 블록에 걸쳐 하나의 로우가 저장되어 있는 형태</p>
<p><strong>🔹 로우 마이그레이션 (Row Migration)</strong>
: 데이터블록에서 수정이 발생하면 수정된 데이터를 해당 데이터 블록에서 저장하지 못하고 다른 블록의 빈 공간을 찾아 저장하는 방식
    → 로우 체이닝과 로우 마이그레이션이 발생하여 많은 블록에 데이터가 저장되면 DB 메모리에서 디스크 I/O가 발생할 때 많은 I/O가 발생하여 성능저하 발생.
트랜잭션을 분석하여 적절하게 1:1관계로 분리함으로써 성능향상이 가능하도록 해야 한다.</p>
<h3 id="✅-pk에-의해-테이블을-분할하는-방법-파티셔닝">✅ PK에 의해 테이블을 분할하는 방법 (파티셔닝)</h3>
<p><strong>🔹파티셔닝 (Partitioning)</strong>
: 테이블 수평분할 기법. 논리적으로는 하나의 테이블이지만 물리적으로 여러 데이터 파일에 분산 저장, 데이터 조회 범위를 줄여 성능 향상</p>
<ol>
<li><p><strong>RANGE PARTITION</strong> : 대상 테이블이 날짜 또는 숫자 값으로 분리가 가능하고 각 영역별로 트랜잭션이 분리되는 경우 
즉, 데이터 값의 범위를 기준으로 분할</p>
<pre><code class="language-sql">// ex)
 PARTITION BY COL1
 ORDER BY COL3
 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW</code></pre>
</li>
<li><p><strong>LIST PARTITON</strong> : 지점, 사업소 등 핵심적인 코드값으로 PK가 구성되어 있고 대량의 데이터가 있는 테이블의 경우
즉, 특정한 값을 기준으로 분할</p>
</li>
<li><p><strong>HASH PARTITION</strong> : 지정된 HASH 조건에 따라 해시 알고리즘이 적용되어 테이블이 분리
즉, 해시 함수를 적용하며 분할, DBMS가 알아서 분할 관리, 데이터 위치를 알 수 없음</p>
</li>
<li><p><strong>COMPOSITE PARTITON</strong> : 여러 파티션 기법을 복합적으로 사용하여 분할</p>
</li>
</ol>
<p><strong>※ 파티션 인덱스 (Partition Index)</strong></p>
<ul>
<li>Global Index, Local Index : 여러 파티션에서 단일 인덱스 사용, 파티션 별로 각자 인덱스 사용</li>
<li>Prefixed Index, Non-Prefixed Index : 파티션키와 인덱스키 동일, 파티션키와 인덱스키 구분</li>
</ul>
<h3 id="✅-테이블에-대한-수평수직분할의-절차">✅ 테이블에 대한 수평/수직분할의 절차</h3>
<ol>
<li>데이터 모델링을 완성한다.</li>
<li>DB 용량산정을 한다.</li>
<li>대량 데이터가 처리되는 테이블에 대해 트랜잭션 처리 패턴을 분석한다.</li>
<li>컬럼 단위로 집중화된 처리가 발생하는지, 로우 단위로 집중된 처리가 발생하는지 분석하여 집중화된 단위로 테이블을 분리하는 것을 검토한다.</li>
</ol>
<hr>
<h2 id="part-5-db-구조와-성능">PART 5. DB 구조와 성능</h2>
<h3 id="✅-슈퍼타입--서브타입-데이터-모델-변환을-통한-성능-향상">✅ 슈퍼타입 / 서브타입 데이터 모델 변환을 통한 성능 향상</h3>
<p><strong>🔹 슈퍼/서브 타입 모델</strong>
: 속성을 항당하여 배치하는 수평 분할된 형태의 모델(공통 속성은 슈퍼타입으로 모델링하고 차이가 있는 속성은 서브타입으로 구분됨), 변환을 통해 </p>
<p>1) 정확하게 업무를 표현할 수 있고, 
2) 물리적 모델링 시 선택의 폭을 넓힐 수 있다.</p>
<p><strong>🔹 슈퍼/서브 타입 데이터 모델의 변환</strong></p>
<ul>
<li>슈퍼/서브타입에 대한 변환을 잘못하면 성능이 저하되는 이유
: 트랜잭션의 특성을 고려하지 않고 테이블을 설계했기 때문이다.</li>
<li>변환 기준 : 데이터 양, 트랜잭션 유형</li>
</ul>
<ol>
<li>트랜잭션은 항상 일괄로 처리하는데 테이블은 개별로 유지되어 Union 연산에 의해 성능이 저하될 수 있다.</li>
<li>트랜잭션은 항상 서브타입 개별로 처리하는데 테이블은 하나로 통합되어 있어 불필요하게 많은 양의 데이터가 집약되어 있어 성능이 저하되는 경우가 있다.</li>
<li>트랜잭션은 항상 슈퍼+서브 타입을 공통으로 처리하는데 개별로 유지되어 있거나 하나의 테이블로 집약되어 있어 성능이 저하되는 경우가 있다. </li>
</ol>
<p><strong>🔹 슈퍼/서브 타입 데이터 모델의 변환 기술</strong></p>
<ol>
<li>1:1 타입(OneToOne Type) : 개별로 발생되는 트랜잭션에 대해서는 개별 테이블로 구성한다.
슈퍼타입과 서브타입 각각 필요한 속성과 유형에 적합한 데이터만 가지도록 분리하여 1:1 관계를 갖도록 한다.</li>
<li>슈퍼/서브 타입(Plus Type) : 슈퍼타입과 서브타입을 공통으로 처리하는 트랜잭션에 대해 슈퍼타입과 서브타입 각각의 테이블로 구성한다.</li>
<li>All in One 타입(Single Type) : 전체를 하나로 묶어 트랜잭션이 발생할 때는 하나의 테이블로 구성한다.</li>
</ol>
<table>
<thead>
<tr>
<th></th>
<th>1:1 타입</th>
<th>슈퍼/서브 타입</th>
<th>All in One 타입</th>
</tr>
</thead>
<tbody><tr>
<td>특징</td>
<td>개별 테이블 유지</td>
<td>슈퍼/서브 타입 테이블 구성</td>
<td>단일 테이블 구성</td>
</tr>
<tr>
<td>트랜잭션 유형</td>
<td>개별 처리</td>
<td>슈퍼/서브 타입 공통 처리</td>
<td>일괄 처리</td>
</tr>
<tr>
<td>확장성</td>
<td>좋음 (테이블 추가 용이)</td>
<td>보통</td>
<td>나쁨</td>
</tr>
<tr>
<td>조인 성능</td>
<td>나쁨 (조인 많이 필요)</td>
<td>나쁨 (조인 많이 필요)</td>
<td>좋음</td>
</tr>
<tr>
<td>I/O 성능</td>
<td>좋음</td>
<td>좋음</td>
<td>나쁨 (항상 전체 데이터 조회)</td>
</tr>
<tr>
<td>관리용이성</td>
<td>나쁨</td>
<td>나쁨</td>
<td>좋음</td>
</tr>
</tbody></table>
<h3 id="✅-pkfk-db-성능-향상">✅ PK/FK DB 성능 향상</h3>
<p><strong>🔹 컬럼 순서 조절을 통한 성능 향상</strong></p>
<ul>
<li>인덱스의 특징은 여러 개의 속성이 하나의 인덱스로 구성되어 있을 때 앞쪽에 위치한 속성의 값이 비교자로 있어야 좋은 효율을 나타낸다.</li>
<li>앞쪽에 위치한 속성의 값이 가급적 ‘=’ 아니면 최소한 범위 ‘BETWEEN’ ‘&lt;&gt;’가 들어와야 효율적이다.
→ 여러 조건이 있을 경우 등호 조건이 걸리는 컬럼을 선두로 이동</li>
</ul>
<p><strong>🔹 인덱스 특성을 고현한 성능 향상</strong></p>
<ul>
<li>물리적인 테이블에 FK 제약 걸었을 때는 반드시 FK 인덱스를 생성하도록 하고,</li>
<li>FK 제약이 걸리지 않았을 경우에는 FK인덱스를 생성하는 것을 기본정책으로 하되,</li>
<li>발생되는 트랜잭션에 의해 겨의 활동되지 않았을 때에만 FK인덱스를 지우는 방법으로 하는 것이 적절한 방법이다.
  → FK에도 인덱스를 생성할 필요가 있다.</li>
</ul>
<hr>
<h2 id="part-6-분산-db-데이터에-따른-성능">PART 6. 분산 DB 데이터에 따른 성능</h2>
<h3 id="✅-분산-db">✅ 분산 DB</h3>
<ol>
<li>분산된 DB를 하나의 가성 시스템을 사용할 수 있도록 한 DB</li>
<li>논리적으로 동일한 시스템에 속하지만, 컴퓨터 네트워크를 통해 물리적으로 분산되어 있는 데이터 집합</li>
<li>과거에는 위치 중심이었으나 현재는 업무 필요에 따라 분산 설계</li>
</ol>
<p><strong>🔹 설계 방식</strong></p>
<ul>
<li>상향식 : 지역 스키마 작성 후 전역 스키마 작성</li>
<li>하향식 : 전역 스키마 작성 후 지역사상 스키마 작성</li>
</ul>
<p><strong>🔹 분산 DB 장단점</strong></p>
<ul>
<li>장점<ul>
<li>신뢰성과 가용성 증가</li>
<li>빠른 응답 속도와 통신비용 절감</li>
<li>용량 확장 용이</li>
<li>지역 자치성, 효율성, 융통성, 각 지역 사용자 요구 수용</li>
</ul>
</li>
<li>단점<ul>
<li>관리 및 통제 어려움</li>
<li>데이터 무결성 관리 어려움</li>
<li>S/W 개발 비용 및 처리 비용 증가</li>
<li>불규칙한 응답 속도</li>
<li>오류의 잠재성 증대</li>
</ul>
</li>
</ul>
<h3 id="✅-분산-db를-만족하기-위한-6가지-투명성-분위지중장병행">✅ 분산 DB를 만족하기 위한 6가지 투명성 (분위지중장병행)</h3>
<ol>
<li><strong>분할 투명성(단편화)</strong> : 하나의 논리적 Relation이 여러 단편으로 분할되어 각 사본이 여러 site에 저장된다.</li>
<li><strong>위치 투명성</strong> : 사용하려는 데이터의 저장 장소가 명시되지 않아도 된다. <pre><code>              위치 정보가 시스템 카탈로그에 유지된다.</code></pre></li>
<li><strong>지역사상 투명성</strong> : 지역 DBMS와 물리적 DB 사이의 사상(Mapping)이 보장된다.</li>
<li><strong>중복 투명성</strong> : DB 객체가 여러 site에 중복 되어 있는지 알 필요가 없다.</li>
<li><strong>장애 투명성</strong> : 구성요소(DBMS, 컴퓨터)의 장애에 무관한 트랜잭션의 원자성이 유지된다.</li>
<li><strong>병행 투명성</strong> : 다수 트랜잭션 동시 수행시 결과의 일관성 유지된다. <pre><code>              병렬이 아니다.
              TimeStamp, 분산 2단계 Locking 이용</code></pre></li>
</ol>
<h3 id="✅-분산-db-적용-기법">✅ 분산 DB 적용 기법</h3>
<ol>
<li><strong>테이블 위치 분산</strong> : 설계된 테이블의 위치를 본사와 지사단위로 분산</li>
<li><strong>테이블 분할 분산(Table Fragmentation)</strong> : 각각의 테이블을 쪼개어 분산<ul>
<li>수평 분할: 로우 단위로 분리</li>
<li>수직 분할: 컬럼 단위로 분리</li>
</ul>
</li>
<li><strong>테이블 복제 분산(Table Replication)</strong> 
: 동일한 테이블을 다른 지역이나 서버에서 동시에 생성하여 관리하는 유형
원격지 조인을 내부 조인으로 변경하여 성능을 향상시킨다.<ul>
<li>부분복제: 마스터 DB에서 테이블의 내용만 다른 지역이나 서버에 위치</li>
<li>광역복제: 마스터 DB 테이블의 내용을 각 지역이나 서버에 존재</li>
</ul>
</li>
<li><strong>테이블 요약 분산(Table Summarization)</strong> 
: 지역 간에 또는 서버 간에 데이터가 비슷하지만 서로 다른 유형으로 존재하는 경우<ul>
<li>분석요약: 동일한 테이블 구조를 가지고 있으면서 분산되어 있는 동일한 내용의 데이터를 이용하여 통합된 데이터를 산출하는 방식</li>
<li>통합요약: 분산되어 있는 다른 내용의 데이터를 이용하여 통합된 데이터를 산출하는 방식
→ 다른 내용은 반드시 테이블 구조가 다르다는 이미뿐 아니라, 각 데이터가 다루는 업무 영역이나 의미가 다르다는 것을 포함할 수 있다.</li>
</ul>
</li>
</ol>
<h3 id="✅-분산-db-설계를-고려해야-하는-경우">✅ 분산 DB 설계를 고려해야 하는 경우</h3>
<ol>
<li>성능이 중요한 사이트</li>
<li>공통코드, 기준정보, 마스터 데이터의 성능 향상</li>
<li>실시간 동기화가 요구되지 않는 경우.
거의 실시간의 업무적인 특징을 가지고 있는 경우</li>
<li>특정 서버에 부하가 집중되어 부하를 분산</li>
<li>백업 사이트 구성하는 경우</li>
</ol>
<p>GIS(Global Single Instance)는 통합데이터 구조</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SQLD] 1과목. 데이터 모델링의 이해 요약 정리]]></title>
            <link>https://velog.io/@jiseong_98/SQLD-%EA%B3%BC%EB%AA%A9-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81%EC%9D%98-%EC%9D%B4%ED%95%B4-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jiseong_98/SQLD-%EA%B3%BC%EB%AA%A9-1.-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81%EC%9D%98-%EC%9D%B4%ED%95%B4-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sat, 24 May 2025 07:38:50 GMT</pubDate>
            <description><![CDATA[<p>오늘은 SQLD 자격증 시험을 준비하기 위해 데이터 모델링 내용에 대해 요약 정리를 해보려고 한다!</p>
<h2 id="part-1-데이터-모델의-이해">PART 1. 데이터 모델의 이해</h2>
<h3 id="✅-모델링이란">✅ 모델링이란?</h3>
<p>→ 현실세계를 단순화하여 표현하는 것.</p>
<p>🔹 <strong>모델링의 특징</strong></p>
<ul>
<li>추상화: 현실세계를 일정한 형식에 맞추어 표현</li>
<li>단순화: 현실세계를 <strong>약속된 규약</strong>에 의해 제한된 표기법이나 언어로 표현하여 쉽게 이해할 수 있도록하는 개념</li>
<li>명확화: 누구나 이해하기 쉽게 하기 위해 정확하게 현상을 기술하는 것</li>
</ul>
<p>🔹 <strong>모델링의 세가지 관점</strong></p>
<ul>
<li>데이터 관점: 업무와 데이터, 데이터간의 관계성</li>
<li>프로세스 관점: 업무가 실제로 무엇을 하는지 (진행)</li>
<li>데이터와 프로세스의 상관 관점: 처리하는 일의 방법에 따라 데이터가 어떤 영향을 받고 있는지</li>
</ul>
<h3 id="✅-데이터-모델링이란">✅ 데이터 모델링이란?</h3>
<p>→ 현실 세계의 데이터를 추상화하고 단순화하여 데이터베이스에 저장할 수 있도록 구조화 하는 과정</p>
<p><strong>🔹 목적:</strong> </p>
<ol>
<li>정보에 대한 표기법을 통일하여 업무 내용 분석 정확도 증대</li>
<li>데이터 모델을 기초로 DB 생성</li>
</ol>
<p><strong>🔹 기능:</strong>
가시화 / 명세화 / 구조화된 툴 제공 / 문서화 / 다양한 관점 제공 / 구체화</p>
<p><strong>🔹 중요성</strong></p>
<ul>
<li>파급효과(Leverage): 데이터 모델 변경이 전체 시스템에 미치는 영향이 큼. (초기에 정확하게 설계 중요)</li>
<li>간결한 표현(Conciseness): 정보 요구사항과 한계를 간결하게 표현하는 도구</li>
<li>데이터 품질<ul>
<li>유일성: 데이터 중복 저장 방지 (무결성 확보)</li>
<li>유연성: 데이터 정의와 데이터 사용 프로세스 분리 (시스템 변화에 유연하게 대응 가능)</li>
<li>일관성: 데이터의 일관성을 유지</li>
</ul>
</li>
</ul>
<p><strong>🔹 유의점</strong></p>
<ul>
<li>중복(Duplication): 동일한 정보가 여러 곳에 중복 저장되어 데이터 일관성이 깨질 우려</li>
<li>비유연성(Inflexibility): 사소한 업무변화에 데이터 모델이 수시로 변경되면 안됨</li>
<li>비일관성(Inoonsistency): 데이터 간의 참조 무결성이 깨지면 안됨</li>
</ul>
<p><strong>🔹 이해관계자</strong>: 
개발자 / DBA / 모델러 / 현업업무전문가, 완성된 모델을 정확히 해석할 수 있어야 함</p>
<h3 id="✅-데이터-모델링-3단계">✅ 데이터 모델링 3단계</h3>
<ol>
<li>개념적 모델링: <ul>
<li>엔티티와 속성을 도출하고 ERD를 작성한다.</li>
<li>업무 중심적이고 포괄적인 수준의 모델링이다.</li>
</ul>
</li>
<li>논리적 모델링: <ul>
<li>식별자를 도출하고 속성과 관계등을 정의한다.</li>
<li>정규화를 수행하여 데이터 모델의 독립성과 재사용성을 확보하고, 
논리 데이터 모델은 데이터 모델링 완료 상태이다.</li>
</ul>
</li>
<li>물리적 모델링: <ul>
<li>DB를 구축한다.</li>
<li>성능 및 보안 등 물리적인 성격을 고려한다.</li>
</ul>
</li>
</ol>
<p>👉🏻 프로젝트 생명주기(Life Cycle): 계획 &gt; 분석 &gt; 설계 &gt; 개발 &gt; 테스트 &gt; 전환/이행 단계로 구성된다.
      계획과 분석 단계는 <strong>개념적 모델링</strong>, 분석 단계는 <strong>논리적 모델링</strong>, 설계 단계는 <strong>물리적 모델링</strong>에 해당한다.</p>
<h3 id="✅-db의-3단계-구조">✅ DB의 3단계 구조</h3>
<p>→ 데이터 독립성 확보를 목표로 한다.
<img src="https://velog.velcdn.com/images/jiseong_98/post/d6bf5bb9-3cc0-472a-a07b-c2ee550ea170/image.png" alt=""></p>
<p><strong>🔹 DB 독립성의 필요성</strong>
→ 데이터의 중복성과 데이터 복잡도 증가로 인한 <strong>유지보수 비용 증가</strong>, <strong>요구사항 대응 저하</strong></p>
<p><strong>🔹 3층 스키마(3-level Schema)</strong></p>
<ul>
<li>외부 스키마: 개개 사용자가 보는 개인적 DB 스키마</li>
<li>개념 스키마: 모든 사용자 관점을 통합한 전체 DB</li>
<li>내부 스키마: 물리적 장치에서 데이터가 실질적 저장</li>
</ul>
<p><strong>🔹 데이터 독립성</strong></p>
<ul>
<li>논리적 독립성: 개념 스키마가 변경되어도 외부 스키마에 영향 ❌ (논리적 사상 없음)</li>
<li>물리적 독립성: 내부 스키마가 변경되어도 외부/개념 스키마는 영향 ❌ (물리적 사상 없음)</li>
</ul>
<p><strong>💡Mapping(사상)이란?</strong> 
→ 상호 독립적인 개념을 연결시켜주는 다리</p>
<p><strong>🔹 데이터 모델링 3요소</strong>
→ 엔터티(Entity), 관계(Relationship), 속성(Attribute)</p>
<h3 id="✅-erdentity-relationship-diagram">✅ ERD(Entity Relationship Diagram)</h3>
<p>[ 엔터티 도출 ] → [ 엔터티 배치 ] → [ 엔터티 간 관계 설정 ] → [ 관계명 기술 ]
→ [ 관계차수 표현( 1:1 , 1:N, M:N ) ] → [ 관계선택사양 표현: 필수 / 선택 ] </p>
<p>💡엔터티: 사각형, 관계: 마름모, 속성: 타원형</p>
<h3 id="✅-좋은-데이터-모델의-요소">✅ 좋은 데이터 모델의 요소</h3>
<ol>
<li>완전성: 업무에 필요한 모든 데이터가 모델에 정의</li>
<li>중복배제: 하나의 DB내에 동일한 사실은 한번만</li>
<li>업무규칙: 많은 규칙을 사용자가 공유하도록 제공</li>
<li>데이터 재사용: 데이터가 독립적으로 설계돼야 함</li>
<li>의사소통: 업무규칙은 엔터티, 서브타입, 속성, 관계 등의 형태로 최대한 자세히 표현</li>
<li>통합성: 동일한 데이터는 한 번만 정의. 참조활용</li>
</ol>
<hr>
<h2 id="part-2-엔터티">PART 2. 엔터티</h2>
<p>: 업무에서 관리해야 하는 데이터의 집합, 명사형, 인스턴스의 집합</p>
<p><strong>🔹 엔터티의 특징</strong></p>
<ol>
<li>반드시 해당 업무에서 필요하고 관리하고자 함</li>
<li>유일한 식별자에 의해 식별 가능</li>
<li>두 개 이상의 인스턴스의 집합</li>
<li>업무 프로세스에 의해 이용되어야 함</li>
<li>반드시 속성이 있어야 함 
(예외적으로 관계엔터티의 경우는 주식별자 속성만 가지고 있어도 엔터티로 인정)</li>
<li>다른 엔터티와 최소 1개 이상의 관계가 있어야 함.
(관계를 생략하여 표현해야하는 경우는 통계성 엔터티, 코드성 엔터티, 시스템 처리시 
내부 필요에 의한 엔터티 도출과 같은 경우)</li>
</ol>
<h3 id="✅-엔터티의-분류">✅ 엔터티의 분류</h3>
<p><strong>🔹 유무형에 따른 분류:</strong> 유형, 개념, 사건 엔터티</p>
<ul>
<li>유형 엔터티: 물리적 형태가 있고, 안정적이고 지속적으로 활용되는 엔터티
ex) 사원, 물품, 강사 등</li>
<li>개념 엔터티: 물리적 형태가 없고, 개념적인 정보를 담고 있는 엔터티
ex) 조직, 보험 상품 등</li>
<li>사건 엔터티: 업무 수행시 발생하는 엔터티. 주로 특정 행위의 발생을 기록한다.
ex) 주문, 청구, 미납 등</li>
</ul>
<p><strong>🔹 발생 시점에 따른 분류</strong>: 기본/키, 중심, 행위 엔터티</p>
<ul>
<li>기본 엔터티(Key Entity): 원래 존재하는 정보, 타엔터티의 부모 역할, 자신의 고유한 주식별자를 가진다.
ex) 사원, 부서 등</li>
<li>중심 엔터티(Main Entity): 기본 엔터티로부터 발생한다. 다른 엔터티와의 관계로 많은 행위 엔터티를 생성한다. 
ex) 계약, 사고, 주문 등</li>
<li>행위 엔터티(Active Entity): 2개 이상의 부모 엔터티로부터 발생하며, 내용이 자주 바뀌거나 양이 증가한다.
ex) 주문 목록, 사원 변경 이력 등</li>
</ul>
<h3 id="✅-엔터티의-명명규칙">✅ 엔터티의 명명규칙</h3>
<ol>
<li>현업 업무에서 사용하는 용어를 사용한다. </li>
<li>약어 사용을 지양한다.</li>
<li>단수 명사를 사용한다.</li>
<li>고유한 이름 사용한다. (유일성 보장)</li>
<li>생성 의미대로 부여한다. (명확성)</li>
</ol>
<hr>
<h2 id="part-3-속성">PART 3. 속성</h2>
<p>: 인스턴스의 구성요소로, 엔터티가 가지는 의미상 분리되지 않는 최소의 단위이다.</p>
<h3 id="✅-엔터티와-인스턴스-및-속성과-속성값-간의-관계">✅ 엔터티와 인스턴스 및 속성과 속성값 간의 관계</h3>
<p><img src="https://velog.velcdn.com/images/jiseong_98/post/2d87551b-58f4-4502-aada-805aa0420f64/image.png" alt=""></p>
<ul>
<li>한 개의 엔터티는 2개 이상의 인스턴스 집합이다.</li>
<li>한 개의 인스턴스는 2개 인상의 속성을 가진다.</li>
<li>한 개의 속성은 1개의 속성값을 가진다.</li>
</ul>
<h3 id="✅-속성-표기법">✅ 속성 표기법</h3>
<p>: IE 표기법, Barker 표기법</p>
<h3 id="✅-속성의-특징">✅ 속성의 특징</h3>
<ol>
<li>업무에서 필요하고 관리하고자 하는 정보이다.</li>
<li>주식별자에 함수적으로 종속된다.</li>
<li>속성값을 하나만 가진다. 
(하나 이상의 속성값이면 정규화가 필요하다.)</li>
</ol>
<h3 id="✅-속성의-분류">✅ 속성의 분류</h3>
<p><strong>🔹 특성에 따른 분류</strong></p>
<ul>
<li>기본 속성: 비즈니스 프로세스에서 도출되는 본래의 속성</li>
<li>설계 속성: 데이터 모델링 과정에서 업무 규칙화를 위해 발생하는 속성. ex) 일련 번호</li>
<li>파생 속성: 다른 속성에 의해 만들어지는 속성. 빠른 성능을 낼 수 있도록 원래 속성의 값을 계산한다.<pre><code>          (↔ 저장 속성은 유도 속성을 생성하는데 사용되는 속성)</code></pre></li>
</ul>
<p><strong>🔹 분해 가능 여부에 따른 분류</strong></p>
<ul>
<li>단일 속성: 하나의 의미</li>
<li>복합 속성: 여러 의미, 단일 속성으로 분해 가능. ex) 주소</li>
<li>다중값 속성: 여러 값, 엔터티로 분해 가능</li>
</ul>
<p><strong>🔹 엔터티 구성방식에 따른 분류</strong></p>
<ul>
<li>기본키 속성(Primary Key): 엔터티를 식별할 수 있는 속성</li>
<li>외래키 속성(Foreign Key): 다른 엔터티와의 관계에서 포함된 속성</li>
<li>일반 속성: 엔터티에 포함되고 PK나 FK 속성이 아닌 속성</li>
</ul>
<p><strong>💡도메인:</strong> 각 속성이 가질 수 있는 값의 범위. ex) 5글자</p>
<h3 id="✅-속성의-명명규칙">✅ 속성의 명명규칙</h3>
<ol>
<li>해당업무에서 사용하는 이름 부여</li>
<li>서술식 속성명은 사용 금지</li>
<li>약어 사용 금지</li>
<li>전체 데이터모델에서 유일성 확보</li>
</ol>
<hr>
<h2 id="part-4-관계">PART 4. 관계</h2>
<p>: 엔터티의 인스턴스 사이의 논리적인 연관성으로서 존재의 형태로서나 행위로서 서루에게 연관성이 부여된 상태</p>
<ul>
<li>존재에 의한 관계. ex) 소속된다.</li>
<li>행위에 의한 관계. ex) 주문한다.</li>
</ul>
<p><strong>🔹 관계의 페어링</strong>
: 엔터티 안에 인스턴스가 개별적으로 관계를 가지는 것.</p>
<p><strong>🔹 UML의 연관 관계, 의존 관계</strong></p>
<ul>
<li>연관(존재적) 관계: 항상 이용하는 관계</li>
<li>의존 관계: 상대방, 행위에 의해 발생하는 관계</li>
</ul>
<h3 id="✅-관계의-표기법">✅ 관계의 표기법</h3>
<p><strong>🔹 관계명</strong>: 관계의 이름
<strong>🔹 관계 차수</strong>: 관계 내 튜플의 전체 개수, 1은 직선 N은 삼발로 표시
    → M:N 관계: 관계형 DB에서 M:N 관계의 조인은 카테시안 곱이 발생한다.
<strong>🔹 관계 선택성(관계선택사양, Optionality)</strong>: 필수는 1 선택은 0으로 표시</p>
<h3 id="✅-관계의-종류">✅ 관계의 종류</h3>
<p><strong>🔹 ERD 기준</strong>: 존재적 관계와 행위에 의한 관계를 구분하지 않고 표기한다.</p>
<ul>
<li>존재 관계: 엔터티 간의 상태</li>
<li>행위 관계: 엔터티 간에 발생하는 행위</li>
</ul>
<p><strong>🔹 UML(Unified Modeling Language) 기준</strong></p>
<ul>
<li>연관 관계(Association): 실선 표기</li>
<li>의존 관계(Dependency): 점선 표기</li>
</ul>
<p><strong>🔹 식별자에 따른 분류</strong></p>
<ul>
<li><p>식별 관계: 부모 엔터티의 식별자를 자식 엔터티에서 주식별자로 사용
  ※ 약한 엔터티: 부모 엔터티에 종속되어 존재 (↔ 강한 엔터티는 독립적으로 존재한다.)</p>
</li>
<li><p>비식별 관계: 부모 엔터티의 식별자를 자식 엔터티에서 일반 컬럼으로 참조 사용, 약한 종속 관계
  ※ 식별 관계만으로 연결되면 주식별자 수가 많아질 수밖에 없으므로
  관계 강약 분석, 자식 엔터티의 독릭 PK 필요성, SQL 복잡성과 개발 생산성 고려 필요.</p>
</li>
</ul>
<h3 id="✅-관계-체크사항">✅ 관계 체크사항</h3>
<ol>
<li>2개의 엔터티 사이에 관심이 있는 연관 규칙이 존재하는가</li>
<li>2개의 엔터티 사이에 정보의 조합이 발생하는가</li>
<li>업무기술서, 장표에 관계 연결에 대한 규칙을 서술 하였는가</li>
<li>업무기술서, 장표에 관계 연결을 가능하게 하는 동사가 존재하는가</li>
</ol>
<hr>
<h2 id="part-5-식별자">PART 5. 식별자</h2>
<p>: 엔터티 내에서 인스턴스를 구분하는 구분자.</p>
<ul>
<li>식별자는 논리 데이터 모델링 단계에서 사용</li>
<li>Key는 물리 데이터 모델링 단계에서 사용</li>
</ul>
<h3 id="✅-식별자의-특징">✅ 식별자의 특징</h3>
<ul>
<li><strong>유일성:</strong> 주식별자에 의해 모든 인스턴스들이 유일하게 구분할 수 있다.</li>
<li><strong>최소성:</strong> 주식별자를 구성하는 속성의 수는 유일성을 만족하는 최소의 수가 되어야 한다.</li>
<li><strong>불변성:</strong> 지정된 주식별자의 값은 자주 변하지 않아야 한다.</li>
<li><strong>존재성:</strong> 주식별자가 지정이 되면 반드시 값이 들어와야 한다.</li>
</ul>
<h3 id="✅-식별자-분류">✅ 식별자 분류</h3>
<p><strong>🔹 대표성 여부에 따른 분류</strong>: 주식별자, 보조식별자</p>
<ul>
<li>주식별자: 대표성을 만족하는 식별자, 타 엔터티와 참조 관계를 연결할 수 있다.</li>
<li>보조식별자: 유일성과 최소성만 만족하는 식별자로, 참조 관계 연결에 사용할 수 없다.</li>
</ul>
<blockquote>
<p><strong>⚙️ DB 키의 종류</strong></p>
</blockquote>
<ul>
<li>기본키(PK, Primary Key): 엔터티를 대표하는 키, 후보키 중 선정된다.</li>
<li>후보키: 유일성과 최소성을 만족하는 키</li>
<li>슈퍼키: 유일성만 만족하는 키</li>
<li>외래키(FK, Foreign Key): 여러 테이블의 기본 키 필드, 
참조 무결성(Referential Integrity)을 확인하기 위해 사용됨 (허용된 데이터 값만 저장하기 위함)</li>
</ul>
<p><strong>🔹 생성 여부에 따른 분류</strong>: 내부식별자, 외부식별자</p>
<ul>
<li>내부 식별자: 자연스럽게 존재(스스로 생성되는)하는 식별자 (~ 본질 식별자)</li>
<li>외부 식별자: 다른 엔터티와의 관계를 통해 생성되는 식별자</li>
</ul>
<p><strong>🔹 속성 수에 따른 분류</strong>: 단일식별자, 복합식별자</p>
<ul>
<li>단일 식별자: 하나의 속성으로 구성</li>
<li>복합 식별자: 2개 이상의 속성으로 구성</li>
</ul>
<p><strong>🔹 대체 여부에 따른 분류</strong>: 본질식별자, 인조식별자</p>
<ul>
<li>본질 식별자: 업무에 의해 만들어지는 식별자, 대체될 수 없는 식별자</li>
<li>인조 식별자: 인위적으로 만들어지는 대체 가능한 식별자 (순서번호, Sequence Number)를 사용하여 생성된 식별자.
1) 후보 식별자 중 주식별자로 선정할 것이 없거나, 
2) 주식별자가 너무 많은 컬럼으로 구성되어 있을 때 사용</li>
</ul>
<h3 id="✅-주식별자-도출-기준">✅ 주식별자 도출 기준</h3>
<ol>
<li>해당 업무에서 자주 이용되는 속성</li>
<li>명칭, 내역 등과 같이 이름으로 기술되는 것들은 사용하지 않는다.</li>
<li>복합으로 주식별자로 구성할 경우 너무 많은 속성은 지양한다.</li>
</ol>
<h3 id="✅-식별자-관계">✅ 식별자 관계</h3>
<p><strong>🔹 식별자 관계(Identifying Relationship)</strong>
: 부모 엔터티의 주식별자가 자식 엔터티의 주식별자의 일부 또는 전부로 상속되는 관계</p>
<p><strong>✔️ 특징:</strong></p>
<ul>
<li>강한 연결: 부모가 없으면 자식이 존재할 수 없는 생명 주기의 종속성을 가진다.</li>
<li>표기: ERD에서는 실선으로 표기한다.</li>
</ul>
<p><strong>✔️ 주요 사용 경우:</strong></p>
<ol>
<li>부모로부터 받은 식별자를 자식 엔터티의 주식별자로 직접 활용하는 경우.</li>
</ol>
<p><strong>🔹 비식별자(Non-identifying Relationship)</strong>
: 부모 엔터티의 주식별자가 자식의 일반 속성으로 상속되는 관계</p>
<p><strong>✔️ 특징:</strong></p>
<ul>
<li>약한 연결: 부모 엔터티가 없어도 자식 엔터티가 독립적으로 존재할 수 있거나, <pre><code>          자식 엔터티의 식별 방식이 부모와 관계없이 독자적으로 정의되는 경우가 많다.</code></pre></li>
<li>표기: ERD에서는 점선으로 표기한다.</li>
</ul>
<p><strong>✔️ 주요 사용 경우:</strong></p>
<ol>
<li>부모 없는 자식이 생성될 수 있는 경우</li>
<li>부모와 자식의 생명주기가 다른 경우</li>
<li>여러 개의 엔터티가 하나의 엔터티로 통합되었는데 각각의 엔터티가 별도의 관계를 가진 경우</li>
<li>자식 엔터티에 별도의 주식별자를 생성하는 것이 더 유리한 경우</li>
<li>SQL 문장이 길어져 복잡성 증가되는 것을 방지</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT란?]]></title>
            <link>https://velog.io/@jiseong_98/JWT%EB%9E%80</link>
            <guid>https://velog.io/@jiseong_98/JWT%EB%9E%80</guid>
            <pubDate>Thu, 08 May 2025 01:38:37 GMT</pubDate>
            <description><![CDATA[<p>오늘은 JWT 토큰에 대해서 정리를 해보겠다.
프로젝트에서 JWT를 사용했는데 JWT가 무엇인지, 왜 사용하는지에대해 정리할것이다!</p>
<h1 id="jwt">JWT</h1>
<p>JWT(Json Web Token)은 사용자 인증 및 권한 부여에 사용되는 웹 표준 방식으로, 클라이언트와 서버 간에 정보를 안전하게 전송하는데 사용된다.</p>
<p>세션 기반 인증 방식과 달리, JWT는 토큰에 필요한 정보를 담고 있어 서버에서 세션을 관리할 필요가 없어 확장성이 좋고 서버 부하를 줄일 수 있다.</p>
<h2 id="jwt의-특징">JWT의 특징</h2>
<h3 id="1-stateless-무상태">1. Stateless (무상태)</h3>
<ul>
<li>서버가 세션 상태를 별도로 저장할 필요 없이, 토큰 자체에 사용자 정보를 담아 처리할 수 있다.</li>
</ul>
<h3 id="2-안정성-url-safe">2. 안정성 (URL-safe)</h3>
<ul>
<li>Base64Url 인코딩을 사용해 HTTP 환경에서도 안전하게 주고받을 수 있다.</li>
</ul>
<h3 id="3-간편성-self-contained">3. 간편성 (self-contained)</h3>
<ul>
<li>필요한 인증 정보가 토큰 자체에 포함되어 있어, 별도의 데이터베이스 조회 없이도 사용자 상태를 판단할 수 있다.</li>
</ul>
<h3 id="4-가볍고-효율적-compact">4. 가볍고 효율적 (Compact)</h3>
<ul>
<li>짧은 문자열로 인코딩되어 HTTP 헤더, URL 파라미터 등에 부담 없이 실을 수 있다.</li>
</ul>
<h2 id="jwt의-구성-요소">JWT의 구성 요소</h2>
<p>JWT는 Header, Payload, Signature 3가지로 구성이 되어있으며, 각 구성요소는 JSON 객체를 Base64Url로 인코딩되어 표현되고, 인코딩된 각 구성요소를 점(.)으로 구분한다.</p>
<h3 id="헤더-header">헤더 (Header)</h3>
<p>Header는 토큰의 타입(JWT)과 서명에 사용된 알고리즘(HS256 등)이 들어있다.</p>
<pre><code class="language-tsx">{
  &quot;alg&quot;: &quot;HS256&quot;,     // 서명에 사용된 알고리즘 방식을 지정
  &quot;typ&quot;: &quot;JWT&quot;,       // 토큰의 타입을 지정
}</code></pre>
<h3 id="페이로드-payload">페이로드 (Payload)</h3>
<p>Payload는 실제로 담고자 하는 “클레임(Claim)”을 포함한다.
클레임은 토큰에 담기는 정보 조각을 의미하며, 다음과 같은 종류가 있다.</p>
<ul>
<li><strong>등록된(Registered) 클레임</strong>: 토큰 처리에 일반적으로 사용되는 표준 클레임<ul>
<li>예: <code>iss</code>(발급자), <code>exp</code>(만료시간), <code>sub</code>(주제), <code>aud</code>(대상자)</li>
</ul>
</li>
<li><strong>공개(Public) 클레임</strong>: 자유롭게 정의할 수 있는 공용 정보<ul>
<li>예: <code>userId</code>, <code>email</code>, <code>role</code></li>
</ul>
</li>
<li><strong>비공개(Private) 클레임</strong>: 클라이언트와 서버 간에 별도로 합의된 사용자 정의 정보</li>
</ul>
<pre><code class="language-tsx">// Base64Url로 인코딩 될 Payload JSON 객체 정보
{
  &quot;sub&quot;: &quot;1234567890&quot;,
  &quot;name&quot;: &quot;Hong gildong&quot;,
  &quot;admin&quot;: true,
}
// 실제 JWT 토큰의 payload를 디코딩한 정보
{
  &quot;userId&quot;:&quot;67f668ebe24ea6e057939449&quot;,
  &quot;username&quot;:&quot;admin&quot;,
  &quot;iat&quot;:1745844070,     // exp - iat = 21600( 6시간 = 21600초 )
  &quot;exp&quot;:1745865670,
}</code></pre>
<h3 id="서명-signature">서명 (Signature)</h3>
<p>Signature는 JWT의 무결성을 검증하기 위해 생성된다.
Header와 Payload를 인코딩한 문자열을 연결하고, 이를 서버의 비밀 키(Secret Key) 와 함께 서명하여 생성한다.</p>
<p>Signature를 만드는 공식은 아래와 같다.</p>
<pre><code class="language-javascript">// Header + Payload를 SecretKey를 가지고 SHA256 해시로 서명한다.
HMACSHA256(
    base64UrlEncode(Header) + &quot;.&quot; + base64UrlEncode(Payload),
    SecretKey
)</code></pre>
<h2 id="jwt의-활용-및-주의-사항">JWT의 활용 및 주의 사항</h2>
<h3 id="🔹-jwt-활용">🔹 JWT 활용</h3>
<ul>
<li><strong>로그인 인증 (Authorization)</strong><ul>
<li>로그인 성공 시, 서버가 JWT를 발급해주고 이후 요청마다 JWT를 통해 사용자 인증</li>
</ul>
</li>
<li><strong>OAuth 2.0 / OpenID Connect</strong><ul>
<li>구글, 네이버, 카카오 로그인 같은 소셜 로그인에서 인증 토큰으로 사용</li>
</ul>
</li>
<li><strong>정보 교환 (Information Exchange)</strong><ul>
<li>안전하게 정보(예: 사용자 ID, 권한)를 주고 받을 때</li>
<li>마이크로서비스 아키텍처(MSA) 간의 인증</li>
<li>클라이언트 - 서버 간 정보 교환</li>
</ul>
</li>
</ul>
<h3 id="🔸-jwt-사용-시-주의사항">🔸 JWT 사용 시 주의사항</h3>
<ul>
<li><p><strong>Payload는 암호화되어 있지 않다.</strong></p>
<ul>
<li><p>JWT를 단순 디코딩하면 Payload의 내용은 누구나 볼 수 있기 때문에, 설계 시 정보 선택에 신중해야 한다.</p>
<p><img src="https://github.com/user-attachments/assets/f185e161-4067-4f51-b46f-a957ab64d59a" alt="jwt 토큰 디코딩"></p>
</li>
<li><p>민감한 정보(비밀번호, 주민번호 등)는 절대 저장해서는 안된다.</p>
</li>
</ul>
</li>
<li><p><strong>토큰 탈취에 주의해야한다.</strong></p>
<ul>
<li>반드시 HTTPS를 사용하여 네트워크 구간을 보호해야한다.</li>
</ul>
</li>
<li><p><strong>만료시간(exp)을 설정해야 한다.</strong></p>
<ul>
<li>무기한 유효한 토큰은 보안 위협이 크므로 적절한 만료 정책을 설정해야한다.</li>
<li>서버가 Stateless하게 동작하기 때문에, 발급한 토큰을 중간에 무효화하기 쉽지 않다.</li>
<li>이를 보완하기 위해 블랙리스트(BlackList) 방식이나 짧은 만료시간 설정 + 리프레시 토큰(Refresh Token) 전략을 사용한다.</li>
</ul>
</li>
<li><p><strong>Secret Key를 안전하게 관리해야한다.</strong></p>
<ul>
<li>Secret Key가 유출되면 누구나 유효한 토큰을 발급할 수 있게 되어, 시스템 전체가 위험해질 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="jwt-토큰의-장단점">JWT 토큰의 장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li>서버가 상태를 저장하지 않아 확장이 매우 쉽다.</li>
<li>인증 처리가 빠르다. (DB 조회 없이 토큰만 검증하면 끝난다.)</li>
<li>다양한 플랫폼(웹, 모바일, 서버 간 통신)에서 사용하기 편리하다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>토큰이 탈취되면 만료 전까지 계속 사용될 위험이 있다.</li>
<li>토큰을 폐기하기가 어렵다. (별도 BlackList 관리가 필요하다.)</li>
<li>Payload가 노출될 수 있어 민감한 정보 저장에 주의해야한다.</li>
</ul>
<h2 id="jwt-토큰이-사용되는-사례">JWT 토큰이 사용되는 사례</h2>
<h3 id="1-서버-수가-많거나-마이크로서비스msa-아키텍처를-사용하는-경우">1. 서버 수가 많거나, 마이크로서비스(MSA) 아키텍처를 사용하는 경우</h3>
<ul>
<li>Stateless 구조가 확장성과 유지보수에 유리하다.</li>
</ul>
<h3 id="2-다양한-플랫폼웹앱서버-간-통신-등을-넘나드는-경우">2. 다양한 플랫폼(웹/앱/서버 간 통신 등)을 넘나드는 경우</h3>
<ul>
<li>다양한 클라이언트와 서버 간 토큰 전달이 용이하다.</li>
</ul>
<h2 id="refresh-token-전략">Refresh Token 전략</h2>
<h3 id="전략의-필요성">전략의 필요성</h3>
<ul>
<li>Access Token은 일반적으로 서버가 기억하지 않는다 - Stateless</li>
<li>클라이언트가 탈취당하면 만료(exp) 전까지 악용 가능하다.</li>
</ul>
<p>따라서, Access Token을 짧게 만료시킨다.</p>
<p>하지만 Access Token을 너무 짧게 하면 사용자가 계속 로그인해야 하는 불편이 생긴다. </br>
이 문제를 해결하기 위해, </br>
”Refresh Token으로 Access Token을 재발급”하는 구조를 도입한다.</p>
<h3 id="🔹-다회성-refresh-token">🔹 다회성 Refresh Token</h3>
<ol>
<li>로그인 시 Access Token과 Refresh Token을 발급하고, Refresh Token은 HTTP-only Cookie에 저장하여 응답한다.</li>
<li>발급 받은 Refresh Token은 DB나 Redis 등 서버에 저장을 한다.</li>
<li>API 요청 시 Access Token을 검증한다.</li>
<li>Access Token이 만료된 경우 Cookie에 Refresh Token이 유효한 토큰인지 검증을 한다.</li>
<li>유효한 토큰인 경우 Access Token은 재발급한다.</li>
</ol>
<pre><code>[Login]
 └─&gt; [Access Token 발급 (짧은 수명)]
 └─&gt; [Refresh Token 발급 (긴 수명)]

[API 요청 시]
 └─&gt; Access Token 검증 → 성공 시 요청 처리

[Access Token 만료 시]
 └─&gt; Refresh Token 서버 제출 → 검증 → 새 Access Token 발급</code></pre><h3 id="🔹-일회성-refresh-token-rotation-방식">🔹 일회성 Refresh Token (Rotation 방식)</h3>
<ul>
<li>다회성 Refresh Token과 동작 방식(1 ~ 4번)이 동일하다.</li>
</ul>
<ol>
<li>Access Token을 발행시 Refresh Token도 함께 발행한다.</li>
<li>기존 Refresh Token은 폐기하고, 새로 발행한 Refresh Token을 서버(DB 또는 Redis 등)에 저장한다.</li>
<li>새로 발행한 Refresh Token으로 HTTP-only Cookie를 갱신하고, Access Token을 응답한다.</li>
</ol>
<pre><code>[Login]
 └─&gt; [Access Token 발급 (짧은 수명)]
 └─&gt; [Refresh Token 발급 (긴 수명)]

[API 요청 시]
 └─&gt; Access Token 검증 → 성공 시 요청 처리

[Access Token 만료 시]
 └─&gt; Refresh Token 서버 제출 → 검증 → 새 Access Token 발급
 └─&gt; Refresh Token도 갱신 → 서버 저장(DB or Redis 등)</code></pre><h2 id="jwt-사용-시-발생할-수-있는-보안-이슈-및-방어-방법">JWT 사용 시 발생할 수 있는 보안 이슈 및 방어 방법</h2>
<table>
<thead>
<tr>
<th>주요 이슈</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>탈취(Token Theft)</td>
<td>토큰이 클라이언트 측 또는 네트워크에서 탈취될 수 있다.</td>
</tr>
<tr>
<td>만료 시간 관리 실패</td>
<td>토큰이 무제한으로 유효하거나, 너무 길게 유효하면 탈취 시 피해가 커진다.</td>
</tr>
<tr>
<td>민감 정보 노출</td>
<td>Payload는 암호화되지 않아 누구나 디코딩하여 볼 수 있다.</td>
</tr>
<tr>
<td>서명 알고리즘 관련 취약점</td>
<td>잘못된 알고리즘 설정(예: <code>alg: none</code>)으로 인해 서명 검증이 무력화될 수 있다.</td>
</tr>
<tr>
<td>Refresh Token 관리 실패</td>
<td>Refresh Token 탈취 또는 무효화 실패 시 재로그인 없이 장기간 악용될 수 있다.</td>
</tr>
<tr>
<td>Replay Attack(재사용 공격)</td>
<td>탈취한 토큰을 재전송하여 공격하는 방식이다.</td>
</tr>
</tbody></table>
<h3 id="1-탈취-token-theft">1. 탈취 (Token Theft)</h3>
<p>문제:</p>
<ul>
<li>클라이언트(localStorage, sessionStorage)에서 JavaScript를 통해 접근 가능한 토큰이 XSS(스크립트 공격)로 탈취될 수 있다.</li>
<li>HTTPS가 없는 경우 네트워크 중간자 공격(Man-in-the-Middle Attack)으로 토큰이 유출될 수 있다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>항상 HTTPS를 사용하여 토큰 전송 구간을 암호화한다.</li>
<li>HttpOnly Cookie를 사용해 JavaScript에터 토큰에 접근할 수 없게 만든다.</li>
<li>클라이언트 애플리케이션에서 XSS 방어(콘텐츠 보안 정책(CSP) 적용, 입력값 검증 등)를 철저히 한다.</li>
<li>토큰 저장 위치는 localStorage 대신 Secure Cookie를 권장한다.</li>
</ul>
<hr>
<h3 id="2-만료-시간-관리-실패">2. 만료 시간 관리 실패</h3>
<p>문제:</p>
<ul>
<li>토큰 만료(exp) 시간을 너무 길게 설정하면 탈취 시 위험 기간이 길어진다.</li>
<li>토큰을 무기한으로 사용하는 경우 위험이 극대화된다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>Access Token의 만료 시간을 짧게 설정한다. (예: 15분 이내)</li>
<li>Refresh Token을 사용하여 짧은 Access Token을 주기적으로 갱신한다.</li>
<li>Refresh Token에도 반드시 만료(exp)를 설정한다.</li>
</ul>
<hr>
<h3 id="3-민감-정보-노출">3. 민감 정보 노출</h3>
<p>문제:</p>
<ul>
<li>JWT Payload는 Base64Url 인코딩되어 있을 뿐, 암호화된 것이 아니다.</li>
<li>누구나 쉽게 Payload를 디코딩하여 내용을 볼 수 있다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>절대 민감한 정보(비밀번호, 주민번호, 카드번호 등)는 Payload에 저장하지 않는다.</li>
<li>사용자 ID, 권한 정보(role) 등 최소한의 정보만 담는다.</li>
<li>민감 정보가 꼭 필요하다면 별도로 암호화하거나 토큰 외부에서 관리한다.</li>
</ul>
<hr>
<h3 id="4-서명-알고리즘-취약점">4. 서명 알고리즘 취약점</h3>
<p>문제:</p>
<ul>
<li>잘못된 JWT 라이브러리 설정으로 <code>alg: none</code>을 허용할 경우, 서명 검증 없이 토큰을 통과시킬 수 있다.</li>
<li>대칭키(HS256) 대신 비대칭키(RS256)을 사용할 때 키 관리를 잘못하면 위조 토큰이 통과될 수 있다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>서버에서 alg(알고리즘)를 명시적으로 고정한다. (예: HS256만 수락)</li>
<li>JWT 라이브러리에서 <code>alg: none</code>을 허용하지 않게 강제한다.</li>
<li>비대칭키(RS256) 사용하는 경우, 공개키 / 개인키 관리에 주의한다.</li>
</ul>
<hr>
<h3 id="5-refresh-token-관리-실패">5. Refresh Token 관리 실패</h3>
<p>문제:</p>
<ul>
<li>Refresh Token을 탈취당하면 장기간 Access Token 재발급이 가능해진다.</li>
<li>만약 Refresh Token을 서버에서 무효화(BlackList)하지 못하면 리스크가 커진다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>Refresh Token은 HttpOnly, Secure 속성을 가진 쿠키에 저장한다.</li>
<li>Refresh Token Rotation 전략(한번 쓰면 교체)을 적용한다.</li>
<li>Refresh Token 재발급 시, 사용자에게 이메일 알림이나 푸시 알림으로 통지할 수 있다.</li>
</ul>
<hr>
<h3 id="6-replay-attact-재사용-공격">6. Replay Attact (재사용 공격)</h3>
<p>문제:</p>
<ul>
<li>탈취한 토큰을 동일한 서버에 다시 보내서 불법 접근하는 공격이다.</li>
</ul>
<p>방어 방법:</p>
<ul>
<li>각 요청마다 JWT만이 아니라, 추가적인 요청 ID(Nonce)를 사용해 재사용을 방지한다.</li>
<li>중요한 요청(API)에는 2차 인증(2FA)을 적용한여 한 번 더 사용자 확인을 거친다.</li>
<li>Refresh Token 사용 시 Rotation 전략을 적용해 한 번 사용한 토큰은 다시 사용할 수 없게 만든다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Socket.IO 란?]]></title>
            <link>https://velog.io/@jiseong_98/Socket.IO-%EB%9E%80</link>
            <guid>https://velog.io/@jiseong_98/Socket.IO-%EB%9E%80</guid>
            <pubDate>Tue, 22 Apr 2025 09:30:04 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 Socket.IO 라이브러리에 대해서 정리를 해보겠다!</p>
<h2 id="✨-socketio란">✨ Socket.IO란?</h2>
<p><strong>Socket.IO</strong>는 브라우저와 서버 간의 <strong>실시간 양방향 이벤트 기반 통신</strong>을 쉽게 구현할 수 있도록 도와주는 JavaScript 라이브러리이다.</p>
<ul>
<li>클라이언트와 서버 모두 JavaScript로 작성 가능하다.</li>
<li>WebSocket 위에서 동작하지만, 단순 WebSocket보다 <strong>풍부한 기능</strong> 제공한다.</li>
<li>Fallback 메커니즘 내장: 브라우저가 WebSocket을 지원하지 않아도 동작이 가능하다. (XHR long-polling 등으로 대체)</li>
</ul>
<blockquote>
<p>🔍 <strong>XHR long-polling이란?</strong>
    WebSocket을 사용할 수 없는 환경(예: 오래된 브라우저, 방화벽 제한 등)에서 사용하는 대체 통신 방식이다.
    클라이언트가 서버에 HTTP 요청을 보내고, 서버가 새 데이터가 생길 때까지 응답을 지연시킨 후 응답을 보내는 방식이다.
    실시간성은 WebSocket보다 떨어지지만, WebSocket이 막힌 환경에서도 비슷한 경험을 제공할 수 있는 장점이 있다.</p>
</blockquote>
<hr>
<h2 id="✅-socketio의-구성-요소">✅ Socket.IO의 구성 요소</h2>
<p>Socket.IO는 크게 두 부분으로 구성된다:</p>
<ul>
<li><strong>socket.io (서버)</strong>: Node.js 기반 서버 모듈</li>
<li><strong>socket.io-client (클라이언트)</strong>: 브라우저 혹은 Node.js에서 실행되는 클라이언트 모듈</li>
</ul>
<hr>
<h2 id="🔄-socketio-연결-방식">🔄 Socket.IO 연결 방식</h2>
<p>Socket.IO는 처음에 <strong>HTTP를 통해 연결을 시작</strong>하지만, 클라이언트와 서버 간 handshake 과정에서 <strong>자동으로 WebSocket으로 업그레이드된</strong>다.</p>
<p>이 과정은 Socket.IO가 연결되는 과정이다:</p>
<ol>
<li>클라이언트가 <code>http://</code> 또는 <code>https://</code> 주소로 서버에 연결 요청을 보냄</li>
<li>서버는 handshake를 통해 WebSocket을 사용할 수 있는지 협상</li>
<li>브라우저가 지원하면 <code>Upgrade: websocket</code> 헤더를 통해 WebSocket 프로토콜로 전환</li>
<li>이후 실시간 데이터 통신은 WebSocket(<code>ws://</code> 또는 <code>wss://</code>)을 통해 지속</li>
</ol>
<p>즉, 사용자는 그냥 <code>io(&quot;http://localhost:3000&quot;)</code>처럼 작성해도, <strong>Socket.IO가 내부에서 자동으로 WebSocket 연결로 업그레이드</strong>해서 사용한다.</p>
<hr>
<h2 id="⚙️-기본-사용법">⚙️ 기본 사용법</h2>
<h3 id="서버-측-nodejs">서버 측 (Node.js)</h3>
<pre><code class="language-bash">npm install socket.io     // 먼저 Socket.IO 라이브러리를 설치한다.</code></pre>
<pre><code>const { Server } = require(&quot;socket.io&quot;);
const io = new Server(3000);

io.on(&quot;connection&quot;, (socket) =&gt; {
  console.log(&quot;클라이언트 연결됨&quot;, socket.id);

  socket.on(&quot;message&quot;, (data) =&gt; {
    console.log(&quot;수신 메시지:&quot;, data);
    socket.emit(&quot;message&quot;, `Echo: ${data}`);
  });

  socket.on(&quot;disconnect&quot;, () =&gt; {
    console.log(&quot;클라이언트 연결 해제&quot;);
  });
});</code></pre><h3 id="클라이언트-측-브라우저">클라이언트 측 (브라우저)</h3>
<pre><code class="language-html">&lt;script src=&quot;https://cdn.socket.io/4.5.4/socket.io.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
  const socket = io(&quot;http://localhost:3000&quot;);

  socket.on(&quot;connect&quot;, () =&gt; {
    console.log(&quot;서버와 연결됨&quot;, socket.id);
    socket.emit(&quot;message&quot;, &quot;Hello Server!&quot;);
  });

  socket.on(&quot;message&quot;, (data) =&gt; {
    console.log(&quot;서버로부터 메시지:&quot;, data);
  });
&lt;/script&gt;</code></pre>
<hr>
<h2 id="🔄-주요-기능-및-특징">🔄 주요 기능 및 특징</h2>
<h3 id="1-자동-재연결-지원">1. <strong>자동 재연결 지원</strong></h3>
<ul>
<li>네트워크가 끊겼다가 복구되어도 자동으로 reconnect 시도</li>
</ul>
<h3 id="2-fallback-메커니즘">2. <strong>Fallback 메커니즘</strong></h3>
<ul>
<li>WebSocket이 지원되지 않는 환경에서도 <strong>XHR-polling</strong>으로 대체가 가능하다.</li>
</ul>
<h3 id="3-방room과-네임스페이스namespace">3. <strong>방(Room)과 네임스페이스(Namespace)</strong></h3>
<ul>
<li><strong>Room</strong>: 특정 유저 그룹에게만 메시지를 브로드캐스트할 수 있다.</li>
<li><strong>Namespace</strong>: URI를 나누어 채널을 분리한다. (ex. <code>/chat</code>, <code>/admin</code>)</li>
</ul>
<h3 id="4-브로드캐스팅">4. <strong>브로드캐스팅</strong></h3>
<ul>
<li>특정 클라이언트를 제외하거나, 방(Room) 안의 모든 유저에게 메시지를 보낼 수 있다</li>
</ul>
<pre><code>socket.broadcast.emit(&quot;msg&quot;, &quot;다른 모든 사용자에게 전송&quot;);
io.to(&quot;room1&quot;).emit(&quot;msg&quot;, &quot;room1 사용자들에게만 전송&quot;);</code></pre><h3 id="5-미들웨어-지원">5. <strong>미들웨어 지원</strong></h3>
<ul>
<li>클라이언트 연결 시 인증 로직을 실행하거나, 메시지 전처리가 가능하다</li>
</ul>
<pre><code>io.use((socket, next) =&gt; {
  const token = socket.handshake.auth.token;
  if (isValidToken(token)) {
    next();
  } else {
    next(new Error(&quot;인증 실패&quot;));
  }
});</code></pre><hr>
<h2 id="🧠-socketio-vs-websocket-차이점">🧠 Socket.IO vs WebSocket 차이점</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>WebSocket</th>
<th>Socket.IO</th>
</tr>
</thead>
<tbody><tr>
<td>프로토콜</td>
<td>표준 WebSocket (RFC 6455)</td>
<td>자체 프로토콜 (WebSocket 위 또는 Polling)</td>
</tr>
<tr>
<td>지원 브라우저</td>
<td>최신 브라우저 필요</td>
<td>구형 브라우저도 지원 (Fallback 있음)</td>
</tr>
<tr>
<td>자동 재연결</td>
<td>❌ 없음</td>
<td>✅ 기본 지원</td>
</tr>
<tr>
<td>네임스페이스/방</td>
<td>❌ 직접 구현해야 함</td>
<td>✅ 내장 기능</td>
</tr>
<tr>
<td>메시지 구조</td>
<td>문자열 또는 바이너리</td>
<td>이벤트 기반 (이벤트명 + 데이터)</td>
</tr>
<tr>
<td>미들웨어</td>
<td>❌ 없음</td>
<td>✅ 연결 미들웨어 사용 가능</td>
</tr>
</tbody></table>
<hr>
<h2 id="⚠️-socketio의-한계점">⚠️ Socket.IO의 한계점</h2>
<p>Socket.IO는 강력하고 편리한 기능을 제공하지만, 아래와 같은 한계점도 존재한다:</p>
<h3 id="✅-websocket과-공유하는-한계점">✅ WebSocket과 공유하는 한계점</h3>
<ul>
<li><strong>상태 기반 연결 유지</strong>: 연결된 모든 클라이언트에 대해 소켓을 유지해야 하므로, 많은 사용자가 접속하면 메모리/소켓 자원이 많이 소모된다.</li>
<li><strong>프록시/방화벽 문제</strong>: 일부 네트워크 환경에서 WebSocket과 마찬가지로 연결이 차단되거나, <code>Upgrade</code> 헤더가 차단될 수 있다.</li>
<li><strong>REST 기반 인프라와의 통합 난이도</strong>: 미들웨어 체인, 인증 흐름 등이 WebSocket과 다르기 때문에 통합 설계가 필요하다.</li>
</ul>
<h3 id="⚠️-socketio만의-추가-제약">⚠️ Socket.IO만의 추가 제약</h3>
<ul>
<li><strong>표준이 아닌 자체 프로토콜 사용</strong>: WebSocket 표준(RFC 6455)과는 다르게 Socket.IO는 자체 포맷과 handshake 구조를 사용하기 때문에, 표준 WebSocket 클라이언트와는 호환되지 않는다.</li>
<li><strong>버전 간 호환성 문제</strong>: 서버와 클라이언트의 Socket.IO 버전이 다르면 연결이 되지 않거나 오류가 발생할 수 있다. (같은 메이저 버전 권장)</li>
<li><strong>추가 오버헤드 발생</strong>: 이벤트명, 네임스페이스, 방(Room) 정보 등 메타데이터로 인해 성능 부하가 늘어날 수 있다.</li>
</ul>
<hr>
<h2 id="🔐-인증-및-보안">🔐 인증 및 보안</h2>
<h3 id="1-handshakeauth를-활용해-jwt-등-인증-정보를-전달할-수-있다">1. <code>handshake.auth</code>를 활용해 JWT 등 인증 정보를 전달할 수 있다.</h3>
<p>Socket.IO에서는 <code>handshake.auth</code>를 활용해 클라이언트가 연결 시 인증 정보를 함께 전달할 수 있다. 가장 일반적인 예는 <strong>JWT(Json Web Token)</strong> 기반 인증이다.</p>
<h3 id="✅-jwt-인증-흐름-예시">✅ JWT 인증 흐름 예시</h3>
<h3 id="📦-클라이언트-측-jwt-전달">📦 클라이언트 측 (JWT 전달)</h3>
<pre><code>const socket = io(&quot;http://localhost:3000&quot;, {
  auth: {
    token: &quot;Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&quot;
  }
});</code></pre><ul>
<li><code>auth</code> 필드에 JWT 또는 다른 인증 데이터를 담아 서버로 보냅니다.</li>
</ul>
<h3 id="🔐-서버-측-인증-처리">🔐 서버 측 (인증 처리)</h3>
<pre><code>io.use((socket, next) =&gt; {
  const token = socket.handshake.auth.token;
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    socket.user = payload; // 이후 socket.user로 사용자 정보 접근 가능
    next();
  } catch (err) {
    next(new Error(&quot;인증 실패: 토큰이 유효하지 않음&quot;));
  }
});</code></pre><ul>
<li><code>socket.handshake.auth.token</code>을 꺼내서 <code>jwt.verify()</code> 등으로 검증한다.</li>
<li>실패 시 <code>next(new Error(...))</code>로 연결을 차단할 수 있다.</li>
</ul>
<p>이 방식은 REST API에서의 <code>Authorization: Bearer &lt;token&gt;</code> 방식과 유사하게, <strong>Socket.IO 연결에서도 인증 로직을 적용할 수 있도록 해준다.</strong></p>
<h3 id="2-https--wss-기반-배포를-권장한다">2. HTTPS + WSS 기반 배포를 권장한다.</h3>
<h3 id="3-미들웨어를-통한-인증-필터링-구현을-권장한다">3. 미들웨어를 통한 인증 필터링 구현을 권장한다.</h3>
<ul>
<li>Socket.IO의 <code>io.use()</code> 메서드를 활용하여 <strong>서버에 연결 요청이 들어오기 전에 인증/인가 로직을 선행 적용</strong>할 수 있다.</li>
<li>이를 통해 모든 연결 시도에 대해 공통 필터처럼 인증 로직을 삽입할 수 있고, 필요 시 연결을 거부하거나 추가 정보를 주입할 수 있다.</li>
</ul>
<h3 id="🔍-예시-관리자-권한을-가진-사용자만-연결-허용하기">🔍 예시: 관리자 권한을 가진 사용자만 연결 허용하기</h3>
<pre><code>    io.use((socket, next) =&gt; {
      const token = socket.handshake.auth.token;
      try {
        const payload = jwt.verify(token, process.env.JWT_SECRET);
        if (payload.role !== &#39;admin&#39;) {
          return next(new Error(&quot;접근 권한 없음&quot;));
        }
        socket.user = payload;
        next();
      } catch (err) {
        next(new Error(&quot;인증 실패: 유효하지 않은 토큰&quot;));
      }
    });</code></pre><pre><code>- 이 예시에서는 `JWT` 토큰의 payload 안에 있는 `role` 값을 기준으로, **관리자가 아닐 경우 연결을 거부한**다.
- 인증에 성공한 사용자 정보는 `socket.user`에 주입하여 이후 이벤트 핸들러에서 활용할 수 있다.</code></pre><hr>
<h2 id="🔗-참고-링크">🔗 참고 링크</h2>
<ul>
<li>공식 문서: <a href="https://socket.io/docs/v4/">https://socket.io/docs/v4/</a></li>
<li>GitHub: <a href="https://github.com/socketio/socket.io">https://github.com/socketio/socket.io</a></li>
<li>FAQ: <a href="https://socket.io/docs/v4/troubleshooting/">https://socket.io/docs/v4/troubleshooting/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebSocket 이란?]]></title>
            <link>https://velog.io/@jiseong_98/WebSocket-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@jiseong_98/WebSocket-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Mon, 21 Apr 2025 19:35:51 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 이전에 프로젝트를 진행하면서 사용하였던 WebSocket 프로토콜에 대해 자세히 알아보려고 한다!</p>
<h2 id="✨-websocket이란">✨ WebSocket이란?</h2>
<p>WebSocket은 클라이언트(보통은 웹 브라우저)와 서버 간에 <strong>하나의 TCP 연결을 유지하면서</strong> 양방향 통신을 가능하게 해주는 프로토콜이다.</p>
<ul>
<li>HTTP처럼 TCP 위에서 동작하지만, <strong>요청-응답이 아닌 전이중(Full Duplex)</strong> 통신을 지원한다.</li>
<li>단 한 번의 연결로 양방향으로 자유롭게 데이터를 주고받을 수 있다.</li>
<li>2011년 <strong>RFC 6455</strong>를 통해 표준으로 제정되었다.</li>
</ul>
<h3 id="🧠-websocket은-전송-계층-프로토콜일까">🧠 WebSocket은 전송 계층 프로토콜일까?</h3>
<p>정확히 말하면, <strong>WebSocket은 전송 계층(Transport Layer) 프로토콜이 아니다.</strong></p>
<p>WebSocket은 <strong>TCP(전송 계층)를 기반으로 동작하는 애플리케이션 계층(Application Layer) 프로토콜</strong>이다.
즉:</p>
<ul>
<li>TCP는 데이터를 신뢰성 있게 전송하는 전송 계층 프로토콜</li>
<li>WebSocket은 그 위에서 <strong>메시지 프레이밍, 양방향 통신, 핸드셰이크 등 고수준 통신 규약</strong>을 정의하는 애플리케이션 계층 프로토콜</li>
</ul>
<p>OSI 7계층 기준으로 보면:</p>
<table>
<thead>
<tr>
<th>계층</th>
<th>예시</th>
<th>WebSocket</th>
</tr>
</thead>
<tbody><tr>
<td>7 - 애플리케이션</td>
<td>HTTP, FTP, WebSocket</td>
<td>✅ WebSocket 위치</td>
</tr>
<tr>
<td>4 - 전송</td>
<td>TCP, UDP</td>
<td>⚙️ WebSocket이 사용하는 기반</td>
</tr>
</tbody></table>
<p>따라서 WebSocket은 <strong>전송 계층을 &#39;사용&#39;할 뿐</strong>, 그 자체는 <strong>애플리케이션 계층에 속한다.</strong></p>
<hr>
<h2 id="🧠-websocket이-왜-필요한가">🧠 WebSocket이 왜 필요한가?</h2>
<p>HTTP는 클라이언트가 요청해야 서버가 응답할 수 있는 <strong>단방향 통신 구조</strong>이다. 
따라서 서버가 클라이언트에게 먼저 데이터를 보낼 수 없다.</p>
<p>이를 극복하기 위한 방식으로는 다음과 같은 것들이 있었다:</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
<th>한계</th>
</tr>
</thead>
<tbody><tr>
<td>Polling</td>
<td>클라이언트가 일정 주기로 서버에 요청</td>
<td>실시간성 부족, 서버 과부하</td>
</tr>
<tr>
<td>Long Polling</td>
<td>서버가 응답을 지연하다가 새 데이터가 생기면 응답</td>
<td>지연 시간, 다중 연결 유지 필요</td>
</tr>
<tr>
<td>SSE (Server Sent Events)</td>
<td>서버에서 클라이언트로 단방향 스트리밍</td>
<td>클라이언트 → 서버 전송 불가</td>
</tr>
</tbody></table>
<p>⬇️ 이 모든 방식은 HTTP의 틀을 억지로 비틀어 쓴 방식이다. </p>
<p>✅ WebSocket은 이 한계를 완전히 극복한 기술이다.</p>
<hr>
<h2 id="⚖️-websocket-프로토콜-구조-rfc-6455">⚖️ WebSocket 프로토콜 구조 (RFC 6455)</h2>
<h3 id="1-handshake-초기-연결">1. Handshake (초기 연결)</h3>
<p>WebSocket은 <strong>HTTP 기반으로 연결을 시작</strong>한다. 이 과정을 <strong>Handshake</strong>라고 한다.</p>
<h3 id="📉-클라이언트-요청-예시">📉 클라이언트 요청 예시:</h3>
<pre><code>GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket - &#39;WebSocket으로 프로토콜을 바꾸고 싶다&#39;라는 명시적인 요청
Connection: Upgrade - &#39;Upgrade 요청을 실제로 적용하&#39;라는 지시
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13</code></pre><h3 id="📊-서버-응답-예시">📊 서버 응답 예시:</h3>
<pre><code>HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</code></pre><ul>
<li>이 과정을 통해 HTTP → WebSocket 프로토콜로 업그레이드된다.</li>
<li>보안 연결은 <code>wss://</code>를 사용하며, TLS(SSL) 기반이다.</li>
</ul>
<h3 id="2-메시지-프레임-구조">2. 메시지 프레임 구조</h3>
<p>WebSocket은 메시지를 <strong>프레임 단위로 분할</strong>해서 전송한다.</p>
<ul>
<li>프레임 - 애플리케이션 계층에서 데이터를 전송하는 단위</li>
</ul>
<p>프레임은 크게 다음과 같은 종류가 있다:</p>
<ul>
<li>✉️ <strong>텍스트 프레임</strong>: UTF-8 인코딩된 문자열</li>
<li>📂 <strong>바이너리 프레임</strong>: ArrayBuffer, Blob 등</li>
<li>⚖️ <strong>제어 프레임</strong>: ping, pong, close</li>
</ul>
<blockquote>
<p>💡 프레임은 WebSocket 자체의 논리적 단위이며, 
실제로는 TCP 위에서 전송되고 TCP는 내부적으로 이를 여러 개의 IP 패킷으로 나눠 전송할 수 있다.</p>
<p>즉, <strong>WebSocket 프레임은 TCP 세그먼트를 통해 전송되며, 
세그먼트가 패킷으로 캡슐화되어 전송</strong>되는 구조이다.</p>
</blockquote>
<h3 id="3-연결-유지-및-종료">3. 연결 유지 및 종료</h3>
<ul>
<li><strong>ping/pong</strong> 프레임을 통해 연결이 살아있는지 확인한다.</li>
<li>클라이언트나 서버는 <code>close</code> 프레임을 통해 연결을 종료할 수 있다.</li>
</ul>
<h3 id="🔄-ping--pong-프레임이란">🔄 Ping / Pong 프레임이란?</h3>
<ul>
<li><strong>ping</strong>: 연결이 유효한지 확인하기 위해 클라이언트 또는 서버가 보낸다.</li>
<li><strong>pong</strong>: ping에 대한 응답으로 자동 혹은 수동으로 전송된다.</li>
<li>주로 <strong>유휴 연결 상태를 확인하거나</strong>, <strong>타임아웃 감지를 위한 헬스체크 용도</strong>로 사용된다.</li>
</ul>
<p><strong>예시 흐름:</strong></p>
<ul>
<li>서버 → ping → 클라이언트</li>
<li>클라이언트 → pong 자동 응답 (라이브러리에서 처리)</li>
</ul>
<blockquote>
<p>ping/pong 프레임은 RFC6455에 따라 제어 프레임(control frame)에 속하며, 응답이 오지 않으면 연결을 종료하거나 재연결할 수 있는 근거가 된다.</p>
</blockquote>
<hr>
<h2 id="💻-브라우저에서의-websocket-사용법-mdn-기준">💻 브라우저에서의 WebSocket 사용법 (MDN 기준)</h2>
<pre><code>const socket = new WebSocket(&quot;wss://example.com/socket&quot;);

socket.addEventListener(&quot;open&quot;, () =&gt; {
  console.log(&quot;WebSocket 연결 완료&quot;);
  socket.send(&quot;Hello, server!&quot;);
});

socket.addEventListener(&quot;message&quot;, (event) =&gt; {
  console.log(&quot;서버로부터 메시지:&quot;, event.data);
});

socket.addEventListener(&quot;close&quot;, () =&gt; {
  console.log(&quot;연결 종료&quot;);
});

socket.addEventListener(&quot;error&quot;, (err) =&gt; {
  console.error(&quot;에러 발생:&quot;, err);
});</code></pre><ul>
<li><code>ws://</code> 또는 <code>wss://</code>로 URL을 시작한다.</li>
<li>연결이 열리면 메시지를 자유롭게 주고받을 수 있다.</li>
</ul>
<hr>
<h2 id="⚠️-websocket의-한계와-주의사항">⚠️ WebSocket의 한계와 주의사항</h2>
<h3 id="1-상태-유지-비용이-높다">1. 상태 유지 비용이 높다</h3>
<ul>
<li>연결 유지 기반이라 서버 리소스를 많이 차지한다. (메모리, 커넥션 수 등)</li>
<li>수천~수만 사용자 연결 시 서버 확장을 고려할 필요가 있다. (e.g. Redis adapter, sticky session)</li>
</ul>
<h3 id="2-http2-http3과의-통합이-제한적">2. HTTP/2, HTTP/3과의 통합이 제한적</h3>
<ul>
<li>WebSocket은 HTTP/1.1에 최적화 되어있다.</li>
<li>HTTP/2/3에서는 멀티플렉싱과의 충돌로 비표준 확장에 의존해야 한다.</li>
</ul>
<h3 id="3-프록시방화벽-간섭">3. 프록시/방화벽 간섭</h3>
<ul>
<li>사내망이나 공공 네트워크에서 WebSocket 업그레이드 요청이 차단될 수 있다.</li>
</ul>
<h3 id="4-인증-처리-복잡성">4. 인증 처리 복잡성</h3>
<ul>
<li>WebSocket은 자체 인증 체계 없다.</li>
<li>JWT나 세션 토큰 등을 handshake 시 직접 처리해야 한다.</li>
</ul>
<h3 id="5-기존-http-인프라와-통합-어려움">5. 기존 HTTP 인프라와 통합 어려움</h3>
<ul>
<li>REST 미들웨어, 로깅 시스템과 호환하려면 별도 설계 필요하다.</li>
</ul>
<hr>
<h2 id="✅-websocket이-적합한-상황">✅ WebSocket이 적합한 상황</h2>
<ul>
<li><strong>실시간 채팅, 메신저</strong></li>
<li><strong>주식, 코인, 환율 실시간 시세 서비스</strong></li>
<li><strong>온라인 게임</strong> (빠른 동기화 필요)</li>
<li><strong>문서 협업 툴</strong> (실시간 동시 편집)</li>
<li><strong>IoT 디바이스 데이터 스트리밍</strong></li>
<li><strong>실시간 알림 시스템 (push notification)</strong></li>
</ul>
<blockquote>
<p>단순 API, 낮은 빈도의 요청이라면 오히려 REST가 효율적일 수 있다.</p>
</blockquote>
<hr>
<h2 id="📝-정리">📝 정리</h2>
<ul>
<li>WebSocket은 HTTP보다 실시간성과 효율성이 뛰어나다.</li>
<li>연결 유지 비용이 낮고, 헤더 오버헤드도 적다.</li>
<li>인증 처리 시 Handshake에 토큰을 함께 전송하거나 <code>query</code>, <code>headers</code> 활용이 가능하다.</li>
<li>모바일 환경에서는 <strong>자동 재연결 로직</strong>과 <strong>ping/pong 감지</strong>가 중요하다.</li>
<li>실시간 채팅, 게임, 주식 시세, IoT 등에서 필수 기술이다.</li>
</ul>
<hr>
<h2 id="🔗-참고한-문서들">🔗 참고한 문서들</h2>
<ul>
<li>RFC 6455 WebSocket 프로토콜 표준: <a href="https://datatracker.ietf.org/doc/html/rfc6455">https://datatracker.ietf.org/doc/html/rfc6455</a></li>
<li>MDN WebSocket API 가이드: <a href="https://developer.mozilla.org/ko/docs/Web/API/WebSocket">https://developer.mozilla.org/ko/docs/Web/API/WebSocket</a></li>
<li>JavaScript.info WebSocket 설명: <a href="https://ko.javascript.info/websocket#ref-906">https://ko.javascript.info/websocket#ref-906</a></li>
<li>기술 블로그: <a href="https://velog.io/@sj_yun/Web-Socket%EC%9D%B4%EB%9E%80">https://velog.io/@sj_yun/Web-Socket%EC%9D%B4%EB%9E%80</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 정리]]></title>
            <link>https://velog.io/@jiseong_98/Docker-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jiseong_98/Docker-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 08 Apr 2025 18:57:03 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트는 프로젝트를 docker로 배포하고 싶어서 배포전에 docker에 관해서 공부한 내용을 정리해본다!</p>
<h2 id="docker란">Docker란?</h2>
<p>Docker는 애플리케이션을 컨테이너(container)라는 단위로 격리해서 실행할 수 있게 해주는 플랫폼이다.</p>
<ul>
<li>앱 실행에 필요한 환경(OS, 라이브러리, 종속성 등)을 하나의 이미지로 패키징하는 것이다.</li>
<li>어디서나(개발 환경, 테스트 환경, 클라우드 서버) 동일하게 실행 가능하다.</li>
<li>기존의 &quot;내 PC에서는 작동하는데..?&quot; 문제를 해결해준다.</li>
</ul>
<p>Docker는 가볍고 빠른 가상화 기술이다. VM과 달리 OS를 공유하기 때문에 훨씬 빠르고 자원도 적게 든다.</p>
<hr>
<h3 id="docker를-사용하는-이유">Docker를 사용하는 이유</h3>
<ul>
<li>개발 환경의 일관성 : 팀원마다 다른 환경 문제를 해결할 수 있다.</li>
<li>빠른 배포 : 이미지 기반으로 빠르게 서버에 배포가 가능하다.</li>
<li>마이크로서비스 : 각각의 서비스를 독립된 컨테이너로 관리가 가능하다.</li>
<li>테스트 : 테스트용 환경을 빠르게 구성하고 제거가 가능하다.</li>
<li>확장성 : 클라우드 + 컨테이너 오케스트레이션(Kubernates)과 찰떡궁합이다.</li>
</ul>
<hr>
<h3 id="docker-container란">Docker Container란?</h3>
<blockquote>
<p>Docker Image를 실행한 인스턴스를 말한다.</p>
</blockquote>
<ul>
<li>이미지: 실행 전 상태</li>
<li>컨테이너: 실행 중인 인스턴스 (실제 동작)</li>
</ul>
<h4 id="특징">특징</h4>
<ul>
<li>격리된 공간에서 앱이 실행된다.</li>
<li>독립적이면서도 매우 가볍다.</li>
<li>실행/중지/삭제가 매우 빠르다.<pre><code># 예: Node.js 앱 컨테이너 실행
docker run -d -p 3000:3000 my-app</code></pre></li>
</ul>
<hr>
<h3 id="여러-컨테이너-간-통신">여러 컨테이너 간 통신</h3>
<p>컨테이너들끼리는 같은 Docker 네트워크 안에 잇으면 서로 이름으로 통신할 수 있다.</p>
<h4 id="방법-1-docker-compose-기본-네트워크-활용">방법 1. docker-compose 기본 네트워크 활용</h4>
<pre><code>// docker-compose
version: &#39;3.9&#39;
services:
  backend:
    build: ./backend
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      - redis

  redis:
    image: redis:7
    ports:
      - &quot;6379:6379&quot;

// Node.js에서 redis라는 이름으로 접근 가능
import { createClient } from &#39;redis&#39;;

const redis = createClient({
  socket: {
    host: &#39;redis&#39;, // 컨테이너 이름
    port: 6379,
  }
});</code></pre><h4 id="방법-2-사용자-정의-네트워크">방법 2. 사용자 정의 네트워크</h4>
<pre><code>// bash - 메인 네트워크 생성
$ docker network create my-network

// docker-compose - 각 컨테이너에 네트워크 연결
services:
    backend:
    image: my-backend
    networks:
      - my-network

  redis:
    image: redis
    networks:
      - my-network

networks:
  my-network:
    external: true    # 이미 만들어둔 네트워크를 사용한다</code></pre><h4 id="방법-3-직접-docker로-컨테이너-띄울-때-네트워크-지정">방법 3. 직접 Docker로 컨테이너 띄울 때 네트워크 지정</h4>
<pre><code># 네트워크 생성
$ docker network create my-bridge-net

# 네트워크에 각 컨테이너 연결
$ docker run -d --name backend --network my-bridge-net my-backend
$ docker run -d --name redis --network my-bridge-net redis</code></pre><hr>
<h3 id="docker-image란">Docker Image란?</h3>
<blockquote>
<p>컨테이너 실행을 위한 파일 시스템 스냅샷 + 설정 정보</p>
</blockquote>
<ul>
<li>불변(immutable)</li>
<li>여러 레이어로 구성되어 있어 캐시 재사용이 가능하다.</li>
<li>빌드 명령어:<pre><code>$ docker build -t my-app:1.0 .</code></pre>이미지를 기반으로 컨테이너를 무한히 만들어 낼 수 있다.</li>
</ul>
<hr>
<h3 id="dockerfile이란">Dockerfile이란?</h3>
<blockquote>
<p>이미지를 만들기 위한 설정 스크립트이다.</p>
</blockquote>
<h4 id="예시-nodejs-앱용-dockerfile">예시: Node.js 앱용 Dockerfile</h4>
<pre><code># 1. 기반 이미지
FROM node:18

# 2. 작업 디렉토리 생성 및 설정
WORKDIR /app

# 3. 의존성 파일 복사
COPY package*.json ./

# 4. 의존성 설치
RUM npm install

# 5. 앱 소스 복사
COPY . .

# 6. 앱 포트 개방(선택)
EXPOSE 3000

# 7. 앱 실행 명령
CMD [&quot;npm&quot;, &quot;start&quot;]</code></pre><h4 id="도커-파일-주요-명령어-정리">도커 파일 주요 명령어 정리</h4>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>FROM</code></td>
<td><strong>기반 이미지</strong> 지정 (<code>ubuntu</code>, <code>node</code>, <code>python</code> 등)</td>
</tr>
<tr>
<td><code>WORKDIR</code></td>
<td>작업 디렉토리 설정 (없으면 생성됨)</td>
</tr>
<tr>
<td><code>COPY</code></td>
<td>로컬 파일을 이미지에 복사</td>
</tr>
<tr>
<td><code>RUN</code></td>
<td>쉘 명령 실행 (예: <code>npm install</code>, <code>apt-get install</code>)</td>
</tr>
<tr>
<td><code>CMD</code></td>
<td><strong>컨테이너가 시작될 때 실행할 명령어</strong></td>
</tr>
<tr>
<td><code>ENTRYPOINT</code></td>
<td>CMD와 유사하지만 고정된 실행 진입점 설정</td>
</tr>
<tr>
<td><code>ENV</code></td>
<td>환경 변수 설정</td>
</tr>
<tr>
<td><code>EXPOSE</code></td>
<td>컨테이너가 사용할 포트 명시 (문서화 목적)</td>
</tr>
<tr>
<td><code>ARG</code></td>
<td>빌드 타임 변수 지정</td>
</tr>
<tr>
<td><code>LABEL</code></td>
<td>메타데이터 지정 (<code>version</code>, <code>maintainer</code> 등)</td>
</tr>
<tr>
<td><code>VOLUME</code></td>
<td>볼륨 마운트 지정 (데이터 지속용)</td>
</tr>
<tr>
<td>---</td>
<td></td>
</tr>
<tr>
<td>### Dockerfile &gt; 이미지 &gt; 배포 흐름</td>
<td></td>
</tr>
<tr>
<td>1. Dockerfile 작성</td>
<td></td>
</tr>
<tr>
<td>2. <code>docker build</code>로 이미지 생성</td>
<td></td>
</tr>
<tr>
<td>3. <code>docker run</code>으로 로컬 테스트</td>
<td></td>
</tr>
<tr>
<td>4. Docker Hub에 <code>docker push</code></td>
<td></td>
</tr>
<tr>
<td>5. 원격 서버에서 <code>docker pull</code></td>
<td></td>
</tr>
<tr>
<td>6. 컨테이너 실행 + Nginx 등으로 외부 노출</td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="docker-composeytml이란">docker-compose.ytml이란?</h3>
<blockquote>
<p>여러 개의 컨테이너를 한 번 정의하고 실행하는 설정 파일</p>
</blockquote>
<h4 id="예시-nodejs--mongodb">예시: Node.js + MongoDB</h4>
<pre><code>version: &#39;3.9&#39;
services:
  backend:
    build: ./backend
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      - mongo
    environment:
      - MONGO_URL=mongodb://mongo:27017/mydb

  mongo:
    image: mongo:6
    volumes:
      - mongo-data:/data/db
    ports:
      - &quot;27017:27017&quot;

volumes:
  mongo-data:</code></pre><pre><code># 실행 명령어
$ docker-compose up -d --build
$ docker-compose down</code></pre><hr>
<h3 id="환경변수-주입-environment--env_file">환경변수 주입: environment &amp; env_file</h3>
<pre><code># environment:
services:
  app:
    environment:
      - NODE_ENV=production
      - PORT=3000

# env_file:
services:
  app:
    env_file:
      - .env</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 단위 테스트 코드 작성하기 (1) - 단위 테스트]]></title>
            <link>https://velog.io/@jiseong_98/TypeScript-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-1-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jiseong_98/TypeScript-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-1-%EB%8B%A8%EC%9C%84-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Mon, 07 Apr 2025 12:24:15 GMT</pubDate>
            <description><![CDATA[<p>이번 포스트에서는 TypeScript 단위테스트에서 작성해보려고한다!
지금까지 개발을 할 때 코드를 작성하고 Postman으로 직접 테스트하면서 오류를 찾았었다.
테스트 코드의 중요성을 못느끼고 살았는데 TypeScript를 공부하면서 테스트 코드 작성도 같이 공부해보려고 한다!
테스트 코드를 작성하면서 느낀점은 기존에 작성했던 코드를 수정할 일이 생겼을 때 직접 API를 호출해서 테스트를 안하고 테스트 코드를 실행하기만 하면 테스트가 된다는 점에서 테스트 코드 작성의 중요성을 느꼈다!
테스트 코드의 종류로는 단위 테스트, 통합 테스트 등 여러가지가 있는데 나는 일단 단위 테스트 먼저 정리해보려고 한다.
나중에 통합 테스트(Integration Test), 부하 테스트 / 스트레스 테스트(Load / Stress Test) 이렇게 정리해보려고 한다!</p>
<hr>
<h2 id="단위-테스트unit-test란">단위 테스트(Unit Test)란?</h2>
<p>단위 테스트는 프로그램의 가장 작은 단위인 함수나 메서드가 예상대로 동작하는지 검증하는 테스트다</p>
<blockquote>
<p>쉽게 말하면, &quot;이 함수가 입력을 받았을 때, 기대한 결과를 내보내는가?&quot;를 자동으로 체크하는 코드!</p>
</blockquote>
<h3 id="예시">예시</h3>
<pre><code>// 함수
function add(a: number, b: number): number {
    return a + b;
}

// 단위 테스트 (Jest)
test(&#39;add 함수는 두 수의 합을 반환해야 한다.&#39;, () =&gt; {
    expect(add(2, 5)).toBe(5);
});</code></pre><hr>
<h3 id="단위-테스트를-작성하는-이유는">단위 테스트를 작성하는 이유는?</h3>
<h4 id="1-코드의-신뢰성-확보">1. 코드의 신뢰성 확보</h4>
<ul>
<li>기능이 바뀌어도 기존 함수들이 잘 작동하는지 자동으로 검증할 수 있다.<h4 id="2-리팩토링할-때-안심">2. 리팩토링할 때 안심</h4>
</li>
<li>코드 구조를 바꾸더라도 &quot;기능은 그대로&quot;라는 걸 테스트로 증명할 수 있다.<h4 id="3-빠른-피드백">3. 빠른 피드백</h4>
</li>
<li>개발 중 바로바로 오류를 파악할 수 있어 디버깅 시간이 줄어든다.</li>
</ul>
<hr>
<h3 id="단위-테스트의-특징">단위 테스트의 특징</h3>
<ul>
<li>테스트 범위: 아주 작다(함수, 메서드 단위)</li>
<li>속도: 매우 빠르다.</li>
<li>외부 의존성: 거의 없다 (DB, 네트워크, 파일시스템과 분리)</li>
<li>테스트 도구: Jest, Mocha, JUnit, Pytest 등</li>
<li>테스트 대상: 순수 함수, 클래스 메서드 등</li>
</ul>
<hr>
<h3 id="단위-테스트의-한계">단위 테스트의 한계</h3>
<ul>
<li>전체 흐름(비즈니스 로직)을 테스트하기 어렵다.</li>
<li>실제 환경(DB, API 등)과 차이가 있어 테스트 결과가 실제와 다를 수 있다.</li>
<li>잘못된 mock 설정은 잘못된 신뢰를 줄 수 있다.</li>
</ul>
<hr>
<h3 id="단위-테스트를-잘-작성하는-방법">단위 테스트를 잘 작성하는 방법</h3>
<ol>
<li>AAA 패턴 사용한다.<ul>
<li>Arrange(준비), Act(실행), Assert(검증) 구조로 테스트를 작성한다.</li>
</ul>
</li>
<li>테스트는 독립적으로 한다.<ul>
<li>하나의 테스트가 다른 테스트에 영향을 주지 않도록 작성한다.</li>
</ul>
</li>
<li>의미 있는 테스트 이름을 작성한다.<ul>
<li>어떤 기능을 테스트하는지 명확하게 표현한다.</li>
</ul>
</li>
<li>mock/stub를 활용한다.<ul>
<li>외부 의존성(DB, API 등)은 mocking/stubbing으로 제거한다.</li>
</ul>
</li>
<li>하나의 테스트는 하나의 검증만한다.<ul>
<li>실패 시 원인을 명확하게 알 수 있다.</li>
</ul>
</li>
<li>테스트 커버리지보다 품질 우선.<ul>
<li>모든 함수, 메서드를 테스트 하는것 보다, 중요한 로직 테스트를 우선으로 테스트 코드를 작성한다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="단위-테스트-환경을-설정해보자">단위 테스트 환경을 설정해보자!</h2>
<p>Jest 테스트 프레임워크를 활용한 테스트 환경을 설정할 것이다!</p>
<h4 id="1️⃣-첫번째로-테스트-프레임워크를-설치한다">1️⃣ 첫번째로 테스트 프레임워크를 설치한다.</h4>
<blockquote>
<p>npm install --save-dev jest ts-jest @types/jest</p>
</blockquote>
<h4 id="2️⃣-두번째로-설정-파일을-초기화한다">2️⃣ 두번째로 설정 파일을 초기화한다.</h4>
<blockquote>
<p>// jest.config.js 생성
npx ts-jest config:init</p>
</blockquote>
<p>나의 jest.config.js의 초기 설정은 이렇다!</p>
<pre><code>/** @type {import(&#39;ts-jest&#39;).JestConfigWithTsJest} **/
// ts-jest를 사용하는 Jest 설정임을 명시하는 타입 선언 (자동 완성 및 타입 체크 지원)

module.exports = {
    preset: &#39;ts-jest&#39;,
    testEnvironment: &#39;node&#39;,    // 테스트 실행 환경을 node로 설정
    roots: [&#39;&lt;rootDir&gt;/src&#39;],    // root directory 설정
    testMatch: [    // 테스트 파일 경로 패턴 지정
        &#39;**/tests/**/*.test.ts&#39;
    ],
    transform: {    // .ts 또는 .tsx 파일을 ts-jest를 이용해 반환
        &quot;^.+\.tsx?$&quot;: [&#39;ts-jest&#39;, {}],
    },

    // 테스트 커버리지 리포트를 생성
    collectCoverage: true,
    converageDirectory: &#39;coverage/&#39;,

    // Jest가 모듈을 찾을 때 참조할 디렉토리들
    moduleDirectories: [&#39;node_modules&#39;, &#39;src&#39;],
    moduleNameMapper: {
        &quot;^@/(.*)$&quot;: &#39;&lt;rootDir&gt;/src/$1&#39;,
        &quot;^@tests/(.*)$&quot;: &#39;&lt;rootDir&gt;/tests/$1&#39;,
        ...
    }
};</code></pre><blockquote>
<h4 id="초기-설정-속성">초기 설정 속성</h4>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>testEnvironment</code></td>
<td>테스트가 실행될 환경. 보통 <code>&quot;node&quot;</code> 또는 <code>&quot;jsdom&quot;</code> 사용.</td>
</tr>
<tr>
<td><code>transform</code></td>
<td>파일을 테스트 전에 어떻게 변환할지 설정. TypeScript라면 <code>ts-jest</code> 사용.</td>
</tr>
<tr>
<td><code>testMatch</code></td>
<td>어떤 파일을 테스트로 인식할지 glob 패턴으로 지정.</td>
</tr>
<tr>
<td><code>testRegex</code></td>
<td><code>testMatch</code> 대신 정규식으로 테스트 파일 경로 지정 가능.</td>
</tr>
<tr>
<td><code>collectCoverage</code></td>
<td><code>true</code>로 설정하면 테스트 커버리지 리포트를 생성함.</td>
</tr>
<tr>
<td><code>coverageDirectory</code></td>
<td>커버리지 리포트를 저장할 디렉토리 이름 설정. 기본값은 <code>&quot;coverage&quot;</code>.</td>
</tr>
<tr>
<td><code>coveragePathIgnorePatterns</code></td>
<td>커버리지 측정에서 제외할 경로 목록.</td>
</tr>
<tr>
<td><code>moduleNameMapper</code></td>
<td>경로 별칭(alias) 설정할 때 사용. 예: <code>@/</code> → <code>src/</code></td>
</tr>
<tr>
<td><code>setupFiles</code></td>
<td>테스트 실행 전에 설정할 스크립트 목록. 예: 환경 변수 설정</td>
</tr>
<tr>
<td><code>setupFilesAfterEnv</code></td>
<td>각 테스트 환경 설정 후 실행할 스크립트 (예: <code>jest-extended</code>, <code>@testing-library/jest-dom</code>)</td>
</tr>
<tr>
<td><code>moduleFileExtensions</code></td>
<td>Jest가 인식할 파일 확장자 목록. 기본값: <code>[&quot;js&quot;, &quot;json&quot;, &quot;jsx&quot;, &quot;ts&quot;, &quot;tsx&quot;, &quot;node&quot;]</code></td>
</tr>
<tr>
<td><code>roots</code></td>
<td>Jest가 테스트를 찾을 디렉토리 목록. 기본값은 <code>[&quot;&lt;rootDir&gt;&quot;]</code>.</td>
</tr>
<tr>
<td><code>globals</code></td>
<td>전역 설정값 지정. 예: <code>ts-jest</code> 관련 설정</td>
</tr>
<tr>
<td><code>verbose</code></td>
<td><code>true</code>로 설정하면 테스트 실행 결과를 더 자세히 출력함.</td>
</tr>
<tr>
<td><code>bail</code></td>
<td>실패한 테스트가 있으면 즉시 중단할지 여부. 기본은 <code>false</code>.</td>
</tr>
<tr>
<td>jest.config.js 파일 초기 설정을 할 때 조금 애먹었던건 tsConfig.js 설정에서 paths로 설정해뒀던 경로들을 jest.config.js에도 설정을 해야한다는 거였다!</td>
<td></td>
</tr>
<tr>
<td>설정을 안하면 별칭으로 import하는 클래스들을 찾아오지 못한다..ㅎ</td>
<td></td>
</tr>
</tbody></table>
</blockquote>
<h4 id="3️⃣-세번째로-단위-테스트용-디렉토리를-분리한다">3️⃣ 세번째로 단위 테스트용 디렉토리를 분리한다.</h4>
<p>보통 단위 테스트용 데이터를 모아두는 디렉토리로 <code>__mock__/</code>, <code>factory/</code>, <code>fixtures/</code>를 사용한다고 한다.</p>
<p><strong>폴더 구조 예시를 보여주겠다!</strong></p>
<pre><code>project-root/
├── src/
│
├── tests/                            ← 테스트 전용 디렉토리
│   ├── unit/                         ← 단위 테스트
│   │   ├── utils/
│   │   │   └── utils.test.ts
│   │   └── services/
│   │       └── post.service.test.ts
│   │
│   ├── integration/                  ← 통합 테스트
│   │   └── user-flow.test.ts
│   │
│   ├── __mocks__/                    ← 모킹 객체/모듈
│   │   ├── axios.ts                  ← 예: axios 모킹
│   │   └── fs.ts
│   │
│   ├── factory/                      ← 테스트용 객체 생성 함수
│   │   └── userFactory.ts
│   │
│   ├── fixtures/                     ← 고정된 더미 데이터 (JSON 등)
│   │   ├── sample-user.json
│   │   └── config-fixture.ts
│   │
│   ├── setupTests.ts                ← 테스트 환경 초기 설정 (예: jest setup)
│   └── jest.global.d.ts             ← 전역 타입 정의 (필요시)
│
├── jest.config.ts
├── tsconfig.json
└── package.json</code></pre><blockquote>
<h3 id="📁-디렉터리-설명">📁 디렉터리 설명</h3>
</blockquote>
<ul>
<li>unit - 컴포넌트나 서비스 등 모듈 단위 테스트</li>
<li>intergration - 여러 모듈이 엮인 흐름 테스트</li>
<li><code>__mocks__</code> - 모듈이나 라이브러리를 mocking할 때 사용 (axios, fs, i18n 등)</li>
<li>factory - 객체 생성 도우미 함수들 (동적 더미 객체 생성)</li>
<li>fixtures - 고정된 더미 데이터 (예: JSON, config 등)</li>
<li>setupTests.ts - 글로벌 테스트 설정 (ex: jest.useFakeTimers(), cleanup() )</li>
<li>jest.global.d.ts - Jest 커스텀 matchers, 글로벌 변수 타입 정의</li>
</ul>
<h4 id="4️⃣-네번째로-테스트용-코드를-작성한다">4️⃣ 네번째로 테스트용 코드를 작성한다.</h4>
<p>실제 데이터베이스에 접근하는 PostDao와 같은 DB 접근 객체는 <code>jest.fn()</code>을 사용해 mock으로 대체하여 테스트 한다.
<code>jest.fn()</code>은 함수를 mocking, <code>jest.mock()</code>은 모듈 자체를 mocking 하는 것이다.
<code>describe()</code>는 관련 테스트들을 그룹화하고, <code>it()</code> 또는 <code>test()</code>는 각각의 테스트 케이스를 작성하는 데 사용한다.</p>
<pre><code>/**
 * Post Service Unit Test 파일
 */

import &#39;reflect-metadata&#39;;
import { PostService } from &#39;@/services/post.service&#39;;
import { S3FileStorageService } from &quot;@/services/file.service&quot;;
import { PostDao } from &quot;@/daos/post.dao&quot;;
import { describe } from &quot;node:test&quot;;

// jest.mock: 특정 모듈 전체를 mock 처리할 때 사용
jest.mock(&#39;@/services/file.service&#39;);

describe(&#39;PostService&#39;, () =&gt; {
  let postService: PostService;
  let s3FileStorageService: jest.Mocked&lt;S3FileStorageService&gt;;
  let postDao: jest.Mocked&lt;PostDao&gt;;

  beforeEach(() =&gt; {
    // S3FileStorageService를 mock instance로 생성
    s3FileStorageService = {
      deleteFiles: jest.fn(),
    } as unknown as jest.Mocked&lt;S3FileStorageService&gt;;

    // postDao mock 생성
    postDao = {
      findAll: jest.fn(),    // 함수 mocking
      findById: jest.fn(),
      create: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    } as unknown as jest.Mocked&lt;PostDao&gt;;

    postService = new PostService(postDao, s3FileStorageService);
  });

  // describe: findAll 테스트 그룹화 
  describe(&#39;findAll()&#39;, () =&gt; {
    // it: 테스트 케이스 작성(test로 작성해도 무방)
    it(&quot;전체 게시글 조회에 성공하고 데이터를 반환한다.&quot;, async () =&gt; {
      const mockPosts = [{id: 1, title: &#39;테스트 게시글&#39;}];

      // postDao.findAll의 응답을 정한다.
      postDao.findAll.mockResolvedValue({
        success: true,
        data: mockPosts
      });

      // postService.findAll() 실행
      const result = await postService.findAll();

      // 예상 응답과, 실제 응답이 같은지 비교한다.
      expect(result).toEqual({ success: true, data: mockPosts });
      expect(postDao.findAll).toHaveBeenCalled();
    });

    it(&quot;전체 게시글 조회 내역이 없어서 404 오류를 반환한다.&quot;, async () =&gt; {
      postDao.findAll.mockResolvedValue({
        success: false,
        data: []
      });

      await expect(postService.findAll()).rejects.toThrow(&#39;게시글을 찾을 수 없습니다.&#39;);
      expect(postDao.findAll).toHaveBeenCalled();
    })

    it(&quot;전체 게시글 조회 중 오류 발생으로 500 에러 반환&quot;, async () =&gt; {
      postDao.findAll.mockResolvedValue({
        success: false,
        error: &quot;전체 Post 조회 중 문제 발생&quot;
      });

      await expect(postService.findAll()).rejects.toThrow(&#39;전체 Post 조회 중 문제 발생&#39;);
      expect(postDao.findAll).toHaveBeenCalled();
    })
  });
});</code></pre><h4 id="5️⃣-다섯번째로-테스트-실행-명령어를-입력하여-테스트를-한다">5️⃣ 다섯번째로 테스트 실행 명령어를 입력하여 테스트를 한다.</h4>
<p><code>npx jest</code>를 직접 console에 입력해서 실행을 해도 되고, package.json scripts에 등록해서 사용해도 된다!</p>
<p> <strong>Scripts 추가 예시</strong>
 <img src="https://velog.velcdn.com/images/jiseong_98/post/26822edd-396f-4e74-81f2-244461321502/image.png" alt=""></p>
<blockquote>
<p><strong>npx jest 관련 옵션</strong></p>
</blockquote>
<ul>
<li><code>--watchAll</code> : 
프로젝트의 모든 테스트를 감시하면서 실행한다.
(git 상태 무시하고  전체 실행)</li>
<li><code>--watch</code> :
Git의 변경된 파일들만 감시해서 테스트 실행
(예: <code>git status</code>로 modified만 파일들 중심)</li>
<li><code>--coverage</code> :
테스트 커버리지 리포트도 함께 출력</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 프로그래밍 (3) - SOLID 원칙]]></title>
            <link>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-3-SOLID-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-3-SOLID-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Sun, 06 Apr 2025 20:23:07 GMT</pubDate>
            <description><![CDATA[<p><strong>객체 지향 프로그래밍(OOP)</strong>으로 프로젝트를 개발할 때, 지켜야할 원칙이 있다! 
그것이 바로 <strong>&quot;SOLID 원칙&quot;</strong>
SOLID 원칙은 <strong>유지보수성과 확장성</strong>이 뛰어난 코드를 설계하기 위한 핵심 철학이기 때문에 이 원칙들을 잘 지켜가며 개발하면 <strong>깔끔하고 안정적인 구조의 프로젝트</strong>를 만들 수 있을 것이다.
고로 이 포스트에선 SOLID 원칙을 지켜야하는 이유와 SOLID 원칙에 대해서 정리하고자 한다.</p>
<h2 id="solid-원칙이란">SOLID 원칙이란?</h2>
<p>SOLID는 객체 지향 설계의 5가지 핵심 원칙을 의미하는 약자다!</p>
<blockquote>
<p>S - 단일 책임 원칙 (Single Responsibility Principle)
O - 개방/폐쇄 원칙 (Open-Close Principle)
L - 리스코프 치환 원칙 (Liskov Substitution Principle)
I - 인터페이스 분리 원칙 (Interface Segregation Principle)
D - 의존 역전 원칙 (Dependency Inversion Principle)</p>
</blockquote>
<hr>
<h3 id="1-단일-책임-원칙-srp-single-responsibility-principle">1. 단일 책임 원칙 (SRP: Single Responsibility Principle)</h3>
<blockquote>
<p>클래스는 하나의 책임(기능)만 가져야 한다.</p>
</blockquote>
<h4 id="🧠-개념">🧠 개념</h4>
<ul>
<li>하나의 클래스는 하나의 일만 해야 한다.</li>
<li>여러 이유로 변경되면 안 된다.</li>
</ul>
<h4 id="💡-비유">💡 비유</h4>
<blockquote>
<p>자동차 클래스가 운전도 하고, 정비 일정 관리도 한다면? 너무 많은 일을 하게 되는 것!
운전은 Car, 정비는 MaintenanceManager가 각각 맡아야한다!</p>
</blockquote>
<h4 id="❌-안-좋은-예">❌ 안 좋은 예</h4>
<pre><code>class Vehicle {
  drive() { /* 주행 */ }
  scheduleMaintenance() { /* 정비 일정 관리 */ }  // 책임 과다!
}</code></pre><h4 id="✅-개선-예">✅ 개선 예</h4>
<pre><code>// 탈 것 클래스
class Vehicle {
  drive() { /* 주행 */ }
}

// 유지관리 클래스
class MaintenanceManager {
  scheduleMaintenance() { /* 정비 일정 관리 */ }
}</code></pre><hr>
<h3 id="2-개방-폐쇄-원칙-ocp-open-closed-principle">2. 개방-폐쇄 원칙 (OCP: Open-Closed Principle)</h3>
<blockquote>
<p>확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.</p>
</blockquote>
<h4 id="🧠-개념-1">🧠 개념</h4>
<ul>
<li>기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다.</li>
</ul>
<h4 id="💡-비유-1">💡 비유</h4>
<blockquote>
<p>새로운 자동차 종류(예: 전기차)를 만들기 위해 기존 <code>Car</code> 클래스를 수정하면, 기존 기능이 깨질 위험이 있다.
대신 확장을 통해 새로운 클래스를 만든다.</p>
</blockquote>
<h4 id="❌-안-좋은-예-1">❌ 안 좋은 예</h4>
<pre><code>class Vehicle {
  startEngine(type: string) {
    if (type === &#39;electric&#39;) console.log(&quot;전기차 엔진 ON&quot;);
    else if (type === &#39;gas&#39;) console.log(&quot;가솔린 엔진 ON&quot;);
  }
}</code></pre><h4 id="✅-개선-예-1">✅ 개선 예</h4>
<pre><code>interface Engine {
  start(): void;
}

// 전기차 엔진 클래스 생성
class ElectricEngine implements Engine {
  start() { console.log(&quot;전기차 엔진 ON&quot;); }
}

// 가솔린 엔진 클래스 생성
class GasEngine implements Engine {
  start() { console.log(&quot;가솔린 엔진 ON&quot;); }
}

class Vehicle {
  // 생성자: 사용하는 엔진 클래스로 객체 생성
  constructor(private engine: Engine) {}
  startEngine() {
    this.engine.start();
  }
}</code></pre><hr>
<h3 id="3-리스코프-치환-원칙-lsp-liskov-substitution-principle">3. 리스코프 치환 원칙 (LSP: Liskov Substitution Principle)</h3>
<blockquote>
<p>부모 클래스의 객체를 사용하는 곳에 자식 클래스 객체를 넣어도 문제가 없어야 한다.</p>
</blockquote>
<h4 id="🧠-개념-2">🧠 개념</h4>
<ul>
<li>자식 클래스는 부모의 기능과 계약을 유지한다.</li>
</ul>
<h4 id="💡-비유-2">💡 비유</h4>
<blockquote>
<p>탈 것(Vehicle)을 사용하는 코드에 Truck이나 ElectricCar를 넣어도 정상 동작 해야한다.
근데 ElectricCar인데 기름 넣는 <code>refule()</code>을 필수로 구현하라고 하면 안된다!</p>
</blockquote>
<h4 id="❌-안-좋은-예-2">❌ 안 좋은 예</h4>
<pre><code>// Vehicle을 사용하는 곳에 ElectricCar를 사용할 경우 refuel에서 에러가 발생!
class Vehicle {
  refuel() { console.log(&quot;기름 충전&quot;); }
}

class ElectricCar extends Vehicle {
  refuel() { throw new Error(&quot;전기차는 기름을 넣을 수 없습니다.&quot;); }  // 규약 위반
}</code></pre><h4 id="✅-개선-예-2">✅ 개선 예</h4>
<pre><code>// refuel() 메서드를 자식 클래스에서 정의하여 Vehicle을 사용하는 곳에 
// FuelVehicle, ElectricCar을 넣어도 아무런 문제가 발생하지 않음!  
class Vehicle {}

class FuelVehicle extends Vehicle {
  refuel() { console.log(&quot;기름 충전&quot;); }
}

class ElectricCar extends Vehicle {
  charge() { console.log(&quot;전기 충전&quot;); }
}</code></pre><hr>
<h3 id="4-인터페이스-분리-원칙isp-interface-segregation-principle">4. 인터페이스 분리 원칙(ISP: Interface Segregation Principle)</h3>
<blockquote>
<p>클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.</p>
</blockquote>
<h4 id="🧠-개념-3">🧠 개념</h4>
<ul>
<li>인터페이스는 작고 명확하게 분리하자.</li>
</ul>
<h4 id="💡-비유-3">💡 비유</h4>
<blockquote>
<p><code>CarService</code> 인터페이스에 <code>chargeElectric()</code>이 있으면, 휘발유차(Car)도 구현해야한다.
즉, 인터페이스는 각 클래스에 맞춰서 작게 나누는 것이 좋다.</p>
</blockquote>
<h4 id="❌-안-좋은-예-3">❌ 안 좋은 예</h4>
<pre><code>// GasCar는 사용하지 않는 메서드를 구현해야하므로 ISP를 위반한다
interface VehicleService {
  refuel(): void;
  chargeElectric(): void;
}

class GasCar implements VehicleService {
  refuel() {}
  chargeElectric() {} // 휘발유차에 필요 없는 기능!
}</code></pre><h4 id="✅-개선-예-3">✅ 개선 예</h4>
<pre><code>// 인터페이스를 기능 단위로 분리하고, 각 클래스는 필요한 기능만 구현한다
interface Refuelable {
  refuel(): void;
}

interface Chargeable {
  chargeElectric(): void;
}

class GasCar implements Refuelable {
  refuel() {}
}

class ElectricCar implements Chargeable {
  chargeElectric() {}
}

// 두 가지 모두 가능한 차는 다중 상속을 받으면 된다.
class HybridCar implements Refuelable, Chargeable {
  refuel() {}
  chargeElectric() {}
}</code></pre><hr>
<h3 id="5-의존-역전-원칙-dip-dependency-inversion-principle">5. 의존 역전 원칙 (DIP: Dependency Inversion Principle)</h3>
<blockquote>
<p>추상화에 의존해야지, 구현체에 의존하면 안 된다.</p>
</blockquote>
<h4 id="🧠-개념-4">🧠 개념</h4>
<ul>
<li>고수준 모듈은 저수준 모듈에 의존하지 않아야 한다.</li>
<li>둘 다 인터페이스나 추상 클래스에 의존해야 한다.</li>
</ul>
<h4 id="💡-비유-4">💡 비유</h4>
<blockquote>
<p>운전자가 어떤 엔진을 쓰는지는 모르고, Engine 인터페이스만 필요하다.
구체적인 <code>GasEngine</code>, <code>ElectricEngine</code>은 바껴도 문제 없어야 한다.</p>
</blockquote>
<h4 id="❌-안-좋은-예-4">❌ 안 좋은 예</h4>
<pre><code>// GasEngine()을 적접 생성하고 있어 고수준 모듈이 저수준 모듈에 의존해서는 안되는 원칙 위반
class GasEngine {
  start() { console.log(&quot;가솔린 엔진 ON&quot;); }
}

class Car {
  engine = new GasEngine(); // 구체적인 구현에 직접 의존
}</code></pre><h4 id="✅-개선-예-4">✅ 개선 예</h4>
<pre><code>// Car 객체 생성 시 사용하는 엔진 클래스를 주입하여 사용하면 되기 때문에 저수준 모듈에 의존하지 않는다.
interface Engine {
  start(): void;
}

class GasEngine implements Engine {
  start() { console.log(&quot;가솔린 엔진 ON&quot;); }
}

class Car {
  constructor(private engine: Engine) {}
  startCar() {
    this.engine.start();
  }
}
</code></pre><hr>
<h2 id="solid-원칙을-지켜야-하는-이유">SOLID 원칙을 지켜야 하는 이유</h2>
<h3 id="1-유지보수가-쉬워진다">1. 유지보수가 쉬워진다.</h3>
<ul>
<li>시간이 지나면서 코드가 변경될 수밖에 없는데, SOLID 원칙을 적용하면 특정 부분만 고쳐도 전체 시스템이 안정적으로 유지된다.</li>
<li>버그가 발생해도 원인을 빠르게 파악하고, 수정이 쉽다.</li>
</ul>
<h3 id="2-확장에-유연하다">2. 확장에 유연하다.</h3>
<ul>
<li>새로운 기능을 추가할 때 기존 코드를 건드리지 않고 확장할 수 있다.</li>
<li>변화에 유연한 구조를 만들 수 있기 때문에, 새로운 요구사항에 대응하기 쉽다.</li>
</ul>
<h3 id="3-코드의-가독성과-명확성이-높아진다">3. 코드의 가독성과 명확성이 높아진다.</h3>
<ul>
<li>각 클래스나 모듈의 책임이 명확해지니 역할 분리가 확실해진다.</li>
<li>다른 사람이 코드를 읽을 때도, &quot;이 클래스는 뭐 하는 애지?&quot;가 쉽게 보인다.</li>
</ul>
<h3 id="4-재사용성과-테스트가-쉬워진다">4. 재사용성과 테스트가 쉬워진다.</h3>
<ul>
<li>각 구성 요소가 독립적이어서, 재사용도 쉽고 유닛 테스트 작성도 쉽다.</li>
<li>특히 DIP(의존 역전 원칙)는 Mock 객체 활용에 최적화돼 있다.</li>
</ul>
<h3 id="5-결합도를-낮추고-응집도를-높인다">5. 결합도를 낮추고, 응집도를 높인다.</h3>
<ul>
<li>모듈 간 의존도를 줄이고, 각 모듈의 책임을 집중시킨다.</li>
<li>즉, 한 클래스가 다른 클래스에 끌려다니지 않도록 만든다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 프로그래밍 (2) - 4가지 핵심 개념]]></title>
            <link>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-2-4%EA%B0%80%EC%A7%80-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-2-4%EA%B0%80%EC%A7%80-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sun, 06 Apr 2025 18:55:35 GMT</pubDate>
            <description><![CDATA[<p>객체 지향 프로그래밍(이하 OOP)의 4가지 핵심 개념은 OOP를 이해하는데 가장 기본이 되고, 각각이 실제로 소프트웨어를 &quot;잘 설계&quot;하는데 큰 역할을 한다!
고로 이번 포스트에서는 4가지 핵심 개념(캡슐화, 상속, 다형성, 추상화)에 대해 설명과 코드로 정리해보려고 한다! (코드는 타입스크립트로 작성하겠다!)</p>
<h2 id="🎯-객체-지향-프로그래밍의-4가지-핵심-개념">🎯 객체 지향 프로그래밍의 4가지 핵심 개념</h2>
<h3 id="1-캡슐화-encapsulation">1. 캡슐화 (Encapsulation)</h3>
<blockquote>
<p>데이터와 기능을 하나로 묶고, 외부에서 접근을 제한하는 것</p>
</blockquote>
<h4 id="🧠-개념">🧠 개념</h4>
<ul>
<li><p>객체는 자신의 데이터(속성)을 보호해야한다.</p>
</li>
<li><p>객체 내부의 세부 구현은 감추고(<code>private</code>), 필요한 것만 외부에 보여준다(<code>public</code>).</p>
</li>
<li><p>외부는 객체가 제공하는 메서드를 통해서만 데이터를 다를 수 있다.</p>
<h4 id="👍🏻-장점">👍🏻 장점</h4>
</li>
<li><p>객체의 무결성을 유지한다.</p>
</li>
<li><p>유지보수가 쉬워진다. 
(내부 구조가 바뀌어도 외부는 그대로이기 때문에 해당 부분만 수정하면 된다!)</p>
<h4 id="📝-코드">📝 코드</h4>
<pre><code>class Car {
private readonly brand: string; // readonly: 한 번 초기화 이후 변경할 수 없도록 사용하는 키워드
private oil: number;
// 객체 초기화
constructor(brand: string, oil?: number) {
  this.brand = brand;
  this.oil = oil ? oil : 0;
}

/* 메서드 */
// getter: 브랜드 확인 (읽기 전용)
getBrand(): string {
  return this.brand;
}

</code></pre></li>
</ul>
<p>  // setter: 기름 충전
  refuel(oil: number) {
    if (oil &lt;= 0) {
      console.log(&quot;주유량은 0보다 커야 합니다.&quot;);
      return ;
    }</p>
<pre><code>this.oil += oil;
console.log(`${oil}L가 충전되었습니다. 휘발유 잔여량: ${this.oil}L`);</code></pre><p>  }</p>
<p>  // getter: 주행 시작
  startDrive() {
    if (this.oil &lt;= 0) {
      console.log(&quot;현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.&quot;);
      return ;
    }</p>
<pre><code>console.log(&quot;주행을 시작합니다.&quot;);</code></pre><p>  }
}</p>
<p>const myCar = new Car(&#39;Hyundai&#39;);
myCar.getBrand(); // &#39;Hyundai&#39;
myCar.startDrive(); // 현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.
myCar.refuel(10); // &#39;10L가 충전되었습니다. 휘발유 잔여량: 10L&#39;
myCar.refuel(0);  // &#39;주유량은 0보다 커야 합니다.&#39;
myCar.startDrive(); // 주행을 시작합니다.</p>
<pre><code>#### ✅ 정리
- `private` 키워드로 내부 속성을 보호한다.
- `get`, `set` 메서드로 제한적이고 안전하게 접근이 가능하다.
- 외부에서는 객체의 내부 구현을 몰라도 메서드를 이용해 사용이 가능하다.(brand, oil 등을 몰라도 메서드를 사용해 주유, 주행 등 car 클래스의 기능을 사용할 수 있다.)
- 이렇게 하면 데이터 보호와 유효성 검사가 쉬워진다.

---
### 2. 상속 (Inheritance)
&gt; 부모 클래스의 속성과 메서드를 자식 클래스가 물려받는 것
즉, 기존 클래스를 확장하여 새로운 클래스를 만드는 것

#### 🧠 개념
- 공통된 속성과 기능을 부모 클래스에 정의하고,
자식 클래스는 그걸 **그대로 사용**하거나 **확장**할 수 있다.
- 중복 코드를 줄이고, 공통 로직을 재사용할 수 있게 해준다.
- 자식 클래스는 필요한 기능만 추가하거나 변경해서 사용할 수 있다(확장성↑).
- 상속하기 위해서는 `extends` 키워드를 상속 받을 클래스에 명시하여 사용한다.
#### 👍🏻 장점
- 코드 재사용성이 높아진다.
- 공통된 기능을 부모 클래스에서 관리하면 유지보수가 쉬워진다.
- 계층적인 구조로 소프트웨어를 설계할 수 있다(확장성↑).
#### 📝 코드</code></pre><p>// 부모 클래스: Vehicle
class Vehicle {
  protected readonly brand: string; // protected: 자식 클래스에서 접근 가능.</p>
<p>  protected constructor(brand: string) {
    this.brand = brand;
  }</p>
<p>  // getter: 브랜드 확인 (읽기 전용)
  getBrand(): string {
    return this.brand;
  }
}</p>
<p>// 자식 클래스: Car(부모클래스: Vehicle 상속)
class Car extends Vehicle {
  /* 속성 확장 */
  private oil: number = 0;</p>
<p>  // 생성자: 브랜드와 초기 기름량 설정
  constructor(brand: string, oil?: number) {
    super(brand); // 부모 생성자에 brand 전달
    this.oil = oil &amp;&amp; oil &gt; 0 ? oil : 0;
  }</p>
<p>  // getBrand() 메서드 오버라이드
  // override 키워드로 오버라이드 메소드인 것을 명시적으로 표시
  // * override 키워드는 TS 4.3 이상에서만 지원. (생략 가능)
  override getBrand(): string {
    return <code>브랜드: ${super.getBrand()}</code>;
  }</p>
<p>  /* 메서드 확장 */
  // setter: 기름 충전
  refuel(oil: number) {
    if (oil &lt;= 0) {
      console.log(&quot;주유량은 0보다 커야 합니다.&quot;);
      return ;
    }</p>
<pre><code>this.oil += oil;
console.log(`${oil}L가 충전되었습니다. 휘발유 잔여량: ${this.oil}L`);</code></pre><p>  }</p>
<p>  // getter: 주행 시작
  startDrive() {
    if (this.oil &lt;= 0) {
      console.log(&quot;현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.&quot;);
      return ;
    }</p>
<pre><code>console.log(&quot;주행을 시작합니다.&quot;);</code></pre><p>  }
}</p>
<p>const myCar = new Car(&#39;Hyundai&#39;);
myCar.getBrand(); // &#39;Hyundai&#39;
myCar.startDrive(); // 현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.
myCar.refuel(10); // &#39;10L가 충전되었습니다. 휘발유 잔여량: 10L&#39;
myCar.refuel(0);  // &#39;주유량은 0보다 커야 합니다.&#39;
myCar.startDrive(); // 주행을 시작합니다.</p>
<pre><code>#### ✅ 정리
- `extends` 키워드로 부모 클래스를 상속받는다.
- 부모 클래스의 기능을 재사용하고, 필요한 기능만 자식 클래스에서 추가 또는 오버라이드 한다.
- 코드 중복 없이 더 효율적인 구조를 구성할 수 있다.

---
### 3. 추상화 (Abstraction)
&gt; 복잡한 내부 구현은 숨기고, 중요한 개념만 외부에 공개하는 것

#### 🧠 개념
- 객체의 공통적인 특성과 동작만 뽑아내어 설계의 뼈대(추상 클래스 또는 인터페이스)를 만든다.
_    - 추상 클래스와 인터페이스에 대한 정리는 따로 포스트 하겠다!!_
- 구체적인 구현은 상속받은 자식 클래스에서 작성한다.
- 사용자는 내부 동작을 몰라도 외부 인터페이스만 보고 객체를 사용할 수 있다.

#### 👍🏻 장점
- 불필요한 정보를 숨겨 복잡도를 낮춘다.
- 코드의 설계가 명확해지고 유지보수가 쉬워진다.
- 다양한 구현체를 만들 수 있고, 확장이 용이하다.

#### 📝 코드</code></pre><p>// 부모 클래스: Vehicle (추상 클래스)
abstract class Vehicle {
  protected readonly brand: string; // protected: 자식 클래스에서 접근 가능</p>
<p>  // protected 키워드로 자식 클래스에서 접근 가능하도록 설정
  protected constructor(brand: string) {
    this.brand = brand;
  }</p>
<p>  // getter: 브랜드 확인 (읽기 전용)
  getBrand(): string {
    return this.brand;
  }</p>
<p>  abstract startDrive(): void;      // startDrive를 추상화 메서드로 설정
}</p>
<p>// 자식 클래스: Car (Vehicle을 상속)
class Car extends Vehicle {
  /* 속성 확장 */
  private oil: number;</p>
<p>  // 생성자: 브랜드와 초기 기름량 설정
  constructor(brand: string, oil?: number) {
    super(brand); // 부모 생성자에 brand 전달
    this.oil = oil &amp;&amp; oil &gt; 0 ? oil : 0;
  }</p>
<p>  // getBrand() 메서드 오버라이드
  // override 키워드로 오버라이드 메소드인 것을 명시적으로 표시
  // * override 키워드는 TS 4.3 이상에서만 지원. (생략 가능)
  override getBrand(): string {
    return <code>브랜드: ${super.getBrand()}</code>;
  }</p>
<p>  /* 메서드 확장 */
  // setter: 기름 충전
  refuel(oil: number) {
    if (oil &lt;= 0) {
      console.log(&quot;주유량은 0보다 커야 합니다.&quot;);
      return ;
    }</p>
<pre><code>this.oil += oil;
console.log(`${oil}L가 충전되었습니다. 휘발유 잔여량: ${this.oil}L`);</code></pre><p>  }</p>
<p>  // getter: 주행 시작
  startDrive() {
    if (this.oil &lt;= 0) {
      console.log(&quot;현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.&quot;);
      return ;
    }</p>
<pre><code>console.log(&quot;주행을 시작합니다.&quot;);</code></pre><p>  }
}</p>
<p>const myCar = new Car(&#39;Hyundai&#39;, 0);
myCar.getBrand(); // &#39;브랜드: Hyundai&#39;
myCar.startDrive(); // 현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.
myCar.refuel(10); // &#39;10L가 충전되었습니다. 휘발유 잔여량: 10L&#39;
myCar.refuel(0);  // &#39;주유량은 0보다 커야 합니다.&#39;
myCar.startDrive(); // 주행을 시작합니다.</p>
<pre><code>
#### ✅ 정리
- `abstract` 키워드로 추상 클래스나 추상 메서드를 정의한다.
- 추상 클래스는 인스턴스를 만들 수 없고, 상속해서 사용해야 한다.
- 실제 동작은 자식 클래스에서 정의하므로 유연하고 확장 가능한 설계가 가능하다.

---
### 4. 다형성 (Polymorphism)
&gt; 같은 인터페이스로 다양한 객체를 일관되게 사용할 수 있는 것

#### 🧠 개념
- 같은 부모 클래스 또는 인터페이스를 상속받은 객체들이 동일한 메서드를 서로 다르게 동작한다.
- 즉, 하나의 메서드가 상황에 따라 다양한 방식으로 실행될 수 있다.

#### 👍🏻 장점
- 코드 유연성과 확장성이 증가한다.
- 새로운 클래스가 추가되어도 기존 코드를 수정하지 않아도 된다(OCP 원칙).
- 다양한 객체를 하나의 타입으로 통합해 관리할 수 있다.

#### 📝 코드</code></pre><p>// 부모 클래스: Vehicle (추상 클래스)
abstract class Vehicle {
  protected readonly brand: string; // protected: 자식 클래스에서 접근 가능</p>
<p>  // protected 키워드로 자식 클래스에서 접근 가능하도록 설정
  protected constructor(brand: string) {
    this.brand = brand;
  }</p>
<p>  // getter: 브랜드 확인 (읽기 전용)
  getBrand(): string {
    return this.brand;
  }</p>
<p>  abstract startDrive(): void;      // startDrive를 추상화 메서드로 설정
}</p>
<p>// 자식 클래스: Car (Vehicle을 상속)
class Car extends Vehicle {
  /* 속성 확장 */
  private oil: number;</p>
<p>  // 생성자: 브랜드와 초기 기름량 설정
  constructor(brand: string, oil?: number) {
    super(brand); // 부모 생성자에 brand 전달
    this.oil = oil &amp;&amp; oil &gt; 0 ? oil : 0;
  }</p>
<p>  // getBrand() 메서드 오버라이드
  // override 키워드로 오버라이드 메소드인 것을 명시적으로 표시
  // * override 키워드는 TS 4.3 이상에서만 지원. (생략 가능)
  override getBrand(): string {
    return <code>브랜드: ${super.getBrand()}</code>;
  }</p>
<p>  /* 메서드 확장 */
  // setter: 기름 충전
  refuel(oil: number) {
    if (oil &lt;= 0) {
      console.log(&quot;주유량은 0보다 커야 합니다.&quot;);
      return ;
    }</p>
<pre><code>this.oil += oil;
console.log(`${oil}L가 충전되었습니다. 휘발유 잔여량: ${this.oil}L`);</code></pre><p>  }</p>
<p>  // getter: 주행 시작
  startDrive() {
    if (this.oil &lt;= 0) {
      console.log(&quot;현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.&quot;);
      return ;
    }</p>
<pre><code>console.log(&quot;자동차 주행을 시작합니다.&quot;);</code></pre><p>  }
}</p>
<p>// 자식 클래스: Bike (Vehicle을 상속)
class Bike extends Vehicle {
  // 생성자: 초기 브랜드 설정
  constructor(brand: string) {
    super(brand);
  }</p>
<p>  startDrive() {
    console.log(&quot;자전거 주행을 시작합니다.&quot;);
  }
}</p>
<p>const myCar = new Car(&#39;Hyundai&#39;, 0);
const myBike = new Bike(&#39;Fixie&#39;);
myCar.getBrand();   // &#39;브랜드: Hyundai&#39;
myCar.startDrive(); // 현재 휘발유가 없어 주행할 수  없습니다. 기름을 충전해 주세요.
myCar.refuel(10);   // &#39;10L가 충전되었습니다. 휘발유 잔여량: 10L&#39;
myCar.refuel(0);    // &#39;주유량은 0보다 커야 합니다.&#39;</p>
<p>// 다형성: 동일한 메서드(startDrive)를 호출해도 다른 방식으로 동작함
myCar.startDrive();   // 자동차 주행을 시작합니다.
myBike.startDrive();  // 자전거 주행을 시작합니다.</p>
<p>```</p>
<h4 id="✅-정리">✅ 정리</h4>
<ul>
<li><code>startDrive()</code> 라는 하나의 메서드가 객체에 따라 다르게 실행된다.</li>
<li>동일한 부모 클래스를 상속받은 객체를 하나의 배열이나 함수로 묶어 처리할 수 있다.</li>
<li>새로운 타입의 탈것이 추가되어도 기존 코드는 그대로 사용할 수 있어 유지보수성이 좋다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 지향 프로그래밍 (1) - OOP란?]]></title>
            <link>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1-OOP%EB%9E%80</link>
            <guid>https://velog.io/@jiseong_98/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-1-OOP%EB%9E%80</guid>
            <pubDate>Sun, 06 Apr 2025 17:01:19 GMT</pubDate>
            <description><![CDATA[<p>클래스 기반 TypeScript를 새로 공부하면서 객체 지향 프로그래밍이 뭔지, 어떻게 사용하는지, 꼭 지켜야하는 원칙 등 다시 학습을 하기 위해 정리해보려고 한다!
이번 포스트에서는 객체 지향 프로그래밍이 무엇인지, 장단점이 뭔지, 언제 사용하는지에 대해 정리해보려고 한다!</p>
<h3 id="🎯-객체-지향-프로그래밍oop-object-oriented-programming이란">🎯 객체 지향 프로그래밍(OOP, Object-Oriented Programming)이란?</h3>
<p>프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 &#39;객체(Object)&#39;라는 기본 단위로 나누고 이들의 상호 작용으로 서술하는 방식이다.
객체 지향 프로그래밍의 반대 개념으로는, <strong>절차적 프로그래밍(PP, Procedure Programming)</strong> 이라는 것이 있다. 절차적 프로그래밍은 따로 정리해보겠다!</p>
<blockquote>
<h4 id="객체란">객체란?</h4>
<p>&#39;메소드, 변수&#39;를 가지며 특정 역할을 수행하도록 인간이 정의한 추상적인 개념이다.
쉽게 말해 현실 세계의 사물이나 개념을 코드로 표현한 것이라고 보면 된다.
ex) 사람, 자동차, 게시글 등</p>
</blockquote>
<hr>
<h3 id="🏗-객체-지향은-설계-기법이다">🏗 객체 지향은 설계 기법이다.</h3>
<p>객체 지향은 단순히 코드를 &quot;작성&quot;하는 방식이 아니라, 어떻게 구조화하고, 어떤 기준으로 책임을 나눌 것인지에 대한 설계 철학이다.</p>
<h4 id="📌-왜-설계-기법인가">📌 왜 설계 기법인가?</h4>
<ul>
<li>객체 지향은 프로그램을 구성하고 설계하는 방법에 대한 이야기이다.</li>
<li>어떤 클래스를 만들고, 어떤 책임을 갖게 하고, 어떤 객체들이 상호작용해야 할지를 먼저 설계한 뒤 구현에 들어간다.</li>
<li>즉, OOP는 코드를 어떻게 나눌 것인가, 모듈 간의 의존성을 어떻게 줄일 것인가, 변경에 유연하게 대응할 수 있는 구조는 어떤가 등을 고민하며 만드는 설계 중심의 접근법이다.</li>
</ul>
<blockquote>
<p>그래서 객체 지향은 단순한 문법의 사용이 아닌, <strong>좋은 소프트웨어 구조를 만들기 위한 설계 원칙</strong>으로 이해해야 한다.</p>
</blockquote>
<hr>
<h3 id="👍🏻-객체-지향-프로그래밍의-장점">👍🏻 객체 지향 프로그래밍의 장점</h3>
<h4 id="1-자연적인-모델링">1. 자연적인 모델링</h4>
<p>OOP는 사물이나 개념을 코드로 모델링하기 좋은 구조를 제공한다.
예를 들어 &quot;사람&quot;, &quot;자동차&quot;, &quot;게시글&quot; 같은 개념을 객체로 표현하면, 실제 업무 로직을 그대로 코드로 옮겨오기 쉬워지고, 이해하기 직관적인 구조를 만들 수 있다.</p>
<h4 id="2-유지보수와-확장성">2. 유지보수와 확장성</h4>
<ul>
<li>각 객체는 자신만의 책임과 역할을 가지므로, 변경 사항이 있어도 다른 객체에 영향을 최소화할 수 있다.</li>
<li>각 기능 확장시에도 새로운 클래스를 추가하거나 기존 객체를 상속, 위임, 합성 등의 방법으로 유연하게 확장할 수  있다.<h4 id="3-코드-재사용성">3. 코드 재사용성</h4>
</li>
<li>한 번 정의한 객체나 클래스를 다양한 곳에서 재사용할 수 있다.</li>
<li>상속이나 다형성을 활용하면 공통 로직을 추상화하고, 필요한 부분만 확장하는 구조로 재사용성을 극대화할 수 있다.</li>
</ul>
<h3 id="👎🏻-객체-지향-프로그래밍의-단점">👎🏻 객체 지향 프로그래밍의 단점</h3>
<h4 id="1-복잡한-구조">1. 복잡한 구조</h4>
<ul>
<li>OOP는 객체 간의 관계와 상호작용을 잘 설계해야 하기 때문에, 작은 프로젝트에 적용하면 오히려 과하게 복잡해질 수 있다!</li>
<li>클래스, 인터페이스, 상속 구조 등이 많아지면 코드의 구조를 이해하는데 시간이 오래 걸릴 수 있다.<h4 id="2-과도한-추상화의-위험">2. 과도한 추상화의 위험</h4>
</li>
<li>추상화는 유연성을 높여주지만, 지나친 추상화는 오히려 이해를 어렵게 만들고 유지보수를 힘들게 할 수 있다</li>
<li>객체를 지나치게 쪼개거나 역할을 억지로 분리하다 보면 불필요한 클래스가 많아져서 생산성이 떨어질 수 있다.<h4 id="3-실행-흐름-추적이-어려움">3. 실행 흐름 추적이 어려움</h4>
</li>
<li>객체 간에 메시지를 주고받으며 동작하기 때문에, 함수형 프로그래밍처럼 흐름이 직관적으로 보이지 않을 수 있다.</li>
<li>특히 초보자에게는 객체 간의 상호작용 구조가 디버깅이나 테스트를 어렵게 느껴지게 할 수 있다.<h4 id="4-단위-테스트가-어려울-수-있다">4. 단위 테스트가 어려울 수 있다.</h4>
</li>
<li>객체가 외부 상태에 의존하거나 다른 객체와 밀접하게 연결되어 있으면, 테스트를 위해 많은 설정(mocking, stub 등)이 필요할 수 있다.</li>
<li>의존성이 많은 객체는 테스트 코드 작성이 복잡해지고, 유닛 테스트의 순수성이 떨어질 위험도 있다.<h4 id="5-성능-오버헤드">5. 성능 오버헤드</h4>
</li>
<li>캡슐화, 다형성, 상속 등의 기능은 런타임 시 메모리 사용량이나 호출 비용이 늘어날 수 있다.</li>
<li>특히 실시간 처리가 중요한 시스템에서는, OOP보다는 절차지향이나 함수형 방식이 유리한 경우도 있다.</li>
</ul>
<hr>
<h3 id="🧱-그럼-oop는-언제-적합할까">🧱 그럼 OOP는 언제 적합할까?</h3>
<ul>
<li>복잡한 비즈니스 로직이 많은 대규모 프로젝트</li>
<li>도메인 중심 설계(Domain-Driven Design, DDD)를 도입한 시스템</li>
<li>협업이 많고 모듈화가 중요한 프로젝트</li>
<li>시간이 지남에 따라 유지보수/기능 추가가 자주 일어나는 장기 운영 서비스</li>
</ul>
<hr>
<h3 id="💬-객체-지향-프로그래밍-예시">💬 객체 지향 프로그래밍 예시!</h3>
<h4 id="자동차-객체-예시">자동차 객체 예시</h4>
<ul>
<li><p>속성 (Property): 객체가 가지는 고유한 데이터나 상태</p>
</li>
<li><p>메서드 (Method): 객체가 수행할 수 있는 기능이나 동작</p>
<pre><code>class Car {
/* 속성 */
private readonly brand: string; // readonly: 한 번 초기화 이후 변경할 수 없도록 사용하는 키워드
private color: string;

// 생성자: 객체가 생성될 때 속성 초기화
constructor(brand: string, color: string) {
  this.brand = brand;
  this.color = color;
}

/* 메서드 */
startEngine() {
  console.log(`${this.brand} 시동을 겁니다.`);
}

drive() {
  console.log(`${this.brand}를 운전 중입니다.`);
}

changeColor(color: string) {
  this.color = color;
  console.log(`자동차의 색상이 ${this.color}로 변경되었습니다.`)
}
}
</code></pre></li>
</ul>
<p>const myCar = new Car(&#39;Hyundai&#39;, &#39;black&#39;);
myCar.startEngine(); // Hyundai 시동을 겁니다.
myCar.changeColor(&#39;blue&#39;);    // 자동차의 색상이 blue로 변경되었습니다.</p>
<p>```</p>
<hr>
<h3 id="👪-객체지향언어의-종류">👪 객체지향언어의 종류</h3>
<p>Java, C++, C#, Phthon, JavaScript, TypeScript, Swift, Kotlin, Ruby, PHP, Objective-C 등</p>
<ul>
<li>Java : 100% 객체지향 언어에 가깝다. 대부분 대기업, 중견기업에서 많이 사용한다.</li>
<li>C++ : C 언어에 객체지향 개념이 추가된 언어이다. 성능 중심의 응용 프로그램에 사용된다.</li>
<li>C# : 마이크로소프트에서 만든 언어로, .NET 기반의 객체지향 프로그래밍에 최적화되어있다.</li>
<li>Python : 동적 타입 언어로 객체지향뿐 아니라 함수형, 절차형도 지원한다. AI, 머신러닝, 딥러닝, 데이터 분석 분야에서 많이 사용한다.</li>
<li>JavaScript : 원래는 함수형 기반 언어였지만, ES6 이후 클래스 기반 객체지향 프로그래밍으로 개발이 가능해졌다.</li>
<li>TypeScript : JavaScript에 정적 타입과 클래스 기반 객체지향 기능을 추가한 언어로 대규모 프로젝트에 적합하다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Green Developers 프로젝트(502 포텐데이) 후기]]></title>
            <link>https://velog.io/@jiseong_98/Green-Developers-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8502-%ED%8F%AC%ED%85%90%EB%8D%B0%EC%9D%B4-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@jiseong_98/Green-Developers-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8502-%ED%8F%AC%ED%85%90%EB%8D%B0%EC%9D%B4-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 03 Apr 2025 12:42:40 GMT</pubDate>
            <description><![CDATA[<h1 id="🧠-mz오피스--mz세대를-위한-문자메일-작성-도우미-ai-웹-서비스">🧠 MZ오피스 – MZ세대를 위한 문자/메일 작성 도우미 AI 웹 서비스</h1>
<h2 id="1-프로젝트-설명">1. 프로젝트 설명</h2>
<ul>
<li><strong>서비스명:</strong> MZ오피스  </li>
<li><strong>한 줄 소개:</strong> MZ세대를 위한 문자/메일 작성 도우미 AI 챗봇 웹 서비스  </li>
<li><strong>서비스 링크:</strong> <a href="https://newbie.mz-office.site">https://newbie.mz-office.site</a>  </li>
<li><strong>참여 인원:</strong> 기획자 1명, 디자이너 1명, 백엔드 1명, 프론트 1명 (총 4명)  </li>
<li><strong>프로젝트 기간:</strong>  <ul>
<li>프로토타입 개발: 2025.02.21 ~ 2025.03.02 (10일)  </li>
<li>고도화 개발: 2025.03.03 ~ 2025.03.12 (10일)</li>
</ul>
</li>
</ul>
<h3 id="🎯-기획-배경">🎯 기획 배경</h3>
<p>최근 통계를 보면 신입 사원들이 직장 내에서 메일이나 문자 작성에 어려움을 겪는 경우가 많다고 한다.<br>MZ오피스는 이 문제를 해결해보자는 아이디어에서 시작된 서비스다.<br><em>참고 자료: 취업포털 사람인 (2020년 10월 9일, 기업 191개 대상  조사)</em></p>
<h3 id="🏆-수상-성과">🏆 수상 성과</h3>
<ul>
<li>프로토타입 개발 참여자 투표 3위  </li>
<li>고도화 트랙 결선(데모데이) <strong>최종 우수상 수상</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiseong_98/post/29e5fba5-fa00-48c4-a37a-eb90dbedd4ad/image.jpg" alt="데모데이 수상"></p>
<hr>
<h2 id="2-ncloud-적용-과정">2. Ncloud 적용 과정</h2>
<ul>
<li>사용한 NCP 서비스:<br><code>NCP Server(VPC), Load Balancer(VPC), Public IP, Global DNS, CLOVA Studio, Certificate Manager</code></li>
</ul>
<blockquote>
<p>아래는 우리 팀이 실제로 구성한 서비스 아키텍처다.<br><img src="https://velog.velcdn.com/images/jiseong_98/post/3cf0db06-8015-4c8d-baf3-a3df9997c6a7/image.png" alt="서비스 아키텍처"></p>
</blockquote>
<hr>
<h3 id="🖥-ncp-server-vpc">🖥 NCP Server (VPC)</h3>
<p>AWS의 EC2와 유사한 기능을 제공하며,<br>클라우드 환경에서 가상머신 형태로 서버, 스토리지, 데이터베이스 등을 직접 다룰 수 있다.<br>우리 팀은 프론트와 백엔드 서버를 따로 만들어서 각각 관리했다.</p>
<hr>
<h3 id="⚖️-ncp-load-balancer-vpc">⚖️ NCP Load Balancer (VPC)</h3>
<p>AWS의 ELB(Elastic Load Balancing)처럼,<br>들어오는 요청을 하나의 진입점으로 받아 여러 서버에 분산해주는 역할을 한다.</p>
<p>처음엔 Target Group을 생성해서 서버를 등록했고,<br>그 다음 Load Balancer를 만들면서 해당 Target Group을 연결했다.<br>그리고 Certificate Manager를 통해 Cloud Basic 인증서를 발급받아 HTTPS 리스너에 연결했고,<br>HTTP 리스너에는 redirection 규칙을 추가해서 HTTP 접근 시 HTTPS로 자동 리디렉션되도록 설정했다.</p>
<hr>
<h3 id="🌍-public-ip">🌍 Public IP</h3>
<p>서버에는 직접 Public IP를 붙이지 않고,<br>로드밸서에만 Public IP를 할당해서 외부에서 접근할 수 있게 구성했다.<br>이렇게 하면 실제 서버 IP는 외부에 드러나지 않아 보안적으로도 더 안정적이다.</p>
<hr>
<h3 id="🌐-global-dns">🌐 Global DNS</h3>
<p>도메인은 가비아에서 구매했고,<br>Global DNS에 등록해서 도메인 기반으로 서비스에 접근할 수 있게 만들었다.<br>호스트 도메인을 활용해 프론트와 백엔드를 나눠서 설정했다.</p>
<hr>
<h3 id="💬-clova-studio">💬 CLOVA Studio</h3>
<p>이건 Naver의 AI 관련 서비스다.<br>나는 이번 프로젝트에서 프론트엔드를 담당해서 잘 모른다.. 하핳 :)<br>나중에 기회가 되면 공부해서 따로 정리해볼 생각이다!</p>
<hr>
<h3 id="🔐-certificate-manager">🔐 Certificate Manager</h3>
<p>HTTPS 적용을 위해 필요한 인증서를 발급받는 서비스다.<br>인증서는 크게 세 가지로 나뉜다.</p>
<ul>
<li><strong>Cloud Basic</strong>: NCP 내에서만 사용 가능한 무료 인증서. Let&#39;s Encrypt와 비슷하다.  </li>
<li><strong>Advanced</strong>: 외부에서도 사용 가능한 유료 인증서.  </li>
<li><strong>Global Edge Dedicated</strong>: Global Edge 전용 무료 인증서.</li>
</ul>
<p>우리는 여기서 Cloud Basic을 선택했고,<br>Load Balancer에 연결해서 HTTPS 설정을 마무리했다.</p>
<hr>
<h2 id="3--ncloud-사용-중-만족했던-점--아쉬웠던-점">3.  Ncloud 사용 중 만족했던 점 &amp; 아쉬웠던 점</h2>
<p>만족했던 점은 NCP내에서 인증서를 무료로 생성 가능하고 로드밸런서로 쉽게 연동할 수 있었던게 만족스러웠다!</p>
<hr>
<h2 id="4-프로그램-참여-소감">4. 프로그램 참여 소감</h2>
<p>전체적으로 봤을 때, Ncloud 안에서 서버, 보안, 배포, 도메인까지 전부 연동이 잘 돼 있어서 생각보다 훨씬 수월하게 구성할 수 있었다.<br>특히 AWS를 써본 사람이라면 진입장벽이 거의 없을 거라 생각한다.</p>
<p>참고로 신규 가입 시 10만 원 상당의 크레딧도 제공되는데,<br>우리는 프로그램에 참여해서 추가로 20만 크레딧을 받았고,<br>조건을 만족하면 최대 70만 크레딧까지 받을 수 있다고 한다.<br>사실상 클라우드 배포랑 운영을 <strong>공짜로 경험해볼 수 있는</strong> 셈이다.</p>
<p><em>( 짧은 기간동안 프로젝트 완성 및 배포, 운영을 경험해보기에 좋은 프로그램 인거 같다! 프로젝트 아이디어가 좋으면 상도 받을 수 있으니 개인적으로 해당 프로그램에 참여하는 걸 추천한다! )</em></p>
<p>참여하고 싶은 사람은 아래 링크를 참고하면 될거 같다!
( <a href="https://bside.best/potenday">비사이드 포텐데이 링크</a> )</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript - 의존성 주입 라이브러리]]></title>
            <link>https://velog.io/@jiseong_98/TypeScript-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-1</link>
            <guid>https://velog.io/@jiseong_98/TypeScript-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-1</guid>
            <pubDate>Tue, 01 Apr 2025 21:42:40 GMT</pubDate>
            <description><![CDATA[<p>TypeScript 공부를 시작하고 나서,
익숙했던 함수형 구조에서 클래스형 구조로 개발을 하려니 생각보다 많이 어색하다… (˚ ˃̣̣̥⌓˂̣̣̥ )</p>
<p>&quot;어색하면 그냥 함수형으로 개발하면 되지 않나?&quot; 라고 생각할 수도 있지만,
TypeScript에서만 제대로 활용할 수 있는 몇몇 라이브러리들은 클래스형 구조를 기반으로 만들어져 있기 때문에 함수형 구조만 고집하면 오히려 TypeScript의 장점을 제대로 살리지 못하는 것 같았다.
그래서 지금은 어색하더라도 클래스형 구조에 익숙해지려고 노력 중이다! ٩( ᐖ )و</p>
<p>오늘은 그중에서도 TypeScript + Express.js 환경에서 활용할 수 있는 <strong>의존성 주입(Dependency Injection) 라이브러리</strong>인 <code>typedi</code>에 대해 공부해보려고 한다! 
<em>(Nest.JS는 의존성 주입 시스템을 내장하고 있어서 typedi를 사용하지 않는다고 한다.)</em></p>
<p>엇! 그러면 typedi를 쓸 거면 그냥 Nest.JS를 사용하면 되는거 아닌가?
이거에 답변은 &quot;NO!&quot; 이다.</p>
<p>Nest.JS는 구조가 체계적이고 안정적인 반면, 프레임워크 자체가 무거운 편이다.
반대로 Express.JS는 가볍고 자유도가 높아서, 내가 원하는 아키텍처를 유연하게 구성할 수 있다.</p>
<p>결국 중요한 건 <strong>내가 만들려는 프로젝트의 성격</strong>이다.</p>
<ul>
<li>프로젝트가 비교적 작고, 빠르게 개발하고 싶지만 기본적인 구조화는 하고 싶다?
👉 Express.js + typedi 조합이 적절하다! </li>
<li>대규모 프로젝트고, 체계적이고 안정적인 설계를 원한다?
👉 그럴 땐 NestJS를 선택하는 게 낫다!</li>
</ul>
<p><em>ps. 
쓰니가 typedi를 공부하다가 Nest.JS까지 검색 하게 되었는데..
 Nest.JS는 구조가 Spring Boot랑 비슷한거 같다.
 Nest.JS 잘하면 Spring Boot도 금방 배울 수 있을지도..?</em> 🤔</p>
<h2 id="typedi-란">typedi 란?</h2>
<blockquote>
<p>Node.JS에서 의존성 주입(Dependency Injection)을 가능하게 해주는 가변고 실용적인 DI 컨테이너.</p>
</blockquote>
<h3 id="typedi-데코레이터-종류와-역할">typedi 데코레이터 종류와 역할</h3>
<ul>
<li><code>@Service()</code> - 클래스를 DI 컨테이너에 등록</li>
<li><code>@Inject()</code> - 필드나 생성자 파라미터에 의존성 주입</li>
<li><code>@InjectRepository()</code> - TypeORM 리포지토리 주입 (typeorm-typedi-extensions 사용 시)</li>
<li><code>Token</code> - 클래스 기반 식별자 객체 ( 데코레이터 아님 )</li>
<li><code>Container</code> - 수동으로 인스턴스 등록/가져오기 위한 객체 (데코레이터 아님!!)</li>
</ul>
<h3 id="1️⃣-service">1️⃣ @Service()</h3>
<blockquote>
<p>클래스를 typedi 컨테이너에 등록하는 데코레이터
클래스 위에서 사용하고 해당 클래스를 <strong>자동으로 싱글톤 인스턴스로 관리</strong>해준다!</p>
</blockquote>
<pre><code>import { Service } from &#39;typedi&#39;

@Service()
export class UserService {
    getUserName() {
        return &#39;jiseong choi&#39;;
    }
}</code></pre><h3 id="2️⃣-inject">2️⃣ @Inject()</h3>
<blockquote>
<p>말 그대로 <strong>주입(Inject)</strong>할 때 사용.
다른 클래스에서 정의한 Service를 사용하고 싶을때 사용한다.
사용방법은 <strong>생성자 주입 방식</strong>, <strong>필드 주입 방식</strong>이 있다!</p>
</blockquote>
<pre><code>/* 생성자 주입 방식 */
import { Inject, Service } from &#39;typedi&#39;

@Service()
export class PostService {
    constructor(
        @Inject(() =&gt; UserService) private userService: UserService,
    ) {}

    getPostAuthor() {
        return this.userService.getUserName;
    }
}

/* 필드 주입 방식 */
import { Inject, Service } from &#39;typedi&#39;

@Service()
export class PostService {
    @Inject(() =&gt; UserService)
    private userService!: UserService;

    getPostAuthor() {
        return this.userService.getUserName;
    }
}</code></pre><h4 id="❗-생성자-주입-방식과-필드-주입-방식의-차이점-">❗ 생성자 주입 방식과, 필드 주입 방식의 차이점 !</h4>
<blockquote>
<ul>
<li><strong>생성자 주입 방식</strong><ul>
<li>의존성을 <strong>생성자 파라미터</strong>를 통해 주입받는 방식.</li>
<li>클래스가 어떤 의존성을 필요로 하는지 명확하게 드러남</li>
<li>외부에서 인스턴스를 만들 때 mock 객체를 넘기기 쉬워 <strong>테스트하기에도 유리!</strong></li>
<li>클래스가 <code>@Service</code>로 DI 컨테이너에 등록이 되어 있는 경우 <code>@Inject</code> <strong>생략 가능!!</strong>
( 대신, <code>Reflect Metadata</code>를 사용해서 타입 정보를 읽어오는 방식이라
 tsconfig에 <code>emitDecoratorMetadata</code>가 설정이 되어있어야 한다! )</li>
<li><strong>생략이 불가능</strong>한 경우 (<code>@Inject</code> 필수)<ul>
<li><code>@Token()</code>으로 주입하는 경우 -&gt; <strong>클래스가 아니</strong>라서 자동으로 추론 ❌<ul>
<li><code>Container.set()</code>으로 <strong>수동 등록</strong>한 인스턴스를 주입하는 경우</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><strong>필드 주입 방식</strong><ul>
<li>클래스 내부 필드에 직접 데코레이터를 붙여 사용하는 방식</li>
<li>코드가 간단해 보인다는 장점이 있지만, 테스트할 때는 필드를 직접 교체해야한다!</li>
<li>테스트 코드에서는 <code>as any</code>를 사용해서 필드를 억지로 수정해야하는 경우도 있다.
(필드를 <code>public</code>이나 <code>protected</code>로 선언하면 수정을 안해도 되지만, 서비스 내부 구현을 외부에서 바꾸지 못하게 보호하는게 원칙이기 때문에 대부분 <code>private</code>를 사용한다.)</li>
</ul>
</li>
</ul>
</blockquote>
<h4 id="💡-테스트-코드를-통해-알아보자-jest-기준">💡 테스트 코드를 통해 알아보자! (Jest 기준)</h4>
<pre><code>/* 생성자 주입 방식 테스트 */
describe(&#39;PostService - 필드 주입 테스트&#39;, () =&gt; {
    it(&#39;getPostAuthor()는 유저 이름을 반환해야 한다.&#39;, () =&gt; {
        const mockUserService = {
            getUserName: () =&gt; &#39;MockUser&#39;,
        } as UserService;

        const postService = new PostService(mockUserService);
        expect(postService.getPostAuthor()).toBe(&#39;MockUser&#39;);
    });
});

/* 필드 주입 방식 테스트 */
describe(&#39;PostService - 필드 주입 테스트&#39;, () =&gt; {
    it(&#39;getPostAuthor()는 유저 이름을 반환해야 한다.&#39;, () =&gt; {
        const postService = new PostService();

        // 필드를 직접 수동으로 mock 객체로 설정
        (postService as any).userService = {
            getUserName: () =&gt; &#39;MockUser&#39;,
        } as UserService;

        const result = postService.getPostAuthor();
        expect(result).toBe(&#39;MockUser&#39;);
    });
});</code></pre><p><em><strong>🚩 이러한 이유들 때문에 테스트&amp;유지보수성을 고려했을 때, 생성자 주입 방식이 더 적합하다!</strong></em></p>
<h3 id="3️⃣-injectrepository-typeorm-연동-시">3️⃣ @InjectRepository() (TypeORM 연동 시)</h3>
<p>TypeORM + typedi를 함께 사용할 때, TypeORM의 Repository를 의존성 주입(DI) 방식으로 주입해주는 테코레이터다!</p>
<blockquote>
<p>즉, UserRepository 같은 TypeORM 레포지토리를 직접 new 하지 않고,
typedi가 자동으로 주입해주는 것!</p>
</blockquote>
<ul>
<li>typedi 자체에서 제공하는 건 아니고, 
<code>typeorm-typedi-extensions</code> 패키지를 설치해야 사용할 수 있다.</li>
<li>TypeORM 이란?<ul>
<li>typeorm은 sequelize랑 비슷함! - RDBMS 구조에 딱 맞는 ORM</li>
<li>typeorm - TypeScript에 최적화 </li>
<li>sequelize - JavaScript에 최적화</li>
<li>즉, <code>find()</code>, <code>findOneBy()</code> 같은 함수로 SQL 작성 없이 데이터 조회 가능!</li>
</ul>
</li>
</ul>
<h4 id="injectrepository-사용방법">@InjectRepository() 사용방법</h4>
<pre><code>/* data-source.ts 또는 main.ts, index.ts에서 앱 시작 전에 설정 */
import { useContainer } from &#39;typeorm&#39;;
import { Container } from &#39;typedi&#39;;

useContainer(Container);    // typedi를 TypeORM에 연결</code></pre><pre><code>/* user.service.ts */
import { Service } from &#39;typedi&#39;;
import { InjectRepository } from &#39;typeorm-typedi-extensions&#39;;
import { Repository } from &#39;typeorm&#39;;
import { User } from &#39;./entities/User&#39;;

@Service()
export class UserService {
    constructor(
        // Service에서 직접 사용해도되지만, dao에서 사용하고 dao를 의존성 주입해도 된다!
        @InjectRepository(User)    
        private readonly userRepository: Repository&lt;User&gt;,
    ) {}

    findAllUsers() {
        return this.userRepository.find();
    }
}</code></pre><h3 id="4️⃣-token-데코레이터-아님">4️⃣ Token (데코레이터 아님)</h3>
<p><code>typedi</code>에서 <code>Token</code>은 간단히 말하자면 <strong>의존성을 구분하기 위한 고유한 식별자</strong>이다.</p>
<pre><code>/* logger.interface.ts */
export interface Logger {
    log(message: string): void;
}


/* logger.ts - 구현체 생성 */
export class ConsoleLogger implements {
    log(message: string): void {
        console.log(`[ConsoleLogger] ${message}`);
    }
}
export class FileLogger implements {
    log(message: string): void {
        console.log(`[FileLogger] ${message} (written to file)`);
    }
}


/* token.ts - 토큰 생성 */
import { Token } from &#39;typedi&#39;
import type { Logger } from &#39;./logger.interface&#39;;

export const LoggerToken = new Token&lt;Logger&gt;();


/* main.ts 또는 app.ts - 런타임에서 분기 &amp; 주입 */
import { Container } from &#39;typedi&#39;;
import { LoggerToken } from &#39;./token&#39;;
import { FileLogger, ConsoleLogger } from &#39;./logger&#39;;

 // 환경에 따라 구현체 분기
if (process.env.NODE_ENV === &quot;production&quot;) {
    Container.set(LoggerToken, new FileLogger());
} else {
    Container.set(LoggerToken, new ConsoleLogger());
}

 // 실제 서비스 실행
const app = Container.get(UserService);


/* user.service.ts - 실제 주입받는 클래스 */
import { Inject, Service } from &#39;typedi&#39;;
import { Logger } from &#39;./logger.interface&#39;;
import { LoggerToken } from &#39;./token&#39;;

@Service()
export class UserService {
    constructor(
        @Inject(LoggerToken) private readonly logger: Logger
    ) {}
}
</code></pre><p>기본적으로 TypeDI는 <strong>클래스를 식별자(identifier)</strong>로 사용하여 의존성을 주입한다.
하지만 인터페이스를 식별자로 사용해 의존성 주입을 하려 한다면 문제가 발생한다.
그 이유는 TypeScript에서 인터페이스는 컴파일 타임에만 존재하고, 런타임에는 사라지기 때문이다.
반면 TypeDI의 의존성 주입은 런타임에 이루어지므로, 런타임에 존재하지 않는 인터페이스는 주입 대상이 없다는 에러를 발생시킨다.</p>
<p>이럴 때 “그렇다면 인터페이스를 주입하지 말고, 인터페이스를 구현한 클래스를 직접 주입하면 되지 않을까?” 라는 생각이 들 수 있다.
물론 가능하다! 하지만 문제는 환경이나 조건에 따라 사용하는 구현체가 달라질 수 있다는 점이다.
예를 들어 개발 환경에서는 ConsoleLogger, 운영 환경에서는 FileLogger를 쓰는 식이다.</p>
<p>이렇게 분기가 생기면, 그 분기 로직을 어디에 둘 것인가가 중요해진다.
클래스로 만들 수도 있고, 함수를 만들어서 분기할 수도 있다.
하지만 이렇게 분기하는 클래스나 함수를 만들 경우, 조건이 추가될 때마다 해당 분기 로직을 수정해야 한다.
이건 바로 OCP(개방-폐쇄 원칙) 위배다.</p>
<p>OCP란 기존 코드를 수정하지 않고 확장할 수 있어야 한다는 원칙이다.
즉, 분기 조건이 추가되더라도 비즈니스 로직 내부를 수정하지 않고, 외부에서 구현체를 주입받는 방식이 가장 이상적이다.
이렇게 하면 구조가 변경에 강해지고, 비즈니스 로직을 직접 수정하는 것이 아니기 때문에 실수로 인한 오류를 최소화 할 수 있다.</p>
<p>이런 이유로 TypeDI에서는 Token을 사용하여 인터페이스의 구현체를 명시하고,
인터페이스를 식별자로 사용하는 의존성 주입을 가능하게 만드는 것이다.</p>
<h3 id="5️⃣-container-데코레이터-아님">5️⃣ Container (데코레이터 아님)</h3>
<p><code>Container</code>는 typedi에서 제공하는 중심 유틸 객체로,<br><strong>직접 의존성을 주입하거나, 인스턴스를 수동으로 등록할 때</strong> 사용한다.</p>
<ul>
<li><strong>의존성 등록</strong>: 특정 Token이나 클래스에 어떤 객체를 사용할지 명시적으로 등록 (<code>Container.set(...)</code>)</li>
<li><strong>의존성 주입</strong>: 등록된 객체를 필요한 곳에 자동으로 주입 (<code>@Inject(...)</code>, <code>Container.get(...)</code>)</li>
<li><strong>싱글 인스턴스 관리</strong>: 같은 의존성은 기본적으로 한 번만 생성되어 재사용됨 (싱글턴 패턴)</li>
<li><strong>모듈 간 결합도 낮춤</strong>: 객체를 직접 <code>new</code> 하지 않고 컨테이너에서 주입받기 때문에 느슨한 결합이 가능</li>
</ul>
<p>따라서 <code>Container</code>는 앞에서 설명한 <code>@Service()</code>, <code>@Inject()</code>, <code>@InjectRepository()</code>, <code>Token</code> 등을 <strong>등록하고 유지하며, 의존성 주입 시스템이 제대로 작동하도록 만들어주는 핵심 객체</strong>이다.</p>
<h2 id="💬-typedi-정리를-마치며">💬 TypeDI 정리를 마치며...</h2>
<p>TypeDI에 대해서 공부하고 정리하면서 가장 어려웠던 내용은 Token이었다.
검색을 해도 자료가 많이 없고, 공식 문서에도 설명이 너무 짧아서 어떤 상황에서 왜 사용하는지를 알기가 어려웠다.
결국 ChatGPT한테 무한 질문을 던지면서
내가 이해한 내용이 맞는지 하나하나 확인해가며 공부했다.
그 과정에서 Token을 사용하는 이유와 언제, 어떻게 사용하는지를 정확하게 알 수 있었다.</p>
<p>비록 시간은 오래 걸렸지만, 이해하고 나니 나름 뿌듯하다..🙃 하하하</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript 개발자가 TypeScript를 공부하게 된 이유 & velog를 시작한 이유]]></title>
            <link>https://velog.io/@jiseong_98/JavaScript-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-TypeScript%EB%A5%BC-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%9C%A0-velog%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@jiseong_98/JavaScript-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-TypeScript%EB%A5%BC-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B2%8C-%EB%90%9C-%EC%9D%B4%EC%9C%A0-velog%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Tue, 01 Apr 2025 12:27:08 GMT</pubDate>
            <description><![CDATA[<p>최근 채용 시장을 보면 TypeScript라는 키워드가 자주 눈에 띄었다.
그래서 “TypeScript가 뭘까?” 싶어서 검색해보니, JavaScript에 타입을 추가한 확장형 언어라고 한다!
즉, JavaScript의 모든 기능은 그대로 사용할 수 있고, 여기에 변수나 함수의 타입을 명시해서
JavaScript에서 발생할 수 있는 런타임 오류를 줄일 수 있는, 좀 더 안정적인 언어인 것 같았다.</p>
<p>&#39;더 안정적이다&#39;라는 말은 곧, 앞으로는 TypeScript를 쓰는 곳이 더 많아지고,
JavaScript만 사용하는 곳은 점점 줄어들 수도 있다는 생각이 들었다.
그래서 나도 TypeScript를 공부해보기로 마음먹었다.</p>
<p>처음 TypeScript를 접하는 입장이라, 새 프로젝트를 하나 만드는 것보다는
기존 JavaScript 기반 프로젝트를 TypeScript로 리팩토링하면
시간도 절약되고 JavaScript와의 차이도 더 잘 느낄 수 있을 것 같았다.
그래서 예전에 인프런을 들으며 만든 서버 프로젝트를 TypeScript로 리팩토링하고 있다!</p>
<p>TypeScript에 대한 지식이 거의 없어서, 주로 ChatGPT를 활용하고 있는데
정말 큰 도움이 되고 있다. 라이브러리 추천도 해주고,
JavaScript와 어떻게 다른지 설명도 잘 해줘서 실무적인 감각도 빠르게 익히는 느낌이다.</p>
<p>물론, ChatGPT가 알려주는 코드를 그대로 쓰기보다는
왜 이걸 사용하는지, 대체 가능한 다른 방식은 없는지 등을 비교하고
노션에 따로 정리도 해두고 있다.</p>
<p>그런데 이렇게 혼자 보기엔 좀 아깝달까..?
나처럼 비슷한 고민을 하는 사람들도 있을 것 같아서
기술 블로그에 내용을 정리해서 공유해보기로 했다.</p>
<p>앞으로도 TypeScript 관련 내용이나 CS 개념들을
공부하면서 잘 정리해 올려보려고 한다 :)</p>
]]></description>
        </item>
    </channel>
</rss>