<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jiu-jung.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 08 Jan 2026 14:21:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jiu-jung.log</title>
            <url>https://velog.velcdn.com/images/jiu-jung/profile/827a9162-8985-47db-aafa-f2c2b7abe04b/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jiu-jung.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jiu-jung" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[객체 지향 프로그래밍 (Object Oriented Programming) 정리]]></title>
            <link>https://velog.io/@jiu-jung/%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-Object-Oriented-Programming-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jiu-jung/%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-Object-Oriented-Programming-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 08 Jan 2026 14:21:27 GMT</pubDate>
            <description><![CDATA[<h2 id="객체"><strong>객체?</strong></h2>
<p>실제 세계의 객체를 생각하면 된다 → 어떠한 상태(속성)를 갖고 특정 행위를 할 수 있는 독립적인 단위.</p>
<h2 id="class"><strong>Class</strong></h2>
<p>객체의 설계도, 속성과 동작을 정의한다.</p>
<p>사용자 정의 자료형(user defined data type)</p>
<h2 id="object"><strong>Object</strong></h2>
<p>상태(데이터)와 행동(메소드)를 함께 가지는 독립적인 단위</p>
<p>Class → <strong>Instantiate</strong> → Object</p>
<p>실제 메모리에 할당된 인스턴스</p>
<hr>
<h1 id="객체-지향-프로그래밍-object-oriented-programming"><strong>객체 지향 프로그래밍 (Object Oriented Programming)</strong></h1>
<p>객체 중심의 프로그래밍 패러다임.</p>
<blockquote>
<p>프로그램을 여러개의 객체로 구성된 시스템으로 바라보는 방식</p>
</blockquote>
<br>

<h2 id="필요성"><strong>필요성</strong></h2>
<h3 id="절차-지향-프로그래밍procedure-oriented-programming">절차 지향 프로그래밍(Procedure Oriented Programming)</h3>
<ul>
<li><strong>함수가 프로그램의 기본 단위이다.</strong><ul>
<li><strong>프로그램이 수행해야 할 일들을 순서대로 나열하고, 각 작업을 독립적인 함수로 분리하여 작성한다.</strong></li>
</ul>
</li>
<li>데이터와 함수가 분리되어있다.</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>실행 흐름이 직관적이고 이해하기 쉽다.</li>
<li>실행 속도가 빠르다. 하드웨어 제어에 유리하다.</li>
<li>작은 규모의 프로그램 개발 / 프로토타입 개발에 유리하다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>중복 코드가 발생하기 쉽고, 코드의 재사용성이 낮다</li>
<li>데이터 보호가 어렵다. (책임 및 권한의 분리가 어렵다.)</li>
<li>잦은 전역변수 사용으로 오류가 발생하기 쉽다.</li>
<li>유지보수가 어렵다.</li>
</ul>
<br>

<h3 id="객체-지향-프로그래밍">객체 지향 프로그래밍</h3>
<p><strong>장점</strong></p>
<ul>
<li>코드의 재사용이 쉽다.</li>
<li>상속, 다형성 → 기능 확장이 쉽다.</li>
<li>프로그램을 모듈화할 수 있어 유지보수가 편리하다.</li>
<li>대규모 SW 개발에 적합하다.</li>
<li>실제 세계의 사물이나 개념을 프로그램에 표현할 수 있다.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>설계 단계가 복잡하다(요구사항 분석, 모델링).</li>
<li>추상화, 메모리 → 실행속도 느림.</li>
<li>학습곡선</li>
</ul>
<br>

<h2 id="특징"><strong>특징</strong></h2>
<h3 id="1-캡슐화encapsulation">1. 캡슐화(Encapsulation)</h3>
<p>데이터를 외부에서 직접 접근하지 못하게 하고, 메소드를 통해서만 접근할 수 있게 제한하는 개념.</p>
<p>데이터 + 메서드를 객체로 캡슐화하고, 외부에서는 객체 내부를 알 수 없도록 숨긴다.</p>
<blockquote>
<p><strong>파이썬의 캡슐화</strong></p>
</blockquote>
<p>완전히 기술적으로 막지는 않고, 관례로 구현된다. → 파이썬의 철학: “we are consenting adults here”</p>
<p><strong>Protected</strong>: <code>_</code> 클래스 및 하위 클래스에서 접근 가능.</p>
<pre><code class="language-python">class Person():
    def __init__(self, name):
        self._name = name</code></pre>
<p><strong>Private</strong>: <code>__</code> (<strong>name mangling)</strong> 정의한 클래스 내에서만 접근 가능.</p>
<pre><code class="language-python">class Person():
    def __init__(self, name):
        self.__name = 0</code></pre>
<p><strong>@property</strong></p>
<pre><code class="language-python">class User:
    def __init__(self, age):
        self._age = age

    @property
    def age(self):
        print(&quot;getter 호출&quot;)
        return self._age

    @age.setter
    def age(self, value):
        print(&quot;setter 호출&quot;)
        if value &lt; 0:
            raise ValueError(&quot;나이는 음수 불가&quot;)
        self._age = value</code></pre>
<pre><code class="language-python">u = User(20)

u.age        # getter 호출
u.age = 30   # setter 호출</code></pre>
<pre><code class="language-python">u.age  ──&gt;  getter(age) ──&gt;  _age
u.age= ──&gt;  setter(age) ──&gt;  _age</code></pre>
<p><strong>접근 경로가 통제된다.</strong></p>
<h3 id="2-추상화abstraction">2. 추상화(Abstraction)</h3>
<p>객체가 제공하는 행동(기능)만 드러내고, 내부 구현을 숨기는 설계 방식.</p>
<p>외부에서 객체의 내부 구현은 몰라도 된다. 객체 외부에서 메소드를 사용하는 사람은 구현 내용을 몰라도 사용할 수 있다.</p>
<h3 id="3-상속inheritance">3. 상속(Inheritance)</h3>
<p>기존에 작성된 클래스의 속성과 메소드를 물려받아 사용할 수 있다.</p>
<p>기존 코드를 재사용할 수 있다. 상속을 통해 다형성을 구현할 수 있다.</p>
<pre><code class="language-python">class Vehicle:
    def __init__(self, name):
        self.name=name

    def move(self):
        print(f&quot;{self.name} 이동중&quot;)

class Car(Vehicle):
    def __init__(self, name, wheels):
        super().__init__(name)
        self.wheels=wheels

    def info(self):
        print(f&quot;{self.name} 바퀴는 {self.wheels}개&quot;)

def practice_inheritance():
    banner(&quot;3) OOP: Inheritance&quot;)

    car = Car(&quot;Taxi&quot;, 4)
    car.move()   # 예상: Taxi 이동 중
    car.info()   # 예상: Taxi 바퀴는 4개</code></pre>
<h3 id="4-다형성polymorphism">4. 다형성(polymorphism)</h3>
<p>하나의 메소드/클래스가 다양하게 동작할 수 있다.</p>
<p>오버라이딩(상속받은 메소드 재정의), 오버로딩(매개변수에 따라 여러개 정의)</p>
<pre><code class="language-python"># w/o polymorphism

class Dog:
        def __init__(self, name):
                self.name = name

        def bark(self):
                return f&quot;{self.name} says Woof!&quot;

class Cat:
        def __init__(self, name):
                self.name = name

        def meow(self):
                return f&quot;{self.name} says Meow!&quot;

def make_animal_speak(animal):
        if isinstance(animal, Dog):
                print(animal.bark())
        elif isinstance(animal, Cat):
                print(animal.meow())
        else:
                raise ValueError(&quot;Unknown animal&quot;)

# with polymorphism

class Animal:
        def __init__(self, name):
                self.name = name

        def speak(self):
                raise NotImplementedError

class Dog(Animal):
        def speak(self):
                return f&quot;{self.name} says Woof!&quot;

class Cat(Animal):
        def speak(self):
                return f&quot;{self.name} says Meow!&quot;

def make_animal_speak(animal):
        print(animal.speak())

dog = Dog(&quot;Buddy&quot;)
cat = Cat(&quot;Whiskers&quot;)
make_animal_speak(dog)
make_animal_speak(cat)</code></pre>
<p>같은 기능을 하는 메서드 이름을 같게 → 복잡성 줄이고, 코드 재사용, 기능 확장 용이</p>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://wikidocs.net/288046">1.3. 절차지향 언어 vs 객체지향 언어</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] Amazon Cognito 도입기]]></title>
            <link>https://velog.io/@jiu-jung/AWS-Amazon-Cognito-%EB%8F%84%EC%9E%85%EA%B8%B0-wphk85t6</link>
            <guid>https://velog.io/@jiu-jung/AWS-Amazon-Cognito-%EB%8F%84%EC%9E%85%EA%B8%B0-wphk85t6</guid>
            <pubDate>Sun, 24 Aug 2025 07:48:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>Amazon Cognito 도입기</strong>
Lambda Trigger와 API Gateway Proxy로 AWS 아키텍처 완성하기</p>
</blockquote>
<pre><code>목차

1. Amazon Cognito 소개
2. ALB + Cognito 연동 - 실패기
3. Lambda 트리거를 활용하여 인증 흐름 커스터마이징하기
4. 외부 API 호출 문제 NAT Gateway 없이 해결하기
5. Cognito 도입 시 고려할 점</code></pre><h1 id="1-amazon-cognito-소개">1. Amazon Cognito 소개</h1>
<p>Amazon Cognito는 <strong>AWS에서 제공하는 인증 플랫폼</strong>이다. 웹이나 모바일 앱에서 로그인, 회원가입, 권한 제어 같은 기능을 쉽게 구현할 수 있도록 해준다.
Cognito는 크게 user pool, identity pool로 구성된다.</p>
<ul>
<li>User pool을 통해 앱 또는 API에 사용자를 인증하고 권한을 부여한다.</li>
<li>Identity pool을 통해 사용자가 aws 리소스에 액세스 할 수 있도록 권한을 부여한다.</li>
</ul>
<p>쉽게 말해, <strong>user pool은 사용자 인증, identity pool은 aws 리소스 권한 관리</strong>를 담당한다. 우리 프로젝트에서는 앱에 대한 사용자의 인증에 활용하고자, User Pool을 활용했다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d8bbaff4-88fa-465e-9059-d5b6aa89db27/image.png" alt=""></p>
<h3 id="user-pool-인증-흐름">User pool 인증 흐름</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/6ae05584-8e6d-4f38-b434-7adfe263ace8/image.png" alt="">
user pool 인증 흐름은 위와 같다.</p>
<ul>
<li>앱에서 로그인을 시도하면 Cognito 관리형 로그인 페이지로 연결되고, 사용자가 인증을 마치면 토큰을 앱에 전달한다.</li>
<li>이 토큰은 백엔드 서버로 전달되어 API 호출에 사용된다.</li>
</ul>
<br>

<hr>
<h1 id="2-alb--cognito-연동---실패기">2. ALB + Cognito 연동 - 실패기</h1>
<h3 id="2-1-application-load-balancer--cognito-연동-구조-선택">2-1. Application Load Balancer + Cognito 연동 구조 선택</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/1834ab95-ebb5-41ab-87c5-bdf647e7dbf2/image.png" alt="">ALB와 Cognito를 연동한 구조에서는 사용자가 ALB에 요청을 보내면, ALB가 인증되지 않은 사용자는 Cognito로 리디렉션하고, 인증 후에는 트래픽이 컨테이너로 전달된다.
ALB가 인증을 직접 처리해주기 때문에 백엔드에서 따로 인증 로직을 구현할 필요가 없고, 따라서 인증 과정에서 외부 api 호출이 필요 없다는 점에서 이 아키텍처를 선택했다.</p>
<h3 id="2-2-alb--cognito-연동-시도">2-2. ALB + Cognito 연동 시도</h3>
<h4 id="기술적-구현-가능성-확인">기술적 구현 가능성 확인</h4>
<p>ALB + Cognito 연동 구조를 구축하고, 
테스트 컨테이너를 통해 인증, REST API 호출, WebSocket 통신 모두 성공적으로 수행되는 것을 확인했다.</p>
<h4 id="실제-프론트-배포-단계에서-리디렉션-url-제약">실제 프론트 배포 단계에서 리디렉션 URL 제약</h4>
<ul>
<li>ALB + Cognito 연동 시, Callback URL을 <code>https://&lt;ALB DNS&gt;/oauth2/idpresponse</code> 로 설정해야 한다.</li>
<li>하지만 실제 서비스는 인증 완료 후 <strong>프론트엔드 도메인으로 리디렉션</strong>되어야 한다.</li>
</ul>
<blockquote>
<p>⟹ <strong>프론트가 Cognito와 직접 통신하고, 받은 토큰을 백엔드에 전달하는 구조</strong>로 변경했다. <br>
이 과정을 통해 아키텍처 설계 시에 실제 개발 흐름도 고려해야 된다는 교훈을 얻었다. (프론트 코드가 백엔드 프로젝트 내에 위치할 것이라고 생각했지만, 실제로는 따로 배포해서 문제 발생했으니..)</p>
</blockquote>
<br>

<hr>
<h1 id="3-lambda-트리거를-활용하여-인증-흐름-커스터마이징하기">3. Lambda 트리거를 활용하여 인증 흐름 커스터마이징하기</h1>
<p>이후, Cognito를 이용한 인증 흐름을 구현하면서 Cognito 기본 기능만으로는 개발 요구사항들을 만족시키기 어려웠다.
따라서, <strong>Cognito에 Lambda Trigger를 연결하여 인증 흐름을 커스터마이징</strong>했다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/b072da89-a603-46ba-8200-1fad7198fb35/image.png" alt=""></p>
<p>위는 최종 아키텍처 다이어그램이다. Cognito와 Lambda가 연동된 구조를 확인할 수 있다. 크게 보면 다음과 같은 구조이다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/f2ac9ce7-df25-4551-96fe-daccee38d6cc/image.png" alt="">
Cognito에 두가지 Lambda trigger가 연결되어 있다. 간단히 설명하자면 다음과 같다.</p>
<ul>
<li>사용자가 회원가입을 완료하면 <strong>Post Confirmation</strong> 트리거가 작동하고, 이때 <code>updateUser</code> 람다함수가 호출된다.</li>
<li>액세스 토큰이 발급되기 전에 <strong>Pre Token Generation</strong> 트리거가 작동해, JWT의 email 같은 클레임을 수정하는 <code>customToken</code> 람다함수가 실행된다.</li>
</ul>
<p>이제 각 람다 함수와 트리거가 왜 필요한지, 어떻게 구현했는지 자세히 설명하겠다.</p>
<h3 id="1-updateuser">1. <code>updateUser()</code></h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/edbd9f6c-fddc-4c05-a442-4f8cfdfa1aef/image.png" alt=""> 아키텍처 내 위치는 좌측과 같고, 람다 코드는 우측에 있다.
이 람다 함수와 트리거는 회원가입 직후, Cognito 사용자 정보를 내부 DB로 업데이트한다.</p>
<p>Cognito의 경우, 사용자 정보를 자체 사용자 디렉토리에 저장하지만, 우리 백엔드에도 사용자 데이터가 필요해서 이를 가져와야 했다.
따라서 람다함수를 생성하고 Cognito에 트리거를 연결하여, Dynamodb에 가입이 완료된 사용자의 정보를 저장했다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/dd385c85-8525-40b9-91b3-ebf44bbef9b0/image.png" alt=""> 회원가입이 발생하면 <code>Post confirmation trigger</code>가 발동되고, <code>updateUser()</code> 람다함수가 호출된다. 이 람다함수는 회원 정보를 DynamoDB에 저장한다.</p>
<h3 id="2-customtoken">2. <code>customToken()</code></h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/1fcd2cd0-56d0-4482-b15f-f5905ab0c139/image.png" alt="">아키텍처 내 위치는 좌측과 같고, 람다 코드는 우측에 있다.
이 람다함수는 access token 생성시에 헤더에 email 필드를 추가한다.</p>
<p>실제 배포 테스트를 진행하면서, 백엔드에서는 access token에 email 헤더가 필요하지만, cognito에서는 access token에 email 필드를 제공하지 않는 이슈가 발생했다.
Identity token을 발급해서 백엔드 로직을 수정할 수도 있었지만, 이미 배포가 완료된 상태였기에 백엔드를 수정하는것보다 람다 함수로 토큰을 수정하는것이 효율적이라고 판단해서 아래 흐름을 도입했다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d8391f6e-49d1-432b-a563-d762a89afad7/image.png" alt=""> 토큰이 생성되기 전에 <code>Pre token generation trigger</code>가 발동되면, 연결된 <code>customToken()</code> 람다가 호출되어, 토큰의 헤더에 email 클레임을 추가한다. 이를 통해 별도의 백엔드 로직 수정 없이 문제를 해결했다.</p>
<br>

<p>* 이외에도 다양한 람다 트리거들이 있어, 아래 공식문서를 참고하여 각자의 요구사항에 맞는 트리거를 적용하면 된다.
<a href="https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/cognito-user-pools-working-with-lambda-triggers.html">docs.aws.amazon.com</a></p>
<hr>
<h1 id="4-외부-api-호출-문제-nat-gateway-없이-해결하기">4. 외부 API 호출 문제: NAT Gateway 없이 해결하기</h1>
<h2 id="4-1-private-subnet-내부에서-외부-api-호출-발생">4-1. Private subnet 내부에서 외부 API 호출 발생</h2>
<p><code>application-cognito.yml</code></p>
<pre><code class="language-java">spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://cognito-idp.ap-northeast-2.amazonaws.com/ap-northeast-2_*********</code></pre>
<p>위 코드는 스프링 애플리케이션의 Cognito 설정 파일의 일부이다. Spring Security는 Cognito가 서명한 JWT를 검증할 때, 서명에 사용된 공개 키 목록(JWKS)을 Cognito의 Public Endpoint를 통해 동적으로 가져온다.</p>
<p>문제는 우리 애플리케이션이 보안 강화를 위해 외부 인터넷 접속이 차단된 Private Subnet 내의 ECS 컨테이너에 배포되었다는 점이었다. 초기 아키텍처 설계 시, 외부 API 호출이 필요 없다고 판단하여 비용 효율을 위해 NAT Gateway를 도입하지 않았다.</p>
<p>그러나 이 설계로 인해 정작 인증에 필수적인 JWKS 요청이 외부로 나가지 못하고 차단되는 문제가 발생했다. 결국 임시 조치로 NAT Gateway를 추가하여 문제를 해결했지만, 이로 인해 인프라 비용 크게 증가했다.</p>
<h2 id="4-2-api-gateway--lambda-프록시로-nat-gateway-대체하기">4-2. API Gateway + Lambda 프록시로 NAT Gateway 대체하기</h2>
<blockquote>
<p>이 케이스에서 외부 api 호출은 특정 시점에만, 특정 api로 발생했다. 따라서 <strong>api gateway와 lambda로 프록시</strong>를 구성하여 NAT Gateway를 대체해보고자 시도했다.</p>
</blockquote>
<p>그 방법은 다음과 같다.</p>
<h3 id="1-lambda-생성">1. Lambda 생성</h3>
<p>jwks를 반환하는 api를 호출하는 lambda 함수를 생성한다.
<code>jwkProxy()</code></p>
<pre><code class="language-python">import json
import urllib.request
COGNITO_JWKS_URL = &quot;https://cognito-idp.ap-northeast-2.amazonaws.com/ap-northeast-2_*********/.well-known/jwks.json&quot;
def handler(event, context):
    try:
        with urllib.request.urlopen(COGNITO_JWKS_URL) as resp:
            jwks_dict = json.loads(resp.read().decode(&quot;utf-8&quot;))
        return jwks_dict
    except Exception as exc:
        return { &quot;error&quot;: str(exc) }</code></pre>
<h3 id="2-vpc-endpoint-생성">2. VPC Endpoint 생성</h3>
<p>private api gateway와 컨테이너가 배포되는 private subnet을 연결하는 vpc endpoint를 생성한다.</p>
<ul>
<li>서비스: <code>execute-api</code></li>
<li><code>DNS 이름 활성화</code> 활성화<h3 id="3-rest-api-private-gateway-생성-및-lambda-연결">3. REST API Private Gateway 생성 및 Lambda 연결</h3>
</li>
</ul>
<ol>
<li><strong>REST API Private Gateway 생성</strong>
2번에서 만든 VPC Endpoint와 연결</li>
<li><strong>리소스 생성</strong></li>
<li><strong>메서드 생성 + Lambda 연결</strong>
메서드 생성 시, <code>Lambda 프록시 통합</code> 옵션을 비활성화한다.</li>
<li><strong>리소스 정책 생성</strong>
리소스 정책은 다음과 같이 설정한다.<pre><code class="language-json">{
 &quot;Version&quot;: &quot;2012-10-17&quot;,
 &quot;Statement&quot;: [
   {
     &quot;Sid&quot;: &quot;AllowAllFromThisVpce&quot;,
     &quot;Effect&quot;: &quot;Allow&quot;,
     &quot;Principal&quot;: &quot;*&quot;,
     &quot;Action&quot;: &quot;execute-api:Invoke&quot;,
     &quot;Resource&quot;: &quot;arn:aws:execute-api:{region}:{account-id}:{api-id}/*/*/*&quot;,
     &quot;Condition&quot;: {
       &quot;StringEquals&quot;: {
         &quot;aws:SourceVpce&quot;: &quot;{vpc-endpoint-id}&quot;
       }
     }
   }
 ]
}</code></pre>
<h3 id="4-spring-configuration-수정">4. Spring Configuration 수정</h3>
<code>application-cognito.yml</code><pre><code class="language-java">spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/jwks</code></pre>
</li>
</ol>
<ul>
<li><code>{api-id}</code>: api gateway id</li>
</ul>
<br>

<h2 id="실행-확인">실행 확인</h2>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/2d01530e-73ee-4b9c-b681-a7730799a45e/image.png" alt="">NAT Gateway를 제거했다. (*pingpong은 다른 팀 리소스이다.)
<img src="https://velog.velcdn.com/images/jiu-jung/post/ef1bd609-4350-41a4-a351-5ae993414d37/image.png" alt="">로그인 후 인증에 성공했다!</p>
<blockquote>
<p>NAT Gateway를 제거해도 인증 흐름이 정상 작동했다.
이렇게 <strong>API Gateway와 Lambda를 통한 api 요청 프록시</strong>에 성공해서, <strong>nat gateway로 인한 비용을 줄일 수 있었다.</strong></p>
</blockquote>
<hr>
<h1 id="5-cognito-도입-시-고려할-점">5. Cognito 도입 시 고려할 점</h1>
<p>마지막으로, Cognito를 도입하며 느낀 장단점을 정리해봤다.</p>
<table>
<thead>
<tr>
<th>장점</th>
<th>주의점</th>
</tr>
</thead>
<tbody><tr>
<td>• JWT 발급 로직을 직접 구현할 필요 없음 <br>• Lambda Trigger로 인증 로직 확장 가능 <br>• AWS 리소스들과의 연동이 쉬움</td>
<td>• 전체 인증 워크플로우를 Cognito 기준으로 재설계해야 함<br>• 사용자 정보를 자체적으로 저장하기 때문에, DB 구조를 고려해야 함<br>• 사용자 데이터 변경이 잦다면, 직접 구현이 더 효율적일 수 있음</td>
</tr>
</tbody></table>
<blockquote>
<p>정리하자면, <strong>MVP 개발</strong>에는 빠르고 강력한 인증 플랫폼으로 Cognito를 추천하지만 <strong>장기적인 유연성과 관리 비용</strong>까지 고려해 선택하시는 것을 권장한다. (개인적인 생각)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[실시간 채팅 시스템 AWS 아키텍처 설계 회고]]></title>
            <link>https://velog.io/@jiu-jung/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-AWS-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jiu-jung/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-AWS-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 29 Jun 2025 15:07:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Amazon Cloud Club Ewha 3기 사이드 프로젝트</p>
</blockquote>
<h3 id="기능-구현보다-왜-이-구조를-선택했는지를-더-많이-배운-프로젝트">기능 구현보다, 왜 이 구조를 선택했는지를 더 많이 배운 프로젝트</h3>
<p>Amazon Cloud Club Ewha 3기 사이드 프로젝트에서 실시간 채팅 시스템 인프라를 설계하고 운영하며, 아키텍처는 “정답을 한 번에 맞히는 일”이 아니라 <strong>요구사항과 제약에 따라 선택을 계속 수정해 가는 과정</strong>이라는 점을 배웠다.</p>
<p>이번 프로젝트에서 가장 크게 느낀 것은, 기술은 많이 아는 것보다 <strong>어떤 기준으로 선택하고 어떤 이유로 포기하는지</strong>가 더 중요하다는 점이었다.<br>처음에는 “AWS 서비스를 최대한 많이 써보자”는 생각으로 접근했지만, 실제 구현과 배포를 겪으면서 점차 기준이 분명해졌다.</p>
<p>내가 이 프로젝트에서 아키텍처를 판단할 때 계속 확인한 기준은 아래 세 가지였다.</p>
<ul>
<li><strong>팀이 실제로 구현 가능한가</strong></li>
<li><strong>운영 복잡도를 감당할 수 있는가</strong></li>
<li><strong>비용 대비 얻는 이점이 충분한가</strong></li>
</ul>
<p>이 글은 그 기준이 어떻게 생겼고, 실제로 어떤 선택으로 이어졌는지 순서대로 정리한 회고다.</p>
<p>아키텍처 발전 과정을 순서대로 회고한다.</p>
<h3 id="1-처음에는-가능해-보이는-구조를-그렸고-이후에는-실제로-운영-가능한-구조로-바꿨다">1. 처음에는 “가능해 보이는 구조”를 그렸고, 이후에는 “실제로 운영 가능한 구조”로 바꿨다</h3>
<p>프로젝트 초기에 가장 먼저 떠올린 구조는 Lambda 기반 서버리스 아키텍처였다.</p>
<ul>
<li>API Gateway + Lambda + DynamoDB</li>
<li>실시간 채팅은 WebSocket API</li>
<li>일반 요청은 REST API</li>
</ul>
<p>짧은 기간 안에 MVP를 빠르게 만들기에는 합리적인 선택처럼 보였다.<br>특히 인프라 운영 경험이 많지 않은 팀에게는 서버를 직접 관리하지 않아도 된다는 점이 매력적이었다.</p>
<p>하지만 팀 회의를 거치며 이 선택을 바꾸게 됐다.<br>백엔드 팀원들은 웹소켓 서버를 직접 구현해보고 싶어 했고, 이번 프로젝트는 단순히 결과물을 만드는 것뿐 아니라 <strong>구조를 직접 다뤄보는 경험</strong>도 중요했다.<br>즉, 여기서는 “관리 부담이 적은 구조”보다 <strong>팀의 학습 목표와 구현 방식에 맞는 구조</strong>가 더 중요했다.</p>
<p>이 지점에서 서버리스는 제외했고, ECS 기반 아키텍처로 방향을 바꿨다.</p>
<p>이 과정에서 배운 점은 명확했다.<br><strong>좋은 기술이냐보다, 지금 팀의 목표와 제약에 맞느냐가 먼저다.</strong></p>
<p>초기에는 AWS로 인프라를 구축해본 경험이 없어, 리소스 배치나 VPC 구조를 고려하지 않았다.</p>
<h3 id="1안-mvp">1안 (MVP)</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/b434dd96-83a1-48d0-bca5-9555d60ed154/image.jpg" alt=""></p>
<ul>
<li>짧은 기간의 프로젝트이기에 개발 부담을 줄이기 위해 서버리스 Lambda로 설계했다.</li>
<li><code>API Gateway</code> + <code>Lambda</code> + <code>Dynamodb</code> 구조</li>
<li>API Gateway는 websocket 통신을 지원한다.</li>
<li>WebSocket &amp; REST 분리 구조
실시간 채팅은 WebSocket API, 나머지는 REST API로 나누어 각각 다른 API Gateway로 연결된다.</li>
</ul>
<h3 id="2안-elasticache-도입">2안 (Elasticache 도입)</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/48e8ba88-fb7c-43e5-94b9-fae7a59990d6/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/jiu-jung/post/ee733895-8da4-4582-9499-05413b0da714/image.png" alt=""></p>
<ul>
<li>속도 개선 요구사항을 맞추기 위해 elasticache를 추가했다.</li>
<li>Lambda에서 private subnet의 elasticache를 어떻게 접근해야 하는지 몰랐다..</li>
</ul>
<h3 id="3안-cognito-도입">3안 (Cognito 도입)</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/a385f038-4d31-4dc5-8ded-122dc23eb827/image.png" alt=""></p>
<ul>
<li>사용자 인증/인가를 Cognito로 처리하는 구조.</li>
<li>사용자 정보를 어떻게 DynamoDB에 저장해야 할지에 대한 구조가 부족했다.</li>
</ul>
<hr>
<h1 id="2-아키텍처-초안-팀-회의-후">2. 아키텍처 초안 (팀 회의 후)</h1>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/e5c69421-cc0d-48fd-80b9-f459f12d1c91/image.jpg" alt=""></p>
<blockquote>
<p>팀 회의 후, <strong>ECS</strong> 기반 아키텍처로 결정했다.</p>
</blockquote>
<h3 id="개발자-요구사항으로-서버리스-제외">개발자 요구사항으로 서버리스 제외</h3>
<p>백엔드 개발자들이 직접 웹소켓 서버를 구축해보고 싶어해서 서버리스는 제외하게 되었다.</p>
<h3 id="ecs-fargate-선택-이유">ECS Fargate 선택 이유</h3>
<ul>
<li>인프라를 구성해본 경험이 없는 팀원에게 관리 부담이 적어서</li>
<li>(비용을 지원해주는 프로젝트라 가격이 더 비싸도 한번 써보고 싶어서)</li>
</ul>
<h3 id="문제점">문제점</h3>
<ul>
<li>ENI를 위한 별도의 private subnet 구성 → 실제로는 ECS에서 자동 할당되어 별도의 subnet이 필요 없다.</li>
<li>Cognito가 어떻게 애플리케이션과 상호작용하는지 고려하지 않았다.</li>
<li>Redis/SQS 가 어떻게 작동하는지 모르고 그냥 붙였다. (NAT Gateway, ALB, SQS 작동 방식 등 전반적으로 잘 몰랐다.)</li>
</ul>
<hr>
<h1 id="3-elasticache-선택-논의">3. Elasticache 선택 논의</h1>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d33b406e-fade-4d23-a8bb-e094b77e33bd/image.jpg" alt=""></p>
<p>아키텍처를 개선하는 과정에서 <strong>Redis 사용 여부</strong>에 대해 백엔드와 논의해 다음 3가지 옵션을 비교했다.</p>
<h4 id="1-elasticache--직접-pubsub-구현">1. ElastiCache + 직접 pub/sub 구현</h4>
<p>구현 난이도 높음. 백엔드 파트에서 불가능하다고 판단.</p>
<h4 id="2-spring의-simple-broker-사용">2. Spring의 Simple Broker 사용</h4>
<p>구현 간단하지만, multi-AZ 미지원 (고가용성 X)</p>
<h4 id="3-외부-브로커amazon-mq">3. 외부 브로커(Amazon mq)</h4>
<p>구현 단순, 비용 증가</p>
<blockquote>
<p><code>경험 부족</code> + <code>짧은 개발 기간</code> + <code>비용 지원</code> → *<em>Amazon MQ *</em> 선택</p>
</blockquote>
<hr>
<h1 id="4-최종-아키텍처과제-제출용">4. 최종 아키텍처(과제 제출용)</h1>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/25f1330a-cca6-4b78-9d09-2db6837ba9dd/image.jpg" alt=""></p>
<ul>
<li><p>실제 aws 콘솔에서 설정은 해보지 않은 상태에서 완성한 아키텍처이다. (현실적인 문제를 겪지 않았다.)</p>
</li>
<li><p>ALB와 Cognito를 연동하여 인증된 요청만 백엔드에 전달하도록 설계했다. (프론트가 백엔드 레포지토리에 포함되어 배포된다고 생각해서..) 이를 위해서는 Cognito의 callback URI를 ALB 도메인으로 설정해야 했다.</p>
</li>
<li><p>하지만 실제로는 프론트엔드가 별도로 배포되기 때문에 callback URI를 프론트 도메인으로 지정해야 하는 충돌이 발생하게 된다...</p>
</li>
</ul>
<hr>
<h1 id="5-최종-아키텍처발표용">5. 최종 아키텍처(발표용)</h1>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/ce35ddac-0258-4401-8820-63fa3e1d2ec5/image.jpg" alt="">
실제 구현을 해보며 실현 가능한 아키텍처가 되었다.</p>
<ul>
<li><p>위에 적힌 이유로 Cognito + ALB 연동하지 않고, Cognito는 프론트와 소통하는 방식으로 바꿨다.</p>
</li>
<li><p>그러자, 백엔드 Spring Security에서 JWT 검증 시 외부 API 호출(JWKS) 필요.. → NAT Gateway 추가</p>
</li>
</ul>
<h3 id="cicd-파이프라인-구축">CI/CD 파이프라인 구축</h3>
<ul>
<li>Dockerfile에서 환경 변수 관리의 중요성 느낌.</li>
<li>테스트용 변수도 환경 변수화하면 좋을듯.</li>
<li>인프라-백엔드 소통 필요성<ul>
<li>VPC endpoint로 Amazon MQ에 연결했으나, MQ ARN 전달 실수로 NAT Gateway 제거 시 연결 실패..</li>
</ul>
</li>
</ul>
<h3 id="배포-후-이슈">배포 후 이슈</h3>
<ul>
<li>Access Token 내 이메일 누락 → Cognito <code>Pre-sign-up Lambda 트리거</code> 추가</li>
<li>사용자 정보 저장→ <code>Post-confirmation Lambda 트리거</code>로 DynamoDB 연동
단, AWS 콘솔에서 사용자 수정 시 반영되지 않는 구조적 한계 있음 (MVP 단계에서는 수용 가능..)</li>
</ul>
<hr>
<h1 id="6-nat-gateway-대체-성공">6. NAT Gateway 대체 성공</h1>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/2094fa29-2608-4a65-82f5-de6ed08aae2f/image.jpg" alt=""></p>
<h3 id="lambda--private-api-gateway로-외부-jwk-api-호출-대체">Lambda + Private API Gateway로 외부 JWK API 호출 대체</h3>
<ul>
<li>사실 원래 이 방식으로 설계했으나, API Gateway 정책 오류로 급히 NAT Gateway 붙이게 된 것임.</li>
<li>API Gateway 정책과 Spring Security 설정(<code>jwk-set-uri</code>)을 수정하여** NAT Gateway 대체 성공!**</li>
</ul>
<h3 id="amazon-mq-연결-이슈">Amazon MQ 연결 이슈</h3>
<ul>
<li>Amazon MQ는 VPC Endpoint로 연결돼 있었으나, 백엔드에 MQ ARN만 전달되어 있어 NAT 제거 시 접근 불가</li>
<li><strong>환경 변수/시크릿 관리의 중요성</strong> 깨달음 → AWS Parameter Store나 Secrets Manager 사용해보고 싶음. Terraform과 같은 IaC로 관리해도 같은 실수는 발생하지 않을 것 같음.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Capstone] ControlNet - Inpaint으로 특정 부위 텍스처링 자동화하기]]></title>
            <link>https://velog.io/@jiu-jung/Capstone-ControlNet-Inpaint%EC%9C%BC%EB%A1%9C-%ED%8A%B9%EC%A0%95-%EB%B6%80%EC%9C%84-%ED%85%8D%EC%8A%A4%EC%B2%98%EB%A7%81-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jiu-jung/Capstone-ControlNet-Inpaint%EC%9C%BC%EB%A1%9C-%ED%8A%B9%EC%A0%95-%EB%B6%80%EC%9C%84-%ED%85%8D%EC%8A%A4%EC%B2%98%EB%A7%81-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 17 May 2025 06:29:05 GMT</pubDate>
            <description><![CDATA[<p>캡스톤디자인과창업프로젝트B | 2025 Spring</p>
<blockquote>
<p>우리 연구 <strong><em>Lightweight Part-Aware 3D Texture Editing via 2D-Based Methods</em></strong>의 목표는 2D 기반 파트 인식 기법을 활용해 3D 오브젝트의 특정 부위를 편집할 수 있는 경량화 텍스처링 파이프라인을 구현하는 것이다.
파이프라인 중 핵심 단계인 2D 텍스처 생성을 위해 어떤 모델이 적합할지 검토한 결과, 조건 기반 이미지 생성에 특화된 <strong>ControlNet-Inpaint</strong>를 실험 대상으로 선정했다.
이 글에서는 WebUI 환경에서 ControlNet-Inpaint를 활용해 실제로 특정 부위를 텍스처링할 수 있는지를 확인한 실험 과정을 담았고, 이어서 파이프라인 통합을 위해 해당 기능을 CLI 기반 스크립트로 구현한 과정까지 정리했다.</p>
</blockquote>
<hr>
<h2 id="🌐-controlnet-adding-conditional-control-to-text-to-image-diffusion-models">🌐 ControlNet: Adding Conditional Control to Text-to-Image Diffusion Models</h2>
<p><a href="https://github.com/lllyasviel/ControlNet">🔗 GitHub</a> | <a href="https://arxiv.org/abs/2302.05543">📄 arXiv</a></p>
<blockquote>
<p>기존 <strong>Text-to-Image</strong> 모델인 Stable Diffusion에
스케치, 윤곽선 등 <strong>시각적 가이드</strong>를 추가해
원하는 형태나 구도를 더 <strong>정밀하게 제어</strong>할 수 있도록 확장한 모델.</p>
</blockquote>
<ul>
<li>Stable Diffusion에 <strong>외부 조건(condition)</strong>을 명시적으로 부여해 이미지 생성 과정을 세밀하게 조정할 수 있도록 만든 확장 네트워크 구조</li>
<li>기존에는 텍스트 프롬프트만으로 이미지를 생성했다면, ControlNet은 다음과 같은 low-level vision 정보를 함께 활영하여 결과물의 형태, 구성, 레이아웃을 더 정확하게 제어한다.<ul>
<li>윤곽선 (Edge)</li>
<li>Depth Map</li>
<li>Pose Estimation</li>
<li>Segmentation</li>
</ul>
</li>
</ul>
<h3 id="controlnet-아키텍처">ControlNet 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/fd618ff7-d4bb-4ce9-a460-90dbc9ea857d/image.png" alt=""></p>
<ul>
<li><strong>Base 모델</strong>: Stable Diffusion 기반 U-Net 아키텍처</li>
<li><strong>Control branch</strong>: 각 U-Net 블록에 연결된 side branch로, 입력 condition에서 추출된 feature 전달</li>
<li><strong>Zero Convolution</strong>: 초기 가중치를 0으로 설정해 pretrained weight를 손상시키지 않음</li>
</ul>
<p>학습 시 base 모델의 weight는 freeze하고 <strong>control branch만 fine-tuning</strong>하여 학습 효율이 높고, 기존 pretrained 모델의 weight를 손상시키지 않아 성능을 유지하면서도 새로운 조건 입력에 유연하게 반응할 수 있다.</p>
<h3 id="controlnet-inpaint">ControlNet-Inpaint</h3>
<ul>
<li>특정 부위를 복원하거나 새롭게 생성하는 데 특화된 Inpainting 전용 모델</li>
<li>텍스트 프롬프트 외에도 <code>원본 이미지</code> + <code>마스크 정보</code>를 활용하여 특정 영역만 편집 가능</li>
<li><strong>특징</strong><ul>
<li><strong>마스크 기반</strong>으로 특정 부위만 변경 가능</li>
<li>원본 구조 유지 및 context-aware 생성</li>
<li>프롬프트로 스타일·질감·객체 제어 가능</li>
<li>Part-aware 편집에 최적화</li>
</ul>
</li>
</ul>
<h3 id="💡-우리-파이프라인에-controlnet-inpaint가-필요한-이유">💡 우리 파이프라인에 ControlNet-Inpaint가 필요한 이유</h3>
<ul>
<li>텍스트 + 이미지 + 마스크 조합으로 <strong>원하는 부위만 정확하게 텍스처링</strong> 가능</li>
<li>WebUI 실험을 통해 <strong>성능 검증</strong> 완료</li>
<li>빠른 속도 (1장당 5~10초)와 낮은 VRAM 요구사항 (8GB)으로 <strong>경량화</strong> 목표와 일치</li>
</ul>
<br>

<hr>
<h2 id="💻-web-ui로-controlnet-inpaint-실행하기">💻 Web UI로 ControlNet-Inpaint 실행하기</h2>
<blockquote>
<p>참고</p>
</blockquote>
<ul>
<li><a href="https://github.com/Mikubill/sd-webui-controlnet/discussions/1143">Guidelines for using inpaint in A1111</a></li>
<li><a href="https://github.com/Mikubill/sd-webui-controlnet">sd-webui-controlnet</a></li>
<li><a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">stable-diffusion-webui</a></li>
</ul>
<h3 id="0-실행-환경">0. 실행 환경</h3>
<ul>
<li>GPU: RTX 3070 Ti Laptop</li>
<li>OS: Windows 11</li>
</ul>
<h3 id="1-stable-diffusion-web-ui-설치">1. Stable Diffusion Web UI 설치</h3>
<ol>
<li><a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre"><code>sd.webui.zip</code></a> 다운로드 후 압축 해제</li>
<li><code>update.bat</code> 실행해 최신 버전으로 업데이트</li>
<li><code>run.bat</code> 실행 후, 브라우저에서 <a href="http://127.0.0.1:7860">http://127.0.0.1:7860</a> 접속</li>
</ol>
<h3 id="2-controlnet-확장-설치">2. ControlNet 확장 설치</h3>
<ol>
<li><code>Extensions</code> → <code>Install from URL</code></li>
<li>URL 입력 (<a href="https://github.com/Mikubill/sd-webui-controlnet.git">https://github.com/Mikubill/sd-webui-controlnet.git</a>) → <code>Install</code>
 <img src="https://velog.velcdn.com/images/jiu-jung/post/cb36eef5-1e38-4014-b469-0f3098976a11/image.png" alt="">
 설치 완료 메시지 확인</li>
<li><code>Installed</code> &gt; <code>Check for updates</code> &gt; <code>Apply and restart UI</code> 클릭</li>
<li>터미널 포함 전체 재시작</li>
</ol>
<h3 id="3-controlnet-inpaint-모델-다운로드">3. ControlNet-Inpaint 모델 다운로드</h3>
<ul>
<li><a href="https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/blob/main/control_v11p_sd15_inpaint_fp16.safetensors">📥 Safetensors 다운로드</a></li>
<li>저장 경로: <code>sd.webui\webui\extensions\sd-webui-controlnet\models</code></li>
</ul>
<h3 id="4-inpaint-실행">4. Inpaint 실행</h3>
<ol>
<li><p><code>img2img</code> &gt; <code>Generation</code> &gt; <code>Inpaint Upload</code><br><img src="https://velog.velcdn.com/images/jiu-jung/post/2141d6bf-a90d-4b90-ad6e-118a55dd28e1/image.png" alt=""></p>
</li>
<li><p>원본 이미지, 마스크 이미지 업로드
<img src="https://velog.velcdn.com/images/jiu-jung/post/6af58ea8-7d9e-4b94-b120-b12a8b2d44ff/image.png" alt=""></p>
</li>
<li><p>ControlNet 활성화
스크롤을 내려보면 ControlNet 패널이 보인다. 확장한 뒤 Enable을 눌러서 활성화해주고, Preprocessor와 Model을 설정해준다.
 <img src="https://velog.velcdn.com/images/jiu-jung/post/0792ef78-a178-404c-afbc-5f03bae26fc7/image.png" alt=""></p>
</li>
</ol>
<ol start="4">
<li><p>텍스트 프롬프트 입력 후 Generate 클릭
<img src="https://velog.velcdn.com/images/jiu-jung/post/19dc87d9-4477-4c54-96c2-30d881317df4/image.png" alt=""></p>
</li>
<li><p>실행결과
<img src="https://velog.velcdn.com/images/jiu-jung/post/4a4c2d9b-4a76-4d68-9b2e-d68df857dc3b/image.png" alt="">
이미지와 텍스트 조합을 통한 특정 부위 수정 
<img src="https://velog.velcdn.com/images/jiu-jung/post/6fd27547-8820-4c0b-b294-0838db03ace6/image.png" alt="">
생성 로그는 터미널에서 확인 가능</p>
</li>
</ol>
<hr>
<h2 id="🛠️-cli-기반-controlnet-inpaint-스크립트-구현">🛠️ CLI 기반 ControlNet-Inpaint 스크립트 구현</h2>
<p>WebUI는 실험에는 적합하지만, 반복 실험과 파이프라인 통합에는 비효율적이다.
→ 따라서 CLI 방식으로 ControlNet-Inpaint를 실행할 수 있는 스크립트를 별도로 구현했다.</p>
<h3 id="환경-설정">환경 설정</h3>
<ul>
<li>OS: Ubuntu 22.04</li>
<li>Python: 3.10</li>
<li>GPU: RTX Titan</li>
<li>Cuda: 11.8</li>
<li>Pytorch: 2.1.0</li>
</ul>
<pre><code># conda 환경 생성
conda create -n controlnet python=3.10 -y
conda activate controlnet

# PyTorch 2.1.0 + CUDA 11.8
conda install pytorch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 pytorch-cuda=11.8 -c pytorch -c nvidia

pip install --upgrade diffusers transformers accelerate safetensors \
             pillow opencv-python tqdm

pip install xformers==0.0.25 triton==2.2.0</code></pre><h3 id="프로젝트-구조">프로젝트 구조</h3>
<pre><code class="language-java">controlnet-inpaint-cli/
├── assets/
│   ├── init.png          # 원본 이미지
│   └── mask.png          # 마스크 이미지, 흰색 수정
├── outputs/
│   └── result.png        # 결과물 저장 폴더
├── controlnet_inpaint_cli.py
└── README.md
</code></pre>
<h3 id="스크립트">스크립트</h3>
<p><code>controlnet_inpaint_cli.py</code></p>
<pre><code class="language-python">#!/usr/bin/env python
import argparse, torch
from diffusers import ControlNetModel, StableDiffusionControlNetInpaintPipeline
from diffusers.utils import load_image

# === 내부 고정 파라미터 ===
NUM_STEPS      = 40      # 샘플링 횟수
GUIDANCE_SCALE = 7.5     # CFG
COND_SCALE     = 1.0     # ControlNet 영향력
BASE_MODEL     = &quot;runwayml/stable-diffusion-v1-5&quot;
CN_MODEL       = &quot;lllyasviel/control_v11p_sd15_inpaint&quot;

def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument(&quot;--prompt&quot;, required=True, help=&quot;텍스트 프롬프트&quot;)
    p.add_argument(&quot;--image&quot;,  required=True, help=&quot;원본 이미지 경로&quot;)
    p.add_argument(&quot;--mask&quot;,   required=True, help=&quot;마스크 이미지 경로&quot;)
    p.add_argument(&quot;--out&quot;,    required=True, help=&quot;결과 이미지 경로&quot;)
    return p.parse_args()

def main():
    args   = parse_args()
    device = &quot;cuda&quot; if torch.cuda.is_available() else &quot;cpu&quot;
    dtype  = torch.float16 if device == &quot;cuda&quot; else torch.float32

    # 1) 모델 로드
    controlnet = ControlNetModel.from_pretrained(CN_MODEL, torch_dtype=dtype)
    pipe = StableDiffusionControlNetInpaintPipeline.from_pretrained(
        BASE_MODEL, controlnet=controlnet, safety_checker=None, torch_dtype=dtype
    ).to(device)
    pipe.enable_model_cpu_offload()
    try: pipe.enable_xformers_memory_efficient_attention()
    except: pass

    # 2) 이미지 로드
    init_img = load_image(args.image).convert(&quot;RGB&quot;)
    mask_img = load_image(args.mask).convert(&quot;L&quot;)

    # 3) 추론
    result = pipe(
        prompt=args.prompt,
        image=init_img,
        mask_image=mask_img,
        num_inference_steps=NUM_STEPS,
        guidance_scale=GUIDANCE_SCALE,
        controlnet_conditioning_scale=COND_SCALE,
    ).images[0]

    result.save(args.out)
    print(f&quot;Saved → {args.out}&quot;)

if __name__ == &quot;__main__&quot;:
    main()</code></pre>
<ul>
<li><code>ControlNetModel</code> + <code>StableDiffusionControlNetInpaintPipeline</code> 조합.</li>
<li><code>pipe.enable_model_cpu_offload()</code> → 8 GB VRAM에서도 동작.</li>
<li><code>--cond-scale (0 ~ 2)</code>로 ControlNet 영향력 조정.</li>
</ul>
<h3 id="실행방법">실행방법</h3>
<pre><code class="language-bash"># conda 환경 활성화 후 프로젝트 루트에서
python controlnet_inpaint_cli.py \
  --prompt &quot;a chair with white chair legs&quot; \
  --image  assets/init.png \
  --mask   assets/mask.png \
  --out    outputs/tiger_inpaint.png \</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 채팅 시스템 설계 - 1주차]]></title>
            <link>https://velog.io/@jiu-jung/AWS-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jiu-jung/AWS-%EC%B1%84%ED%8C%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Tue, 13 May 2025 05:51:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Amazon Cloud Club Ewha 3기 사이드 프로젝트</p>
</blockquote>
<p><strong><code>Infra</code></strong> 파트를 맡았다.
이번주는 채팅 시스템 위한 Websocket 스터디, 유저 시나리오 작성, 아키텍처 초안 작성을 진행했다.</p>
<hr>
<h1 id="0-주제-및-요구사항">0. 주제 및 요구사항</h1>
<p><strong>주제</strong>
<code>채팅 시스템 설계</code></p>
<p><strong>요구사항</strong></p>
<ul>
<li>보안적으로 안전하도록 구성한다.</li>
<li>캐시(ElastiCache) 사용 권장</li>
<li>가용성 및 안정성을 높인다.</li>
<li>AWS의 서비스들을 최대한 활용하여 운영비용을 절감한다.</li>
<li>$40 ~ $60 사이의 비용을 사용한다.</li>
<li>최소한의 기능을 완성도 있게 구현한다.</li>
</ul>
<p><strong>인프라 요구사항</strong></p>
<ul>
<li>평균적으로 약 <code>1000명</code>의 유저가 해당 서비스를 사용<ul>
<li>최대 동시 접속 사용자 수 <code>500명</code> 예상</li>
</ul>
</li>
<li>초당 약 <code>50건</code>의 메시지 전송 처리 필요</li>
<li>최대 <code>6개월</code>간 메시지 내역 보관 필요</li>
<li>메시지 전송 지연 시간은 <code>2초</code> 이내</li>
</ul>
<p><strong>심화 요구사항</strong></p>
<ul>
<li>그룹 채팅 지원: 최대 <code>100명</code>까지 참여 가능한 그룹 채팅방 제공</li>
<li>첨부 파일: 채팅에서 최대 <code>10MB</code> 크기의 파일 첨부 기능 지원</li>
<li>오프라인 메시지 처리: 사용자가 오프라인 상태일 때 메시지 저장 및 재접속 시 동기화</li>
<li>멀티 디바이스 지원: 동일 계정으로 최대 <code>3개 기기</code> 동시 접속 지원 (동일 계정 접속 수 제한)</li>
</ul>
<hr>
<h1 id="1-websocket-스터디">1. Websocket 스터디</h1>
<p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-%EC%9B%B9-%EC%86%8C%EC%BC%93-Socket-%EC%97%AD%EC%82%AC%EB%B6%80%ED%84%B0-%EC%A0%95%EB%A6%AC">inpa.tistory.com</a></p>
<h3 id="필요성">필요성</h3>
<ul>
<li>HTTP는 클라이언트의 요청이 있어야 서버가 응답 → 새로운 정보를 얻기 위해 항상 새로운 URL 요청<ul>
<li>Polling 방식으로 채팅 구현할 수 있지만, 단방향 통신의 한계</li>
</ul>
</li>
<li>웹이 발전하며 더 동적인 표현과 상호작용이 필요해짐</li>
<li>“실시간 양방향 통신” 필요</li>
</ul>
<hr>
<h3 id="웹소켓이란">웹소켓이란?</h3>
<ul>
<li>HTML5 표준 기술</li>
<li>클라이언트(브라우저)와 서버 사이 동적인 양방향 채널 구성</li>
<li><code>Websocket API</code> 로 사용</li>
<li>최초 연결은 HTTP로 연결, 그 후로 지속적인 양방향 통신 가능</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/32cb2c99-d9e8-4738-80b0-186e4eb61ffe/image.png" alt=""></p>
<hr>
<h3 id="aws-websocket-api">AWS Websocket API</h3>
<p><a href="https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/apigateway-websocket-api-overview.html">API Gateway의 WebSocket API 개요 - Amazon API Gateway</a></p>
<p><a href="https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/websocket-api-chat-app.html">자습서: WebSocket API, Lambda 및 DynamoDB를 사용하여 WebSocket 채팅 앱 생성 - Amazon API Gateway</a></p>
<p><a href="https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/websocket-api-step-functions-tutorial.html">자습서: AWS 통합을 통해 WebSocket API 생성 - Amazon API Gateway</a></p>
<blockquote>
<p>WebSocket API, Lambda 및 DynamoDB를 사용하여 AWS 서비스만을 통해 서버리스 WebSocket 채팅을 구현할 수 있다.</p>
</blockquote>
<hr>
<h1 id="2-유저-시나리오">2. 유저 시나리오</h1>
<p>이때는 프론트엔드가 없었기에, 백엔드 관점에서 작성했다.</p>
<pre><code>회원가입 및 로그인 - REST API
- 사용자 A는 이름, 이메일, 비밀번호 등 최소한의 정보를 입력하여 회원가입을 진행한다.
- 클라이언트는 사용자 정보를 `POST /signup` API로 서버에 전송하고, 서버는 이를 저장 후 성공 응답을 반환한다.
- 이후, A는 로그인 화면에서 이메일과 비밀번호를 입력하고 `POST /login` API를 통해 인증 요청을 보낸다. 이때 비밀번호는 해싱하여
- 서버는 인증이 성공하면 JWT 토큰 또는 세션 토큰을 발급하며, 클라이언트는 이를 저장해 이후 요청에 인증 헤더로 포함한다.
- 로그인 후 사용자 A는 인증 토큰을 기반으로 WebSocket 연결을 시작하거나, REST API를 통해 채팅 이력을 조회할 수 있다.

1:1 채팅 - WebSocket

- 사용자 A는 서버와 WebSocket 연결을 수립한다.
- 사용자 B도 별도로 WebSocket 연결을 수립해 있는 상태다.
- A가 메시지를 작성하고 WebSocket을 통해 메시지를 전송한다.
- 서버는 메시지를 저장한 뒤, B의 WebSocket 세션을 확인해 2초 이내에 메시지를 푸시(PUSH)한다.
- B가 실시간 응답을 보내면 동일한 흐름으로 A에게 전송된다.

그룹 채팅 - WebSocket

- 사용자 A는 WebSocket을 통해 그룹 채팅방을 만든다.
- 그룹 생성 후, A가 메세지를 전송한다.
- 서버는 해당 메시지를 저장한 뒤, 그룹 참가자(B, C)의 연결된 WebSocket 세션을 찾아 동시에 브로드캐스팅한다.
- 각 수신자는 동일한 시간 내 (2초 이내)에 메시지를 수신한다.

메시지 이력 조회 - REST API

- 사용자 A가 채팅방에서 이전 대화를 확인하고자 할 때, `GET /chat/history?chat_type=group&amp;chat_id=g123&amp;limit=50` 형태의 API를 호출한다.
- 서버는 캐시 저장소 또는 백업된 저장소(DB)에서 해당 채팅방 ID에 대응하는 메시지를 역순으로 페이징 조회한다.
- 클라이언트는 받은 메시지를 타임스탬프 기준으로 정렬해 사용자에게 출력한다.
- 이 API는 WebSocket 세션이 종료된 상태에서도 호출 가능하며, 과거 6개월 이내 데이터가 반환된다.

네트워크 불안정 대응

- 사용자 A가 메시지를 보내는 도중 연결이 끊기면, WebSocket 클라이언트는 자동으로 재연결을 시도한다.
- 재연결 후, 미확인 메시지를 다시 서버에 전송한다 (`message_id` 포함).
- 서버는 `message_id` 또는 타임스탬프를 기준으로 중복 메시지를 식별하고, 중복 없이 저장 및 전송한다.
- 메시지는 수신자에게 정상적으로 도착한다.</code></pre><hr>
<h2 id="최종-유저-시나리오">최종 유저 시나리오</h2>
<p>팀원들이 작성해준 최종 유저 시나리오는 다음과 같다.</p>
<pre><code>1. 회원가입
    - 사용자는 이메일, pw로 회원가입

2. 로그인/로그아웃
    - 사용자는 이메일, pw로 로그인
    - 사용자는 이메일, pw로 로그아웃

3. 일대일 채팅
    - 사용자는 프로필 목록에서 채팅할 상대를 선택
    - 혹은 채팅방 목록에서 채팅방을 선택
    - 실시간으로 메시지 전송 및 수신
    - 읽음 확인 및 타이핑 중 표시 기능 제공

4. 다대다 채팅
    - 사용자는 여러 참가자를 초대하여 그룹 채팅방 생성
    - 혹은 채팅방 목록에서 채팅방을 선택
    - 모든 참가자는 참가자 추가/제거 가능
    - 모든 참가자는 메시지를 보내고 받을 수 있음

5. 이전 메세지 조회
    - 채팅방 입장 후, 해당 채팅방의 이전 메세지들 확인 가능
    - (추가 기능) 이미지/파일 첨부 및 공유기능
    - 이전 메세지 검색기능

6. (추가 기능) 브라우저 푸시 메세지
    - 브라우저에 접속해 있을 때 채팅 송신 시 푸시 메세지 팝업

7. (추가 기능) 파일 전송/조회
    - 사용자는 일대일 채팅으로 파일을 전송할 수 있음
    - 사용자는 다대다 채팅으로 파일을 전송할 수 있음</code></pre><hr>
<h1 id="3-아키텍처-초안">3. 아키텍처 초안</h1>
<p>aws 서비스를 최대한 활용하고, 비용을 줄이기 위해 Lambda를 이용한 심플한 아키텍처를 설계했다.</p>
<h3 id="최소-아키텍처">최소 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/1b9e26d2-466b-4e46-aa54-0a370f206ed9/image.jpg" alt="">
가장 간단한 아키텍처이다. API gateway, Lambda, DynamoDB만을 이용하여 채팅을 구현한다.</p>
<hr>
<h3 id="캐싱-추가">캐싱 추가</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/74fc78cf-6b31-4c32-8b73-b8b5f74f8a2f/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/3b24aa62-82f9-404a-8f46-0a1fb1e1dc08/image.png" alt="">
ElastiCache를 추가했다. ElastiCache를 사용하기 위해 VPC의 private subnet 안에 배치했지만, 서버리스 아키텍처에 안어울린다고 생각했다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d008ef3a-a510-4847-aed2-2950653b89d8/image.png" alt="">
<a href="https://aws.amazon.com/ko/blogs/tech/amazon-elasticache-serverless-operation/">aws.amazon.com</a>
찾아보니 이렇게 서버리스로도 사용할 수 있다고 한다.</p>
<hr>
<h3 id="cognito-추가">Cognito 추가</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/c0c48ba8-7bf2-41b8-ace0-eb106ae480c9/image.png" alt="">
Cognito를 사용하면 jwt를 사용하지 않아도 되는줄 알고 잘못 그린 것같다.</p>
<hr>
<blockquote>
<p>회의 결과, 백엔드 파트가 직접 서버를 구성하고 WebSocket을 구현해보고 싶어해서 함수단위로 실행하는 Lambda는 사용하지 않기로 했다. 
대안은 EC2, ECS등이 있다. ECS Fargate의 경우 서버리스지만, 컨테이너 기반이라 스프링부트 앱을 컨테이너화해서 그대로 올리면 백엔드 입장에선 달라지는게 없는 것 같아서 괜찮은 대안이다.</p>
</blockquote>
<hr>
<h2 id="최종-아키텍처-초안">최종 아키텍처 초안</h2>
<p><code>draw.io</code>에서 aws 2025를 추가하면 쉽게 그릴 수 있다.
<img src="https://velog.velcdn.com/images/jiu-jung/post/5c2f249e-5e0e-43e5-b82e-b419e4ea200b/image.png" alt=""></p>
<h3 id="네트워크-구성">네트워크 구성</h3>
<ul>
<li><strong>ALB</strong>
  HTTPS · WebSocket 트래픽을 Fargate 컨테이너로 분산</li>
<li><strong>CloudFront</strong>
  CDN 캐시 배포</li>
<li><strong>Route53</strong>
  도메인 관리, DNS 라우팅</li>
<li><strong>NAT Gateway</strong>(필요 시)
  프라이빗 서브넷의 Fargate가 외부와 통신</li>
</ul>
<h3 id="컨테이너-실행-및-배포">컨테이너 실행 및 배포</h3>
<ul>
<li><strong>ECS</strong>
  컨테이너 오케스트레이션(서비스, 배포, 헬스체크)</li>
<li><strong>Fargate</strong>
  Spring Boot 채팅 컨테이너를 서버리스로 실행</li>
<li><strong>ECR</strong>
  백엔드 도커 이미지 저장소</li>
</ul>
<h3 id="인증보안">인증/보안</h3>
<ul>
<li><strong>IAM</strong>
  ECS/CI/CD/Secrets 권한 제어</li>
<li><strong>Secrets Manager</strong>
  애플리케이션 설정·DB 자격증명·JWT 서명 키 같은 비밀(Secrets), 파라미터 중앙 관리</li>
<li><strong>Cognito</strong>
  사용자 인증 및 JWT 발급
  *클라이언트가 인터넷에서 직접 호출, 백엔드는 JWT 검증만 수행 (인터넷 X)</li>
</ul>
<h3 id="데이터-저장전송">데이터 저장/전송</h3>
<ul>
<li><strong>DynamoDB</strong>
  Message, RoomMeta, User(TTL 6개월) 저장</li>
<li><strong>SQS(or MQ)</strong>
  DynamoDB 비동기 쓰기 대기열</li>
<li><strong>ElastiCache for Redis</strong>
  실시간 메시지 fan‑out, WebSocket 세션 관리</li>
<li><strong>S3</strong>
  정적 파일, 프론트 자산 저장소</li>
</ul>
<blockquote>
<p><strong>메세지 쓰기 시나리오</strong>
<strong>1. 클라이언트로 메시지 전송 (실시간)</strong>
서버는 먼저 <code>ElastiCache</code> Redis Stream에 메시지를 XADD하여 WebSocket 구독 클라이언트에게 바로 <strong>fan-out</strong>.
<strong>2. 비동기 저장 처리</strong>
동시에 메시지를 <code>Amazon SQS</code>에 큐잉 → 백그라운드에서 스프링부트 서버가 읽어 <code>DynamoDB</code>에 영구 저장.
메시지 유실 방지를 위해 XADD 성공 후 SQS 전송까지 완료된 후에만 ACK 처리.</p>
</blockquote>
<h3 id="로깅모니터링">로깅/모니터링</h3>
<ul>
<li><strong>CloudWatch</strong>
  로그 수집·메트릭·알람</li>
</ul>
<hr>
<h1 id="-1">-1</h1>
<p>더 공부해야 할 주제</p>
<ul>
<li>ECS Fargate를 선택한 이유</li>
<li>ECS Fargate는 서버리스인데, private subnet에 tasks가 생성되어서 어떻게 작동하는지</li>
<li>ElastiCache와 ECS Fargate Task는 동일 vpc에 있으니까 nat gateway가 필요 없는지</li>
<li>cognito, cloudfront, route53는 언제 어떻게 구성해야 할지</li>
<li>현재의 multi-az 구조가 필요한지</li>
<li>cloud watch 사용법</li>
<li>sqs/mq를 추가할지 말지, 어떻게 dynamodb와 연결하는지</li>
<li>비용 계산</li>
</ul>
<p>적다보니 너무 많다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] TIL 2]]></title>
            <link>https://velog.io/@jiu-jung/HTTP-TIL-2</link>
            <guid>https://velog.io/@jiu-jung/HTTP-TIL-2</guid>
            <pubDate>Mon, 14 Apr 2025 04:55:46 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">1부 HTTP: 웹의 기초
├── 1장 HTTP 개관
│   ├── 1.6 TCP 커넥션  
│   ├── 1.7 프로토콜 버전  
│   ├── 1.8 웹의 구성요소  
│   ├── 1.9 시작의 끝  
│   └── 1.10 추가 정보  
│
├── 2장 URL과 리소스
│   ├── 2.1 인터넷의 리소스 탐색하기
│   ├── 2.2 URL 문법</code></pre>
<h3 id="16-tcp-커넥션">1.6 TCP 커넥션</h3>
<p>&quot;HTTP는 <strong>애플리케이션 계층 프로토콜</strong>이다. HTTP는 <strong>네트워크 통신의 핵심적인 세부사항에 대해서 신경 쓰지 않는다</strong>. 대신 대중적이고 신뢰성 있는 인터넷 전송 프로토콜인 TCP/IP 에게 맡긴다.&quot;</p>
<h4 id="tcp-connection-identifier">TCP Connection Identifier</h4>
<ul>
<li>IP 주소: 서버의 위치를 나타내는 네트워크 주소</li>
<li>포트 번호: 해당 서버 내에서 실행 중인 애플리케이션을 식별 (HTTP: 80, HTTPS: 443)</li>
</ul>
<h4 id="telnet-netcatnc">Telnet, NetCat(nc)</h4>
<p>HTTP 요청을 직접 테스트하거나 커넥션을 점검할 때 유용한 도구</p>
<h3 id="18-웹의-구성요소">1.8 웹의 구성요소</h3>
<h4 id="프록시">프록시</h4>
<p>클라이언트-서버 중개자</p>
<blockquote>
<ul>
<li><strong>포워드 프록시</strong>
클라이언트(Front-end)에게 미리 저장해둔 정적 데이터 반환</li>
</ul>
</blockquote>
<ul>
<li><strong>리버스 프록시</strong>
다수의 서버를 프록시 서버 하단부에 배치, 조건에 맞는 요청을 적절한 서버에게 전달(분산)
<img src="https://velog.velcdn.com/images/jiu-jung/post/1af91d07-7192-497b-b034-fa4688c835e8/image.png" alt="">
<a href="https://jcdgods.tistory.com/322">jcdgods.tistory.com</a> </li>
</ul>
<p>항목 | Forward Proxy | Reverse Proxy
|-|-|-|
대리하는 쪽 | 클라이언트 (사용자) | 서버 (서비스 제공자)
주 사용 목적 | - 사용자의 익명성 보호<br>- 방화벽 우회<br>- 접근 제어<br>- 캐싱 | - 서버 IP 숨김<br>- 로드 밸런싱<br>- SSL 종료<br>- 캐싱
접속 대상 | 여러 외부 서버 (인터넷 등) | 여러 내부 서버 (서비스 서버 등)
요청 흐름 | 사용자가 프록시에 요청 → 프록시가 외부 서버에 요청 | 사용자가 프록시에 요청 → 프록시가 내부 서버에 전달
사용 예시 | - 회사에서 사내 직원들이 인터넷 접속<br>- VPN, Tor<br>- 웹 필터링 | - Nginx를 웹서버 앞에 둠<br>- AWS CloudFront<br>- Netlify, Vercel 같은 플랫폼
IP 숨김 대상 | 사용자의 IP 주소 | 서버의 IP 주소
클라이언트 입장 | 실제 서버를 모름 (프록시 통해서만 접속) | 실제 서버가 어디 있는지 모름 (프록시 주소로만 접속)</p>
<h4 id="캐시">캐시</h4>
<p>일종의 프락시 서버
HTTP 헤더의 <code>Cache-Control</code>, <code>ETag</code>, <code>Last-Modified</code> 등을 통해 동작</p>
<blockquote>
<p>프락시 = 캐시 라고 알고 있었는데, 캐시는 캐싱 기능을 하는 일종의 프락시였다. 프락시는 캐싱외에도 보안, 로드발란싱, 접근 제어 등으로 사용된다.</p>
</blockquote>
<h4 id="게이트웨이">게이트웨이</h4>
<p>서로 다른 프로토콜/네트워크와 HTTP 사이를 중개하는 서버
클라이언트 입장에서는 일반 HTTP 서버처럼 보임
e.g., NAT 게이트웨이, 인터넷 게이트웨이(IGW)</p>
<blockquote>
<p>여러 게이트웨이를 배우면서 게이트웨이가 무엇인지는 몰랐는데, 다른 네트워크/프로토콜과 http를 중개한다는 걸 알게되면서 내가 알고있던 게이트웨이들의 공통점이 그것인걸 깨달았다..
<strong>NAT 게이트웨이</strong> - 사설 망과 인터넷 중개
<strong>IGW</strong> - VPC와 인터넷 중개</p>
</blockquote>
<h4 id="터널">터널</h4>
<p>데이터 내용을 열어보지 않고 그대로 전달만 하는 중개자</p>
<h4 id="에이전트">에이전트</h4>
<p>사용자 대신 HTTP 요청을 생성하고 전송하는 클라이언트 프로그램
HTTP 요청 시 <code>User-Agent</code> 헤더를 통해 자신을 식별함
e.g., 웹 크롤러</p>
<h3 id="21-인터넷의-리소스-탐색하기">2.1 인터넷의 리소스 탐색하기</h3>
<h4 id="url이-있기-전">URL이 있기 전</h4>
<p>&quot;ftp.joes-hardware.com에 FTP로 접속해. 익명 사용자로 로그인한 다음 비밀번호로 네 이름을 입력해. pub 디렉터리로 이동한 다음, 바이너리 형식으로 전환해. 이제 complete-catalog.xls란 이름의 파일을 너의 로컬 파일 시스렘에 내려 받은 다음 보면 될 거야&quot;
-&gt; “<a href="ftp://ftp">ftp://ftp</a> .lots-o-books . com/pub/complete-catalog.xls를 열어봐”</p>
<ul>
<li>URL을 사용하면 이런 애플리케이션들에서 하나의 인터페이스를 통해 일관된 방식으로 많은 리소스에 접근 가능</li>
</ul>
<h3 id="22-url-문법">2.2 URL 문법</h3>
<pre><code>&lt;스킴&gt;://&lt;사용자 이름&gt;:&lt;비밀번호&gt;@&lt;호스트&gt;:&lt;포트&gt;/&lt;경로&gt;;&lt;파라미터&gt;?&lt;질의&gt;#&lt;프래그먼트&gt;</code></pre><ul>
<li>중요 컴포넌트: 스킴, 호스트, 경로</li>
<li>localhost
<a href="http://localhost:8080">http://localhost:8080</a>, <a href="http://127.0.0.1:8080">http://127.0.0.1:8080</a><blockquote>
<p>The IP address 127.0. 0.1 is called the <strong>loopback address</strong> and is used by a computer to refer to itself. It is also known as localhost. When a server is running on your local PC, it will be accessible at 127.0. 0.1.</p>
</blockquote>
<h4 id="스킴">스킴</h4>
어떤 프로토콜?
대소문자 구분x</li>
</ul>
<h4 id="호스트와-포트">호스트와 포트</h4>
<p>리소스 호스팅 장비, 서버 위치
호스트-&gt; 도메인 이름 또는 IP 주소
포트 -&gt; 생략 시 기본 포트 사용 (HTTP는 80, HTTPS는 443)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTTP] TIL 1]]></title>
            <link>https://velog.io/@jiu-jung/HTTP-WIL</link>
            <guid>https://velog.io/@jiu-jung/HTTP-WIL</guid>
            <pubDate>Mon, 07 Apr 2025 00:35:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>ACC 스터디에서 <a href="https://product.kyobobook.co.kr/detail/S000001033001"><strong>HTTP 완벽 가이드</strong></a>를 읽고 정리하는 글</p>
</blockquote>
<pre><code class="language-java">1부 HTTP: 웹의 기초
├── 1장 HTTP 개관
│   ├── 1.3 리소스
│   ├── 1.4 트랜잭션
│   ├── 1.5 메세지</code></pre>
<h3 id="http는-신뢰성-있는-프로토콜을-사용하기-때문에"><em>&quot;HTTP는 신뢰성 있는 프로토콜을 사용하기 때문에&quot;</em></h3>
<ul>
<li>HTTP/3는 UDP인데? 라고 생각이 들었지만 <strong>QUIC이 UDP위의 전송 계층 프로토콜</strong>이고 신뢰성을 보장한다.</li>
</ul>
<h3 id="mimemultipurpose-internet-mail-extensions">MIME(Multipurpose Internet Mail Extensions)</h3>
<ul>
<li><code>Content-type: image/jpg</code></li>
<li>HTTP/3에서도 쓰나?하는 의문이 들었다. 챗지피티에 따르면 HTTP의 핵심 메시지 포맷과 콘텐츠 타입 표현 방식은 유지된다고 한다.</li>
</ul>
<h3 id="uri-vs-url-vs-urn">URI vs. URL vs. URN</h3>
<ul>
<li>URI(Uniform Resource <strong>Identifier</strong>)의 종류로 URL(Uniform Resource <strong>Locator</strong>), URN(Uniform Resource <strong>Name</strong>), URC 등이 있다.</li>
<li>URI, URL 는 사실상 동의어처럼 쓰이고 있다. 하지만 <strong>URL은 URI의 일종이고, URL이 광범위하게 쓰이고있기 때문에 동일하게 인식</strong>되는 것이다. (URL은 파일의 위치를 정확히 가르키고 있기 때문에 당연히 Identifier가 되니까 URI의 일종)</li>
<li>URN은 상용화되지 않아서 많은 사람들이 개념에 대해 헷갈려한다. 결론은 URI와 URL만 알아도 괜찮을 듯.</li>
</ul>
<blockquote>
<p><a href="https://www.ietf.org/rfc/rfc3986.txt">RFC3986</a>
<strong>1.1.3.  URI, URL, and URN</strong>
   A URI can be further classified as a locator, a name, or both.  The term <strong>&quot;Uniform Resource Locator&quot; (URL) refers to the subset of URIs that, in addition to identifying a resource, provide a means of locating the resource</strong> by describing its primary access mechanism (e.g., its network &quot;location&quot;).  The term &quot;Uniform Resource Name&quot; (URN) has been used historically to refer to both URIs under the &quot;urn&quot; scheme [RFC2141], which are required to remain globally unique and persistent even when the resource ceases to exist or becomes unavailable, and to any other URI with the properties of a name. 
   An individual scheme does not have to be classified as being just one of &quot;name&quot; or &quot;locator&quot;.  Instances of URIs from any given scheme may have the characteristics of names or locators or both, often depending on the persistence and care in the assignment of identifiers by the naming authority, rather than on any quality of the scheme.  <strong>Future specifications and related documentation should use the general term &quot;URI&quot; rather than the more restrictive terms &quot;URL&quot; and &quot;URN&quot; [RFC3305].</strong></p>
</blockquote>
<h3 id="트랜잭션">트랜잭션</h3>
<ul>
<li>Request, Response를 주고받는것을 트랜잭션이라고 한다.</li>
<li>웹페이지는 여러 객체로 되어있기 때문에, 하나의 웹페이지를 가져올 때 여러번의 트랜잭션이 일어난다.</li>
<li>컴퓨터네트워크 수업에서 HTTP가 (base HTML + 웹페이지 내의 객체들)을 전송하는 여러 방식들을 배웠다. 뒷장에 자세히 나올듯</li>
<li><em>Persistent, Non-Persistent, Non-Persistent with parallel, Pipelining*</em></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 데이터베이스 - RDS, DynamoDB]]></title>
            <link>https://velog.io/@jiu-jung/AWS-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-RDS-DynamoDB</link>
            <guid>https://velog.io/@jiu-jung/AWS-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-RDS-DynamoDB</guid>
            <pubDate>Sun, 06 Apr 2025 13:54:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Amazon Cloud Club Ewha 3기 핸즈온 세션 이론 정리
Week 2 - 데이터베이스 - RDS, DynamoDB</p>
</blockquote>
<h1 id="1-rds">1. RDS</h1>
<h2 id="1-1-데이터베이스">1-1. 데이터베이스</h2>
<h3 id="개요">개요</h3>
<p>구조화된 정보 또는 데이터의 조직화된 모음. DBMS에 의해 제어된다.</p>
<h3 id="종류">종류</h3>
<table>
<thead>
<tr>
<th>Relational Database</th>
<th>Non-Relational Database</th>
</tr>
</thead>
<tbody><tr>
<td>구조화된 데이터 저장</td>
<td>비정형 데이터 저장</td>
</tr>
<tr>
<td>엄격한 스키마</td>
<td>스키마 없음</td>
</tr>
<tr>
<td>테이블 형태</td>
<td>테이블이 아닌 형태(문서, 키-값, 그래프 등)</td>
</tr>
<tr>
<td>SQL 사용</td>
<td>자체 API 사용</td>
</tr>
<tr>
<td>MySQL, PostgreSQL</td>
<td>MongoDB, Redis</td>
</tr>
</tbody></table>
<h3 id="relational-database의-종류">Relational Database의 종류</h3>
<table>
<thead>
<tr>
<th>On-Premise</th>
<th>AWS EC2</th>
<th>AWS RDS</th>
</tr>
</thead>
<tbody><tr>
<td>사용자가 직접 서버를 구축하여 DB 관리</td>
<td>EC2 인스턴스 위에 직접 DB 설치</td>
<td>AWS의 완전관리형 RDB 서비스</td>
</tr>
</tbody></table>
<hr>
<h2 id="1-2-aws-rds">1-2. AWS RDS</h2>
<p><a href="https://aws.amazon.com/ko/rds/features/?nc1=h_ls">aws.amazon.com</a></p>
<h3 id="개요-1">개요</h3>
<p>*<em>Relational Database Service
*</em>AWS 클라우드에서 관계형 데이터베이스를 쉽게 설치, 운영 및 확장할 수 있도록 지원하는 완전관리형 서비스.</p>
<h3 id="장점">장점</h3>
<ul>
<li>간편한 관리</li>
<li>가용성(Availability) 및 안정성(Durability)
자동 백업, 스냅샷, Multi-AZ</li>
<li>보안성(Security)</li>
<li>확장성(Scalability)
Read Replica 지원</li>
<li>비용 효율성(Cost Effective)</li>
</ul>
<h3 id="multi-az">Multi-AZ</h3>
<p><a href="https://aws.amazon.com/ko/rds/features/multi-az/">aws.amazon.com</a></p>
<ul>
<li>다른 가용영역(AZ)에 자동으로 복제본 생성 및 동기화</li>
<li>복제본은 대기 데이터베이스(Standby) 역할</li>
<li>장애 발생 시 자동으로 대기 인스턴스로 페일오버(Failover)</li>
<li><strong>성능 향상이 아닌 가용성 확보 목적</strong></li>
</ul>
<h3 id="read-replica">Read Replica</h3>
<p><a href="https://aws.amazon.com/ko/rds/features/read-replicas/">aws.amazon.com</a></p>
<ul>
<li>읽기 전용 복제본 생성</li>
<li>읽기 쿼리의 성능 향상 및 분산 처리 가능 (Scale Out)
  읽기 중심의 데이터베이스 워크로드 처리량 향상</li>
<li>비동기 복제</li>
<li>읽기 성능 분산 및 Scale Out</li>
</ul>
<blockquote>
<p><strong>SAA Example</strong>
Q: RDS를 사용하고 있는 회사에서 하루/한달에 한 번씩 통계용
으로 데이터를 읽어오는 데 성능에 문제가 생기는 것을 발견
했다. 이때의 솔루션은?
A: RDS Read Replica를 사용한다.</p>
</blockquote>
<h3 id="multi-az-vs-read-replica">Multi-AZ vs. Read Replica</h3>
<table>
<thead>
<tr>
<th>Multi-AZ</th>
<th>Read Replica</th>
</tr>
</thead>
<tbody><tr>
<td>장애 대비</td>
<td>성능 개선</td>
</tr>
<tr>
<td>동기 복제</td>
<td>비동기 복제</td>
</tr>
</tbody></table>
<hr>
<h1 id="2-dynamodb">2. DynamoDB</h1>
<h2 id="2-1-sql">2-1. SQL</h2>
<h3 id="sql-vs-nosql">SQL vs. NoSQL</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>SQL</th>
<th>NoSQL</th>
</tr>
</thead>
<tbody><tr>
<td>구조</td>
<td>고정된 스키마</td>
<td>유연한 스키마</td>
</tr>
<tr>
<td>데이터 형태</td>
<td>테이블(행/열)</td>
<td>문서, 키-값 등</td>
</tr>
<tr>
<td>언어</td>
<td>SQL</td>
<td>자체 API</td>
</tr>
<tr>
<td>확장성</td>
<td>수직 확장</td>
<td>수평 확장</td>
</tr>
<tr>
<td>대표 예시</td>
<td>MySQL, PostgreSQL</td>
<td>MongoDB, DynamoDB</td>
</tr>
</tbody></table>
<h4 id="nosql">NoSQL</h4>
<ul>
<li>비관계형 데이터베이스에서 사용</li>
<li>가용성과 확장성이 높고 고성능에 최적화</li>
<li>Instagram, facebook등에서 사용</li>
</ul>
<hr>
<h2 id="2-2-amazon-dynamodb">2-2. Amazon DynamoDB</h2>
<h3 id="개요-2">개요</h3>
<ul>
<li>10ms 미만 응답 속도의 <strong>서버리스 NoSQL 완전관리형 DB</strong></li>
<li>고가용성 및 내구성 보장<ul>
<li>10ms 내 응답</li>
<li>SSD 저장 + 다중 AZ 복제</li>
</ul>
</li>
<li>Auto Scaling 지원</li>
</ul>
<h3 id="핵심구성요소">핵심구성요소</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/7bd267b1-7825-4909-946e-c96d5a3eea73/image.png" alt=""></p>
<ul>
<li><strong>Table</strong>: 데이터 저장 단위</li>
<li><strong>Items(항목)</strong>: 테이블의 한 행
  People 테이블에서 각 항목은 한 사람
  Cars 테이블의 경우 각 항목은 차량 한 대</li>
<li><strong>Attributes(속성)</strong>: 아이템의 속성
  각 항목은 하나 이상의 속성으로 구성
  People 테이블의 항목: PersonID, LastName, FirstName</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/35255e1b-35f5-4f91-a297-19761931cfd0/image.png" alt=""></p>
<ul>
<li>People 테이블에서 기본 키(PK)는 한 개의 속성(PersonID)으로
구성</li>
<li>기본 키를 제외하고, People 테이블에는 스키마가 없다.
: 속성이나 데이터 형식을 미리 정의할 필요가 없다</li>
<li>각 항목에는 자체의 고유 속성이 있을 수 있다</li>
<li>일부 항목에는 중첩된 속성(Address)도 가질 수 있다</li>
</ul>
<h3 id="테이블구조">테이블구조</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/c15c333e-5084-4dbe-a521-835ae1cafb19/image.png" alt=""></p>
<ul>
<li>Primary key + Attributes</li>
<li>Primary Key = Partition Key (+ Sort Key)<ul>
<li>Partition Key: 데이터 분산 기준 (Equal 조건만 가능)</li>
<li>Sort Key: 파티션 내 정렬 기준 (범위 쿼리 가능)</li>
</ul>
</li>
</ul>
<h4 id="partition-key">Partition Key</h4>
<ul>
<li>기본 키. 데이터 분산 기준</li>
<li>파티션 결정
DynamoDB는 테이블의 크기가 10G를 초과하면 데이터에 대한
Partition을 나눈다(<strong>Sharding</strong>)
같은 Partition Key를 가지면 같은 Partition에 저장되는 것(Hash Function)<h4 id="sort-key">Sort Key</h4>
</li>
<li>Partition Key로 저장할 파티션 공간을 결정하고 나서, 같은 Partition이라면 Sort Key 값을 기준으로 정렬되어 저장됨</li>
<li>일치, 부등호, 포함 등의 범위를 지정할 수 있는 검색을 지원한다</li>
<li>Partion Key는 일치검색(Equal)만 지원</li>
</ul>
<blockquote>
<p> <strong>AWS Lambda</strong></p>
</blockquote>
<ul>
<li>DynamoDB에서는 Batch 쓰기 지원
Lambda를 이용해서 batch.put_item으로 여러번의 데이터 추가를 하나의 함수로 한 번에 진행할 수 있다
<code>BatchWriteItem API</code>: 한 번에 최대 25개의 항목까지 DynamoDB 테이블에
삽입할 수 있는 API
<code>CloudWatch Events</code> : 특정 시간 또는 주기적으로 Lambda 함수를 실행하도록 설정</li>
<li>DynamoDB의 특성상 (NoSQL) Join 지원 X
Join이 부득이하게 필요한 경우 Lambda로 구현 가능</li>
</ul>
<hr>
<table>
<thead>
<tr>
<th>항목</th>
<th><strong>Amazon RDS</strong></th>
<th><strong>Amazon DynamoDB</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>DB 유형</strong></td>
<td>관계형 데이터베이스 (SQL 기반)</td>
<td>비관계형 데이터베이스 (NoSQL, 키-값/문서형)</td>
</tr>
<tr>
<td><strong>데이터 구조</strong></td>
<td>테이블, 행, 열 (고정 스키마)</td>
<td>테이블, 아이템, 속성 (유연한 스키마)</td>
</tr>
<tr>
<td><strong>쿼리 언어</strong></td>
<td>SQL (MySQL, PostgreSQL 등)</td>
<td>자체 API (SQL 사용 X)</td>
</tr>
<tr>
<td><strong>스케일링</strong></td>
<td>수직 확장 (인스턴스 크기 조절) <br> 읽기 성능은 Read Replica 사용</td>
<td>수평 확장 (자동 스케일링) <br> 용량에 따라 자동 조정</td>
</tr>
<tr>
<td><strong>고가용성</strong></td>
<td>Multi-AZ 구성으로 장애 대비</td>
<td>리전 및 AZ 복제 내장 (기본 내구성 높음)</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>복잡한 쿼리에 적합 <br> 조인, 트랜잭션 지원</td>
<td>단순/빠른 읽기-쓰기 작업에 최적화 <br> 밀리초 응답</td>
</tr>
<tr>
<td><strong>트랜잭션 지원</strong></td>
<td>완전한 ACID 트랜잭션 지원</td>
<td>제한적 트랜잭션 지원 (단순한 경우에만 사용)</td>
</tr>
<tr>
<td><strong>비용 구조</strong></td>
<td>인스턴스 기준 요금 (사용 시간 + 저장소 + 백업)</td>
<td>요청 기반 과금 (읽기/쓰기 용량 또는 온디맨드)</td>
</tr>
<tr>
<td><strong>운영/관리</strong></td>
<td>백업, 패치 등 자동화되었지만 일부 설정 필요</td>
<td>완전 서버리스 (인프라 관리 불필요)</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>전통적인 웹앱, ERP, CRM, 금융 시스템 등</td>
<td>IoT, 게임 리더보드, 실시간 로그 처리, 모바일 앱 등</td>
</tr>
<tr>
<td><strong>장점</strong></td>
<td>관계형 구조로 복잡한 비즈니스 로직 구현 용이</td>
<td>확장성, 고속 처리, 관리 편의성 탁월</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 스토리지 - S3 & CloudFront]]></title>
            <link>https://velog.io/@jiu-jung/AWS-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-S3-CloudFront</link>
            <guid>https://velog.io/@jiu-jung/AWS-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-S3-CloudFront</guid>
            <pubDate>Thu, 27 Mar 2025 15:54:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Amazon Cloud Club Ewha 3기 핸즈온 세션 이론 정리
Week 2 - 스토리지 - S3 &amp; CloudFront</p>
</blockquote>
<hr>
<blockquote>
<h1 id="1-storage">1. Storage</h1>
</blockquote>
<h2 id="1-1-cloud-storage란">1-1. Cloud Storage란?</h2>
<p>: 클라우드 컴퓨팅 제공업체를 통해 데이터와 파일을 인터넷에 저장할 수 있는 <strong>클라우드 컴퓨팅 모델</strong>
사용자는 Public 인터넷 또는 Private 네트워크로 스토리지 접근</p>
<hr>
<h2 id="1-2-storage-유형-비교">1-2. Storage 유형 비교</h2>
<table>
<thead>
<tr>
<th>유형</th>
<th>저장 방식</th>
<th>주요 특징</th>
<th>대표 서비스</th>
<th>주요 사용 사례</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Block Storage</strong></td>
<td>블록 단위</td>
<td><strong>고성능, 저지연, 빠른 액세스</strong>, DB 등 엔터프라이즈 시스템에 적합</td>
<td>Amazon EBS</td>
<td>DB, ERP 등</td>
</tr>
<tr>
<td><strong>File Storage</strong></td>
<td>파일/폴더 계층 구조</td>
<td>NAS 기반, 파일 공유 및 <strong>다중 사용자</strong> 접근 가능</td>
<td>Amazon EFS</td>
<td>파일 서버 등</td>
</tr>
<tr>
<td><strong>Object Storage</strong></td>
<td>객체 단위 (데이터 + 메타데이터)</td>
<td>대용량, 비정형 데이터에 적합, <strong>REST API</strong> 기반 접근</td>
<td>Amazon S3</td>
<td>정적 웹 콘텐츠, 백업</td>
</tr>
</tbody></table>
<hr>
<blockquote>
<h1 id="2-amazon-s3">2. Amazon S3</h1>
</blockquote>
<h2 id="2-1-개요">2-1. 개요</h2>
<p>: AWS의 대표 <strong>Object Storage</strong> 서비스
데이터를 버킷 내의 객체로 저장</p>
<table>
<thead>
<tr>
<th>특징</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>액세스 제어</td>
<td>객체별 ACL을 통해 데이터에 접근 가능한 사용자 관리</td>
</tr>
<tr>
<td>높은 내구성</td>
<td>여러 AZ에 복제되어 저장되기 때문에 오랫동안 유실될 가능성 매우 낮음</td>
</tr>
<tr>
<td>데이터 저장</td>
<td>객체단위 데이터 저장</td>
</tr>
<tr>
<td>데이터 저장 용량</td>
<td>단일객체 최대 크기 5TB</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-2-구성">2-2. 구성</h2>
<p>구성: 버킷 + 객체
(버킷: 최상위 디렉토리, 객체: 디렉토리 내에 저장되는 파일)</p>
<h3 id="📁-버킷-bucket">📁 버킷 (Bucket)</h3>
<p>: 객체 저장을 위한 컨테이너</p>
<ul>
<li>객체 무제한으로 저장 가능</li>
<li>전역적으로 고유한 이름 필요</li>
<li>계정당 최대 100개 생성 가능</li>
</ul>
<h3 id="📚-객체-object">📚 객체 (Object)</h3>
<p>: S3에 실제로 저장되는 기본 단위 데이터 개체</p>
<h4 id="특징">특징</h4>
<ul>
<li>하나 이상의 버킷에 저장</li>
<li>최대 크기: 5TB<h4 id="구성">구성</h4>
데이터 + 메타데이터</li>
<li>데이터: 저장하려는 실제 콘텐츠 (임의의 바이트 시퀀스)</li>
<li>메타데이터: 객체 정보 저장용 이름-값 쌍 (사용자 지정 가능)<h4 id="식별-방식">식별 방식</h4>
버킷 + 키 + 버전 ID 조합으로 객체를 유일하게 식별</li>
<li>키: 객체의 이름, 버킷 내 고유 ID</li>
<li>버전 ID: 객체 저장 시 S3가 자동 생성하는 고유 문자열<h4 id="태그">태그</h4>
객체 분류 및 관리에 사용되는 키-값 형태의 메타정보<h4 id="액세스-제어-정보">액세스 제어 정보</h4>
객체 접근 권한 관리
리소스 기반(ACL, 버킷 정책) + 사용자 기반 권한 제어 모두 지원</li>
</ul>
<hr>
<h2 id="2-3-보안">2-3. 보안</h2>
<h3 id="🔑-데이터-암호화">🔑 데이터 암호화</h3>
<h4 id="서버측-암호화-server-side-encryption-sse">서버측 암호화 (Server-Side Encryption, SSE)</h4>
<ul>
<li><strong>데이터를 받은 애플리케이션/서비스 서버</strong>가 해당 데이터 암호화</li>
<li>사용자가 S3에 데이터를 업로드하면 AWS가 해당 데이터를 서버에서 자동으로 암호화하여 저장하고, 사용자가 데이터를 요청할 때 자동으로 복호화하여 제공</li>
<li>현재 S3는 서버 측 암호화(SSE-S3)를 S3 내 모든 버킷 암호화의 기본 수준으로 적용<h4 id="클라이언트측-암호화-client-side-encryption">클라이언트측 암호화 (Client-Side Encryption)</h4>
</li>
<li>전송 및 저장시 보안을 보장하기 위해 <strong>로컬에서 데이터 암호화</strong> -&gt; AWS를 포함한 제3자에게 객체 노출 방지
높은 보안 보장, 관리 및 구현 복잡성 증가</li>
<li><strong>S3 암호화 클라이언트</strong>
S3 암호화 클라이언트가 객체를 암호화하고 버킷에 업로드
사용자와 S3간 중재자 역할</li>
</ul>
<h3 id="🪬-액세스-제어-방식">🪬 액세스 제어 방식</h3>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>리소스 기반 정책</strong></td>
<td>S3 리소스(버킷/객체)에 연결된 JSON 형식의 정책 <br> 버킷 소유자가 설정 가능</td>
</tr>
<tr>
<td><strong>자격 증명 기반 정책</strong></td>
<td>IAM 사용자, 그룹, 역할에 연결된 정책, IAM에서 중앙 관리<br>- 관리형 정책: 다수에 정책 부여<br>- 인라인정책: 단일에 1:1로 정책 연결</td>
</tr>
<tr>
<td><strong>CORS (Cross-Origin Resource Sharing)</strong></td>
<td>도메인 간 요청 허용 여부 설정 <br> 정적 웹 호스팅 시 외부 접근 제어 가능</td>
</tr>
<tr>
<td><strong>Pre-signed URL</strong></td>
<td>유효 기간이 있는 서명된 URL 생성<br>버킷 정책 변경 없이 임시 접근 허용</td>
</tr>
</tbody></table>
<ul>
<li><strong>IAM (Identity and Access Management)</strong>
AWS에서 사용자, 그룹, 역할에게 적절한 권한을 부여하고 접근을 제어하는 서비스</li>
</ul>
<hr>
<h2 id="2-4-스토리지-관리">2-4. 스토리지 관리</h2>
<h3 id="버전-관리">버전 관리</h3>
<ul>
<li>한 버킷에 여러버전의 객체 보관 -&gt; 실수로 삭제/수정된 객체 복구 가능</li>
<li>버킷 버전관리 활성화 -&gt; 저장되는 객체에 버전id 자동 생성</li>
<li>한 번 활성화하면 비활성화 불가(중단 가능)</li>
</ul>
<h3 id="객체-복제">객체 복제</h3>
<table>
<thead>
<tr>
<th>유형</th>
<th>설명</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>CRR (리전 간)</strong></td>
<td>다른 리전으로 비동기 복제</td>
<td>규정 준수, 장애 대비</td>
</tr>
<tr>
<td><strong>SRR (동일 리전)</strong></td>
<td>동일 리전 내 복제</td>
<td>백업, 계정 간 복사</td>
</tr>
<tr>
<td><strong>배치 복제</strong></td>
<td>기존 객체 일괄 복제</td>
<td>대량 마이그레이션</td>
</tr>
</tbody></table>
<h3 id="스토리지-클래스">스토리지 클래스</h3>
<table>
<thead>
<tr>
<th>클래스</th>
<th>특성</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>*<em>S3 Standard *</em></td>
<td>자주 접근, 지연 시간 짧음</td>
<td>일반 데이터 저장</td>
</tr>
<tr>
<td><strong>S3 Standard-IA</strong></td>
<td>자주 접근 X, 낮은 비용</td>
<td>비정기 접근 데이터</td>
</tr>
<tr>
<td><strong>S3 One Zone-IA</strong></td>
<td>단일 AZ, 더 낮은 비용</td>
<td>재생성 가능한 데이터</td>
</tr>
<tr>
<td><strong>S3 Glacier</strong></td>
<td>장기 보관용, 복구 시간 수 분~수 시간</td>
<td>백업, 로그 장기 보관</td>
</tr>
<tr>
<td><strong>S3 Glacier Deep Archive</strong></td>
<td>복구 시간 수 시간~12시간 이상</td>
<td>규제 준수용 장기 아카이빙</td>
</tr>
</tbody></table>
<h3 id="수명-주기-lifecycle">수명 주기 (Lifecycle)</h3>
<table>
<thead>
<tr>
<th>작업 유형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>전환 작업</strong></td>
<td>일정 기간 이후 다른 스토리지 클래스로 전환</td>
</tr>
<tr>
<td>*<em>만료 작업 *</em></td>
<td>객체를 자동으로 삭제하여 비용 절감</td>
</tr>
</tbody></table>
<hr>
<blockquote>
<h1 id="3-amazon-cloudfront">3. Amazon CloudFront</h1>
</blockquote>
<h2 id="3-1-개요">3-1. 개요</h2>
<p>: AWS의 <strong>CDN (Content Delivery Network)</strong> 서비스
사용자 가까운 위치(Edge Location)에서 콘텐츠 캐싱 및 전송</p>
<hr>
<h2 id="3-2-주요-구성-요소">3-2. 주요 구성 요소</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Edge Location</strong></td>
<td>사용자와 가까운 지역에 있는 캐시 서버</td>
</tr>
<tr>
<td><strong>Regional Edge Cache (REC)</strong></td>
<td>Edge Location과 Origin 사이의 중간 캐시층</td>
</tr>
<tr>
<td><strong>Origin</strong></td>
<td>콘텐츠 원본 위치 (S3, EC2 등) <br> 정적, 동적 콘텐츠 모두 처리</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-3-동작-흐름">3-3. 동작 흐름</h2>
<ol>
<li>사용자가 콘텐츠 요청</li>
<li>DNS가 가장 가까운 Edge Location으로 라우팅</li>
<li>Cache Hit → 바로 제공<br>Cache Miss → REC 확인 → 없으면 Origin 요청</li>
<li>콘텐츠 수신 후 Edge Location에 캐싱</li>
</ol>
<hr>
<h2 id="3-4-origin-보안">3-4. Origin 보안</h2>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>OAI (Origin Access Identity)</strong></td>
<td>CloudFront를 통해서만 S3에 접근 허용</td>
</tr>
<tr>
<td><strong>OAC (Origin Access Control)</strong></td>
<td>IAM 기반 세밀한 접근 제어 (OAI의 개선 버전)</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-5-기능">3-5. 기능</h2>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>** HTTPS 지원**</td>
<td>오리진이 HTTP만 지원해도 CloudFront는 HTTPS 제공</td>
</tr>
<tr>
<td><strong>지역 제한</strong></td>
<td>특정 국가 또는 지역 사용자 차단 가능</td>
</tr>
<tr>
<td><strong>Signed URL</strong></td>
<td>객체별 임시 접근 URL (IP, 만료시간 설정 가능) <br> 객체 하나당 하나의 url</td>
</tr>
<tr>
<td><strong>Signed Cookie</strong></td>
<td>다수 객체에 대한 접근 설정 (일괄 제어 가능) <br> 다수의 객체에 하나의 signed cookie</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-6-장점">3-6. 장점</h2>
<ul>
<li><strong>지연 시간 감소</strong> → 빠른 응답 제공  </li>
<li><strong>트래픽 증가 대응</strong> → 로드 밸런싱 기반 확장성  </li>
<li><strong>보안 강화</strong> → HTTPS, 접근 제어 정책, OAI/OAC </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 네트워크 구성 - VPC, Route53]]></title>
            <link>https://velog.io/@jiu-jung/AWS-VPC%EC%99%80-Route53</link>
            <guid>https://velog.io/@jiu-jung/AWS-VPC%EC%99%80-Route53</guid>
            <pubDate>Fri, 21 Mar 2025 11:29:21 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Amazon Cloud Club Ewha 3기 핸즈온 세션 이론 정리
Week 1 - 네트워크 구성 - VPC, Route53</p>
</blockquote>
<blockquote>
<h1 id="1-route53">1. Route53</h1>
</blockquote>
<h2 id="1-1-route53-개요">1-1. Route53 개요</h2>
<p><strong>: AWS의 DNS 서비스</strong></p>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li>도메인 등록</li>
<li>DNS 라우팅</li>
<li>리소스 상태 확인</li>
</ul>
<hr>
<h2 id="1-2-ipinternet-protocol">1-2. IP(Internet Protocol)</h2>
<p>인터넷에 연결되어 있는 모든 장치를 식별할 수 있도록 각각의 장비에게 부여되는 고유 주소</p>
<ul>
<li>state ↔ dynamic</li>
<li>public ↔ private<ul>
<li>cmd <code>ipconfig</code> → private ip 주소
<img src="https://velog.velcdn.com/images/jiu-jung/post/26b0066d-49e2-477b-80d9-b0ad169d428b/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h2 id="1-3-dnsdomain-name-system">1-3. DNS(Domain Name System)</h2>
<p>DHCP(L3 ip주소 할당) → ARP(L4 mac주소 조회) → <strong>DNS</strong>(L3 ip 주소 조회) → TCP(L2 연결) → HTTPS(L5 통신)</p>
<ul>
<li><p><strong>Forwarding DNS</strong>
  도메인 네임(aws.amazon.com)을 네트워크 주소(192.168.1.0)으로 변환</p>
</li>
<li><p><strong>Reverse DNS</strong></p>
</li>
<li><p><strong>분산형 데이터베이스 시스템</strong>
  Root DNS로부터 .com DNS(TLD DNS) 를 찾고, .com DNS로부터 amazon.com DNS(Authoritative DNS)를 찾아, amazon.com DNS에서 aws.amazon.com의 IP주소 획득(계층적)</p>
</li>
<li><p><strong>DNS Routing</strong>
  <img src="https://velog.velcdn.com/images/jiu-jung/post/eaaff12e-51f3-4cac-9a23-d4a5b33317e2/image.png" alt=""></p>
</li>
<li><p><strong>DNS 레코드</strong>
  Request 패킷을 받았을때, 어떻게 처리할지 정해둔 지침으로, RR(Name, Value, Type, TTL)의 형식
  AWS 는 alias 레코드 제공 → AWS 리소스로 라우팅 가능</p>
</li>
</ul>
<hr>
<h2 id="1-4-라우팅-정책">1-4. 라우팅 정책</h2>
<p><a href="https://docs.aws.amazon.com/ko_kr/Route53/latest/DeveloperGuide/routing-policy.html">docs.aws.amazon.com</a></p>
<ul>
<li>단순 라우팅 정책(Simple routing policy)</li>
<li>장애 조치 라우팅 정책(Failover routing policy)</li>
<li>지리 위치 라우팅 정책(Geolocation routing policy)</li>
<li>지리 근접 라우팅 정책</li>
<li>지연 시간 라우팅 정책</li>
<li>IP 기반 라우팅 정책</li>
<li>다중 응답 라우팅 정책(Multivalue answer routing policy)</li>
<li>가중치 기반 라우팅 정책(Weighted routing policy)</li>
</ul>
<hr>
<blockquote>
<h1 id="2-vpc">2. VPC</h1>
</blockquote>
<h2 id="2-0-배경지식">2-0. 배경지식</h2>
<h3 id="💡-ip-주소revisit">💡 IP 주소(revisit)</h3>
<ul>
<li>32비트(IPv4)</li>
<li>8비트씩 표현</li>
<li>255.255.255.255(11111111.11111111.11111111.11111111)<h3 id="💡-cidrclassless-inter-domain-routing">💡 CIDR(Classless Inter-Domain Routing)</h3>
<a href="https://aws.amazon.com/ko/what-is/cidr/">aws.amazon.com</a></li>
<li>라우팅 효율성을 향상시키는 <strong>ip주소 할당 방식</strong></li>
<li>ip 주소 = 네트워크주소 + 호스트주소<ul>
<li>이 네트워크 주소와 호스트 주소가 특정 개수의 비트로 할당되지 않고(Classless), 가변 길이 서브넷 마스킹(Variable Length Subnet Mask, VLSM)에 의해 결정됨</li>
</ul>
</li>
<li><strong>장점</strong><ul>
<li>IP 주소 낭비 감소</li>
<li>데이터를 빠르게 전송</li>
<li>Virtual Private Cloud 생성</li>
<li>슈퍼넷을 유연하게 생성</li>
</ul>
</li>
<li><strong>AWS의 CIDR Block</strong>
  <img src="https://velog.velcdn.com/images/jiu-jung/post/2e21743e-2194-438f-9551-02d3e77ecb7a/image.png" alt=""><h3 id="💡-aws-구조">💡 AWS 구조</h3>
<a href="https://aws.amazon.com/ko/about-aws/global-infrastructure/regions_az/">aws.amazon.com</a></li>
<li><strong>Region(리전)</strong><ul>
<li>AWS가 전 세계에서 데이터 센터를 클러스터링하는 물리적 위치</li>
<li>AWS의 데이터센터들을 지리적으로 나눈 단위</li>
<li>각 AWS 리전은 지리적 영역 내에서 격리되고 물리적으로 분리된 최소 3개의 AZ로 구성</li>
</ul>
</li>
<li><strong>AZ(Availability Zone, 가용 영역)</strong><ul>
<li>여러 데이터 센터들을 묶은 단위</li>
<li>논리적 데이터 센터의 각 그룹</li>
<li>AWS 리전의 중복 전력, 네트워킹 및 연결이 제공되는 하나 이상의 개별 데이터 센터로 구성</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-1-vpc-virtual-priavate-cloud">2-1. VPC (Virtual Priavate Cloud)</h2>
<p><a href="https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/what-is-amazon-vpc.html">docs.aws.amazon.com</a>
<a href="https://medium.com/harrythegreat/aws-%EA%B0%80%EC%9E%A5%EC%89%BD%EA%B2%8C-vpc-%EA%B0%9C%EB%85%90%EC%9E%A1%EA%B8%B0-71eef95a7098">medium.com/harrythegreat</a>
*<em>: 나만의 독립된 가상 네트워크 망
*</em><img src="https://velog.velcdn.com/images/jiu-jung/post/ede5180f-2208-4c3b-a960-ca9c1e6a22c7/image.png" alt="">
⬆️ VPC 이전
<img src="https://velog.velcdn.com/images/jiu-jung/post/68efd552-587d-47d3-b32e-ab2a936753a0/image.png" alt="">
⬆️ VPC 등장
-&gt; VPC별로 네트워크 구성, 설정 가능. 각각의 VPC는 완전히 독립된 네트워크처럼 작동.</p>
<ul>
<li>리전당 5개 설계 가능(리전 단위로 생성)</li>
<li>VPC당 5개의 CIDR 설정 가능</li>
<li>사설 IPv4 범위만 할당 가능</li>
<li>VPC CIDR는 다른 VPC나 네트워크와 범위가 겹치면 안됨</li>
<li>계정을 만들면 Default VPC 생성됨</li>
</ul>
<hr>
<h3 id="✅-subnet">✅ Subnet</h3>
<p>*<em>: IP 네트워크 논리적인 영역을 쪼개서 만든 하위 네트워크망
*</em></p>
<ul>
<li>Public Subnet ↔ Private Subnet</li>
<li>VPC의 IP 범위 안에서 서브넷의 IP 범위 지정. 상위 네트워크보다 subnet mask 더 길고, ip 범위 작음.</li>
<li>VPC가 하나의 리전에 존재하는 것처럼, 서브넷은 하나의 AZ에만 존재</li>
</ul>
<p><strong><code>Why subnetting?</code></strong></p>
<ul>
<li>더 많은 네트워크망을 만들기 위해서.</li>
<li>필요한 host 개수만큼만 host ip 배정하고, subnetting을 통해 더 많은 네트워크 망 생성</li>
</ul>
<hr>
<h3 id="✅-internet-gatewayigw">✅ Internet Gateway(IGW)</h3>
<p><strong>: VPC와 인터넷을 연결해주는 관문</strong></p>
<ul>
<li>VPC의 리소스를 인터넷에 연결하도록 허용하는 EC2 인스턴스나 람다 함수</li>
<li>VPC 기본적으로 격리된 네트워크 환경 → VPC 리소스들은 기본적으로 인터넷 연결 X</li>
<li>IGW와 Subnet 연결해야 인터넷 연결 가능, 이를 위해 Subnet의 Routing Table 설정 필요</li>
</ul>
<hr>
<h3 id="✅-routing-table">✅ Routing Table</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/803356f1-9804-4eb6-a3f5-f27323351f70/image.png" alt=""></p>
<ul>
<li>⬆️ Routing table<ul>
<li>모든 ip → IGA A</li>
<li>private subnet ip → Local(IGA A로 안감, longest prefix match)</li>
</ul>
</li>
<li>A는 private subnet, B는 public subnet</li>
</ul>
<p><strong><code>private subnet 내부의 리소스(e.g., RDS)가 인터넷 연결이 필요한 상황</code></strong></p>
<ul>
<li>private subnet 내부에서 조건적으로 외부와 통신할 수 있는 방법?<ul>
<li><strong>NAT Gateway</strong></li>
<li><strong>Bastion Host</strong></li>
</ul>
</li>
</ul>
<hr>
<h3 id="✅-nat-gateway">✅ NAT Gateway</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/276dfa44-76f3-44c7-8ece-eed6d028fbe4/image.png" alt=""></p>
<ul>
<li><strong>public subnet 내</strong>에 NAT Gateway 설정</li>
<li>private subnet이 외부 라우팅이 필요할 때, NAT Gateway를 거치도록 라우팅테이블 설정</li>
<li>Private Subnet의 AWS 리소스 → Public Subnet의 NAT Gateway → IGW → 외부 연결</li>
<li>특정 AZ에서 생성, Elasic IP 사용(: 고정 public ip)</li>
<li>여러 개의 AZ에 생성해 내결함성(fault torelance) 강화 가능: 다중 NAT Gateway</li>
</ul>
<hr>
<h3 id="✅-bastion-host">✅ Bastion Host</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/a439aa1d-4f5c-4553-b34d-48904e1f0f76/image.png" alt=""></p>
<ul>
<li><p><strong>Public Subnet에 위치</strong>해 외부에서 Private Subnet으로의 통신을 도와주는 대리인(Public Subnet 내부의 EC2 Instance)</p>
</li>
<li><p>Bastion Host 설정</p>
<ol>
<li><p>Bastion Host Security Group
 Bastion Host의 보안 그룹은 제한된 CIDR(외부)로부터 Private Subnet으로의 접근을 허용해야 함.</p>
</li>
<li><p>EC2 instance Security Group
 private subnet의 instance의 보안 그룹은 Bastion Host의 보안 그룹 혹은 Bastion Host의 개인 ip에 대해 SSH 연결을 허용해야 함.</p>
</li>
</ol>
</li>
</ul>
<hr>
<h3 id="✅-security-group보안-그룹">✅ Security Group(보안 그룹)</h3>
<ul>
<li>인스턴스 단위로 적용할 수 있는 네트워크 보안 기술</li>
<li>인스턴스 inbound, outbound 트래픽 허용(Allow) 규칙 모음집</li>
<li>기본 설정 → 모든 허용 차단</li>
</ul>
<hr>
<h3 id="✅-naclnetwork-acl">✅ NACL(Network ACL)</h3>
<ul>
<li>Subnet을 오고가는 모든 트래픽에 대해 허용(allow)하거나 거부(deny)</li>
<li>기본 NACL(Default NACL) → 모든 요청 허용</li>
<li>직접 NACL 설정 → inbound, outbound 요청 모두 거부된 상태로 설정</li>
<li>우선순위<ul>
<li>규칙마다 숫자(1~32766) 부여하여 우선순위 지정</li>
<li>숫자가 낮을수록 우선순위 높음</li>
<li>보통 100단위로 설정</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>Security Group</th>
<th>NACL</th>
</tr>
</thead>
<tbody><tr>
<td>인스턴스 단위 보안</td>
<td>Subnet 단위 보안</td>
</tr>
<tr>
<td>stateful</td>
<td>stateless</td>
</tr>
<tr>
<td>모든 규칙이 평가되고 허용/거부 여부 결정</td>
<td>가장 높은 우선순위를 가진 규칙이 우선 평가되며, 첫 번째 비교로 허용/거부 결정</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>ec2 인스턴스 하나에만 적용</td>
<td>subnet안의 모든 ec2에 적용</td>
</tr>
<tr>
<td>허용(allow)규칙만 설정 가능</td>
<td>허용(Allow), 거부(Deny) 규칙 모두 설정 가능</td>
</tr>
<tr>
<td>모든 허용 차단하도록 기본 설정</td>
<td>모든 요청 허용하도록 기본 설정</td>
</tr>
</tbody></table>
<p><em>네트워크ACL과 보안그룹이 충돌 → *</em>보안그룹이 우선**
<img src="https://velog.velcdn.com/images/jiu-jung/post/056c8468-7376-4f07-8329-a5727eefe3b8/image.png" alt="">
<img src="https://velog.velcdn.com/images/jiu-jung/post/2576abf1-f4b0-4697-a1f5-6c88c9b7b52a/image.png" alt=""></p>
<hr>
<h3 id="✅-vpc-peering">✅ VPC Peering</h3>
<p><strong>: Private IPv4/v6 주소를 사용해 두 VPC 간 트래픽을 라우팅</strong> → 두 VPC 간 통신을 가능하게 하는 서비스</p>
<ul>
<li>AWS 네트워크를 통해 연결되므로 안전</li>
<li>VPC의 CIDR가 서로 겹치면 안됨</li>
<li>VPC 피어링 관계는 전이되지 않음</li>
<li>피어링을 활성화하고 싶다면 VPC 서브넷의 라우팅 테이블도 통신이 가능하도록 업데이트 필요</li>
<li>서로 다른 계정, 서로 다른 리전에서도 가능</li>
</ul>
<hr>
<h3 id="✅-vpc-endpoint">✅ VPC Endpoint</h3>
<p>: VPC 내부 리소스가 외부 AWS 서비스와 통신할 때, 외부 인터넷을 거치지 않고 AWS 백본 네트워크를 통해 서비스에 도달할 수 있도록 지원하는 서비스
→ <strong>Public IP 없어도 AWS 서비스 사용 가능</strong>
<img src="https://velog.velcdn.com/images/jiu-jung/post/883ae0fe-15ae-4f01-89a3-feeb0580d9a8/image.png" alt=""></p>
<p>초록색 → public ip를 가진 인스턴스만 IGW를 통해 인터넷에 연결되어 AWS 서비스 사용 가능
파란색 → public ip가 없는 인스턴스도 VPC Endpoint를 통해 AWS 서비스 사용 가능</p>
<hr>
<h1 id="-1-정리">-1. 정리</h1>
<p>Route53: AWS의 DNS 서비스
AWS 구조 - 리전, AZ
VPC</p>
<ul>
<li>Subnet</li>
<li>Internet Gateway</li>
<li>Routing Table</li>
<li>NAT Gateway &amp; Bastion Host</li>
<li>Security Group &amp; NACL</li>
<li>VPC Peering</li>
<li>VPC Endpoint</li>
</ul>
<p>개념 까먹었을 땐 -&gt; <a href="https://medium.com/harrythegreat/aws-%EA%B0%80%EC%9E%A5%EC%89%BD%EA%B2%8C-vpc-%EA%B0%9C%EB%85%90%EC%9E%A1%EA%B8%B0-71eef95a7098">medium.com/harrythegreat</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Capstone] Diffusion model 기초]]></title>
            <link>https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Tue, 28 Jan 2025 07:26:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://xoft.tistory.com/32">xoft.tistory.com</a>를 참고하여 Diffusion model 이해에 필요한 기본 개념을 정리했다.</p>
</blockquote>
<blockquote>
<h3 id="목차">목차</h3>
<p><a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#probability-vs-likelihood">Probability vs. Likelihood
</a><a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#mlemaximum-likelyhood-estimation">MLE(Maximum Likelyhood Estimation)</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#markov-processmpmarkov-chain">Markov Process(MP)(=Markov Chain)
</a><a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#baysian-rule">Baysian Rule</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#kl-divergence">KL Divergence</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#vae">VAE</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#clip">CLIP</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#sds">SDS</a>
<a href="https://velog.io/@jiu-jung/Diffusion-model-%EA%B3%B5%EB%B6%80%ED%95%98%EA%B8%B0-%EA%B8%B0%EC%B4%88#diffusion">Diffusion</a></p>
</blockquote>
<h2 id="probability-vs-likelihood">Probability vs. <strong>Likelihood</strong></h2>
<h3 id="probability"><strong>Probability</strong></h3>
<p>확률분포 $p(x)$가 고정된 상태에서 관측 사건 $x$가 변화할 때의 확률.</p>
<p>일상적으로 쓰이는 확률의 의미. &quot;어떤 일이 일어날 가능성&quot;</p>
<h3 id="likelihood"><strong>Likelihood</strong></h3>
<p>관측된 데이터 $x$가 고정된 상태에서, 이를 설명할 수 있는 확률분포 $p_θ(x)$가 변화할 때의 값.
주어진 데이터 ${x_1, x_2, ..., x_n}$가 특정 확률분포 $p_θ(x)$에서 생성되었을 가능성
주어진 데이터가 특정 확률분포를 따를 가능성</p>
<p>Likelihood가 크다는 것은, 관측된 데이터가 해당 확률분포로부터 생성되었을 가능성이 크다는 것.</p>
<p>관측된 데이터를 잘 설명하는 확률분포를 찾는 MLE에서 쓰인다.</p>
<h4 id="이산-likelihood">이산 Likelihood</h4>
<p>$$
L(θ; x) = P(x | θ) = ∏_{i=1}^n p_θ(x_i)
$$</p>
<p>$p_{\theta}$: PMF(확률질량함수), $\theta$: 확률분포, x: 관측값</p>
<h4 id="연속-likelihood">연속 Likelihood</h4>
<p>$$
L(θ; x) = P(x | θ) = ∏_{i=1}^n f_θ(x_i)
$$</p>
<p>$f_{\theta}$: PDF(확률밀도함수)</p>
<h4 id="log-likelihood">log likelihood</h4>
<p>log는 증가함수이므로, 최대 최소를 구할 때 log likelihood를 써도 상관없다.</p>
<p>$$
ℓ(θ; x) = log L(θ; x) = ∑_{i=1}^n log p_θ(x_i)
$$</p>
<p>$$
ℓ(θ; x) = log L(θ; x) = ∑_{i=1}^n log f_θ(x_i)
$$</p>
<blockquote>
<p><strong>Probability</strong>는 사건 <code>x</code>의 확률을 계산하며, <strong>Likelihood</strong>는 특정 사건이 관측되었을 때 해당 사건을 잘 설명할 수 있는 확률분포를 찾는 데 사용됨.</p>
</blockquote>
<hr>
<h2 id="mlemaximum-likelyhood-estimation">MLE(Maximum Likelyhood Estimation)</h2>
<p>(gmm - em algorithm에서 봤던 개념)
관측된 데이터를 가장 잘 설명하는 확률분포의 파라미터를 추정하는 방법.</p>
<p>주어진 데이터 ${x₁, x₂, ..., xₙ}$에 대해, 확률분포 $p_θ(x)$의 Likelihood를 최대화하는 $θ$를 찾음.</p>
<p>$$
\hat{\theta} = \arg\max_{\theta} \prod_{i=1}^n p_\theta(x_i) = \arg\max_{\theta} \sum_{i=1}^n \log p_\theta(x_i)
$$</p>
<h3 id="gaussian-distribution-mle"><strong>Gaussian Distribution MLE</strong></h3>
<p>Gaussian Distribution의 log likelihood를 각각 $μ$, $σ$에 대해 미분하고, 0이 되는 지점을 찾는다.</p>
<ul>
<li><p>가우시안 분포 파라미터: 평균 $μ$, 표준편차 $σ$</p>
</li>
<li><p>가우시안 분포 확률분포함수</p>
</li>
</ul>
<p>$$
p(x | \mu, \sigma) = \frac{1}{\sigma \sqrt{2\pi}} \exp\left(-\frac{(x - \mu)^2}{2\sigma^2}\right)
$$</p>
<ul>
<li><p>log likelihood</p>
<p>  $$
  \ell(\mu, \sigma; x) = -\frac{n}{2} \log(2\pi) - n \log(\sigma) - \frac{1}{2\sigma^2} \sum_{i=1}^n (x_i - \mu)^2
  $$</p>
</li>
</ul>
<p>평균 $μ$의 MLE: 데이터의 평균</p>
<p>$$
\hat{\mu} = \frac{1}{n} \sum_{i=1}^n x_i= \frac{x_1 + x_2 + \dots + x_n}{n}
$$
표준편차 $σ$ 의 MLE: 데이터의 표준편차</p>
<p>$$
\hat{\sigma} = \sqrt{\frac{1}{n} \sum_{i=1}^n (x_i - \hat{\mu})^2}= \sqrt{\frac{(x_1 - {\mu})^2 + (x_2 - \hat{\mu})^2 + \dots + (x_n - \hat{\mu})^2}{n}}</p>
<p>$$</p>
<hr>
<h2 id="markov-processmpmarkov-chain">Markov Process(MP)(=Markov Chain)</h2>
<blockquote>
<p>Markov Property를 갖는 discrete time stochastic process.</p>
</blockquote>
<ul>
<li>Markov Property: 현재 상태가 오직 바로 이전(n-1) 상태에 의해서만 결정됨.</li>
</ul>
<p>$$
P(X_t | X_{t-1}, X_{t-2}, \dots, X_0) = P(X_t | X_{t-1})
$$</p>
<ul>
<li><p>discrete time: 시간이 이산적으로 표현됨(t=n, n-1, n-2, …)</p>
</li>
<li><p>stochastic process: 시간에 따라 어떤 사건이 발생할 확률이 변화하는 과정; 시간에 따라 상태가 확률적으로 변화</p>
</li>
</ul>
<hr>
<h2 id="baysian-rule">Baysian Rule</h2>
<p>조건부 확률을 계산하는 공식으로, 관측된 데이터 $D$를 기반으로 모델 $H$의 사후 확률을 계산함.</p>
<p>$$
P(H | D) = \frac{P(D | H) P(H)}{P(D)}
$$</p>
<ul>
<li>$P(H|D)$: 관측된 데이터를 기반으로 $H$가 참일 확률 (사후 확률).</li>
<li>$P(D|H)$: $H$가 참일 때 데이터를 관측할 확률 (Likelihood).</li>
<li>$P(H)$: 사전 확률.</li>
<li>$P(D)$: 관측된 데이터를 기반으로 한 전체 확률.</li>
</ul>
<hr>
<h2 id="kl-divergence">KL Divergence</h2>
<p>두 확률분포 $p$와 $q$ 간의 차이를 측정하는 지표.</p>
<p>분포 $p$가 $q$에 비해 얼마나 &quot;다르게&quot; 분포되어 있는지를 나타내는 척도.</p>
<p>정보량의 차이를 수치화하며, Loss 함수로 자주 사용됨.</p>
<p>Diffusion model에서 Forward process와 Reverse process의 분포 차이를 줄이는 데 활용됨.</p>
<h3 id="공식">공식</h3>
<p>이산 확률 분포</p>
<p>$$
        D_{\text{KL}}(p || q) = \sum_{x} p(x) \log \frac{p(x)}{q(x)}
        $$</p>
<p>연속 확률 분포<br>$$
        D_{\text{KL}}(p || q) = \int p(x) \log \frac{p(x)}{q(x)} dx
        $$</p>
<h3 id="특징"><strong>특징</strong></h3>
<p>$D_{KL}(p || q) ≥ 0$.</p>
<p>$D_{KL}(p || q) ≠ D_{KL}(q || p$ (거리개념이 아님).</p>
<p>분포 $p$와 $q$가 동일하면 $D_{KL}(p || q) = 0$.</p>
<h3 id="엔트로피">엔트로피</h3>
<p>엔트로피는 확률 분포가 가지는 &quot;불확실성&quot;의 양을 나타내며, KL Divergence는 두 분포의 엔트로피 차이를 측정하는 방식.</p>
<p>$$
D_{KL}(p \parallel q) = H(p, q) - H(p)</p>
<p>$$</p>
<ul>
<li>$H(p)$: 분포$p$의 엔트로피(정보량)   </li>
<li>$H(p, q)$: 분포 $p$와 $q$가 함께 발생할 때의 합동 엔트로피($p$와 $q$가 동시에 발생할 때 얻을 수 있는 정보량)
  이때 정보량이란 질문의 정보량!</li>
</ul>
<hr>
<h2 id="vae">VAE</h2>
<p>Variational Auto Encoder</p>
<p>Autoencoder 기반의 모델로, 특정 데이터의 잠재 공간(latent space)을 확률분포로 학습.</p>
<p>데이터를 단일 점으로 학습하는 기존 Autoencoder와 달리, 잠재 변수를 확률분포 $z ~ p(z)$로 모델링.</p>
<p>$$
\mathcal{L} = \mathbb{E}<em>{q(z|x)}[\log p(x|z)] - D</em>{\text{KL}}(q(z|x) || p(z))
$$</p>
<ul>
<li>첫 번째 항: reconstruction loss</li>
<li>두 번째 항: 잠재 분포 $q(z|x)$와 $p(z)$ 간의 KL Divergence.</li>
</ul>
<hr>
<h2 id="clip">CLIP</h2>
<p>Contrastive Language-Image Pretraining</p>
<p>OpenAI에서 2021년 개발한 텍스트와 이미지를 연결하는 모델</p>
<p>텍스트와 이미지 데이터를 동일한 잠재 공간(shared latent space)에 매핑하여 서로 간의 관련성을 학습함.</p>
<h3 id="contrastive-learning"><strong>Contrastive Learning</strong></h3>
<p>입력 샘플 간의 비교를 통해 학습을 하는 것</p>
<p>입력 쌍(positive, negative)을 비교하여 학습.</p>
<p>관련 있는 쌍은 가까이, 관련 없는 쌍은 멀리 배치.</p>
<hr>
<h2 id="sds">SDS</h2>
<p>Score Distillation Sampling</p>
<p>DreamFusion에서 Text-to-3D 모델 학습에 처음 제안된 Loss 방식.</p>
<p>이미지-텍스트 모델(CLIP)을 활용하여 텍스트 조건에 맞는 3D 생성 모델을 학습.</p>
<hr>
<h2 id="diffusion">Diffusion</h2>
<h3 id="forward-process">Forward process</h3>
<p>데이터를 점점 노이즈로 변환하는 과정.
데이터 $x₀$에서 시작하여 시간 $t$에 따라 점진적으로 노이즈를 추가:</p>
<p>$$
q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{\alpha_t} x_{t-1}, (1 - \alpha_t)I)
$$</p>
<h3 id="reverse-process">Reverse process</h3>
<p>노이즈 데이터를 점진적으로 복원하는 과정.
학습 대상 분포는 $p_θ(x_{t-1} | x_t)$.</p>
<p>$$
\mathcal{L} = D_{\text{KL}}(q(x_{t-1}|x_t) || p_\theta(x_{t-1}|x_t))
$$</p>
<p>Forward process에서 관찰된 데이터로 Reverse process 분포를 학습하여 MLE를 최대화.</p>
<p>학습 대상: $P_{\theta}(X_{t-1}|X_{t})$; 확률분포 $q$에서 관측한 값으로 확률분포 $p_\theta$의 likelihood를 구했을  때 MLE.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Spring Security 기초]]></title>
            <link>https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 10 Jan 2025 05:53:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88#spring-security">Spring Security</a>
<a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88#authentication--authorization">Authentication &amp; Authorization</a>
<a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88#spring-filter">Spring Filter</a>
<a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88#spring-security-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98">Spring Security Architecture</a>
<a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88#%EA%B8%B0%ED%83%80-%ED%8A%B9%EC%A7%95">기타 특징</a></p>
</blockquote>
<h2 id="spring-security">Spring Security</h2>
<p>인증, 권한 부여, 보호 기능을 제공하는 프레임워크.</p>
<blockquote>
<p>Spring Security is a framework that provides <strong>authentication</strong>, <strong>authorization</strong>, and <strong>protection</strong> against common attacks.
<a href="https://docs.spring.io/spring-security">docs.spring.io</a></p>
</blockquote>
<h2 id="authentication--authorization">Authentication &amp; Authorization</h2>
<blockquote>
<p>Authentication is how we verify the identity of who is trying to access a particular resource. <a href="https://docs.spring.io/spring-security/reference/features/authentication/index.html">docs.spring.io</a>
Authorization is determining who is allowed to access a particular resource. <a href="https://docs.spring.io/spring-security/reference/features/authorization/index.html">docs.spring.io</a></p>
</blockquote>
<h3 id="authentication인증">Authentication(인증)</h3>
<p>리소스에 접근하려는 사용자의 신원을 증명하는 것.</p>
<h3 id="authorization권한-부여">Authorization(권한 부여)</h3>
<p>리소스에 대한 사용자의 접근 권한을 확인하는 것.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/a1834624-38a0-4934-b1c2-4723953db6e6/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>Popular Authentication Techniques</th>
<th>Popular Authorization Techniques</th>
</tr>
</thead>
<tbody><tr>
<td>Password-Based Authentication</td>
<td>*<em>Role-Based Access Controls (RBAC) *</em></td>
</tr>
<tr>
<td>Passwordless Authentication</td>
<td><strong>JSON web token (JWT) Authorization</strong></td>
</tr>
<tr>
<td>2FA/<strong>MFA</strong> (Two-Factor Authentication / Multi-Factor Authentication)</td>
<td>SAML Authorization</td>
</tr>
<tr>
<td>Single sign-on (SSO)</td>
<td>OpenID Authorization</td>
</tr>
<tr>
<td>Social authentication</td>
<td><strong>OAuth 2.0 Authorization</strong></td>
</tr>
</tbody></table>
<p><a href="https://www.geeksforgeeks.org/difference-between-authentication-and-authorization/">geeksforgeeks.org</a></p>
<hr>
<h2 id="spring-filter">Spring Filter</h2>
<blockquote>
<p>Spring Security’s Servlet support is based on <strong>Servlet Filters</strong> <a href="https://docs.spring.io/spring-security/reference/servlet/architecture.html">docs.spring.io</a></p>
</blockquote>
<h3 id="servlet">Servlet</h3>
<p>Java 기반의 웹 애플리케이션에서 <strong>클라이언트의 요청을 처리하고, 그에 대한 응답을 생성</strong>하는 서버 측 프로그램.
스프링 MVC에서 Servlet이란 <code>DispatcherServlet</code>의 인스턴스.</p>
<h3 id="servlet-filter">Servlet Filter</h3>
<p><strong>Servlet 요청 및 응답</strong>을 가로채고 처리하는 기능을 한다.
요청이 서블릿에 도달하기 전에, 또는 서블릿에서 응답이 클라이언트로 전달되기 전에 특정 로직을 실행한다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/1896fa55-5bdb-4cdb-8c6e-0ab9cf498b5c/image.png" alt=""></p>
<blockquote>
<h4 id="filter-vs-interceptor">Filter vs Interceptor</h4>
<table>
<thead>
<tr>
<th>Filter</th>
<th>Interceptor</th>
</tr>
</thead>
<tbody><tr>
<td>DispatcherServlet 전/후 로직 처리</td>
<td>Dispatcher Servlet이 Controller를 호출하기 전/후에</td>
</tr>
<tr>
<td>웹 컨테이너(Web Container)가 관리</td>
<td>스프링 컨테이너(Spring Container)가 관리</td>
</tr>
<tr>
<td>스프링 외부 기술</td>
<td>스프링 내부 기술</td>
</tr>
<tr>
<td><a href="https://mangkyu.tistory.com/173">mangkyu.tistory.com</a></td>
<td></td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/jiu-jung/post/76e2a03c-c54e-4c40-92fc-671841482eeb/image.png" alt=""></td>
<td></td>
</tr>
</tbody></table>
</blockquote>
<hr>
<h2 id="spring-security-아키텍처">Spring Security 아키텍처</h2>
<h3 id="filterchain"><code>FilterChain</code></h3>
<p>클라이언트가 애플리케이션에서 요청을 받으면 웹 컨테이너는 <code>FilterChain</code>를 생성한다.
<code>FilterChain</code>는 <strong>여러개의 Filter와 단일 Servlet 인스턴스</strong>로 구성된다. 필터의 순서는 보안 로직 처리에 영향을 미치므로 매우 중요하다.
<img src="https://velog.velcdn.com/images/jiu-jung/post/02314a83-c61d-420e-ad57-4321c62c9e6f/image.png" alt=""></p>
<h3 id="delegatingfilterproxy"><code>DelegatingFilterProxy</code></h3>
<blockquote>
<p>Spring provides a Filter implementation named DelegatingFilterProxy that allows bridging between the Servlet container’s lifecycle and Spring’s ApplicationContext. <a href="https://docs.spring.io/spring-security/reference/servlet/architecture.html">docs.spring.io</a></p>
</blockquote>
<p>스프링은 Filter의 구현체인 <code>DelegatingFilterProxy</code>를 제공한다. 이는 <strong>Servlet Container와 Spring의 IOC Container를 연결해주는 다리</strong> 역할을 하는 필터이다.
Servlet Container는 Spring Bean을 직접 사용할 수 없으므로, <code>DelegatingFilterProxy</code>가 Spring Bean에게 작업을 위임한다.
즉, <code>DelegatingFilterProxy</code>는 보안 작업을 수행하지 않고, Spring 컨테이너에 작업을 위임하는 역할만 하는 Servlet Filter이다.
<img src="https://velog.velcdn.com/images/jiu-jung/post/c6f6f9af-0fb0-43f7-8fa4-772c99d8fddc/image.png" alt=""></p>
<h3 id="filterchainproxy"><code>FilterChainProxy</code></h3>
<p>Spring Security에서 제공하는 특별한 Filter로, 이를 통해 <strong>여러 Filter 인스턴스를 <code>SecurityFilterChain</code>을 통해 관리</strong>할 수 있다.
<code>DelegatingFilterProxy</code>로 감싸져 있으며, 요청을 적합한 <code>SecurityFilterChain</code>으로 위임한다.
<code>SecurityFilterChain</code>의 URL 패턴이 겹칠 경우, 가장 먼저 매칭되는 필터 체인만 호출된다.
<img src="https://velog.velcdn.com/images/jiu-jung/post/559f4267-a647-4483-a326-8b38d0b646fb/image.png" alt=""></p>
<h3 id="securityfilterchain"><code>SecurityFilterChain</code></h3>
<ul>
<li><code>SecurityFilterChain</code>은 <code>FilterChainProxy</code>가 현재 요청에 대해 호출해야 할 Spring Security Filter 인스턴스를 결정하는 데 사용된다. </li>
<li><code>SecurityFilterChain</code> 내부의 Security Filter들은 일반적으로 Bean이지만, <code>DelegatingFilterProxy</code>가 아닌 <code>FilterChainProxy</code>에 등록된다. <code>FilterChainProxy</code>를 사용하는 것은 직접 Servlet 컨테이너나 <code>DelegatingFilterProxy</code>에 등록하는 것과 비교하여 다음과 같은 여러 가지 이점을 제공한다.<ol>
<li>Spring Security Servlet 지원의 시작점 제공</li>
<li>중앙 집중화된 필터 관리</li>
<li>유연한 필터 체인 호출 조건</li>
</ol>
</li>
<li>여러개의 <code>SecurityFilterChain</code> 인스턴스
<img src="https://velog.velcdn.com/images/jiu-jung/post/7435dc6d-849f-4b52-955c-dffe12e2e454/image.png" alt="">
URL과 매치되는 첫번째 <code>SecurityFilterChain</code>만 호출된다. 따라서, 필터 체인의 선언 순서가 매우 중요하다.
각 <code>SecurityFilterChain</code>은 독립적이며, 여러개 또는 0개의 Filter 인스턴스를 가질 수 있다. (0개의 경우 특정 요청을 무시할 때 사용된다.)</li>
</ul>
<h3 id="security-filters">Security Filters</h3>
<p>Security Filter는 <code>SecurityFilterChain</code> API와 함께 <code>FilterChainProxy</code>에 삽입된다. Security Filter는 일반적으로 <code>HttpSecurity</code> 인스턴스를 이용하여 선언된다.</p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults())
            .authorizeHttpRequests(authorize -&gt; authorize
                .anyRequest().authenticated()
            );

        return http.build();
    }

}</code></pre>
<p>위의 Configuration은 다음과 같은 순서의 Filter를 설정한다.
<code>CsrfFilter(.csrf) &gt; UsernamePasswordAuthenticationFilter(.formLogin) &gt; BasicAuthenticationFilter(.httpBasic) &gt; AuthorizationFilter(.authorizeHttpRequests)</code>
이렇게 특정 순서로 수행되며, 순서는 <a href="https://github.com/spring-projects/spring-security/blob/6.4.2/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java">github</a>에서 확인할 수 있다.</p>
<h2 id="기타-특징">기타 특징</h2>
<ol>
<li><p><strong>Spring MVC와 독립적인 관리</strong><br>REST API에서 JWT 또는 OAuth 2.0을 활용해 스프링 MVC와 독립적으로 동작.</p>
</li>
<li><p><strong>Java Config 기반 설정</strong><br>@EnableWebSecurity를 통해 보안 설정을 활성화.
주로 <code>SecurityFilterChain</code>과 <code>AuthenticationManager</code>를 활용.</p>
</li>
<li><p><strong>어노테이션 지원</strong><br>Spring Security 3.2 이후로 XML 설정 없이 어노테이션으로 간단히 설정 가능.
<code>@PreAuthorize</code>, <code>@PostAuthorize</code></p>
</li>
<li><p><strong>기본 인증 방식</strong>  </p>
<ul>
<li>기본적으로 <strong>세션과 쿠키</strong> 기반 인증.</li>
<li>REST API에서는 <strong>토큰 기반 인증 방식(OAuth 2.0, JWT)</strong>  권장.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="참고자료">참고자료</h2>
<p><a href="https://velog.io/@eileen0379/SPRING-WIL-8">velog.io/@eileen0379</a>
<a href="https://docs.spring.io/spring-security">docs.spring.io</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 스프링 시큐리티 및 OAuth2 통합]]></title>
            <link>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-8</link>
            <guid>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-8</guid>
            <pubDate>Wed, 08 Jan 2025 07:56:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>GDG on Campus Backend-Spring 스터디 WIL
<strong>Week 08 - 스프링 시큐리티 및 OAuth2 통합</strong></p>
</blockquote>
<h1 id="spring-security-기초">Spring Security 기초</h1>
<p><a href="https://velog.io/@jiu-jung/Spring-Spring-Security-%EA%B8%B0%EC%B4%88">velog.io/@jiu-jung</a></p>
<hr>
<h1 id="access-control">Access Control</h1>
<h2 id="rbacrole-based-access-control-역할-기반-접근-제어">RBAC(Role Based Access Control, 역할 기반 접근 제어)</h2>
<p>정보에 대한 접근 권한을 역할에 따라 결정.</p>
<h3 id="장점">장점</h3>
<ul>
<li>보안 강화 &amp; 데이터 보호</li>
<li>그룹화 통해 권한 관리 간소화</li>
<li>다수의 사람에 대해 유연하고 직관적이며 효율적인 통제 가능<h3 id="기본-규칙">기본 규칙</h3>
</li>
<li>Role Assignment (역할 할당)</li>
<li>Role Authorization (역할 기반 권한 부여)</li>
<li>Permission Authorization (권한 승인)</li>
</ul>
<h3 id="구현-예시">구현 예시</h3>
<p><a href="https://www.geeksforgeeks.org/example-of-rbac-in-spring-security">geeksforgeeks.org</a></p>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                .requestMatchers(&quot;/user/**&quot;).hasRole( &quot;USER&quot;)
                .anyRequest().authenticated()
                .and()
                .formLogin()
               // Specify the URL for the registration page

                .and()
                .logout().logoutSuccessUrl(&quot;/&quot;).permitAll();

        return http.build();
    }

}</code></pre>
<pre><code class="language-java">@Configuration
public class UserDetailsConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsManager userDetailsManager() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(
                User.withUsername(&quot;admin&quot;)
                        .password(passwordEncoder().encode(&quot;admin&quot;))
                        .roles(&quot;ADMIN&quot;)
                        .build()
        );
        manager.createUser(
                User.withUsername(&quot;user&quot;)
                        .password(passwordEncoder().encode(&quot;user&quot;))
                        .roles(&quot;USER&quot;)
                        .build()
        );
        return manager;
    }
}</code></pre>
<hr>
<h2 id="메소드-수준-보안-method-level-security">메소드 수준 보안 (Method-Level Security)</h2>
<p>RBAC를 서비스 계층으로 확장한 방식.</p>
<ul>
<li><p><code>@Configuration</code> 클래스에서 <code>@EnableMethodSecurity</code> 활성화 필요</p>
<pre><code class="language-java">@Configuration
@EnableMethodSecurity
public class SecurityConfig { 
    // Other security configurations here 
}</code></pre>
</li>
<li><p><code>@PreAuthorize</code> : 메소드 실행 전 권한 체크</p>
<pre><code class="language-java">@PreAuthorize(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or (hasRole(&#39;ROLE_USER&#39;) and #userId == principal.userId)&quot;) 
public void updateUser(int userId) { 
// Method logic here 
}</code></pre>
</li>
<li><p><code>@PostAuthorize</code> : 메소드 실행 후 반환값에 따라 접근 제어</p>
</li>
<li><p><code>@Secured</code> : 특정 역할 기반으로 접근 제어</p>
<pre><code class="language-java">@Secured(&quot;ROLE_ADMIN&quot;) 
public void deleteUser(int userId) { 
// Method logic here 
}</code></pre>
</li>
</ul>
<hr>
<h2 id="aclaccess-control-list-액세스-제어-목록">ACL(Access Control List, 액세스 제어 목록)</h2>
<p>개별 리소스에 대한 접근을 세분화하여 정의 및 관리</p>
<h3 id="장점-1">장점</h3>
<ul>
<li>리소스 단위 권환 관리 -&gt; 리소스(도메인 객체)에 사용자/그룹별 권한 부여</li>
<li><code>hasPermission()</code>을 통해 접근성 관리 DB화</li>
<li>Spring security는 <code>spring-security-acl</code> 라이브러리 제공)</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>도메인 객체가 갖는 접근성에 대한 정보 활용 X -&gt; ACL 테이블 별도로 관리</li>
<li>사용자/도메인 객체가 많아지면 접근 권한에 대한 경우의 수가 기하급수적으로 증가</li>
<li>기술 난이도가 높아 유지보수 어려움</li>
</ul>
<h3 id="기본-구조">기본 구조</h3>
<ul>
<li>Sid(Security Identity) - 사용자/그룹</li>
<li>Permission - 객체에 대한 권한</li>
<li>Domain Object - 보호할 객체</li>
</ul>
<h3 id="acl-도메인-모델">ACL 도메인 모델</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/7e3b840d-7596-4097-926f-a1a4cba10e2b/image.png" alt=""></p>
<p><code>ACL_CLASS</code> : 도메인 객체의 종류
<code>ACL_SID</code> : 권한의 주체가 되는 사용자 정보 &amp; Role 정보
<code>ACL_OBJECT_IDENTITY</code> : 보안의 대상이 되는 정보 객체
<code>ACL_ENTRY</code> : 접근 권한 정보</p>
<hr>
<h1 id="oauth-20-open-authorization">OAuth 2.0 (Open Authorization)</h1>
<p><a href="https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html">docs.spring.io</a></p>
<p>인터넷 사용자가 애플리케이션에 직접 비밀번호를 제공하지 않고, 권한을 위임하여 리소스에 접근하도록 하는 프로토콜</p>
<ul>
<li>개방형 표준 프로토콜로, third-party 프로그램에게 리소스 소유자를 대신해 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식으로 작동</li>
<li>third-party 프로그램(구글, 카카오 등)에게 로그인 및 개인정보 관리에 대한 권한을 위임하여 third-party 프로그램이 가지고 있는 사용자에 대한 리소스 조회 가능</li>
</ul>
<h2 id="주요-구성-요소">주요 구성 요소</h2>
<ul>
<li>Resource Owner(리소스 소유자) : 보호된 자원에 접근할 수 있는 자격을 부여해주는 객체. OAuth 2.0 프로토콜 흐름에서 클라이언트 인증하는 역할 수행</li>
<li>Client(애플리케이션) : 보호된 자원을 사용하기 위해 접근을 요청하는 애플리케이션</li>
<li>Authentication Server : 사용자의 보호된 자원을 호스팅하는 서버</li>
<li>Resource Server : Authentication Server로 인증/인가 수행. Client의 접근 자격을 확인하고 Access Token을 발급하여 권한을 부여한다.</li>
</ul>
<h2 id="인증-방식">인증 방식</h2>
<h3 id="authorization-code-grant">Authorization Code Grant</h3>
<p>기본이 되는 방식
간편 로그인에서 많이 사용
권한 부여 승인을 위해 자체 생성한 Authorization Code 를 전달하는 방식으로 많이 쓰임
<img src="https://velog.velcdn.com/images/jiu-jung/post/72cbf972-9349-4b9c-b242-d33a56573172/image.png" alt=""></p>
<h3 id="implicit-grant">Implicit Grant</h3>
<p>자격증명을 안전하게 저장하기 힘든 클라이언트(ex. 자바스크립트 등의 스크립트언어 없이 사용한 브라우저)에게 최적화된 방식
권한 부여 승인 코드 없이 바로 Access Token 발급
응답성과 효율성은 높아지지만 Access Token이 URL로 전달된다는 단점 존재
<img src="https://velog.velcdn.com/images/jiu-jung/post/6073168e-1b9a-456b-af03-a68ba3608ea3/image.png" alt=""></p>
<h3 id="resource-owner-password-credential-grant자원-소유자-자격증명-승인-방식">Resource Owner Password Credential Grant(자원 소유자 자격증명 승인 방식)</h3>
<p>username, password로 Access_Token을 받는 방식
권한서버, 리소스 서버, 클라이언트가 모두 같은 시스템에 있을 때 사용
<img src="https://velog.velcdn.com/images/jiu-jung/post/942da740-f7d2-4599-a645-ca11547edbf4/image.png" alt=""></p>
<h3 id="client-credential-grant클라이언트-자격증명-승인-방식">Client Credential Grant(클라이언트 자격증명 승인 방식)</h3>
<p>클라이언트의 자격증명만으로 Access Token 획득하는 가장 간단한 방식
자신이 관리하는 리소스 혹은 권한 서버에 해당 클라이언트를 위한 제한된 리소스 접근 권한이 설정되어 있는 경우 사용
<img src="https://velog.velcdn.com/images/jiu-jung/post/9012599c-3677-4cb3-abb6-a0b9d3dcf26d/image.png" alt=""></p>
<h1 id="spring-security--oauth-20-통합">Spring Security &amp; Oauth 2.0 통합</h1>
<ol>
<li>`application.property``파일에 Oauth2.0 클라이언트 설정 추가</li>
<li>build.gradle에 의존성 추가</li>
<li><code>SecurityConfig</code> 클래스에서 리소스 서버의 보안 구성 정의</li>
</ol>
<p>장점</p>
<ul>
<li>복잡한 인증 및 인가 요구사항을 효과적으로 관리 가능</li>
<li>OAuth 2.0 -&gt; 사용자 데이터에 대한 안전한 접근 권한 부여 가능</li>
<li>Spring Security -&gt; 프로세스를 간소화하여 구현할 수 있는 풍부한 기능 제공</li>
<li>개발자는 보안이 강화된 애플리케이션을 쉽고 빠르게 구축 가능</li>
</ul>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://velog.io/@eileen0379/SPRING-WIL-8">velog.io/@eileen0379</a>
<a href="https://techdocs.broadcom.com/us/en/vmware-tanzu/platform-services/single-sign-on-for-tanzu/1-14/sso-tanzu/grant-types.html">techdocs.broadcom.com</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring 실습] 스프링 프로젝트 GCP로 배포하기]]></title>
            <link>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GCP%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GCP%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 03 Jan 2025 13:07:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>스프링 GCP 배포</strong>
온라인 도서 관리 프로그램을 Google Cloud Platform로 배포해보자.</p>
</blockquote>
<h1 id="배경지식">배경지식</h1>
<h2 id="gcp">GCP</h2>
<p>Google에서 개발한 클라우드 컴퓨팅 플랫폼.
사용자가 클라우드 환경에서 애플리케이션을 개발, 배포 및 관리할 수 있도록 돕는 플랫폼.</p>
<h2 id="vm-인스턴스">VM 인스턴스</h2>
<p>Google 인프라에서 호스팅되는 가상 머신(VM)
(Compute Engine 인스턴스, 가상 머신 인스턴스 , VM 인스턴스, VM은 모두 같은 동의어)
<a href="https://cloud.google.com/compute/docs/instances?hl=ko">cloud.google.com</a></p>
<blockquote>
<p>GCP의 VM 인스턴스를 생성한 후, 로컬 컴퓨터와 연결할 것이다.</p>
</blockquote>
<h2 id="sshsecure-shell-protocol">SSH(Secure Shell Protocol)</h2>
<p>네트워크 프로토콜로, 안전한 통신을 위해 암호화를 사용하여 데이터와 명령을 전송
이 프로토콜은 RSA 보안 방식을 기반으로 하며, 공개 키(Public Key)와 개인 키(Private Key)를 활용하여
인증과 보안을 유지한다.</p>
<blockquote>
<p>로컬 pc에서 Public Key를 생성하고 이를 GCP에 등록하여, 로컬 pc와 GCP의 VM 인스턴스 간 연결을 설정할 것이다.</p>
</blockquote>
<hr>
<blockquote>
<h2 id="스프링-gcp-배포-개요">스프링 GCP 배포 개요</h2>
</blockquote>
<ol>
<li>GCP 준비</li>
<li>VM 인스턴스 생성 및 설정</li>
<li>고정 IP 및 방화벽 설정</li>
<li>SSH key 생성 및 등록</li>
<li>로컬 pc에서 원격 VM 연결</li>
<li>배포</li>
</ol>
<h1 id="1-gcp-준비">1. GCP 준비</h1>
<h2 id="google-cloud-가입-및-새-프로젝트-생성">Google Cloud 가입 및 새 프로젝트 생성</h2>
<ol>
<li><p>Google Cloud 가입
<a href="https://cloud.google.com">https://cloud.google.com</a>
<img src="https://velog.velcdn.com/images/jiu-jung/post/0b79c909-c6f4-40d0-a229-523b4e8f16e0/image.png" alt=""></p>
</li>
<li><p>새 프로젝트 생성
<img src="https://velog.velcdn.com/images/jiu-jung/post/4e6f98d0-7776-4c71-961e-1c1fa23b9f29/image.png" alt=""></p>
</li>
</ol>
<h1 id="2-vm-인스턴스-생성-및-설정">2. VM 인스턴스 생성 및 설정</h1>
<h2 id="vm-인스턴스-생성">VM 인스턴스 생성</h2>
<ol>
<li><p>생성한 프로젝트로 진입하기.</p>
</li>
<li><p><code>Computer Engine</code> &gt; <code>VM 인스턴스</code> &gt; <code>인스턴스 만들기</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/a30dcb04-8642-4e07-8fc2-c20e499fa964/image.png" alt=""></p>
</li>
<li><p>머신 구성 설정
<img src="https://velog.velcdn.com/images/jiu-jung/post/88c78f4f-f897-41ac-b028-7851d9fae701/image.png" alt=""></p>
<blockquote>
<p>이름: 원하는 이름 (ex. instance)
리전: 지역 (ex. 서울)
머신 유형: e2-micro (용도에 맞게)</p>
</blockquote>
</li>
<li><p>OS 및 스토리지 설정
<img src="https://velog.velcdn.com/images/jiu-jung/post/ffedbeaf-85a7-4919-95ec-9d859bd3f07b/image.png" alt=""></p>
<blockquote>
<p>운영체제: <code>Ubuntu</code></p>
</blockquote>
</li>
<li><p>네트워킹 설정
<img src="https://velog.velcdn.com/images/jiu-jung/post/bedd6245-8257-47ea-b92a-4e859b98b33a/image.png" alt=""></p>
<blockquote>
<p>방화벽: <code>HTTP 트래킹 허용</code>, <code>HTTP 트래킹 허용</code></p>
</blockquote>
</li>
<li><p>보안 설정
<img src="https://velog.velcdn.com/images/jiu-jung/post/460a6997-b828-409c-ad92-0f0aaab0dc16/image.png" alt=""></p>
<blockquote>
<p>액세스 범위: <code>모든 Cloud API에 대한 전체 액세스 허용</code></p>
</blockquote>
</li>
<li><p>생성된 VM 인스턴스 확인
<img src="https://velog.velcdn.com/images/jiu-jung/post/a917df58-8270-4385-a62f-69281329a6e2/image.png" alt="">외부 IP는 배포에 쓰이므로 메모해둔다.</p>
</li>
</ol>
<p>VM 인스턴스 생성 완료!</p>
<h2 id="vm-인스턴스-설정">VM 인스턴스 설정</h2>
<ol>
<li><code>VM 인스턴스</code> &gt; <code>SSH</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/95095ff8-8d7f-4ad6-b622-24c96a355ecd/image.png" alt=""><img src="https://velog.velcdn.com/images/jiu-jung/post/54e566b6-4e3e-47d2-a7e4-45cc22cafe8a/image.png" alt=""></li>
<li>root 계정 설정 및 Java 환경 설치
Spring 프로젝트의 java 버전에 맞는 Java 환경 설치<pre><code class="language-bash">sudo passwd
sudo su
sudo apt update
sudo apt-get install openjdk-21-jre
sudo apt-get install openjdk-21-jdk
java --version</code></pre>
</li>
</ol>
<h1 id="3-고정-ip-및-방화벽-설정">3. 고정 IP 및 방화벽 설정</h1>
<h2 id="고정-ip-설정">고정 IP 설정</h2>
<p>인스턴스가 재시작돼도 동일한 ip로 접속할 수 있도록 고정 IP를 설정하자.</p>
<ol>
<li><p><code>VPC 네트워크</code> &gt; <code>IP 주소</code> &gt; <code>외부 고정 IP 주소 예약</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/3593c893-89fe-4295-a003-9254ff18ae95/image.png" alt=""></p>
</li>
<li><p>고정 주소 예약
<img src="https://velog.velcdn.com/images/jiu-jung/post/d41826b6-2344-4ea2-9d9e-010db40bc922/image.png" alt=""></p>
<blockquote>
<p>이름: 원하는 이름 (ex. static-ip)
네트워크 서비스 계층: <code>표준</code>
연결대상: <strong><code>이전에 생성한 VM 인스턴스</code></strong> 선택</p>
</blockquote>
</li>
<li><p>생성된 외부 고정 IP 확인
<img src="https://velog.velcdn.com/images/jiu-jung/post/0971354e-3348-4539-9cb8-e5e5429f809e/image.png" alt=""></p>
</li>
</ol>
<p>고정 IP 설정 완료!</p>
<h2 id="방화벽-규칙-생성">방화벽 규칙 생성</h2>
<p>spring boot의 기본 포트인 8080포트를 열어주기 위해 방화벽 규칙을 생성하자.</p>
<ol>
<li><p><code>VPC 네트워크</code> &gt; <code>방화벽</code> &gt; <code>방화벽 규칙 만들기</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/6117c5b1-ee66-4d7d-849b-36ab68ce7518/image.png" alt=""></p>
</li>
<li><p>방화벽 규칙 만들기
<img src="https://velog.velcdn.com/images/jiu-jung/post/9c80fe4b-95df-4b37-b2ee-dd4f8227b537/image.png" alt=""></p>
<blockquote>
<p>이름: 원하는 이름 (ex. springboot-8080)
소스 IPv4 범위: <code>0.0.0.0/0</code>
프로토콜 및 포트: <code>TCP</code> 포트 <code>8080</code></p>
</blockquote>
<p>모든 IP주소에서 오는, TCP 프로토콜을 사용하고  목적지 포트 번호가 8080인 트래픽을 허용한다는 의미이다.</p>
</li>
<li><p>생성된 방화벽 규칙 확인
<img src="https://velog.velcdn.com/images/jiu-jung/post/0745520e-7d29-456e-82fa-69c784ddc619/image.png" alt=""></p>
</li>
</ol>
<p>방화벽 규칙 생성 완료!</p>
<h1 id="4-ssh-key-생성-및-등록">4. SSH key 생성 및 등록</h1>
<h2 id="로컬-pc에서-ssh-key-생성">로컬 PC에서 SSH Key 생성</h2>
<p>로컬 pc에서 VM 인스턴스에 접근하기 위해 로컬 pc에서 SSH key를 생성하자.</p>
<ol>
<li><p>Windows Powershell 관리자 모드로 실행</p>
</li>
<li><p>사용자 계정으로 이동</p>
<pre><code class="language-bash">PS C:\WINDOWS\system32&gt; cd ..
PS C:\WINDOWS&gt; cd ..
PS C:\&gt; cd Users
PS C:\Users&gt; cd [username]</code></pre>
<ol start="3">
<li><code>.ssh</code> 폴더 생성<pre><code class="language-bash">PS C:\Users\[username]&gt; mkdir .ssh
PS C:\Users\[username]&gt; cd .ssh</code></pre>
</li>
</ol>
</li>
<li><p>SSH key 생성</p>
<pre><code class="language-bash"> PS C:\Users\[username]\.ssh&gt; ssh-keygen -t rsa -f [key name] -C [GCP account]
 예) PS C:\Users\[username]\.ssh&gt; ssh-keygen -t rsa -f gcp_key -C abcd@gmail.com    </code></pre>
</li>
<li><p>생성된 public key 확인</p>
<pre><code class="language-bash">PS C:\Users\[username]\.ssh&gt; type [key name].pub</code></pre>
<p>생성된 public key가 출력된다. 출력된 값은 다음 단계에서 필요하니 메모해둔다.</p>
</li>
</ol>
<h2 id="gcp-console에서-ssh-key-등록">GCP Console에서 SSH Key 등록</h2>
<ol>
<li><p><code>Compute Engine</code> &gt; <code>메타데이터</code> &gt; <code>수정</code> &gt; <code>SSH 키</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/fd12a923-3f97-4022-8c95-8cdd7d2b57ba/image.png" alt=""></p>
</li>
<li><p>로컬 pc에서 생성한 SSH public key 입력
<img src="https://velog.velcdn.com/images/jiu-jung/post/1c526788-cfc6-43cf-8989-7cc7337fba08/image.png" alt=""></p>
</li>
<li><p>로컬 pc Windows powershell에서 등록 확인</p>
<pre><code class="language-bash">ssh -i /Users/[user name]/.ssh/[key name] [GCP 계정 앞부분]@[VM 외부 IP]
예)
ssh -i /Users/[user name]/.ssh/[key name] abcd@10.20.30.40</code></pre>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d7da0c83-ced0-473d-b661-6fd6fdd7703b/image.png" alt=""></p>
</li>
</ol>
<h1 id="5-로컬-pc에서-원격-vm-연결">5. 로컬 pc에서 원격 VM 연결</h1>
<h2 id="vscode를-통한-ssh-연결">VSCode를 통한 SSH 연결</h2>
<ol>
<li><p>VS Code에서 확장프로그램 <code>Remote - SSH</code> 설치</p>
</li>
<li><p>Remote Explorer 아이콘 &gt; <code>+</code> 버튼
<img src="https://velog.velcdn.com/images/jiu-jung/post/faf16164-7585-4812-a4fd-ab1f59cac12b/image.png" alt="">
좌측 패널에 생성된 Remote Explorer 아이콘 클릭
(생성되지 않으면 우클릭해서 선택)</p>
</li>
<li><p><code>ssh [GCP 계정 앞부분]@[VM 외부 ip]</code> 입력 후 엔터
<img src="https://velog.velcdn.com/images/jiu-jung/post/ec2b892b-b40a-4619-a3af-b1cd128d0f96/image.png" alt=""></p>
</li>
<li><p>config 파일 열기
<img src="https://velog.velcdn.com/images/jiu-jung/post/6360bcc8-dca6-41ca-8e4c-033b242eee55/image.png" alt=""></p>
</li>
<li><p>config 파일 수정
<img src="https://velog.velcdn.com/images/jiu-jung/post/c22021ce-f0c1-478c-b6e8-b486f72cd69d/image.png" alt=""></p>
<blockquote>
<p>Host: 원하는 이름
HostName: VM 외부 IP
User: GCP 계정 앞부분
IdentityFile: 키 파일 경로</p>
</blockquote>
</li>
<li><p><code>Remote Explorer</code> &gt; <code>SSH</code> &gt; <code>VM 외부 IP</code> &gt; <code>Connect</code> &gt; SSH key 비밀번호 입력
<img src="https://velog.velcdn.com/images/jiu-jung/post/f376f8a3-026f-46f3-a156-47962fb40280/image.png" alt=""><img src="https://velog.velcdn.com/images/jiu-jung/post/831b0804-be87-4102-87e7-4f41a0065364/image.png" alt=""></p>
</li>
<li><p>연결 확인
터미널을 열어서 VM 인스턴스에 연결되면 연결 성공
<img src="https://velog.velcdn.com/images/jiu-jung/post/d8bd7a44-6d89-4191-bad3-7b91ec568158/image.png" alt=""></p>
</li>
</ol>
<h1 id="6-배포">6. 배포</h1>
<h2 id="jar-생성-및-이동"><code>.jar</code> 생성 및 이동</h2>
<ol>
<li><p><code>IntelliJ</code> &gt; <code>우측 패널 gradle 로고</code> &gt; <code>프로젝트명</code> &gt; <code>Tasks</code> &gt; <code>build</code> &gt; <code>bootJar</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/2add7b13-300f-4bf6-a1f8-902e0067a1b6/image.png" alt="">프로젝트 폴더 build &gt; libs 경로에 .jar 파일 생성 확인</p>
</li>
<li><p><code>VSCode</code> &gt; <code>(SSH 연결 후)</code> &gt; <code>EXPLORER</code> &gt; <code>Open Folder</code>
<img src="https://velog.velcdn.com/images/jiu-jung/post/121bb16f-28ec-4db6-8dee-30c920f6d694/image.png" alt=""></p>
</li>
<li><p><code>.jar</code> 파일 이동
<img src="https://velog.velcdn.com/images/jiu-jung/post/3343c09c-ab0e-4df9-b6fe-9e066276d579/image.png" alt=""></p>
</li>
</ol>
<h2 id="spring-boot-실행">Spring boot 실행</h2>
<ol>
<li><p>VS code 터미널에서 스프링 실행</p>
<pre><code class="language-bash">nohup java -jar [파일명].jar</code></pre>
<blockquote>
<p>Java로 작성된 애플리케이션을 실행하면서 터미널 세션 종료와 무관하게 계속 실행되도록 설정하는 명령어.
표준 출력과 표준 에러 출력이 nohup.out 파일에 저장된다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/b21a8272-714a-4001-b255-8c3df8d76e0c/image.png" alt=""></p>
</li>
<li><p>실행 확인
nohup.out을 열면 스프링 부트가 실행된 것을 확인할 수 있다.<img src="https://velog.velcdn.com/images/jiu-jung/post/e806f1a2-9366-45db-96f2-a942c7bcc259/image.png" alt=""></p>
</li>
</ol>
<h2 id="서비스-확인">서비스 확인</h2>
<p>브라우저에서 http://[VM외부IP주소]:8080 으로 접속
<img src="https://velog.velcdn.com/images/jiu-jung/post/99d28616-5904-4e28-a14d-3a0ffaf7b31e/image.png" alt=""></p>
<p><strong>배포 완료!</strong></p>
<hr>
<h1 id="참고자료">참고자료</h1>
<p><a href="https://developerkkyu37.tistory.com/81">developerkkyu37.tistory.com</a>
<a href="https://developerkkyu37.tistory.com/82">developerkkyu37.tistory.com</a>
<a href="https://velog.io/@hyeri_hello/Spring-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-GCP-Google-Cloud-Platform-%EC%84%9C%EB%B2%84-%EB%B0%B0%ED%8F%AC">velog.io/@hyeri_hello</a>
<a href="https://velog.io/@hyeri_hello/Mac-OS-SSH-%EC%A0%91%EC%86%8D-%EB%B0%A9%EB%B2%95-%EB%A1%9C%EC%BB%AC-%EC%9B%90%EA%B2%A9-GCP-%ED%99%9C%EC%9A%A9">velog.io/@hyeri_hello</a>
<a href="https://velog.io/@chaejm55/%EB%B0%B1%EC%97%94%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%EC%9A%A9-GCP-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0Compute-Engine-Cloud-SQL-IntelliJ-Spring-Boot">velog.io/@chaejm55</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] JPA 및 Hibernate를 사용한 ORM 통합]]></title>
            <link>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-7-ej4dy9dl</link>
            <guid>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-7-ej4dy9dl</guid>
            <pubDate>Sat, 28 Dec 2024 13:25:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>GDG on Campus Backend-Spring 스터디 WIL
<strong>Week 07 - JPA 및 Hibernate를 사용한 ORM 통합</strong></p>
</blockquote>
<h2 id="ormobject-relational-mapping-이란"><code>ORM(Object-Relational Mapping)</code> 이란?</h2>
<p>객체 지향 프로그래밍에서, 객체와 관계형 데이터베이스의 테이블 간 데이터를 자동으로 매핑하는 기술</p>
<h2 id="jpajava-persistence-api-란"><code>JPA(Java Persistence API)</code> 란?</h2>
<ul>
<li>자바 표준 ORM 명세로, 애플리케이션과 데이터베이스 간 데이터 처리 로직을 추상화.</li>
<li>자바의 ORM(Object-Relational Mapping) 기술 표준으로 사용되는 인터페이스의 모음.
<img src="https://velog.velcdn.com/images/jiu-jung/post/ed4c3496-df65-440e-93ce-9a1433621973/image.png" alt=""></li>
<li>SQL 중심적인 개발 -&gt; 객체 중심적인 개발
<img src="https://velog.velcdn.com/images/jiu-jung/post/039800be-95f0-4d90-ad88-063f22ceca05/image.png" alt=""><ul>
<li>생산성 증가 : 반복적인 SQL 작성에서 벗어나 객체 중심으로 개발</li>
<li>유지보수 수월 : 데이터베이스 변경에 따른 영향 최소화</li>
<li>Object와 RDB간의 패러다임 불일치 해결 : 객체와 관계형 데이터베이스 간의 구조적 차이를 매핑으로 해결</li>
</ul>
</li>
</ul>
<h2 id="hibernate-란"><code>Hibernate</code> 란?</h2>
<ul>
<li>자바 진영의 다양한 ORM 프레임워크 중 가장 많이 사용되는 프레임워크</li>
<li>JPA(Java Persistence API) 표준의 구현체 중 하나로, JPA의 명세를 따르면서도 자체적인 추가 기능을 제공<ul>
<li>ORM 기능 : 객체와 관계형 데이터베이스 간의 매핑</li>
<li>JDBC 추상화 : 데이터베이스 연결과 명령 처리를 간소화.</li>
<li>JPQL(Java Persistence Query Language), 네이티브 SQL, Querydsl 등 다양한 쿼리 언어 지원</li>
<li>캐싱 기능 : 1차 및 2차 캐싱으로 성능 최적화</li>
<li>데이터베이스 독립성</li>
</ul>
</li>
<li><code>SessionFactory</code>를 사용해서 session을 만들어 DB와 커뮤니케이션
<img src="https://velog.velcdn.com/images/jiu-jung/post/63864b62-98f3-497c-99dd-accd052ea64e/image.png" alt=""></li>
</ul>
<h3 id="hibernate-사용-방법"><code>Hibernate</code> 사용 방법</h3>
<ol>
<li>엔티티 클래스 작성</li>
<li>entity manager 작성</li>
<li>crud 작업 수행</li>
</ol>
<h2 id="2차-캐싱">2차 캐싱</h2>
<ul>
<li>영속성 컨텍스트의 1차 캐싱을 넘어, 애플리케이션 전체에서 데이터를 캐싱하여 성능 최적화하는 방식</li>
<li>1차 캐싱(영속성 컨텍스트 내 캐싱)이 개별 세션에 국한된 반면, 2차 캐싱은 애플리케이션 전역에서 공유됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/146ea85a-9c0d-448a-afc7-3b93a1e102da/image.png" alt=""></p>
<h2 id="지연-로딩---즉시-로딩">지연 로딩 (&lt;-&gt; 즉시 로딩)</h2>
<ul>
<li>필요한 시점에 데이터를 로딩하는 방식</li>
<li>동작 원리<ul>
<li>엔티티를 조회할 때, 연관된 데이터를 프록시(Proxy) 객체로 대신 설정</li>
<li>연관(JOIN)된 데이터에 접근하면 Hibernate가 데이터베이스에서 필요한 데이터를 실시간으로 조회</li>
</ul>
</li>
</ul>
<h2 id="batch-fetching-전략"><code>Batch Fetching</code> 전략</h2>
<ul>
<li><p>JPA에서 지연 로딩(Lazy Loading)으로 인해 발생하는 N+1 문제를 해결하기 위한 전략</p>
<ul>
<li>N+1 문제: 하나의 엔티티를 조회한 후 연관된 데이터를 개별적으로 가져오면서 N번의 추가 쿼리가 발생하는 문제
예) 부모 엔티티 1개를 조회한 후 자식 엔티티를 10개 조회하면, 총 1 + 10 = 11개의 쿼리가 발생</li>
</ul>
</li>
<li><p>동작 원리</p>
<ul>
<li>연관된 데이터를 개별적으로 조회하는 지연로딩과 달리,</li>
<li>연관된 데이터의 ID를 모아서 한 번에 조회하여 쿼리 호출 횟수를 줄인다</li>
</ul>
</li>
</ul>
<h2 id="jpa-vs-hibernate-vs-spring-data-jpa"><code>JPA</code> vs <code>Hibernate</code> vs <code>Spring Data JPA</code></h2>
<p><a href="https://lealea.tistory.com/238">lealea.tistory.com</a></p>
<blockquote>
<p><strong>JPA</strong>: 객체-관계 매핑(ORM)을 위한 <strong>표준 명세</strong>인 <strong>인터페이스</strong>
<strong>Hibernate</strong>: JPA의 <strong>구현체</strong>. JPA의 다른 구현체도 존재
<strong>Spring Data JPA</strong>: JPA 기반 애플리케이션 개발을 보다 간편하게 만드는 <strong>라이브러리/프레임워크</strong></p>
</blockquote>
<p>Spring Data JPA로 구현하면 로그에 Hibernate 구동이 뜬다. 이는 Spring Data JPA는 내부적으로 Hibernate를 이용하기 때문이다!</p>
<hr>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://codingbyjhyunn.tistory.com/77">codingbyjhyunn.tistory.com</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Capstone] LGM: Large Multi-View Gaussian Model for High-Resolution 3D Content Creation 실행]]></title>
            <link>https://velog.io/@jiu-jung/Capstone-LGM-Large-Multi-View-Gaussian-Model-for-High-Resolution-3D-Content-Creation-%EC%8B%A4%ED%96%89</link>
            <guid>https://velog.io/@jiu-jung/Capstone-LGM-Large-Multi-View-Gaussian-Model-for-High-Resolution-3D-Content-Creation-%EC%8B%A4%ED%96%89</guid>
            <pubDate>Sun, 24 Nov 2024 07:03:31 GMT</pubDate>
            <description><![CDATA[<p>캡스톤디자인과창업프로젝트A | 2024-fall </p>
<blockquote>
<h3 id="lgm-large-multi-view-gaussian-model-for-high-resolution-3d-content-creation">LGM: Large Multi-View Gaussian Model for High-Resolution 3D Content Creation</h3>
<p><a href="https://me.kiui.moe/lgm/">Project page</a>
<a href="https://github.com/3DTopia/LGM">Github</a>
<a href="https://arxiv.org/abs/2402.05054">Paper</a>
<a href="https://huggingface.co/spaces/ashawkey/LGM">Demo</a></p>
</blockquote>
<blockquote>
<p><strong>Colab으로 LGM 실행하기 <a href="https://github.com/jiu-jung/LGM-colab">Colab 링크</a></strong></p>
</blockquote>
<h1 id="서론">서론</h1>
<p>우리팀은 연구 주제로 <strong>Single view 3D reconstruction</strong>을 잡았다. 현재 어떤 연구들이 있는지 찾아보고 직접 돌려보며 개선점을 파악했다. 
먼저 <a href="https://dreamgaussian.github.io/"><strong>DreamGaussian</strong></a>과 <a href="https://one-2-3-45.github.io/"><strong>One-2-3-45</strong></a>를 돌려봤다. <code>DreamGaussian</code>은 <strong>3DGS</strong> 기반이고, <code>One-2-3-45</code>은 <strong>NeRF</strong> 기반이다. 결과적으로 3DGS 기반이 성능도 더 좋았고, 시간과 리소스 측면에서도 효율적임을 발견했다. 이후 3DGS 기반 모델을 찾아보다가, <strong>LGM</strong>이라는 논문을 발견했다.
LGM을 돌렸을 때, <code>DreamGaussian</code>에서는 이상했던 형태가 엄청나게 개선되어서 놀랐다. 그리고 texture와 geometry 측면에서 개선할 점을 발견했다. 자세한 설명은 <a href="https://velog.io/@jiu-jung/Capstone-LGM-Large-Multi-View-Gaussian-Model-for-High-Resolution-3D-Content-Creation-%EC%8B%A4%ED%96%89#%EC%8B%A4%ED%96%89">실행</a> 파트에서 설명하겠다.</p>
<hr>
<h1 id="논문-리뷰">논문 리뷰</h1>
<p>간단하게만 LGM 논문을 정리하겠다.</p>
<h2 id="pipeline">Pipeline</h2>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/a484e610-150a-4003-8e0b-31f09f699fd4/image.png" alt="">
Image Input의 경우)</p>
<ol>
<li>Single image 를 입력받아서, <code>ImageDream</code>을 통해 multi-view(4가지 카메라 위치) images를 생성한다.</li>
<li><code>Large Multi-View Gaussian Model</code>을 통해 3D Gaussian을 생성한다</li>
<li>Mesh Extraction</li>
</ol>
<hr>
<h2 id="architecture">Architecture</h2>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/f953e29a-1dad-4609-9bce-7e7d5d6b5ee3/image.png" alt=""></p>
<h3 id="assymetric-u-net"><strong>Assymetric U-Net</strong></h3>
<ul>
<li><strong>Input</strong>
생성된 multi-view image들과 카메라 위치(Camera Ray Embeddings)</li>
<li><strong>Output</strong>
4개의 feature map(Multi-view Gaussian Features)</li>
</ul>
<h3 id="fusing"><strong>Fusing</strong></h3>
<p>Assymetric U-Net의 output인 각 feature map은 Gaussian의 집합으로 transform된다.이 Gaussian들이 합쳐져 최종 3D Gaussian이 생성된다.</p>
<hr>
<h2 id="mesh-extraction-pipeline">Mesh Extraction Pipeline</h2>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/024fab38-c096-4674-be1d-9d58c1b48b38/image.png" alt="">
3D Gaussian -&gt; <a href="https://github.com/NVlabs/instant-ngp"><code>Instant-NGP</code></a> -&gt; NeRF -&gt; <a href="https://github.com/ashawkey/nerf2mesh"><code>NeRF2Mesh</code></a> -&gt; Mesh</p>
<hr>
<h1 id="실행">실행</h1>
<p><a href="https://github.com/3DTopia/LGM">github.com/3DTopia/LGM</a>
<a href="https://github.com/camenduru/LGM-jupyter">github.com/camenduru/LGM-jupyter</a></p>
<p>위 깃허브 코드들을 참고해서 코랩을 만들어서 돌려봤다. -&gt; <a href="https://github.com/jiu-jung/LGM-colab">Colab 링크</a></p>
<p>* 구글 드라이브 마운트 후 <code>LGM/data_test</code>에 입력 이미지 업로드(배경 제거 후 <code>.png</code> 형식)
* Xformer 페이지에 들어가서 버전 확인 후 torch와 cuda 버전 확인</p>
<hr>
<h3 id="trouble-shooting">Trouble Shooting</h3>
<ol>
<li><p><strong>Xformer 설치</strong>
<code>!pip install ./diff-gaussian-rasterization</code> 에서 오류가 계속 발생했다.
torch, cuda, xformer등의 버전이 맞지 않아서 생긴 오류였다. github 페이지대로 torch 버전을 설치하면, xformer가 제대로 설치되지 않는 것 같았다. 그래서 xformer 페이지에 들어가서 버전을 확인하고 재설치 후 해결됐다.</p>
</li>
<li><p><strong>pretrained weights 다운로드</strong>
깃헙 페이지대로 실행하면 저장한 pretrained 이름이 달라서 오류가 난다. 왜 model_fp16이 아닌 다른 파일명으로 해놨는지 이해가 안된다. 어쨌든 밑 코드를 보고 <code>model_fp16_fixrot</code> -&gt; <code>model_fp16</code>로 수정해서 해결되었다.</p>
</li>
<li><p><strong>gradio</strong>
이걸 40분을 기다렸다.. ^^ app.py의 마지막 코드에서 share=True로 바꿔야 public url이 생기고, 거기로 접속해서 gradio에서 실행하는거다… gradio를 몰랐던 내 잘못.. 코랩이 일을 안하는데 계속 실행되면 항상 점검해보자... gradio보다는 직접 test하는게 정확할테니 이 코드는 그냥 빼버렸다. </p>
</li>
<li><p><strong>local gui</strong>
Colab에서 위의 local gui는 되지 않았다. 코랩에서는 실행이 불가능한 코드라 뺐다. 대신 <code>LGM/workspace_test</code>에 저장되는 결과물을 다운받아서 확인하면 된다.</p>
</li>
<li><p>** mesh conversion**
github에 제공된 코드로는 실행이 되지 않고 계속 에러가 뜬다. <a href="https://github.com/3DTopia/LGM/issues/62">github issue</a>와 <a href="https://github.com/camenduru/LGM-jupyter">Github - LGM-jupyter</a>를 참고해보니, <code>--force_cuda_rast</code> 옵션을 주면 해결된다.</p>
</li>
</ol>
<hr>
<h1 id="실행-결과">실행 결과</h1>
<p>여러 샘플을 입력했을 때, <code>DreamGaussian</code>보다 성능이 훨씬 좋았다. 지금부터 가장 차이가 컸던 샘플에 대한 <code>DreamGaussian</code>과 <code>LGM</code>의 결과를 비교해보겠다.</p>
<h2 id="input-image">Input image</h2>
<img src="https://velog.velcdn.com/images/jiu-jung/post/dab399c6-7f49-42db-9d6e-4dc6fb8e1519/image.png" width="40%">

<p>이 이케아 인형은 정면도 측면도 아닌 애매한 각도에서 찍어서 사실 제대로 reconstruction하기 어려울 것이다. 이 single image를 input으로 넣어서 돌려보았다.</p>
<hr>
<h2 id="dreamgaussian"><code>DreamGaussian</code></h2>
<h3 id="obj"><code>.obj</code></h3>
<img src="https://velog.velcdn.com/images/jiu-jung/post/728b292c-77e1-4200-afd4-059d0b9a4aba/image.gif" width="50%">


<h3 id="geometry">Geometry</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/827575ab-3152-4274-8189-6b2fbf61f12f/image.png" alt="">
Vertex: 30,841, Edge: 84,652, Face: 53,958, Triangle: 53,958</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/79cb44c5-5bfe-4a4a-a51b-b0d34433eed1/image.png" alt="">
내부에 불필요한 geometry가 존재한다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/3df7c51c-997a-418d-8985-b8b291e26f42/image.png" alt="">
강아지 형태가 전혀 아니다.</p>
<hr>
<h2 id="lgm">LGM</h2>
<h3 id="ply"><code>.ply</code></h3>
<img src="https://velog.velcdn.com/images/jiu-jung/post/4e7b6c87-2821-4272-9205-e29b058af3b8/image.gif" width="60%">

<h4 id="obj-1"><code>.obj</code></h4>
<img src="https://velog.velcdn.com/images/jiu-jung/post/04e7c55c-2801-4fc4-8d9d-f1c6c94f0d9d/image.gif" width="70%">

<h3 id="texture">Texture</h3>
<p>edge를 바탕으로 텍스쳐를 예측하는 것인지, 예측한 뒷면의 텍스쳐의 색이 너무 어두웠다.</p>
<h3 id="geometry-1">Geometry</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d7315191-51e2-41fb-bc56-ba6e9831440a/image.png" alt="">
Vertex: 26,770, Edge: 73,519, Face: 46,924, Triangle: 46,924</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/2e5487da-7254-4493-b829-0fdb3c317437/image.png" alt="">
내부에 불필요한 geometry가 있지만, <code>DreamGaussian</code>보단 적고 작다.</p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/8e08e17d-0b56-488b-8528-d9e9232fc695/image.png" alt="">
아래쪽에 hole이 있다.전체적으로 표면이 울퉁불퉁하다.</p>
<hr>
<h2 id="결과-분석">결과 분석</h2>
<p>두 모델을 비교해서 정리해보면, </p>
<blockquote>
<ul>
<li><code>LGM</code>이 전체적인 형태를 훨씬 잘 예측했다.</li>
</ul>
</blockquote>
<ul>
<li>Geometry 측면에서는 <code>LGM</code>의 Vertex 수가 적었다.</li>
<li>Texture 측면에서는 <code>DreamGaussian</code>이 잘 예측했다.</li>
</ul>
<p>DreamGaussian과 LGM의 방식은 굉장히 다르다. 이 둘을 결합하여 장점만 가져올 수 있을지 궁금하다.</p>
<br/>
<br/>

<hr>
<h4 id="부록">부록</h4>
<p>귀여워서 <a href="https://huggingface.co/spaces/ashawkey/LGM">huggingface demo</a>로 돌려본 햄스터
<img src="https://velog.velcdn.com/images/jiu-jung/post/8e2a6c9c-00de-4425-8933-0d8a71d56b3d/image.jpg" width="30%">
<img src="https://velog.velcdn.com/images/jiu-jung/post/93aa7f7a-e5e4-4705-a1db-88d699efe748/image.gif" width="55%"></p>
<p>demo로 하면 직접 돌렸을때보다 결과가 안좋긴 한데, 감안해도 말도 안되는 결과가 나왔다. 어이가 없다. 화질이 안좋아서 그런가...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring 실습] 트랜잭션 전파 속성 테스트]]></title>
            <link>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-%EC%86%8D%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-%EC%86%8D%EC%84%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sat, 23 Nov 2024 13:50:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>특정 비즈니스 로직을 위한 사용자 정의 롤백 규칙을 가진 트랜잭션 관리 통합</strong></p>
</blockquote>
<p>지난주 과제에선 jpa를 써서 jdbc template을 쓰는 코드로 수정했다.</p>
<h4 id="schemasql"><code>schema.sql</code></h4>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS member (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL
);</code></pre>
<p>jdbc는 jpa와 달리 자동으로 데이터베이스 설정을 해주지 않는다. <code>resources</code>폴더에 <code>schema.sql</code> 파일을 만들어준다.</p>
<h4 id="transactionconfig"><code>TransactionConfig</code></h4>
<pre><code class="language-java">package com.example.demo.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    } // spring boot의 경우에는 자동으로 설정되어있어서 따로 설정 필요 없음.
}
</code></pre>
<p><code>@EnableTransactionManagement</code></p>
<h4 id="member"><code>Member</code></h4>
<pre><code class="language-java">package com.example.demo.domain;

public class Member{
    private Long id;
    private String username;

    // Constructor
    // Getters and Setters
}</code></pre>
<h4 id="memberrepository"><code>MemberRepository</code></h4>
<pre><code class="language-java">package com.example.demo.repository;

import com.example.demo.domain.Member;

import java.util.List;
import java.util.Optional;

public interface MemberRepository{
    List&lt;Member&gt; findAll();
    Optional&lt;Member&gt; findById(Long id);
    Optional&lt;Member&gt; findByUsername(String username);
    void deleteById(Long id);
    void save(Member member);
}

//public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; { }</code></pre>
<h4 id="memberservice"><code>MemberService</code></h4>
<pre><code class="language-java">package com.example.demo.service;

import org.springframework.stereotype.Service;
import com.example.demo.domain.Member;
import com.example.demo.repository.MemberRepository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public List&lt;Member&gt; getAllMembers() {
        return memberRepository.findAll();
    }

    public Optional&lt;Member&gt; getMemberById(Long id) {
        return memberRepository.findById(id);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Member join(Member member) {
        if (&quot;error&quot;.equals(member.getUsername())) {
            throw new IllegalArgumentException(&quot;username == error&quot;);
        }
        memberRepository.save(member);
        return member;
    }

    public Optional&lt;Member&gt; update(Long id, Member updatedMember) {
        Optional&lt;Member&gt; memberOptional = getMemberById(id);
        memberOptional.ifPresent(member -&gt; member.setUsername(updatedMember.getUsername()));
        return memberOptional;
    }


    public void deleteById(Long id) {
        getMemberById(id); // id 존재 여부 확인하면서 예외 처리
        memberRepository.deleteById(id);
    }
}</code></pre>
<p><code>join()</code> 메서드의 전파 속성은 REQUIRED로 설정한다. 테스트를 위해 username이 &quot;error&quot;이면 예외가 발생하게 한다.</p>
<h4 id="memberservicerequirednew"><code>MemberServiceRequiredNew</code></h4>
<pre><code class="language-java">package com.example.demo.service;

import com.example.demo.domain.Member;
import com.example.demo.repository.MemberRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberServiceRequiredNew {

    private final MemberRepository memberRepository;

    public MemberServiceRequiredNew(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void joinNew(Member member) {
        if (&quot;error&quot;.equals(member.getUsername())) {
            throw new IllegalArgumentException(&quot;username == error&quot;);
        }
        memberRepository.save(member);
    }
}</code></pre>
<p>전파 속성이 REQUIRES_NEW일 때를 테스트하기 위해 <code>Service</code> 클래스를 새로 만들어줬다.</p>
<h4 id="memberdao"><code>MemberDao</code></h4>
<pre><code class="language-java">package com.example.demo.repository;

import com.example.demo.domain.Member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;

class MemberRowMapper implements RowMapper&lt;Member&gt;{
    @Override
    public Member mapRow(ResultSet rs, int rowNum) throws SQLException{
        Member member = new Member();
        member.setId(rs.getLong(&quot;id&quot;));
        member.setUsername(rs.getString(&quot;username&quot;));

        return member;
    }
}

@Repository
public class MemberDao implements MemberRepository{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public List&lt;Member&gt; findAll() {
        String sql = &quot;SELECT * FROM member&quot;;
        return jdbcTemplate.query(sql, new MemberRowMapper());
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;SELECT * FROM member WHERE id = ?&quot;;
        try {
            return Optional.ofNullable(jdbcTemplate.queryForObject(sql, new MemberRowMapper(), id));
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }

    @Override
    public Optional&lt;Member&gt; findByUsername(String username) {
        String sql = &quot;SELECT * FROM member WHERE username = ?&quot;;
        try {
            return Optional.ofNullable(
                    jdbcTemplate.queryForObject(sql, new MemberRowMapper(), username)
            );
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty(); // null 대신 Optional.empty() 반환
        }
    }


    @Override
    public void deleteById(Long id) {
        String sql = &quot;DELETE FROM member WHERE id = ?&quot;;
        jdbcTemplate.update(sql, id);
    }

    @Override
    public void save(Member member) {
        String sql = &quot;INSERT INTO member (username) VALUES (?)&quot;;
        try {
            jdbcTemplate.update(sql, member.getUsername());
        } catch (Exception e) {
            throw new RuntimeException(&quot;Failed to save member: &quot; + member.getUsername(), e);
        }
    }

    public void clearDb(){
        String sql = &quot;DELETE FROM member&quot;;
        jdbcTemplate.update(sql);
    }

}</code></pre>
<p><code>RowMapper</code>를 상속하여 데이터를 객체로 변환하는 클래스를 정의했다.
<code>JdbcTemplate</code>을 이용하여 MemberRepository의 클래스를 구현했다.</p>
<hr>
<h2 id="테스트-코드">테스트 코드</h2>
<h3 id="testmemberdata"><code>TestMemberData</code></h3>
<pre><code class="language-java">package com.example.demo.service;

import com.example.demo.domain.Member;
import com.example.demo.repository.MemberDao;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertTrue;


@SpringBootTest
public class TestMemberData {
    @Autowired
    private MemberService memberService;

    @Autowired
    private MemberServiceRequiredNew memberServiceRequiredNew;

    @Autowired
    private MemberDao memberRepository;

    @AfterEach
    public void afterEach(){
        memberRepository.clearDb();
    }

    @Test
    public void requiredTest() {
        // Given
        Member member1 = new Member(&quot;user1&quot;);
        Member member2 = new Member(&quot;error&quot;); // 오류를 유발할 사용자

        try {
            // 트랜잭션이 시작된 상태에서 두 번의 join 호출
            memberService.join(member1); // 트랜잭션에 참여
            memberService.join(member2); // 예외 발생 → 전체 트랜잭션 롤백
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Then
        // 트랜잭션 롤백 여부 확인
        boolean isUser1Present = memberRepository.findByUsername(&quot;user1&quot;).isPresent();
        boolean isErrorPresent = memberRepository.findByUsername(&quot;error&quot;).isPresent();
        System.out.println(&quot;MemberService is proxied: &quot; + AopUtils.isAopProxy(memberService));

        System.out.println(&quot;User1 present after rollback: &quot; + isUser1Present);
        System.out.println(&quot;Error present after rollback: &quot; + isErrorPresent);

        assertTrue(memberRepository.findByUsername(&quot;user1&quot;).isEmpty(), &quot;User1 should be rolled back&quot;);
        assertTrue(memberRepository.findByUsername(&quot;error&quot;).isEmpty(), &quot;Error should be rolled back&quot;);
    }

    @Test
    public void requiredNewTest() {
        // Given
        Member member1 = new Member();
        member1.setUsername(&quot;user2&quot;);

        Member member2 = new Member();
        member2.setUsername(&quot;error&quot;);

        try {
            memberServiceRequiredNew.joinNew(member1);
            memberServiceRequiredNew.joinNew(member2);

        } catch (Exception e) {
            e.printStackTrace();
        }

        // Then
        // member2는 롤백되었지만, member1은 커밋되어야 함
        assertTrue(memberRepository.findByUsername(&quot;user2&quot;).isPresent());
        assertTrue(memberRepository.findByUsername(&quot;error&quot;).isEmpty());
    }


}
</code></pre>
<ul>
<li><p><code>requiredTest()</code>
member2를 join할 때 예외가 발생해서 전체 트랜잭션이 롤백될 것이다. 따라서 member1과 member2 모두 memberRepository에 저장되지 않았을 것이다.</p>
</li>
<li><p><code>requireNewTest()</code>
member2를 join할 때 예외가 발생한다. 하지만 트랜잭션 속성이 REQUIRES_NEW이기 때문에 member1의 join의 트랜잭션이 아닌 새로운 트랜잭션이 롤백된다. 결과적으로 member1은 커밋되고, member2는 롤백된다.</p>
</li>
</ul>
<hr>
<h2 id="테스트-결과">테스트 결과</h2>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/03ff32b6-c025-463d-94e0-d4ffc3093d32/image.png" alt="">
<code>requredTest()</code> 테스트가 실패했다. 디버그를 위해서 로그도 확인하고 프록시 객체인지 확인해봤다.</p>
<pre><code>MemberService is proxied: true
User1 present after rollback: true
Error present after rollback: false</code></pre><p>이유를 모르겠지만 전체 롤백이 되지 않았다... 좀 더 찾아보고 고쳐야겠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 스프링 DAO와 JDBC 템플릿 및 트랜잭션 관리]]></title>
            <link>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-6</link>
            <guid>https://velog.io/@jiu-jung/Spring-WIL-GDGoc-week-6</guid>
            <pubDate>Fri, 22 Nov 2024 11:33:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>GDG on Campus Backend-Spring 스터디 WIL
<strong>Week 06 - 스프링 DAO와 JDBC 템플릿 및 트랜잭션 관리</strong></p>
</blockquote>
<hr>
<h1 id="스프링-dao">스프링 DAO</h1>
<h3 id="data-access-object데이터-접근-객체">Data Access Object(데이터 접근 객체)</h3>
<blockquote>
<p>실제로 데이터베이스에 접근하는 객체
서비스와 데이터베이스를 연결하는 역할을 하며, 실제로 DB에 접근하여 데이터 CRUD 작업(삽입, 삭제, 조회, 수정)을 수행한다.</p>
</blockquote>
<ul>
<li><strong>캡슐화</strong>
DAO는 데이터베이스 작업을 추상화하고 캡슐화하여 데이터 소스 접근과 비즈니스 로직을 분리한다.</li>
<li><strong>재사용성</strong>
DAO 패턴은 코드의 재사용을 도와준다. 다양한 데이터 소스에 대해 일관된 인터페이스를 제공한다.</li>
<li><strong>유지보수성</strong>
데이터 접근 로직에 한 곳에 모여 있어 유지보수에 유리하다. 변경사항을 쉽게 적용할 수 있다.</li>
</ul>
<h3 id="스프링-dao의-필요성">스프링 DAO의 필요성</h3>
<ul>
<li><strong>코드 간소화</strong>
반복적인 JDBC 코드 간소화. 개발자는 비즈니스 로직에 집중 가능</li>
<li><strong>예외처리 개선</strong>
checked 예외를 unchecked 예외로 변환하여 예외 처리의 유연성을 높인다.</li>
<li><strong>테스트 용이성</strong>
의존성 주입을 통해 DAO를 쉽게 모의 객체로 대체할 수 있다. 이는 단위테스트를 용이하게 한다.</li>
</ul>
<blockquote>
<h4 id="chcked-unchocked-exception">Chcked, Unchocked Exception</h4>
<p><strong>Checked exception</strong>: RuntimeException의 하위 클래스가 아니면서 Exception 클래스의 하위 클래스들. 반드시 에러 처리를 해야 한다.
<strong>Unchecked exception</strong>: RuntimeException의 하위 클래스들. 실행 중에(runtime) 발생할 수 있는 예외를 의미. <strong>에러 처리를 강제하지 않는다.</strong>
<img src="https://velog.velcdn.com/images/jiu-jung/post/c58e206f-9fa3-41d7-abda-2e1e1a298349/image.png" alt=""></p>
</blockquote>
<hr>
<h1 id="jdbc-템플릿">JDBC 템플릿</h1>
<ul>
<li><strong>템플릿 메서드 패턴</strong>
JDBC 템플릿은 템플릿 메서드 패턴을 구현하여 반복적인 코드를 추상화한다.</li>
<li><strong>콜백 사용</strong>
RowMapper, ResultSetExtractor 등의 콜백을 통해 결과 매핑을 커스터마이즈할 수 있다.</li>
<li><strong>배치 작업 지원</strong>
batchUpdate 메서드로 대량 데이터 처리를 효율적으로 처리한다.</li>
</ul>
<blockquote>
<h4 id="템플릿-메서드-패턴">템플릿 메서드 패턴</h4>
<p>메소드의 구조를 정의하고, 일부 단계를 하위 클래스에서 구현하도록 하는 디자인 패턴. 이를 통해 메소드의 구조 변경 없이 특정 단계를 재정의할 수 있다. 이는 반복적인 코드를 효과적으로 추상화하고 재사용성을 높이는데 도움이 된다.<img src="https://velog.velcdn.com/images/jiu-jung/post/72817064-d53c-4f9f-a498-011ac9da7edf/image.png" alt=""></p>
</blockquote>
<h3 id="jdbc-템플릿-관련-의존성">JDBC 템플릿 관련 의존성</h3>
<ul>
<li><code>spring-jdbc</code>
JDBC 및 데이터소스 지원을 위한 핵심 의존성. 이는 JDBC 템플릿을 사용하기 위해 필요하다.</li>
<li><code>spring-tx</code> 
트랜잭션 관리를 위한 의존성. 선언적 트랜잭션 관리를 사용할 때 필요하지만, spring 4.3.3 버전부터 <code>spring-tx</code>가 <code>spring-jdbc</code>에 포함.</li>
<li>데이터베이스 드라이버
사용하는 데이터베이스에 맞는 JDBC 드라이버를 추가. (MySQL을 사용한다면 <code>mysql-connector-java</code> 추가)</li>
</ul>
<h3 id="jdbc-템플릿-사용법">JDBC 템플릿 사용법</h3>
<blockquote>
<ol>
<li>의존성 추가</li>
<li>DataSource 설정
데이터베이스 연결 정보를 포함한 <code>DataSource</code>를 구성한다.</li>
<li>JdbcTemplate 생성
DataSource를 이용해 <code>JdbcTemplate</code> 객체를 생성한다.</li>
</ol>
</blockquote>
<h3 id="데이터베이스-설정---datasource">데이터베이스 설정 - <code>DataSource</code></h3>
<ol>
<li><code>DataSource</code> 빈 정의
xml 또는 java 설정으로 <code>DataSource</code> 빈을 정의한다. 이는 데이터베이스 연결 정보를 포함한다.</li>
<li>Connection Pool 설정
HikariCP와 같은 연결 풀을 사용하여 성능을 최적화하여 데이터베이스 연결을 효율적으로 관리한다.</li>
<li>property 외부화
데이터베이스 연결 정보를 properties 파일로 분리한다.</li>
</ol>
<h3 id="jdbc-사용시-문제점">JDBC 사용시 문제점</h3>
<ol>
<li>자원 관리 부담
Connection, Statement, ResultSet 등의 자원을 수동으로 관리해야 한다. 이는 메모리 낭비의 위험이 있더,</li>
<li>예외 처리 복잡성
JDBC의 checked 예외는 처리가 번거롭다. 이는 코드를 복잡하게 만든다.</li>
<li>코드 중복
연결, 명령문 생성, 결과 처리 등의 코드가 반복되기 때문에 코드의 가독성을 떨어뜨린다.</li>
</ol>
<h3 id="jdbc-템플릿-장점">JDBC 템플릿 장점</h3>
<ol>
<li>자원 관리 자동화
JDBC 템플릿이 연결 열기와 닫기를 관리.</li>
<li>예외 변환
JDBC 예외를 스프링의 <code>DataAccessException</code>으로 변환한다. 이는 예외 처리를 일관되게 만든다.</li>
<li>코드 간소화
반복적인 코드를 제거하여 핵심 로직에 집중할 수 있다.</li>
</ol>
<h3 id="jpa와-jdbc">JPA와 JDBC</h3>
<h4 id="jpajava-persistent-api">JPA(Java Persistent API)</h4>
<p>객체 지향 데이터 모델링 및 영속성 관리를 제공하는 표준 API. 복잡한 SQL 쿼리 작성을 자동화하고 객체지향적 프로그래밍 방식을 지원한다.</p>
<h4 id="jdbcjava-database-connectivity">JDBC(Java Database Connectivity)</h4>
<p>데이터베이스와 직접 통신하는 Java API. SQL 쿼리를 직접 작성하고 실행해야 하며, 개발자가 데이터베이스 관련 코드를 직접 관리해야 한다.</p>
<hr>
<h1 id="트랜잭션">트랜잭션</h1>
<h2 id="acid">ACID</h2>
<p>트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질.</p>
<h4 id="atomicity원자성">Atomicity(원자성)</h4>
<p>트랜잭션의 모든 연산이 성공하거나 모두 실패해야 한다. 이는 데이터의 일관성을 유지한다.</p>
<h4 id="consistency일관성">Consistency(일관성)</h4>
<p>트랜잭션 전후로 데이터베이스가 일관된 상태를 유지해야 한다. 이는 데이터 무결성을 보장한다.</p>
<h4 id="isolation고립성">Isolation(고립성)</h4>
<p>동시에 실행되는 트랜잭션들이 서로 영향을 주지 않아야 한다. 이는 동시성 문제를 방지한다.</p>
<h4 id="durability지속성">Durability(지속성)</h4>
<p>성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다. 이는 데이터의 안정성을 보장한다.
<img src="https://velog.velcdn.com/images/jiu-jung/post/6117feea-9fe9-4e0c-bf97-42dd06763529/image.png" alt=""></p>
<h2 id="트랜잭션-관리의-중요성">트랜잭션 관리의 중요성</h2>
<h4 id="데이터-일관성">데이터 일관성</h4>
<p>트랜잭션은 여러 데이터베이스 작업을 <strong>하나의 논리적 단위</strong>로 묶어 데이터의 <strong>일관성</strong>을 보장한다.</p>
<h4 id="에러-복구">에러 복구</h4>
<p>문제 발생 시 <strong>롤백</strong>을 통해 데이터 무결성을 유지하여 시스템의 <strong>안정성</strong>을 높인다.</p>
<h4 id="동시성-제어">동시성 제어</h4>
<p>여러 사용자의 <strong>동시 접근</strong>을 관리하여 데이터의** 정확성과 일관성**을 보장한다.</p>
<h2 id="선언적-트랜잭션-관리">선언적 트랜잭션 관리</h2>
<p>*<em>1. 트랜잭션 관리자 설정
*</em><code>DataSourceTransactionManager</code> 빈 정의. 이는 JDBC 기반 트랜잭션을 관리한다.</p>
<p>*<em>2. <code>@EnableTransactionManagement</code>
*</em>설정 클래스에 어노테이션 추가하여 선언전 트랜잭션 관리 활성화</p>
<p><strong>3. <code>@Transactional</code>
*<em>트랜잭션이 필요한 *</em>메서드나 클래스</strong>에 @Transactional`을 붙여 해당 범위에 트랜잭션을 적용한다.</p>
<h2 id="트랜잭션-전파">트랜잭션 전파</h2>
<blockquote>
<p>어떤 트랜잭션이 <strong>동작 중</strong>인 과정에서 <strong>다른 트랜잭션을 실행</strong>할 경우 <strong>어떻게 처리</strong>하는가에 대한 개념</p>
</blockquote>
<h3 id="필요성">필요성</h3>
<ul>
<li><strong>메서드 호출</strong>
한 메서드가 다른 메서드를 부를 때, 트랜잭션이 함께 이어져서 호출된다.</li>
<li><strong>안전한 데이터 처리</strong>
작업이 하나로 묶여 처리되므로, 문제가 생기면 모든 작업이 취소되어 안전하게 처리가 가능하다.</li>
<li><strong>쉬운 설정</strong>
스프링에서 제공하는 기능으로 복잡한 코드 없이 간단하게 설정 가능하다.</li>
<li><strong>실무 활용</strong>
여러 데이터 작업을 안전하게 처리해야할 때 유용하다.</li>
</ul>
<h3 id="트랜잭션-전파-속성">트랜잭션 전파 속성</h3>
<table>
<thead>
<tr>
<th>전파 속성</th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><strong>REQUIRED</strong></td>
<td>기존 트랜잭션이 있으면 참여하고, 없으면 새로 생성. 가장 흔히 사용되는 기본 설정.</td>
</tr>
<tr>
<td><strong>REQUIRES_NEW</strong></td>
<td>항상 새로운 트랜잭션 시작. 기존 트랜잭션은 일시중단됨.</td>
</tr>
<tr>
<td><strong>NESTED</strong></td>
<td>기존 트랜잭션 내에서 중첩 트랜잭션 실행. 부분적 롤백 가능.</td>
</tr>
<tr>
<td><strong>SUPPORT</strong></td>
<td>트랜잭션이 있으면 참여하고, 없어도 비트랜잭션으로 실행.</td>
</tr>
<tr>
<td><img src="https://velog.velcdn.com/images/jiu-jung/post/9633c0c7-9afb-42a7-8d40-a73bc431a278/image.png" alt=""></td>
<td></td>
</tr>
</tbody></table>
<h2 id="에러-처리">에러 처리</h2>
<h4 id="1-전파-설정">1. 전파 설정</h4>
<p><code>@Transactional</code>의 propagation 속성으로 트랜잭션 전파 방식을 지정한다.</p>
<h4 id="2-예외-처리">2. 예외 처리</h4>
<p>트랜잭션 내에서 발생한 예외를 적절히 처리한다. RuntimeException은 기본적으로 롤백을 유발한다.</p>
<h4 id="3-롤백-규칙">3. 롤백 규칙</h4>
<p><code>rollbackFor</code>, <code>noRollbackFor</code> 속성으로 롤백 규칙을 세밀하게 조절할 수 있다.</p>
<h2 id="예시">예시</h2>
<h3 id="create-연산"><code>CREATE</code> 연산</h3>
<ul>
<li><strong><code>UPDATE()</code> 메서드 사용</strong>
JdbcTemplate의 <code>update()</code>메서드로 INSERT쿼리를 실행하여 데이터를 삽입할 수 있다.</li>
<li><strong>파라미터 바인딩</strong>
<code>?</code> 플레이스홀더를 사용하여 악의적인 SQL Injection을 방지할 수 있다.</li>
<li><strong>KeyHolder 사용</strong>
자동 생성된 키 값을 얻기 위해 사용할 수 있다. 특히 KeyHolder는 새로 삽입된 데이터의 기본 키 값을 편리하게 가져올 수 있도록 도와준다.</li>
</ul>
<h3 id="read-연산"><code>READ</code> 연산</h3>
<ul>
<li><strong><code>queryForObject()</code></strong></li>
<li><em>단일 결과*</em>를 조회할 때 사용.
<code>RowMapper</code>를 통해 결과를 객체로 매핑한다.</li>
<li><strong><code>query()</code></strong></li>
<li><em>여러 결과*</em>를 리스트로 조회할 때 사용한다.
복잡한 객체 매핑에 유용하다.</li>
<li><strong>RowMapper</strong> 구현
ResultSet의 데이터를 객체로 변환하는 로직을 정의함으로써 재사용 가능한 매핑 전략을 제공한다.<h3 id="update-연산"><code>UPDATE</code> 연산</h3>
</li>
<li><strong><code>update()</code> 메서드</strong>
CREATE 연산과 비슷하게 UPDATE 쿼리 실행에도 <code>update()</code>메서드를 사용한다.</li>
<li><strong>다중 파라미터</strong>
여러개의 파라미터를 순서대로 전달한다. 복잡한 UPDATE문을 실행할 때 유용하다.</li>
<li><strong>배치 업데이트</strong>
<code>batchUpdate()</code>메서드로 여러 레코드를 한번에 업데이트할 수 있다. 대량 데이터 처리 시 성능을 향상시킨다.<h3 id="delete-연산"><code>DELETE</code> 연산</h3>
</li>
<li>** <code>update()</code> 사용**
DELETE쿼리도 <code>update()</code>메서드를 실행한다. 영향받은 행의 수를 반환받아 삭제 여부를 확인할 수 있다.</li>
<li><strong>조건부 삭제</strong>
WHERE절을 사용하여 특정 조건에 맞는 데이터만 안전하게 삭제한다.</li>
<li><strong>예외처리</strong>
<code>DataAccessException</code>을 catch하여 삭제 중 발생할 수 있는 오류를 처리한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring 입문] 06.스프링 DB 접근 기술(1)]]></title>
            <link>https://velog.io/@jiu-jung/Spring-%EC%9E%85%EB%AC%B8-06.%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A01</link>
            <guid>https://velog.io/@jiu-jung/Spring-%EC%9E%85%EB%AC%B8-06.%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A01</guid>
            <pubDate>Sun, 17 Nov 2024 06:37:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>H2 데이터베이스 설치
순수 JDBC
스프링 통합 테스트</p>
</blockquote>
<h1 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h1>
<h4 id="1-다운로드-및-설치">1. 다운로드 및 설치</h4>
<p><a href="https://www.h2database.com">https://www.h2database.com</a>
h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.</p>
<h4 id="2-h2bat-실행">2. <code>h2.bat</code> 실행</h4>
<p><code>h2/bin/h2.bat</code> 실행</p>
<h4 id="3-데이터베이스-파일-생성">3. 데이터베이스 파일 생성</h4>
<p>최초 실행 시 데이터베이스 파일을 생성해야 한다.
JDBC URL: <code>jdbc:h2:~/test</code>를 입력하고 연결 클릭
<code>C:\Users\jung</code>에 <code>test.mv.db</code> 파일 생성 확인
이후부터는 JDBC URL: <code>jdbc:h2:tcp://localhost/~/test</code> 로 연결한다.
파일에 직접 접근하지 않고 <strong>소켓</strong>을 통해 접근하는 것.</p>
<blockquote>
<p>데이터베이스가 꼬이면 서버 연결을 완전히 끊고, <code>test.mv.db</code> 삭제하고 다시 생성하는 것도 방법이다.</p>
</blockquote>
<h3 id="데이터베이스-생성">데이터베이스 생성</h3>
<p>DDL(Data Definition Language)를 h2 콘솔에 입력하여 데이터베이스를 생성한다.</p>
<pre><code class="language-sql">drop table if exists member CASCADE;
create table member
(
 id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);</code></pre>
<ul>
<li>spring에서 <code>Long</code>이 sql 문법에서는 <code>bigint</code></li>
<li><code>generated by default as identity</code>: db가 자동으로 값 채워줌</li>
</ul>
<h3 id="데이터-삽입">데이터 삽입</h3>
<p>DML(Data Manipulation Language)를 h2 콘솔에 입력하여 데이터베이스에 데이터를 삽입한다.
<code>insert into member(name) values(&#39;spring&#39;)</code></p>
<ul>
<li>id는 데이터베이스에서 자동으로 생성해준다.</li>
</ul>
<h3 id="sql-폴더">SQL 폴더</h3>
<p>프로젝트 루트에 <code>sql</code> 디렉토리 생성 후,
<code>ddl.sql</code>에 위의 ddl문을 저장해두면 github에 업로드할 수 있고 관리도 편하다.</p>
<hr>
<h1 id="순수-jdbc">순수 JDBC</h1>
<h3 id="jdbcmemberrepository-작성"><code>JdbcMemberRepository</code> 작성</h3>
<pre><code class="language-java">package com.jiu.spring_basic.repository;

import com.jiu.spring_basic.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;

    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public Member save(Member member) {
        String sql = &quot;insert into member(name) values(?)&quot;;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            pstmt.setString(1, member.getName());

            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys(); // Statement.RETURN_GENERATED_KEYS 와 매칭

            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException(&quot;id 조회 실패&quot;);
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        String sql = &quot;select * from member where id = ?&quot;;

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);

            pstmt.setLong(1, id);

            rs = pstmt.executeQuery();

            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public List&lt;Member&gt; findAll() {
        String sql = &quot;select * from member&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List&lt;Member&gt; members = new ArrayList&lt;&gt;();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        String sql = &quot;select * from member where name = ?&quot;;
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong(&quot;id&quot;));
                member.setName(rs.getString(&quot;name&quot;));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource); // DataSourceUtils를 통해서 getConnection
    }

    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource); // DataSourceUtils를 통해서 releaseConnection
    }
}</code></pre>
<ul>
<li>DataSourceUtils를 <strong>통해서</strong> get, release Connection</li>
</ul>
<h3 id="springconfig-변경"><code>SpringConfig</code> 변경</h3>
<pre><code class="language-java">@Configuration
public class SpringConfig {

    private DataSource dataSource;

    @Autowired
    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public MemberRepository memberRepository() {
//        return new MemoryMemberRepository();
        return new JdbcMemberRepository(dataSource);
    }
}</code></pre>
<ul>
<li>Configuration 파일에서 MemberRepository를 반환하는 코드만 변경해주면 된다.</li>
<li><code>DataSource</code>
DataSource는 <strong>데이터베이스 커넥션을 획득할 때 사용하는 객체</strong>다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다. 그래서 DI를 받을 수 있다.</li>
</ul>
<h3 id="실행">실행</h3>
<p>꼭 <strong>h2 database를 실행시킨 후</strong>, spring application을 실행시켜야 한다.
<a href="http://localhost:8080">http://localhost:8080</a> 에 접속하면 h2 데이터베이스에 생성했던 회워 목록을 조회할 수 있다.
회원 가입을 하면, 가입된 회원을 h2 콘솔에서도 확인할 수 있다.</p>
<h3 id="설명">설명</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/6c624162-1aa6-4e37-815c-2918c1de6ec1/image.png" alt="">
<img src="https://velog.velcdn.com/images/jiu-jung/post/d151880a-fbf1-4d27-a933-f7b86ff1a554/image.png" alt=""></p>
<ul>
<li>개방-폐쇄 원칙(OCP, Open-Closed Principle)
확장에는 열려있고, 수정, 변경에는 닫혀있다.</li>
<li>다형성</li>
<li>스프링의 <strong>DI (Dependencies Injection)</strong>을 사용하면 <strong>기존 코드를 전혀 손대지 않고, 설정만으로</strong> 구현 클래스를 변경할 수 있다.</li>
<li>데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.</li>
</ul>
<hr>
<h1 id="스프링-통합-테스트">스프링 통합 테스트</h1>
<p>기존 테스트는 순수 자바 테스트코드.
스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해보자.</p>
<p><code>MemberServiceIntegrationTest</code></p>
<pre><code class="language-java">@SpringBootTest
@Transactional // 테스트 후 rollback
class MemberServiceIntegrationTest {

    // test 시 간단하게 field injection
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    void join() {
        //given
        Member member = new Member();
        member.setName(&quot;spring&quot;);

        //when
        Long saveId = memberService.join(member);

        //then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());

    }

    @Test
    void Dup_Member_join(){
        //given
        Member member1 = new Member();
        member1.setName(&quot;dup&quot;);

        Member member2 = new Member();
        member2.setName(&quot;dup&quot;);

        //when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
    }
}</code></pre>
<ul>
<li><code>@SpringBootTest</code>
스프링 컨테이너와 테스트를 함께 실행한다.</li>
<li><code>@Transactional</code>
테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.</li>
<li>순수 자바 단위 테스트가 더 좋은 테스트일 확률이 높다.</li>
</ul>
<h3 id="실행-1">실행</h3>
<ul>
<li>테스트를 위해 h2 콘솔에서 데이터를 지운다.
<code>delete from member</code></li>
<li>실행 시 통합 테스트이므로 테스트 로그에 spring 로그도 뜬다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring 실습] REST API 설계]]></title>
            <link>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-REST-API-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@jiu-jung/Spring-%EC%8B%A4%EC%8A%B5-REST-API-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Sat, 16 Nov 2024 14:26:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>** HATEOAS와 커스텀 예외 처리를 포함한 REST API 구축**</p>
</blockquote>
<h3 id="프로젝트-구조">프로젝트 구조</h3>
<pre><code>📂 controller      
│  └─ MemberController.java
📂 domain         
│  └─ Member.java
 📂 exception        
│  └─ MemberNotFoundException.java
│  └─ MemberNotFoundExceptionHandler.java
📂 repository     
│  └─ MemberRepository.java
📂 service          
│  └─ MemberService.java
└─ Week5Application.java</code></pre><hr>
<h2 id="hateoas-없이-구현">HATEOAS 없이 구현</h2>
<p><strong>h2 database</strong>와 <strong>spring jpa</strong> 사용</p>
<h4 id="buildgradle"><code>build.gradle</code></h4>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    runtimeOnly &#39;com.h2database:h2&#39;
}</code></pre>
<h4 id="applicationproperties"><code>application.properties</code></h4>
<pre><code>// h2 database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

// jpa
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=create
// jpa log
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true</code></pre><p><code>spring.jpa.hibernate.ddl-auto=create</code>: 엔티티를 보고 자동으로 데이터베이스 생성</p>
<h4 id="member"><code>Member</code></h4>
<pre><code class="language-java">package com.example.demo.domain;

import jakarta.persistence.*;
import org.springframework.hateoas.RepresentationModel;

@Entity
@Table(name = &quot;Members&quot;)
public class Member extends RepresentationModel&lt;Member&gt; {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    // Constructor
    public Member() {}

    public Member(String username) {
        this.username = username;
    }

    // Getters and Setters
}</code></pre>
<ul>
<li><code>@Entity</code>
이 클래스가 JPA 엔티티임을 선언
데이터베이스 테이블과 매핑되며, JPA가 이 클래스를 사용하여 데이터베이스 작업(저장, 조회, 수정, 삭제)을 수행</li>
<li><code>@Table(name = &quot;Members&quot;)</code>
이 엔티티 클래스가 데이터베이스의 어떤 테이블과 매핑될지 지정.</li>
<li><code>@Id</code>
이 필드가 Primary Key(기본 키)임을 지정.</li>
<li><code>@GeneratedValue(strategy = GenerationType.IDENTITY)</code>
id 필드가 자동으로 생성되도록 설정.</li>
<li><code>private Long id;</code>
엔티티의 Primary Key로, 데이터베이스에서 자동으로 생성되는 필드.</li>
<li><code>private String username;</code>
엔티티의 username 속성으로, 데이터베이스 테이블의 열(column)에 매핑.</li>
</ul>
<h4 id="memberrepository"><code>MemberRepository</code></h4>
<pre><code class="language-java">package com.example.demo.repository;

import com.example.demo.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
public interface MemberRepository extends JpaRepository&lt;Member, Long&gt; { }</code></pre>
<ul>
<li><p>JpaRepository&lt;<strong>Entity 클래스, PK 타입</strong>&gt;</p>
</li>
<li><p>JpaRepository는 PagingAndSortingRepository, QueryByExampleExecutor 인터페이스를 상속</p>
</li>
<li><p>PagingAndSortingRepository는 CrudRepository 인터페이스를 상속
<img src="https://velog.velcdn.com/images/jiu-jung/post/888c29d4-a048-48d8-94e0-ad2faade1e6a/image.png" alt=""></p>
</li>
<li><p>CrudRepository 인터페이스는 기본적인 CRUD 메소드 제공
: save(), findById(), existsById(), count(), deleteById(), delete(), deleteAll()</p>
</li>
<li><p>QueryByExampleExecutor 인터페이스는 더 다양한 CRUD 메소드 제공
: findOne(), findAll(), count(), exists()</p>
</li>
</ul>
<h4 id="memberservice"><code>MemberService</code></h4>
<pre><code class="language-java">package com.example.demo.service;

import com.example.demo.exception.MemberNotFoundException;
import org.springframework.stereotype.Service;
import com.example.demo.domain.Member;
import com.example.demo.repository.MemberRepository;

import java.util.List;
import java.util.Optional;

@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public List&lt;Member&gt; getAllMembers() {
        return memberRepository.findAll();
    }

    public Member getMemberById(Long id) {
        return memberRepository.findById(id)
                .orElseThrow(() -&gt; new MemberNotFoundException(&quot;No member found with ID &quot; + id));
    }

    public Member createMember(Member member) {
        return memberRepository.save(member);
    }

    public Member updateMember(Long id, Member updatedMember) {
        Member member = getMemberById(id);
        member.setUsername(updatedMember.getUsername());
        return memberRepository.save(member);
    }

    public void deleteMember(Long id) {
        getMemberById(id); // id 존재 여부 확인하면서 예외 처리
        memberRepository.deleteById(id);
    }
}</code></pre>
<ul>
<li><code>getMemberById()</code>에서 id에 해당하는 멤버가 존재하지 않을 때, <strong>커스텀 예외 처리</strong></li>
</ul>
<h4 id="membercontroller">MemberController</h4>
<pre><code class="language-java">package com.example.demo.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.demo.domain.Member;
import com.example.demo.service.MemberService;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping(&quot;/api/members&quot;)
public class MemberController {

    private final MemberService memberService;

    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    // GET /api/members
    @GetMapping
    public ResponseEntity&lt;List&lt;Member&gt;&gt; getAllMembers(){
        return ResponseEntity.ok(memberService.getAllMembers());
    }

    // GET /api/members/{id}
    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Member&gt; getMemberById(@PathVariable Long id) {
        return ResponseEntity.ok(memberService.getMemberById(id));
    }

    // POST /api/members
    @PostMapping
    public ResponseEntity&lt;Member&gt; createMember(@RequestBody Member member) {
        Member createdMember = memberService.createMember(member);
        return ResponseEntity.status(201).body(createdMember);
    }

    // PUT /api/members/{id}
    @PutMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;Member&gt; updateMember(@PathVariable Long id, @RequestBody Member member) {
        return ResponseEntity.ok(memberService.updateMember(id, member));
    }

    // DELETE /api/members/{id}
    @DeleteMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;String&gt; deleteMember(@PathVariable Long id) {
        memberService.deleteMember(id);
        return ResponseEntity.noContent().build();
    }
}</code></pre>
<ul>
<li><code>ResponseEntity</code>를 이용하여 반환<h4 id="membernotfoundexception">MemberNotFoundException</h4>
<pre><code class="language-java">package com.example.demo.exception;
</code></pre>
</li>
</ul>
<p>public class MemberNotFoundException extends RuntimeException {
    public MemberNotFoundException(String message) {
        super(message);
    }
}</p>
<pre><code>#### MemberNotFoundExceptionHandler
```java
package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class MemberNotFoundExceptionHandler {

    @ExceptionHandler(MemberNotFoundException.class)
    public ResponseEntity&lt;Object&gt; handleMemberNotFound(MemberNotFoundException ex) {
        Map&lt;String, Object&gt; response = new HashMap&lt;&gt;();
        response.put(&quot;error&quot;, &quot;Member Not Found&quot;);
        response.put(&quot;message&quot;, ex.getMessage());
        response.put(&quot;timestamp&quot;, LocalDateTime.now());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }
}</code></pre><ul>
<li><strong>Object를 반환</strong>해서 에러 종류, 메세지, 타임스탬프를 반환해준다.</li>
</ul>
<h3 id="troubleshooting">TroubleShooting</h3>
<ul>
<li>h2 콘솔 잘못 들어감 -&gt; <code>localhost:8080/h2-console/</code>로 들어가야 한다!</li>
<li>id를 넘겨줘야하는 메소드들에서 오류가 났다. 찾아보니, <code>(@PathVariable(&quot;id&quot;) Long id)</code> 이렇게 &quot;id&quot;까지 명시해주면 해결된다. Gradle로 빌드하면 안그래도 된다고 한다. <a href="https://velog.io/@ghwns9991/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-3.2-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EC%9D%B4%EB%A6%84-%EC%9D%B8%EC%8B%9D-%EB%AC%B8%EC%A0%9C">velog.io/@ghwns9991</a></li>
</ul>
<hr>
<h3 id="실행-결과">실행 결과</h3>
<h4 id="post">POST</h4>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/69ce3f5a-143c-4e62-8325-41a5f773a094/image.png" alt=""></p>
<h4 id="get">GET</h4>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/b38f583d-beb2-42a2-bda9-963e87adce94/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/295a76a5-9ea6-4d61-825c-2602405c5032/image.png" alt=""></p>
<h4 id="put">PUT</h4>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/3b5a47d6-0c23-4986-9c98-497f088deade/image.png" alt=""></p>
<h4 id="delete">DELETE</h4>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/633e37f8-cd35-4442-adb3-28a32fcbd2fa/image.png" alt=""></p>
<h4 id="custom-exception">Custom Exception</h4>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/a4f12923-1c93-4408-b15e-7143a22df0ae/image.png" alt=""></p>
<ul>
<li>에러 종류, 메세지, 타임스탬프가 잘 반환되었다.</li>
</ul>
<hr>
<h2 id="hateoas-구현">HATEOAS 구현</h2>
<h4 id="buildgradle-1"><code>build.gradle</code></h4>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-hateoas&#39;
}</code></pre>
<h4 id="membercontroller-1"><code>MemberController</code></h4>
<p><strong>1. HATEOAS 모델 적용</strong>
<code>EntityModel</code> 또는 <code>CollectionModel</code>을 사용하여 링크 추가.
<strong>2. 링크 생성</strong>
<code>WebMvcLinkBuilder</code>를 사용해 관련 메서드와 리소스를 연결.</p>
<pre><code class="language-java">    // GET /api/members/{id}
    @GetMapping(&quot;/{id}&quot;)
    public ResponseEntity&lt;EntityModel&lt;Member&gt;&gt; getMemberById(@PathVariable(&quot;id&quot;) Long id) {
        Member member = memberService.getMemberById(id);

        // HATEOAS 링크 추가
        EntityModel&lt;Member&gt; memberModel = EntityModel.of(member,
                WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(MemberController.class).getMemberById(id)).withSelfRel(),
                WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(MemberController.class).getAllMembers()).withRel(&quot;all-members&quot;)
        );

        return ResponseEntity.ok(memberModel);
    }</code></pre>
<blockquote>
<h4 id="hateoas-구현-1">HATEOAS 구현</h4>
</blockquote>
<ol>
<li><code>EntityModel</code> 또는 <code>CollectionModel</code> 사용</li>
<li><code>RepresentationModel</code> 사용</li>
</ol>
<hr>
<h3 id="실행-결과-1">실행 결과</h3>
<p><img src="https://velog.velcdn.com/images/jiu-jung/post/d36c6c02-5009-4dab-bcb5-2631b8bed5f4/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>