<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ZZAME'S</title>
        <link>https://velog.io/</link>
        <description>I DEVELOP THEREFORE, I AM 😄</description>
        <lastBuildDate>Thu, 02 Apr 2026 16:59:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ZZAME'S</title>
            <url>https://velog.velcdn.com/images/dev_zzame/profile/ffd31180-394a-4a32-a8c9-19e3a2a53b0b/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ZZAME'S. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_zzame" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AI Agent와 함께 Kotlin 웹 애플리케이션을 A to Z로 개발하며 공부하는 방법]]></title>
            <link>https://velog.io/@dev_zzame/AI-Agent%EC%99%80-%ED%95%A8%EA%BB%98-Kotlin-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-A-to-Z%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%A9%B0-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev_zzame/AI-Agent%EC%99%80-%ED%95%A8%EA%BB%98-Kotlin-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-A-to-Z%EB%A1%9C-%EA%B0%9C%EB%B0%9C%ED%95%98%EB%A9%B0-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 02 Apr 2026 16:59:29 GMT</pubDate>
            <description><![CDATA[<h1 id="ai-agent와-함께-kotlin-웹-애플리케이션을-a-to-z로-개발하며-공부하는-방법">AI Agent와 함께 Kotlin 웹 애플리케이션을 A to Z로 개발하며 공부하는 방법</h1>
<p>요즘 Kotlin을 공부하면서 가장 크게 느끼는 건, 더 이상 문법만 따로 공부하는 시대는 아니라는 점이다.</p>
<p>예전에는 책, 강의, 예제 코드를 보며 문법을 익히고 나중에 프로젝트를 붙였다면, 지금은 아예 반대로 접근하고 있다.</p>
<p><strong>AI Agent를 활용해 웹 애플리케이션을 처음부터 끝까지 직접 만들어가면서 언어와 프레임워크를 학습하는 방식</strong>이다.</p>
<p>현재 나는 Java + Spring Boot 4년차 백엔드 개발자로서 Kotlin + Spring Boot 기반 이커머스 백엔드로 전환하기 위해, 실제 프로젝트를 만들며 공부하고 있다.</p>
<p>이 과정에서 가장 큰 변화는 AI를 단순 코드 생성기가 아니라, <strong>개발 전 과정을 함께 운영하는 Agent</strong>로 활용하고 있다는 점이다.</p>
<hr>
<h2 id="공부가-아니라-실전-개발로-시작했다">공부가 아니라 실전 개발로 시작했다</h2>
<p>이번 학습은 문법 책부터 시작하지 않았다.</p>
<p>처음부터 아래 목표를 잡았다.</p>
<ul>
<li>Kotlin + Spring Boot</li>
<li>JPA + QueryDSL</li>
<li>MariaDB</li>
<li>Docker</li>
<li>이커머스 주문/정산 도메인</li>
<li>최종 배포 가능한 웹 애플리케이션</li>
</ul>
<p>즉 단순 샘플이 아니라,
<strong>실제로 운영 가능한 수준의 백엔드 웹 애플리케이션을 A to Z로 만드는 것 자체를 학습 방식으로 삼았다.</strong> fileciteturn0file1L88-L113</p>
<p>문법은 프로젝트 안에서 자연스럽게 흡수된다.</p>
<p>예를 들어 <code>data class</code>, <code>null safety</code>, <code>Kotlin DSL</code>, <code>kapt</code>, QueryDSL 설정 같은 개념도 모두 실제 기능 구현 단계에서 익히고 있다.</p>
<hr>
<h2 id="ai-agent를-프로젝트-운영-시스템으로-사용했다">AI Agent를 프로젝트 운영 시스템으로 사용했다</h2>
<p>핵심은 Codex를 단순히 코드를 짜주는 도구로 쓰지 않은 것이다.</p>
<p>이번에는 AI Agent를 아래처럼 역할 기반으로 분리했다.</p>
<h3 id="1-curriculummd--제품-로드맵">1. curriculum.md = 제품 로드맵</h3>
<p>무엇을 어떤 순서로 만들지 정의한다.</p>
<ul>
<li>프로젝트 생성</li>
<li>Kotlin 문법 적응</li>
<li>Spring Boot 구조화</li>
<li>JPA + QueryDSL</li>
<li>이커머스 핵심 도메인</li>
<li>트랜잭션 / 동시성</li>
<li>운영 고도화</li>
</ul>
<p>즉 PM의 PRD나 기술 로드맵 문서 역할이다. fileciteturn0file1L41-L86</p>
<h3 id="2-progressmd--실행-보드">2. progress.md = 실행 보드</h3>
<p>현재 어디까지 완료했는지 체크한다.</p>
<p>현재 진행 상황은 다음과 같다.</p>
<ul>
<li>프로젝트 생성 완료</li>
<li>Kotlin DSL 완료</li>
<li>QueryDSL QClass 생성 완료</li>
<li>DTO → data class 전환 완료</li>
<li>현재 진행: null-safe 리팩토링 fileciteturn0file0L1-L18</li>
</ul>
<p>이 문서는 Jira 티켓 보드처럼 동작한다.</p>
<h3 id="3-codex--시니어-페어-프로그래머--리뷰어">3. Codex = 시니어 페어 프로그래머 + 리뷰어</h3>
<p>Codex는 아래 규칙으로만 움직인다.</p>
<ul>
<li>항상 가장 앞의 미완료 step 1개만 수행</li>
<li>다음 기능 미리 구현 금지</li>
<li>구현 후 코드 리뷰</li>
<li>Kotlin 관점 피드백</li>
<li>Spring 구조 리뷰</li>
<li>다음 미완료 step 제안</li>
</ul>
<p>이 구조 덕분에 AI가 학습 흐름을 망치지 않고, 단계적으로 성장시키는 파트너가 된다.</p>
<hr>
<h2 id="a-to-z-개발-방식의-가장-큰-장점">A to Z 개발 방식의 가장 큰 장점</h2>
<p>이 방식의 가장 큰 장점은 <strong>학습과 실무가 분리되지 않는다는 점</strong>이다.</p>
<p>지금 진행 중인 <code>STEP 01-02</code>는 null-safe 처리 리팩토링이다. fileciteturn0file0L13-L18</p>
<p>겉으로 보면 단순 Kotlin 문법 학습처럼 보일 수 있다.
하지만 실제로는 이후 단계와 자연스럽게 연결된다.</p>
<ul>
<li>공통 응답 객체</li>
<li>상품 조회 API</li>
<li>Product Entity</li>
<li>QueryDSL 검색</li>
<li>주문 생성</li>
<li>정산 계산</li>
</ul>
<p>즉 지금 배우는 null safety가 나중에</p>
<ul>
<li>상품 조회 API의 nullable response 설계</li>
<li>주문 상태 nullable 처리</li>
<li>정산 금액 계산 시 null-safe 로직</li>
</ul>
<p>으로 그대로 이어진다.</p>
<p>언어 문법이 실제 비즈니스 문제 해결과 붙는 순간 학습 속도가 훨씬 빨라진다.</p>
<hr>
<h2 id="ai-agent-시대에는-개발-시스템-설계가-중요하다">AI Agent 시대에는 개발 시스템 설계가 중요하다</h2>
<p>이번 경험을 통해 느낀 건, 이제 중요한 건 문법 암기가 아니라는 점이다.</p>
<p>오히려 더 중요한 건 아래다.</p>
<ul>
<li>어떤 제품을 만들 것인가</li>
<li>어떤 step으로 분리할 것인가</li>
<li>AI에게 어떤 제약을 줄 것인가</li>
<li>리뷰 기준을 어떻게 세울 것인가</li>
<li>학습을 어떻게 프로젝트화할 것인가</li>
</ul>
<p>결국 AI Agent 시대의 개발 학습은,
<strong>코드를 잘 짜는 능력보다 개발 시스템을 설계하는 능력이 더 중요해지고 있다.</strong></p>
<p>특히 아래 구조가 핵심이었다.</p>
<pre><code class="language-text">로드맵 설계 → 진행표 관리 → AI 구현 → 리뷰 → 완료 체크 → 다음 step</code></pre>
<p>이 루프가 돌아가면 AI는 단순 코드 생성기가 아니라,
<strong>제품을 함께 만들어가는 개발 파트너</strong>가 된다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>현재 나는 AI Agent와 함께 Kotlin 기반 이커머스 웹 애플리케이션을 처음부터 끝까지 만들며 공부하고 있다.</p>
<p>단순히 Kotlin 문법을 익히는 것이 아니라,</p>
<ul>
<li>프로젝트 생성</li>
<li>도메인 설계</li>
<li>API 구현</li>
<li>QueryDSL 최적화</li>
<li>정산 로직</li>
<li>동시성</li>
<li>Redis 캐시</li>
<li>Docker 배포</li>
</ul>
<p>까지 모두 하나의 학습 프로젝트 안에서 경험하는 방식이다. fileciteturn0file1L214-L267</p>
<p>개인적으로 이 방식은 앞으로 새로운 언어를 익힐 때 가장 강력한 방법이라고 느낀다.</p>
<p><strong>AI Agent를 활용하면 공부와 실무 프로젝트를 분리하지 않고, 제품을 만들면서 곧바로 성장할 수 있다.</strong></p>
<p>특히 백엔드 개발자에게는 단순 문법 학습보다,
<strong>실제 운영 가능한 웹 애플리케이션을 A to Z로 만드는 경험 자체가 가장 빠른 성장 루프</strong>가 된다.</p>
<hr>
<h2 id="다음-글-예고">다음 글 예고</h2>
<p>다음 글에서는 이번 방식의 핵심이었던
<strong><code>progress.md</code>를 기반으로 AI Agent에게 step 단위로 안정적으로 개발시키는 방법</strong>을 더 구체적으로 다뤄볼 예정이다.</p>
<ul>
<li>step 분리 기준</li>
<li>완료 체크 규칙</li>
<li>AI가 범위를 넘지 않게 하는 프롬프트 설계</li>
<li>코드 리뷰 자동화 포맷</li>
<li>실무 프로젝트에 바로 적용하는 방법</li>
</ul>
<p>AI Agent를 제대로 활용하려면, 결국 프롬프트보다 <strong>운영 시스템 설계가 더 중요하다</strong>는 이야기를 이어서 정리해보려 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java 개발자가 Kotlin data class를 만나면 가장 먼저 체감하는 변화]]></title>
            <link>https://velog.io/@dev_zzame/Java-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-Kotlin-data-class%EB%A5%BC-%EB%A7%8C%EB%82%98%EB%A9%B4-%EA%B0%80%EC%9E%A5-%EB%A8%BC%EC%A0%80-%EC%B2%B4%EA%B0%90%ED%95%98%EB%8A%94-%EB%B3%80%ED%99%94-j86z2nzz</link>
            <guid>https://velog.io/@dev_zzame/Java-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-Kotlin-data-class%EB%A5%BC-%EB%A7%8C%EB%82%98%EB%A9%B4-%EA%B0%80%EC%9E%A5-%EB%A8%BC%EC%A0%80-%EC%B2%B4%EA%B0%90%ED%95%98%EB%8A%94-%EB%B3%80%ED%99%94-j86z2nzz</guid>
            <pubDate>Thu, 02 Apr 2026 16:48:29 GMT</pubDate>
            <description><![CDATA[<p>Java + Spring Boot 개발자로 실무를 하다 보면 DTO, 응답 객체, 이벤트 메시지, 캐시 객체처럼 <strong>데이터를 담기 위한 클래스</strong>를 정말 많이 만들게 된다.</p>
<p>문제는 이런 클래스들이 비즈니스 로직보다 <strong>반복 코드가 훨씬 많다</strong>는 점이다.</p>
<p>필드 선언, 생성자, getter, equals, hashCode, toString까지 작성하다 보면 정작 중요한 도메인 설계보다 보일러플레이트 코드가 더 눈에 들어온다.</p>
<p>Kotlin을 처음 배우면서 가장 먼저 생산성 차이를 체감하게 되는 문법 중 하나가 바로 <code>data class</code>였다.</p>
<p>이번 글에서는 Java 개발자 관점에서 <code>data class</code>가 무엇인지, Java DTO와 어떻게 다른지, 어떤 점이 편리한지, 그리고 실무에서 반드시 주의해야 할 포인트까지 정리해본다.</p>
<hr>
<h2 id="data-class란">data class란?</h2>
<p><code>data class</code>는 말 그대로 <strong>데이터 보관 목적의 전용 클래스</strong>다.</p>
<p>특히 아래와 같은 객체에 가장 적합하다.</p>
<ul>
<li>Request DTO</li>
<li>Response DTO</li>
<li>Query 결과 DTO</li>
<li>이벤트 메시지</li>
<li>Redis 캐시 객체</li>
<li>외부 API 응답 매핑 객체</li>
</ul>
<p>Kotlin은 데이터 보관에 필요한 핵심 메서드를 자동 생성해준다.</p>
<ul>
<li>getter</li>
<li><code>equals()</code></li>
<li><code>hashCode()</code></li>
<li><code>toString()</code></li>
<li><code>copy()</code></li>
<li><code>componentN()</code></li>
</ul>
<p>즉 Java에서 반복적으로 작성하던 DTO 코드 대부분이 사라진다.</p>
<hr>
<h2 id="java-dto와-비교">Java DTO와 비교</h2>
<p>Java에서 가장 흔한 DTO는 이런 형태다.</p>
<pre><code class="language-java">public class ProductResponse {
    private Long id;
    private String name;
    private int price;

    public ProductResponse(Long id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return &quot;ProductResponse{&quot; +
                &quot;id=&quot; + id +
                &quot;, name=&#39;&quot; + name + &#39;\&#39;&#39; +
                &quot;, price=&quot; + price +
                &#39;}&#39;;
    }
}</code></pre>
<p>Lombok을 쓰더라도 보통 이렇게는 남는다.</p>
<pre><code class="language-java">@Getter
@AllArgsConstructor
@ToString
public class ProductResponse {
    private Long id;
    private String name;
    private int price;
}</code></pre>
<p>Kotlin에서는 이렇게 끝난다.</p>
<pre><code class="language-kotlin">data class ProductResponse(
    val id: Long,
    val name: String,
    val price: Int
)</code></pre>
<p>Java 개발자 입장에서 가장 충격적인 포인트는 <strong>클래스의 목적이 코드에 그대로 드러난다</strong>는 점이다.</p>
<p>“이 객체는 데이터를 담기 위한 용도다”가 문법 자체에 녹아 있다.</p>
<hr>
<h2 id="어떤-점이-편리한가">어떤 점이 편리한가?</h2>
<h2 id="1-dto-코드량이-압도적으로-줄어든다">1) DTO 코드량이 압도적으로 줄어든다</h2>
<p>Spring Boot에서 API 하나 만들 때 DTO가 최소 2~4개는 나온다.</p>
<ul>
<li>request</li>
<li>response</li>
<li>service result</li>
<li>query projection</li>
</ul>
<p>Java에서는 클래스 수가 많아질수록 getter, constructor, equals 작성 비용이 커진다.</p>
<p>Kotlin은 선언만 남는다.</p>
<pre><code class="language-kotlin">data class CreateOrderRequest(
    val productId: Long,
    val quantity: Int,
    val couponId: Long?
)</code></pre>
<p>실무에서 DTO가 많은 이커머스, 정산, 검색 시스템일수록 체감이 크다.</p>
<hr>
<h2 id="2-tostring-자동-생성으로-로그-확인이-편하다">2) toString 자동 생성으로 로그 확인이 편하다</h2>
<p>운영 로그나 디버깅에서 DTO 내용을 바로 확인하기 좋다.</p>
<pre><code class="language-kotlin">val dto = ProductResponse(1L, &quot;맥북&quot;, 2000000)
println(dto)</code></pre>
<p>출력 결과</p>
<pre><code class="language-text">ProductResponse(id=1, name=맥북, price=2000000)</code></pre>
<p>Java에서 <code>toString()</code> 직접 만들던 습관이 거의 사라진다.</p>
<hr>
<h2 id="3-copy가-실무에서-정말-강력하다">3) copy()가 실무에서 정말 강력하다</h2>
<p>개인적으로 Java 개발자가 가장 크게 체감하는 생산성 차이는 <code>copy()</code>다.</p>
<p>기존 객체 일부만 변경한 새 객체를 쉽게 만들 수 있다.</p>
<pre><code class="language-kotlin">val original = ProductResponse(1L, &quot;맥북&quot;, 2000000)
val discounted = original.copy(price = 1800000)</code></pre>
<p>이커머스에서 자주 등장하는 아래 시나리오에 매우 잘 맞는다.</p>
<ul>
<li>할인 가격 계산</li>
<li>주문 상태 변경 응답</li>
<li>캐시 데이터 일부 갱신</li>
<li>이벤트 payload 수정</li>
</ul>
<p>Java에서는 builder나 새 생성자를 다시 호출해야 한다.</p>
<hr>
<h2 id="4-구조-분해-선언이-가능하다">4) 구조 분해 선언이 가능하다</h2>
<pre><code class="language-kotlin">val product = ProductResponse(1L, &quot;맥북&quot;, 2000000)
val (id, name, price) = product</code></pre>
<p>복합 반환값을 다룰 때 생각보다 유용하다.</p>
<p>특히 작은 유틸성 로직에서 생산성이 좋다.</p>
<hr>
<h2 id="실무에서-가장-좋은-사용처">실무에서 가장 좋은 사용처</h2>
<p>너처럼 Spring Boot 백엔드 기준으로 가장 좋은 위치는 아래다.</p>
<h3 id="request--response-dto">Request / Response DTO</h3>
<pre><code class="language-kotlin">data class ProductCreateRequest(
    val name: String,
    val price: Int
)</code></pre>
<h3 id="querydsl-projection-dto">QueryDSL Projection DTO</h3>
<pre><code class="language-kotlin">data class ProductSearchResult(
    val productId: Long,
    val productName: String,
    val sellerName: String,
    val stock: Int
)</code></pre>
<h3 id="redis-캐시-객체">Redis 캐시 객체</h3>
<pre><code class="language-kotlin">data class ProductCache(
    val id: Long,
    val price: Int,
    val stock: Int
)</code></pre>
<hr>
<h2 id="반드시-주의할-점">반드시 주의할 점</h2>
<h2 id="1-jpa-entity에는-기본적으로-비추천">1) JPA Entity에는 기본적으로 비추천</h2>
<p>이건 Java 개발자가 Kotlin으로 넘어오면서 가장 많이 실수하는 부분이다.</p>
<p><code>data class</code>를 Entity에 그대로 쓰면 편해 보이지만 실제로는 위험하다.</p>
<pre><code class="language-kotlin">@Entity
data class Product(
    @Id
    val id: Long,
    val name: String
)</code></pre>
<p>왜 위험하냐면 아래 문제가 생긴다.</p>
<ul>
<li><code>equals()</code> / <code>hashCode()</code> 자동 생성</li>
<li>JPA 프록시 객체와 비교 충돌</li>
<li>지연 로딩 필드 비교 시 예상치 못한 쿼리</li>
<li>양방향 연관관계 순환 참조</li>
<li>엔티티 identity 보장 이슈</li>
</ul>
<p>특히 Hibernate Proxy와 충돌하는 순간 디버깅이 어려워진다.</p>
<h3 id="결론">결론</h3>
<p><strong>Entity는 일반 class 사용이 안전하다.</strong></p>
<hr>
<h2 id="2-너무-큰-객체에-copy-남용-주의">2) 너무 큰 객체에 copy() 남용 주의</h2>
<p><code>copy()</code>는 새 객체를 만든다.</p>
<p>필드가 많고 중첩 객체가 깊은 DTO에서 과도하게 사용하면 메모리 사용량과 가독성이 나빠질 수 있다.</p>
<p>특히 대량 배치 처리 DTO에서는 주의가 필요하다.</p>
<hr>
<h2 id="3-mutable-propertyvar-남용-주의">3) mutable property(var) 남용 주의</h2>
<pre><code class="language-kotlin">data class ProductResponse(
    var price: Int
)</code></pre>
<p>가능하면 <code>val</code> 중심으로 immutable하게 유지하는 것이 좋다.</p>
<p>실무에서는 불변 객체가 버그를 줄인다.</p>
<hr>
<h2 id="java-개발자에게-추천하는-학습-순서">Java 개발자에게 추천하는 학습 순서</h2>
<p>가장 빠르게 익숙해지는 방법은 기존 Java DTO를 하나씩 바꾸는 것이다.</p>
<p>추천 순서:</p>
<ol>
<li>Response DTO</li>
<li>Request DTO</li>
<li>QueryDSL Projection DTO</li>
<li>Redis 캐시 DTO</li>
<li>이벤트 메시지</li>
</ol>
<p>반대로 Entity는 가장 마지막에 신중하게 접근해야 한다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>Kotlin의 <code>data class</code>는 단순히 코드를 줄이는 문법이 아니다.</p>
<p>Java + Spring Boot 개발자에게는 DTO 설계 방식 자체를 더 <strong>의도 중심으로 바꾸는 도구</strong>에 가깝다.</p>
<ul>
<li>코드량 감소</li>
<li>로그 가독성 향상</li>
<li>copy 기반 안전한 객체 변경</li>
<li>DTO 유지보수성 향상</li>
</ul>
<p>특히 이커머스처럼 DTO가 많은 도메인에서는 생산성 차이가 매우 크다.</p>
<p>다만 JPA Entity에 그대로 적용하는 것은 위험할 수 있으므로,
<strong>DTO 전용으로 먼저 익히고 Entity는 일반 class로 유지하는 습관</strong>이 가장 안전하다.</p>
<p>처음 Kotlin을 배우는 Java 개발자라면,
현재 운영 중인 Spring Boot 프로젝트의 Response DTO부터 <code>data class</code>로 바꿔보는 것만으로도 Kotlin의 장점을 빠르게 체감할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java + Spring Boot 개발자가 Kotlin DSL을 꼭 알아야 하는 이유]]></title>
            <link>https://velog.io/@dev_zzame/Java-Spring-Boot-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-Kotlin-DSL%EC%9D%84-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dev_zzame/Java-Spring-Boot-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-Kotlin-DSL%EC%9D%84-%EA%BC%AD-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 02 Apr 2026 15:47:04 GMT</pubDate>
            <description><![CDATA[<p>Java + Spring Boot로 실무를 오래 하다 보면 <code>build.gradle</code>은 익숙하지만, Kotlin 프로젝트를 처음 시작할 때 <code>build.gradle.kts</code>를 보고 잠깐 멈추게 된다.</p>
<p>나 역시 Java + Spring Boot 기반으로 API, 정산, 이커머스 백엔드 서비스를 개발해오다가 Kotlin + Spring Boot 전환을 준비하면서 가장 먼저 마주친 변화가 바로 <strong>Kotlin DSL(Kotlin Domain Specific Language)</strong> 이었다.</p>
<p>처음에는 단순히 “Groovy 대신 Kotlin 문법으로 바뀐 설정 파일” 정도로 생각했지만, 실제로 사용해보니 Kotlin 프로젝트에서는 단순한 문법 변화 이상의 의미가 있었다. 특히 IntelliJ 기반 생산성과 코드 안정성 측면에서 체감 차이가 크다.</p>
<p>이번 글에서는 Java + Spring Boot 개발자 관점에서 Kotlin DSL이 무엇인지, 왜 사용하는지, 그리고 실무에서 어떤 장점이 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="kotlin-dsl이란">Kotlin DSL이란?</h2>
<p>Kotlin DSL은 Gradle 설정 파일을 <strong>Groovy가 아닌 Kotlin 문법으로 작성하는 방식</strong>이다.</p>
<p>기존 Java 프로젝트에서는 아래처럼 많이 사용했다.</p>
<pre><code class="language-groovy">// build.gradle
plugins {
    id &#39;org.springframework.boot&#39; version &#39;3.3.0&#39;
}</code></pre>
<p>Kotlin 프로젝트에서는 아래처럼 바뀐다.</p>
<pre><code class="language-kotlin">// build.gradle.kts
plugins {
    id(&quot;org.springframework.boot&quot;) version &quot;3.3.0&quot;
}</code></pre>
<p>핵심 차이는 파일 확장자다.</p>
<ul>
<li>Groovy DSL: <code>build.gradle</code></li>
<li>Kotlin DSL: <code>build.gradle.kts</code></li>
</ul>
<p>여기서 <code>kts</code>는 Kotlin Script를 의미한다.
즉, <strong>빌드 설정 자체를 Kotlin 코드처럼 다루는 방식</strong>이다.</p>
<hr>
<h2 id="왜-java-개발자가-kotlin-dsl을-알아야-할까">왜 Java 개발자가 Kotlin DSL을 알아야 할까?</h2>
<p>Kotlin + Spring Boot로 전환하려는 Java 개발자라면 사실상 Kotlin DSL은 필수에 가깝다.</p>
<p>애플리케이션 코드는 Kotlin인데 Gradle 설정만 Groovy로 유지하면 문맥 전환 비용이 생긴다.</p>
<p>예를 들어 서비스 코드는 Kotlin으로 이렇게 작성한다.</p>
<pre><code class="language-kotlin">@Service
class ProductService(
    private val productRepository: ProductRepository
)</code></pre>
<p>그런데 빌드 설정은 다시 Groovy로 돌아가면 다음과 같은 불편함이 생긴다.</p>
<ul>
<li>문법이 다름</li>
<li>IDE 자동완성 차이</li>
<li>타입 안정성 부족</li>
<li>설정 실수 발견이 늦음</li>
</ul>
<p>Kotlin DSL을 사용하면 프로젝트 전반이 Kotlin으로 통일되기 때문에 학습과 유지보수 흐름이 훨씬 자연스럽다.</p>
<hr>
<h2 id="실무에서-가장-크게-느끼는-장점-4가지">실무에서 가장 크게 느끼는 장점 4가지</h2>
<h2 id="1-intellij-자동완성이-강력하다">1) IntelliJ 자동완성이 강력하다</h2>
<p>Java + IntelliJ에 익숙한 개발자라면 이 장점이 가장 크게 체감된다.</p>
<p>Groovy는 동적 타입 기반이라 자동완성이 제한적일 때가 많다.
반면 Kotlin DSL은 타입 기반이라 IDE가 설정 옵션을 더 정확하게 제안한다.</p>
<pre><code class="language-kotlin">dependencies {
    implementation(&quot;org.springframework.boot:spring-boot-starter-web&quot;)
}</code></pre>
<p>오타나 잘못된 블록 위치도 IDE에서 빠르게 확인 가능하다.</p>
<hr>
<h2 id="2-타입-안정성이-좋다">2) 타입 안정성이 좋다</h2>
<p>Groovy DSL은 런타임에야 문제를 발견하는 경우가 종종 있다.</p>
<p>반면 Kotlin DSL은 설정 자체가 Kotlin 코드라서 컴파일 단계에서 오류를 더 빨리 잡아준다.</p>
<p>예를 들어 QueryDSL, kapt, sourceSets 같은 설정을 추가할 때 안정감 차이가 크다.</p>
<p>특히 실무에서 멀티모듈, code generation, annotation processor를 다룰 때 유지보수성이 좋아진다.</p>
<hr>
<h2 id="3-kotlin-학습에-자연스럽게-도움이-된다">3) Kotlin 학습에 자연스럽게 도움이 된다</h2>
<p>Kotlin을 배우는 입장에서는 Kotlin DSL이 단순 빌드 설정을 넘어 <strong>Kotlin 문법 연습장 역할</strong>도 한다.</p>
<p>예를 들어 함수 호출, 람다, named style 구조에 자연스럽게 익숙해진다.</p>
<pre><code class="language-kotlin">tasks.withType&lt;Test&gt; {
    useJUnitPlatform()
}</code></pre>
<p>이런 DSL 스타일은 이후 Spring Kotlin 코드 작성 방식과도 연결된다.</p>
<hr>
<h2 id="4-querydsl-jpa-설정이-더-깔끔하다">4) QueryDSL, JPA 설정이 더 깔끔하다</h2>
<p>백엔드 실무에서 중요한 부분은 결국 JPA + QueryDSL 설정이다.</p>
<p>특히 이커머스처럼 조회 조건이 복잡한 도메인에서는 QueryDSL 설정이 빈번하다.</p>
<p>Kotlin DSL에서는 아래처럼 명확하게 관리할 수 있다.</p>
<pre><code class="language-kotlin">plugins {
    kotlin(&quot;kapt&quot;)
}

dependencies {
    implementation(&quot;com.querydsl:querydsl-jpa:5.0.0:jakarta&quot;)
    kapt(&quot;com.querydsl:querydsl-apt:5.0.0:jakarta&quot;)
}</code></pre>
<p>Java 개발자 입장에서 이 부분은 매우 중요하다.
왜냐하면 Kotlin 전환 시 가장 먼저 부딪히는 실무 이슈가 <strong>QClass 생성과 kapt 설정</strong>이기 때문이다.</p>
<hr>
<h2 id="java-개발자가-처음-헷갈리는-포인트">Java 개발자가 처음 헷갈리는 포인트</h2>
<h3 id="plugins-블록">plugins 블록</h3>
<pre><code class="language-kotlin">plugins {
    kotlin(&quot;jvm&quot;) version &quot;2.0.0&quot;
}</code></pre>
<p>문자열 기반 <code>id()</code>와 Kotlin 전용 <code>kotlin()</code> 함수가 함께 쓰인다.
처음에는 낯설지만 곧 익숙해진다.</p>
<h3 id="dependencies-문법">dependencies 문법</h3>
<pre><code class="language-kotlin">implementation(&quot;org.springframework.boot:spring-boot-starter-data-jpa&quot;)</code></pre>
<p>Groovy의 작은따옴표 대신 함수 호출처럼 보이는 형태다.</p>
<h3 id="extra-property--변수-관리">extra property / 변수 관리</h3>
<pre><code class="language-kotlin">val querydslVersion = &quot;5.0.0&quot;</code></pre>
<p>상수를 코드처럼 선언할 수 있어 가독성이 좋다.</p>
<hr>
<h2 id="추천-학습-순서">추천 학습 순서</h2>
<p>Java + Spring Boot 개발자가 Kotlin DSL에 익숙해지려면 아래 순서가 가장 효율적이다.</p>
<ol>
<li><code>build.gradle</code>과 <code>build.gradle.kts</code> 비교</li>
<li>Spring Web + JPA 의존성 추가</li>
<li>Kotlin kapt 설정</li>
<li>QueryDSL 설정</li>
<li>sourceSets / generated 경로 설정</li>
<li>멀티모듈 설정 경험</li>
</ol>
<p>특히 현재 운영 중인 Java Spring 프로젝트를 작은 모듈 단위로 Kotlin DSL로 옮겨보면 학습 속도가 매우 빠르다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>Kotlin DSL은 단순히 “Gradle 문법이 바뀐 것”이 아니다.</p>
<p>Java + Spring Boot 개발자가 Kotlin 생태계로 넘어갈 때,
가장 먼저 프로젝트의 <strong>빌드, 설정, 코드 스타일을 Kotlin스럽게 통일하는 출발점</strong>이다.</p>
<p>특히 IntelliJ 기반 개발 생산성, QueryDSL 설정 안정성, 타입 안정성, Kotlin 문법 적응 측면에서 실무 효율이 매우 좋다.</p>
<p>Kotlin + Spring Boot 백엔드 전환을 목표로 한다면,
서비스 코드보다 먼저 <code>build.gradle.kts</code>를 읽고 직접 수정해보는 경험이 생각보다 큰 학습 효과를 준다.</p>
<p>다음 글에서는 <strong>Java 개발자 기준으로 Spring Boot + Kotlin + QueryDSL 프로젝트를 Kotlin DSL로 처음 세팅하는 방법</strong>을 실제 예제로 정리해볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[중고 신입 개발자 생존일지]]></title>
            <link>https://velog.io/@dev_zzame/%EC%A4%91%EA%B3%A0-%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%83%9D%EC%A1%B4%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@dev_zzame/%EC%A4%91%EA%B3%A0-%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%83%9D%EC%A1%B4%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Sat, 27 Jan 2024 05:37:21 GMT</pubDate>
            <description><![CDATA[<p>아주아주 오랜만에 포스팅 하네요 :)
이전 직장의 근무환경도 꽤 괜찮았지만 개발팀과 경영진간의 갈등으로 인해 세달만에 그만두게 되었습니다. ㅠㅠ 난 오래 일하고 싶었다구.....
그곳에서도 꽤 많은걸 배우고 성장 했지만, 더 나은 환경이 있을거라 생각하고 울며겨자먹기로 퇴사하고 이력서를 또 굉장히 많이 돌렸죠...!</p>
<p>평소 이커머스쪽에 관심이 많기도 하고 이력서 돌릴 당시 AI가 굉장히 핫한 아이템으로 떠오르고 있던터라 AI 관련 회사로 가도 좋겠다 라고 생각하고 있었어요.
이력서 돌리고 며칠도 안지나 면접 일정이 몇 곳 잡혔죠.
그중에 관심이 갔던 회사는 AI를 활용해서 수험생들의 입시 합격률과 전략을 작성해주는 곳과, 쿠팡/티몬/위메프와 같은 이커머스 쇼핑몰들의 셀러들에게 선정산을 해주는 서비스를 갖고 있는 회사였어요. 여의도와 상암이었기에 직주거리도 적당하다 생각했습니다.</p>
<p>이력서 돌리고 바로 홀로 대만여행을 갔었기에 면접 준비할 시간은 없었어요 ㅠㅠ 귀국 하자마자 면접을 보고 두곳 다 결과가 일주일도 안되서 나왔습니다. 한곳은 당일 바로 연락을 하더라구요?</p>
<p>결국 선택한 회사는 핀테크 계열의 선정산 업체였습니다. 평소 관심 있었던 이커머스에 대한 이해와 핀테크 지식도 함께 쌓을 수 있을 것 같다는 생각에 선택했었어요. 그리고 예전에 혼자서 무대인사 정보 스크래핑 한다고 파이썬으로 크롤링 하던 경험이 있었는데, 제가 선택한 핀테크 회사에서도 물론 파이썬은 아니지만 자바로 스크래핑을 한다고 하더라구요. 그래서 무척이나 관심이 갔습니다.</p>
<p>핀테크 회사에는 백엔드 개발자로 들어간게 아니라 풀스택 개발자로 입사한거였어요. 그렇기 때문에 처음에는 백엔드 관련 업무도 했지만, 필요에 의해서 프론트 작업도 동시에 했어요. 신입 입장에서 굉장히 감사한 일이죠. 신입 때는 세분화된 업무보다는 이것저것 모두 해보며 웹 개발에 대한 이해를 높이는 게 굉장히 중요하다 생각해요. 그런점에서 제 상황에 딱 맞는 회사였죠.</p>
<p>다만 입사하고 이틀만에 본 업무에 투입됐어요. 중고라도 신입인데... 서비스에 대한 이해도 없는 상태에서 업무에 투입되다 보니 잘잘한 실수가 발생하곤 했지만, 저보다 6개월 먼저 입사 했던 선임님 덕분에 수월하게 해결해 나갈 수 있었어요. 지금도 그 선임님께 많은걸 배우고 그동안에도 많은걸 배워서 제 커리어를 쌓는데 참 많은걸 기여해주신 분이라 생각해서 감사한 마음이 커요.</p>
<p>입사하고 이틀만에 업무에 투입되고 매일 같이 야근을 했어요. 일주일에 5일을 일한다면 4.9일은 야근을 한 것 같아요 ... ㅠㅠ 그치만 회사가 성장하는 속도가 체감이 되다보니 몸은 힘들고 스트레스를 받지만 견딜 수 있었어요. 회사가 성장한다는 건 저 또한 함께 성장하고 있다는 반증일 수도 있으니까요.</p>
<p>그리고 24년 1월이 됐어요. 입사한지 7개월이 지났고 처음 약속 했던 연봉협상 날 말이에요. 입사한지 반년만에 연봉 인상을 20%나 했다구요...!! 그동안 빡쎄게 야근하고 스트레스 받아가며 성장한 결실이었다고 생각해요. 앞으로도 열심히 해야죠.. ㅎㅎ 6개월 뒤에 또 연봉 협상을 할텐데... 남들 1년동안 인상하는 거에 비하면 아주아주 높은 인상률을 기록할 것 같은 느낌입니다? 역시 노력 앞에 장사 없어요. 그냥 한곳에서 열심히 하면 언젠간 보상 받을 수 있는 것 같아요.</p>
<p>아 그리고 1월 기준 신입분들이 굉장히 많이 들어왔어요. 앞으로도 회사가 공격적인 영업을 할 것 같은데... 어깨가 무거워지네요. 사실 저와 선임님이 회사에 입사 했을 때에 비하면 지금 신입분들은 온보딩 할 시간도 주어지고 CTO님과 저희에게 이런저런 교육을 많이 받는데 굉장히 부러우면서도 제가 다른 사람에게 무언가를 알려줄 수 있는 위치에 있다는게 신기하면서도 뿌듯해요.</p>
<p>다음 생존일지 쓸 때 쯤이면 회사가 또 얼마나 성장해 있을까요? 그리고 그때도 저는 지금처럼 즐겁게 개발을 하고 있을까요? 헤헤</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신입 개발자 생존일지 1주차]]></title>
            <link>https://velog.io/@dev_zzame/%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%83%9D%EC%A1%B4%EC%9D%BC%EC%A7%80-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@dev_zzame/%EC%8B%A0%EC%9E%85-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%83%9D%EC%A1%B4%EC%9D%BC%EC%A7%80-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 26 Mar 2023 10:11:42 GMT</pubDate>
            <description><![CDATA[<p>서울에 정착한지도 꽤 오랜시간이 지났습니다. 사실 영국에서 돌아온 후로부터 시간이 너무 빠르게 지나가 그에 비하면 3년은 정말 짧다고 느껴지지만, 취준을 하던 2년은 아주아주 길었던 것 같아요. 암흑기 그 자체였구요ㅠㅠ 그래도 옆에 든든하게 서포팅 해주는 짝꿍이 있어서 견딜 수 있지 않았나 싶습니다😆</p>
<p>사실 저는 웹개발자가 되고 싶었지만 게임 회사에 출근하게 되었습니다. 직장을 구할 때 크게 고려했던 부분들을 꼽아 보자면 다음과 같습니다.</p>
<ul>
<li>첫째, 체력은 나의 업무능력과 연결된다. 곧 직주거리는 나의 생명이자 체력!<ul>
<li>IT 회사들이 판교나 강남에 집중되어 있지만, 상암이나 공덕, 영등포에도 꽤 많은 IT 회사들이 있었기에 Maximum 편도 50분으로 잡았습니다. 결국 출근하게 된 회사는 집에서 지하철 3정거장 떨어진 곳입니다.</li>
</ul>
</li>
<li>둘째, 기업의 발전 가능성은 곧 나의 성장 가치.<ul>
<li>반복된 업무만 해서는 성장할 수 없고, 성장하지 못하면 도태될 거라는 걸 알았기에 SI나 솔루션은 리스트업도 하지 않았습니다. 물론 파수와 같은 좋은 SI, 솔루션 업체들도 있지만, 대체로 시스템 구조상 성장의 가능성이 적기 때문에 서비스를 직접 개발하는 회사여야 했습니다.</li>
</ul>
</li>
<li>셋째, 일과 삶의 적절한 조화, 워라밸!<ul>
<li>물론 업무를 통해 꾸준히 성장할 수 있다면 좋겠지만, 스스로 자기계발을 하는 시간, 이를 위해 충전할 수 있는 시간이 보장되는 곳이어야 했습니다. 주 29시간 근무라는 타이틀은 거부할래야 거부할 수가 없었습니다.</li>
</ul>
</li>
</ul>
<p>이렇게 2023년 3월 7일부터 신입 개발자 솔짜미는 게임 회사에 출근하게 되었습니다.</p>
<p>첫주차는 당연히 큰 업무를 하지 않을 거라 생각하고 들뜬 마음으로 출근을 했습니다. 첫 출근을 해보니 현재 회사에서 서비스 중인 모바일 게임은 1개가 있고, 조만간 하나 더 출시를 앞두고 있는 상황이었습니다. 게임 클라이언트를 거의 대부분 개발이 완료된 상태이며, 게임 서버는 로컬에서 1차 테스팅만 마치고 아직 아무런 출시 세팅도 되지 않은 상태더라구요.</p>
<p>제 첫 업무는 이제 곧 출시할 게임의 리눅스 서버와 운영서버, 데이터베이스를 구축하는 일이었습니다. 취준할 때도 AWS는 정말 많이 사용해봤기에 큰 어려움 없이 업무를 할 수 있겠다 생각했었습니다.</p>
<p>리눅스 서버 구축하는거야 당연히 VERY EASY 했죠. DB도 MySQL을 사용할 거라고 했기에 그냥 AWS RDS를 이용해 MySQL을 만들고 작업 PC에서 CTO님께 전달받은 Data 파일들을 Workbench를 이용해 수동으로 넣어두었습니다. 여기까진 정말 너무너무너무 순조로웠어요.</p>
<p>문제는 여기서부터.</p>
<p>평소 Redis에 대해서 들어보긴 했지만, 이게 언제 어디서 어떻게 쓰는 친구인지.. 도통 알고 있지 못했습니다. Redis 서버를 생성하고 관련 세팅들을 하려고 하는데 이 인터넷 세상에는 정말 다양한 정보들이 많아서…ㅎㅎ 뭐가 옳은 정보인지 판별하는 일은 직접 해보지 않으면 알 수가 없었기에 굉장히 많은 삽질을 했습니다…</p>
<p>1주차가 지난 후에야 너무나도 단순하고 쉬운 업무였지만 그때는 뭐가 그렇게 어려웠는지.. 서버 개발자가 되고 싶다고 했으나 IP 통신 방식과 구조에 대해서 잘 몰랐던 제게 Redis 세팅은 너무 어려웠어요. 오죽하면 CTO님이 따로 불러 IP 관련 강의를 해줄 정도였어요. 굉장히 창피하고 숨고 싶은 순간이었지만, 지나고 보니 저런 천사도 따로 없을 것 같다는 생각이 들더라구요.</p>
<p>그치만 첫주차 때, 퇴근 후 집에 돌아오면 항상 녹초가 되고 힘들었어요. 몸도 맘도 둘다요 ㅠㅠ 짝꿍이 퇴근하고 집에 오자 그날 따라 왜 그리 짝꿍이 보고 싶었던지… 이야기 하다가 괜히 울컥하더라구요. 아마 제일 편한 사람이랑 같이 있으니까 긴장이 풀려서 그런 것 같았어요. 그런 제 모습을 보고 짝꿍은 위로도 해주면서 현실적인 조언을 해줬어요. 다른 회사를 가도 신입 때는 누구나 힘들텐데 여기서 그만두고 SI 회사를 갈 것인지, 아니면 일단 버텨보고 적응될 때까지 기다릴지 정하라구요.</p>
<p>입사 1주만에 그만두고 싶은 맘이 굴뚝 같았지만, 전 다섯마리 냥이들과 짝꿍을 책임져야 할 가장이기 때문에!! 다시 열심히 해보기로 마음 먹었습니다!! 원래 처음엔 다 힘든 거니까요 ㅎㅎ 그치만 1주차는 정말 너무 힘들었어요 ㅠㅠ 모든 신입들 화이팅 ㅠㅠ!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[redis 서버 구축하고 외부에서 외부 서버에서 원격 접속하기]]></title>
            <link>https://velog.io/@dev_zzame/redis-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B3%A0-%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C-%EC%99%B8%EB%B6%80-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%EC%9B%90%EA%B2%A9-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_zzame/redis-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B3%A0-%EC%99%B8%EB%B6%80%EC%97%90%EC%84%9C-%EC%99%B8%EB%B6%80-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%EC%9B%90%EA%B2%A9-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 14 Mar 2023 05:27:00 GMT</pubDate>
            <description><![CDATA[<p><a href="https://infoscoco.com/92">https://infoscoco.com/92</a></p>
<p>redis.conf 파일 설정 끝내고나서, 서비스 재시작 해줫는데 계속해서 원격 접속이 안되길래 찾아보니까sudo systemctl restart redis-server로 재시작을 해야 하는 것...
모든 블로그를 너무 믿지 말자 ㅠㅠ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[S3 버킷에 권한이 없을 때]]></title>
            <link>https://velog.io/@dev_zzame/S3-%EB%B2%84%ED%82%B7%EC%97%90-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%97%86%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@dev_zzame/S3-%EB%B2%84%ED%82%B7%EC%97%90-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%97%86%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Tue, 14 Mar 2023 02:08:19 GMT</pubDate>
            <description><![CDATA[<p><a href="https://shxrecord.tistory.com/182">https://shxrecord.tistory.com/182</a>
<a href="https://victorydntmd.tistory.com/334">https://victorydntmd.tistory.com/334</a>
<a href="https://honeywater97.tistory.com/139">https://honeywater97.tistory.com/139</a></p>
<p>S3 버킷에 파일 업로드 하려고 하는데 오류 났을 때
this xml file does not appear to have any style information associated with it. the document tree is shown below. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot Maven 프로젝트를 Jenkins를 이용해 CI/CD 하는 법]]></title>
            <link>https://velog.io/@dev_zzame/Spring-Boot-Maven-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Jenkins%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-CICD-%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@dev_zzame/Spring-Boot-Maven-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-Jenkins%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-CICD-%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Mon, 27 Feb 2023 08:54:46 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 Spring Boot Maven 프로젝트를 Jenkins를 이용해 CI/CD 하는 방법에 대해 알아보고자 합니다.</p>
<h2 id="1-jenkins-설치-및-설정">1. Jenkins 설치 및 설정</h2>
<p>Jenkins를 설치하고 설정합니다. Jenkins 설치 가이드는 <a href="https://www.jenkins.io/doc/book/installing/">공식 홈페이지</a>를 참고하세요.</p>
<h2 id="2-jenkins-plugin-설치">2. Jenkins Plugin 설치</h2>
<p>Jenkins Plugin Manager에서 다음과 같은 Plugin을 설치합니다.</p>
<ul>
<li>Maven Plugin</li>
<li>Git Plugin</li>
</ul>
<h2 id="3-jenkins-job-생성">3. Jenkins Job 생성</h2>
<p>Jenkins Dashboard에서 새로운 Job을 생성합니다.</p>
<h3 id="31-소스-코드-관리">3.1. 소스 코드 관리</h3>
<p>소스 코드 관리 항목에서 Git을 선택하고, 본인의 프로젝트 Git Repository URL을 입력합니다.</p>
<h3 id="32-빌드">3.2. 빌드</h3>
<p>빌드 항목에서 &quot;Invoke top-level Maven targets&quot;를 선택하고, &quot;Goals&quot;에 다음과 같은 내용을 입력합니다.</p>
<pre><code>clean package
</code></pre><h3 id="33-빌드-트리거-설정">3.3. 빌드 트리거 설정</h3>
<p>빌드 트리거 항목에서 다음과 같은 설정을 합니다.</p>
<ul>
<li><p>Poll SCM: 체크박스를 체크하고, Schedule에 다음과 같이 입력합니다.</p>
<pre><code>  * * * * *
</code></pre></li>
<li><p>GitHub hook trigger for GITScm polling: 체크박스를 체크합니다.</p>
</li>
</ul>
<h3 id="34-빌드-후-조치">3.4. 빌드 후 조치</h3>
<p>빌드 후 조치 항목에서 다음과 같은 설정을 합니다.</p>
<ul>
<li><p>Send build artifacts over SSH: 체크박스를 체크하고, SSH Site에 본인의 서버 정보를 입력합니다.</p>
</li>
<li><p>SSH 실행 스크립트: 다음과 같은 스크립트를 입력합니다.</p>
<pre><code>  #!/bin/bash

  APP_NAME={Spring Boot Application 이름}
  APP_PORT={Spring Boot Application 실행 포트}

  APP_PID=$(ps -ef | grep $APP_NAME | grep -v grep | awk &#39;{print $2}&#39;)

  if [ -z &quot;$APP_PID&quot; ]
  then
      echo &quot;No running $APP_NAME&quot;
  else
      kill -9 $APP_PID
      echo &quot;Killed $APP_NAME (PID: $APP_PID)&quot;
  fi

  nohup java -jar -Dserver.port=$APP_PORT {Spring Boot Application 실행 파일 경로} &gt; /dev/null 2&gt;&amp;1 &amp;
  echo &quot;Started $APP_NAME&quot;
</code></pre></li>
</ul>
<p>Jenkins Job 설정이 완료되었습니다.</p>
<h2 id="4-github-webhook-설정">4. GitHub Webhook 설정</h2>
<p>GitHub Repository에서 다음과 같은 Webhook을 설정합니다.</p>
<ul>
<li>Payload URL: <code>http://{Jenkins 서버 주소}/github-webhook/</code></li>
<li>Content type: <code>application/json</code></li>
<li>Which events would you like to trigger this webhook?: <code>Just the push event</code></li>
</ul>
<p>이제 코드를 Push하면 Jenkins에서 자동으로 빌드하고, 빌드 결과물을 서버에 배포하게 됩니다.</p>
<h2 id="5-문제점">5. 문제점</h2>
<p>이 방법으로 CI/CD를 진행하다보면 몇 가지 문제점이 발생할 수 있습니다.</p>
<h3 id="51-무분별한-빌드">5.1. 무분별한 빌드</h3>
<p>빌드 트리거 항목에서 &quot;Poll SCM&quot;을 체크하고, Schedule을 설정하면 주기적으로 Git Repository를 Polling합니다. 이 때, Git Repository에 변경 사항이 없어도 무분별하게 빌드를 진행하게 됩니다. 따라서, &quot;GitHub hook trigger for GITScm polling&quot;을 이용해 Push 이벤트가 발생할 때만 빌드하도록 설정하는 것이 좋습니다.</p>
<h3 id="52-보안">5.2. 보안</h3>
<p>Jenkins에서 빌드 후 조치 항목에서 SSH Site 정보를 입력해야 합니다. 이 때, 보안상 취약점이 발생할 수 있습니다. 따라서, SSH Site 정보를 Jenkins Credential에 등록하고, Jenkins Job 설정에서 Credential을 이용하도록 변경하는 것이 좋습니다.</p>
<h2 id="6-결론">6. 결론</h2>
<p>이번 포스팅에서는 Spring Boot Maven 프로젝트를 Jenkins를 이용해 CI/CD하는 방법을 안내했습니다. 하지만, 이 방법이 유일한 방법은 아니며, 더 나은 방법이나 다른 도구를 이용해 CI/CD를 진행할 수도 있습니다.</p>
<p>해당 포스팅은 아래 블로그를 참고하여 실습하며 요약 작성한 것입니다.
<a href="https://velog.io/@korea3611/Spring-Boot-Jenkins-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0">https://velog.io/@korea3611/Spring-Boot-Jenkins-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[효율적인 개발을 하기 위한 CI와 CD(일해라 SLAVE여!)]]></title>
            <link>https://velog.io/@dev_zzame/%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EA%B0%9C%EB%B0%9C%EC%9D%84-%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-CI%EC%99%80-CD%EC%9D%BC%ED%95%B4%EB%9D%BC-SLAVE%EC%97%AC</link>
            <guid>https://velog.io/@dev_zzame/%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EA%B0%9C%EB%B0%9C%EC%9D%84-%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-CI%EC%99%80-CD%EC%9D%BC%ED%95%B4%EB%9D%BC-SLAVE%EC%97%AC</guid>
            <pubDate>Mon, 27 Feb 2023 08:52:40 GMT</pubDate>
            <description><![CDATA[<p>CI(Continuous Integration)와 CD(Continuous Delivery/Deployment)는 소프트웨어 개발 프로세스에서 자동화된 빌드, 테스트, 배포를 위해 사용되는 방법론입니다.</p>
<h2 id="ci-continuous-integration">CI (Continuous Integration)</h2>
<p>CI는 개발자들이 작성한 코드를 자동으로 빌드하고 테스트하는 과정입니다. 새로운 코드 변경 사항이 이전 코드와 충돌할 가능성이 있는지 미리 확인하여 문제를 방지할 수 있습니다. 이는 버그를 미리 발견하고 수정할 수 있도록 도와주며, 개발자들이 코드를 빠르게 통합하여 공유할 수 있도록 합니다.</p>
<h2 id="cd-continuous-deliverydeployment">CD (Continuous Delivery/Deployment)</h2>
<p>CD는 CI와 마찬가지로 자동화된 프로세스를 사용합니다. CI가 코드를 빌드하고 테스트하는 것이라면, CD는 빌드된 코드를 자동으로 배포하는 과정입니다. Continuous Delivery는 코드가 실제로 배포될 준비가 되었을 때, 수동으로 배포할지 자동으로 배포할지 결정하는 것이며, Continuous Deployment는 코드가 자동으로 배포되는 것을 의미합니다.</p>
<p>이러한 방법론을 사용하면 개발과 배포를 자동화하여 효율성을 높이고, 인적 오류를 줄일 수 있습니다. 또한, 개발자들이 새로운 코드를 더 빠르게 공유하고, 사용자들이 더 빠르게 새로운 기능을 사용할 수 있도록 합니다.</p>
<h3 id="cicd-도구의-종류-및-장단점">CI/CD 도구의 종류 및 장단점</h3>
<h3 id="ci-도구">CI 도구</h3>
<ul>
<li>Jenkins: 가장 많이 사용되는 오픈소스 CI 도구. 다양한 플러그인을 제공하며, 유연하고 확장성이 높다. 하지만 설정이 복잡하고, 자원 소모가 크다는 단점이 있습니다.</li>
<li>Travis CI: GitHub와 연동하여 사용할 수 있는 CI 도구. 설정이 간단하고, 사용이 편리하다. 그러나 오픈소스 프로젝트에 대해서만 무료로 사용할 수 있으며, 프라이빗 리포지토리를 사용하려면 유료로 사용해야 합니다.</li>
<li>CircleCI: 설정이 간단하고, 다양한 언어와 프레임워크를 지원한다. 프라이빗 리포지토리를 무료로 사용할 수 있으며, 유료로 사용할 경우, 기능이 더욱 확장됩니다.</li>
</ul>
<h3 id="cd-도구">CD 도구</h3>
<ul>
<li>AWS CodeDeploy: 아마존 웹 서비스에서 제공하는 CD 도구. 다양한 플랫폼과 언어를 지원하며, 서버리스 아키텍처에 대한 지원이 강력하다. 그러나 설정이 복잡하고, 사용이 어려울 수 있습니다.</li>
<li>Google Cloud Build: 구글 클라우드 플랫폼에서 제공하는 CD 도구. 소스 코드를 빌드하고, Docker 이미지를 생성하는 등 다양한 기능을 제공한다. 그러나 AWS와 비교하여 기능이 상대적으로 적다는 단점이 있습니다.</li>
<li>Jenkins X: Kubernetes 위에서 동작하는 CI/CD 도구. 클라우드 환경의 애플리케이션 배포를 위한 기능이 매우 강력하다. 하지만 Kubernetes에 대한 지식이 필요하며, 설정이 복잡할 수 있습니다.</li>
</ul>
<p>CI/CD 도구를 사용하면 개발과 배포를 자동화하여 인적 오류를 줄이고, 더욱 빠르고 안정적인 개발/배포를 할 수 있다는 장점이 있다. 하지만 도구 선택 시, 프로젝트의 특성과 요구사항에 맞는 도구를 선택하는 것이 중요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java Collection Framework 란?]]></title>
            <link>https://velog.io/@dev_zzame/Java-Collection-Framework-%EB%9E%80</link>
            <guid>https://velog.io/@dev_zzame/Java-Collection-Framework-%EB%9E%80</guid>
            <pubDate>Thu, 16 Feb 2023 09:01:51 GMT</pubDate>
            <description><![CDATA[<h1 id="intro">Intro</h1>
<ul>
<li>Java 를 접해보지 않은 사람이라도 Collection 이라는 단어를 들으면 느낌적으로 오는 어감이 있을 것입니다. 특히 미술 전시를 할 때 ‘누군가의 컬렉션’ 과 같이 ‘모음’ 또는 ‘수집’ 과 같은 늬앙스로 사용되곤 합니다. Collection Framework 가 만들어진 이유도 비슷할거라고 생각합니다.</li>
<li>저는 Collection Framework 를 보통 알고리즘 문제를 풀거나 어떠한 데이터들을 한 곳에 모아두었다가 꺼내어 쓸 때 사용하곤 합니다. 너무 어렵게 생각하실 필요 없을 것 같습니다.</li>
</ul>
<hr>
<h1 id="collection-framework-란-무엇일까요">Collection Framework 란 무엇일까요?</h1>
<h2 id="collection-interface-collection-framework">Collection Interface? Collection Framework…?</h2>
<p>더 정확히 표기하자면 Java Collection Framework(JCF) 라고 표기할 수 있겠네요. Back-end 개발을 하다보면 데이터를 저장해 두었다가 꺼내 쓰는 등, 특정 데이터들을 용도에 맞게 효과적으로 처리할 수 있도록 도와주는 표준화된 방법들이 담긴 클래스 정도로 이해하시면 될 것 같습니다. 다시 말해, 데이터를 저장하는 자료구조와 데이터를 처리하는 알고리즘을 구조화해서 클래스로 만들어 놓은 것을 말합니다. </p>
<p>아래 사진은 Java Collection Framework의 상속 구조를 나타낸 이미지입니다.</p>
<p><img src="https://beginnersbook.com/wp-content/uploads/2022/09/Java_Collections.jpg" alt="출처 : [https://beginnersbook.com/java-collections-tutorials/](https://beginnersbook.com/java-collections-tutorials/)"></p>
<p>출처 : <a href="https://beginnersbook.com/java-collections-tutorials/">https://beginnersbook.com/java-collections-tutorials/</a></p>
<p>Collection 인터페이스는 크게 List와 Queue, Set 3가지 상위 인터페이스로 분류가 됩니다. Map 의 경우에는 Value 만 가지고 있는 Collection 인터페이스와 달리 Key 까지 가지고 있으니 디자인 단계에서 따로 분류가 되었다고 Stack Overflow 에서 본 적이 있습니다.</p>
<p>다시 말해, Map 인터페이스는 Collection 인터페이스를 상속 받고 있지 않지만, Java Collection Framework 에는 포함되는 개념입니다. 우리가 흔히 부르는 Collection 에는 List, Queue, Set 와 Map 이 포함되었다고 생각하시면 쉽습니다.</p>
<h2 id="collection-interface-들의-특징">Collection Interface 들의 특징</h2>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>구현클래스</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Set</td>
<td>HashSet / LinkedHashSet / SortedSet / TreeSet</td>
<td>순서를 유지하지 않는 데이터들의 집합이며, 중복을 허용하지 않음</td>
</tr>
<tr>
<td>Queue</td>
<td>Deque / PriorityQueue</td>
<td>먼저 넣은 데이터를 먼저 꺼내는 데이터 집합</td>
</tr>
<tr>
<td>List</td>
<td>ArrayList / LinkedList / Vector</td>
<td>데이터들의 순서가 존재하며, 중복 또한 허용한다</td>
</tr>
<tr>
<td>Map</td>
<td>HashTable / HashMap / TreeMap</td>
<td>키(Key) 와 값(Value) 가 서로 한쌍을 이루는 데이터 집합으로, 순서는 유지하지 않으며, 키(Key) 의 중복은 허용하지 않지만, 값(Value) 의 중복은 허용하는 데이터 집합</td>
</tr>
</tbody></table>
<h3 id="set-인터페이스">Set 인터페이스</h3>
<p>순서를 유지할 필요가 없고, 값들의 중복을 허용하고 싶지 않을 때 사용되는 자료구조 입니다.</p>
<ul>
<li><p>HashSet</p>
<ul>
<li><p>순서를 예측할 수 없습니다.</p>
</li>
<li><p>가장 빠른 임의 접근 속도를 가지고 있습니다.</p>
</li>
<li><p>Thread Safe 하지 않습니다.</p>
<aside>
💡 Thread Safe 란?
스레드 안전(thread 安全, 영어: thread safety)은 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다. 보다 엄밀하게는 하나의 함수가 한 스레드로부터 호출되어 실행 중일 때, 다른 스레드가 그 함수를 호출하여 동시에 함께 실행되더라도 각 스레드에서의 함수의 수행 결과가 올바로 나오는 것으로 정의한다.

</aside>
</li>
</ul>
</li>
<li><p>TreeSet</p>
<ul>
<li>정렬 방법을 지정할 수 있습니다.(default = 오름차순)</li>
<li>Thread Safe 하지 않습니다.</li>
</ul>
</li>
<li><p>LinkedHashSet</p>
<ul>
<li>입력된 순서대로 저장합니다.</li>
<li>Thread Safe 하지 않습니다.</li>
</ul>
</li>
</ul>
<h3 id="list-인터페이스">List 인터페이스</h3>
<p>데이터들이 저장된 순서를 따르며, 데이터들의 중복을 허용하는 자료구조 입니다.</p>
<ul>
<li>ArrayList<ul>
<li>단방향 포인터 구조로 각 데이터에 대한 인덱스를 가지고 있기 때문에 조회하는데 있어 성능이 우수합니다.</li>
<li>순차적으로 데이터가 추가된다는 조건하에서는 검색이 빠릅니다.</li>
<li>List 컬렉션을 여러 스레드에서 공유한다면 Thread Safe 하지 않습니다.</li>
</ul>
</li>
<li>LinkedList<ul>
<li>양방향 포인터 구조로 데이터의 삽입, 삭제가 빈번할 경우 데이터의 위치 정보만 수정해주면 되기 때문에 좋은 성능을 발휘할 수 있습니다.</li>
<li>검색 속도가 느립니다.</li>
<li>Stack, Queue, 양방향 Queue 를 만드는 용도로 주로 사용됩니다.</li>
</ul>
</li>
<li>Vector<ul>
<li>과거에는 대용량 데이터 처리를 위해 사용했었지만, 내부에서 자동으로 동기화가 일어나기 때문에 비교적 성능이 좋지 못하고, 무거워 최근에는 잘 사용하지 않습니다.</li>
<li>ArrayList 와 내부 구조는 같지만, Thread Safe 합니다.</li>
</ul>
</li>
</ul>
<h3 id="map-인터페이스">Map 인터페이스</h3>
<p>Key(키)와 Value(값) 의 쌍으로 이루어진 자료구조입니다. 순서는 유지하지 않으며, Key(키) 의 중복은 허용하지 않지만, Value(값) 의 중복은 허용합니다.</p>
<ul>
<li>HashTable<ul>
<li>HashMap 보다 느립니다.</li>
<li>null 불가합니다.</li>
<li>Thread Safe 합니다.</li>
</ul>
</li>
<li>HashMap<ul>
<li>Key(키) 의 중복을 허용하지 않으며, 순서를 따르지 않습니다.</li>
<li>null 값이 올 수 있습니다.</li>
<li>대체로 HashMap 객체를 생성할 때는 매개변수가 없는 생성자를 사용합니다. 하지만 HashMap 에 담을 데이터가 많은 경우에는 초기에 생성해주는 편입니다.</li>
</ul>
</li>
<li>TreeMap<ul>
<li>다른 Map 들과 마찬가지로 순서를 보장하지 않습니다.</li>
<li>하지만 Key(키) 와 Value(값) 을 저장하는 순간, Key(키) 가 정렬됩니다.</li>
<li>기본적으로 오름차순으로 정렬되며, 숫자 &gt; 알파벳 대문자 &gt; 알파벳 소문자 &gt; 한글 순으로 정렬됩니다.</li>
<li>Key(키) 의 정렬이 필요할 경우에는 HashMap 보다는 TreeMap 을 사용하길 권장합니다.</li>
</ul>
</li>
</ul>
<hr>
<h1 id="마무리">마무리</h1>
<p>추상적으로 Java Collection 에는 Set, List, Map 등이 있지만, 각 인터페이스들이 어떠한 경우에 사용 되고, 어떤 경우에 유리한 지에 대해 정확하게 정리되어 있지 않았습니다. 보통 자료구조라 하면 ArrayList 에 데이터들을 넣고 반복문을 통해 가져다 쓰는 정도로만 생각을 했었는데, Java Collection 에 대해 정리하다보니 앞으로 개발을 하며 더 많은 활용을 할 수 있겠다 라는 생각을 했습니다.</p>
<p>추가적으로 배열과 컬렉션 인터페이스가 어떤 차이점을 가지고 있는지에 대해서 공부해보는 것도 좋을 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이번주 읽어볼만한 아티클]]></title>
            <link>https://velog.io/@dev_zzame/%EC%9D%BD%EC%96%B4%EB%B3%BC-%EC%95%84%ED%8B%B0%ED%81%B4</link>
            <guid>https://velog.io/@dev_zzame/%EC%9D%BD%EC%96%B4%EB%B3%BC-%EC%95%84%ED%8B%B0%ED%81%B4</guid>
            <pubDate>Tue, 14 Feb 2023 07:35:29 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>MSA
<a href="https://velog.io/@sorzzzzy/MSA-MSA%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B8%B0%EC%88%A01-Spring-Boot-Spring-Cloud-Docker">https://velog.io/@sorzzzzy/MSA-MSA를-위한-기술1-Spring-Boot-Spring-Cloud-Docker</a></p>
</li>
<li><p>JPA
<a href="https://brunch.co.kr/@springboot/595">https://brunch.co.kr/@springboot/595</a></p>
</li>
<li><p>QueryDSL
<a href="https://dev.gmarket.com/33">https://dev.gmarket.com/33</a></p>
</li>
<li><p>JUnit5
<a href="https://medium.com/zigbang/spring-boot-junit5-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-9702c544bf3b">https://medium.com/zigbang/spring-boot-junit5-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-9702c544bf3b</a></p>
</li>
<li><p>Spring Security(OAuth2.0 social login)
<a href="https://deeplify.dev/back-end/spring/oauth2-social-login">https://deeplify.dev/back-end/spring/oauth2-social-login</a></p>
</li>
<li><p>OOP(Object Oriented Programming, 객체지향프로그래밍)
<a href="https://bae200ok.medium.com/%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-%EC%9E%85%EB%AC%B8-8b291e1cb50f">https://bae200ok.medium.com/%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-%EC%9E%85%EB%AC%B8-8b291e1cb50f</a></p>
</li>
<li><p>Java 8 with 함수형 프로그래밍
<a href="https://tecoble.techcourse.co.kr/post/2021-09-30-java8-functional-programming/">https://tecoble.techcourse.co.kr/post/2021-09-30-java8-functional-programming/</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP를 HTTPS로!(feat. SSL 인증서 발급)]]></title>
            <link>https://velog.io/@dev_zzame/HTTP%EB%A5%BC-HTTPS%EB%A1%9Cfeat.-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89</link>
            <guid>https://velog.io/@dev_zzame/HTTP%EB%A5%BC-HTTPS%EB%A1%9Cfeat.-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%B0%9C%EA%B8%89</guid>
            <pubDate>Mon, 13 Feb 2023 08:45:17 GMT</pubDate>
            <description><![CDATA[<h2 id="issue--외부-api-사용이-안돼요">issue : 외부 API 사용이 안돼요!</h2>
<p>본인은 Spring Boot로 SNS 플랫폼을 만들었습니다. 분명히 로컬에서 테스트 할 때는 외부 API(Facebook Social Login) 가 정상적으로 작동되고 있었습니다. 그런데 Deploy 이후 해당에 접근이 되지 않는 이슈가 발생했습니다. 친절하게도 Facebook(Meta) 에서는 원인을 설명해주더라구요.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/c5a02a99-f56f-43aa-b287-3cddc7c3fc51/image.png" alt=""></p>
<p>이밖에도 구글과 Stack OverFlow 에서 서치를 해보니, 얼마 전 Meta 의 보안 관련 이슈 때문에 보안 정책이 변경 되었기 때문에, SSL 인증서를 발급받지 않은 HTTP 로 접근을 하면 Meta의 API 들을 사용할 수 없다는 것이었습니다. 원인을 알았으니 해결은 베리 ESAY!</p>
<h2 id="solved--aws-route53-hostingkr-cloudflare">solved : AWS ROUTE53, HOSTINGKR, CLOUDFLARE</h2>
<p>먼저 저는 AWS EC2를 이용해 ubuntu 환경에서 프로젝트를 Deploy 했습니다. GCP나 NPC, Heroku 무엇으로 배포를 하던 SSL 인증서 발급 받는데는 지장이 없습니다. 배포까지 완료된 상황에서 SSL 인증서를 발급 받는 것이기 때문에 이 글을 보고 계신 분이라면 여기까지는 하셨으리라 생각됩니다.</p>
<p>저는 바로 Cloudflare 로 달려가서 회원가입과 로그인을 마치고, AWS EC2 에서 발급 받은 public IP를 입력하니 인가되지 않은 도메인이라며 오류를 뱉어내더라구요. 아마 public IP가 아니라 정상적인 domain 만 취급하나봐요. 이전에 리액트 공부하며 개인 포폴 사이트로 사용하려고 HOSTINGKR 에서 구매해두었던 domain 이 있어서 제 public IP와 연결 시켜야 했어요.</p>
<p><a href="https://www.cloudflare.com/ko-kr/">Cloudflare 홈페이지</a></p>
<p>이전에 AWS 에서 ROUTE53 이라는 서비스를 본 기억이 있어서 바로 또 AWS 로 달려갔죠…!</p>
<p>Route53에 제 도메인과 public IP를 등록하고 HOSTINGKR의 제 domain 관리로 들어가 DNS 서버를 변경해주었습니다. domain 을 통해 배포된 사이트에 접속이 가능해졌어요!!(거의 끝)</p>
<p>이제 남은 과정은 너무나도 쉽습니다. 다시 Cloudflare 로 이동해서 domain 을 입력해주고 시키는대로 하기만 하면 돼요!  AWS ROUTE53에서 했던 것처럼 Cloudflare 가 시키는대로 DNS 를 변경해주면 끝이에요. 정말 끝이에요.</p>
<p>이제 남은건 커피 한 잔하며 DNS 가 전세계로 전파되길 기다리는 일!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 초기 전략-DDL AUTO 옵션]]></title>
            <link>https://velog.io/@dev_zzame/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%B4%88%EA%B8%B0-%EC%A0%84%EB%9E%B5-DDL-AUTO-%EC%98%B5%EC%85%98</link>
            <guid>https://velog.io/@dev_zzame/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%B4%88%EA%B8%B0-%EC%A0%84%EB%9E%B5-DDL-AUTO-%EC%98%B5%EC%85%98</guid>
            <pubDate>Mon, 02 Jan 2023 01:09:23 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스-초기-전략-ddl-auto-옵션">데이터베이스 초기 전략-DDL AUTO 옵션</h1>
<p>applicaton.properties에 추가한 jpa 옵션 중에 주의 깊게 봐야 할 설정은 DDL AUTO 옵션이다. <strong>spring.jpa.hibernate.ddl-auto</strong> 옵션을 통해 애플리케이셔녀 구동 시 JPA의 데이터베이스 초기화 전략을 설정할 수 있다. 전략은 총 5가지가 있다.</p>
<ul>
<li>none :  사용하지 않음</li>
<li>create : 기존 테이블 삭제 후 새로운 테이블 생성</li>
<li>create-drop : 기존 테이블 삭제 후 새로운 테이블 생성. 종료 시점에 테이블 삭제</li>
<li>update : 변경된 스키마 적용</li>
<li>validate : 엔티티와 테이블 정상 매핑 확인</li>
</ul>
<p>update 옵션에서 컬럼 삭제는 엄청난 문제를 발생시킬 수 있기 때문에 컬럼 추가만 반영된다. 개발 초기에는 create 또는 update 옵션을 이용해서 익숙해지는데 집중하고, 추후에 validate 옵션을 설정해 주는 것이 좋다. 실제 운영환경이 아닐 경우에는 개발 편의상 create 옵션을 사용해 개발하는 것이 좋다.</p>
<p><strong>스테이징, 운영환경에서는 절대로 create, create-drop, update를 사용해서는 안 된다</strong>. 스테이징과 운영 서버에서는 테이블 생성 및 컬럼 추가, 삭제, 변경은 데이터베이스에서 직접하며, none을 사용하거나 validate를 이용해 정상적인 매핑 관계만 확인해야 한다.</p>
<blockquote>
<p><strong>스테이징 환경과 운영환경의 의미</strong>
스테이징 환경이란 운영환경과 거의 동일한 환경으로 구성하여, 운영환경에 배포하기 전 여러 가지 기능(성능, 장애 등)을 검증하는 환경이다. 운영환경은 실제 서비스를 운영하는 환경이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Data JPA 개요]]></title>
            <link>https://velog.io/@dev_zzame/Spring-Data-JPA-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@dev_zzame/Spring-Data-JPA-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Sun, 01 Jan 2023 12:58:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>JPA(Java Persistence API)는 자바 ORM 기술에 대한 API 표준이다.
<strong>ORM</strong>이란 ‘Object Relational Mapping’의 약자로 객체와 관계형 데이터베이스를 매핑해주는 것을 말한다.
그렇다면 ORM 기술을 왜 나왔는지와 관계형 데이터베이스의 문제점에 대해서 알아보도록 하자.</p>
</blockquote>
<h1 id="jpa란-무엇일까">JPA란 무엇일까?</h1>
<p>학생에 관한 데이터를 관리하는 Student 클래스가 있고, 학생 데이터를 관계형 데이터베이스에서 관리하기 위해서 우리는 SQL문을 사용한다. <strong>SQL 중심 개발의 문제점</strong>은 우리가 흔히 부르는 CRUD(INSERT, DELETE, UPDATE, SELECT)를 작성해서 객체를 관계형 데이터베이스에 넣어주고 가져오는 작업을 하는 것이다. 즉, 자바 객체를 SQL을 통해 데이터베이스에서 관리하게 하고, 데이터베이스에 저장된 데이터를 자바 애플리케이션에서 사용하려면 SQL을 통해 다시 자바 객체로 변환하는 반복적인 작업을 해야 한다. 다시 한번 더 말해, <strong>백엔드 코드에 집중해야 할 개발자가 SQL을 매핑하는 역할을 반복적으로 해야한다는 것</strong>이다.</p>
<p>또한 객체와 관계형 데이터베이스의 패러다임이 불일치 한다는 것이 가장 큰 문제점이다. 자바는 객체 지향 패러다임으로 만들어졌고, 관계형 데이터베이스는 데이터를 정규화해서 잘 보관하는 것을 목표로 한다. 객체를 데이터베이스에 넣기 위해서는 SQL문을 통해 변환하여 저장해야 하고, 데이터베이스에서 객체를 다시 꺼내오기 위해서는 복잡한 SQL문을 작성해야 한다. 결국 객체를 데이터 전달용으로 사용할 뿐 객체지향적으로 프로그래밍을 할 수가 없다. 이러한 <strong>문제점을 해결하기 위해 등장한 것이 ORM 기술</strong>이다.</p>
<p>객체는 객체지향적으로, 데이터베이스는 패러다임에 맞게 잘 정규화하여 설계한다. 그리고 <strong>ORM은 중간에서 2개를 매핑해주는 역할</strong>을 한다. 이를 통해 개발자는 코드를 조금 더 객체적으로 설계하고 비즈니스 로직에만 집중할 수 있게 된다.</p>
<p>JPA는 위에서 설명한 ORM 기술의 표준 명세로 자바에서 제공하는 API이다. 즉, JPA는 인터페이스고 이를 구현한 대표적인 구현체로는 Hibernate, EclipseLink, DataNucleus, OpenJpa, TopLink 등이 있다. 이 중에 JPA 인터페이스를 구현한 가장 대표적인 오픈소스가 Hibernate(하이버네이트)이다. </p>
<hr>
<h1 id="jpa-사용-시-장점">JPA 사용 시 장점</h1>
<h2 id="1-특정-데이터베이스에-종속되지-않음">1. 특정 데이터베이스에 종속되지 않음</h2>
<p>애플리케이션 개발을 위해 데이터베이스로 오라클(Oracle)을 사용해 개발했다고 가정해보자. 오라클을 오픈소스인 MariaDB로 변경하려면 데이터베이스마다 쿼리문이 다르기 때문에 쿼리문 전체를 수정해주어야 한다. 따라서 처음 선택한 데이터베이스를 변경하기 어렵다는 것이다. 하지만 JPA는 추상화한 데이터 접근 계층을 제공한다. 설정 파일에 어떤 데이터베이스를 사용할 것인지 알려주면 얼마든지 데이터베이스를 변경할 수 있다.</p>
<h2 id="2-객체지향적-프로그래밍">2. 객체지향적 프로그래밍</h2>
<p>JPA를 사용하면 데이터베이스 설계 중심의 패러다임을 가지고 객체지향적으로 설계가 가능하다. 이를 통해 조금 더 직관적이고 비즈니스 로직에 집중할 수 있다.</p>
<h2 id="3-생산성-향상">3. 생산성 향상</h2>
<p>데이터베이스 테이블에 새로운 컬럼이 추가되었을 경우, 해당 테이블의 컬럼을 사용하는 DTO 클래스의 필드도 모두 변경해야 한다. JPA에서는 테이블과 매핑된 클래스에 필드만 추가한다면 쉽게 관리가 가능하다. 또한 SQL문을 직접 작성하지 않고 객체를 사용해 동작하기 때문에 유지보수 측면에서도 좋고 재사용성도 증가한다는 장점이 있다.</p>
<hr>
<h1 id="jpa-사용-시-단점">JPA 사용 시 단점</h1>
<h2 id="1-복잡한-쿼리-처리">1. 복잡한 쿼리 처리</h2>
<p>통계 처리와 같이 복잡한 쿼리를 사용할 경우에는 SQL문을 사용하는 게 나을 수도 있다. JPA에서는 Native SQL을 통해 기존의 SQL문을 사용할 수도 있지만, 그러면 특정 데이터베이스에 종속된다는 단점이 생긴다. 이를 보완하기 위해서 SQL과 유사한 기술인 <strong>JPQL</strong>을 지원한다.</p>
<h2 id="2-성능-저하-위험">2. 성능 저하 위험</h2>
<p>객체 간의 매핑을 잘못했을 경우 성능 저하가 발생할 수도 있으며, 자동으로 생성되는 쿼리가 많기 때문에 개발자가 의도하지 않는 쿼리로 인해 성능이 저하되기도 한다.</p>
<h2 id="3-학습-시간">3. 학습 시간</h2>
<p>JPA를 제대로 사용하려면 알아야 할 것들이 많기 때문에 학습하는데 시간이 다소 소요된다. 개인적으로는 관계혀여 데이터베이스를 충분히 알아야 JPA를 사용할 수 있기 때문에 관계형 데이터베이스를 학습한 후에 JPA를 사용하길 권장한다.</p>
<hr>
<h1 id="jpa-동작-방식">JPA 동작 방식</h1>
<h2 id="엔티티">엔티티</h2>
<p>엔티티(Entity)란 데이터베이스의 테이블에 대응하는 클래스이다. @Entity라는 어노테이션이 붙은 클래스는 JPA에서 관리하며 엔티티라고 한다. 데이터베이스에 student 테이블을 만들고, 이에 대응되는 student.java 클래스를 만들어서 @Entity 어노테이션을 붙이면 이 클래스가 엔티티가 되는 것이다. 클래스 자체나 생성한 인스턴스도 엔티티라고 부른다.</p>
<h2 id="엔티티-매니저-팩토리">엔티티 매니저 팩토리</h2>
<p>엔티티 매니저 팩토리(Entity Manager Factory)는 엔티티 매니저 인스턴스를 관리하는 주체이다. 애플리케이션을 실행할 때 한 개만 만들어지며 사용자로부터 요청이 오면 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성한다.</p>
<h2 id="엔티티-매니저">엔티티 매니저</h2>
<p>엔티티 매니저(Entity Manager)란 영속성 컨텍스트에 접근해 엔티티에 대한 데이터베이스 작업을 제공한다. 내부적으로 데이터베이스 커넥션을 사용해 데이터베이스에 접근한다. 엔티티 매니저의 몇 가지 메소드를 살펴보자.</p>
<ol>
<li>find() : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장한다.</li>
<li>persist() : 엔티티를 영속성 컨텍스트에 저장한다.</li>
<li>remove() : 엔티티 클래스를 영소성 컨텍스트에서 삭제한다.</li>
<li>flush() : 영속성 컨텍스트에 저장된 내용을 데이터베이스 반영한다.</li>
</ol>
<h2 id="영속성-컨텍스트">영속성 컨텍스트</h2>
<p>JPA를 이해하기 위해서는 영속성 컨텍스트(Persistence Context)를 이해하는 것이 가장 중요하다. 엔티티를 영구 저장하는 환경으로 엔티티 매니저를 통해 영속성 컨텍스트에 접근한다.</p>
<hr>
<h1 id="영속성-컨텍스트-사용-시-이점">영속성 컨텍스트 사용 시 이점</h1>
<p>JPA는 왜 이렇게 영속성 컨텍스트를 사용해야 할까? 바로 애플리케이션과 데이터베이스 사이에 영속성 컨텍스트라는 중간 계층을 만들었기 때문이다. 이렇게 중간 계층을 만들면 버퍼링, 캐싱 등을 할 수 있는 장점이 있다.</p>
<h2 id="1차-캐시">1차 캐시</h2>
<p>영속성 컨텍스트에는 내부에는 엔티티를 저장하는 저장소인 1차 캐시가 존재하며 Map&lt;KEY, VALUE&gt;로 저장된다. entitymanager.find() 메소드 호출 시 영속성 컨텍스트의 1차 캐시를 조회한다. 엔티티가 존재할 경우 해당 엔티티를 반환하고, 엔티티가 없으면 데이터베이스에서 조회 후 1차 캐시에 저장 및 반환한다. 1차 캐시는 일반적으로 트랜잭션을 시작하고 종료할 때까지만 유효하다.</p>
<h2 id="동일성-보장">동일성 보장</h2>
<p>하나의 트랜잭션에서 같은 키값으로 영속성 컨텍스트에 저장된 엔티티를 조회하면 같은 엔티티 조회를 보장한다. 바로 1차 캐시에 저장된 엔티티를 조회하기 때문에 가능하다.</p>
<h2 id="트랜잭션을-지원하는-쓰기-지연">트랜잭션을 지원하는 쓰기 지연</h2>
<p>영속성 컨텍스트는 쓰기 지연 SQL 저장소가 존재한다. entitymanager.persist()를 호출하면 1차 캐시에 저장되는 것과 동시에 쓰기 지연 SQL 저장소에 SQL문이 저장된다. 이렇게 SQL을 쌓아두고 트랜잭션을 커밋하는 시점에 저장된 SQL문들이 flush되면서 데이터베이스에 반영된다. 이렇게 모아서 보내기 때문에 성능상 이점을 볼 수 있는 것이다.</p>
<h2 id="변경감지">변경감지</h2>
<p>JPA는 1차 캐시에 데이터베이스에서 처음 불러온 엔티티의 스냅샷 값을 갖고 있다. 그리고 1차 캐시에 저장된 엔티티와 스냅샷을 비교 후 변경된 내용이 있다면 UPDATE SQL문을 쓰기 지연 SQL 저장소에 담아둔다. 그리고 데이터베이스에 커밋 시점에 변경 내용을 자동으로 반영한다. 즉, 따라 update문을 호출할 필요가 없다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[23.01.01]]></title>
            <link>https://velog.io/@dev_zzame/23.01.01</link>
            <guid>https://velog.io/@dev_zzame/23.01.01</guid>
            <pubDate>Sun, 01 Jan 2023 11:39:37 GMT</pubDate>
            <description><![CDATA[<ol>
<li>JPA 동적쿼리/정적쿼리</li>
<li>OAuth2.0으로 로그인 구현</li>
<li>JWT</li>
<li>thymeleaf/mustache/JSP 차이점</li>
<li>각 DB간 공통점/차이점</li>
<li>JPA/QLRM/ORM</li>
<li>MVC 패턴</li>
<li>싱글톤</li>
<li>AOP</li>
<li>Maven</li>
<li>Gradle</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS 해킹 당하다!!!(저보고 천만원을 내라구요?)]]></title>
            <link>https://velog.io/@dev_zzame/AWS-%ED%95%B4%ED%82%B9-%EB%8B%B9%ED%95%98%EB%8B%A4%EC%A0%80%EB%B3%B4%EA%B3%A0-%EC%B2%9C%EB%A7%8C%EC%9B%90%EC%9D%84-%EB%82%B4%EB%9D%BC%EA%B5%AC%EC%9A%94</link>
            <guid>https://velog.io/@dev_zzame/AWS-%ED%95%B4%ED%82%B9-%EB%8B%B9%ED%95%98%EB%8B%A4%EC%A0%80%EB%B3%B4%EA%B3%A0-%EC%B2%9C%EB%A7%8C%EC%9B%90%EC%9D%84-%EB%82%B4%EB%9D%BC%EA%B5%AC%EC%9A%94</guid>
            <pubDate>Tue, 13 Dec 2022 19:50:13 GMT</pubDate>
            <description><![CDATA[<p>이 게시글을 보고 있는 사람이라면,</p>
<p>저희와 비슷한 상황이 생겼거나, 도움이 필요하신 분이라 생각합니다.</p>
<p>그래서 결론부터 작성하고 포스팅 시작하겠습니다.</p>
<h2 id="결론-">결론 :</h2>
<p>본인 과실이 없다면 99.9999% 환불 받을 수 있습니다.</p>
<p>제일 중요한 건 언어가 통하지 않는다고 멘붕하지 않고 침착하게 해결하려는 자세입니다.</p>
<p>타 커뮤니티에서 3억이 과금되어 6천만원은 자부담 했다는 내용이 있긴 하지만,</p>
<p>첫 1회에 한하여 본인 과실이 없을 경우 AWS는 재량껏 환불해주고 있습니다.</p>
<p>시간과 타이밍이 금입니다. 하루라도 빨리 AWS Support 팀에게 해킹 사실을 알리고 case를 urgent로 개설하세요.</p>
<h2 id="2022년-12월-02일-늦은-밤">2022년 12월 02일 늦은 밤</h2>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/04338ee7-1bf2-4634-810c-413123754265/image.jpeg" alt=""></p>
<p>나와 내 짝꿍은 한국 VS 포르투갈의 경기를 보기 위해 늦은 시간까지 안자고 버티고 있었다.</p>
<p>강팀 포르투갈과 경기에서 이겨야만 16강에 올라간다는 두려움과 혹시나 하는 희망을 품고 있을 무렵.</p>
<p>핸드폰으로 구글메일 알람이 울렸다.</p>
<p>카드사에서 온 메일처럼 보였으나 처음엔 결제금액이 말도 안되는 금액이라 흔한 피싱이겠거니 생각했었다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/3ca70ac5-755a-4165-8946-d1f065f8fc08/image.png" alt=""></p>
<p>그런데 짝꿍 핸드폰으로 결제가 승인되었다는 카드사 알림까지 띠링! 울리는 순간</p>
<p>축구로 가득찼던 기대는 사라져버리고, 온몸의 신경이 결제 메일로 향했다.</p>
<p>정신차리고 결제내역을 보니 AWS에서 결제가 된 것이었다.</p>
<p>내 AWS 계정에 짝꿍 카드를 등록한 적이 없는데… 왜 결제가 된걸까?</p>
<p>알고보니 짝꿍이 2022년 1월에 AWS 관련 교육을 나와 함께 들으면서 카드를 등록 해놓고 계정을 방치해 놓았던 것.</p>
<h3 id="그런데-의문이-있다-내-짝꿍은-개발자가-아니다-개발을-하지도-않는다-왜-과금이-된걸까">그런데 의문이 있다. 내 짝꿍은 개발자가 아니다. 개발을 하지도 않는다. 왜 과금이 된걸까?</h3>
<p>짝꿍이 교육 당시 만들었던 계정으로 로그인을 시도하는데 로그인이 되질 않는다.</p>
<p>AWS 가입 당시 구글메일로 왔던 인증메일 같은게 있나 메일함을 구석구석 찾아보았다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/2aceea98-bb16-4c8a-88ef-7b5ccc7ddb7a/image.png" alt=""></p>
<p>네…?</p>
<p>저희는 AWS 계정을 바꾼 적이 없는데요?</p>
<p>며칠 전인 11월 28일에 누군가 내 짝꿍의 AWS 계정을 해킹해서 접속 계정 주소를 변경해버렸다.</p>
<p>짝꿍은 개발도 안하고 github이나 cloud 등에 aws 관련 정보를 업로드 한 적도 없다.</p>
<p>어떻게 해킹한거지?(아직까지도 모름)</p>
<p>과금이 된 계정은 이미 해킹당해서 접속을 할 수가 없어 내 계정으로 로그인해서 Support(고객센터)에서 create case(1:1 문의와 비슷한 서비스)를 했다.(지인 계정이 없다면 로그인 없이도 create case를 할 수도 있다.)</p>
<p>전반적인 내용은,</p>
<aside>
💡 난 AWS 교육을 위해 올해 초에 계정을 생성하고 카드를 등록했어.
그 이후에 개발은 커녕 AWS 계정에 로그인한 적도 없는데 부당한 과금이 발생했어.
그리고 메일을 확인해보니 누군가 내 계정을 임의로 변경해버렸어. 아마 해킹당한 것 같아.
나는 이 부당한 청구에 대해서 지불할 수가 없어.
나는 한국의 서울에 살고 있고, 너희가 내 로그인 기록을 볼 수 있을거라고 생각해.

<p>부탁해. 나는 나의 계정을 되찾고 싶고, 부당한 요금을 내지 않을 방법이 필요해.</p>
<p>나는 한국의 서울에 살고 있고, 너희가 내 로그인 기록을 볼 수 있을거라고 생각해.</p>
<p>좀 도와줘. 답장 기다릴게.</p>
</aside>

<p>이러했다.</p>
<h2 id="2022년-12월-03일-이른-새벽">2022년 12월 03일 이른 새벽</h2>
<p>첫번째 국가번호를 쓰는 나라에서 국제전화가 걸려왔다.</p>
<blockquote>
<p>현재 너의 case가 개설되었고, 
이 문제를 해결하기 위해 우리 아마존은 널 무조건적으로 도울거야. 
준비됐어?
오늘이 주말이라 주말 지나고 월요일이 되면 진행상황을 보고해줄게.</p>
</blockquote>
<p>라는 내용의 전화 통화였다.
<img src="https://velog.velcdn.com/images/dev_zzame/post/f8d0b44c-fc4c-4386-880e-8c3f1a43ac07/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/f8f1a2d7-328b-40c9-b076-f122a8eb5aff/image.png" alt=""></p>
<p>Obrigado!, 호날두!</p>
<h2 id="2022년-12월-06일-미국은-월요일">2022년 12월 06일, 미국은 월요일</h2>
<p>아마존의 일처리 속도는 어마어마했다.</p>
<p>물론 한국은 화요일 새벽이었지만, 미국의 영업시간이 되자마자 메일 한통이 왔다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/c4c189ad-618c-46bf-a885-3e1702a7afe6/image.png" alt=""></p>
<p>고갱님!</p>
<p>메일 복구 해드렸는데요?</p>
<p>로그인 한번 해보시지요!</p>
<p>복구된 계정은 이전에 짝꿍이 사용하던 구글 계정이었다.</p>
<p>로그인을 하고 Support에 가보니 이미 case가 개설되어 있었다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/aa4b2853-393f-4a34-9010-b6daa98569b5/image.png" alt=""></p>
<p>대략적인 내용은,</p>
<blockquote>
<p>고갱님… 아이디에서 비정상적인 행동들이 보여요!
해킹당한 거 아니면 확인 좀요….</p>
</blockquote>
<p>라는 내용!</p>
<p>하지만 내 짝꿍은 이미 11월 28일에 AWS 계정을 해킹 당했으니까,</p>
<p>저 case와 메일을 받을 수가 없었다.(해커의 완전범죄)</p>
<p>Support를 보니 11월 28일에 개설되었던 위의 case 외에도</p>
<p>계정이 복구된 날짜에 새롭게 개설된 case가 하나 더 있었다.</p>
<p>내 짝꿍과 같이 AWS 해킹으로 인해 고통 받는 사람들 위해 전문을 이미지가 아니라 텍스트 원문으로 공유하고자 한다.</p>
<blockquote>
<p>Hi there,
Thank you for reaching back regarding your account&#39;s security alert.
Please know that once we detect any exposed access key or credential, our internal service team place a flag on the account, we will need to establish a protocol that must be followed to consider the account fully secured and sanitized again.
As soon as we confirm that the account is secured and fully sanitized, we&#39;ll reach our billing team to review the unauthorized charges generated during the compromise event.
To complete sanitizing your account, please follow the steps below:
Step 1: Terminate Cloudwatch Resources
Logs:</p>
</blockquote>
<ol>
<li>Access the Cloudwatch logs console here:
<a href="https://eu-central-1.console.aws.amazon.com/cloudwatch/home?region=eu-central-1#logsV2:log-groups">https://eu-central-1.console.aws.amazon.com/cloudwatch/home?region=eu-central-1#logsV2:log-groups</a></li>
<li>Select the Log group, and then choose Actions, Delete.</li>
<li>When prompted for confirmation, choose Yes, Delete. Repeat for all unauthorized logs.</li>
<li>Make sure to check region by region and terminate all those unauthorized.
Step 2: Terminate Elemental MediaLive Resources
Channels: </li>
<li>Open the MediaLive console at
<a href="https://eu-central-1.console.aws.amazon.com/medialive/home?region=eu-central-1#!/welcome">https://eu-central-1.console.aws.amazon.com/medialive/home?region=eu-central-1#!/welcome</a></li>
<li>On the Channels page, choose the option by the channel name.</li>
<li>If the channel is running, choose Stop.</li>
<li>Choose Delete.</li>
<li>Make sure to check region by region and terminate all those unauthorized.
Inputs:</li>
<li>Open the MediaLive console at
<a href="https://eu-central-1.console.aws.amazon.com/medialive/home?region=eu-central-1#!/welcome">https://eu-central-1.console.aws.amazon.com/medialive/home?region=eu-central-1#!/welcome</a></li>
<li>In the navigation pane, choose Inputs.</li>
<li>On the Inputs page, find the input that you want to delete, and then look at the State column.</li>
<li>If the state is Detached, then choose Delete.</li>
<li>Make sure to check region by region and terminate all those unauthorized.
Input security group:</li>
<li>Open the MediaLive console at
<a href="https://console.aws.amazon.com/medialive/">https://console.aws.amazon.com/medialive/</a></li>
<li>In the navigation pane, choose Input security groups.</li>
<li>On the Input security groups page, look at the State for the group to delete:</li>
<li>If the State is Idle, choose the group, and then choose Delete.</li>
<li>If the State is In use, continue with this procedure.</li>
<li>Make a note of the ID of the input security group. For example, 1234567.</li>
<li>Choose the group, and then choose Edit.</li>
<li>On the Edit input security group page, look at the Inputs on the right side and count how many inputs are attached to this input security group.</li>
<li>Choose the first input. Then on the page for that input, choose Edit. On the Edit page, in the Input security group, either create a new input security group for this input or choose another group (make sure you don&#39;t rechoose the same group; check the ID that you noted earlier). Choose Update so that this input is no longer attached to the input security group that you want to delete.
If there are still more inputs associated with this input group, then in the navigation pane, choose Input security groups, and repeat these steps to detach this input security group from all the inputs.</li>
<li>After detaching the last input from this input security group, wait for the State of the input security group to specify Idle. Then choose the group, and choose Delete.
Step 3: Terminate S3 Resources</li>
<li>Sign in to the AWS Management Console and open the Amazon S3 console at
<a href="https://console.aws.amazon.com/s3/">https://console.aws.amazon.com/s3/</a></li>
<li>In the Buckets list, select the option next to the name of the bucket that you want to delete, and then choose Delete at the top of the page.</li>
<li>On the Delete bucket page, confirm that you want to delete the bucket by entering the bucket name into the text field, and then choose Delete bucket.
Note:
If the bucket contains any objects, empty the bucket before deleting it by selecting the empty bucket configuration link in the This bucket is not empty error alert and following the instructions on the Empty bucket page. Then return to the Delete bucket page and delete the bucket.</li>
<li>To verify that you&#39;ve deleted the bucket, open the Buckets list and enter the name of the bucket that you deleted. If the bucket can&#39;t be found, your deletion was successful.
Once you complete the steps above, please reply to this case with your confirmation.
I look forward to hear back from you!
We value your feedback. Please share your experience by rating this and other correspondences in the AWS Support Center. You can rate a correspondence by selecting the stars in the top right corner of the correspondence.
Best regards,
Nicole U.
Amazon Web Services<blockquote>
</blockquote>
</li>
</ol>
<p>너무나도 친절하신 우리의 AWS 니콜님… ㅠㅠ</p>
<p>이슈 해결하는 내내 정말 큰 도움 받았다.</p>
<p>위의 내용을 요약하자면,</p>
<blockquote>
<p>고갱님! 너, 계정 해킹 당한 것 같은 느낌적인 느낌이 들어.
그러니까 우리가 도와줄게. 시키는대로만 해줘. 그럼 부당요금은 환불해줄 수도 있어!
해커가 고갱님 계정으로 이거저거 많이 사용했는데,
그 흔적들부터 지워보자고!
첫번째, <strong>Cloudwatch</strong> 리소스 종료
두번째, <strong>MediaLive</strong> 리소스 종료
세번째, <strong>S3</strong> 리소스 종료
이거 전부다 하고나서 다시 Reply 해줘!</p>
</blockquote>
<p>라는 내용이었다.</p>
<p>해커가 사용한 서비스들을 보니, 우리돈으로 코인 채굴했네…</p>
<p>위의 Step 1~2번은 모두 종료했는데,</p>
<p>S3는 짧은 시간동안 말도 안되는 트래픽이 발생하다보니,</p>
<p>콘솔에 접근이 안되도록 밴이 되어 있었다.</p>
<h2 id="2022년-12월-07일">2022년 12월 07일</h2>
<p>다시 한 번, 니콜에게 S3 콘솔에 접근이 안되어서</p>
<p>너가 나에게 요청한 Step3를 못하고 있다고 reply를 하니</p>
<p>아래와 같은 답장이 왔다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/f4b8f589-54ca-479a-ad98-ecf88d199a9b/image.png" alt=""></p>
<blockquote>
<p>안녕하세요…?
저는 니콜 친구 셀레나인데욥…
니콜이 휴가 갔어요…ㅎ…!
그치만 걱정 마세요!
저 셀레나가 있잖아요???
지금 관련 팀에서 왜 S3에 접근이 안되는지 문제 파악중이니까 곧 해결해준대요~</p>
</blockquote>
<p>라는 내용이었다.</p>
<h2 id="2022년-12월-08일">2022년 12월 08일</h2>
<p>하루만에 복귀한 우리의 니콜느님…!</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/da923100-433e-46b6-bdfe-a236dc510e4d/image.png" alt=""></p>
<p>이제 S3 콘솔에 접근을 할 수 있는 상태가 되었으니,</p>
<p>해커가 만들어놓은 버킷을 지우라는 내용이었다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/6ac6a74e-25ff-4be9-8477-593a9ce61827/image.png" alt=""></p>
<p>해커가 만든 버킷을 확인하러 들어갔는데,</p>
<p>프랑크푸르트에 사는 해커인지는 모르겠으나,</p>
<p>괜히 독일이 미워졌다.</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/627dad5f-34ef-49f0-a4f6-849ffe9c6c73/image.png" alt=""></p>
<p><strong>look-who-we-are</strong></p>
<p>정말 재수 없는 버킷명(내 눈 앞에 나타나기만 해봐라^^;;;;)</p>
<p><img src="https://velog.velcdn.com/images/dev_zzame/post/09bc6331-70e4-43a6-b6d6-fd574895e927/image.png" alt=""></p>
<p>버킷을 모두 지우고 니콜에게 보고를 했다.</p>
<h2 id="2022년-12월-09일">2022년 12월 09일</h2>
<p>니콜에게서 또 한 번의 답장이 왔다.</p>
<blockquote>
<p>Hi there,
Thank you for taking the required steps to secure your account.
All restrictions on your account have been removed.
Our billing adjustments team will review charges for the usage that you didn&#39;t authorize. Before they can do so, you must take the following steps to help prevent similar issues from reoccurring:</p>
</blockquote>
<ol>
<li>Set up at least two of the following services to monitor cost and usage:</li>
</ol>
<ul>
<li>Budgets:<blockquote>
<p><a href="https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html">https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html</a></p>
<ul>
<li>CloudWatch:</li>
</ul>
<p><a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/gs_monitor_estimated_charges_with_cloudwatch.html#gs_creating_billing_alarm">https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/gs_monitor_estimated_charges_with_cloudwatch.html#gs_creating_billing_alarm</a></p>
<ul>
<li>CloudTrail:</li>
</ul>
<p><a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html">https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html</a></p>
<p>-Trusted Advisor:</p>
<p><a href="https://docs.aws.amazon.com/awssupport/latest/user/get-started-with-aws-trusted-advisor.html">https://docs.aws.amazon.com/awssupport/latest/user/get-started-with-aws-trusted-advisor.html</a></p>
<p>Should you have web facing resources or APIs in your account, you may wish to consider AWS WAF which can provide protection for your workloads against well-known attack types.</p>
<p>Please see the WAF documentation for usage and pricing:</p>
<p><a href="https://docs.aws.amazon.com/waf/latest/developerguide/getting-started.html">https://docs.aws.amazon.com/waf/latest/developerguide/getting-started.html</a></p>
<p><a href="https://aws.amazon.com/waf/pricing/">https://aws.amazon.com/waf/pricing/</a></p>
<p>For more information about managing your AWS cost and usage, see the following:</p>
<p><a href="https://docs.aws.amazon.com/cost-management/latest/userguide/what-is-costmanagement.html">https://docs.aws.amazon.com/cost-management/latest/userguide/what-is-costmanagement.html</a></p>
<ol start="2">
<li>Respond to the support case and confirm which two cost and usage monitoring services you have set up. </li>
<li>We request you to enable Cost Explorer as it would help us to review the charges. To turn on Cost Explorer, follow the steps here:</li>
</ol>
<p><a href="https://docs.aws.amazon.com/cost-management/latest/userguide/ce-enable.html">https://docs.aws.amazon.com/cost-management/latest/userguide/ce-enable.html</a></p>
<p>If you don&#39;t set up preventative measures, then note the following:</p>
<ul>
<li><p>We can&#39;t submit a request to review a bill adjustment for December.</p>
</li>
<li><p>If this issue occurs again, then we can&#39;t submit a request to review future bills.</p>
</li>
</ul>
<p>As a reminder, you should regularly review the AWS Customer Agreement and AWS Shared Responsibility Model to make sure that you understand the agreements and responsibilities that are involved in your use of the Service Offerings. For more information, see the following:</p>
<ul>
<li>Customer Agreement:</li>
</ul>
<p><a href="https://aws.amazon.com/agreement/">https://aws.amazon.com/agreement/</a></p>
<ul>
<li>Shared Responsibility Model:</li>
</ul>
<p><a href="https://aws.amazon.com/compliance/shared-responsibility-model/">https://aws.amazon.com/compliance/shared-responsibility-model/</a></p>
<p>I look forwatd to hear back from you!</p>
<p>We value your feedback. Please share your experience by rating this and other correspondences in the AWS Support Center. You can rate a correspondence by selecting the stars in the top right corner of the correspondence.</p>
<p>Best regards,
Nicole U.
Amazon Web Services</p>
</blockquote>
</li>
</ul>
<p>내용인즉,</p>
<blockquote>
<p>안녕하세요!
저 니콜인데요~
해커 흔적은 다 지우신 것 같네용!
또 다시 해킹 당하면 그땐 저희가 못도와드리니까…
계정 보안 설정 좀 같이 해봐요 우리~
Budgets, CloudWatch, CloudTrail 등등
AWS에서 제공하는 여러 보안 서비스들이 있는데요~
이거 한번 설정해보세요! 두개 이상 하셔야 합니다!</p>
</blockquote>
<p>라는 내용이었다.</p>
<p>나는 전부 다 설정했다.</p>
<p>관련 가이드와 매뉴얼 링크를 함께 보내줘서 쉽게 설정할 수 있었다.</p>
<p>설정을 모두 끝마치고 니콜에게 또 보고하니 하루가 갔다.</p>
<h2 id="2022년-12월-10일">2022년 12월 10일</h2>
<p>그 다음 날 니콜에게 걱정 마라,</p>
<p>우리의 빌링 팀에서 너의 case를 계속해서 파악하고 있고 도와주려 하고 있다!</p>
<p>라는 내용의 답장이 왔었다.</p>
<h2 id="2022년-12월-11일">2022년 12월 11일</h2>
<p>미국은 토요일.</p>
<p>영업 안합니다요~</p>
<h2 id="2022년-12월-12일">2022년 12월 12일</h2>
<p>미국은 일요일.</p>
<p>영업 안합니다요~</p>
<h2 id="2022년-12월-13일">2022년 12월 13일</h2>
<p>드디어 미국 사람들이 일하는 시간!</p>
<blockquote>
<p>Hello,
Thank you for your patience while we worked with our billing team to review the unauthorized charges in your account. 
As part of the AWS Customer Agreement, customers are responsible for all activities that occur under their accounts. This includes all applicable fees and charges for use of AWS services.</p>
</blockquote>
<ul>
<li>As a one-time exception, we’ve issued a refund of $5,619.11 USD for charges on your November bill.
The refund is returned to the card or bank account that you used to pay the bill. The refund can take up to 7 business days to appear. Because processing times vary by bank, we can’t tell you the date that you can expect the refund to appear.
You can confirm the refund in your Billing console:
<a href="https://console.aws.amazon.com/billing/home#/paymenthistory">https://console.aws.amazon.com/billing/home#/paymenthistory</a></li>
<li>We’ve also approved a credit of $610.31 USD. This credit has been applied it to your AWS account for the month of December. The credit automatically absorbs any service charges that it applies to. Because the credit reduces your service charges, you will see an update to the total tax amount. <blockquote>
<p>You can see the credit that we applied in your console:</p>
<p><a href="https://console.aws.amazon.com/billing/home#/credits">https://console.aws.amazon.com/billing/home#/credits</a></p>
<p>To view your bill for the current month, see:</p>
<p><a href="https://console.aws.amazon.com/billing/home#/bills">https://console.aws.amazon.com/billing/home#/bills</a></p>
<p>The Billing console can take up to 48 hours to show the updated bill and tax total.</p>
<p>For your convenience, I have resolved this case. No further action is required on your part.</p>
<p>We value your feedback. Please share your experience by rating this and other correspondences in the AWS Support Center. You can rate a correspondence by selecting the stars in the top right corner of the correspondence.</p>
<p>Best regards,
Nicole U.
Amazon Web Services</p>
</blockquote>
</li>
</ul>
<p>드디어 니콜에게서 답장이 왔다. 최종 해결 보고였다.</p>
<p>너무 행복하니까 전문을 공개한다.</p>
<p>내용인즉,</p>
<p>가입할 때, 고객 약관에 따르면 원래 너가 모든 비용을 내야하는 게 맞지만,</p>
<p>이번 한번만 특별하게 5619달러는 환불해줄게.</p>
<p>그리고 이번달에 추가적으로 청구될 610달러는 AWS 크레딧을 지급해줄테니 그걸로 내도록 해.</p>
<p>대신 수수료나 세금은 너가 내!(약 60달러)</p>
<h2 id="2022년-12월-14일-새벽">2022년 12월 14일 새벽,</h2>
<p>글을 쓰고 있는 지금.</p>
<p>사건이 접수되고 총 10일만에 모든게 해결되었다.</p>
<p>다른 블로그나 커뮤니티에서는 최소 30일에서 최장 60일까지도 걸렸다고 하던데</p>
<p>AWS 측에서 시키는거 바로하고, 즉각 보고하니 휴무일 4일 제외하면 일주일도 채 안걸렸다.</p>
<p>이글은 AWS를 사용하는 모든 사용자에게 발생할 수 있는 사건이기 때문에,</p>
<p>이렇게 글로 남겨 모두에게 공유하고자 한다.</p>
<p>무엇보다도 해킹을 당하지 않으려면</p>
<p>깃헙이나 여타 커뮤니티 등에 자신의 AWS 계정에 접근할 수 있는 코드나 정보를 남기지 않아야 한다.</p>
<p>그리고 MFA(2차 인증) 설정을 통해 계정 보안을 강화해야 한다.</p>
<p>짝꿍 계정이 해킹당한 탓에 약 열흘간 맘졸였지만,</p>
<p>큰 교육과 교훈이 된 것 같다.</p>
<p>데이터베이스, 클라우드 해킹은 개인의 신용정보 도용으로도 이어질 수 있는만큼</p>
<p>앞으로 더 보안에 힘써야 할 것 같다.</p>
<p>AWS 해킹으로 인해 깊은 우울감이나, 멘붕이 오신 분들, 도움이 필요하신 분들이 계시다면</p>
<p><a href="mailto:dev.zzame@gmail.com">dev.zzame@gmail.com</a>으로 언제든지 메일 주세요.</p>
<p>기꺼이 도와드리겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] @Autowired와 @RequiredArgsConstructor 차이점 및 비교]]></title>
            <link>https://velog.io/@dev_zzame/Spring-Boot-Autowired%EC%99%80-RequiredArgsConstructor-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EB%B0%8F-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@dev_zzame/Spring-Boot-Autowired%EC%99%80-RequiredArgsConstructor-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EB%B0%8F-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Wed, 30 Nov 2022 16:58:02 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>개요</strong></p>
<ul>
<li>의존성 주입이란 것을 할 때 @Autowired 대신 생성자 주입을 활용하면 좋다고 한다. 일단 바로 적용할 수 있는 생성자 주입 방법을 간단하게 적어보겠다.</li>
</ul>
</li>
<li><p><strong>의존성이 뭔데?</strong></p>
<ul>
<li>HelloWorld 클래스에서 hello함수가 호출되기 위해서는 SayHello 클래스가 필요하다.</li>
<li>이 때 HelloWorld 클래스는 SayHello 클래스의 의존성을 가진다고 이야기한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">//HelloWorld.java
classHelloWorld {
private SayHello sayHello;

publicHelloWorld() {
this.sayHello =new SayHello();
    }

publicstartHelloWorld() {
this.sayHello.hello();
    }
 }</code></pre>
<ul>
<li><strong>왜 @Autowired를 지양해야 하는가?</strong><ul>
<li>@Autowired를 활용한 의존성 주입을 필드 주입이라고 한다. 필드 주입은 사용법이 매우 간단해서 대부분 의존성 주입을 필드 주입으로 접하지만, 편리하단 것 말고는 장점이 없어서 스프링 4.3부터는 사용하지 않는 것을 권장한다고 한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Controller
publicclassBoardController {
    @Autowiredprivate IBoardItemService boardItemService;

    /* 이하 생략 */
}</code></pre>
<ul>
<li><strong>그럼 @Autowired 대신 뭘 써야 할까?</strong><ul>
<li>스프링팀에서는 항상 생성자 주입을 사용하는 것을 권장하고 있습니다. 생성자 주입을 사용할 경우 아래와 같은 장점이 있다고 합니다.</li>
<li>① 순환 참조 방지</li>
<li>② 테스트 코드 작성 용이</li>
<li>③ 코드 악취 제거</li>
<li>④ 객체 변이 방지 (final 가능)</li>
</ul>
</li>
</ul>
<blockquote>
<p>Spring Team recommends: <strong><em>“Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies”.</em></strong></p>
</blockquote>
<ul>
<li><p>위의 예제를 필드 주입에서 생성자 주입으로 바꿔보겠다.</p>
</li>
</ul>
<pre><code class="language-java">@Controller
publicclassBoardController {
privatefinal IBoardItemService boardItemService;

publicBoardController(IBoardItemService boardItemService) {
this.boardItemService = boardItemService;
    }

    /* 이하 생략 */
}</code></pre>
<ul>
<li><strong>더 편하게 하려면?</strong><ul>
<li>생성자 주입으로 작성하게 되면 객체가 추가될 때마다 선언을 하고 생성자를 수정해줘야 하는데 여간 번거로운 게 아니다.</li>
<li>생성자 주입으로 작성하게 되면 객체가 추가될 때마다 선언을 하고 생성자를 수정해줘야 하는데 여간 번거로운 게 아니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
publicclassBoardController {
privatefinal IBoardItemService boardItemService;

    /* 이하 생략 */
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 브라우저에서 DB까지 데이터 보내고 저장하기]]></title>
            <link>https://velog.io/@dev_zzame/Spring-Boot-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-DB%EA%B9%8C%EC%A7%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%B4%EB%82%B4%EA%B3%A0-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_zzame/Spring-Boot-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-DB%EA%B9%8C%EC%A7%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%B4%EB%82%B4%EA%B3%A0-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 30 Nov 2022 16:57:24 GMT</pubDate>
            <description><![CDATA[<p><strong>우리는 POST 요청을 통해 브라우저의 BODY 영역에 있는 Form 내부의 데이터를 데이터 저장소에 저장하고 싶을 때가 있다.</strong></p>
<p>Spring Boot에서는 <strong>DTO</strong>(Data Transfer Object)라는 걸 이용해서 데이터를 전송 받은 후, <strong>Entity</strong>화 시킨 이후 비즈니스 로직을 수행하는 <strong>Service</strong>에서 JpaRepository를 상속하는 <strong>Repository</strong>라는 Interface를 DI(의존성 주입) 받은 후, save() 메소드를 이용해 DB에 최종적으로 저장하게 된다.</p>
<p>코드로 쉽게 확인해 보자.</p>
<pre><code>@Slf4j
@RequiredArgsConstructor
@Controller //1.IoC 2.파일을 반환하는 컨트롤러
public class AuthController {

@PostMapping(&quot;/auth/signup&quot;)
    public String signup(SignupDto signupDto){
        User user = signupDto.toEntity();
        User userEntity = authService.회원가입(user);
        System.out.println(userEntity);
        return &quot;auth/signin&quot;;
    }

}</code></pre><pre><code>package com.hansol.photogramstart.dto;

import com.hansol.photogramstart.domain.user.User;
import lombok.Data;

@Data //Getter, Setter 생성
public class SignupDto {
    private String username;
    private String password;
    private String email;
    private String name;

    public User toEntity() {
        return User.builder()
                .username(username)
                .password(password)
                .email(email)
                .name(name)
                .build();
    }
}
</code></pre><pre><code>package com.hansol.photogramstart.domain.user;

import org.springframework.data.jpa.repository.JpaRepository;

//JpaRepository를 상속했기 때문에 어노테이션 없이도 자동으로 IoC에 등록이 됨
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
}
</code></pre><pre><code>package com.hansol.photogramstart.domain.user;

//JPA(Java Persistance API)를 이용해 데이터를 영구히 저장할 것

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data //Getter, Setter 생성
@Entity //DB에 TABLE을 생성
public class User {
    @GeneratedValue(strategy = GenerationType.IDENTITY) //번호 증가 전략
    @Id
    private Long id;

    private String username;

    private String password;

    private String email;

    private String name;

    private String website;

    private String bio;

    private String phone;

    private String gender;

    private String profileImgUrl;

    private String role;

    private LocalDateTime createDate;

    @PrePersist //DB에 INSERT되기 직전에 실행
    public void createDate(){
        this.createDate = LocalDateTime.now();
    }
}
</code></pre><pre><code>package com.hansol.photogramstart.service;

import com.hansol.photogramstart.domain.user.User;
import com.hansol.photogramstart.domain.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service //1.IoC 2.트랜잭션 관리
public class AuthService {

    private final UserRepository userRepository;

    public User 회원가입(User user) {
        //회원가입 진행
        User userEntity = userRepository.save(user);
        return userEntity;
    }
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[도메인 설정이 완료 되었다면 접근 권한도 설정 해줘야죠?]]></title>
            <link>https://velog.io/@dev_zzame/%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%84%A4%EC%A0%95%EC%9D%B4-%EC%99%84%EB%A3%8C-%EB%90%98%EC%97%88%EB%8B%A4%EB%A9%B4-%EC%A0%91%EA%B7%BC-%EA%B6%8C%ED%95%9C%EB%8F%84-%EC%84%A4%EC%A0%95-%ED%95%B4%EC%A4%98%EC%95%BC%EC%A3%A0</link>
            <guid>https://velog.io/@dev_zzame/%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%84%A4%EC%A0%95%EC%9D%B4-%EC%99%84%EB%A3%8C-%EB%90%98%EC%97%88%EB%8B%A4%EB%A9%B4-%EC%A0%91%EA%B7%BC-%EA%B6%8C%ED%95%9C%EB%8F%84-%EC%84%A4%EC%A0%95-%ED%95%B4%EC%A4%98%EC%95%BC%EC%A3%A0</guid>
            <pubDate>Wed, 30 Nov 2022 16:56:53 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>ViewController에서 각 CRUD 요청에 대한 도메인 설정이 완료 되었다면?</strong></p>
<ul>
<li><p>아래와 같이 각 CRUD에 대한 매핑을 마쳤다면 특정 페이지는 권한 부여 없이도 접근이 가능하겠지만,</p>
</li>
<li><p>개인정보나 중요한 메시지가 담긴 페이지들에 대해서는 권한을 갖고 있는 사용자만 접근이 가능하도록 설정을 해주어야 한다.</p>
<pre><code>package com.hansol.photogramstart;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ViewControllerTest {

 @GetMapping(&quot;/auth/signup&quot;)
 public String signupPage() {
    return &quot;auth/signup&quot;;
 }

 @GetMapping(&quot;/auth/signin&quot;)
 public String signinPage() {
    return &quot;auth/signin&quot;;
 }

 @GetMapping(&quot;/image/story&quot;)
 public String storyPage() {
    return &quot;image/story&quot;;
 }

 @GetMapping(&quot;/image/popular&quot;)
 public String popularPage() {
    return &quot;image/popular&quot;;
 }

 @GetMapping(&quot;/image/upload&quot;)
 public String uploadPage() {
    return &quot;image/upload&quot;;
 }

 @GetMapping(&quot;/user/profile&quot;)
 public String profilePage() {
    return &quot;user/profile&quot;;
 }

 @GetMapping(&quot;/user/update&quot;)
 public String updatePage() {
    return &quot;user/update&quot;;
 }
}
</code></pre></li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>Security Config 설정하기</strong></p>
<ul>
<li><p>먼저 @Configuration를 통해 Config 파일이라는 것을 IoC에 등록해준다.</p>
</li>
<li><p>그리고 @EnableWebSecurity 어테이션으로 해당 파일로 시큐리티를 활성화 할 것임을 알려준다.</p>
</li>
<li><p>이후 Security Config와 관련된 설정들을 커스터마이징 해준다.</p>
<pre><code>package com.hansol.photogramstart.config;

import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;

@EnableWebSecurity //해당 파일로 시큐리티를 활성화
@Configuration //IoC 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
      //super.configure(http);
      //super.configure(http)를 삭제하면, 기존 시큐리티가 가지고 있던 기능이 모두 비활성화 됨
      http.authorizeRequests()
              .antMatchers(&quot;/&quot;, &quot;/user/**&quot;, &quot;/image/**&quot;, &quot;/subscribe/**&quot;, &quot;/comment/**&quot;).authenticated()
              .anyRequest().permitAll()
              .and()
              .formLogin()
              .loginPage(&quot;/auth/signin&quot;)
              .defaultSuccessUrl(&quot;/&quot;);
      // &quot;/&quot;, &quot;/user/**&quot;, &quot;/image/**&quot;, &quot;/subscribe/**&quot;, &quot;/comment/**&quot;와 같은 요청이 왔을 경우에는 인증이 필요함.
      // 그 이외의 요청에 대해서는 모두 권한 없이도 접근이 가능하도록 설정.
      // 접근이 필요한 페이지에 대해서 접근을 요청할 경우, 접근 권한이 없다면 로그인 페이지를 보여주어 권한을 갖도록 유도함.
  }
}
</code></pre></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 동작원리]]></title>
            <link>https://velog.io/@dev_zzame/Spring-Boot-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@dev_zzame/Spring-Boot-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Tue, 29 Nov 2022 10:42:44 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>내장 톰켓을 가진다.</strong></p>
<ul>
<li>톰켓을 따로 설치할 필요 없이 바로 실행가능하다.</li>
<li>웹서버(아파치)는 자바코드를 읽을 수 없기 때문에 jsp와 같은 파일을 웹브라우저의 요청에 대해서 응답해줄 수가 없다.</li>
<li>이를 해결하기 위해 톰켓이 존재한다.</li>
<li>톰켓은 jsp파일의 자바코드를 컴파일 하여 html파일로 변환한다. 그리고 웹서버에 다시 전달하면 웹서버가 웹브라우저에 응답해주게 된다.</li>
</ul>
</li>
<li><p><strong>서블릿 컨테이너</strong></p>
<ul>
<li>웹브라우저에서 URL(자원 접근)을 통해 정적인 파일을 요청할 때는 웹서버가 작동한다.</li>
<li>웹브라우저에서 URI(식별자 접근)을 통해 자바 파일을 요청할 때는 톰켓이 작동한다.</li>
<li>스프링의 경우에는 URL 접근 방식이 불가능하다. 특정 파일만 요청하는 게 불가능하다는 말이며, 톰켓과 자바를 무조건 거쳐야 한다는 뜻이다.</li>
<li>클라이언트로부터 어떠한 요청이 왔을 때, 서블릿 컨테이너는 최초요청일 경우에는 스레드를 하나 생성하고 응답을 해준다. 기본적으로 멀티스레드 이기 때문에 동시에 여러 처리를 해줄 수 있지만, 보유하고 있는 스레드가 모두 작동 중일 경우에는 딜레이 후에 동작이 완료된 스레드에 배정되어 응답하게 된다.</li>
</ul>
</li>
</ul>
<p><img src="https://blog.kakaocdn.net/dn/OCwgQ/btqCtYeuOMu/ETtkL2p1e0IewF5IRq6WGK/img.png" alt="https://blog.kakaocdn.net/dn/OCwgQ/btqCtYeuOMu/ETtkL2p1e0IewF5IRq6WGK/img.png"></p>
<ul>
<li><strong>web.xml</strong><ul>
<li>ServletContext의 초기 파라미터</li>
<li>Session의 유효시간 설정</li>
<li>Servlet/JSP에 대한 정의</li>
<li>Servlet/JSP 매핑</li>
<li>Mime Type 매핑</li>
<li>Welcome File list</li>
<li>Error Pages 처리</li>
<li>리스너/필터 설정</li>
<li>보안</li>
</ul>
</li>
</ul>
<blockquote>
<p>여기에서 Servlet/JSP 매핑시(web.xml에 직접 매핑 or <strong>@WebServlet 어노테이션 사용)</strong>에 모든 클래스에 매핑을 적용시키기에는 코드가 너무 복잡해지기 때문에 FrontController 패턴을 이용함.</p>
</blockquote>
<ul>
<li><p><strong>FrontController 패턴</strong></p>
<ul>
<li>최초 앞단에서 request 요청을 받아서 필요한 클래스에 넘겨준다. 왜? web.xml에 다 정의하기가 너무 힘듬.</li>
<li>이때 새로운 요청이 생기기 때문에 request와 response가 새롭게 new될 수 있다. 그래서 아래의 RequestDispatcher가 필요하다.</li>
</ul>
</li>
<li><p><strong>RequestDispatcher</strong></p>
<ul>
<li>필요한 클래스 요청이 도달했을 때 FrontController에 도착한 request와 response를 그대로 유지시켜준다.</li>
</ul>
</li>
<li><p><strong>DispatcherServlet</strong></p>
<ul>
<li>FrontController 패턴을 직접짜거나 RequestDispatcher를 직접구현할 필요가 없다. 왜냐하면 스프링에는 DispatcherServlet이 있기 때문이다. DispatcherServlet은 FrontController 패턴 + RequestDispatcher이다.</li>
<li>DispatcherServlet이 자동생성되어 질 때 수 많은 객체가 생성(IoC)된다. 보통 필터들이다. 해당 필터들은 내가 직접 등록할 수 도 있고 기본적으로 필요한 필터들은 자동 등록 되어진다.</li>
</ul>
</li>
<li><p><strong>스프링 컨테이너</strong></p>
<ul>
<li>DispatcherServlet에 의해 생성되어지는 수 많은 객체들은 ApplicationContext에서 관리된다. 이것을 IoC라고 한다.</li>
<li><strong>ApplicationContext</strong></li>
<li>IoC란 제어의 역전을 의미한다. 개발자가 직접 new를 통해 객체를 생성하게 된다면 해당 객체를 가르키는 레퍼런스 변수를 관리하기 어렵다. 그래서 스프링이 직접 해당 객체를 관리한다. 이때 우리는 주소를 몰라도 된다. 왜냐하면 필요할 때 DI하면 되기 때문이다.</li>
<li>DI를 의존성 주입이라고 한다. 필요한 곳에서 ApplicationContext에 접근하여 필요한 객체를 가져올 수 있다. ApplicationContext는 싱글톤으로 관리되기 때문에 어디에서 접근하든 동일한 객체라는 것을 보장해준다.</li>
<li>ApplicationContext의 종류에는 두가지가 있는데 (root-applicationContext와 servlet-applicationContext) 이다.</li>
<li><strong>a. servlet-applicationContext</strong></li>
<li>servlet-applicationContext는 ViewResolver, Interceptor, MultipartResolver 객체를 생성하고 웹과 관련된 어노테이션 Controller, RestController를 스캔 한다. 해당 파일은 DispatcherServlet에 의해 실행된다.</li>
<li><strong>b. root-applicationContext</strong></li>
<li>root-applicationContext는 해당 어노테이션을 제외한 어노테이션 Service, Repository등을 스캔하고 DB관련 객체를 생성한다. (스캔이란 : 메모리에 로딩한다는 뜻) 해당 파일은 ContextLoaderListener에 의해 실행된다. ContextLoaderListener를 실행해주는 녀석은 web.xml이기 때문에 root-applicationContext는 servlet-applicationContext보다 먼저 로드 된다.</li>
<li>당연히 servlet-applicationContext에서는 root-applicationContext가 로드한 객체를 참조할 수 있지만 그 반대는 불가능하다. 생성 시점이 다르기 때문이다.</li>
<li><strong>Bean Factory</strong></li>
<li>필요한 객체를 Bean Factory에 등록할 수 도 있다. 여기에 등록하면 초기에 메모리에 로드되지 않고 필요할 때 getBean()이라는 메소드를 통하여 호출하여 메모리에 로드할 수 있다. 이것 또한 IoC이다. 그리고 필요할 때 DI하여 사용할 수 있다. ApplicationContext와 다른 점은 Bean Factory에 로드되는 객체들은 미리 로드되지 않고 필요할 때 호출하여 로드하기 때문에 lazy-loading이 된다는 점이다.</li>
</ul>
</li>
</ul>
<p><img src="https://blog.kakaocdn.net/dn/q43e6/btqCvx1OYiy/MJv6bpvTjEtC4XsNsR4m71/img.png" alt="https://blog.kakaocdn.net/dn/q43e6/btqCvx1OYiy/MJv6bpvTjEtC4XsNsR4m71/img.png"></p>
<ul>
<li><strong>요청 주소에 따른 적절한 컨트롤로 요청 (Handler Mapping)</strong><ul>
<li>GET요청 =&gt; <a href="http://localhost:8080/post/1">http://localhost:8080/post/1</a></li>
<li>해당 주소 요청이 오면 적절한 컨트롤러의 함수를 찾아서 실행한다.</li>
</ul>
</li>
<li><strong>응답</strong><ul>
<li>html파일을 응답할지 Data를 응답할지 결정해야 하는데 html 파일을 응답하게 되면 ViewResolver가 관여하게 된다.</li>
<li>하지만 Data를 응답하게 되면 MessageConverter가 작동하게 되는데 메시지를 컨버팅할 때 기본전략은 json이다.</li>
</ul>
</li>
</ul>
<p><img src="https://blog.kakaocdn.net/dn/qntbk/btqCzBhZ33L/ifWzqKL76nFdalVNzKApk1/img.png" alt="https://blog.kakaocdn.net/dn/qntbk/btqCzBhZ33L/ifWzqKL76nFdalVNzKApk1/img.png"></p>
]]></description>
        </item>
    </channel>
</rss>