<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>code_able.log</title>
        <link>https://velog.io/</link>
        <description>할수 있다! code able</description>
        <lastBuildDate>Tue, 17 Feb 2026 14:28:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>code_able.log</title>
            <url>https://velog.velcdn.com/images/code_able/profile/26560654-dbe9-42b3-8956-c6489508480e/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. code_able.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/code_able" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[바이브코딩 회고]]></title>
            <link>https://velog.io/@code_able/2026-%EC%84%A4-%EB%AA%85%EC%A0%88-%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@code_able/2026-%EC%84%A4-%EB%AA%85%EC%A0%88-%EB%B0%94%EC%9D%B4%EB%B8%8C%EC%BD%94%EB%94%A9-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 17 Feb 2026 14:28:26 GMT</pubDate>
            <description><![CDATA[<p>필자는 OpenClaw, Claude cowork, AntiGravity 같은 도구를 적극적으로 활용했고,
skill을 정의하고, claude.md에 설계 의도를 상세히 기록하며
AI를 만능 개발자로 만들어 
엄청나게 많은 사이드 프로젝트를 수행 하고 돈을 버는 것을 상상했다.</p>
<p>초반에는 확실히 가능해 보였다.
코드 생성 속도는 놀라울 정도로 빨랐고, 반복 작업은 거의 즉시 해결되었다.</p>
<p>하지만 프로젝트가 커질수록 어려움을 맞이 했고 
봇의 커밋만 있던 레파지토리에 납기와 품질을 지키 위해 결국 나의 커밋이 끼어들기 시작했다.
새로운 아이디어는 설렘에서 부담으로 전환 되었다.</p>
<h3 id="생성은-빠르지만-통합은-느리다">생성은 빠르지만, 통합은 느리다</h3>
<p>애플리케이션이 커질수록 다음과 같은 일이 반복되었다.</p>
<ul>
<li><p>컨텍스트가 점점 많아지고</p>
</li>
<li><p>이를 AI에게 설명하는 오버헤드가 커졌으며</p>
</li>
<li><p>디자인과 코드의 일관성이 흔들리기 시작했다</p>
</li>
<li><p>기능이 많아 질수록 인프라와 보안의 부담은 점점 커져갔다.</p>
</li>
</ul>
<p>아무리 skill을 정교하게 정의해도,
아무리 문서화를 상세히 해도,
결국 장기적인 설계 의도와 시스템의 정합성을 유지하는 일은 나의 몫이었다.</p>
<p>시간이 지날수록 깨닫게 되었다.</p>
<p>AI에게 기대했던 일들이 점점 다시 나에게 돌아오고 있었다.</p>
<p>생성은 AI가 담당했지만,
통합과 QC 책임은 여전히 개발자의 영역이었다.</p>
<h3 id="명세-지옥">명세 지옥</h3>
<p>AI는 명세를 기반으로 동작한다.
주어진 정보 안에서 최적의 결과를 생성하는 데에는 매우 강하다.</p>
<p>하지만 현실의 소프트웨어 개발은 단순히 명세를 구현하는 일이 아니다.</p>
<p>현실에는:</p>
<ul>
<li><p>불완전한 요구사항</p>
</li>
<li><p>암묵적인 도메인 지식</p>
</li>
<li><p>장기 유지보수 고려</p>
</li>
<li><p>리스크 판단과 안정성 확보</p>
</li>
<li><p>책임 있는 의사결정</p>
</li>
</ul>
<p>이 존재한다.</p>
<p>AI는 뛰어난 생성 능력을 가졌지만,
위험을 감지하고 장기적인 안정성을 책임지는 존재는 아니다.</p>
<p>그 부분은 여전히 인간의 역할이다.</p>
<h3 id="결론">결론</h3>
<p>AI는 분명 강하다.
그리고 앞으로 더 강해질 것이다.</p>
<p>하지만 AI는 개발자를 대체하는 존재가 아니라 하나의 개발 툴로 인식하게 되었다.</p>
<p>AI의 발전에 지나치게 긴장할 필요도,
과도하게 들뜰 필요도 없다.</p>
<p>AI가 우리의 기술적 구현 능력을 빠르게 따라잡고 있는 것은 사실이지만,
문제를 정의하고, 맥락을 이해하고, 책임 있는 결정을 내리는 능력은 또 다른 차원의 영역이다.</p>
<p>예전처럼 기본기를 다듬고,
꾸준히 나아가자.</p>
<p>AI는 새로운 개발 툴의 등장일 뿐이다. 공포에 휘둘릴 필요는 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Oracle cloud argocd network 이슈]]></title>
            <link>https://velog.io/@code_able/Oracle-cloud-argocd-network-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@code_able/Oracle-cloud-argocd-network-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Mon, 06 Oct 2025 02:49:15 GMT</pubDate>
            <description><![CDATA[<h1 id="oci-oke에서-argocd-private-repository-연결-실패-해결">OCI OKE에서 ArgoCD Private Repository 연결 실패 해결</h1>
<h2 id="문제-상황">문제 상황</h2>
<ul>
<li>OCI OKE 클러스터에서 ArgoCD 설치 후 private GitHub repository 연결 실패</li>
<li><code>dial tcp 20.200.245.247:22: connect: connection timed out</code> 에러 발생</li>
</ul>
<h2 id="문제-진단-과정">문제 진단 과정</h2>
<h3 id="1-네트워크-설정-확인">1. 네트워크 설정 확인</h3>
<pre><code class="language-bash"># 외부 연결 테스트
kubectl run test-curl --image=curlimages/curl --restart=Never -- curl -m 5 -I https://github.com
# 결과: Connection timed out</code></pre>
<h3 id="2-oci-vcn-설정-점검">2. OCI VCN 설정 점검</h3>
<ul>
<li>✅ Security List: <code>0.0.0.0/0</code> Egress Rules 설정됨</li>
<li>✅ Route Table: Internet Gateway 연결됨  </li>
<li>✅ Public Subnet: 워커 노드에 Public IP 할당됨</li>
<li>✅ NSG: 적용되지 않음</li>
</ul>
<h3 id="3-핵심-문제-발견">3. 핵심 문제 발견</h3>
<pre><code class="language-bash"># Host network Pod에서는 연결 성공
kubectl exec -n kube-system kube-proxy-xxx -- curl -I https://github.com
# HTTP/2 200 ✅

# 일반 Pod에서는 연결 실패  
kubectl run test --image=curlimages/curl -- curl -I https://github.com
# Connection timed out ❌</code></pre>
<p><strong>원인</strong>: OCI VCN-Native CNI에서 Pod의 외부 연결을 위한 SNAT 설정 이슈</p>
<h2 id="임시-해결책">임시 해결책</h2>
<h3 id="argocd-repo-server를-hostnetwork로-실행">ArgoCD repo-server를 hostNetwork로 실행</h3>
<pre><code class="language-bash"># repo-server 설정 백업
kubectl get deployment argocd-repo-server -n argocd -o yaml &gt; backup.yaml

# hostNetwork 설정 적용
kubectl patch deployment argocd-repo-server -n argocd --patch=&#39;{
  &quot;spec&quot;: {
    &quot;template&quot;: {
      &quot;spec&quot;: {
        &quot;hostNetwork&quot;: true,
        &quot;dnsPolicy&quot;: &quot;ClusterFirstWithHostNet&quot;
      }
    }
  }
}&#39;</code></pre>
<h3 id="결과-확인">결과 확인</h3>
<pre><code class="language-bash">kubectl get pods -n argocd | grep repo-server
# argocd-repo-server-xxx  1/1  Running</code></pre>
<h2 id="결론">결론</h2>
<ul>
<li>OCI OKE의 VCN-Native CNI에서 Pod 외부 연결 문제 발생</li>
<li>hostNetwork 설정으로 임시 해결 가능</li>
<li>근본적 해결을 위해서는 OCI 지원팀 문의 필요</li>
</ul>
<h2 id="참고사항">참고사항</h2>
<ul>
<li>이 문제는 OKE Quick Create로 생성된 클러스터에서 발생</li>
<li>Security List, Route Table 등 모든 설정이 올바르더라도 Pod 레벨에서 외부 연결 차단됨</li>
<li>Production 환경에서는 OCI 지원을 통한 근본적 해결 권장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 - Bridge 패턴]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Bridge-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Bridge-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 21 Nov 2024 14:17:16 GMT</pubDate>
            <description><![CDATA[<h3 id="bridge-패턴이란">Bridge 패턴이란</h3>
<p>객체의 구현부와 추상부를 분리하여 독립적으로 확장할 수 있도록 설계하는 구조적 디자인 패턴입니다. 이 패턴은 기능 계층과 구현 계층을 분리하여, 하나의 계층을 변경해도 다른 계층에 영향을 주지 않도록 설계합니다.</p>
<h3 id="목적">목적</h3>
<ul>
<li>기능(Fuctionality)와 구현(Implementation)을 분리하여 독립적인 확장을 가능하게 합니다.</li>
<li>계층 구조가 복잡해지는 것을 방지하고, 코드의 유연성과 확장성을 높입니다.</li>
<li>런타임 시에 구현 객체를 동적으로 변경할 수 있습니다.</li>
</ul>
<h3 id="구조">구조</h3>
<ul>
<li>Abstraction (추상화):<ul>
<li>기능 계층을 정의합니다.</li>
<li>구현 객체에 대한 참조를 포함하고, 클라이언트가 사용할 인터페이스를 제공합니다.</li>
</ul>
</li>
<li>Implementor (구현부):<ul>
<li>구체적인 구현을 정의합니다.</li>
<li>추상화 계층에 의해 호출됩니다.</li>
</ul>
</li>
<li>RefinedAbstraction (확장된 추상화):<ul>
<li>Abstraction을 확장하여 추가 기능을 제공합니다.</li>
</ul>
</li>
<li>ConcreteImplementor (구체적인 구현부):<ul>
<li>Implementor의 구체적인 구현을 제공합니다.</li>
</ul>
</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>기능과 구현의 독립적인 확장이 가능합니다. 하나를 변경해도 다른 계층에 영향을 미치지 않습니다.</li>
<li>런타임에서 구현 객체를 동적으로 변경할 수 있습니다.</li>
<li>복잡성 감소: 상속 관계가 많아지는 문제를 해결합니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>계층 분리를 위해 구현이 복잡해질 수 있음.</li>
<li>초기 설계 단계에서 계층을 명확히 구분하는 설계가 요구됩니다.</li>
</ul>
<h3 id="브리지-패턴의-예제">브리지 패턴의 예제</h3>
<pre><code class="language-python"># Implementor: 색상을 정의하는 인터페이스
class Color:
    def apply_color(self):
        pass


# ConcreteImplementor: 색상의 구체적인 구현
class Red(Color):
    def apply_color(self):
        return &quot;Red&quot;


class Blue(Color):
    def apply_color(self):
        return &quot;Blue&quot;


# Abstraction: 모양을 정의하는 클래스
class Shape:
    def __init__(self, color: Color):
        self.color = color

    def draw(self):
        pass


# RefinedAbstraction: 모양의 구체적인 확장
class Circle(Shape):
    def draw(self):
        return f&quot;Circle filled with {self.color.apply_color()} color&quot;


class Square(Shape):
    def draw(self):
        return f&quot;Square filled with {self.color.apply_color()} color&quot;


# Client Code
red = Red()
blue = Blue()

circle = Circle(red)
square = Square(blue)

print(circle.draw())  # Output: Circle filled with Red color
print(square.draw())  # Output: Square filled with Blue color</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 - Fasade 패턴]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Fasade-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Fasade-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 21 Nov 2024 14:12:50 GMT</pubDate>
            <description><![CDATA[<h3 id="fasade-패턴이란">Fasade 패턴이란</h3>
<p>복잡한 서브시스템을 단순화하기 위해 제공되는 단순한 인터페이스를 설계하는 구조적 디자인 패턴입니다. 여러 클래스와 그들의 메서드로 구성된 복잡한 시스템을 사용할 때, 클라이언트가 직접 복잡성을 다루지 않고 단순한 인터페이스만으로 시스템을 쉽게 사용할 수 있도록 도와줍니다.</p>
<h3 id="목적">목적</h3>
<p>복잡한 서브시스템의 사용성을 개선하기 위해 사용됩니다.
클라이언트 코드가 서브시스템의 세부사항에 종속되지 않도록 보호합니다.
서브시스템 내부 구현이 변경되더라도 클라이언트 코드에 영향을 최소화합니다.</p>
<h3 id="구조">구조</h3>
<ul>
<li>Subsystem (서브시스템): 복잡한 로직을 포함하는 클래스들의 집합입니다. 각 클래스는 고유한 기능을 가지고 있습니다.</li>
<li>Facade (파사드): 서브시스템의 복잡성을 숨기고, 클라이언트가 사용할 단순화된 인터페이스를 제공합니다.</li>
<li>Client (클라이언트): 파사드 객체를 사용해 서브시스템의 기능을 간접적으로 호출합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>서브시스템의 복잡성 숨기기: 클라이언트는 단순화된 인터페이스를 통해 복잡한 서브시스템을 쉽게 사용할 수 있습니다.</li>
<li>유지보수성 증가: 서브시스템 내부 구현이 변경되더라도, 파사드 인터페이스만 유지되면 클라이언트 코드에는 영향을 미치지 않습니다.</li>
<li>서브시스템 의존성 감소: 클라이언트가 서브시스템의 구체적인 클래스에 의존하지 않도록 분리합니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>서브시스템의 모든 기능을 파사드에 노출하려면 복잡성이 증가할 수 있습니다.</li>
<li>잘못 설계된 파사드는 추가 기능 추가 시 유연성이 부족할 수 있습니다.</li>
</ul>
<h3 id="파사드-패턴의-예제">파사드 패턴의 예제</h3>
<pre><code class="language-python"># Subsystem: 홈 시어터 구성 요소
class BluRayPlayer:
    def on(self):
        print(&quot;BluRay Player is ON&quot;)

    def play(self, movie: str):
        print(f&quot;Playing movie: {movie}&quot;)

    def stop(self):
        print(&quot;Stopping BluRay Player&quot;)

    def off(self):
        print(&quot;BluRay Player is OFF&quot;)


class Projector:
    def on(self):
        print(&quot;Projector is ON&quot;)

    def wide_screen_mode(self):
        print(&quot;Projector is set to wide screen mode&quot;)

    def off(self):
        print(&quot;Projector is OFF&quot;)


class SurroundSoundSystem:
    def on(self):
        print(&quot;Surround Sound System is ON&quot;)

    def set_volume(self, level: int):
        print(f&quot;Setting volume to {level}&quot;)

    def off(self):
        print(&quot;Surround Sound System is OFF&quot;)


# Facade: 홈 시어터 파사드
class HomeTheaterFacade:
    def __init__(self, blu_ray: BluRayPlayer, projector: Projector, sound_system: SurroundSoundSystem):
        self.blu_ray = blu_ray
        self.projector = projector
        self.sound_system = sound_system

    def watch_movie(self, movie: str):
        print(&quot;\nStarting the movie experience...&quot;)
        self.blu_ray.on()
        self.blu_ray.play(movie)
        self.projector.on()
        self.projector.wide_screen_mode()
        self.sound_system.on()
        self.sound_system.set_volume(10)

    def end_movie(self):
        print(&quot;\nEnding the movie experience...&quot;)
        self.blu_ray.stop()
        self.blu_ray.off()
        self.projector.off()
        self.sound_system.off()


# Client: 클라이언트 코드
blu_ray = BluRayPlayer()
projector = Projector()
sound_system = SurroundSoundSystem()

# Facade 객체 생성
home_theater = HomeTheaterFacade(blu_ray, projector, sound_system)

# 영화 관람
home_theater.watch_movie(&quot;Inception&quot;)
# 영화 종료
home_theater.end_movie()</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Proxy 패턴]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Proxy-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Proxy-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 12 Nov 2024 14:33:00 GMT</pubDate>
            <description><![CDATA[<h3 id="proxy-패턴이란">Proxy 패턴이란</h3>
<p>프록시(Proxy) 패턴은 어떤 객체에 대한 접근을 제어하는 대리자 객체를 제공하는 구조적 디자인 패턴입니다. 프록시 객체는 실제 객체의 대리 역할을 하며, 클라이언트가 실제 객체에 접근할 때 이를 대신 처리하거나 중간 작업을 수행합니다. 프록시 패턴은 주로 객체 접근을 제어하고, 추가 기능(예: 로깅, 캐싱, 지연 로딩 등)을 추가할 때 유용합니다.</p>
<p>프록시 패턴은 클라이언트가 직접 접근하지 않도록 막아야 하거나, 객체 접근을 추가 로직으로 제어해야 할 때 사용됩니다. 예를 들어, 리소스가 큰 객체의 초기화를 지연시킨다거나, 객체 접근 시 인증을 체크하는 것 등이 프록시 패턴의 주요 사용 예입니다.</p>
<h3 id="프록시-패턴의-종류">프록시 패턴의 종류</h3>
<p>프록시 패턴은 다양한 방식으로 구현될 수 있으며, 일반적으로 다음과 같은 종류로 나뉩니다:</p>
<ul>
<li>가상 프록시(Virtual Proxy): 객체의 초기화가 무겁거나 지연 로딩이 필요한 경우, 필요할 때만 객체를 생성합니다.</li>
<li>원격 프록시(Remote Proxy): 원격 객체에 대한 대리자 역할을 하여 네트워크 상의 객체와의 통신을 처리합니다.</li>
<li>보호 프록시(Protection Proxy): 객체 접근에 대한 권한을 제어합니다.</li>
<li>스마트 프록시(Smart Proxy): 접근 시 추가적인 행동을 수행하는 프록시입니다. (예: 참조 카운팅, 로깅)</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>지연 로딩을 통해 성능을 최적화할 수 있습니다.</li>
<li>접근 제어와 같은 추가 작업을 객체의 주된 기능에 영향을 주지 않고도 구현할 수 있습니다.</li>
<li>리소스 관리: 사용하지 않는 자원의 초기화를 늦춰 리소스 관리가 용이합니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>객체를 실제로 사용하기 전에 프록시를 통해 접근하므로 복잡도가 증가할 수 있습니다.</li>
<li>프록시 객체를 관리해야 하므로, 코드가 복잡해질 수 있으며, 디버깅과 유지보수가 어려울 수 있습니다.</li>
</ul>
<h3 id="예제-python으로-프록시-패턴-구현하기">예제: Python으로 프록시 패턴 구현하기</h3>
<p>아래 예제에서는 프록시 패턴을 사용하여 파일 읽기 작업을 지연 로딩하는 가상 프록시를 구현해 보겠습니다. 실제 파일을 읽는 RealFile 클래스에 접근하기 전에 프록시가 대신 역할을 수행하다가, 진짜 객체가 필요한 경우에만 초기화합니다.</p>
<pre><code class="language-python">from time import sleep

# RealSubject: 실제 객체 역할을 하는 클래스
class RealFile:
    def __init__(self, filename):
        self.filename = filename
        self.load_file()  # 파일을 실제로 로드하는 작업

    def load_file(self):
        print(f&quot;Loading file &#39;{self.filename}&#39;...&quot;)
        sleep(2)  # 파일 로딩 시뮬레이션

    def display_content(self):
        return f&quot;Displaying content of &#39;{self.filename}&#39;&quot;

# Proxy: 실제 객체에 접근을 제어하는 프록시 클래스
class FileProxy:
    def __init__(self, filename):
        self.filename = filename
        self._real_file = None  # 실제 객체를 지연 로딩합니다.

    def display_content(self):
        # 실제 객체를 필요할 때만 초기화
        if self._real_file is None:
            self._real_file = RealFile(self.filename)
        return self._real_file.display_content()

# Client Code
file_proxy = FileProxy(&quot;sample.txt&quot;)

# 파일을 처음 호출할 때 로드하고 내용을 출력
print(file_proxy.display_content())  
# 두 번째 호출부터는 이미 로드된 파일을 사용
print(file_proxy.display_content())</code></pre>
<h3 id="프록시-패턴의-사용-예">프록시 패턴의 사용 예</h3>
<ul>
<li>지연 초기화: 무거운 리소스를 사용할 때, 실제로 필요할 때까지 초기화를 지연시킵니다.</li>
<li>액세스 제어: 특정 객체에 대한 접근 권한을 관리하는 경우(예: 인증 및 권한 검사).</li>
<li>네트워크 프록시: 원격 서버나 네트워크 상의 자원에 접근할 때, 네트워크 요청을 프록시가 대신 처리하여 클라이언트가 원격 객체와 상호작용할 수 있게 합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Array와 Linked List]]></title>
            <link>https://velog.io/@code_able/Array%EC%99%80-Linked-List</link>
            <guid>https://velog.io/@code_able/Array%EC%99%80-Linked-List</guid>
            <pubDate>Tue, 12 Nov 2024 13:48:13 GMT</pubDate>
            <description><![CDATA[<h2 id="개념">개념</h2>
<h3 id="배열-array">배열 (Array)</h3>
<p>연속된 메모리 블록에 요소를 저장하는 자료구조입니다.
각 요소는 인덱스를 통해 직접 접근할 수 있어, 고정된 크기의 데이터를 다룰 때 효율적입니다.</p>
<h3 id="링크드-리스트-linked-list">링크드 리스트 (Linked List)</h3>
<p>요소(노드)가 포인터로 연결된 자료구조입니다.
각 노드는 데이터를 저장하는 공간과 다음 노드를 가리키는 포인터로 구성됩니다.
노드가 동적으로 메모리에 할당되므로, 배열과 달리 크기가 가변적입니다.</p>
<h2 id="시간-복잡도">시간 복잡도</h2>
<h3 id="배열-array-1">배열 (Array)</h3>
<p>접근: O(1) – 인덱스를 통해 즉시 접근할 수 있습니다.
삽입/삭제: O(n) – 요소를 삽입하거나 삭제할 때, 다른 요소를 이동해야 하므로 시간이 걸립니다.</p>
<h3 id="링크드-리스트-linked-list-1">링크드 리스트 (Linked List)</h3>
<p>접근: O(n) – 인덱스를 통한 직접 접근이 불가능하고, 첫 노드부터 순차적으로 탐색해야 합니다.
삽입/삭제: O(1) (앞이나 뒤에서 삽입/삭제 시) – 포인터를 변경하는 것만으로 가능하므로 빠릅니다. 그러나 중간에 삽입이나 삭제는 탐색이 필요해 O(n)이 될 수 있습니다.</p>
<h2 id="가비지-컬렉션gc">가비지 컬렉션(GC)</h2>
<h3 id="배열-array-2">배열 (Array)</h3>
<p>배열은 연속된 메모리 블록을 할당받기 때문에, 요소가 할당된 상태에서 연속된 블록으로 유지됩니다.
단일 메모리 할당/해제가 이뤄지므로, GC 부담이 적고 메모리 단편화가 줄어듭니다.
메모리에 오래 남아 있는 요소는 적으므로 GC의 빈도와 부담이 낮습니다.</p>
<h3 id="링크드-리스트-linked-list-2">링크드 리스트 (Linked List)</h3>
<p>노드마다 개별적으로 동적 메모리를 할당받으므로, 사용이 끝난 노드가 많아지면 GC가 빈번하게 작동하게 됩니다.
각 노드의 포인터로 연결되어 있어, 참조가 복잡하게 얽히면 사용되지 않는 노드도 메모리에서 해제되지 않는 경우가 발생할 수 있습니다.
메모리 단편화가 발생할 수 있으며, 이는 GC 성능에 악영향을 미칩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Adapter 패턴]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Adapter-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Adapter-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 11 Nov 2024 11:23:09 GMT</pubDate>
            <description><![CDATA[<h3 id="adapter-패턴이란">Adapter 패턴이란</h3>
<p>호환되지 않는 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 중간에 어댑터 역할을 하는 클래스를 두는 구조적 디자인 패턴입니다. 어댑터는 기존 클래스를 수정하지 않고 다른 인터페이스와 호환되도록 도와주기 때문에 기존 코드에 대한 수정이 필요 없고, 새로운 코드와 쉽게 통합할 수 있습니다.</p>
<h3 id="어댑터-패턴을-사용하는-이유">어댑터 패턴을 사용하는 이유</h3>
<ul>
<li>호환성 문제 해결: 서로 다른 인터페이스를 사용하는 클래스들이 함께 작동하도록 해줍니다.</li>
<li>재사용성 증가: 기존 코드를 수정하지 않고도 새로운 요구사항이나 환경에 맞게 기존 클래스를 재사용할 수 있습니다.</li>
<li>유연성 향상: 어댑터를 사용하면 코드 구조를 변경하지 않고 새로운 기능을 추가할 수 있습니다.</li>
</ul>
<h3 id="구조">구조</h3>
<ul>
<li>Target (목표 인터페이스): 클라이언트가 기대하는 인터페이스입니다.</li>
<li>Client (클라이언트): Target 인터페이스를 사용해 필요한 작업을 수행하는 객체입니다.</li>
<li>Adaptee (적응 대상): 기존의 인터페이스를 가진 클래스입니다. </li>
<li>Target과 호환되지 않아 어댑터가 필요합니다.</li>
<li>Adapter (어댑터): Target 인터페이스를 구현하고, Adaptee를 감싸서 클라이언트가 Target을 통해 Adaptee의 기능을 사용할 수 있도록 합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>기존 코드를 수정하지 않고, 새로운 기능을 추가하거나 호환성을 확보할 수 있습니다.</li>
<li>인터페이스가 맞지 않는 객체들을 함께 사용할 수 있습니다.</li>
<li>코드 재사용성을 높이고, 시스템의 확장성을 증대시킵니다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>구조가 복잡해질 수 있으며, 너무 많은 어댑터를 사용하면 코드의 가독성이 떨어질 수 있습니다.</li>
<li>시스템 내 의존성이 증가할 수 있습니다.</li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python"># Target Interface (클라이언트가 기대하는 인터페이스)
class EuropeSocket:
    def plug_in_european_socket(self):
        return &quot;Using European socket power.&quot;

# Adaptee (기존에 존재하는 호환되지 않는 클래스)
class USAPowerPlug:
    def plug_in_usa_socket(self):
        return &quot;Using USA socket power.&quot;

# Adapter (어댑터 클래스)
class USAToEuropeAdapter(EuropeSocket):
    def __init__(self, usa_plug: USAPowerPlug):
        self.usa_plug = usa_plug

    def plug_in_european_socket(self):
        # 호환되지 않는 인터페이스를 맞춰주는 어댑터 메서드
        return self.usa_plug.plug_in_usa_socket()

# Client Code (클라이언트 코드)
def charge_device(socket: EuropeSocket):
    print(socket.plug_in_european_socket())

# Usage Example
usa_plug = USAPowerPlug()
adapter = USAToEuropeAdapter(usa_plug)
charge_device(adapter)
# Output:
# Using USA socket power.</code></pre>
<h3 id="실제-사용-예">실제 사용 예</h3>
<ul>
<li>라이브러리 통합: 기존 시스템에 새 라이브러리를 도입할 때, 새 라이브러리의 인터페이스가 기존 시스템과 맞지 않다면 어댑터를 사용해 인터페이스를 맞춥니다.</li>
<li>레거시 코드 통합: 기존 레거시 시스템이 다른 인터페이스를 사용할 경우, 어댑터를 통해 새 시스템과 호환되도록 만들 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Singleton]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Singleton</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Singleton</guid>
            <pubDate>Sat, 09 Nov 2024 13:57:40 GMT</pubDate>
            <description><![CDATA[<h3 id="singleton-패턴-이란">Singleton 패턴 이란</h3>
<p>클래스의 인스턴스가 오직 하나만 생성되도록 보장하며, 전역 접근이 가능하게 하는 디자인 패턴입니다. 주로 글로벌 상태를 유지해야 하거나, 자원을 공유해야 하는 경우에 유용합니다. 대표적인 예로 로그 파일 관리 객체, 설정 객체, 데이터베이스 연결 객체 등이 있습니다.</p>
<h3 id="싱글톤-패턴을-사용하는-이유">싱글톤 패턴을 사용하는 이유</h3>
<ul>
<li>자원의 절약: 여러 번 인스턴스를 생성하지 않고, 하나의 인스턴스를 공유함으로써 메모리와 자원을 절약할 수 있습니다.</li>
<li>전역 접근: 애플리케이션 전역에서 동일한 인스턴스를 참조할 수 있어 상태 관리와 접근이 간단해집니다.</li>
<li>데이터 일관성: 특정 자원의 상태나 설정 값이 여러 인스턴스에서 충돌하거나 일관성이 깨지는 것을 방지합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>자원 절약 및 성능 최적화에 도움이 됩니다.</li>
<li>애플리케이션 전체에서 동일한 인스턴스를 통해 일관된 상태 유지가 가능합니다.<h3 id="단점">단점</h3>
</li>
<li>전역 상태를 유지하기 때문에, 코드의 복잡성이 증가하고 의존성이 높아질 수 있습니다.</li>
<li>다중 스레드 환경에서 싱글톤 객체의 동기화 처리가 필요할 수 있습니다.</li>
</ul>
<h3 id="싱글톤-패턴의-사용-예">싱글톤 패턴의 사용 예</h3>
<ul>
<li>로그 객체: 여러 클래스가 동일한 로그 파일에 로그를 남길 때.</li>
<li>설정 객체: 애플리케이션의 설정 값을 공유하는 객체.</li>
<li>데이터베이스 연결 풀: DB와의 연결을 유지하고 공유해야 할 때.</li>
</ul>
<h3 id="구현">구현</h3>
<p><strong>new</strong> 메서드를 사용하는 싱글톤 패턴</p>
<pre><code class="language-python">class Singleton:
    _instance = None  # 인스턴스 저장을 위한 클래스 변수

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

# Example usage
singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True, 두 객체는 동일한 인스턴스입니다.</code></pre>
<p>데코레이터를 사용한 싱글톤 패턴</p>
<pre><code class="language-python">def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Singleton:
    pass

# Example usage
singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)  # True</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Builder ]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Builder</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Builder</guid>
            <pubDate>Sat, 09 Nov 2024 13:53:28 GMT</pubDate>
            <description><![CDATA[<h3 id="builder-패턴이란">Builder 패턴이란</h3>
<p>복잡한 객체를 단계적으로 생성하는 방법을 제공하는 생성 패턴입니다. 이 패턴은 객체 생성 과정에서의 세부 설정이나 조립이 필요한 경우에 유용하며, 다양한 속성들을 선택적으로 설정하여 객체를 만들 수 있게 합니다. 특히, 복잡한 객체 생성 시 코드를 더 읽기 쉽게 만들고, 유연성을 높이며, 객체 생성의 과정과 표현을 분리하는 데 중점을 둡니다.</p>
<h3 id="빌더-패턴을-사용하는-이유">빌더 패턴을 사용하는 이유</h3>
<ul>
<li>객체 생성 과정의 복잡도 감소: 복잡한 객체의 생성 과정이 빌더 클래스로 분리되어, 가독성과 유지보수가 쉬워집니다.</li>
<li>유연한 객체 구성: 선택적인 속성들을 가질 수 있으며, 필요한 속성만 설정하여 객체를 생성할 수 있습니다.</li>
<li>변경 용이성: 빌더 클래스를 수정하여 다양한 객체 구성을 쉽게 추가할 수 있습니다.</li>
</ul>
<h3 id="빌더-패턴의-구조">빌더 패턴의 구조</h3>
<ul>
<li>Builder (빌더): 객체를 단계별로 생성하는 인터페이스나 추상 클래스를 정의합니다.</li>
<li>Concrete Builder (구체 빌더): 빌더 인터페이스를 구현하여 실제 객체를 생성하는 클래스입니다.</li>
<li>Director (디렉터): 빌더 객체를 통해 각 단계별로 생성 과정을 정의하여 최종 제품을 반환합니다.</li>
<li>Product (제품): 생성될 복합 객체입니다. 여러 단계에서 설정되는 속성을 포함합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>코드의 가독성이 높아지고, 객체 생성 로직이 명확하게 드러납니다.</li>
<li>불변 객체를 쉽게 만들 수 있습니다. 객체 생성 후에는 속성이 변경되지 않도록 할 수 있습니다.</li>
<li>복잡한 객체 생성 시 객체의 일관성을 보장합니다. 필수 필드가 누락되었을 때 에러를 발생시키는 등의 로직을 추가할 수 있습니다.</li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python"># Product Class
class UserProfile:
    def __init__(self, name, age, address, phone, email):
        self.name = name
        self.age = age
        self.address = address
        self.phone = phone
        self.email = email

    def __str__(self):
        return f&quot;UserProfile(name={self.name}, age={self.age}, address={self.address}, phone={self.phone}, email={self.email})&quot;

# Builder Class
class UserProfileBuilder:
    def __init__(self):
        self.name = None
        self.age = None
        self.address = None
        self.phone = None
        self.email = None

    def set_name(self, name):
        self.name = name
        return self

    def set_age(self, age):
        self.age = age
        return self

    def set_address(self, address):
        self.address = address
        return self

    def set_phone(self, phone):
        self.phone = phone
        return self

    def set_email(self, email):
        self.email = email
        return self

    def build(self):
        # 필요한 속성을 체크하고, 조건이 만족되면 최종 객체를 반환
        if self.name is None or self.age is None:
            raise ValueError(&quot;Name and age are required&quot;)
        return UserProfile(self.name, self.age, self.address, self.phone, self.email)

# Client Code
try:
    user = (UserProfileBuilder()
            .set_name(&quot;Alice&quot;)
            .set_age(30)
            .set_address(&quot;123 Main St&quot;)
            .set_phone(&quot;555-1234&quot;)
            .set_email(&quot;alice@example.com&quot;)
            .build())

    print(user)
    # 출력:
    # UserProfile(name=Alice, age=30, address=123 Main St, phone=555-1234, email=alice@example.com)
except ValueError as e:
    print(f&quot;Error: {e}&quot;)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 -Abstract Factory]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Abstract-Factory</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Abstract-Factory</guid>
            <pubDate>Sat, 09 Nov 2024 13:49:18 GMT</pubDate>
            <description><![CDATA[<h3 id="abstract-factory-패턴이란">Abstract Factory 패턴이란</h3>
<p>추상화 팩토리 패턴(Abstract Factory Pattern)은 관련된 객체들을 그룹으로 묶어 일관된 방식으로 생성할 수 있도록 하는 디자인 패턴입니다. 여러 종류의 객체를 묶어서 생성할 때 유용하며, 객체의 구체적인 클래스가 아닌 추상적인 인터페이스에 의존하게 하여 유연성을 높입니다.</p>
<h3 id="추상화-팩토리-패턴의-구조">추상화 팩토리 패턴의 구조</h3>
<ul>
<li>AbstractFactory (추상 팩토리): 관련 객체들을 생성하는 메서드들을 선언하는 인터페이스입니다.</li>
<li>ConcreteFactory (구체 팩토리): AbstractFactory를 구현하여 특정 제품군을 생성하는 메서드를 제공합니다.</li>
<li>AbstractProduct (추상 제품): 생성될 제품의 인터페이스를 정의합니다.</li>
<li>ConcreteProduct (구체 제품): AbstractProduct를 구현한 실제 제품 클래스입니다.</li>
<li>Client (클라이언트): AbstractFactory와 AbstractProduct를 사용하여 구체적인 객체 생성에 대해 알 필요 없이 객체들을 사용합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>일관성: 관련 있는 객체들을 일관성 있게 생성할 수 있습니다.</li>
<li>확장성: 새로운 제품군을 추가할 때 ConcreteFactory만 추가하면 되므로 코드 확장이 용이합니다.</li>
<li>의존성 감소: 클라이언트는 구체 클래스가 아닌 추상 인터페이스에 의존하므로 구체적인 제품 변경이 클라이언트에 영향을 주지 않습니다.</li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod

# 1. Abstract Products 정의
class Button(ABC):
    @abstractmethod
    def click(self) -&gt; str:
        pass

class Checkbox(ABC):
    @abstractmethod
    def check(self) -&gt; str:
        pass

# 2. Concrete Products 정의
class MacButton(Button):
    def click(self) -&gt; str:
        return &quot;Clicking a Mac-styled Button&quot;

class WindowsButton(Button):
    def click(self) -&gt; str:
        return &quot;Clicking a Windows-styled Button&quot;

class MacCheckbox(Checkbox):
    def check(self) -&gt; str:
        return &quot;Checking a Mac-styled Checkbox&quot;

class WindowsCheckbox(Checkbox):
    def check(self) -&gt; str:
        return &quot;Checking a Windows-styled Checkbox&quot;

# 3. Abstract Factory 정의
class GUIFactory(ABC):
    @abstractmethod
    def create_button(self) -&gt; Button:
        pass

    @abstractmethod
    def create_checkbox(self) -&gt; Checkbox:
        pass

# 4. Concrete Factories 정의
class MacFactory(GUIFactory):
    def create_button(self) -&gt; Button:
        return MacButton()

    def create_checkbox(self) -&gt; Checkbox:
        return MacCheckbox()

class WindowsFactory(GUIFactory):
    def create_button(self) -&gt; Button:
        return WindowsButton()

    def create_checkbox(self) -&gt; Checkbox:
        return WindowsCheckbox()

# 5. Client 코드
def create_ui(factory: GUIFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    print(button.click())
    print(checkbox.check())

# 팩토리 선택에 따라 Mac 또는 Windows 스타일의 UI 생성
if __name__ == &quot;__main__&quot;:
    os_type = &quot;Mac&quot;  # 혹은 &quot;Windows&quot;

    if os_type == &quot;Mac&quot;:
        factory = MacFactory()
    elif os_type == &quot;Windows&quot;:
        factory = WindowsFactory()
    else:
        raise ValueError(&quot;Unknown OS type&quot;)

    create_ui(factory)
    # 출력 예시:
    # Clicking a Mac-styled Button
    # Checking a Mac-styled Checkbox</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Factory Method]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Factory-Method</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Factory-Method</guid>
            <pubDate>Sat, 09 Nov 2024 13:45:16 GMT</pubDate>
            <description><![CDATA[<h3 id="factory-method-패턴이란">Factory Method 패턴이란</h3>
<p>팩토리 메서드 패턴은 객체 생성 코드를 서브클래스에서 정의하도록 위임하는 디자인 패턴입니다. 팩토리 메서드는 구체적인 클래스 대신 부모 클래스 또는 인터페이스에서 정의한 메서드를 통해 객체를 생성하게 하여, 객체 생성의 책임을 서브클래스가 지도록 합니다.</p>
<p>이 패턴은 상속과 다형성을 활용하여 생성 과정을 커스터마이즈할 수 있도록 하는데, 특히 생성해야 할 객체 타입이 자주 변경되거나 새로운 타입이 추가될 가능성이 있는 경우에 유용합니다.</p>
<p>팩토리 메서드 패턴의 구조</p>
<ul>
<li>Creator (창조자): 객체 생성을 위한 팩토리 메서드를 선언하는 추상 클래스입니다. 팩토리 메서드는 일반적으로 추상 메서드로 정의되며, 이를 구현하는 서브클래스들이 실제 생성 로직을 담당합니다.</li>
<li>ConcreteCreator (구체 창조자): Creator의 서브클래스이며, 특정 제품(객체)을 생성하는 팩토리 메서드를 구현합니다.</li>
<li>Product (제품): 생성될 객체의 인터페이스 또는 추상 클래스입니다.</li>
<li>ConcreteProduct (구체 제품): 실제 생성되는 객체들로, Product 인터페이스를 구현합니다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>객체 생성의 책임 분리: 객체 생성 로직을 별도의 메서드로 분리하여 관리합니다.</li>
<li>확장성: 새로운 타입의 객체를 쉽게 추가할 수 있으며, 클라이언트 코드를 수정하지 않고도 새로운 객체를 생성할 수 있습니다.</li>
<li>유연성: 클라이언트는 추상 클래스를 통해 작업하므로 객체 생성 방식을 변경하거나 확장해도 클라이언트 코드에는 영향을 주지 않습니다.</li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod

# 1. Product 인터페이스 정의
class Document(ABC):
    @abstractmethod
    def render(self):
        pass

# 2. ConcreteProduct 클래스 정의
class PDFDocument(Document):
    def render(self):
        return &quot;Rendering PDF Document&quot;

class WordDocument(Document):
    def render(self):
        return &quot;Rendering Word Document&quot;

# 3. Creator 추상 클래스 정의
class DocumentCreator(ABC):
    @abstractmethod
    def create_document(self) -&gt; Document:
        pass

    def render_document(self):
        # 팩토리 메서드를 통해 Document 객체를 생성하여 사용하는 메서드
        doc = self.create_document()
        return doc.render()

# 4. ConcreteCreator 클래스 정의
class PDFCreator(DocumentCreator):
    def create_document(self) -&gt; Document:
        return PDFDocument()

class WordCreator(DocumentCreator):
    def create_document(self) -&gt; Document:
        return WordDocument()

# 5. 클라이언트 코드
if __name__ == &quot;__main__&quot;:
    creators = [PDFCreator(), WordCreator()]

    for creator in creators:
        print(creator.render_document())
    # 출력:
    # Rendering PDF Document
    # Rendering Word Document</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인패턴 - Factory]]></title>
            <link>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Factory</link>
            <guid>https://velog.io/@code_able/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-Factory</guid>
            <pubDate>Sat, 09 Nov 2024 13:42:22 GMT</pubDate>
            <description><![CDATA[<h3 id="factory-패턴이란">Factory 패턴이란</h3>
<p>팩토리 디자인 패턴은 객체 생성 로직을 캡슐화하여 코드의 유연성과 확장성을 높이는 디자인 패턴입니다. 이를 통해 객체 생성에 관한 로직을 클라이언트 코드에서 분리하고, 객체 생성 방식을 재정의하거나 확장할 수 있는 구조를 제공합니다.</p>
<p>팩토리 패턴의 핵심 아이디어는 객체 생성 과정을 메소드에 위임하여, 객체를 직접 생성하지 않고 해당 메소드가 올바른 인스턴스를 반환하도록 하는 것입니다. 이는 코드 변경 없이 새로운 객체 타입을 쉽게 추가할 수 있도록 하며, 특히 클라이언트 코드가 생성되는 구체 클래스에 의존하지 않도록 하는 데에 효과적입니다.</p>
<h3 id="장점">장점</h3>
<ul>
<li>유연성 증가: 객체 생성 로직을 한 곳에 집중하여 유지보수와 확장이 쉽습니다.</li>
<li>코드 재사용성: 클라이언트 코드가 객체 생성 방식을 알 필요가 없으므로 코드 재사용성이 높아집니다.</li>
<li>의존성 감소: 클라이언트는 구체 클래스가 아닌 인터페이스에 의존하므로, 코드 변경이 덜 필요하게 됩니다.</li>
</ul>
<h3 id="예제-코드">예제 코드</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod

# 1. Animal 추상 클래스 정의
class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

# 2. 구체적인 클래스 정의
class Dog(Animal):
    def speak(self):
        return &quot;Woof!&quot;

class Cat(Animal):
    def speak(self):
        return &quot;Meow!&quot;

# 3. AnimalFactory 클래스 정의
class AnimalFactory:
    @staticmethod
    def create_animal(animal_type):
        if animal_type == &quot;dog&quot;:
            return Dog()
        elif animal_type == &quot;cat&quot;:
            return Cat()
        else:
            raise ValueError(f&quot;Unknown animal type: {animal_type}&quot;)

# 4. 클라이언트 코드에서 팩토리 메서드를 사용해 객체 생성
if __name__ == &quot;__main__&quot;:
    animal_type = &quot;dog&quot;  # 혹은 &quot;cat&quot;
    animal = AnimalFactory.create_animal(animal_type)
    print(animal.speak())  # 출력: &quot;Woof!&quot; 또는 &quot;Meow!&quot;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[minikube로 fastapi 배포]]></title>
            <link>https://velog.io/@code_able/minikube%EB%A1%9C-fastapi-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@code_able/minikube%EB%A1%9C-fastapi-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Tue, 16 Jul 2024 11:50:24 GMT</pubDate>
            <description><![CDATA[<h3 id="사용하는-기기-정보">사용하는 기기 정보</h3>
<p>Rasberry pi5
ubuntu 23.10</p>
<h3 id="fastapi-코드-만들기">fastapi 코드 만들기</h3>
<p>프로젝트세팅</p>
<pre><code>pip install poetry
poetry new fastapi-ml

fastapi-ml
├── app
├──── __init__.py
├──── main.py
├── tests
├──── __init__.py
├── Dockerfile
├── poetry.lock
├── project.toml
├── README.md</code></pre><p>main.py 작성</p>
<pre><code class="language-python">from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;*&quot;],
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
)


@app.get(&quot;/check&quot;)
def check():
    return &quot;success&quot;
</code></pre>
<p>dockerfile 작성</p>
<pre><code>FROM python:3.11.6-slim

RUN mkdir -p /app
COPY . /app/
WORKDIR /app

RUN pip3 install poetry
# poetry lock에서 requirements.txt 생성
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
RUN pip3 install --no-cache-dir -r requirements.txt

# app 실행
CMD gunicorn app.main:app --bind 0.0.0.0:8000 -w 3 -k uvicorn.workers.UvicornWorker</code></pre><p>docker hub 배포</p>
<pre><code class="language-shell">docker build -t stygj/fastapi-ml:0.0.1 .
docker push stygj/fastapi-ml:0.0.1</code></pre>
<h3 id="minikube-설치">minikube 설치</h3>
<pre><code class="language-shell">curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-arm64
sudo install minikube-linux-arm64 /usr/local/bin/minikube &amp;&amp; rm minikube-linux-arm64</code></pre>
<h3 id="minikube-시작">minikube 시작</h3>
<pre><code class="language-shell">minikube start</code></pre>
<h3 id="deployment-만들기">deployment 만들기</h3>
<p>fastapi-ml-deployment.yaml 작성</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-ml-deployment
  labels:
    app: fastapi-ml
spec:
  replicas: 3
  selector:
    matchLabels: 
      app: fastapi-ml
  template:
    metadata:
      labels:
        app: fastapi-ml
    spec:
      containers:
        - name: fastapi-ml
          image: stygj/fastapi-ml:0.0.1
          ports:
          - containerPort: 8000</code></pre>
<p>실행</p>
<pre><code class="language-shell">kubectl apply -f fastapi-ml-deployment.yaml</code></pre>
<h3 id="service-만들기">service 만들기</h3>
<p>fastapi-ml-service.yaml 작성</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: fastapi-ml-service
  labels:
    name: fastapi-ml
spec:
  selector:
    app: fastapi-ml
  type: NodePort
  ports:
  - port: 8000
    targetPort: 8000
    name: http
    protocol: TCP</code></pre>
<p>실행</p>
<pre><code class="language-shell">kubectl apply -f fastapi-ml-service.yaml
minikube service fastapi-ml-service --url

kubectl get service
kubectl describe svc/fastapi-ml-service</code></pre>
<p>테스트</p>
<pre><code class="language-shell">kubectl get pods
kubectl logs -f [podsname]

kubectl port-forward svc/fastapi-ml-service --address=0.0.0.0 8000:8000
curl -XGET http://127.0.0.1:8000/check</code></pre>
<h3 id="ingress-만들기">ingress 만들기</h3>
<p>fastapi-ml-ingress.yaml 작성</p>
<pre><code class="language-yaml">apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: fastapi-ml-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: fastapi-ml-service
            port:
              number: 8000</code></pre>
<p>실행</p>
<pre><code>kubectl apply -f fastapi-ml-ingress.yaml

kubectl get ingess
kubectl describe ingress fastapi-ml-ingress</code></pre><p>테스트</p>
<pre><code>minikube ip -&gt; 192.168.49.2
curl -XGET http://192.168.49.2/check</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Junit Assert 써보기]]></title>
            <link>https://velog.io/@code_able/Junit-Assert-%EC%8D%A8%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@code_able/Junit-Assert-%EC%8D%A8%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 01 Jul 2024 03:23:19 GMT</pubDate>
            <description><![CDATA[<h1 id="junit-에서-제공하는-assert-알아보기">junit 에서 제공하는 assert 알아보기</h1>
<h3 id="assertnotnull">assertNotNull</h3>
<p>특정 객체가 null이 아님을 확인합니다. </p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertNotNull;

public class MyTests {
    @Test
    public void testObjectIsNotNull() {
        String str = &quot;JUnit Test&quot;;
        assertNotNull(&quot;The object should not be null&quot;, str);
    }
}</code></pre>
<h3 id="assertnull">assertNull</h3>
<p>객체가 null인지 확인합니다.</p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertNull;

public class MyTests {
    @Test
    public void testObjectIsNull() {
        String str = null;
        assertNull(&quot;The object should be null&quot;, str);
    }
}</code></pre>
<h3 id="assertequals">assertEquals</h3>
<p>두 값이 동일한지 확인합니다.</p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class MyTests {
    @Test
    public void testSum() {
        int expectedSum = 5;
        int actualSum = sum(2, 3);
        assertEquals(&quot;Sum should be 5&quot;, expectedSum, actualSum);
    }

    public int sum(int a, int b) {
        return a + b;
    }
}</code></pre>
<h3 id="asserttrue--assertfalse">assertTrue &amp; assertFalse</h3>
<p>주어진 조건이 true또는 false인지 확인합니다.</p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;

public class MyTests {
    @Test
    public void testIsEven() {
        assertTrue(&quot;Number should be even&quot;, isEven(4));
        assertFalse(&quot;Number should be odd&quot;, isEven(3));
    }

    public boolean isEven(int number) {
        return number % 2 == 0;
    }
}</code></pre>
<h3 id="assertarrayequals">assertArrayEquals</h3>
<p>두 배열이 동일한지 비교합니다. </p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;

public class MyTests {
    @Test
    public void testArrayEquals() {
        int[] expectedArray = {1, 2, 3};
        int[] actualArray = {1, 2, 3};
        assertArrayEquals(&quot;Arrays should be equal&quot;, expectedArray, actualArray);
    }
}</code></pre>
<h3 id="assertthrows">assertThrows</h3>
<p>특정 예외가 발생하는지를 확인합니다.</p>
<pre><code class="language-java">import org.junit.Test;
import static org.junit.Assert.assertThrows;

public class MyTests {
    @Test
    public void testThrowsException() {
        IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -&gt; {
            throw new IllegalArgumentException(&quot;Exception message&quot;);
        });

        assertEquals(ex.getMessage(), &quot;Exception message&quot;);
    }
}
</code></pre>
<h3 id="assertall">assertAll</h3>
<p>람다 표현식이나 메서드 참조를 사용하여 각 단언을 전달합니다.</p>
<pre><code class="language-java">import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class MyTests {
    @Test
    public void testMultipleAssertions() {
        String str1 = &quot;JUnit 5&quot;;
        String str2 = &quot;JUnit 5&quot;;
        String str3 = &quot;Unit testing&quot;;

        assertAll(&quot;Test all assertions&quot;,
            () -&gt; assertEquals(&quot;JUnit 5&quot;, str1),
            () -&gt; assertEquals(&quot;JUnit 5&quot;, str2),
            () -&gt; assertEquals(&quot;JUnit 5&quot;, str3)  // This will fail
        );
    }
}</code></pre>
<h1 id="assertj의-assertthat-알아보기">assertj의 assertThat 알아보기</h1>
<h3 id="객체-비교">객체 비교</h3>
<pre><code class="language-java">import static org.assertj.core.api.Assertions.assertThat;

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class MyTests {
    @Test
    public void testObjects() {
        Person person = new Person(&quot;John&quot;, 30);
        assertThat(person).isNotNull();
        assertThat(person.name).isEqualTo(&quot;John&quot;);
        assertThat(person.age).isBetween(20, 40);
    }
}</code></pre>
<h3 id="타입-비교">타입 비교</h3>
<pre><code class="language-java">import static org.assertj.core.api.Assertions.assertThat;

public class MyTests {
    @Test
    public void testBasicTypes() {
        int number = 10;
        assertThat(number).isGreaterThan(5).isLessThanOrEqualTo(10);

        String text = &quot;Hello&quot;;
        assertThat(text).isEqualTo(&quot;Hello&quot;).startsWith(&quot;He&quot;).endsWith(&quot;lo&quot;).contains(&quot;ell&quot;);
    }
}</code></pre>
<h3 id="컬렉션-및-배열-비교">컬렉션 및 배열 비교</h3>
<pre><code class="language-java">import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;

public class MyTests {
    @Test
    public void testCollections() {
        List&lt;String&gt; names = Arrays.asList(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;);
        assertThat(names).hasSize(3).contains(&quot;Alice&quot;, &quot;Charlie&quot;).doesNotContain(&quot;Eve&quot;);

        int[] numbers = {1, 2, 3, 4, 5};
        assertThat(numbers).containsExactly(1, 2, 3, 4, 5).doesNotContain(0);
    }
}</code></pre>
<h3 id="예외-검증">예외 검증</h3>
<pre><code class="language-java">import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class MyTests {
    @Test
    public void testException() {
        assertThatThrownBy(() -&gt; { throw new IllegalArgumentException(&quot;Invalid argument&quot;); })
            .isInstanceOf(IllegalArgumentException.class)
            .hasMessage(&quot;Invalid argument&quot;);
    }
}</code></pre>
<h3 id="assumingthat">assumingThat</h3>
<ul>
<li>조건: boolean 표현식으로, 이 조건이 참일 때 코드 블록이 실행됩니다.</li>
<li>코드 블록: 조건이 참일 때 실행할 코드 블록을 람다 표현식으로 작성합니다.</li>
</ul>
<pre><code class="language-java">import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;
import org.junit.jupiter.api.Test;

public class MyTests {

    @Test
    public void testWithAssumption() {
        String environment = System.getenv(&quot;TEST_ENV&quot;);

        // 조건이 참일 때만 코드 블록을 실행합니다.
        assumingThat(
            &quot;PROD&quot;.equals(environment),
            () -&gt; {
                // 환경이 &quot;PROD&quot;일 때만 실행
                System.out.println(&quot;Running in production environment&quot;);
                // production-specific tests
            }
        );

        // 항상 실행되는 코드
        System.out.println(&quot;Running in all environments&quot;);
        // common tests
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 3버전 Security+JWT 구현 (2)]]></title>
            <link>https://velog.io/@code_able/Spring-3%EB%B2%84%EC%A0%84-SecurityJWT-%EA%B5%AC%ED%98%84-2</link>
            <guid>https://velog.io/@code_able/Spring-3%EB%B2%84%EC%A0%84-SecurityJWT-%EA%B5%AC%ED%98%84-2</guid>
            <pubDate>Sun, 09 Jun 2024 13:49:55 GMT</pubDate>
            <description><![CDATA[<h3 id="환경">환경</h3>
<p>java 17
Spring 3.3.0
Postgresql 15.5</p>
<h3 id="user-table-flyway-v1__initsql">User table (flyway V1__init.sql)</h3>
<pre><code class="language-sql">CREATE SEQUENCE users_seq START WITH 1;
COMMENT ON SEQUENCE users_seq IS &#39;users 테이블에 대한 유일한 ID를 생성하기 위한 시퀀스&#39;;

CREATE TABLE users (
    id BIGINT DEFAULT nextval(&#39;users_seq&#39;) PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100) ,
    phone VARCHAR(20),
    authority VARCHAR(5),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP
)
;
COMMENT ON TABLE users IS &#39;사용자 정보를 저장하는 테이블&#39;;
COMMENT ON COLUMN users.id IS &#39;사용자를 식별하는 고유값&#39;;
COMMENT ON COLUMN users.username IS &#39;사용자의 계정 이름&#39;;
COMMENT ON COLUMN users.password IS &#39;사용자릐 계정 암호&#39;;
COMMENT ON COLUMN users.email IS &#39;사용자의 이메일&#39;;
COMMENT ON COLUMN users.phone IS &#39;사용자의 전화번호&#39;;
COMMENT ON COLUMN users.authority IS &#39;사용자의 권한&#39;;
COMMENT ON COLUMN users.created_at IS &#39;생성일시&#39;;
COMMENT ON COLUMN users.updated_at IS &#39;변경일시&#39;;
COMMENT ON COLUMN users.deleted_at IS &#39;삭제일시&#39;;</code></pre>
<h3 id="buildgradle">build.gradle</h3>
<pre><code>    // Spring security
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    testImplementation &#39;org.springframework.security:spring-security-test&#39;

    // JWT
    implementation group: &#39;io.jsonwebtoken&#39;, name: &#39;jjwt-api&#39;, version: &#39;0.11.5&#39;
    runtimeOnly group: &#39;io.jsonwebtoken&#39;, name: &#39;jjwt-impl&#39;, version: &#39;0.11.5&#39;
    runtimeOnly group: &#39;io.jsonwebtoken&#39;, name: &#39;jjwt-jackson&#39;, version: &#39;0.11.5&#39;</code></pre><h3 id="applicationyml">application.yml</h3>
<pre><code class="language-yaml">jwt:
  secret: [secret key]
  expirationTime: [expiration time]</code></pre>
<h3 id="securityconfigjava">SecurityConfig.java</h3>
<p>비밀번호 인코더 빈 생성</p>
<pre><code class="language-java">@Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
</code></pre>
<p>RESTFUL API에서 불필요하여 CSRF를 비활성화</p>
<pre><code class="language-java">http.csrf(AbstractHttpConfigurer::disable);</code></pre>
<p>예외 처리 구성</p>
<pre><code class="language-java">http.exceptionHandling(exception -&gt;
        exception.authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler));</code></pre>
<p>세션을 STATELESS로 설정</p>
<pre><code class="language-java">http.sessionManagement(sessionManagement -&gt;
        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));</code></pre>
<p>JWT 필터 추가</p>
<pre><code class="language-java">http.addFilterBefore(new JwtFilter(tokenProvider, customUserDetailsService), UsernamePasswordAuthenticationFilter.class);</code></pre>
<p>인증 없이 접근할 수 있는 엔드포인트 설정</p>
<pre><code class="language-java">return web -&gt; web.ignoring().requestMatchers(
        &quot;/swagger-ui/**&quot;,
        &quot;/signUp&quot;,
        &quot;/signIn&quot;);</code></pre>
<p>전체 코드</p>
<pre><code class="language-java">import com.boilerplate.jwt.*;
import com.boilerplate.service.CustomUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
    private final TokenProvider tokenProvider;

    private final CustomUserDetailsService customUserDetailsService;

    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(exception -&gt;
                        exception.authenticationEntryPoint(jwtAuthenticationEntryPoint)
                                .accessDeniedHandler(jwtAccessDeniedHandler))
                .sessionManagement(sessionManagement -&gt;
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                ).authorizeHttpRequests(authorizeRequests -&gt;
                        authorizeRequests
                                .anyRequest().authenticated()
                )
                .addFilterBefore(new JwtFilter(tokenProvider, customUserDetailsService), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer configure() {
        return web -&gt; web.ignoring().requestMatchers(
                &quot;/swagger-ui/**&quot;,
                &quot;/signUp&quot;,
                &quot;/signIn&quot;
        );
    }
}</code></pre>
<h3 id="jwt-토큰-발급-구현">JWT 토큰 발급 구현</h3>
<pre><code class="language-java">import com.boilerplate.entitiy.Users;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;

@Component
public class TokenProvider implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class);

    private final String secret;

    private final Long expirationTime;

    private Key key;

    public TokenProvider(@Value(&quot;${jwt.secret}&quot;) String secret, @Value(&quot;${jwt.expirationTime}&quot;) long expirationTime) {
        this.secret = secret;
        this.expirationTime = expirationTime;
    }

    @Override
    public void afterPropertiesSet() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    public String createToken(Users uesrs) {
        Claims claims = Jwts.claims();
        claims.put(&quot;username&quot;, uesrs.getUsername());
        claims.put(&quot;email&quot;, uesrs.getEmail());
        claims.put(&quot;phone&quot;, uesrs.getPhone());
        claims.put(&quot;authority&quot;, uesrs.getAuthority());

        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime tokenValidity = now.plusSeconds(this.expirationTime);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(Date.from(now.toInstant()))
                .setExpiration(Date.from(tokenValidity.toInstant()))
                .signWith(this.key, SignatureAlgorithm.HS256)
                .compact();
    }

    public Collection&lt;? extends GrantedAuthority&gt; getAuthentication(String token) {
        Claims claims = getAllClaims(token);

        List&lt;String&gt; authorites = new ArrayList&lt;&gt;();
        authorites.add(&quot;ROLE_&quot;+ claims.get(&quot;authority&quot;).toString());

        return authorites.stream()
                .map(SimpleGrantedAuthority::new)
                .toList();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            logger.info(&quot;Invalid JWT Token&quot;);
        } catch (ExpiredJwtException e) {
            logger.info(&quot;Expired JWT Token&quot;);
        } catch (UnsupportedJwtException e) {
            logger.info(&quot;Unsupported JWT Token&quot;);
        } catch (IllegalArgumentException e) {
            logger.info(&quot;JWT claims string is empty&quot;);
        }
        return false;
    }

    public Claims getAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(this.secret).build()
                .parseClaimsJws(token)
                .getBody();
    }

}</code></pre>
<h3 id="jwt-필터-구현">JWT 필터 구현</h3>
<pre><code class="language-java">import com.boilerplate.service.CustomUserDetailsService;
import com.querydsl.core.util.StringUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;

@RequiredArgsConstructor
public class JwtFilter extends GenericFilterBean {
    private static final Logger log = LoggerFactory.getLogger(JwtFilter.class);

    private final TokenProvider tokenProvider;

    private final CustomUserDetailsService customUserDetailsService;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        String jwt = resolveToken(httpServletRequest);
        String requestURI = httpServletRequest.getRequestURI();

        String username = String.valueOf(tokenProvider.getAllClaims(jwt).get(&quot;username&quot;));
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);

        if (!StringUtils.isNullOrEmpty(jwt) &amp;&amp; tokenProvider.validateToken(jwt)) {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(userDetails, null, tokenProvider.getAuthentication(jwt));
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            log.debug(&quot;save {} in security config / uri : {}&quot;, usernamePasswordAuthenticationToken.getName(), requestURI);
        } else {
            log.debug(&quot;JWT is empty or null / uri : {}&quot;, requestURI);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private String resolveToken(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        Optional&lt;Cookie&gt; authorizationCookie = Arrays.stream(cookies)
                .filter(cookie -&gt; &quot;Autentication&quot;.equals(cookie.getName()))
                .findFirst();
        String bearerToken = authorizationCookie.map(Cookie::getValue).orElse(null);
        if (!StringUtils.isNullOrEmpty(bearerToken)) {
            return bearerToken;
        }
        return null;
    }
}</code></pre>
<h3 id="customuserdetailsjava-구현">CustomUserDetails.java 구현</h3>
<pre><code class="language-java">import com.boilerplate.dto.SignInRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

    private final transient SignInRequest signInRequest;

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        return Collections.emptyList();
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return null;
    }
}</code></pre>
<h3 id="customuserdetailsservicejava-구현">CustomUserDetailsService.java 구현</h3>
<pre><code class="language-java">import com.boilerplate.dto.SignInRequest;
import com.boilerplate.entitiy.Users;
import com.boilerplate.jwt.CustomUserDetails;
import com.boilerplate.repository.CustomUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final CustomUserRepository customUserRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users users = customUserRepository.findOneWithAuthoritiesByUsername(username);

        SignInRequest signInRequest = SignInRequest.builder()
                .username(users.getUsername())
                .password(users.getPassword())
                .build();
        return new CustomUserDetails(signInRequest);
    }
}</code></pre>
<h3 id="권한에러-예외-핸들러-구현">권한에러 예외 핸들러 구현</h3>
<pre><code class="language-java">import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN);
    }
}</code></pre>
<h3 id="인증에러-예외-구현">인증에러 예외 구현</h3>
<pre><code class="language-java">import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}</code></pre>
<h3 id="user-entity">User Entity</h3>
<pre><code class="language-java">import com.boilerplate.enums.UserAuthority;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Table(name=&quot;users&quot;)
@SequenceGenerator(
        name = &quot;users_seq&quot;,
        sequenceName = &quot;users_seq&quot;,
        allocationSize = 1
)
@Getter
public class Users {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = &quot;users_seq&quot;)
    private Long id;

    @Column(name = &quot;username&quot;, length = 50)
    private String username;

    @Column(name = &quot;password&quot;, length = 255)
    private String password;

    @Column(name = &quot;email&quot;, length = 100)
    private String email;

    @Column(name = &quot;phone&quot;, length = 100)
    private String phone;

    @Column(name=&quot;authority&quot;)
    @Enumerated(EnumType.STRING)
    private UserAuthority authority;

    @Column(name = &quot;created_at&quot;)
    private LocalDateTime createdAt;

    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;

    @Column(name = &quot;deleted_at&quot;)
    private LocalDateTime deletedAt;

    @Builder
    public Users(String username, String password, String email, String phone, LocalDateTime createdAt) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.phone = phone;
        this.createdAt = createdAt;

    }
}</code></pre>
<h3 id="customuserrepositoryjava">CustomUserRepository.java</h3>
<pre><code class="language-java">import com.boilerplate.entitiy.QUsers;
import com.boilerplate.entitiy.Users;
import com.boilerplate.repository.CustomUserRepository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@AllArgsConstructor
public class CustomUserRepositoryImpl implements CustomUserRepository {
    private final JPAQueryFactory queryFactory;

    @Override
    public boolean existsByUsername(String username) {
        QUsers user = QUsers.users;
        return queryFactory
                .select(user.username)
                .from(user)
                .where(user.username.eq(username)
                        .and(user.deletedAt.isNull()))
                .fetchFirst() != null;
    }

    @Override
    public Users findOneWithAuthoritiesByUsername(String username) {
        QUsers user = QUsers.users;
        return queryFactory
                    .select(user)
                    .from(user)
                    .where(user.username.eq(username)
                            .and(user.deletedAt.isNull()))
                    .fetchOne();
    }
}</code></pre>
<h3 id="authservicejava">AuthService.java</h3>
<pre><code class="language-java">import com.boilerplate.dto.SignInRequest;
import com.boilerplate.dto.SignUpRequest;
import com.boilerplate.entitiy.Users;
import com.boilerplate.jwt.TokenProvider;
import com.boilerplate.repository.CustomUserRepository;
import com.boilerplate.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;

    private final CustomUserRepository customUserRepository;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    private final TokenProvider tokenProvider;

    public boolean registerUSer(SignUpRequest signUpRequest) {
        String username = signUpRequest.getUsername();
        boolean isExists = this.customUserRepository.existsByUsername(username);
        if (isExists) {
            return false;
        }

        Users user = Users.builder()
                .username(signUpRequest.getUsername())
                .password(bCryptPasswordEncoder.encode(signUpRequest.getPassword()))
                .email(signUpRequest.getEmail())
                .phone(signUpRequest.getPhone())
                .createdAt(LocalDateTime.now())
                .build();
        this.userRepository.save(user);
        return true;
    }

    @Transactional
    public String authenticateUser(SignInRequest signInRequest) throws UsernameNotFoundException{
        Users users = this.customUserRepository.findOneWithAuthoritiesByUsername(signInRequest.getUsername());
        if (bCryptPasswordEncoder.matches(signInRequest.getPassword(), users.getPassword())) {
            return tokenProvider.createToken(users);
        }
        throw new UsernameNotFoundException(signInRequest.getUsername() + &quot; is not found.&quot;);
    }
}</code></pre>
<h3 id="authcontrollerjava">AuthController.java</h3>
<pre><code class="language-java">import com.boilerplate.dto.SignInRequest;
import com.boilerplate.dto.SignUpRequest;
import com.boilerplate.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.boilerplate.response.MessageResponse;

@Tag(name = &quot;Sign up&quot;, description = &quot;회원가입 API&quot;)
@RestController
@ResponseBody
@RequiredArgsConstructor
public class AuthController {

    @Value(&quot;${jwt.expirationTime}&quot;)
    private int expirationTime;

    @Value(&quot;${cookie.SameSite}&quot;)
    private String sameSite;

    private final AuthService authService;

    @Operation(summary = &quot;로그인&quot;, description = &quot;로그인&quot;)
    @ApiResponse(responseCode = &quot;200&quot;, description = &quot;OK&quot;, content = @Content(schema = @Schema(implementation = ApiResponse.class)))
    @ApiResponse(responseCode = &quot;401&quot;, description = &quot;Unauthorized&quot;, content = @Content(schema = @Schema(implementation = ApiResponse.class)))
    @PostMapping(path = &quot;signIn&quot;)
    public ResponseEntity&lt;MessageResponse&gt; authenticateUser(@RequestBody SignInRequest signInDto, HttpServletResponse response) {
        String accessToekn = authService.authenticateUser(signInDto);
        Cookie cookie = new Cookie(&quot;Autentication&quot;,  accessToekn);
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        cookie.setMaxAge(expirationTime);
        cookie.setAttribute(&quot;SameSite&quot;, this.sameSite);
        response.addCookie(cookie);
        return ResponseEntity.status(HttpStatus.OK).body(new MessageResponse(&quot;success&quot;, 200));
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 3버전 Security+JWT 설명 (1)]]></title>
            <link>https://velog.io/@code_able/Spring-3%EB%B2%84%EC%A0%84-SecurityJWT-%EC%84%A4%EB%AA%85-1</link>
            <guid>https://velog.io/@code_able/Spring-3%EB%B2%84%EC%A0%84-SecurityJWT-%EC%84%A4%EB%AA%85-1</guid>
            <pubDate>Sun, 09 Jun 2024 13:30:53 GMT</pubDate>
            <description><![CDATA[<p>블로그나 인프런에서 Spring security를 찾아보면 Spring 2버전의 글과 영상이 대부분입니다. 그래서 제가 한번 3버전의 글을 올려 보고자 합니다. ㅎㅎ</p>
<h3 id="spring-security란">Spring Security란</h3>
<p>스프링 프레임워크 기반의 애플리케이션에서 인증(Authentication)과 권한 부여(Authorization)를 관리하는 강력한 보안 프레임워크입니다. 주요 기능으로는 사용자의 신원을 확인하고, 특정 리소스나 기능에 대한 접근 권한을 제어하는 것입니다. 이 프레임워크는 다양한 인증 메커니즘을 지원하며, 사용자 정의가 용이하도록 설계되었습니다.</p>
<h3 id="구성요소">구성요소</h3>
<ul>
<li><p>Authentication(인증)</p>
<ul>
<li>사용자의 신원을 확인하는 과정입니다. 예를 들어, 사용자 이름과 비밀번호를 입력하여 사용자가 누구인지 식별합니다.</li>
<li>스프링 시큐리티에서는 AuthenticationManager가 인증을 처리하며, 사용자 정보를 담고 있는 Authentication 객체를 반환합니다.</li>
</ul>
</li>
<li><p>Authorization(권한 부여)</p>
<ul>
<li>인증된 사용자가 어떤 리소스나 기능에 접근할 수 있는지를 결정하는 과정입니다. 예를 들어, 관리자만 특정 페이지에 접근할 수 있도록 설정합니다.</li>
<li>권한은 GrantedAuthority로 표현되며, SecurityContext에 저장된 사용자의 Authentication 객체를 통해 확인할 수 있습니다.</li>
</ul>
</li>
<li><p>SecurityContext</p>
<ul>
<li>현재 인증된 사용자에 대한 정보를 담고 있는 컨텍스트입니다. SecurityContextHolder를 통해 전역적으로 접근할 수 있으며, SecurityContext는 Authentication 객체를 보관합니다.</li>
</ul>
</li>
<li><p>Filter Chain</p>
<ul>
<li>다양한 보안 필터들이 체인으로 구성되어 요청을 처리합니다. 각 필터는 인증이나 권한 부여 등의 보안 작업을 수행합니다.</li>
<li>스프링 시큐리티는 UsernamePasswordAuthenticationFilter, JwtFilter와 같은 다양한 기본 필터를 제공합니다.</li>
</ul>
</li>
<li><p>HttpSecurity</p>
<ul>
<li>웹 애플리케이션 보안을 구성하는 데 사용되는 주요 클래스입니다. 보안 설정을 통해 URL 패턴, 인증 방법, 권한 부여 규칙 등을 정의할 수 있습니다.</li>
<li>주로 configure(HttpSecurity http) 메서드를 오버라이드하여 보안 설정을 적용합니다.</li>
</ul>
</li>
<li><p>AuthenticationManager</p>
<ul>
<li>인증을 처리하는 인터페이스로, authenticate 메서드를 통해 사용자의 인증을 처리합니다.</li>
</ul>
</li>
<li><p>UserDetailsService</p>
<ul>
<li>사용자 정보를 가져오는 서비스입니다. 주로 데이터베이스에서 사용자 정보를 조회하여 UserDetails 객체를 반환합니다.</li>
<li>사용자 인증을 위해 loadUserByUsername 메서드를 구현해야 합니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[spring boot - interceptor]]></title>
            <link>https://velog.io/@code_able/spring-boot-interceptor</link>
            <guid>https://velog.io/@code_able/spring-boot-interceptor</guid>
            <pubDate>Sat, 01 Jun 2024 02:48:01 GMT</pubDate>
            <description><![CDATA[<h3 id="인터셉터란">인터셉터란</h3>
<p>서버로 들어온 Request 객체를 컨트롤러의 핸들러에 오기 전에
가로채서 원하는 작업을 할 수 있도록 하는 기능이다.
주로 로그인 여부나 권한 등의 여부를 판단하는 기능에 활용 된다.</p>
<h3 id="작동-과정">작동 과정</h3>
<ol>
<li>url을 통해 서버에 request 객체 전송</li>
<li>DispatcherServlet은 request객체를받아 핸들러를 찾도록 요청한다.</li>
<li>핸들러 실행체인이 인터셉터를 거처 실행된다.</li>
</ol>
<h3 id="인터셉터의-장점">인터셉터의 장점</h3>
<ol>
<li><p>메모리 낭비 및 서버의 부하를 감소 시킨다.
컨트롤러 마다 검증 코드를 붙이면 그만큼 메모리에 올려야 하는 코드가 많아 지며
서버의 부담이 커지는데 이를 인터셉터 하나에서 관리하면 해결이 가능하다.</p>
</li>
<li><p>코드의 누락 방지
특정 컨트롤러에서 로그인 판단 로직을 빼먹을 걱정을 줄일 수 있다.</p>
</li>
</ol>
<h3 id="구현-방법">구현 방법</h3>
<ol>
<li><p>HandlerInterceptor로 인터페이스를 구현하거나 HandlerInterceptorAdapor를 상속받아
오버라이딩을 하여 인터셉터 클래스를 만든다.</p>
</li>
<li><p>부모 클래스에 오버라이딩 할 수 있는 메서드는 3가지다</p>
</li>
</ol>
<ul>
<li>preHandle()<ul>
<li>컨트롤러가 호출 되기 전 실행</li>
<li>리턴값이 boolean형으로 true일때는 핸들러에 접근하고 false일 경우 작업을 중단 한다.</li>
</ul>
</li>
<li>postHandle()<ul>
<li>핸들러의 실행이 완료되고 view가 생성되기 전에 호출된다.</li>
<li>model 객체의 정보를 조작할 수 있다.</li>
</ul>
</li>
<li>afterCompletion()<ul>
<li>view에 최종결과를 생성한 후 실행 된다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 데이터베이스의 컬럼과 엔티티의 속성을 매핑]]></title>
            <link>https://velog.io/@code_able/JPA-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-%EC%BB%AC%EB%9F%BC%EA%B3%BC-%EC%97%94%ED%8B%B0%ED%8B%B0%EC%9D%98-%EC%86%8D%EC%84%B1%EC%9D%84-%EB%A7%A4%ED%95%91</link>
            <guid>https://velog.io/@code_able/JPA-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EC%9D%98-%EC%BB%AC%EB%9F%BC%EA%B3%BC-%EC%97%94%ED%8B%B0%ED%8B%B0%EC%9D%98-%EC%86%8D%EC%84%B1%EC%9D%84-%EB%A7%A4%ED%95%91</guid>
            <pubDate>Sat, 01 Jun 2024 02:47:14 GMT</pubDate>
            <description><![CDATA[<h3 id="entity">@Entity</h3>
<p>@Entity 어노테이션은 클래스가 JPA 엔티티임을 명시합니다. 이 어노테이션이 선언된 클래스는 데이터베이스 테이블에 매핑됩니다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    // getters and setters
}</code></pre>
<h3 id="table">@Table</h3>
<p>@Table 어노테이션은 엔티티 클래스가 매핑될 테이블의 이름을 지정합니다. 생략할 경우 클래스 이름이 테이블 이름으로 사용됩니다.</p>
<pre><code class="language-java">@Entity
@Table(name = &quot;products&quot;)
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    // getters and setters
}</code></pre>
<h3 id="id">@Id</h3>
<p>@Id 어노테이션은 엔티티의 기본 키를 지정합니다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    // getters and setters
}</code></pre>
<h3 id="generatedvalue">@GeneratedValue</h3>
<p>@GeneratedValue 어노테이션은 기본 키 값의 자동 생성을 지정합니다. strategy 속성을 통해 다양한 생성 전략을 설정할 수 있습니다. 주요 전략은 다음과 같습니다:</p>
<p>GenerationType.AUTO: JPA 구현체가 자동으로 선택합니다.
GenerationType.IDENTITY: 데이터베이스의 IDENTITY 컬럼을 사용합니다.
GenerationType.SEQUENCE: 데이터베이스의 시퀀스를 사용합니다.
GenerationType.TABLE: 키 생성용 별도 테이블을 사용합니다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    // getters and setters
}</code></pre>
<h3 id="column">@Column</h3>
<p>@Column 어노테이션은 엔티티 필드를 데이터베이스 컬럼에 매핑하는 데 사용됩니다. 다양한 속성을 통해 컬럼의 세부 설정을 지정할 수 있습니다.</p>
<p>name: 컬럼 이름
nullable: NULL 가능 여부
unique: 유니크 제약 조건 설정
length: 문자열 컬럼의 길이
precision, scale: 숫자 컬럼의 정밀도와 스케일</p>
<pre><code class="language-java">@Column
@Column 어노테이션은 엔티티 필드를 데이터베이스 컬럼에 매핑하는 데 사용됩니다. 다양한 속성을 통해 컬럼의 세부 설정을 지정할 수 있습니다.

name: 컬럼 이름
nullable: NULL 가능 여부
unique: 유니크 제약 조건 설정
length: 문자열 컬럼의 길이
precision, scale: 숫자 컬럼의 정밀도와 스케일</code></pre>
<h3 id="temporal">@Temporal</h3>
<p>@Temporal 어노테이션은 날짜/시간 타입을 매핑할 때 사용됩니다. TemporalType 속성을 통해 날짜, 시간 또는 타임스탬프를 지정할 수 있습니다.</p>
<p>TemporalType.DATE: 날짜 정보만
TemporalType.TIME: 시간 정보만
TemporalType.TIMESTAMP: 날짜와 시간 정보</p>
<pre><code class="language-java">@Temporal
@Temporal 어노테이션은 날짜/시간 타입을 매핑할 때 사용됩니다. TemporalType 속성을 통해 날짜, 시간 또는 타임스탬프를 지정할 수 있습니다.

TemporalType.DATE: 날짜 정보만
TemporalType.TIME: 시간 정보만
TemporalType.TIMESTAMP: 날짜와 시간 정보</code></pre>
<h3 id="enumerated">@Enumerated</h3>
<p>@Enumerated 어노테이션은 enum 타입을 매핑할 때 사용됩니다. EnumType 속성을 통해 ORDINAL (숫자 값) 또는 STRING (문자열 값)로 매핑할 수 있습니다.</p>
<pre><code class="language-java">@Enumerated
@Enumerated 어노테이션은 enum 타입을 매핑할 때 사용됩니다. EnumType 속성을 통해 ORDINAL (숫자 값) 또는 STRING (문자열 값)로 매핑할 수 있습니다.</code></pre>
<h3 id="lob">@Lob</h3>
<p>@Lob 어노테이션은 큰 데이터 객체(BLOB, CLOB)를 매핑할 때 사용됩니다. BLOB (Binary Large Object)는 바이너리 데이터를, CLOB (Character Large Object)는 문자열 데이터를 저장합니다.</p>
<pre><code class="language-java">@Entity
public class Document {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Lob
    private byte[] content;

    // getters and setters
}</code></pre>
<h3 id="transient">@Transient</h3>
<p>@Transient 어노테이션은 특정 필드가 데이터베이스 컬럼에 매핑되지 않도록 합니다. 해당 필드는 JPA가 관리하지 않습니다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;

    @Transient
    private String temporaryData;

    // getters and setters
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 영속성 컨텍스트]]></title>
            <link>https://velog.io/@code_able/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@code_able/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sat, 01 Jun 2024 02:39:46 GMT</pubDate>
            <description><![CDATA[<h1 id="jpa-영속성-컨텍스트">JPA 영속성 컨텍스트</h1>
<p>JPA에서 영속성 컨텍스트(Persistence Context)는 엔티티를 관리하는 환경을 의미합니다. 영속성 컨텍스트는 엔티티 매니저(EntityManager)에 의해 관리되며, 엔티티 매니저는 엔티티의 생명 주기를 관리하고 데이터베이스 작업을 수행하는 데 사용됩니다. 영속성 컨텍스트는 다음과 같은 특징을 갖습니다:</p>
<h3 id="1차-캐시">1차 캐시:</h3>
<p>영속성 컨텍스트는 엔티티의 1차 캐시 역할을 합니다. 이를 통해 동일한 엔티티에 대한 중복 쿼리를 방지하고, 엔티티를 효율적으로 관리할 수 있습니다.
예를 들어, 동일한 트랜잭션 내에서 동일한 엔티티를 여러 번 조회하면, 데이터베이스를 다시 조회하지 않고 1차 캐시에서 엔티티를 반환합니다.</p>
<h3 id="엔티티-생명-주기">엔티티 생명 주기:</h3>
<p>엔티티는 영속성 컨텍스트 내에서 관리되며, 생명 주기에 따라 상태가 변합니다. JPA에서는 엔티티 상태를 크게 네 가지로 나눕니다:</p>
<ul>
<li><p>비영속(Transient): 엔티티가 새로 생성되었지만 아직 영속성 컨텍스트에 저장되지 않은 상태.</p>
</li>
<li><p>영속(Managed): 
엔티티가 영속성 컨텍스트에 저장되어 관리되고 있는 상태.</p>
</li>
<li><p>준영속(Detached): 
엔티티가 한때 영속성 컨텍스트에 저장되었지만 현재는 분리된 상태.</p>
</li>
<li><p>삭제(Removed): 
엔티티가 영속성 컨텍스트에서 삭제된 상태.</p>
</li>
</ul>
<h3 id="변경-감지dirty-checking">변경 감지(Dirty Checking):</h3>
<p>영속성 컨텍스트는 관리 중인 엔티티의 변경 사항을 추적합니다. 트랜잭션이 커밋될 때, 변경된 엔티티는 자동으로 데이터베이스에 반영됩니다.
변경 감지를 통해 개발자는 엔티티 상태를 명시적으로 업데이트할 필요 없이 편리하게 데이터 변경을 처리할 수 있습니다.</p>
<pre><code> Product product = new Product();
 product.setName(&quot;Laptop&quot;);
 product.setPrice(1200.00);
 em.persist(product);
 em.getTransaction().commit();</code></pre><h3 id="jpa-영속성-관련-주요-개념">JPA 영속성 관련 주요 개념</h3>
<ul>
<li><p>EntityManager는 JPA의 핵심 인터페이스로, 영속성 컨텍스트를 관리하고 엔티티의 생명 주기를 제어하는 데 사용됩니다. 주로 엔티티의 생성, 조회, 업데이트, 삭제 작업을 수행합니다.
Entity Transaction:</p>
</li>
<li><p>트랜잭션은 일련의 데이터베이스 작업을 하나의 단위로 묶어 관리합니다. JPA에서는 EntityManager를 통해 트랜잭션을 시작하고 커밋 또는 롤백할 수 있습니다.
Cascade:</p>
</li>
<li><p>JPA에서 연관된 엔티티 간의 작업을 전이(Cascade) 시킬 수 있습니다. 예를 들어, 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장되도록 설정할 수 있습니다.
Fetch Type:</p>
</li>
<li><p>엔티티의 연관 필드를 가져오는 방식으로, 즉시 로딩(EAGER)과 지연 로딩(LAZY)이 있습니다. 즉시 로딩은 연관된 엔티티를 즉시 가져오는 반면, 지연 로딩은 실제로 사용할 때 가져옵니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자주 사용되는 Lombok annotation]]></title>
            <link>https://velog.io/@code_able/%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-Lombok-annotation</link>
            <guid>https://velog.io/@code_able/%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-Lombok-annotation</guid>
            <pubDate>Tue, 14 May 2024 14:07:09 GMT</pubDate>
            <description><![CDATA[<h3 id="allargscontructor">@AllArgsContructor</h3>
<p>모든 필드를 인자로 받는 생성자를 자동으로 생성하는 어노테이션입니다.</p>
<p><strong>코드예시</strong></p>
<pre><code class="language-java">import lombok.AllArgsConstructor;

@AllArgsConstructor
public class MyClass {
    private String field1;
    private int field2;
}</code></pre>
<pre><code class="language-java">public class MyClass {
    private String field1;
    private int field2;

    public MyClass(String field1, int field2) {
        this.field1 = field1;
        this.field2 = field2;
    }
}
</code></pre>
<p><strong>주의사항</strong></p>
<ol>
<li><strong>상속 관계에서의 사용</strong>: 상속 관계에서 @AllArgsConstructor을 사용할 때는 조심해야 합니다. 부모 클래스에 있는 필드들도 자동으로 생성자에 포함되므로, 서브클래스에서 불필요한 생성자가 만들어질 수 있습니다. 이 경우에는 @AllArgsConstructor 대신 @NoArgsConstructor를 사용하고, 필요한 경우에는 @Setter나 @Builder와 같은 Lombok 어노테이션을 사용하여 필드를 초기화하는 것이 좋습니다.</li>
</ol>
<ol start="2">
<li><p><strong>불변 클래스에서의 사용</strong>: 불변(immutable) 클래스에서 @AllArgsConstructor을 사용할 때는 주의해야 합니다. 불변 클래스의 경우에는 생성자를 통해 한 번 값을 설정하면 변경할 수 없으므로, 외부에서 생성자를 통해 주입되는 값의 무결성을 보장해야 합니다. 따라서 불변 클래스에서는 생성자를 통해 받은 값이 유효한지 검증하는 로직을 추가하는 것이 좋습니다.</p>
</li>
<li><p><strong>생성자 오버로딩과 충돌</strong> : @AllArgsConstructor을 사용할 때 기존에 직접 정의한 생성자와 충돌할 수 있습니다. 이 경우에는 Lombok 어노테이션의 exclude 옵션을 사용하여 충돌하는 필드를 제외하거나, @NoArgsConstructor와 @AllArgsConstructor을 함께 사용하여 기본 생성자와 모든 필드를 인자로 받는 생성자를 함께 생성하는 것이 좋습니다.</p>
</li>
<li><p><strong>순서와 가독성</strong>: 생성자에 포함되는 필드들의 순서가 중요합니다. 생성자의 인자 순서와 필드의 순서가 일치해야 합니다. 이러한 순서의 일치를 유지하면 가독성을 높일 수 있습니다.</p>
</li>
</ol>
<h3 id="noargsconstructor">@NoArgsConstructor</h3>
<p>매개변수가 없는 기본 생성자를 자동으로 생성할 수 있습니다.</p>
<p><strong>코드예시</strong></p>
<pre><code class="language-java">import lombok.NoArgsConstructor;

@NoArgsConstructor
public class MyClass {
    private String field1;
    private int field2;
}</code></pre>
<pre><code class="language-java">public MyClass() {

}</code></pre>
<p><strong>주의사항</strong></p>
<ol>
<li><p><strong>JPA 엔티티 클래스에서의 사용</strong>: JPA 엔티티 클래스에서는 매개변수가 없는 기본 생성자가 필수적입니다. 그렇지 않으면 JPA가 올바르게 엔티티를 생성하지 못할 수 있습니다. 따라서 JPA 엔티티 클래스에서는 @NoArgsConstructor를 사용하여 기본 생성자를 명시적으로 추가하는 것이 좋습니다.</p>
</li>
<li><p><strong>다른 생성자와의 충돌</strong>: @NoArgsConstructor를 사용할 때 기존에 직접 정의한 생성자와 충돌할 수 있습니다. 이 경우에는 exclude 옵션을 사용하여 제외하거나, @AllArgsConstructor과 함께 사용하여 모든 필드를 인자로 받는 생성자를 함께 생성할 수 있습니다.</p>
</li>
<li><p><strong>불변 클래스에서의 사용</strong>: 불변(immutable) 클래스에서는 기본 생성자를 사용하여 객체를 생성할 경우 객체의 상태가 변경되지 않기 때문에 유효한 상태인지를 검증할 수 없습니다. 따라서 불변 클래스에서는 생성자를 통해 초기화하는 것이 좋습니다.</p>
<pre><code class="language-java">import lombok.NoArgsConstructor;
</code></pre>
</li>
</ol>
<p>@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ImmutableClass {
    private final String field1;
    private final int field2;</p>
<pre><code>public ImmutableClass(String field1, int field2) {
    this.field1 = field1;
    this.field2 = field2;
}

// Getter 메서드 생략</code></pre><p>}</p>
<pre><code>
4. **직렬화와의 호환성**: 직렬화(serialization)에 사용되는 클래스에서는 기본 생성자를 반드시 제공해야 합니다. 그렇지 않으면 직렬화 과정에서 오류가 발생할 수 있습니다. 따라서 직렬화를 고려하는 클래스에서는 @NoArgsConstructor를 사용하여 기본 생성자를 명시적으로 추가하는 것이 좋습니다.

### @Getter
클래스의 필드에 대한 getter 메서드가 자동으로 생성됩니다. 이를 통해 외부에서 필드에 접근할 때에는 getter 메서드를 통하여 안전하게 접근할 수 있습니다.

**코드예시**
~~~java
import lombok.Getter;

@Getter
public class MyClass {
    private String field1;
    private int field2;
}</code></pre><pre><code class="language-java">public String getField1() {
    return field1;
}

public int getField2() {
    return field2;
}</code></pre>
<p><strong>주의사항</strong></p>
<ol>
<li><p><strong>불변 클래스에서의 사용</strong>: 불변(immutable) 클래스에서는 필드에 대한 getter 메서드를 제공하는 것이 일반적입니다. 그러나 setter 메서드를 제공하지 않고, 필드를 초기화한 후에는 변경되지 않도록 만들어야 합니다. 따라서 불변 클래스에서는 @Getter를 사용하여 필드에 대한 getter 메서드를 생성하는 것이 바람직합니다.</p>
</li>
<li><p><strong>보안 문제</strong>: @Getter를 사용하여 모든 필드에 대한 getter 메서드를 생성하면 모든 필드에 대한 읽기 권한이 생깁니다. 따라서 보안에 민감한 정보를 포함하는 클래스에서는 신중하게 사용해야 합니다.</p>
</li>
<li><p><strong>불필요한 Getter 생성</strong> : 모든 필드에 대해 getter 메서드를 생성하면 필요하지 않은 경우에도 getter 메서드가 생성됩니다. 이는 클래스의 인터페이스를 불필요하게 노출시킬 수 있으므로 주의해야 합니다.</p>
</li>
</ol>
<h3 id="setter">@Setter</h3>
<p>클래스의 필드에 대한 setter 메서드가 자동으로 생성됩니다. 이를 통해 외부에서 필드에 값을 설정할 때에는 setter 메서드를 통하여 안전하게 값을 설정할 수 있습니다.</p>
<p><strong>코드예시</strong></p>
<pre><code class="language-java">import lombok.Setter;

@Setter
public class MyClass {
    private String field1;
    private int field2;
}</code></pre>
<pre><code class="language-java">public void setField1(String field1) {
    this.field1 = field1;
}

public void setField2(int field2) {
    this.field2 = field2;
}</code></pre>
<p><strong>주의사항</strong></p>
<ol>
<li><p><strong>불변 클래스에서의 사용</strong>: 불변(immutable) 클래스에서는 필드의 값을 변경할 수 없도록 만들어야 합니다. 따라서 불변 클래스에서는 @Setter를 사용하여 필드에 대한 setter 메서드를 생성하는 것은 지양해야 합니다.</p>
</li>
<li><p><strong>보안 문제</strong>: @Setter를 사용하여 모든 필드에 대한 setter 메서드를 생성하면 모든 필드에 대한 쓰기 권한이 생깁니다. 따라서 보안에 민감한 정보를 포함하는 클래스에서는 신중하게 사용해야 합니다.</p>
</li>
<li><p><strong>불필요한 쓰기 권한 제공</strong>: 필드에 대한 setter 메서드를 생성하면 필요하지 않은 경우에도 쓰기 권한이 제공됩니다. 이는 클래스의 상태를 불필요하게 변경할 수 있으므로 주의해야 합니다.</p>
</li>
</ol>
<h3 id="tostring">@ToString</h3>
<p>래스의 toString() 메서드를 자동으로 생성해줍니다. 이를 통해 객체의 내부 상태를 문자열로 표현할 수 있습니다.</p>
<p><strong>코드예시</strong></p>
<pre><code class="language-java">import lombok.ToString;

@ToString
public class MyClass {
    private String field1;
    private int field2;
}</code></pre>
<pre><code class="language-java">@Override
public String toString() {
    return &quot;MyClass(field1=&quot; + field1 + &quot;, field2=&quot; + field2 + &quot;)&quot;;
}</code></pre>
<p><strong>주의사항</strong></p>
<ol>
<li><p><strong>무분별한 사용</strong>: 모든 필드를 포함한 문자열을 생성하기 때문에 클래스의 필드 수가 많을 경우에는 생성된 문자열이 너무 길어질 수 있습니다. 필요한 경우에만 사용하는 것이 좋습니다.</p>
</li>
<li><p><strong>필드 순서</strong>: @ToString은 기본적으로 클래스에 선언된 순서대로 필드를 문자열에 포함시킵니다. 필드의 순서를 변경하려면 @ToString 어노테이션에 includeFieldNames 옵션을 사용하여 필드의 순서를 변경할 수 있습니다.</p>
</li>
<li><p><strong>순환 참조 문제</strong>: 순환 참조가 있는 경우 무한 루프에 빠질 수 있으므로 주의해야 합니다. 이러한 문제가 발생하는 경우 @ToString의 exclude 또는 of 옵션을 사용하여 순환 참조되는 필드를 제외시킬 수 있습니다.</p>
</li>
</ol>
<h3 id="data">@Data</h3>
<p>클래스에 대한 모든 기본적인 메서드들을 자동으로 생성해줍니다. 이는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @NoArgsConstructor 등 여러 어노테이션들을 포함하고 있습니다. 이를 통해 코드의 가독성을 높이고, 중복된 코드를 줄일 수 있습니다.
다른 블로그를 보면 대부분 @Data 사용은 지양하는 것 같다</p>
<p><strong>주의사항</strong></p>
<ol>
<li><p><strong>무분별한 사용</strong>: @Data는 모든 필드에 대해 getter와 setter를 생성하기 때문에 보안에 민감한 정보를 포함하는 클래스에서는 신중하게 사용해야 합니다.</p>
</li>
<li><p><strong>불필요한 메서드 생성</strong>: 모든 필드에 대해 getter와 setter를 생성하기 때문에 필요하지 않은 경우에도 생성됩니다. 따라서 클래스의 용도에 따라 적절히 선택하여 사용해야 합니다.</p>
</li>
<li><p><strong>상속 문제</strong>: @Data를 사용하면 클래스에 대한 모든 기본적인 메서드들이 자동으로 생성되기 때문에 상속을 받을 때에도 부모 클래스의 메서드들이 함께 상속됩니다. 이로 인해 의도하지 않은 동작이 발생할 수 있으므로 주의해야 합니다.</p>
</li>
</ol>
<h3 id="reference">reference</h3>
<ul>
<li><a href="https://roopredev.tistory.com/14">https://roopredev.tistory.com/14</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>