<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-kms.log</title>
        <link>https://velog.io/</link>
        <description>BackEnd &amp; Serverless &amp; AI Engineer</description>
        <lastBuildDate>Mon, 10 Mar 2025 08:00:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-kms.log</title>
            <url>https://velog.velcdn.com/images/dev-kms/profile/e4ee57a0-ef0f-438b-997c-e6a0c78e49bc/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-kms.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-kms" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Project - AI Games]]></title>
            <link>https://velog.io/@dev-kms/Project-AI-Games</link>
            <guid>https://velog.io/@dev-kms/Project-AI-Games</guid>
            <pubDate>Mon, 10 Mar 2025 08:00:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="ai를-활용한-퍼즐-게임-개발">AI를 활용한 퍼즐 게임 개발</h3>
</blockquote>
<h3 id="프로젝트-개요">프로젝트 개요</h3>
<ul>
<li>뉴스 기사를 기반으로 AI 기술을 접목하여 크로스워드 및 초성어 게임을 개발한 프로젝트</li>
<li>인적 자원을 최소화하며 전날의 기사를 기반으로 게임에 필요한 리소스를 자동으로 생성</li>
</ul>
</br>

<p>** AI <em>(ChatGPT 4o)</em> ** 를 이용한 추출 목록 대상 </p>
<ol>
<li>키워드 : 기사에서 주요한 키워드</li>
<li>Clue : 추출된 키워드와 기사를 연관시켜 사용자가 키워드를 맞출수 있는 힌트</li>
<li>Definition : 키워드의 사전적 정의 또는 객관적 사실을 생성하여 사용자의 이해를 보조</li>
</ol>
</br>

<hr>
<p>** <em>개발 배경</em> **
_ 뉴욕타임즈의 유명한 크로스워드 게임에서 영감을 받아
한국어의 특성을 반영하여 AI 기반 크로스워드 개발 및
기존 국내 크로스워드의 한계점 ( 단어 조합의 어려움 ) 을 
극복하기 위해 Ai 및 알고리즘을 결합하여 새로운 방식을 구현
_</p>
<hr>
</br>

<h3 id="개발-과정에서의-여러-시도"><strong><em>개발 과정에서의 여러 시도</em></strong></h3>
<p># <strong>1. 알고리즘 기반 접근</strong></p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/656bdbd8-ce30-4189-939f-52adecf836b5/image.png" width = "70%"/>

<p>*<em>단어 파생을 잘 잡아내고 랜덤한 조합을 잘 찾아내지만 <span style = "color : red">  군집을 하나밖에 생성</span>하지 못함 *</em></p>
</br>

<p># <strong>2. LLM 프롬프트 엔지니어링</strong></p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/89b46f78-798e-431f-ac65-0c4d7bf97c8e/image.png" width = "70%"/>

<p>*<em>단어 파생을 잘 잡아내고 랜덤한 조합과 여러 군집을 생성하지만 <span style = "color : red"> 매우 느린 동작 속도 </span> *</em></p>
</br>

<p># <strong>3. 최종방법 ( 알고리즘 + AI 프롬프트 함수화 )</strong></p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/0b3c7e54-5a7e-4747-9165-16ede1c38cf8/image.png" width = "70%"/>

<img src = "https://velog.velcdn.com/images/dev-kms/post/a40c8ce4-0ea9-45b9-ae13-917ccd6111d6/image.png" width = "40%"/>

<img src = "https://velog.velcdn.com/images/dev-kms/post/c12c62eb-f5b1-43ec-9ccf-711ac6bd0972/image.png" width = "80%"/>


<p>** 앞선 두 방법의 강점을 결합해 <span style = "color : deepskyblue"> 알고리즘</span>과 <span style = "color : deepskyblue">AI 프롬프트를 함수화 </span> 하여 
단어 배치의 제약사항을 효율적으로 처리,
빠른 퍼즐 생성 속도와 다수 군집 생성 기능을 모두 확보하여 실용화 가능성 상승**</p>
</br>

<hr>
<h3 id="알고리즘-시연-영상">알고리즘 시연 영상</h3>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/75565b3b-0ef9-4c79-b9b5-2f252548b682/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Project - Curator]]></title>
            <link>https://velog.io/@dev-kms/Project-Curator</link>
            <guid>https://velog.io/@dev-kms/Project-Curator</guid>
            <pubDate>Mon, 24 Feb 2025 07:23:04 GMT</pubDate>
            <description><![CDATA[<p><span style = "color : deepskyblue">  </span>
<img src = "" /></p>
<blockquote>
<h3 id="서버에-의존하지-않는-serverless-aws-아키텍쳐-curator-">*<em>서버에 의존하지 않는 Serverless AWS 아키텍쳐 [Curator] *</em></h3>
</blockquote>
<br/>

<h3 id="프로젝트-개요">프로젝트 개요</h3>
<ul>
<li><strong>목표</strong>
사진기자가 촬영한 사진을 사내 이미지 허브에 업로드하는 과정이 느리고 번거로움.
이러한 작업을 자동화하여 마치 박물관의 큐레이터가 이미지를 정리하고 관리해주는 듯한
경험을 사용자에게 제공하는 것이 목표.</li>
</ul>
<p>이를 반영하여 프로젝트 명을 <strong>Curator</strong>로 명명함.</p>
<br/>

<hr>
<h3 id="기존의-업로드-과정">기존의 업로드 과정</h3>
<img src = "https://velog.velcdn.com/images/dev-kms/post/17ed7d74-cb65-4f81-ba3e-e5d346e9c01a/image.png" width = "100%"/>

<ol>
<li>사진 기자가 촬영한 이미지를 SD카드에서 컴퓨터로 이동</li>
<li>사내 허브 사이트에 로그인</li>
<li>이미지 업로드</li>
</ol>
</br>

<h3 id="개선된-업로드-과정">개선된 업로드 과정</h3>
<img src = "https://velog.velcdn.com/images/dev-kms/post/5cad6c41-546d-4ea9-8e2b-232c63480be9/image.png" width = "100%"/>

<ol>
<li>사진기자가 촬영한 이미지를 <strong>FTP</strong>(<strong><em>File Transfer Protocol</em></strong>)로 업로드</li>
</ol>
</br>

<hr>
<h3 id="기술-아키텍쳐">기술 아키텍쳐</h3>
<img src = "https://velog.velcdn.com/images/dev-kms/post/82d9f7fb-9d45-45eb-9b02-91544a5e492b/image.png" width = "100%"/>

<ul>
<li><strong>Amazon S3</strong> : FTP로 전송된 이미지를 저장</li>
<li><strong>Lambda</strong> : 이미지 처리 (썸네일 생성, 원본 복사, 메타정보 추출)</li>
<li>*<em>SNS &amp; SQS *</em> : 이미지 처리 요청 및 메시지 관리</li>
<li><strong>DynamoDB</strong> : 오류 로그 기록 및 알림 관리</li>
</ul>
</br>

<hr>
<h3 id="구현-및-서비스-흐름">구현 및 서비스 흐름</h3>
<img src = "https://velog.velcdn.com/images/dev-kms/post/c0121ea4-a260-44b2-b94b-63e1a396ed80/image.png" />

<ol>
<li>사용자가 FTP를 통해 이미지를 업로드하면 이미지가 AWS S3버킷에 저장됨.</br>

</li>
</ol>
<img src = "https://velog.velcdn.com/images/dev-kms/post/d137f3c5-2d2d-463e-b196-d0822a54a072/image.png" />

<ol start="2">
<li>S3의 Create이벤트를 트리거로 SNS로 이미지 정보 전송</br>

</li>
</ol>
<img src = "https://velog.velcdn.com/images/dev-kms/post/7cda809e-4bff-4c54-a61a-b2dc8bd60e7a/image.png" />


<ol start="3">
<li>SNS에서 구독중인 SQS 이벤트 메시지 전송</br>

</li>
</ol>
<img src = "https://velog.velcdn.com/images/dev-kms/post/b1591c11-b5fc-4597-a6b2-df97a4842f5e/image.png" />

<ol start="4">
<li>SQS의 트리거로 대상 람다함수들에게 이미지 정보 전송</li>
</ol>
<ul>
<li>사진 모음 페이지에서 이미지 로딩 시간을 빨리하기 위한 프리뷰 썸네일 이미지 생성 람다</li>
<li>원본 이미지를 복사하여 보관용 S3 로 이동시키는 람다</li>
<li>이벤트 객체로 넘어온 이미지의 메타 정보를 추출한 뒤 허브 API를 호출, Library DB에 메타정보 저장</li>
</ul>
</br>

<img src = "https://velog.velcdn.com/images/dev-kms/post/aeebb146-f1d6-4a1a-86b6-4cc1444c81e6/image.png" />

<ol start="5">
<li>람다함수를 통해 생성된 썸네일과 복사된 이미지는 
허브 라이브러리가 바라보고있는 S3 버켓으로 옮겨지며 허브에 등록 완료</li>
</ol>
</br>

<p>위 아키텍쳐를 통해 사진 기자는 컴퓨터 조작 또는 별도의 서버를 사용하지 않고
다이렉트로 허브에 이미지를 업로드 가능.</p>
</br>

<hr>
<h3 id="시연-연상">시연 연상</h3>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/ff26df8e-0e66-486d-a9fa-870292a86c5a/image.gif" alt=""></p>
</br>

<hr>
<h3 id="error-처리">Error 처리</h3>
<p>사용자가 직접 처리하던 일을 자동화하는 AWS 아키텍쳐 구조상 <span style = "color : red"> <strong>에러를 처리하는 부분</strong> </span>은 굉장히 중요함.</p>
<p>SQS에서 세트로 따라다니는 DLQ를 통해 Lambda의 동작이 실패할 경우 자동으로 ReDrive하는 기능을
추가하긴 했지만 개발자의 입장에서 오류가 왜 발생했는가를 파악하기 위해 SES를 이용.</p>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/b8a2c5d4-f9fd-4ea8-b3d3-8e74afbeeebd/image.png" alt=""></p>
<p>Lambda 함수가 실행되었을 때 오류가 발생한다면 그 오류 사항을 Dynamo DB에 저장,
FTP를 전송한 사용자의 이메일에 실패한 이미지명과 실패한 이유를 전송.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS SNS]]></title>
            <link>https://velog.io/@dev-kms/AWS-SNS</link>
            <guid>https://velog.io/@dev-kms/AWS-SNS</guid>
            <pubDate>Tue, 23 Jan 2024 04:41:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="sns-">SNS ?</h1>
</blockquote>
<p>Simple Notification Service, 
말 그대로 간단하게 <span style = "color : deepskyblue"><strong>AWS 서비스들에게 이벤트를 알려주는 서비스</strong></span>다.</p>
<p>SNS의 주요 특징은 다음과 같다.</p>
<ul>
<li>*<em>퍼블리시 / 서브스크라이브 모델 *</em><ul>
<li>SNS는 pub/sub 메시징 모델을 사용한다.
메시지는 중앙&#39;토픽&#39;으로 발행되며, 
해당 토픽을 구독하는 구독자(서비스)에게 자동으로 전달된다.</li>
</ul>
</li>
<li><strong>다양한 구독자 유형 지원</strong><ul>
<li>SNS는 이메일, SMS, HTTP/HTTPS 엔드포인드, 
AWS Lambda 함수, AWS SQS 대기열 등 다양한 구독 유형을 지원한다.</li>
</ul>
</li>
<li><strong>유연한 메시징 옵션</strong><ul>
<li>SNS는 다양한 메시징 옵션을 제공한다.
예를 들어, 직접 메시지를 발송하거나
AWS Lambda, DynamoDB, CloudWatch와 같은 다른 AWS 서비스와 통합하여
메시지를 자동으로 발송할 수 있다.</li>
</ul>
</li>
<li><strong>확정성 및 내구성</strong><ul>
<li>SNS는 자동 확장 기능을 제공하여 높은 처리량의 메시지를 관리할 수 있으며,
AWS 클라우드 인프라를 통해 높은 내구성과 가용성을 제공한다.</li>
</ul>
</li>
</ul>
<hr>
<p>글로만 봐서는 약간 이해하기 힘들 수 있는데,
AWS 공식 문서에 다음과 같은 그림이 있다.</p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/1dc45b71-4bfb-4b09-988a-702980bb7851/image.png" width = "60%"/>

<p><span style = "color : deepskyblue"><strong>동일한 이벤트</strong></span>에 대하여 3가지의 큐를 실행시켜야 할 때,
S3 업로드 이벤트를 모든 3가지 큐에 전송해야한다.</p>
<p>하지만 <strong>SNS를 사용하게 되면 **S3업로드 이벤트를 SNS로 보내주게 만든 뒤,
SNS에서 어느 SQS로 이벤트를 보내야할지 <span style = "color : red"></strong>지정(구독)**</span>하여 간단하게 이벤트를 보낼 수 있다. 
이는 클라우드 아키텍처의 생산성 및 확장성을 굉장히 간단하게 만들어주는 효과를 볼 수 있었다.</p>
<hr>
<p>+++
처음 SNS를 사용하면서 한가지 착각한 점이 있어서,
나 이외에도 똑같은 실수를 범할 사람들을 위해 한가지 더 적는다.
위에서 언급한 바와 같이, SNS에서 SQS를 구독하는건 <span style = "color : red"><strong>서로 다른 SQS를 구독</strong></span>하는거다.</p>
<p>내가 착각한 점은,
아래 그림처럼 </p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/54ab28f3-df1f-4db0-b896-c8fdcf41ffe5/image.png" width = "60%"/>

<p>SNS에 하나의 SQS를 등록시키면 자동으로 병렬처리를 해주는 줄 알았다는 것.
SNS는 위처럼 동작하는게 아니라 아래처럼</p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/4426fee8-5f1e-4c62-995a-1b4ae1116963/image.png" width ="60%"/>

<p>서로 다른 SQS에 이벤트를 보내주는
<strong>Fan-Out 방식</strong>이라는 것을 알아야한다.</p>
<hr>
<p>지금까지 소개한
Lambda, SQS, DLQ, S3, SNS의 서비스들을 하나의 클라우드 아키텍처로 보자면</p>
<img src = "https://velog.velcdn.com/images/dev-kms/post/f9e86b90-4b9c-4840-952d-37027219aa94/image.png" width = "70%"/>

<p>위 처럼 위 처럼 나타낼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS SQS / DLQ]]></title>
            <link>https://velog.io/@dev-kms/AWS-SQS-DLQ</link>
            <guid>https://velog.io/@dev-kms/AWS-SQS-DLQ</guid>
            <pubDate>Tue, 16 Jan 2024 06:39:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="sqs-">SQS ?</h1>
</blockquote>
<p>Simple Queue Service는 AWS에서 제공하는
<span style = "color : deepskyblue" > ** 완전 관리형 메시지 대기열 서비스 이다.** </span>
SQS는 서버리스 애플리케이션 간에 메시지를 안전하게 전송, 저장 및 수신을 할 수 있게 해준다.</p>
<p>주요 특징은 다음과 같다.</p>
<ul>
<li><strong>분산 컴포넌트</strong>간의 비동기 통신<ul>
<li>SQS를 사용하면, 서로 다른 시스템 및 애플리케이션 간에 메시지를 비동기적으로 교환할 수 있다.
이는 컴포넌트들이 서로 독립적으로 작동하면서도 효율적으로 통신할 수 있게 해준다.</li>
</ul>
</li>
<li><strong>탄력적이고 확장 가능</strong><ul>
<li>SQS는 자동으로 확장되므로 메시지의 양이 많아지더라도 안정적으로 처리할 수 있다.
이는 높은 수준의 트래픽과 대량의 메시지를 처리하는 데 매우 유용하다.</li>
</ul>
</li>
<li>** 두 가지 대기열 유형 **<ul>
<li>표준 대기열 : 최대 처리량에 최적화된  대기열,
메시지가 최소 한 번 전달되며, 때때로 <span style = "color : red"> <strong>메시지의 순서가 바뀔 수 있다</strong> </span></li>
<li>++ 직접 경험해본 바로는, S3의 특정 폴더에 업로드 이벤트로 SQS를 선택할 때 
SQS가 FIFO 형식이라면 선택이 불가능했다.</li>
</ul>
</li>
<li><strong>유연한 메시지 관리</strong><ul>
<li>메시지의 가시성 타임아웃, 지연 시간 및 메시지 생명 주기를 설정할 수 있다.</li>
</ul>
</li>
<li><strong>서버리스 통합</strong><ul>
<li>AWS Lambda와 같은 다른 AWS 서비스와 통합하여 
서버리스 아키텍처에서 사용하는 중요한 역할을 수행한다.
SQS가 Lambda 함수의 트리거로 설정되는 것이 일반적이다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
<h1 id="어떨-때-사용">어떨 때 사용?</h1>
</blockquote>
<h3 id="sqs는-말-그대로-queue-서비스이다">SQS는 말 그대로 Queue 서비스이다.</h3>
<p>람다함수와 연동되게 사용한다 가정하면
SQS(1) &lt;-&gt; Lambda(1)
SQS(2) &lt;-&gt; Lambda(2)
. . .
SQS(N) &lt;-&gt; Lambda(N)
이런식으로 SQS에 이벤트가 쌓일 때 마다 람다가 실행된다.</p>
<p>일련의 시나리오를 써보자.</p>
<ul>
<li><ol>
<li>사용자가 FTP를 이용해서 S3의 특정 버킷에 이미지를 업로드한다.</li>
</ol>
</li>
<li><ol start="2">
<li>해당 폴더에 Created 이벤트가 걸려있었고, 해당 이미지의 정보를 SQS에 전송한다.</li>
</ol>
</li>
<li><ol start="3">
<li>SQS에는 이미지 정보가 들어있는 이벤트들이 전달되고, 
SQS에 정보가 들어오자 이를 Trigger로 가지고 있는 Lambda 함수가 실행된다.</li>
</ol>
</li>
</ul>
<p>즉, 사용자는 그저 폴더에 이미지를 마구잡이로 업로드해도 이는 SQS에 차곡차곡 쌓여
Lambda함수를 실행시키게 된다.</p>
<hr>
<h3 id="이쯤-되면-한가지-드는-의문이-있다">이쯤 되면 한가지 드는 의문이 있다.</h3>
<p>SQS는 대량의 이벤트를 처리하는데 적합하다.
하지만 만약,  <span style = " color : red " > ** 특정한 큐가 실패한다면 ** </span> 어떻게될까?
어떤 이벤트가 실패했는지 일일히 Cloud Watch를 뒤져보는 수밖에 없는,
굉장히 비 개발자적인 방법밖에 없다.</p>
<p>SQS는 이에 대비하여 한가지 대책을 내놓았는데,
그게 바로 <span style = " color : deepskyblue " > <strong>배달 못한 편지 대기열, Dead Letter Queue</strong> </span>다.</p>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/6b79aeed-41d9-4651-b9cd-c3136b2f6338/image.png" alt=""></p>
<p>DLQ라고 불리는 이 큐는 항상 SQS와 붙어다니는 큐로, 
SQS의 정상 로직에서 벗어난 이벤트들이 넘어가는 큐라고 생각하면 된다.</p>
<h3 id="dlq의-span-style--color--deepskyblue역할span은-무엇일까">DLQ의 <span style = "color : deepskyblue"><strong>역할</strong></span>은 무엇일까?</h3>
<p>SQS에서 Lambda를 트리거로 사용할 수 있듯,
<strong>DLQ도 마찬가지로 SQS의 일부분이기 때문에 별도의 Lambda를 트리거</strong>로 가져서
<strong>실패한 이벤트에 대한 처리를 DLQ의 트리거에서 해결</strong>하거나 
또는 DLQ에 쌓인 데이터를 보고 어느 부분에서 오류가 발생했는지 확인한 뒤
Lambda 함수를 수정하고 DLQ를 리드라이브시키면 
실패한 이벤트들이 다시 정상적인 큐로 넘어가면서 수정된 람다를 재실행한다.
여기서 <strong>리드라이브</strong>란, 실패한 대기열인 DLQ에 쌓여있는 이벤트들을
<span style = "color : deepskyblue"><strong>다시 정상적인 SQS로 넘기는 작업</strong></span>을 의미한다.</p>
<p>가령, 중요한 이벤트를 담은 큐가 실패한 경우
해당 이벤트를 다시 실행할 수 없을 때 DLQ로 들어온 이벤트를 리드라이브 시킴으로써
다시 정상적인 SQS로 넘기는 작업을 수행한다.</p>
<p>내가 DLQ를 사용하는 방법은 다음과 같다.</p>
<pre><code>Q. 개발자씨, 제가 올린 이미지 몇개가 서버에서 안보이는데 이유좀 알려주실래요?

해결방안) 해당 람다의 트리거로 걸려있는 SQS와 연관된 DLQ로 들어가서 
폴링된(DLQ에 쌓인 이벤트) 메시지가 있나 확인,
폴링된 메시지가 있다면 해당 DLQ에 들어가서 메시지 수신으로 실패한 이벤트의 시간 및 이름을 확인.
Cloud Watch에서 실패한 이미지의 시간대를 포함해서 [Error] 로 검색.
검색된 Error의 이유를 분석, 보고 진행.

A. ~ 이유로 인해서 버그가 발생했습니다. 버그를 일으키는 로직을 수정하여 배포까지 완료했습니다.

Q. 고마워요.
   올린 이미지가 너무 많아서 실패한 이미지를 따로 찾아서 올리기 힘든데, 큰일이네요.

A. 실패한 이미지들은 따로 저장(DLQ)하고 있습니다. 
   필요하시다면 이미지들을 그대로 다시 업로드 해드릴까요?

Q. 다행이네요. 부탁드릴게요.

해결방안) DLQ에서 리드라이브 진행,
Cloud Watch에 들어가서 DLQ에 있던 이벤트들이 제대로 등록되어 동작하는지 확인
</code></pre><p>이렇듯, SQS는 DLQ와 반드시 짝지어 만들어 주는것이 좋다.</p>
<p>+++</p>
<p>일반 SQS와 DLQ를 연동하는 방법은 간단하다.
SQS의 편집으로 들어가서 
<img src = "https://velog.velcdn.com/images/dev-kms/post/4d1b5b99-111f-4ab2-a4dc-82ed201d19ed/image.png"
     width = "70%">
배달 못한 편지 대기열을 <strong>활성화</strong>하고 대기열에서 생성한 DLQ를 선택하면 된다.
<strong>최대 수신수</strong>는 SQS를 재실행할 횟수, 즉 <strong>람다가 3번 실패하면 DLQ로 넘어가라는 의미</strong>이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Lambda]]></title>
            <link>https://velog.io/@dev-kms/AWS-Lambda</link>
            <guid>https://velog.io/@dev-kms/AWS-Lambda</guid>
            <pubDate>Tue, 16 Jan 2024 02:18:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="lambda-">Lambda ?</h1>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/b96b99e7-2f69-4fba-98e6-f5fc4e635365/image.png" alt=""></p>
<p>자바 개발자라면 Lambda 라는 말이 익숙할 것이다.
서로 전혀 다른 일을 수행하지만, 개념적으로는 비슷한 의미를 가지고 있다.
바로 <span style = " color : deepskyblue"> <strong>&quot;간결함&quot;</strong> </span>과 <span style = " color : deepskyblue"><strong>&quot;특정 작업에 대한 초점&quot;</strong></span> 이다.</p>
<p><span style = " color : yellow"><strong>AWS Lambda</strong></span>는 <span style = " color : deepskyblue"><strong>서버 관리 없이 코드를 실행하기 위해 사용</strong></span>되며, 
코드 실행에 필요한 <strong>모든 인프라 관리를 AWS가 담당</strong>한다.</p>
<p>AWS 람다의 주요 <strong>특징</strong>과 <strong>장점</strong>은 다음과 같다.</p>
<ul>
<li><p><strong>서버리스 아키텍처</strong></p>
<ul>
<li>Lambda는 서버 관리를 필요로 하지 않는다
AWS가 서버 인프라를 관리하며, 사용자는 코드 작성과 실행에 집중 할 수 있다.</li>
</ul>
</li>
<li><p><strong>자동 스케일링</strong></p>
<ul>
<li>Lambda는 자동으로 트래픽에 맞춰 확장 및 축소된다.
이는 사용자가 수동으로 리소스를 관리할 필요가 없다는 장점이다.</li>
</ul>
</li>
<li><p><strong>이벤트 기반</strong></p>
<ul>
<li>Lambda는 다양한 이벤트 소스에 의해 트리거 될 수 있다.
예를 들어,
AWS S3 버킷에 이미지가 업로드 되면 
해당 이미지가 S3에 Create 되었다는 이벤트를 발생시켜 Lambda를 실행시켰다.</li>
</ul>
</li>
<li><p><strong>언어 지원</strong></p>
<ul>
<li>Python, Node.js, Java, C#, Go, Ruby 등 다양한 프로그래밍 언어를 지원한다.
하지만 내 주력 언어인 Java의 경우는 jar 파일을 올려야만 실행되는 반면
Python은 웹 에디터에서 수정 및 실행할 수 있으므로
나는 Lambda를 개발할 필요가 생기면 Python으로 개발을 진행한다.</li>
</ul>
</li>
<li><p><strong>비용 효율성</strong></p>
<ul>
<li>Lambda는 실제로 코드가 실행되는 시간에 대해서만 비용을 지불한다.
실행 시간과 메모리 사용량에 따라 요금이 부과되므로,</li>
<li><em>사용하지 않는 시간에는 비용이 발생하지 않는다*</em>.
이는 일정치 않은 트래픽이나 간헐적으로 실행되는 작업데 매우 효율적인 비용 구조이다.</li>
</ul>
</li>
</ul>
<hr>
<p>사용시 주의사항으로,
<img src="https://velog.velcdn.com/images/dev-kms/post/236c5303-acb6-4630-91b3-dde3e29a8467/image.png" alt="">
람다함수를 생성하고 구성으로 가면 [일반 구성] 이라는 탭이 있을텐데,
여기서 제한시간 및 메모리를 잘 설정해야한다.
아마 기본 제한시간이 3초일텐데, 로직이 복잡하다면 3초를 초과하여 TimeOut Error가 발생한다.
또한, 메모리의 용량이 낮으면 람다함수의 동작 시간이 늘어나는데
메모리를 늘리면 물론 동작은 빨라지겠지만 비용이 늘어난다.
<span style = " color : red"><strong>실제로 여러번의 테스트를 거쳐 어느 정도가 적합한지를 알아내는 과정이 반드시 필요하다</strong> </span> </p>
<p><span style = " color : deepskyblue"> </span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS S3 ( Simple Storage Service )]]></title>
            <link>https://velog.io/@dev-kms/AWS-S3-Simple-Storage-Service</link>
            <guid>https://velog.io/@dev-kms/AWS-S3-Simple-Storage-Service</guid>
            <pubDate>Sun, 14 Jan 2024 07:18:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="s3">S3</h2>
</blockquote>
<p><strong>Simple Storage Service</strong>, 
말 그대로 Cloud 에서 사용하는 가상의 스토리지이다.
AWS의 서비스인 만큼 사용하는 용량에 따라 요금을 지불해야하는데, 
<strong>어느 스토리지 클래스를 사용</strong>하느냐에 따라 지불할 요금이 달라진다.</p>
<p>S3 스토리지는 일반적으로 어떤 종류의 데이터를 관리하는지,
그리고 얼마나 자주 그 데이터에 접근해야 하는지에 따라서 분류된다.</p>
<table>
<thead>
<tr>
<th align="center">Standard</th>
<th align="center">Intelligent Tiering</th>
<th align="center">Standard-IA</th>
<th align="center">One Zone _IA</th>
<th align="center">Glacier</th>
</tr>
</thead>
</table>
<hr>
<blockquote>
<p>S3 - Standard ( S3 Default )</p>
</blockquote>
<p>가장 보편적으로 사용되는 스토리지 타입이며,
가장 일반적인 요금제를 가지는 Standard</p>
<hr>
<blockquote>
<p>S3 - Intelligent Tiering ( 지능형 계층화 )</p>
</blockquote>
<p>머신러닝을 통해서 자동으로 파일의 티어(클래스)를 변경하는 서비스</p>
<p>예를들어, 
최근에 파일을 사람들이 자주 접근하면 스탠다드에 옮기고
접근 빈도가 낮으면 IA로 옮기고
다시 많이 찾게되면 스탠다드에 옮기는 형식</p>
<p>퍼포먼스 손해 오버헤드가 없으며 관리비도 그렇게 많이 들지 않음</p>
<hr>
<blockquote>
<p>S3 - Standard IA ( Infrequent Access / 스탠다드 IA )</p>
</blockquote>
<p>자주 접근되지는 않으나 접근 시 빠른 접근이 요구되는 파일이 많을 시 
저렴한 가격에 IA에 보관하면 유용.
일반 S3에 비해 비용은 저렴하나 데이터를 불러올 때 마다 추가 비용 발생</p>
<p>뉴스 신문 서비스를 예로 들자면,
오늘 기사는 트래픽이 많겠지만 1달 , 2달이 지나면 트래픽이 많이 줄어들 것.
그렇다고 트래픽이 계속 하향추세로 간다고 단정짓기도 애매하니, 
나중에 다시금 기사가 이슈화되어
또다시 많은 트래픽이 몰릴 수 있어 
언제든 많은 트래픽이 올 것을 준비하고 있어야하는 시나리오에서 최적.</p>
<p>즉, 사용도가 다소 낮은 경우의 요금제 </p>
<p>일반적으로 S3에서 30일 이상 액세스가 잘 이루어지지 않았다면 
자동으로 IA요금제로 전환되도록 되어있음</p>
<hr>
<blockquote>
<p>S3 - One Zone IA ( 단일 영역 IA )</p>
</blockquote>
<p>기본적으로 IA와 같지만 하나의 AZ에만 데이터를 저장하는 클래스</p>
<p>덜 중요하고 자주 사용되지 않는 데이터를 저장하는데 적합.
단일영역 IA는 스탠다드 IA와 트래픽 요금은 동일하지만 저장 요금은 20% 더 저렴.</p>
<p>하지만 하나의 AZ에만 저장하니 만일 가용 영역에 문제가 생길 경우 
데이터를 손실할 수 있다는 위험이 존재.</p>
<hr>
<blockquote>
<p>S3 - Glacier</p>
</blockquote>
<p>이름에서 볼 수 있듯, 빙하에 데이터를 꽁꽁 얼려 보관하는 컨셉의 아카이브 서비스.</p>
<p>5년 전, 10년 전 데이터, 아무도 볼 것 같지도 않아 쓸모 없는데 
법적인 이유 등으로 보관해야되는 데이터들을 취급하는데 사용됨.
즉, 거의 접근하지 않을 데이터를 저장할 때 유용하며 매우 저렴한 비용을 자랑</p>
<p>단, 데이터 접근 시 대략 분~시간 소요될 정도로 상당히 오래 걸림</p>
<p>그 이외에도, Glacier는 다른 스토리지 클래스와 다른 부분이 많음.</p>
<p>우선 아카이브의 저장 용량은 40TB로 제한되며 ( S3는 제한 X )
아카이브 생성 시 기본적으로 저장 데이터를 암호화하고 
아카이브 이름은 기계 생성 ID 형식을 지님.</p>
<p>S3 스토리지가 여러 티어 클래스로 나뉘듯,
Glacier 스토리지도 3가지 종류를 가지고 있음.</p>
<ul>
<li><strong>S3 Glacier Instant Retrieval</strong>
밀리초 단위의 검색 시간, 분기당 한 번 엑세스에 적합
자주 액세스하지 않지만 이미지 호스팅, 온라인 파일 공유 애플리케이션, 의료 영상 및 건강 기록,
뉴스 미디어 자산, 위성 및 항공 영상과 같이 성능에 민감한 사용 사례에서
즉시 엑세스 할 수 있어야 하는 데이터용으로 설계</li>
</ul>
<ul>
<li><strong>S3 Glacier Flexible Retrival</strong>
수 분 ~ 몇 시간 ( 최대 12시간 )  의 검색 시간, 연간 1 ~ 2회 액세스에 적합
즉각적인 액세스가 필요하지 않지만 백업 또는 재해 복구 사용 사례와 같이 대규모 데이터 집합을
무료로 검색할 수 있는 유연성이 필요한 아카이브 데이터에 이상적인 스토리지 클래스</li>
</ul>
<ul>
<li><strong>S3 Glacier Deep Archive</strong>
가장 깊은 빙하
12 ~ 45시간의 검색 시간, 연 1회 미만 액세스에 적합. 가장 저렴한 스토리지
데이터 집합을 7 ~ 10년 이상 보관하는 금융 서비스, 의료, 미디어, 엔터테이먼트, 공공 부문의 
고객을 위해 설계되었음.</li>
</ul>
<hr>
<blockquote>
<h2 id="스토리지-클래스-변경">스토리지 클래스 변경</h2>
</blockquote>
<p><img src ="https://velog.velcdn.com/images/dev-kms/post/aef27749-9a5c-42a9-86a5-c8f95c714506/image.png" 
     width = "80%">
객체 선택 → 작업 → 스토리지 클래스 편집</p>
<p><img src = "https://velog.velcdn.com/images/dev-kms/post/0fff9965-b623-42ff-8d90-072c43a066c9/image.png"
     width = "80%">
     원하는 S3 스토리 선택 가능</p>
<pre><code>썸네일 이미지를 저장하는데 필요한 최적의 스토리지 클래스는 ?

S3 - One Zone IA
자주 사용하지 않지만 빠르게 액세스 해야 하는 데이터

Standard 대비 22% 저렴

주로 데이터를 2차적으로 백업하거나 재생성하는 데에 사용함 ( Resized Image )</code></pre><hr>
<blockquote>
<h2 id="s3-객체의-생명-주기">S3 객체의 생명 주기</h2>
</blockquote>
<p><img src ="https://velog.velcdn.com/images/dev-kms/post/b9d0bfd3-ae99-4597-893f-6c4abe66b94d/image.png"
     width = "80%"></p>
<pre><code>새로운 버전 파일은 ~ 하고 예전 버전 파일은 삭제해줘

ex) 
1. 30일이 지난 후 삭제
2. 30일이 지난 후 Glacier로 옮기기</code></pre><p><img src = "https://velog.velcdn.com/images/dev-kms/post/d7030147-9c6a-41e8-88a4-f31bbd75f60c/image.png"
     width = "80%"></p>
<pre><code>S3 수명 주기 작업

# 전환 작업
a. 객체가 다른 스토리지 클래스로 전환할 시기를 정의
b. 예를 들어, 생성 후 30일이 지나면 객체를 S3 Standard-IA 스토리지 클래스로 전환하거나
   생성 후 1년이 지나면 객체를 S3 Glacier 스토리지 클래스에 아카이브 하도록


# 만료 작업
a. 객체가 만료되는 시기를 정의
b. Amazon S3는 만료된 객체를 자동으로 삭제
c. 수명 주기 만료 비용은 선택한 객체 만료 시점에 따라 달라짐</code></pre><ul>
<li>스토리지 클래스 간에 객체의 현재 버전 이동<ul>
<li>원하는 스토리지 클래스 전환을 선택하고 객체 생성 후 경과 기간을 설정 </li>
<li>ex ) Standard Storage에 생성한 객체 30일 뒤 Glacier Storage로 변경</li>
</ul>
</li>
</ul>
<ul>
<li><p>스토리지 클래스 간에 객체의 이전 버전 이동</p>
<ul>
<li><p>객체의 이전 버전들을 다른 스토리지 클래스로 이동시킴
버전 관리가 활성화 된 상태에서, 이 옵션은 오래된 객체 버전을 더 저렴한 스토리지로 옮겨 저장.</p>
</li>
<li><p>이전버전? 
버킷을 생성할 때 버전 관리를 활성화 시키면 사용자가 실수로 데이터를 삭제해도
버킷의 저장 데이터들은 버전별로 저장되기 때문에 복구 가능,
그래서 데이터를 수정하거나 작업을 하면 새로운 버전이 생기고
여러 개의 이전 버전들이 생겨남</p>
</li>
</ul>
</li>
</ul>
<ul>
<li>객체의 현재 버전 만료<ul>
<li>설정된 기간이 지나면 객체를 삭제</li>
</ul>
</li>
</ul>
<ul>
<li>객체의 이전 버전 영구 삭제<ul>
<li>이전 버전의 객체를 설정된 시간 후에 자동으로 삭제</li>
</ul>
</li>
</ul>
<ul>
<li><p>만료된 삭제 마커 또는 완료되지 않은 멀티파트 업로드 삭제</p>
<ul>
<li><p>이 설정은 두 가지 경우에 사용됨</p>
</li>
<li><p>만료된 삭제 마커</p>
<ul>
<li>버전 관리가 활성화 된 상태에서 객체를 삭제하면 ‘삭제 마커’가 생성,
이 설정으로 삭제 마커가 표기된 객체를 자동으로 삭제 가능</li>
</ul>
</li>
<li><p>완료되지 않은 멀티 파트 업로드</p>
<ul>
<li>큰 파일을 여러 부분으로 나누어 업로드하는 과정에서 
완료되지 않은 멀티 파트 업로드가 남았을 수 있음. 이 설정으로 그런 데이터를 자동으로 삭제하는 것이 가능,</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>S3 생명 주기를 사용해야 하는 경우


1. 버킷에 주기적으로 로그를 업로드 하는 경우 
  a. 일정 기간 동안에 필요하긴 하지만 , 그 이후에 삭제해도 상관없을 경우


2. 특정 기간 동안만 자주 액세스하는 문서
  a. 일부 문서는 특정 기간 동안에만 자주 액세스 되고,
     그 이후에 거의 액세스가 필요 없을 경우 


3. 보관 목적의 데이터
  a. 어떤 유형의 데이터는 주로 보관 목적으로 Amazon S3 업로드
  b. 예를 들어, 디지털 미디어, 금융 및 의료 기록 등
     가공되지 않은 데이터, 장기 보존 목적의 DB 백업 파일 등의 데이터</code></pre><hr>
<pre><code>Sequence

이미지를 업로드
1. 30일 경과
2. 이전 버전으로 이미지를 옮기기
3. 이전 버전의 이미지를 하루 뒤 삭제</code></pre><img src = "https://velog.velcdn.com/images/dev-kms/post/bb167efc-5ff2-4893-b2a6-021b848203bd/image.png" width = "80%">
객체의 현재 버전 만료 → 이전 버전으로 이미지를 넘김
객체의 이전 버전 영구 삭제 → 이전 버전으로 넘어간 이미지를 삭제

<img src = "https://velog.velcdn.com/images/dev-kms/post/b3cc05a5-f657-4086-bd60-49636e84f49e/image.png" width = "80%">
업로드 된 이후 30일 뒤 이전 버전으로 이미지 넘김
이전 버전으로 넘어간 이미지는 하루 뒤 삭제

<img src = "https://velog.velcdn.com/images/dev-kms/post/7bc3499d-124b-46c2-a8a4-fc6093a57d59/image.png" width = "80%">
규칙을 생성하기 이전,
객체의 생명 주기 State를 다시 한번 확인]]></description>
        </item>
        <item>
            <title><![CDATA[A W S ( Amazon Web Services )]]></title>
            <link>https://velog.io/@dev-kms/A-W-S-Amazon-Web-Services</link>
            <guid>https://velog.io/@dev-kms/A-W-S-Amazon-Web-Services</guid>
            <pubDate>Sun, 14 Jan 2024 05:37:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev-kms/post/d81370bc-a856-431b-9e8e-911303d020ec/image.png" alt=""></p>
<blockquote>
<h2 id="aws-">AWS ?</h2>
</blockquote>
<p><strong>AWS는 &quot;Amazon Web Services&quot;의 약자로, 
아마존에서 제공하는 <span style = "color: deepskyblue">클라우드 컴퓨팅 플랫폼 및 인프라 서비스</span>이다.</strong></p>
<p>원래 백엔드<em>(JAVA)</em> 만 개발했던 나는, 
회사에 입사한 이후 업무상 클라우드도 겸하게 되어서 
최근 AWS와 관련하여 여러가지 개발을 하고있다.
현재는 AWS를 시작한지 거의 반년이 지났는데, 
처음 배울때는 많이 어려웠지만 
최근 들어 조금씩 익숙해지며 정말 편리한 서비스라는 점을 실감했다.</p>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/ddda5bfa-5635-4430-a664-e5dc929b94ba/image.png" alt=""></p>
<p>무엇보다 <strong>Cloud, Severless</strong>라는 개념을 이론으로만 알고있었으나 
실제로 해보니 지난 SI 기업에서 진행하던 온프레미스 방식과의 차이점이 크게 와닿아 
흥미롭게 개발을 진행했던 것 같다.</p>
<hr>
<p>하지만 한가지 단점도 존재했으니, 
나만 그런 걸 수 있겠지만, 언어의 한계가 있었다.
나는 기본적으로 JAVA를 메인 프로그래밍 언어로 잡고있는데 
AWS, 특히 엄청나게 유용한 <span style = "color : Yellow"> <strong>Lambda</strong> </span>서비스는 <strong>파이썬</strong>이 엄청나게 유리했다.</p>
<p>물론 자바로도 충분히 Lambda를 이용할 수 있었지만, 
자바로 람다를 사용하려면 Jar 파일을 만든 뒤에 
이를 AWS에 업로드 해야만 Input -&gt; Result 값을 얻을 수 있었다.
하지만 <strong>파이썬</strong>은 웹 에디터를 통해서 
AWS Lambda 개발 페이지에서 다이렉트로 개발이 가능했다.
때문에 개발자의 개발 속도만 바라본다면 Java 보다는 파이썬이 훨등히 빠르다 .. 는 생각이다! 
(<del>_ 물론 개인적인 생각! _</del>) </p>
<p>따라서 요즘은 서버는 SpringBoot, 자바로 개발하면서 
동시에 Python으로 Lambda를 개발하고있는데
아직 파이썬에 대해 적응을 덜 했다보니 
상호간에 개발을 할때 약간 혼용되는 실수가 없지않아 있지만
언젠가 파이썬도 익숙해진다면 개발자로서 스킬이 크게 올라갈 것 같아서 
기분좋게 일하고 있다.</p>
<p>만약 나처럼 Java 개발자면서 Cloud를 개발해야하는 상황이 온다면 
파이썬을 공부한다는 마음으로 시작해봤으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Angular -  모듈과 컴포넌트]]></title>
            <link>https://velog.io/@dev-kms/Angular-%EB%AA%A8%EB%93%88%EA%B3%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@dev-kms/Angular-%EB%AA%A8%EB%93%88%EA%B3%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Mon, 25 Sep 2023 00:42:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="mudule--component">Mudule &amp; Component</h1>
</blockquote>
<p>앵귤러는 모듈 - 컴포넌트만 익히면 끝난다는 말이 있을 정도로 중요하다고 한다. 
<del>(구글 &amp; 책 &amp; GPT가 ㅎ)</del></p>
<ul>
<li><strong>모듈</strong>
모듈은 애플리케이션의 부분적인 기능을 정의하고 관련된 
컴포넌트, 서비스, 디렉티드 등을 그룹화한다.
여기에는 <span style = "color : yellow"> <strong>@NgModule</strong> </span>데코레이터를 사용하여
모듈을 정의하는 코드가 포함된다.<pre><code>// app.module.ts
</code></pre></li>
</ul>
<p>import { NgModule } from &#39;@angular/core&#39;;
import { BrowserModule } from &#39;@angular/platform-browser&#39;;
import { AppComponent } from &#39;./app.component&#39;;</p>
<p>@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }</p>
<pre><code>위의 코드에서 AppModule은 애플리케이션의 주요 모듈로 ,
**&quot;declarations&quot;** 배열에는 이 모듈에서 사용되는 컴포넌트(&#39;AppComponent&#39;)를 등록한다.
**&quot;imports&quot;** 배열에는 다른 모듈을 가져오는데 사용된다.
**&quot;bootstrap&quot;** 배열에는 애플리케이션의 시작 컴포넌트를 지정한다.

&lt;/br&gt;

---


* **컴포넌트**
컴포넌트는 사용자 인터페이스를 구성하고 해당 부분의 동작을 제어하는 역할을 수행한다.
각 컴포넌트는 TypeScript 클래스로 정의되며,&lt;span style = &quot;color : yellow&quot;&gt; **@Component** &lt;/span&gt; 데코레이터를 사용하여 
메타데이터를 추가한다.
</code></pre><p>// app.component.ts</p>
<p>import { Component } from &#39;@angular/core&#39;;</p>
<p>@Component({
  selector: &#39;app-root&#39;,
  templateUrl: &#39;./app.component.html&#39;,
  styleUrls: [&#39;./app.component.css&#39;]
})
export class AppComponent {
  title = &#39;My Angular App&#39;;
}</p>
<pre><code>위의 코드에서 AppComponent는 애플리케이션의 주요 컴포넌트로 ,
HTML 템플릿과 컴포넌트 클래스가 정의되어 있다.
이 컴포넌트 클래스는 **&quot;title&quot;** 속성을 가지고 있으며, 이 속성은 템플릿에서 사용된다.

&lt;/br&gt;

---

요약하자면, 
&lt;span style = &quot;color : deepskyblue&quot;&gt; **모듈** &lt;/span&gt;은 애플리케이션의 기능을 그룹화하고 구성
&lt;span style = &quot;color : deepskyblue&quot;&gt; **컴포넌트** &lt;/span&gt;는 사용자 인터페이스를 저의하고 해당 동작을 제어
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Storage 유형]]></title>
            <link>https://velog.io/@dev-kms/Storage-%EC%9C%A0%ED%98%95</link>
            <guid>https://velog.io/@dev-kms/Storage-%EC%9C%A0%ED%98%95</guid>
            <pubDate>Tue, 22 Aug 2023 02:05:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="cloud-storage">Cloud Storage</h1>
</blockquote>
<p><em><strong>클라우드</strong>_에서의 <span style = "color : deepskyblue"><strong>Storage</strong></span>란 <strong>데이터를 저장하고 관리하기 위한 공간</strong>을 말한다.
이는 파일, 문서, 이미지, 비디오, Database 등의 <strong>다양한 유형의 데이터를 저장하는데 사용</strong>된다.
<strong>클라우드 Storage</strong>는 인터넷을 통해 액세스하고 관리할 수 있는 저장소로,
전통적인 <span style = "color : red">**_On-premise Storage Solution</em><strong></span> 과는 **다른 특징</strong>을 가지고 있다.</p>
<hr>
<p>클라우드 Storage의 주요 특징과 이점은 다음과 같다.</p>
<ul>
<li><strong>확장성과 유연성</strong><ul>
<li>클라우드 스토리지는 필요에 따라 즉시 확장하거나 축소할 수 있다.
데이터의 양이 증가하거나 감소할 때도 빠르게 대응할 수 있어 
비용을 효율적으로 관리 가능.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>액세스 가능성</strong><ul>
<li>클라우드 스토리지는 인터넷만 연결되어 있으면 
언제 어디서든 데이터에 접근할 수 있다.
때문에 협업의 효율성을 높이는 효과가 있음.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>데이터 백업 및 복원</strong><ul>
<li>클라우드 스토리지는 데이터를 안전하게 백업하고 필요할 때 복원하는 데 유용하다.
장애나 데이터 손실로부터 복구를 지원하는데 도움이 된다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>데이터 보안</strong><ul>
<li>주요 클라우드 제공업체들은 데이터 보안을 위한 다양한 보호 메커니즘을 제공한다.
데이터 암호화, 접근 제어, 방화벽 등의 기능을 통해 데이터 보호 수준을 높이는 것이 가능.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>비용 절감</strong><ul>
<li>클라우드 스토리지는 초기 투자 없이 사용량에 따라 비용을 지불하므로 
비용을 효율적으로 관리 가능.
불필요한 하드웨어 구매나 유지 보수 비용을 줄일 수 있음.</li>
</ul>
</li>
</ul>
<ul>
<li><span style = " color : deepskyblue">★</span> <strong>다양한 스토리지 유형</strong><ul>
<li>클라우드 제공 업체들은 다양한 스토리지 유형을 제공.</li>
<li><em>Block Storage / File Storage / Object Storage*</em> 등
각기 다른 데이터 유형에 맞는 저장 방식을 선택할 수 있음.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>자동화 및 관리</strong><ul>
<li>클라우드 스토리지는 자동화된 관리 기능을 통해 데이터의 복제, 이동, 스케일링 등
다양한 작업을 간편하게 수행할 수 있음.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="block-storage">Block Storage</h2>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/15496ac0-8253-4be8-a435-cbb4fae0b11f/image.png" alt=""></p>
<p><strong>Block Storage</strong>는 <strong>주차장</strong>에 비유할 수 있다.
주차장의 <strong>한 구획</strong>이 <strong>블록</strong>으로 비유되어, <strong>특정 공간</strong>에 <strong>차를 주차하듯</strong>이 
<strong>정해진 블록에 데이터를 저장</strong>한다.</p>
<p><span style = "color : deepskyblue"><strong>블록 스토리지</strong></span>는 데이터를 <strong>블록 단위로 나누어 저장하고 액세스</strong>하는 스토리지 유형이다.</p>
<p>이런식으로 나누어진 블록들은 각각 <strong>고유한 주소</strong>를 가지고 있는데,
이 주소를 통하여 블록들을 재구성하여 데이터를 불러올 수 있다.</p>
<p><strong>Block Storage</strong>는 보통 <span style = "color : deepskyblue"><strong>SAN(Storage Area Network)</strong></span> 또는 <span style = "color : deepskyblue"><strong>가상 머신의 디스크</strong></span>로 사용되며,
<span style = "color : deepskyblue"><strong>정형화된 데이터를 빠르게 처리하는 용도</strong></span>로 사용된다.</p>
<p><span style = "color : slateblue"><strong>장점</strong></span>으로는
<strong>Block Storage</strong>에는 <span style = "color : deepskyblue"><strong>고유 주소</strong></span>가 있어 파일 스토리지와 달리 <strong>계층 구조</strong>도 필요가 없고,
경로도 하나만 있는것이 아니라 다양하게 가지고 있어 그만큼 <span style = "color : deepskyblue"><strong>데이터를 신속하게 검색</strong></span>할 수 있다.
또한 파티션으로 분할될 수 있어 <span style = "color : deepskyblue"><strong>서로 다른 운영체제에서 액세스</strong></span> 할 수 있다.
이러한 이유로 <strong>자유롭고 효율적이며 안정적</strong>이기 때문에 <span style = "color : deepskyblue"><strong>대규모 DB 운영</span>에 적합</strong>하다.</p>
<p><span style = "color : red"><strong>단점</strong></span>으로는 
비용이 많이 든다는 점이다.
메타 데이터 처리가 제한적이기 때문에 데이터 단위가 아닌 
<strong>애플리케이션</strong> 또는 <strong>데이터베이스</strong> 수준에서 작업을 진행하여 관리자의 부담이 발생할 수 있다.</p>
<hr>
<h2 id="file-storage">File Storage</h2>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/54378de1-fd6d-4166-8c62-504784aeb0ab/image.png" alt=""></p>
<p><strong>File Storage</strong>는 <strong>주차 타워</strong>에 비유할 수 있다.
주차타워에 차가 많아지면 차가 <strong>차곡차곡</strong> 쌓이고, <strong>차를 되찾으려면 시간도 오래 걸리며 힘들다.</strong></p>
<p><span style = "color : deepskyblue"><strong>파일 스토리지</strong></span>는 <strong>데이터를 파일 단위로 저장하고 관리</strong>하는 스토리지 유형이다.</p>
<p>일반적인 사용자들이 가장 <strong>익숙한 사용 방식</strong>인데,
바로 우리가 사용하는 <strong><span style = "color : deepskyblue">윈도우 탐색기</span></strong>와 같이 <strong>계층 구조</strong>를 가지고 있어
<strong>폴더 안에 하위 폴더를 만들고 파일을 저장하는 형식과 비슷</strong>하기 때문이다.</p>
<p>파일을 찾으려면 <strong>경로</strong>를 알아야 하는데,
파일들은 <strong>이름, 위치, 생성일, 수정일, 크기 등의 제한적인 메타데이터</strong>를 가지고 있으며
  <strong>파일이 늘어나면 <span style = "color : red"></strong>데이터도 늘어나고<strong></span> 파일을 찾는것도 그만큼 힘들어진다는 특징</strong>이 있다.</p>
<p><span style = "color : slateblue"><strong>장점</strong></span>으로는 
파일 스토리지는 <span style = "color : deepskyblue"><strong>오래전부터 사용해온 전통적인 데이터 스토리지 시스템</strong></span>이기 때문에
그만큼 사용이 <span style = "color : deepskyblue"><strong>친숙</strong></span>하고 <span style = "color : deepskyblue"><strong>표준화</strong></span>가 잘 되어 있다는 점이다.</p>
<p><span style = "color : red"><strong>단점</strong></span>으로는 
데이터가 많아지면 파일과 폴더를 찾기 위하여 <span style = "color : red"><strong>리소스가 많이</strong></span> 들기 때문에
<span style = "color : red"><strong>성능이 저하</strong></span>된다는 점이다.</p>
<hr>
<h2 id="object-storage">Object Storage</h2>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/f221d953-7eb1-448a-8b09-bd2a8f3b1861/image.png" alt="">
<strong>Object Storage</strong>는 <strong>대리 주차</strong>에 비유할 수 있다.</p>
<p>자동차 키만 건네면 <strong>어디에 주차를 하는지 <span style = "color : deepskyblue">알 필요 없이</strong></span>
<span style = "color : deepskyblue"><strong>알아서 공간을 효율적으로 활용</strong></span>하여 빈틈없이 주차를 해준다.
찾을 때도 <strong>보관증만 건네면 쉽게 가져다 주는 것</strong>과 비슷한 개념을 가진다.</p>
<p><strong><span style = "color : deepskyblue">오브젝트 스토리지</span></strong>는 데이터를 개벽 객체 단위로 저장하고 관리하는 스토리지 유형이다.
오브젝트는 비디오, 오디오 뿐만 아니라 텍스트, 기타 다른 파일 유형 등의 모든  데이터를 포괄하는 유형이다.</p>
<p>파일 스토리지와는 달리 계층구조 없이 <span style = "color : deepskyblue"><strong>평면 구조로 데이터를 저장</strong></span>한다.
그만큼 접근이 쉽고 빠르며 확정성이 높다.</p>
<p>오브젝트에는 <strong>메타데이터가 포함</strong>되며 
<strong>파일 스토리지</strong>의 <span style = "color : red"><strong>제한적인 메타데이터</span>와는 달리</strong> <strong>사용자가 원하는 <span style = "color : deepskyblue">상세한 정보도 
  추가</span>할 수 있다.</strong>
이는 곧 <span style = "color : deepskyblue"><strong>데이터 검색에 굉장히 용이하다는 것</strong></span>을 알 수 있다.</p>
<p>이처럼 오브젝트 스토리지는 <span style = "color : deepskyblue"><strong>대량의 데이터</strong></span>를 관리하기 좋은 <strong>최신 스토리지 방식</strong>이다.</p>
<p><span style = "color : slateblue"><strong>장점</strong></span>으로는 
<strong>데이터의 구조</strong>가 계층이 아닌 <span style = "color : deepskyblue"><strong>평면 구조</strong></span>를 가지고 있어 <strong>데이터 접근이 빠르고 확장성이 좋다.</strong>
또한 <strong>메타데이터</strong>가 <span style = "color : deepskyblue"><strong>오브젝트 자체</span>로 저장</strong>되므로 <strong>접근과 검색이 용이</strong>하다.</p>
<p><span style = "color : red"><strong>단점</strong></span>으로는
<strong><span style = "color : red">오브젝트를 수정할 수 없기</span> 때문에 덮어쓰는 방식</strong>을 사용한다.
때문에 <strong>자주 변경되는 데이터를 저장하기엔 효율적이지 않으며</strong>
<strong>수정이 잘 일어나지 않는</strong> 이미지나 영상 데이터를 저장하는것이 적합하다.</p>
<hr>
<blockquote>
<h1 id="요약">요약</h1>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev-kms/post/0e6aa14c-56e3-4909-acac-86999f9b6d20/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Naver Cloud Platform]]></title>
            <link>https://velog.io/@dev-kms/Naver-Cloud-Platform</link>
            <guid>https://velog.io/@dev-kms/Naver-Cloud-Platform</guid>
            <pubDate>Mon, 21 Aug 2023 06:41:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="ncp">NCP?</h1>
</blockquote>
<p><strong>Naver Cloud Platform</strong> 
<a href="https://www.ncloud.com/product">네이버 클라우드 플랫폼</a>은 네이버에서 제공하는 <strong>클라우드 컴퓨팅 서비스</strong>이다.
클라우드 플랫폼은 기업이나 개인이 인터넷을 통해 <span style = " color : deepskyblue"><strong>컴퓨팅 리소스를 빌려 사용</strong></span>하는 방식으로,
개발자들이 <strong>굉장히 쉽게 개발 플랫폼을 구축할 수 있도록</strong> 도와준다.</p>
<p>NCP는 총 <strong>22개의 서비스</strong>를 지원하는데, 오늘은 총 <strong>4가지 서비스</strong>를 알아볼 예정이다.</p>
<ul>
<li><strong>Coupute</strong></li>
</ul>
<ul>
<li><strong>Networking</strong></li>
</ul>
<ul>
<li><strong>Storage</strong></li>
</ul>
<ul>
<li><strong>Database</strong></li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th>NCP</th>
<th>AWS</th>
</tr>
</thead>
<tbody><tr>
<td>COMPUTE</td>
<td>13</td>
<td>23</td>
</tr>
<tr>
<td>Networking</td>
<td>11</td>
<td>19</td>
</tr>
<tr>
<td>Storage</td>
<td>11</td>
<td>20</td>
</tr>
<tr>
<td>Database</td>
<td>9</td>
<td>24</td>
</tr>
</tbody></table>
<hr>
<h2 id="compute">Compute</h2>
<p><span style = "color : deepskyblue"><strong><em>탄탄한 인프라와 오랜 운영 경험을 기반으로 최상의 IT자원을 제공하는 Service</em></strong></span></p>
<p>Compute는 총 7개의 서비스를 가지고 있다.</p>
<ul>
<li>*<em>Server *</em><ul>
<li>클라우드 상에서 서버를 생성하고 확장할 수 있는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li>*<em>SSD Server *</em><ul>
<li>빠른 Input,Output 처리가 가능한 SSD가 장착된 서버를 제공하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li>*<em>GPU Server *</em><ul>
<li>병렬 연산에 최적화된 GPU 서버의 고성능 컴퓨팅 파워를 제공하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Bare Metal Server</strong><ul>
<li>가상화되지 않은 고성능의 물리 서버를 클라우드에서 사용할 수 있는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li>*<em>Auto Scailng *</em><ul>
<li>사전 설정에 따라 서버 수를 자동으로 조절해주는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Cloud Functions</strong><ul>
<li>서버 관리나 프로비저닝 필요 없이 비즈니스 로직을 실행할 수 있는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li>*<em>Application Server Launcher *</em><ul>
<li>서버 이미지로 간편하게 애플리케이션 서버 설치를 지원하는 서비스</li>
</ul>
</li>
</ul>
<hr>
<h2 id="networking">Networking</h2>
<p><span style = "color : deepskyblue"> <em><strong>시간과 장소에 구애받지 않고 빠르고 안정적인 네트워크 환경을 구축해 막힘없는 서비스 구현 가능</strong></em> </span></p>
<p>Networking은 총 8개의 서비스를 가지고 있다.</p>
<ul>
<li><strong>VPC ( Virtual Private Cloud )</strong><ul>
<li>퍼블릭 클라우드 상에서 제공되는 고객 전용 사설 네트워크 공간</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Load Balancer</strong><ul>
<li>서버 성능과 부하량을 고려하여 네트워크 트래픽을 다수의 서버로 분산해주느 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Cloud Connect</strong><ul>
<li>온 프레미스와 네이버 클라우드 플랫폼을 전용 사설 네트워크로 연결하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Global DNS</strong><ul>
<li>서비스 운영에 필요한 도메인을 간편하게 설정하고 관리할 수 있는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>IPsec VPN</strong><ul>
<li>외부에 있는 고객의 네트워크와의 연결을 암호화하여 보호하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>NAT Gateway</strong><ul>
<li>다수의 IP를 하나의 IP로 변환하는데 필요한 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>DNS ( deprecated )</strong><ul>
<li>서비스 운영에 필요한 도메인을 쉽고 간편하게 설정하고 관리할 수 있는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Global Traffic Manager</strong><ul>
<li>DNS를 기반으로 네트워크 트래픽을 다수의 서버로 분산(Load Balancing)해주는 서비스</li>
</ul>
</li>
</ul>
<hr>
<h2 id="storage">Storage</h2>
<p><span style = "color : deepskyblue"> <em>** 시공간 제약 없이 데이터를 저장하고 효율적으로 활용할 수 있는 저장소를 제공하는 서비스 **</em> </span></p>
<p>Storage는 총 5개의 서비스를 가지고 있다.</p>
<ul>
<li><strong>Object Storage</strong><ul>
<li>모든 종류의 데이터를 인터넷상에 저장하고 검색할 수 있는 객체 스토리지</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Archive Storage</strong><ul>
<li>데이터 아카이빙 및 장기 백업에 최적화된 스토리지</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Block Storage</strong><ul>
<li>빠르게 생성하여 사용하고 반납하는 효율적인 스토리지</li>
</ul>
</li>
</ul>
<ul>
<li><strong>NAS</strong><ul>
<li>다수의 서버를 네트워크에 연결하여 사용할 수 있는 스토리지</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Backup</strong><ul>
<li>정책 기반으로 데이터를 쉽고 안전하게 백업하고 복수할 수 있는 서비스</li>
</ul>
</li>
</ul>
<hr>
<h2 id="database">Database</h2>
<p><span style = "color : deepskyblue"> ** <em>관계형 / 비정형 데이터베이스, 메모리 캐시 등 서비스 목적에 맞는 데이터베이스 플랫폼을 선택해 사용할 수 있음</em> ** </span></p>
<p>Database는 총 13개의 서비스를 가지고 있다.</p>
<ul>
<li><strong>Cloud DB for PostgreSQL</strong><ul>
<li>PostgreSQL을 손쉽게 구축하고 자동으로 관리하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>CLoud DB for MySQL</strong><ul>
<li>MySQL 데이터베이스를 손쉽게 구축하고 자동으로 관리하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>CLoud DB for Redis</strong><ul>
<li>Redis 캐시를 클라우드 상에서 간편하게 구축하고 안정적으로 운영하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Cloud DB for MSSQL</strong><ul>
<li>MSSQL 데이터베이스를 클라우드 상에서 간편하게 구축하고 안정적으로 운영하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Cloud DB for MongoDB</strong><ul>
<li>MongoDB 서비스를 손쉽게 구축하고 자동으로 관리하는 서비스</li>
</ul>
</li>
</ul>
<ul>
<li><strong>MSSQL</strong><ul>
<li>Microsoft가 제공하는 관계형 데이터베이스 관리 시스템(RDBMS)</li>
</ul>
</li>
</ul>
<ul>
<li><strong>MySQL</strong><ul>
<li>가장 많이 사용되는 오픈 소스 기반의 관계형 데이터베이스 관리 시스템(RDBMS)  </li>
</ul>
</li>
</ul>
<ul>
<li><strong>CUBRID</strong><ul>
<li>대용량 분산 처리에 적합한 오픈소스 관계형 데이터베이스 관리 시스템</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Redis</strong><ul>
<li>데이터를 메모리에 저장하는 인 메모리 방식의 비관계형 데이터베이스 관리 시스템(NoSQL)</li>
</ul>
</li>
</ul>
<ul>
<li><strong>PostgreSQL</strong><ul>
<li>위치 분석 및 비즈니스 애플리케이션 개발에 적합한 오픈 소스 기반 객체 관계형 데이터베이스 시스템(ORDBMS)</li>
</ul>
</li>
</ul>
<ul>
<li><strong>MariaDB</strong><ul>
<li>MySQL과 높은 호환성을 유지하는 오픈 소스 기반 무료 데이터베이스 관리 시스템 (RDBMS)</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Tibero</strong><ul>
<li>대규모 운영 환경에 탁월한 아키텍처를 제공하는 국산 데이터베이스 관리 시스템(DBMS)</li>
</ul>
</li>
</ul>
<ul>
<li><strong>Database Migration Service</strong><ul>
<li>데이터베이스를 클라우드 환경으로 손쉽게 마이그레이션 하는 서비스</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Angular - 입력 받기]]></title>
            <link>https://velog.io/@dev-kms/Angular-%EC%9E%85%EB%A0%A5-%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@dev-kms/Angular-%EC%9E%85%EB%A0%A5-%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Fri, 11 Aug 2023 06:57:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="생소한-입력처리">생소한 입력처리</h1>
</blockquote>
<p>방금 막 설치한 참이라 많이 막막하지만,
당장 우리가 봐야할 파일은 다행히도 딱 2개,</p>
<pre><code>app.component.html 
app.component.ts</code></pre><p>위 두가지이다.</p>
<ul>
<li>app.component.html<ul>
<li>이 파일은 애플리케이션의 사용자 인터페이스를 정의하는 HTML .
애플리케이션 화면을 <span style = "color : deepskyblue"><strong>렌더링</strong></span>하는데 사용되며,
사용자에게 보여지는 컴포넌트의 구조와 내용을 결정한다.
HTML 템플릿에서는 <strong>Angular의 템플릿 문법</strong>을 사용하여
<span style = "color : deepskyblue"><strong>데이터 바인딩, 디렉팁, 이벤트 처리</strong></span> 등을 정의할 수 있다.
예를 들어 <code>{{ }}</code> 를 사용하여 데이터 바인딩을 하거나
앵귤러 디렉티브를 사용하여 조건부 렌더링과 반복작업을 수행.</li>
</ul>
</li>
<li>app.component.ts<ul>
<li>이 파일은 AppComponent 컴포넌트의 TypeScript 코드를 포함.
TypeScript는 JavaScript의 상위 집합 언어로,</li>
<li><em>정적 타입 검사*</em>와 <strong>객체 지향 프로그래밍 기능</strong>을 지원한다.
AppComponent클래스는 애플리케이션의 주요 로직을 정의하고
컴포넌트의 상태를 관리한다.
이 클래스 내부에는 컴포넌트의 속성, 메서드, 이벤트 핸들러 등이 정의된다. 컴포넌트의 생명 주기 훅도 이 파일에서 구현한다.</li>
</ul>
</li>
</ul>
<p>각 클래스에 대한 설명이지만, 당장은 와닿지 않는다.
역시 개발자는 <span style = "color : red"><strong>백문이불여일타</strong></span>라고, 
직접 코드를 때려봐야 <code>아 이게 이거구나!</code> 싶을거다.</p>
<hr>
<p>먼저 app.component.ts 타입스크립트의
export class AppComponent { <code>...</code> } 안에
<strong>생성자</strong>를 만들어보자</p>
<pre><code>export class AppComponent {
    constructor() {
        console.log(&quot;로그!&quot;)
    }
}</code></pre><p>위 코드는 <strong>생성자</strong>를 이용하기 때문에, 
코드를 저장한 후 4200 포트에 들어가보면 
정상적으로 로그! 가 찍히는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/dev-kms/post/8242963b-91e3-4745-8a14-01b1d752100f/image.png" alt=""></p>
<hr>
<p>이번엔 html을 작성해보자.
먼저, 위와 마찬가지로 app.component.ts 안에 
아래와 같이 변수와 함수를 설정한다.</p>
<pre><code>export class AppComponent {
    constructor() {
        console.log(&quot;로그!&quot;)
    }

    // 추가!
    userName : string = &quot;&quot;;
    setName(name : string) : void {
        this.userName = name;
    }
}</code></pre><p><del>어우 타입스크립트 극혐</del></p>
<p>그리고 이번엔 app.component.html 에 가보자.
모든 코드를 다 <span style = "color : red"><strong>삭제</strong></span>하고, 아래의 짧은 코드만 작성.</p>
<pre><code>&lt;h4&gt;
  &lt;span&gt; {{userName}} &lt;/span&gt;님
&lt;/h4&gt;
&lt;div class=&quot;contents&quot;&gt;

  &lt;label for=&quot;user-name&quot;&gt;사용자 이름 : &lt;/label&gt;

  &lt;input type=&quot;text&quot; name = &quot;user-name&quot; id=&quot;user-name&quot; #nameInput&gt;

  &lt;button type=&quot;button&quot; (click)=&quot;setName(nameInput.value)&quot;&gt;버튼&lt;/button&gt;

&lt;/div&gt;
</code></pre><p>위 코드를 입력하면 
<img src="https://velog.velcdn.com/images/dev-kms/post/e0fcd994-2c7e-4bc8-8752-b78b281983fa/image.png" alt="">
이와 같은 화면이 나왔다가,
이름을 입력한 뒤 버튼을 누르면
<img src="https://velog.velcdn.com/images/dev-kms/post/887694c7-84e7-4bb0-9d53-f3d40d844265/image.png" alt="">
이런식으로 입력한 값이 렌더링 되는것을 확인할 수 있다.</p>
<p>나름 신기한 방식이다.
<span style="color : deepskyblue"><strong>리액트</strong></span>였다면 const [name, setName] = useState(&quot;&quot;);
식으로 선언해서 input value = name으로 잡고
button onSubmit 이벤트를 걸어 setName을 했을텐데,</p>
<p><span style="color : deepskyblue"><strong>앵귤러</strong></span>는 <strong>input에 #nameInput 처럼 <span style="color : deepskyblue">#</span>을 이용하여 입력값</strong>을 받고
<strong>Button에서 <span style="color : deepskyblue">(click)=&quot;&quot;</strong></span> 같은 처음보는 이벤트로
app.component.ts 에서 선언한 메서드를 통해 userName에 접근하여
입력받은 nameInput을 <span style="color : deepskyblue"><strong>nameInput.value</strong></span>를 통해 값을 넣어준다.</p>
<p>어찌보면 더 쉬워보이기도 하는데,
아직은 초짜이기에 뭐가 더 좋고 나쁜가는 잘 모르겠다.</p>
<p>리액트를 작성할 때는
import React ~ 부터 시작해서
export default function ( return (&lt;div&gt; &lt;/div&gt;)) 
이런걸 써야하는데 당장은 HTML 코드만으로 동작하는것도 신기하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Angular - 설치]]></title>
            <link>https://velog.io/@dev-kms/Angular-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@dev-kms/Angular-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Fri, 11 Aug 2023 05:25:34 GMT</pubDate>
            <description><![CDATA[<p>회사에서 진행하는 프로젝트가 Angular를 사용한다고 한다.
난 아직 React밖에 모르는 상황이기 때문에 Angular의 공부가 필요하다.</p>
<blockquote>
<h1 id="angular">Angular</h1>
</blockquote>
<p>앵귤러는 <strong>구글</strong>에서 개발한 <strong>웹 프론트 개발 <span style = "color : deepskyblue">프레임워크</strong></span>다.</p>
<p>리액트가 라이브러리인 반면 앵귤러는 <strong>프레임워크</strong>이기 때문에
속도가 리액트에 비해 느리다는 단점이 있다고 한다.</p>
<p>또 여러가지 이유 때문에 국내에서는 <span style = "color : red"><strong>거의 사용되지 않는</strong></span>
프레임워크라고 하는데, 뭐든지 배워놓으면 나쁠일이야 없다.</p>
<hr>
<p>앵귤러를 사용하기 위해서는 먼저, Node.js를 설치해야한다.
<a href="https://nodejs.org/ko/download">Node.js 다운로드 공식문서</a></p>
<p>Node를 설치했으면, <strong>앵귤러를 설치</strong>하자.
다음 명령어를 CMD 에 입력하면 된다</p>
<pre><code>npm install -g @angular@cli </code></pre><p>앵귤러를 설치했다면, <strong>제대로 설치되었는지 다시 한번 확인</strong>해보자.
<strong>ng</strong>는 앵귤러 관련된 명령어고, 
<strong>version</strong>은 현재 설치된 앵귤러의 버전을 확인하는 용도. 
버전이 안뜨면 제대로 설치되지 않은거다.</p>
<pre><code>ng version</code></pre><hr>
<p>버전이 나오는 것을 확인했다면 간단히 프로젝트를 만들고 실행해보자.</p>
<pre><code>ng new [ Project Name ] //  ex) ng new myAngular</code></pre><p>어느정도의 시간이 지난뒤 프로젝트가 만들어졌다면,</p>
<p>Angular Devkit을 설치</p>
<pre><code>npm i @angular-devkit/build-angular</code></pre><p>그리고 해당 프로젝트가 설치된 폴더로 이동한 뒤 프로젝트를 실행.</p>
<pre><code>cd [ Project name ] // ex) cd myAngular
ng serve // 실행 시키는 명령어. 앞으로 굉장히 자주쓰게 될듯</code></pre><p>실행이 되었다면 <strong>localhost:4200</strong> 으로 접속해보자.
앵귤러는 기본 포트로 <span style = "color : deepskyblue"><strong>4200</strong></span>을 쓰고있다.
접속했을 때
<img src="https://velog.velcdn.com/images/dev-kms/post/0f5207ce-36c9-43e5-a766-0bf84efc7337/image.png" alt="">
위 화면이 보인다면 제대로 설치하고, 실행한 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 함수]]></title>
            <link>https://velog.io/@dev-kms/SQL-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@dev-kms/SQL-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 08 Jun 2023 03:09:08 GMT</pubDate>
            <description><![CDATA[<p>나는 <strong>PostgreSQL, MySQL</strong>을 주로 사용한다.
실무에서는 아무래도 <span style = "color : deepskyblue"><strong>확장성이 좋은 PostgreSQL</strong></span>을 사용하지만,
<strong>간단한 SQL 문제</strong>를 풀기 위해서는 <strong><span style = "color : deepskyblue">단일DB를 사용하는 MySQL</strong></span>을 사용하는것이 좋다.</p>
<p>그리 큰 차이는 없겠지만 SQL 문법은 MySQL에 맞추어 기술하겠다.</p>
<hr>
<h3 id="ifnull">IFNULL</h3>
<ul>
<li>null값을 다른 값으로 대체하는데 사용되는 함수이다.<pre><code>SELECT IFNULL(name, &#39;Unknown&#39;) FROM employees;</code></pre>위 쿼리는 name열의 값이 null이면 Unknown을 출력한다.</li>
</ul>
<hr>
<h3 id="date_format">DATE_FORMAT</h3>
<ul>
<li>날짜를 지정한 포맷으로 변경하는 함수이다.<pre><code>SELECT DATE_FORMAT(NOW(), &#39;%Y-%m-%d&#39;);</code></pre>위 쿼리는 현재 날짜를 YYYY-mm-dd 형식으로 출력된다.
즉, 2023-06-08 처럼 출력된다.</li>
</ul>
<hr>
<h3 id="datediff">DATEDIFF</h3>
<ul>
<li>두 날짜 사이의 차이를 계산하는 함수이다.<pre><code>SELECT DATEDIFF(end_date, start_date) AS days_between
FROM table_name;</code></pre>위 쿼리는 table_name 테이블에서 end_date 열과 start_date 열의 차이를 계산한다.
예를 들어, 
state_date열이 2023-06-01 , end_date열이 2023-06-07 이라면 6을 반환한다.</li>
</ul>
<hr>
<h3 id="case-when">CASE WHEN</h3>
<ul>
<li>조건에 따라 다른 값을 반환하는데 사용되는 문법이다.<pre><code>SELECT
  student_id,
  student_name,
  CASE
      WHEN gender = &#39;male&#39; THEN &#39;남성&#39;
      WHEN gender = &#39;female&#39; THEN &#39;여성&#39;
      ELSE &#39;기타&#39;
  END AS gender
FROM
  students;</code></pre></li>
</ul>
<p>이 문법은 다음과 같은 결과를 반환한다</p>
<table>
<thead>
<tr>
<th align="center">student_id</th>
<th align="center">student_name</th>
<th align="center">gender</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">Alice</td>
<td align="center">여성</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">Bob</td>
<td align="center">남성</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">Carol</td>
<td align="center">기타</td>
</tr>
</tbody></table>
<hr>
<h3 id="if">IF</h3>
<ul>
<li>조건에 따라 다른 값을 반환하는데 사용된다.
CASE WHEN의 경우 다양한 조건을 걸 수 있지만 IF는 TRUE or FALSE 만 구분한다.<pre><code>SELECT IF(age &gt;= 18, &quot;성인&quot;, &quot;미성년자&quot;);</code></pre></li>
</ul>
<hr>
<h3 id="concat">CONCAT</h3>
<ul>
<li>두 개 이상의 문자열을 결합하는데 사용한다.<pre><code>SELECT CONCAT(&quot;Hello&quot;, &quot;World&quot;);</code></pre>위 코드는 두 문자열을 결합하여 &quot;Hello World&quot; 를 반환한다.</li>
</ul>
<p>이를 응용해서, 
LEFT MID RIGHT를 사용해 전화번호 사이사이에 하이픈(-)을 넣을 수도 있다.
만약 번호가 01012346789 형식이라고 한다면</p>
<pre><code>CONCAT( LEFT(USER.TLNO, 3) , &#39;-&#39;, 
    MID(USER.TLNO, 4, 4), &#39;-&#39; , 
    RIGHT(USER.TLNO, 4) )
    AS &#39;전화번호&#39;</code></pre><p>위 쿼리를 실행한 뒤 010-1234-6789 로 출력할 수 있다.
LEFT(USER.TLNO, 3) : USER.TLNO의 LEFT(처음)부터 3번째 까지 출력, 
MID(USER.TLNO, 4, 4) : USER.TLNO의 MID(중간), 4번째부터 그 뒤의 4번째 까지 출력,
RIGHT(USER.TLNO, 4) : USER.TLNO의 RIGHT(끝)부터 4번째 앞부터 끝까지 출력</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot - JWT (구현)]]></title>
            <link>https://velog.io/@dev-kms/Spring-Boot-JWT-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@dev-kms/Spring-Boot-JWT-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 07 Jun 2023 05:52:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="json-web-token-jwt">Json Web Token (JWT)</h1>
</blockquote>
<p>앞선 포스팅에서 JWT를 이용해 인증을 구현한다고 했다.
그렇다면 이 <strong>&quot;인증&quot;</strong>은 <strong>몇번</strong>을 받아야 하는가?</p>
<p>가장 간단히 생각하면 <strong>모든 API 요청에 토큰을 함께 보내어 인증을 구현하는 것</strong>이다.
그러면 각 API는 맨 처음 토큰을 확인함으로써 
<strong>접근을 허용, 또는 거부하는 코드를 실행</strong>하겠지만 이는 <span style = "color : red"><strong>좋은 방법이 아니다.</strong> </span></p>
<h3 id="왜-모든-api-요청에-토큰을-구현하는게-안좋은가">왜 모든 API 요청에 토큰을 구현하는게 안좋은가?</h3>
<p>인증을 필요로하는 API가 <strong>여러개</strong>일 경우, 
<strong>동일한 인증 코드</strong>를 그 <span style = "color : red"><strong>요청 개수만큼 반복해서 실행</span>하기 때문이다.</strong></p>
<p>그렇다면 가장 좋은 방법은 무엇인가?
바로 <strong><span style = "color : deepskyblue">Spring Security</span>를 사용하는 것</strong>이다.
<strong>Spring Security를 사용하는 경우, **
인증 코드를 **한 번만</strong> 짜고 <span style = "color : deepskyblue"><strong>이 코드가 모든 API를 수행하기 바로 전에 실행되도록 구현</strong></span>하면 된다.</p>
<hr>
<h2 id="jwt-토큰-생성-및-반환-구현">JWT 토큰 생성 및 반환 구현</h2>
<p>JWT를 구현하기 위해서 <strong>JWT, Spring Security</strong>를 <strong>Dependency에 추가</strong>해야한다.</p>
<pre><code>dependencies {

    ...

    implementation &quot;io.jsonwebtoken:jjwt:0.9.1&quot;
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;

}</code></pre><p>라이브러리를 추가했다면
모든 인증과 인가와 관련된 클래스를 쉽게 구분하기 위해 security 패키지를 만든다.
그 후, <span style = "color : deepskyblue"><strong>토큰을 발급</strong></span>하는  <strong>TokenProvider.class</strong>를 만든다.</p>
<pre><code>package com.example.demo.security;

/*
TokenProvider 을 작성했다면
이제 로그인 부분에서 TokenProvider 를 이용해 토큰을 생성 후 UserDTO 에 이를 반환해야한다.
*/

@Slf4j
@Service
public class TokenProvider {

    // 시크릿 키
    private static final String SECRET_KEY = &quot;NMA8JPctFuna59f5&quot;;

    // JWT 라이브러리를 이용해 JWT 토큰을 생성
    public String create(UserEntity userEntity) {

        // 토큰이 만료되는 기한을 지금으로부터 1일로 설정
        Date expiryDate = Date.from(
                Instant.now().plus(1, ChronoUnit.DAYS)
        );

        // JWT Token 생성
        return Jwts.builder()

                // header 에 들어갈 내용 및 서명을 하기 위한 SECRET_KEY
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)

                // payload 에 들어갈 내용
                .setSubject(userEntity.getId()) // sub
                .setIssuer(&quot;demo app&quot;) // iss
                .setIssuedAt(new Date()) // iat
                .setExpiration(expiryDate) // exp
                .compact();

    }

    // 토큰을 디코딩, 파싱 및 위조여부 확인
    public String validateAndGetUserId(String token) {

         /*
         parseClaimsJws 메서드가 Base 64로 디코딩 및 파싱
         즉, 헤더와 페이로드를 setSigningKey 로 넘어온 시크릿을 이용해 서명 후, token 의 서명과 비교
         위조되지 않았다면 페이로드(Claims) 리턴, 위조라면 예외를 날림
         그중 우리는 UserId가 필요하므로 getBody 를 부른다.
         */

        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token) // 이거 !
                .getBody();

        // userID 반환
        return claims.getSubject();

    }

}
</code></pre><p>위 코드의 실행 프로세스는 간단하다.</p>
<p><strong>create 메서드</strong>에서 <strong>JWT 라이브러리를 이용해 JWT 토큰을 생성</strong>한다.
이 때 <strong>임의로 지정한 SECRET_KEY</strong>를 <span style = "color : deepskyblue"><strong>개인키</strong></span>로 사용한다.</p>
<p><strong>validateAndGetUserId 메서드</strong>는 <span style = "color : deepskyblue"><strong>토큰을 디코딩, 파싱 및 위조 여부를 확인</span>한다.</strong>
이후 사용하려는 <strong>subject 값, 즉 유저의 아이디를 리턴</strong>한다.
<span style = "color : deepskyblue"><strong>JWT라이브러리</strong></span>를 <strong>사용</strong>하기 때문에 
<strong>JSON을 생성, 서명, 인코딩, 디코딩, 파싱하는 작업을 <span style = "color : red">따로 하지 않아도 된다.</strong></span></p>
<hr>
<h2 id="user-레이어-구현">User 레이어 구현</h2>
<p>로그인에 필요한 User 관련 레이어를 구현한다.
사용자를 관리하기 위해서는 ** Model, Service, Repository, Controller**가 필요하다.</p>
<pre><code>// UserEntity.class

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = &quot;username&quot;)})
@ToString
public class UserEntity {

    @Id
    @GeneratedValue(generator = &quot;system-uuid&quot;)
    @GenericGenerator(name = &quot;system-uuid&quot;, strategy = &quot;uuid&quot;)
    private String id;

    @Column(nullable = false)
    private String username; // 아이디로 사용할 유저 네임

    private String password; // 패스워드

    private String role; // 사용자의 역할 (어드민, 일반사용자)

}</code></pre></br>

<p><strong>UserEntity를 사용</strong>하기 위해 <strong>Repository를 작성</strong>한다.</p>
<pre><code>// UserRepository.interface

@Repository
public interface UserRepository extends JpaRepository&lt;UserEntity, String&gt; {

    UserEntity findByUsername(String username); // 이름 조회
    Boolean existsByUsername(String username); // 이미 이름이 존재하는 경우 

}
</code></pre></br>

<p><strong>UserService</strong>는 <strong>총 2가지의 메서드를 사용</strong>한다.
create() : UserRepository를 이용해 <span style = "color : deepskyblue"><strong>사용자를 생성</strong></span>
getByCredentials() : <span style = "color : deepskyblue"><strong>로그인을 인증</strong></span>할 때 사용</p>
<p>보통 <strong>암호화된 패스워드를 비교</strong>하는 경우, 
<strong>사용자에게 받은 <span style = "color : deepskyblue">패스워드를 같은 방법으로 암호화</span>한 후 
<span style = "color : deepskyblue">그 결과를 DB값과 비교</span>하는것이 자연스러운 흐름이지만</strong></p>
<p><strong>본문에서는 그렇게 하지 않고</strong> <span style = "color : yellow"><strong>matches()</span> 메서드</strong>를 사용한다.
<strong>BCryptPasswordEncoder</strong>는 <span style = "color : red"><strong>같은 값을 인코딩하더라도 할 때마다 값이 다른데</strong>,</span>
패스워드에 <span style = "color : red"><strong>랜덤하게 의미 없는 값을 붙여 결과를 생성하기 때문</span>이다.</strong></p>
<p>이런 <strong>의미 없는 값</strong>을 <strong>보안 용어로 <span style = "color : red">Salt</strong>라 하고, <span style = "color : red"><strong>Salt</strong></span>를 붙여 인코딩하는 것을<span style = "color : red"> <strong>Salting</strong></span>이라고 한다.
따라서 <strong>사용자에게 받은 패스워드를 인코딩하더라도 
DB에 저장된 패스워드와는 다를 확률이 높다.</strong></p>
<p>  대신 <strong>BCryptPasswordEncoder</strong>는 어떤** 두 값이 일치여부를 알려주는 <span style = "color : yellow">matches()</span> 메서드<strong>를 제공한다.
  **이 메서드는 <span style = "color : yellow">Salt를 고려해서 두 값을 비교해준다.</strong></span></p>
<pre><code>// UserService.class
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 사용자 생성
    public UserEntity create(final UserEntity userEntity) {
        if (userEntity == null || userEntity.getUsername() == null) {
            throw new RuntimeException(&quot;Invalid arguments&quot;);
        }
        final String username = userEntity.getUsername();
        if (userRepository.existsByUsername(username)) {
            log.warn(&quot;username already exists {}&quot;, username);
            throw new RuntimeException(&quot;Username already exists&quot;);
        }

        return userRepository.save(userEntity);
    }

    // 로그인 인증, SpringSecurity를 이용하여 패스워드 암호화하여 matches로 비교
    public UserEntity getByCredentials(final String username, final String password, final PasswordEncoder encoder) {
        final UserEntity originalUser = userRepository.findByUsername(username);

        // matches 메서드를 이용해 패스워드가 같은 지 확인
        if (originalUser != null &amp;&amp; encoder.matches(password, originalUser.getPassword())) {
            return originalUser;
        }

        return null;
    }

}
</code></pre></br> 

<p>UserController에서 사용할 UserDTO를 생성한다.</p>
<pre><code>// UserDTO.class

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {

    private String token;
    private String username;
    private String password;
    private String id;

}
</code></pre></br>

<p>UserDTO를 생성했다면 UserController를 구현하자.
UserController는 두 가지 기능을 제공한다.</p>
<ul>
<li>/signup : 회원가입</li>
<li>/signin : 로그인</li>
</ul>
<p><strong>JWT를 생성하는 TokenProvider</strong>를 작성했기 때문에,
로그인 부분에서 <strong>TokenProvider를 이용해<span style = "color : deepskyblue"> 토큰을 생성 후 UserDTO에 이를 반환</strong></span>한다.</p>
<pre><code>@Slf4j
@RestController
@RequestMapping(&quot;/auth&quot;)
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private TokenProvider tokenProvider;

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    // 회원가입 API
    @PostMapping(&quot;/signup&quot;)
    public ResponseEntity&lt;?&gt; registerUser(@RequestBody UserDTO userDTO) {
        try {
            if (userDTO == null || userDTO.getPassword() == null) {
                throw new RuntimeException(&quot;Invalid Password value&quot;);
            }

            // 요청을 이용해 저장할 유저 만들기
            UserEntity user = UserEntity.builder()
                    .username(userDTO.getUsername())
                    .password(passwordEncoder.encode(userDTO.getPassword())) // 비밀번호 암호화
                    .build();

            // 서비스를 이용해 Repository 에 유저 저장
            UserEntity registeredUser = userService.create(user);
            UserDTO responseUserDTO = UserDTO.builder()
                    .id(registeredUser.getId())
                    .username(registeredUser.getUsername())
                    .build();

            return ResponseEntity.ok().body(responseUserDTO);
        } catch (Exception e) {
            // 유저 정보는 항상 하나이므로 리스트로 만들어야 하는 ResponseDTO를 사용하지 않고 그냥 UserDTO 리턴
            ResponseDTO responseDTO = ResponseDTO.builder()
                    .error(e.getMessage())
                    .build();

            return ResponseEntity.badRequest().body(responseDTO);
        }
    }

    // 로그인 API
    @PostMapping(&quot;/signin&quot;)
    public ResponseEntity&lt;?&gt; authenticate(@RequestBody UserDTO userDTO) {

        System.out.println(&quot;DTO : &quot; + userDTO);

        UserEntity user = userService.getByCredentials(
                userDTO.getUsername(),
                userDTO.getPassword(),
                passwordEncoder
        );

        if (user != null) {

            // 토큰 생성
            final String token = tokenProvider.create(user);
            final UserDTO responseUserDTO = UserDTO.builder()
                    .username(user.getUsername())
                    .id(user.getId())
                    .token(token)
                    .build();

            return ResponseEntity.ok().body(responseUserDTO);

        } else {
            ResponseDTO responseDTO = ResponseDTO.builder()
                    .error(&quot;Login failed&quot;)
                    .build();
            return ResponseEntity
                    .badRequest()
                    .body(responseDTO);
        }

    }

}
</code></pre><p>위와 같이 설정한 뒤 ,
Postman을 이용하여 회원가입 후 로그인 API를 불러오면 Token을 return한다
<img src="https://velog.velcdn.com/images/dev-kms/post/a9054e4b-7983-4d6c-b1ad-6e6645e0dfa2/image.png" alt="">
리턴받은 토큰을 Base64로 디코딩하면 다음과 같다</p>
<pre><code>{
    &quot;alg&quot;:&quot;HS512&quot;
}
{
    &quot;sub&quot;:&quot;4028819a876a924901876a966c520000&quot;,
    &quot;iss&quot;:&quot;demo app&quot;,
    &quot;iat&quot;:1684996014,
    &quot;exp&quot;:1685082414
}
|bIcv73S!P2[B܅޷4Lxa~X&#39;O ڿ&#39;O</code></pre><p>header와 payload가 잘 출력되고 
유효성을 검사하는 {서명} 부분도 알수없는 값으로 채워진것을 확인할 수 있다.</p>
<hr>
<p>난 Front 단에서 토큰을 <strong>Session으로 관리</strong>하게 만든 후 이를 Back으로 보내는 방식을 선택했다.
<img src="https://velog.velcdn.com/images/dev-kms/post/9060db22-89ab-44cf-a2c5-8430144a3e94/image.png" alt=""></p>
<p>위 리액트 구문에서 <span style = " color : deepskyblue"><strong>localStorage</strong></span>를 사용하는 경우 쿠키 방식,
즉 웹 브라우저 == 클라이언트 == 사용자 컴퓨터 에 로그인 정보를 저장하기 때문에
<span style = " color : deepskyblue"><strong>웹 브라우저를 닫아도 로그인이 유지</strong></span>되어있다.</p>
<p>localStorage 대신 <span style = " color : red"><strong>SessionStorage</strong></span>를 사용한다면
웹 서버에 로그인 정보를 저장하기 때문에 웹 브라우저를 닫는 순간
<span style = " color : red"><strong>웹 서버가 닫히면서 로그아웃</strong></span> 된다.</p>
<hr>
<p>로그인을 했으니, 이 이후에 요청되는 <strong>모든 api는 <span style = " color : deepskyblue">로그인 세션 정보</span>를 가지고 있어야한다.</strong>
먼저 JWT인증을 관리하는 클래스를 설정한뒤,
SpringSecurity에게 해당 클래스를 사용하도록 설정한다.</p>
<pre><code>/*
스프링 시큐리티에게 JwtAuthenticationFilter 를 사용하라고 알려주는 것
*/

@EnableWebSecurity // Spring Security에서 사용
@Slf4j
public class WebSecurityConfig  {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // http 시큐리티 빌더
        http.cors()
                .and()
                .csrf() // 현재는 사용X, Disable
                    .disable()
                .httpBasic() // token 을 사용하므로 basic 인증 Disable
                    .disable()
                .sessionManagement() // Session 기반이 아님을 선언
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests() // &#39;/&#39; 와 &#39;/auth/**&#39; 경로는 인증 안해도 됨
                    .antMatchers(&quot;/&quot;, &quot;/auth/**&quot;).permitAll()
                .anyRequest() // &#39;/&#39; 와 &#39;/auth/**&#39; 이외의 모든 경로는 인증해야됨
                    .authenticated();

        /*
        filter 등록,
        매 요청마다 CorsFilter 실행한 후에 jwtAuthenticationFilter 실행
        */
        http.addFilterAfter(
                jwtAuthenticationFilter,
                CorsFilter.class
        );

        return http.build();
    }

}
</code></pre><p>그리고 <strong>로그인 세션 정보를 가지고 있어야하는 api 요청 Controller</strong>에 
<strong><span style = "color : yellow">@AuthenticationPrincipal</span> 어노테이션</strong>을 붙여서 TokenProvider에서 설정한 
<strong>JWT Subject 내용을 매개변수</strong>로 넣어준다.</p>
<pre><code>@RestController
@RequestMapping(&quot;/todo&quot;)
@RequiredArgsConstructor
@Slf4j
public class TodoController {

    private final TodoService service;

    @GetMapping(&quot;/test&quot;)
    public ResponseEntity&lt;?&gt; testTodo() {
        String str = service.testService();
        List&lt;String&gt; list = new ArrayList&lt;&gt;();
        list.add(str);
        ResponseDTO&lt;String&gt; response = ResponseDTO.&lt;String&gt;builder().data(list).build();
        return ResponseEntity.ok().body(response);
    }

    @GetMapping
    public ResponseEntity&lt;?&gt; retrieveTodoList(@AuthenticationPrincipal String userId) {

        // (1) 서비스 메서드의 retrieve 메서드를 사용해 Todo 리스트를 가져온다
        List&lt;TodoEntity&gt; entities = service.retrieve(userId);

        // (2) 자바 스트림을 이용해 리턴된 엔티티 리스트를 TodoDTO 리스트로 변환
        List&lt;TodoDTO&gt; dtos = entities.stream().map(TodoDTO::new).collect(Collectors.toList());

        // (3) 변환된 TodoDTO 리스트를 이용해 ResponseDTO를 초기화
        ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().data(dtos).build();

        // (4) ResponseDTO 리턴
        return ResponseEntity.ok().body(response);

    }

    @PostMapping
    public ResponseEntity&lt;?&gt; createTodo(@RequestBody TodoDTO dto, @AuthenticationPrincipal String userId) {

        try {

            // (1) DTO to Entity
            TodoEntity entity = TodoDTO.toEntity(dto);

            // (2) 생성 당시에는 id가 없어야하기 때문에 null 초기화
            entity.setId(null);

            // (3) 임시 유저 아이디를 설정
            entity.setUserId(userId);

            // (4) 서비스를 이용해 Todo 엔티티 생성
            List&lt;TodoEntity&gt; entities = service.create(entity);

            // (5) 자바 스트림을 이용해 리턴된 엔티티 리스트를 TodoDTO 리스트로 변환
            List&lt;TodoDTO&gt; dtos = entities.stream().map(TodoDTO::new).collect(Collectors.toList());

            // (6) 변환된 TodoDTO 리스트를 이용해 ResponseDTO를 초기화
            ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().data(dtos).build();

            // (7) ResponseDTO를 리턴
//            log.info(&quot;생성?&quot;);
            return ResponseEntity.ok().body(response);

        } catch (Exception e) {

            // (8) 혹시 예외가 나는 경우 dto 대신 error에 메시지를 넣어 리턴한다.
            String error = e.getMessage();
            ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().error(error).build();

            return ResponseEntity.badRequest().body(response);

        }

    }

    @DeleteMapping
    public ResponseEntity&lt;?&gt; deleteTodo(@RequestBody TodoDTO dto, @AuthenticationPrincipal String userId) {

        try {

            // (1) TodoEntity로 변환
            TodoEntity entity = TodoDTO.toEntity(dto);

            // (2) 임시 유저 아이디를 생성.
            entity.setUserId(userId);

            // (3) 서비스를 이용해 entity 삭제
            List&lt;TodoEntity&gt; entities = service.delete(entity);

            // (4) 자바 스트림을 이용해 리턴된 엔티티 리스트를 Todo리스트로 변환
            List&lt;TodoDTO&gt; dtos = entities.stream().map(TodoDTO::new).collect(Collectors.toList());

            // (5) 변환된 TodoDTO 리스트를 이용해 ResponseDTO를 초기화 한다.
            ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().data(dtos).build();

            // (6) ResponseDTO를 리턴
            return ResponseEntity.ok().body(response);

        } catch (Exception e) {

            // (7) 혹시 예외가 일어나는 경우 dto 대신 error 에 메시지를 넣어 리턴
            String error = e.getMessage();
            ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().error(error).build();
            return ResponseEntity.badRequest().body(response);
        }

    }

    @PutMapping
    public ResponseEntity&lt;?&gt; updateTodo(@RequestBody TodoDTO dto, @AuthenticationPrincipal String userId) {

        // (1) dto를 entity로 변환
        TodoEntity entity = TodoDTO.toEntity(dto);

        // (2) id를 temporaryUserId로 초기화한다
        entity.setUserId(userId);

        // (3) 서비스를 이용해 entity를 업데이트한다
        List&lt;TodoEntity&gt; entities = service.update(entity);

        // (4) 자바 스트림을 이용해 리턴된 엔티티 리스트를 TodoDTO 리스트로 변환
        List&lt;TodoDTO&gt; dtos = entities.stream().map(TodoDTO::new).collect(Collectors.toList());

        // (5) 변환된 TodoDTO 리스트를 이용해 ResponseDTO를 초기화한다.
        ResponseDTO&lt;TodoDTO&gt; response = ResponseDTO.&lt;TodoDTO&gt;builder().data(dtos).build();

        // (6) ResponseDTO 리턴
        return ResponseEntity.ok().body(response);

    }

}
</code></pre><hr>
<p>위 설정들을 통해 BackEnd 단에서 
JWT, Spring Security를 사용하여 로그인을 세션 방식으로 구현할 수 있다.</p>
<p>여기서 <span style = "color : red"><strong>주의해야할 점</strong></span>은,
JWT를 사용할 때 토큰이 <strong>쿠키로 저장되어 클라이언트로 전송</strong>될 수 있는데
이 때 <span style = "color : red"><strong>HTTPS를 사용하지 않으면 쿠키가 도청</strong></span>될 수 있으며
<strong>악의적인 공격자가 토큰을 가로채어 부정한 요청을 보낼 위험</strong>이 있다.
때문에 <span style = "color : deepskyblue"><strong>JWT를 사용한 웹 프로젝트는 반드시 HTTPS 로 통신</strong></span>하도록 하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot - JWT (이론)]]></title>
            <link>https://velog.io/@dev-kms/Spring-Boot-JWT-%EC%9D%B4%EB%A1%A0</link>
            <guid>https://velog.io/@dev-kms/Spring-Boot-JWT-%EC%9D%B4%EB%A1%A0</guid>
            <pubDate>Wed, 24 May 2023 09:26:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="json-web-token-jwt">Json Web Token (JWT)</h1>
</blockquote>
<p><strong>JWT</strong>는 <strong>클라이언트와 서버간의 인증과 정보 교환</strong>을 <span style = "color : deepskyblue"><strong>안전하게 수행</strong></span>하기 위한 <strong>표준 방법</strong>이다.
정보를 <strong>안전하게 전달하기 위해 <span style = "color : deepskyblue">&quot;전자 서명&quot;</span>된 JSON 객체로 표현</strong>된다.</p>
<pre><code>전자 서명 ? 
전자 서명이란 {헤더}, {페이로드}와 시크릿 키를 이용해 해시 함수에 돌린, 즉 암호화한 결괏값이다.
시크릿 키란 나만 알고있는 문자열, 비밀번호 같은 것이다.
너무 간단하지 않으면 아무거나 상관없다.</code></pre><p>JWT는 총 <span style = "color : deepskyblue"><strong>3가지</span> 부분으로 구성</strong>된다.</p>
<p><strong>1. 헤더 (Header)</strong></p>
<ul>
<li><p>JWT의 헤더는 <span style = "color : deepskyblue"><strong>토큰의 유형</strong></span> 및 <span style = "color : deepskyblue"><strong>서명 알고리즘</strong></span>을 지정한다.
헤더는 JSON 객체로 표현되며, 일반적으로 두개의 속성을 가진다.</p>
<ul>
<li>*<em>typ : *</em>
Type을 줄인 말, 이 토큰의 타입을 의미한다</li>
<li>*<em>alg : *</em>
Alogithm을 줄인 말, 
토큰의 서명을 발행하기 위해 사용된 해시 알고리즘의 종류를 의미.</li>
</ul>
</li>
</ul>
<p><strong>2. 페이로드 (PayLoad)</strong></p>
<ul>
<li>JWT의 페이로드는 토큰에 포함되는 <span style = "color : deepskyblue"><strong>클레임(claim)정보</span>를 담고있다.</strong><ul>
<li><strong>sub :</strong>
Subject를 줄인 말, 이 토큰의 주인을 의미한다.
sub는 ID처럼 유익할 식별자여야 한다.</li>
<li>*<em>iss : *</em>
Issuer를 줄인 말, 이 토큰을 발행한 주체를 의미한다.
예를 들어 페이스북이 이 토큰을 발행한다면 주체가 facebook이 된다.</li>
<li>*<em>iat : *</em>
issued at을 줄인 말, 토큰이 발행된 날짜와 시간을 의미한다.</li>
<li>*<em>exp : *</em>
expiration을 줄인 말, 토큰이 만료되는 시간을 의미한다.</li>
</ul>
</li>
</ul>
<p>** 3. 서명 (Signature) ** </p>
<ul>
<li>서명은 헤더, 페이로드 및 <span style = "color : deepskyblue"><strong>비밀 키</strong></span>를 사용하여 생성된다.
서명은 토큰이 유효하고 변조되지 않았음을 검증하는 데 사용된다.</li>
</ul>
<hr>
<p>JWT의 인증 과정을 로그인을 예로 들어 설명하면,</p>
<ol>
<li><p><strong>최초 로그인</strong> 시 <strong>서버</strong>는 <strong>사용자의 아이디와 비밀번호</strong>를 <span style = "color : deepskyblue"><strong>서버에 저장된 비밀번호에 비교</span>해 인증</strong>한다.</p>
</li>
<li><p><strong>인증된 사용자인 경우</strong>, 사용자의 정보를 이용해 <strong>{헤더}.{페이로드} 부분을 작성</strong>한다.</p>
</li>
<li><p><span style = "color : deepskyblue"><strong>자신의 시크릿 키</strong></span>로{헤더}.{페이로드} 부분을 전자 서명한다.</p>
</li>
<li><p>전자 서명의 결과로 나온 값을 {헤더}.{페이로드}<span style = "color : deepskyblue">{서명}</span>으로 이어붙이고, </p>
</li>
</ol>
<p><strong>Base 64로 인코딩 한 후 반환</strong>한다.</p>
<ol start="5">
<li>이후 누군가 <strong>이 토큰으로 리소스 접근을 요청</strong>하면</li>
</ol>
<p><strong>서버는 이 토큰을 Base64로 디코딩</strong>해서 얻은 JSON을
<strong>{헤더}{페이로드}</strong> 와 <strong>{서명}</strong> 부분으로 나눈다.</p>
<ol start="6">
<li><p><strong>서버는 {헤더}.{페이로드}와 <span style = "color : deepskyblue">자신이 갖고 있는 시크릿키로 전자 서명</span>을 만든 후 ,
방금 만든 전자 서명을 <span style = "color : deepskyblue">HTTP 요청이 가지고온 {서명}부분</span> 과 비교하여 <span style = "color : red">유효성을 검사한다.</strong></span></p>
<p>서버가 방금 시크릿키를 이용해 만든 전자서명과 HTTP 요청의 {서명} 부분이 <span style = "color : deepskyblue"><strong>일치</strong></span>하면
<strong>토큰이 위조되지 않았다는 뜻</strong>이다.</p>
</li>
</ol>
<hr>
<p>누군가 토큰을 훔쳐가면 어떻게 될까?
토큰을 훔쳐가면 당연히 해당 계정의 리소스에 접근할 수 있게 된다.
그렇기 때문에 <strong>반드시 <span style = "color : red">HTTPS</strong></span>(SSL/TSL)를 통해 <strong>통신해야한다.</strong></p>
<p><strong>HTTPS</strong>는 <strong>HTTP</strong>의 <span style = "color : deepskyblue"><strong>보안버전</strong></span>으로, 
암호화된 통신을 제공하여 <strong>데이터의 <span style = "color : deepskyblue">기밀성</span>과 <span style = "color : deepskyblue">무결성</span>을 보호</strong>한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot - Rest API]]></title>
            <link>https://velog.io/@dev-kms/Spring-Boot-Rest-API</link>
            <guid>https://velog.io/@dev-kms/Spring-Boot-Rest-API</guid>
            <pubDate>Wed, 24 May 2023 07:40:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="rest-api">Rest API</h1>
</blockquote>
<p><strong>Rest</strong>는 <strong>Representational State Transfer의 약자</strong>로, <span style = "color : deepskyblue"><strong>아키텍처 스타일</strong></span>이다.
<strong>아키텍처 스타일</strong>은 <strong>아키텍처 패턴</strong>과 약간 다른 개념이다.</p>
<ul>
<li><strong>아키텍처 패턴</strong><ul>
<li>어떤 반복되는 문제 상황을 해결하기 위한 도구</li>
</ul>
</li>
</ul>
<ul>
<li>** 아키텍처 스타일 **<ul>
<li>반복되는 아키텍처 디자인</li>
</ul>
</li>
</ul>
<hr>
<p><strong>Rest 아키텍처 스타일</strong>은 <strong>6가지 <span style = "color : red">제약 조건</strong></span>으로 구성된다.
이 <strong><span style = "color : deepskyblue">가이드 라인</span>을 따르는 API를 <span style = "color : deepskyblue">RestFul API</strong></span> 라고 한다.</p>
<ol>
<li><strong>Client - Server</strong></li>
<li><strong>상태가 없는 Stateless</strong></li>
<li><strong>캐시 가능한 데이터</strong></li>
<li><strong>일관적인 인터페이스</strong></li>
<li><strong>레이어 시스템</strong></li>
<li><strong>코드 온-디맨드</strong></li>
</ol>
<hr>
<h2 id="client-server">Client-Server</h2>
<p>Client-Server 라는 것은 <span style = "color : deepskyblue"><strong>리소스를 관리하는 서버가 존재</strong></span>하고
<span style = "color : deepskyblue"><strong>다수의 클라이언트</strong></span>가 리소스를 소비하기 위해 네트워크를 통해 서버에 접근하는 구조를 의미한다.</p>
</br>

<h2 id="상태가-없는-stateless">상태가 없는 Stateless</h2>
<p><strong>클라이언트가 서버에 요청</strong>을 보낼 때, <strong>이전 요청의 영향을 받지 않았다는 것</strong>을 <strong><span style = "color : red">상태가 없다</span></strong>고 한다.
예를 들어,</p>
<ol>
<li>/Login 으로 요청을 보내고 로그인이 되어 다음 페이지인 /page로 넘어감</li>
<li>/page로 리소스를 불러올 때 <strong>이전 요청에서 Login한 사실을 <span style = "color : red">서버가 알고있는가?</strong></span>
이 때 서버가 Login 한 상태를 <span style = "color : deepskyblue"><strong>알고 있어야 한다면</strong></span> 이것은 <span style = "color : deepskyblue"><strong>상태가 있는 아키텍처</strong></span>이다.
반대로, <span style = "color : red"><strong>서버가 모르고 있다면</strong></span> 이것은 <span style = "color : red"><strong>상태가 없는 아키텍처</strong></span>이다.</li>
</ol>
<p>그럼 로그인은 어떻게 구현하고, 또 유지해야하는가?
<strong>클라이언트는 서버에 요청을 날릴 때 마다 <span style = "color : deepskyblue">요청</span>에 리소스를 받기 위한 <span style = "color : deepskyblue">모든 정보를 포함</span>해야한다.</strong>
<strong>리소스를 수정한 이유 수정한 상태를 유지</strong>해야하는 경우,
<strong><span style = "color : red">서버가 아닌</span> 데이터베이스 같은 퍼시스턴스에 상태를 저장</strong>해야한다.</p>
</br>

<h2 id="캐시-가능한-데이터">캐시 가능한 데이터</h2>
<p>서버에서 리소스를 리턴할 때 <strong>캐시가 가능한지 아닌지 명시</strong>할 수 있어야 한다.
<span style = "color : deepskyblue"><strong>캐시</strong></span>란 <strong><span style = "color : red">클라이언트가 다시 API를 호출하지 않고도</span> 이전에 요청한 데이터를 가져오는 프로세스다.</strong>
캐시는 <strong>API의 성능을 향상시키고 네트워크 트래픽을 줄이는데 도움을 줄 수 있다.</strong></p>
<p>캐시가 가능한 데이터의 예로는 다음과 같은 것들이 있다.</p>
<ul>
<li><strong>자주 변경되지 않는 데이터</strong></li>
<li><strong>자주 요청되는 데이터</strong></li>
<li><strong>크기가 큰 데이터</strong></li>
</ul>
<p>HTTP에서는 cache-control이라는 헤더에 리소스의 캐시 여부를 명시할 수 있다.</p>
</br>

<h2 id="일관적인-인터페이스">일관적인 인터페이스</h2>
<p>시스템 또는 애플리케이션의 리소스에 <span style = "color : deepskyblue"><strong>접근</strong></span>하기 위한 <strong><span style = "color : deepskyblue">인터페이스가 일관되어야 한다</span>는 뜻</strong>이다.
일관적인 인터페이스는 <strong>API를 쉽게 이해하고 사용할 수 있게 도와준다.</strong></p>
<p>일관적인 인터페이스를 달성하려면 다음과 같은 사항을 고려해야한다.</p>
<ul>
<li><strong>요청과 응답의 형식</strong></li>
<li><strong>리소스의 이름</strong></li>
<li><strong>리소스 간의 관계</strong></li>
<li><strong>오류 메시지</strong></li>
</ul>
<p>예를 들어, 
어느 페이지를 로딩하기 위해 필요한 리소스를 요청하는 API가 <strong>&quot;/project.com/update&quot;</strong> 일 때,
이 페이지를 새로 업데이트하기 위해서 사용한 API가 <strong>&quot;/project<span style = "color : red">2</span>.com/update&quot;</strong> 라면,
이는 <span style = "color : red"><strong>일관적인 인터페이스</strong></span>가 아니다.</p>
<p>또한, 
똑같은 페이지의 <strong>어느 요청1에 대한 응답</strong>이 <strong>JSON</strong>이고 
<strong>요청2에 대한 응답</strong>이 <strong>HTML</strong>이라면 <span style = "color : red"><strong>일관적인 인터페이스</strong></span>가 아니다.</p>
</br>

<h2 id="레이어-시스템">레이어 시스템</h2>
<p><strong>클라이언트</strong>가 <strong>서버에 요청</strong>을 날릴 때, <strong>여러개의 레이어로 된 서버를 거칠 수 있다.</strong>
예를 들어 서버는 인증 서버, 캐싱 서버, 로드 밸런서를 거쳐서
최종적으로 애플리케이션에 도착한다고 했을 때,
이 사이의 <strong>레이어들은 요청과 응답에 어떤 영향을 미치지 않으며</strong>
<strong>클라이언트</strong>는 <strong>서버</strong>의 <span style = "color : red"><strong>레이어 존재 유무</span>를 알지 못한다.</strong></p>
<p>레이어 시스템의 예는 다음과 같이 나눌 수 있다.</p>
<ul>
<li><strong>프레젠테이션 계층</strong><ul>
<li>사용자와 상호 작용하는 계층</li>
</ul>
</li>
<li>*<em>비즈니스 계층 *</em><ul>
<li>API의 비즈니스 로직을 담당하는 계층</li>
</ul>
</li>
<li><strong>데이터 계층</strong><ul>
<li>API의 데이터를 저장하는 계층</li>
</ul>
</li>
</ul>
</br>

<h2 id="코드-온-디맨드">코드 온-디맨드</h2>
<p>이 제약은 <span style = "color : deepskyblue"><strong>선택사항</span>이다.</strong>
코드 온-디맨드는 <strong>API가 클라이언트에 코드를 전송할 수 있도록 하는 것</strong>을 말한다.
즉, <strong>클라이언트는 서버에 코드를 요청</strong>할 수 있고, <strong>서버가 리턴한 코드를 실행</strong>할 수 있게 되는 것.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - Props]]></title>
            <link>https://velog.io/@dev-kms/React-Props</link>
            <guid>https://velog.io/@dev-kms/React-Props</guid>
            <pubDate>Thu, 18 May 2023 05:54:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="props">Props</h1>
</blockquote>
<p><strong>props</strong>는 <span style = "color : deepskyblue"><strong>부모 컴포넌트</span>에서 <span style = "color : deepskyblue">자식 컴포넌트</span>로 전달되는 속성</strong>이다.
자식 컴포넌트는 <strong>props를 사용하여 자신의 UI를 구성</strong>할 수 있다.</p>
<ol>
<li><p><strong>부모 컴포넌트에서 Props 전달하기</strong></p>
<ul>
<li>자식 컴포넌트에 전달하려는 데이터를 <strong>Props</strong>로 정의한다.</li>
<li>자식 컴포넌트를 사용하는 <strong>부모 컴포넌트에서 Props값을 설정</strong>하여 전달한다.<pre><code>// ParentComponent.js
import React from &#39;react&#39;;
import ChildComponent from &#39;./ChildComponent&#39;;
</code></pre></li>
</ul>
<p>function ParentComponent() {</p>
<pre><code>const S1 = &quot;Hello, world!&quot;;
 const S2 = &quot;have a good day!&quot;;
return (
 &lt;div&gt;
     &lt;ChildComponent message1={S1} message2={S2} /&gt;
 &lt;/div&gt;
 );</code></pre><p>}</p>
<p>export default ParentComponent;</p>
<pre><code></code></pre></li>
</ol>
</br>

<ol start="2">
<li><p><strong>자식 컴포넌트에서 Props 사용하기</strong></p>
<ul>
<li>자식 컴포넌트의 함수나 클래스 선언에서 &#39;props&#39; 매개 변수를 사용하여 
Props값을 받아온다.</li>
<li>Props값은 &#39;{ }&#39; 중괄호를 사용하여 JSX 내에서 사용할 수 있다.</li>
</ul>
<pre><code>// ChildComponent.js
import React from &#39;react&#39;;
function ChildComponent(props) { // function ChildComponent(S1, S2) 도 사용 가능
    return (
        &lt;div&gt;
            &lt;p&gt;{props.message1}&lt;/p&gt;
             &lt;p&gt;{props.message2}&lt;/p&gt;
        &lt;/div&gt;
     );
}
export default ChildComponent;</code></pre></li>
</ol>
</br>

<p>위 예시의 <strong>&#39;ParentComponent&#39;</strong>에서 
<strong>&quot;Hello, world!&quot; **
**&quot;have a good day!&quot;</strong> 라는 문자열을
각각 <strong>message1, message2</strong>라는 변수에 담아 <strong>ChildComponent</strong>에 <span style = "color : deepskyblue"><strong>Props</span>로 전달</strong>한다.
<strong>ChildComponent에서는 <span style = "color : deepskyblue">{props.message1} , {props.message2}</span> 를 사용하여 <span style = "color : deepskyblue">전달받은 값을 출력</span>한다.</strong></p>
<p>이런식으로 Props는 부모 컴포넌트에서 자식 컴포넌트로 <span style = "color : red"><strong>일방적으로 전달</strong></span>되며,
자식 컴포넌트에서는 Props의 값을 <span style = "color : red"><strong>읽기</span>만 할 수 있다</strong>.
<strong>Props</strong>는 <span style = "color : red"><strong>읽기 전용</strong></span>이므로 <strong>자식 컴포넌트에서 Props값을 변경</strong>하려면
<strong>부모 컴포넌트에서 Props를 업데이트</strong> 해야한다.</p>
<hr>
<p>리액트를 처음 막 다루었을 때, 회사 내부에 리액트 개발자가 아무도 없어서 
이처럼 컴포넌트 값을 다른 컴포넌트로 어떻게 옮기는지 거의 하루종일 찾은적이 있다. 
context API에도 담아보고, SessionStroage, LocalStorage에도 담아보는 등등 
다양한 삽질을 한 결과, 결국 props라는 개념을 가장 늦게 알게되어 사용하게 되었다. 
역시 아는게 힘이다 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 컴포넌트 만들기]]></title>
            <link>https://velog.io/@dev-kms/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dev-kms/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 18 May 2023 02:44:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="컴포넌트-만들기">컴포넌트 만들기</h1>
</blockquote>
<p><strong>자바스크립트 파일을 만들고</strong> 이를 <strong>App.js 컴포넌트에서 불러와</strong>보겠다.
보통 파일을 여러 형식으로 만들던데, 
난 <strong>export default</strong> 형식이 가장 편해서 이 방법으로 정착했다.</p>
<p><strong>export default</strong>는 <strong>ES6</strong>부터 등장한 구문이다.</p>
<ul>
<li><p><span style = "color : deepskyblue"><strong>장점</strong></span></p>
<ul>
<li>편리한 사용성</li>
<li>단일 모듈로 사용</li>
<li>이름 없는 내보내기</li>
</ul>
</li>
<li><p><span style = "color : red"><strong>단점</strong></span></p>
<ul>
<li>명시적인 가져오기가 필요하지 않음</li>
<li>다중 내보내기의 어려움</li>
</ul>
</li>
</ul>
<hr>
<pre><code>import React from &quot;react&quot;;
export default function Todo() {

    return (
        &lt;div&gt;
            &lt;input type = &quot;checkbox&quot; id = &quot;todo0&quot; name = &quot;todo0&quot; value = &quot;todo0&quot; /&gt;
            &lt;label for =&quot;todo0&quot;&gt; Todo 컴포넌트 만들기 &lt;/label&gt;
        &lt;/div&gt;
    )

}</code></pre><p>위 처럼 기본 형식의 컴포넌트를 화면에 렌더링 하기 위해
현재 기본 설정으로 렌더링 되고 있는 App 컴포넌트에 import 시킨다.</p>
<pre><code>import &#39;./App.css&#39;;
import Todo from &#39;./Todo&#39;;

function App() {
    return (
        &lt;div className=&quot;App&quot;&gt;
            &lt;Todo /&gt;
        &lt;/div&gt;
    )
}</code></pre><p>이런식으로 각각의 서비스를 구현한 컴포넌트들을 상호작용 하게끔 만드는 것이 중요한것같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 리액트 설치]]></title>
            <link>https://velog.io/@dev-kms/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@dev-kms/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Wed, 17 May 2023 09:32:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="react">React</h1>
</blockquote>
<p>난 <strong>BackEnd 개발을 주 전공</strong>으로 삼고있지만, 
지난 인턴십에서 백엔드와 더불어 <strong>React를 다루며</strong> <strong>미약하나마 FrontEnd를 개발할 수 있다</strong>.
내가 작성할 React에 관한 글들은 
내가 실무에서 배운것들을 다시 한번 정리하고 복습하는 형식으로 진행될 것이다.</p>
<hr>
<h3 id="nodejs-설치">Node.js 설치</h3>
<p><strong>node.js</strong>는 <span style = "color : deepskyblue"><strong>자바스크립트를 내 컴퓨터에서 실행</span>할 수 있게 해주는 프로그램이다.</strong>
웹 브라우저 밖에서 자바스크립트를 실행할 수 있다는 말은,
<strong>자바 스크립트</strong>로 <strong>클라이언트 언어</strong> 뿐만 아니라 <strong>서버 언어로도 사용</strong>할 수 있다는 뜻이다.</p>
<hr>
<h3 id="npm">NPM</h3>
<p>NPM (Node Package Manager) 은 node.js의 패키지 관리 시스템이다.
<a href="https://nodejs.org/ko/download">node.js 설치 링크</a>
npm은 node.js를 설치하면 같이 설치된다.
설치 후 커맨드 라인에서 npm version 명령어로 버전 정보를 확인하자.</p>
<pre><code>$ npm version

{ npm : &#39; 8.5.0 &#39; 
 ...
 ...
}</code></pre><hr>
<h3 id="react-프로젝트-만들기">React 프로젝트 만들기</h3>
<p>설치된것을 확인했다면 React 프로젝트를 만들어보겠다.
리액트 프로젝트를 시작하려는 폴더를 만들고 해당 폴더로 workSpace를 이동시킨다.</p>
<pre><code>$ mkdir react-project // 폴더 생성
$ cd react-project // 폴더 이동</code></pre><p>리액트 프로젝트를 설치한다.</p>
<pre><code>$ npx create-react-app react-app

!주의 -- npm X , npx O</code></pre><p>react 애플리케이션을 생성하면 React 가 설치되는 로그가 출력된다.</p>
<hr>
<p>프로젝트가 만들어졌으면 해당 폴더에 들어간 뒤 프로젝트를 실행시킨다.</p>
<pre><code>$ cd react-app
$ npm start</code></pre><p>프로젝트가 실행되면  <code>http://localhost:3000</code>경로에서 애플리케이션이 실행된다.
<img src="https://velog.velcdn.com/images/dev-kms/post/24a39c06-fccf-4510-a10f-2f1aa62c9bc9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot - Service]]></title>
            <link>https://velog.io/@dev-kms/Spring-Boot-Service</link>
            <guid>https://velog.io/@dev-kms/Spring-Boot-Service</guid>
            <pubDate>Wed, 17 May 2023 08:32:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="service">Service</h1>
</blockquote>
<p><strong>Srvice 계층</strong>은 <span style = "color : deepskyblue"><strong>비즈니스 로직을 수행하는 계층</strong></span>이다.
컨트롤러에서 요청을 받고 </p>
<ul>
<li><strong>데이터베이스에서 데이터를 조회</strong></li>
<li><strong>도메인 모델을 생성</strong></li>
<li><strong>트랜잭션을 관리</strong></li>
</ul>
<h2 id="하는-등의-작업을-수행한다">하는 등의 작업을 수행한다.</h2>
<p><strong>Service 계층</strong>은 <strong><span style = "color : yellow">@Service</span> 어노테이션</strong>을 사용하여 생성할 수 있다.</p>
<pre><code>@Service
public class UserService{

}</code></pre><p>Service 계층은 Repository 계층을 <span style = "color : yellow"><strong>@Autowired</span> 어노테이션</strong>을 사용하여 의존성을 주입받아 사용한다.</p>
<pre><code>@Service
@RequiredArgsConstructor
public class UserService{
    private final UserRepository userRepository;
}</code></pre></br>

<hr>
<p>Service 단의 <strong>주요 역할</strong>은 <strong>총 <span style = "color : red">5가지</strong></span>이며, 특징은 다음과 같다.</p>
<ul>
<li><strong>비즈니스 로직 구현</strong><ul>
<li>Service 계층은 비즈니스 로직을 구현하기 때문에 <strong>Entity, DTO를 가공 및 변환</strong>하여 </li>
<li><em>다양한 연산을 수행하여 비즈니스 규칙을 충족시키는 역할*</em>을 한다.
예를 들어 사용자 등록, 주문 처리, 결제 처리 등과 같은 비즈니스의 기능을 
Service 계층에서 구현하는 것이다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>트랜잭션 관리</strong><ul>
<li>Service 계층은 <strong>트랜잭션을 관리하는 책임</strong>을 가진다.
여러개의 데이터 액세스 작업이 한 단위로 묶여야 하는 경우, </li>
<li><em>Service 계층에서 트랜잭션을 시작하고 <span style = "color : deepskyblue">커밋</span> 또는 <span style = "color : deepskyblue">롤백</span>을 하는 작업을 수행한다.*</em>
이를 통해 데이터 <strong>일관성</strong>과 <strong>안정성</strong>을 유지할 수 있다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>외부 서비스 통합</strong><ul>
<li>Service 계층은 <strong>외부 서비스</strong>나 <strong>API와의 통합을 수행</strong>할 수 있다.</li>
<li><em>다른 서비스와의 통신*</em>, <strong>외부 데이터의 처리</strong>, <strong>외부 시스템과의 연동</strong> 등을 
Service 단에서 처리할 수 있다. 이를 통해 <span style = "color : deepskyblue"><strong>모듈화와 결합도가 낮은 설계</span>를 구현</strong>한다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>비즈니스 예외 처리</strong><ul>
<li>Service 계층에서 <strong>비즈니스 규칙에 의한 예외를 처리</strong>할 수 있다.
예를 들어 <strong>잘못된 입력 데이터</strong>, <strong>중복된 데이터</strong>, <strong>권한 오류</strong> 등에 대한 예외를
적절하게 처리하고 컨트롤러로 전달할 수 있다.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>단위 테스트</strong><ul>
<li>Service 계층은 <span style = "color : deepskyblue"><strong>단위 테스트</span>를 수행하기에 적합한 계층</strong>이다.</li>
<li><em>비즈니스 로직이 복잡*</em>할 경우, Service 단에서 단위 테스트를 작성하여 </li>
<li><em>각 비즈니스의 기능이 정확하게 동작하고 있는지 확인*</em>할 수 있다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>