<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>log.918</title>
        <link>https://velog.io/</link>
        <description>학습한 내용을 기록하고 공유하며 함께 성장하는 백엔드 개발자입니다.</description>
        <lastBuildDate>Wed, 17 Jun 2026 01:47:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>log.918</title>
            <url>https://velog.velcdn.com/images/fire_918/profile/c7de09d5-1564-4473-b940-d1a76a498945/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. log.918. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/fire_918" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[왜 JPA는 우리가 직접 사용하지도 않는 기본 생성자를 요구할까?]]></title>
            <link>https://velog.io/@fire_918/%EC%99%9C-JPA%EB%8A%94-%EC%9A%B0%EB%A6%AC%EA%B0%80-%EC%A7%81%EC%A0%91-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%8F%84-%EC%95%8A%EB%8A%94-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%9A%94%EA%B5%AC%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@fire_918/%EC%99%9C-JPA%EB%8A%94-%EC%9A%B0%EB%A6%AC%EA%B0%80-%EC%A7%81%EC%A0%91-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80%EB%8F%84-%EC%95%8A%EB%8A%94-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A5%BC-%EC%9A%94%EA%B5%AC%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Wed, 17 Jun 2026 01:47:40 GMT</pubDate>
            <description><![CDATA[<h3 id="kotlin을-쓰다가-java로-돌아오며-다시-생각하게-된-생성자">Kotlin을 쓰다가 Java로 돌아오며 다시 생각하게 된 생성자</h3>
<p>Kotlin을 쓰다가 다시 Java를 사용하게 되면서, 한동안 너무 당연하게 사용했던 개념들을 다시 생각하게 되었습니다.</p>
<p>그중 하나가 바로 <strong>생성자(Constructor)</strong> 입니다.</p>
<p>Kotlin에서는 <code>data class</code> 덕분에 생성자를 직접 다루지 않아도 자동으로 처리되는 경우가 많습니다.</p>
<pre><code class="language-kotlin">data class User(
    val name: String,
    val age: Int
)</code></pre>
<p>이렇게만 작성해도 생성자뿐 아니라 <code>getter</code>, <code>equals()</code>, <code>hashCode()</code>, <code>toString()</code>까지 자동으로 생성됩니다.</p>
<p>그래서 자연스럽게 생성자라는 개념을 깊게 생각하지 않고 넘어갔던 것 같습니다.</p>
<p>하지만 Java로 다시 돌아오니 상황이 달라졌습니다.</p>
<pre><code class="language-java">User user = new User(&quot;지민&quot;, 20);</code></pre>
<p>그리고 JPA를 사용하면서는 기본 생성자까지 등장합니다.</p>
<pre><code class="language-java">protected User() {}</code></pre>
<p>그러다 문득 이런 생각이 들었습니다.</p>
<blockquote>
<p>왜 생성자는 두 개나 필요한 걸까?</p>
</blockquote>
<hr>
<h3 id="생성자는-무엇일까">생성자는 무엇일까?</h3>
<p>생성자를 한마디로 정의하면</p>
<blockquote>
<p>객체가 생성될 때 자동으로 실행되는 초기화 코드</p>
</blockquote>
<p>입니다.</p>
<pre><code class="language-java">new User(&quot;지민&quot;, 20);</code></pre>
<p>이 코드는 단순히 메모리에 객체를 만드는 것이 아닙니다.</p>
<p>객체를 생성하면서 동시에 초기 상태를 설정하는 과정까지 포함하고 있습니다.</p>
<p>즉, 생성자는 객체가 올바른 상태로 시작할 수 있도록 도와주는 역할을 합니다.</p>
<hr>
<h3 id="생성자가-없다면">생성자가 없다면?</h3>
<p>다음과 같은 클래스가 있다고 가정해 보겠습니다.</p>
<pre><code class="language-java">class User {
    String name;
    int age;
}</code></pre>
<p>객체를 생성하면</p>
<pre><code class="language-java">User user = new User();</code></pre>
<p>필드에는 기본값이 들어갑니다.</p>
<ul>
<li><code>name</code> → <code>null</code></li>
<li><code>age</code> → <code>0</code></li>
</ul>
<p>즉, 객체는 생성되었지만 의미 있는 상태라고 보기는 어렵습니다.</p>
<hr>
<h3 id="그래서-등장한-파라미터-생성자">그래서 등장한 파라미터 생성자</h3>
<p>이 문제를 해결하기 위해 사용하는 것이 <strong>파라미터 생성자</strong>입니다.</p>
<pre><code class="language-java">public User(String name, int age) {
    this.name = name;
    this.age = age;
}</code></pre>
<p>이제 객체는 생성과 동시에 필요한 값을 가지게 됩니다.</p>
<pre><code class="language-java">User user = new User(&quot;지민&quot;, 20);</code></pre>
<p>결과적으로 객체는 처음부터 의미 있는 상태를 유지할 수 있습니다.</p>
<hr>
<h3 id="그런데-왜-기본-생성자가-따로-있을까">그런데 왜 기본 생성자가 따로 있을까?</h3>
<p>Java와 JPA를 사용하다 보면 자주 보게 되는 코드가 있습니다.</p>
<pre><code class="language-java">protected User() {}</code></pre>
<p>흥미로운 점은 우리가 직접 이 생성자를 호출하는 경우는 거의 없다는 것입니다.</p>
<p>그렇다면 이 생성자는 누가 사용하며 왜 필요한 것일까요?</p>
<p>바로 <strong>JPA와 같은 프레임워크가 사용합니다.</strong></p>
<p>JPA는 데이터베이스에서 조회한 값을 이용해 객체를 생성할 때 우리가 작성한 파라미터 생성자를 호출하지 않습니다.</p>
<p>먼저 기본 생성자를 이용해 빈 객체를 만든 뒤</p>
<p>리플렉션(Reflection)을 사용하여 필드에 값을 채워 넣는 방식으로 객체를 완성합니다.</p>
<p>그래서 JPA 명세에서는 엔티티에 기본 생성자가 반드시 존재해야 하며 외부에서 무분별하게 사용되지 않도록 보통 <code>protected</code> 접근 제한자를 사용합니다.</p>
<p>즉</p>
<ul>
<li><strong>파라미터 생성자</strong>는 개발자가 의미 있는 객체를 만들기 위해 사용하고</li>
<li><strong>기본 생성자</strong>는 JPA와 같은 프레임워크가 객체를 생성하기 위해 사용합니다.</li>
</ul>
<p>둘은 서로 역할이 다른 생성자인 셈입니다.</p>
<hr>
<h3 id="마무리하며">마무리하며</h3>
<p>Kotlin에서는 많은 것들이 자동으로 처리되기 때문에 깊게 생각하지 않고 넘어갔던 부분들이 있었습니다.</p>
<p>하지만 Java로 돌아와 보니 생성자는 단순히 객체를 생성하기 위한 문법이 아니었습니다.</p>
<p>생성자는</p>
<blockquote>
<p>객체를 어떤 상태로 만들 것인지
그리고 누가 어떤 방식으로 객체를 생성할 것인지를 정의하는 핵심 구조</p>
</blockquote>
<p>였습니다.</p>
<p>그리고 기본 생성자와 파라미터 생성자 역시 중복된 존재가 아니라</p>
<p>각각 <strong>프레임워크를 위한 생성자</strong>와 <strong>개발자를 위한 생성자</strong>라는 서로 다른 역할을 가지고 있었습니다.</p>
<p>앞으로는 생성자를 볼 때 단순히 문법으로 받아들이기보다</p>
<p><strong>&quot;이 객체는 누가, 어떤 방식으로 생성하는가?&quot;</strong></p>
<p>를 먼저 생각해보려 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스가 없던 시절엔 어떻게 데이터를 저장했을까?]]></title>
            <link>https://velog.io/@fire_918/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EA%B0%80-%EC%97%86%EB%8D%98-%EC%8B%9C%EC%A0%88%EC%97%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A0%80%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@fire_918/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4%EA%B0%80-%EC%97%86%EB%8D%98-%EC%8B%9C%EC%A0%88%EC%97%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%A0%80%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Tue, 02 Jun 2026 01:24:33 GMT</pubDate>
            <description><![CDATA[<h3 id="데이터베이스는-왜-등장하게-되었을까">데이터베이스는 왜 등장하게 되었을까?</h3>
<p>프로젝트를 하면서 데이터베이스는 너무나 당연한 존재였습니다.</p>
<p>회원 정보를 저장할 때도 게시글을 조회할 때도 별다른 고민 없이 MySQL과 JPA를 사용했습니다.</p>
<p>그러다 문득 이런 생각이 들었습니다.</p>
<blockquote>
<p>데이터베이스가 없던 시절에는 데이터를 어떻게 관리했을까?
그리고 왜 지금은 대부분의 시스템이 데이터베이스를 사용하는 걸까?</p>
</blockquote>
<p>생각보다 이 질문에 명확하게 답하기는 어려웠습니다.</p>
<p>그래서 관련 내용을 찾아보던 중 데이터베이스가 등장하기 전에는 <strong>파일 처리 시스템(File Processing System)</strong> 이라는 방식으로 데이터를 관리했다는 사실을 알게 되었습니다.</p>
<p>돌이켜보면 저는 <strong>데이터베이스를 사용하는 방법만 알고 있었을 뿐</strong> 데이터베이스가 등장하게 된 배경에 대해서는 깊게 생각해본 적이 없었습니다.</p>
<p>그래서 이번 기회에 파일 처리 시스템부터 다시 살펴보게 되었습니다.</p>
<p>이번 글에서는 파일 처리 시스템이 어떤 방식으로 동작했는지, 어떤 한계가 있었는지, 그리고 그 한계를 해결하기 위해 DBMS가 어떻게 등장하게 되었는지 알아보려고 합니다.</p>
<hr>
<h3 id="파일-처리-시스템은-왜-데이터베이스로-대체되었을까">파일 처리 시스템은 왜 데이터베이스로 대체되었을까?</h3>
<p>처음에는 파일 처리 시스템과 데이터베이스의 차이가 크게 느껴지지 않았습니다.</p>
<p>어차피 데이터베이스도 결국 디스크에 데이터를 저장하는데 왜 파일 처리 시스템은 사라지고 DBMS가 등장하게 된 것일까요?</p>
<p>이 궁금증을 해결하기 위해 파일 처리 시스템에 대해 조금 더 찾아보았습니다.</p>
<p>알아보니 두 방식의 가장 큰 차이는 <strong>데이터를 관리하는 주체</strong>에 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/fire_918/post/75ab29f4-06a6-44e1-a434-2ce79fd8f019/image.png" alt=""></p>
<p><del>이해를 돕기 위해 ChatGPT를 활용해 간단한 다이어그램을 생성해 보았습니다.</del></p>
<p>파일 처리 시스템에서는 데이터를 저장하고 조회하는 기능을 <strong>개발자가 직접 구현</strong>해야 했습니다.</p>
<p>원하는 데이터를 찾기 위해 파일을 읽고, 수정하기 위해 파일을 다시 작성하며, 데이터의 일관성까지 직접 관리해야 했습니다.</p>
<p>반면 DBMS는 이러한 작업을 <strong>대신 수행</strong>해 줍니다.</p>
<p>우리가 SQL 한 줄로 데이터를 조회할 수 있는 것도, 트랜잭션을 통해 데이터의 무결성을 보장받을 수 있는 것도 모두 DBMS 덕분입니다.</p>
<p>즉, 파일 처리 시스템은 <strong>데이터를 개발자가 직접 관리하는 방식</strong>이고, DBMS는 <strong>데이터를 전문적으로 관리해 주는 소프트웨어</strong>라고 볼 수 있습니다.</p>
<p>그렇다면 파일 처리 시스템은 실제로 어떤 방식으로 동작했을까요?</p>
<hr>
<h3 id="파일-처리-시스템은-어떻게-데이터를-관리했을까">파일 처리 시스템은 어떻게 데이터를 관리했을까?</h3>
<p>파일 처리 시스템(File Processing System)은 <strong>데이터를 파일에 직접 저장하고 관리하는 방식</strong>입니다.</p>
<p>오늘날에는 데이터베이스에 데이터를 저장한 뒤 SQL을 통해 조회하고 수정합니다.</p>
<p>하지만 파일 처리 시스템에서는 개발자가 직접 파일을 읽고 데이터를 관리해야 했습니다.</p>
<p>예를 들어 회원 정보를 관리한다고 가정해 보겠습니다.</p>
<pre><code class="language-text">1,김철수,20
2,이영희,21
3,박민수,22</code></pre>
<p>회원 정보는 위와 같이 파일에 저장됩니다.</p>
<p>만약 ID가 2인 회원을 조회해야 한다면 어떻게 될까요?</p>
<p>파일 처리 시스템에서는 파일을 열고 데이터를 한 줄씩 읽어가며 원하는 데이터를 직접 찾아야 합니다.</p>
<pre><code class="language-text">1,김철수,20
2,이영희,21 ← 찾았다!
3,박민수,22</code></pre>
<p>회원을 추가하거나 수정하는 작업도 마찬가지입니다.</p>
<p>파일을 직접 읽고, 수정하고, 다시 저장하는 과정까지 모두 개발자가 구현해야 합니다.</p>
<p>즉, 파일 처리 시스템에서는 데이터 저장뿐만 아니라 조회·수정·삭제 로직까지 <strong>개발자가 직접 관리</strong>해야 했습니다.</p>
<p>작은 규모의 프로그램에서는 큰 문제가 없었습니다.</p>
<p>데이터 양이 적고 사용자 수도 많지 않았기 때문입니다.</p>
<p>하지만 프로그램 규모가 커지고 데이터가 증가하면서 파일 처리 시스템은 점점 <strong>한계</strong>를 드러내기 시작했습니다.</p>
<p>이러한 한계는 결국 데이터베이스와 DBMS가 등장하게 된 배경이 되었습니다.</p>
<hr>
<h3 id="dbms는-어떻게-문제를-해결했을까">DBMS는 어떻게 문제를 해결했을까?</h3>
<p>파일 처리 시스템의 가장 큰 문제는 <strong>데이터를 개발자가 직접 관리해야 한다는 점</strong>이었습니다.</p>
<p>프로그램 규모가 작을 때는 큰 문제가 없었지만, 데이터와 사용자가 늘어나면서 다양한 한계가 드러나기 시작했습니다.</p>
<p>DBMS(Database Management System)는 바로 이러한 문제를 해결하기 위해 등장했습니다.</p>
<p><img src="https://velog.velcdn.com/images/fire_918/post/be7de211-2778-4182-8b43-866bb3574784/image.png" alt=""></p>
<p><del>이해를 돕기 위해 ChatGPT를 활용해 간단한 다이어그램을 생성해 보았습니다.</del></p>
<p><strong>1. 데이터 중복 문제 해결</strong></p>
<p>파일 처리 시스템에서는 같은 정보를 여러 파일에 저장하는 경우가 많았습니다.</p>
<p>하지만 DBMS는 데이터를 중앙에서 관리하여 <strong>중복 저장을 최소화</strong>하고, <strong>데이터의 일관성을 유지</strong>할 수 있도록 도와줍니다.</p>
<p><strong>2. 데이터 종속성 완화</strong></p>
<p>파일 처리 시스템에서는 데이터 구조가 변경되면 관련 프로그램도 함께 수정해야 했습니다.</p>
<p>반면 DBMS는 데이터와 프로그램 사이의 의존성을 줄여 <strong>데이터 구조 변경에 유연하게 대응</strong>할 수 있도록 지원합니다.</p>
<p><strong>3. 동시성 문제 해결</strong></p>
<p>여러 사용자가 동시에 같은 데이터를 수정하면 데이터가 손상될 수 있습니다.</p>
<p>DBMS는 <strong>트랜잭션(Transaction)</strong> 과 <strong>잠금(Lock)</strong> 기능을 제공하여 여러 사용자가 동시에 접근하더라도 데이터의 <strong>무결성(Integrity)</strong> 을 보장합니다.</p>
<p><strong>4. 검색 성능 향상</strong></p>
<p>파일 처리 시스템에서는 원하는 데이터를 찾기 위해 파일 전체를 순차적으로 탐색해야 하는 경우가 많았습니다.</p>
<p>하지만 DBMS는 <strong>인덱스(Index)</strong> 를 활용하여 필요한 데이터를 빠르게 조회할 수 있습니다.</p>
<p>데이터가 많아질수록 이러한 성능 차이는 더욱 커집니다.</p>
<p><strong>5. 다양한 관리 기능 제공</strong></p>
<p>DBMS는 단순히 데이터를 저장하는 역할만 하지 않습니다.</p>
<ul>
<li>권한 관리(Security)</li>
<li>백업 및 복구(Recovery)</li>
<li>데이터 무결성 관리</li>
<li>동시성 제어(Concurrency Control)</li>
</ul>
<p>와 같은 기능을 제공하여 데이터를 더욱 안전하고 효율적으로 관리할 수 있도록 도와줍니다.</p>
<p>DBMS의 등장으로 인해 개발자는 파일 자체를 관리하는 데 시간을 쓰기보다 <strong>비즈니스 로직 구현에 집중</strong>할 수 있게 되었습니다.</p>
<hr>
<h3 id="마무리하며">마무리하며</h3>
<p>이번 글을 작성하면서 가장 크게 느낀 점은 저는 데이터베이스를 사용만 하고 있었다는 점이었습니다.</p>
<p>평소에는 MySQL과 JPA를 너무 당연하게 사용했지만 그 뒤에는 파일 처리 시스템의 한계를 해결하기 위한 수많은 고민이 숨어 있었습니다.</p>
<p>앞으로는 새로운 기술을 공부할 때 단순히 사용 방법만 익히기보다 <strong>어떤 문제를 해결하기 위해 등장했는지</strong> 먼저 생각해 보려고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java 예외, 제대로 이해한 적 있나요?]]></title>
            <link>https://velog.io/@fire_918/Java-%EC%98%88%EC%99%B8-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%9C-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@fire_918/Java-%EC%98%88%EC%99%B8-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%9C-%EC%A0%81-%EC%9E%88%EB%82%98%EC%9A%94</guid>
            <pubDate>Mon, 20 Apr 2026 11:11:16 GMT</pubDate>
            <description><![CDATA[<h3 id="java-예외의-구조">Java 예외의 구조</h3>
<p><img src="https://velog.velcdn.com/images/fire_918/post/0cf31d07-c104-474c-9f78-757ac6f68a06/image.png" alt=""></p>
<p><del><em>직접 그린 구조라 틀린 부분이 있다면 피드백 환영합니다.</em></del></p>
<p>먼저 Java의 예외는 크게 <strong>Error</strong>와 <strong>Exception</strong>으로 나뉩니다.</p>
<p>Error는 JVM 수준에서 발생하는 <strong>비정상적인 상황</strong>입니다.
예를 들어 <code>OutOfMemoryError</code>처럼 힙 메모리가 고갈된 경우, 이미 시스템은 정상적으로 동작할 수 없는 상태에 놓이게 됩니다.</p>
<p>따라서 이를 try-catch로 처리하더라도 문제를 복구할 수 없으며,
오히려 잘못된 상태로 계속 실행될 위험이 있습니다.</p>
<p>즉, Error는 애플리케이션 레벨에서 해결할 수 있는 대상이 아니기 때문에
<strong>빠르게 실패하도록 두는 것이 바람직</strong>합니다.</p>
<p>반면 Exception은 개발자가 <strong>구현한 로직에서 발생하는 예외</strong>입니다. 예측 가능하고 별도로 구현하여 처리할 수 있습니다.</p>
<hr>
<h3 id="exception-vs-runtimeexception">Exception vs RuntimeException</h3>
<p>Exception은 다시 두 가지로 나뉩니다.</p>
<p>여기서 기준이 되는 것이 바로 <strong>RuntimeException</strong>입니다.</p>
<p>Exception (RuntimeException 제외)
→ <strong>Checked Exception</strong></p>
<p>RuntimeException 및 하위 클래스
→ <strong>Unchecked Exception</strong></p>
<p>즉, 단순한 구조적 구분이 아니라
→ RuntimeException을 기준으로 예외의 동작 방식이 나뉘게 됩니다.</p>
<hr>
<p>그렇다면 여기서 한 가지 의문이 생길 수 있습니다.</p>
<blockquote>
<p>왜 굳이 RuntimeException을 따로 두었을까?</p>
</blockquote>
<p>RuntimeException은 단순히 구조적으로 분리된 것이 아니라,
<strong>예외의 성격이 다르기 때문에 구분된 개념</strong>입니다.</p>
<p>Java는 예외를 다음과 같이 바라봅니다.</p>
<ul>
<li><p><strong>Checked Exception</strong>
→ 정상적인 상황에서도 발생할 수 있는 실패
→ 반드시 대비해야 하는 예외</p>
</li>
<li><p><strong>RuntimeException</strong>
→ 잘못된 코드나 로직에서 발생하는 문제
→ 처리하기보다 수정해야 하는 대상</p>
</li>
</ul>
<p>만약 모든 예외를 Checked Exception으로 강제했다면,
간단한 코드에서도 불필요한 try-catch가 반복되며
코드가 과도하게 복잡해졌을 것입니다.</p>
<p>그래서 Java는 RuntimeException을 통해</p>
<blockquote>
<p>이 예외는 굳이 강제하지 않고 개발자에게 맡기겠다.</p>
</blockquote>
<p>라는 선택을 한 것입니다.</p>
<hr>
<h3 id="checked-exception은-언제-사용이-될까">Checked Exception은 언제 사용이 될까?</h3>
<p>Spring/Java로 개발하다 보면 Checked Exception을 직접 다루는 경우가 많지 않습니다.<br>그렇다고 해서 사용되지 않는 것이 아니라 <strong>프레임워크가 대신 처리</strong>하고 있는 경우가 대부분입니다.</p>
<p>Checked Exception의 목적은 다음과 같습니다.</p>
<blockquote>
<p>이 상황은 반드시 대비해야 한다는 것을 컴파일 단계에서 강제하기 위함</p>
</blockquote>
<p>예를 들어 파일을 읽는 경우 다음과 같은 상황이 발생할 수 있습니다.</p>
<ul>
<li>파일이 존재하지 않음</li>
<li>접근 권한이 없음</li>
<li>읽는 도중 오류 발생</li>
</ul>
<p>이러한 상황은 코드의 버그가 아니라 정상적인 실행 과정에서도 발생할 수 있는 실패입니다.<br>그래서 Java는 이를 Checked Exception으로 정의하고 <strong>처리를 강제</strong>합니다.</p>
<p>하지만 실제 개발에서는 이러한 강제가 잘 체감되지 않습니다.<br>그 이유는 <strong>프레임워크</strong>가 Checked Exception을 <strong>내부에서 처리</strong>하기 때문입니다.</p>
<p>예를 들어 Spring에서는 JDBC의 <code>SQLException</code>과 같은 Checked Exception을 내부에서 처리한 뒤<br><code>RuntimeException</code>으로 변환하여 다시 던집니다.</p>
<p>이로 인해 개발자는 Checked Exception을 직접 다루지 않아도 되며 
복잡한 <code>try-catch</code>나 <code>throws</code> 전파 없이 <strong>비즈니스 로직에 집중</strong>할 수 있습니다.</p>
<hr>
<h3 id="마무리하며">마무리하며</h3>
<p>처음에는 Checked Exception과 Unchecked Exception의 차이가 잘 와닿지 않았습니다.
특히 Spring으로 개발하면서 Checked Exception을 직접 다룬 경험이 거의 없었기 때문입니다.</p>
<p>그래서 단순히</p>
<blockquote>
<p>Checked는 외부, Unchecked는 내부 정도로만 막연하게 이해하고 있었던 것 같습니다.</p>
</blockquote>
<p>하지만 이번에 정리하면서 알게 된 것은 이 두 개념의 큰 차이는</p>
<blockquote>
<p>컴파일러가 예외 처리를 강제하느냐의 여부</p>
</blockquote>
<p>라는 점이었습니다.</p>
<p>또한 RuntimeException이 따로 존재하는 이유도 이해할 수 있었습니다.
모든 예외를 강제로 처리하게 만들면 코드가 지나치게 복잡해지기 때문에
Java는 일부 예외에 대해서는 개발자에게 책임을 맡기는 방향을 선택한 것입니다.</p>
<p>그리고 Spring을 사용하면서 Checked Exception을 거의 보지 못했던 이유 역시
프레임워크가 내부에서 이를 처리하고 RuntimeException으로 변환해주기 때문이었습니다.</p>
<p>원래는 예외를 단순히 <strong>발생할 것 같은 비즈니스 로직에 추가하는 것</strong> 정도로만 생각했지만
하지만 이번에 예외 구조를 깊게 정리해보면서 단순한 처리 이상의 의미가 있다는 것을 알게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PR 쌓이는 이유, 알고 계셨나요?]]></title>
            <link>https://velog.io/@fire_918/PR-%EC%8C%93%EC%9D%B4%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%95%8C%EA%B3%A0-%EA%B3%84%EC%85%A8%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@fire_918/PR-%EC%8C%93%EC%9D%B4%EB%8A%94-%EC%9D%B4%EC%9C%A0-%EC%95%8C%EA%B3%A0-%EA%B3%84%EC%85%A8%EB%82%98%EC%9A%94</guid>
            <pubDate>Sun, 19 Apr 2026 15:03:02 GMT</pubDate>
            <description><![CDATA[<h3 id="어느-순간-pr이-쌓이기-시작했습니다">어느 순간 PR이 쌓이기 시작했습니다.</h3>
<p>프로젝트를 진행하다 보니<br>어느 순간부터 머지되지 않은 PR들이 하나둘씩 쌓이기 시작했습니다.</p>
<p>PR을 올리고 리뷰를 기다리는 과정이 반복되면서<br>대기 중인 PR이 점점 늘어나는 상황이었습니다.</p>
<p>처음에는  </p>
<blockquote>
<p>리뷰가 조금 늦어지고 있네<br>정도로만 생각했습니다.</p>
</blockquote>
<p>그때는 단순히 일정이나 상황의 문제라고 넘겼습니다.</p>
<p>하지만 비슷한 상황이 반복되면서<br>점점 이상하다는 생각이 들기 시작했습니다.</p>
<hr>
<h3 id="코드리뷰는-당연한-과정이라고-생각했습니다">코드리뷰는 당연한 과정이라고 생각했습니다.</h3>
<p>저는 코드리뷰를 중요하게 생각했습니다.<br>프로젝트를 한다면 서로의 코드를 확인하고 피드백을 주고받는 과정은<br>필수라고 느꼈습니다.</p>
<p>채용 사이트를 살펴봐도 많은 기업에서 코드리뷰 문화를 강조하고 있었고,<br>저 역시 이를 중요한 역량 중 하나로 받아들이고 있었습니다.</p>
<p>그래서 코드리뷰를 할 때는 모든 코드를 직접 확인했습니다.<br>PR이 올라오면 코드를 하나씩 읽고, 코멘트를 남기고, 수정 요청을 했습니다.</p>
<p>중요한 로직이든, 단순한 코드든 구분 없이 비슷한 수준으로 확인했습니다.</p>
<blockquote>
<p>모든 코드를 꼼꼼히 보고 피드백 하는 것이 좋은 리뷰다.</p>
</blockquote>
<p>이게 제가 가지고 있던 기준이었습니다.</p>
<p>그리고 이 방식이 문제라고는 한 번도 생각해본 적이 없었습니다.</p>
<hr>
<h3 id="코드는-빨라졌지만-리뷰는-그대로였습니다">코드는 빨라졌지만, 리뷰는 그대로였습니다</h3>
<p>곰곰이 생각해보니 문제는 구조였습니다.</p>
<p>코드는 계속 생성되고 있고,<br>특히 AI를 사용하면서 코드 작성 속도는 훨씬 빨라졌습니다.</p>
<p>하지만 코드리뷰는<br><strong>사람이 직접 읽고 이해해야 하는 과정</strong>이었습니다.</p>
<blockquote>
<p>코드 생성 속도 ↑<br>리뷰 속도 → (변하지 않음)</p>
</blockquote>
<p>이 구조에서는 결과가 뻔했습니다.</p>
<blockquote>
<p>리뷰는 밀리고, PR은 쌓인다.</p>
</blockquote>
<p>결국 PR이 쌓이는 건<br>개인의 문제가 아니라 구조적인 문제였습니다.</p>
<p>그리고 그 원인은<br><strong>모든 코드를 사람이 직접 확인하려는 방식</strong>에 있었습니다.</p>
<p>코드의 양이 적을 때는 괜찮았지만,<br>규모가 커질수록 이 방식은 유지하기 어려웠습니다.</p>
<p>특히 AI로 코드 생성 속도가 빨라진 지금은<br>더 이상 맞지 않는 방식일 수도 있다고 느꼈습니다.</p>
<blockquote>
<p>모든 코드를 사람이 본다”는 가정 자체가 한계였습니다.</p>
</blockquote>
<hr>
<h3 id="그래서-코드리뷰-방식을-바꿔보려고-합니다">그래서 코드리뷰 방식을 바꿔보려고 합니다.</h3>
<p>이제는 사람이 모든 코드를 하나하나 직접 검증하는 방식에서 벗어나<br><strong>AI를 활용해 코드리뷰를 보조하는 방향</strong>이 필요하다고 느꼈습니다.</p>
<p>기본적인 오류나 코드 스타일, 반복적인 패턴은 AI가 먼저 빠르게 검증하고,
사람은 그 결과를 바탕으로 전체 흐름과 맥락을 함께 확인하는 방식입니다.</p>
<blockquote>
<p>AI는 빠르게 훑고, 사람은 맥락을 본다.</p>
</blockquote>
<p>여기서 중요한 건 사람이 리뷰에서 빠지는 것이 아니라 <strong>각자의 역할이 더 명확해진다는 점</strong>입니다.</p>
<hr>
<p><strong>AI의 역할</strong></p>
<ul>
<li>코드의 문법적인 오류나 패턴 </li>
<li>기본적인 문제들을 빠르게 검증함</li>
</ul>
<hr>
<p><strong>사람의 역할</strong></p>
<ul>
<li>이 코드가 실제로 맞는 방향인지,  </li>
<li>구조적으로 더 나은 선택은 없는지,  </li>
<li>장기적으로 유지보수가 가능한지를 판단함</li>
</ul>
<hr>
<p>즉, 사람은 여전히 코드를 전체적으로 확인하지만 단순한 검증보다는 <strong>맥락과 판단이 필요한 영역에 더 집중하게 됩니다.</strong></p>
<p>앞으로는 코드리뷰를 단순한 확인 과정이 아니라 <strong>흐름을 관리하는 과정</strong>으로 가져가려고 합니다.</p>
<ul>
<li>AI로 1차 검증을 진행하고  </li>
<li>사람은 전체 맥락과 설계를 함께 확인하고  </li>
<li>리뷰 속도와 품질을 균형 있게 가져갑니다.  </li>
</ul>
<p>결국 중요한 건 얼마나 많이 리뷰하느냐가 아니라 <strong>어떻게 효율적으로 리뷰하느냐</strong>입니다.</p>
<blockquote>
<p>PR이 쌓이지 않게 하려면, 리뷰 방식부터 바뀌어야 합니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로시저와 함수의 차이]]></title>
            <link>https://velog.io/@fire_918/%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80%EC%99%80-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@fire_918/%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80%EC%99%80-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Sun, 12 Apr 2026 07:43:25 GMT</pubDate>
            <description><![CDATA[<h3 id="프로시저와-함수-왜-헷갈릴까">프로시저와 함수 왜 헷갈릴까?</h3>
<p>절차지향 프로그래밍을 공부하던 중 프로시저(Procedure)라는 용어를 접하게 되었습니다. 하나의 동작이나 행위를 묶은 개념이라고 하여 자연스럽게 “그럼 함수(Function)랑 같은 거 아닌가?”라는 생각이 들었습니다. 하지만 자료를 찾아보니 둘은 완전히 같은 개념은 아니었고 미묘하지만 중요한 차이가 존재했습니다. 이 글은 그 궁금증에서 출발해 프로시저와 함수의 차이를 정리해보려 합니다.</p>
<hr>
<h3 id="프로시저procedure란-무엇인가">프로시저(Procedure)란 무엇인가</h3>
<p>프로시저는 <strong>특정 작업</strong>을 수행하기 위해 묶어놓은 코드의 집합을 의미합니다.
쉽게 말해 하나의 <strong>행동</strong>을 정의해놓은 것이라고 볼 수 있습니다.</p>
<p>예를 들어 화면에 문장을 출력하거나 어떤 값을 변경하는 동작처럼 특정 기능을 실행하는 코드 덩어리가 바로 프로시저입니다.</p>
<pre><code class="language-java">void printHello() {
    System.out.println(&quot;Hello&quot;);
}</code></pre>
<p>위 코드에서 printHello()는 “Hello를 출력한다”는 하나의 행동을 수행하는 프로시저입니다.</p>
<p>프로시저의 핵심은 값을 반환하는 것보다 <strong>동작을 수행</strong>하는 것에 초점이 맞춰져 있다는 점입니다.
즉, 결과를 돌려주는 것보다 어떤 일을 <strong>실행</strong>하는 것이 목적입니다.</p>
<p>이러한 특징 때문에 절차지향 프로그래밍에서는 여러 프로시저를 만들어두고 이를 순서대로 실행하며 프로그램의 흐름을 구성하게 됩니다.</p>
<hr>
<h3 id="함수function란-무엇인가">함수(Function)란 무엇인가</h3>
<p>Java로 코드를 작성하면서 “함수”라는 개념을 깊게 생각해본 적은 없었습니다. 실제로는 항상 “메서드”라는 용어만 사용해왔기 때문에 함수와 메서드의 차이에 대해서도 크게 고민하지 않았습니다. 그래서 처음에는 함수와 메서드가 같은 개념이라고 생각했습니다.</p>
<p>하지만 개념적으로 살펴보니 함수는 입력값을 받아 처리한 뒤 <strong>결과를 반환</strong>하는 것에 초점을 둔 개념이었습니다. 즉, 단순히 어떤 동작을 수행하는 것 뿐만이 아닌 값을 만들어내는 역할을 합니다.</p>
<p>예를 들어 두 값을 더하는 기능은 다음과 같이 표현할 수 있습니다.</p>
<pre><code class="language-java">int add(int a, int b) {
    return a + b;
}</code></pre>
<p>이처럼 함수는 입력값을 받아 계산을 수행하고 그 결과를 반환하는 구조를 가집니다.</p>
<p>다만 Java에서는 이러한 형태 역시 모두 클래스 내부에 정의되기 때문에 실제로는 “함수”라는 용어보다 <strong>메서드(Method)</strong> 라는 표현을 사용하게 됩니다. 이 때문에 실무에서는 함수와 메서드의 구분이 크게 드러나지 않으며 자연스럽게 두 개념을 혼용하게 되는 경우가 많습니다.</p>
<hr>
<h3 id="프로시저와-함수의-핵심-차이">프로시저와 함수의 핵심 차이</h3>
<p>앞서 살펴본 것처럼 프로시저와 함수는 모두 <strong>특정 작업을 수행하는 코드의 집합</strong>이라는 공통점을 가집니다. 그렇다면 이 둘을 구분하는 기준은 무엇일까요?</p>
<p>가장 큰 차이는 <strong>값을 반환하느냐</strong> 에 있습니다.</p>
<p>프로시저는 특정 동작을 수행하는 것 자체에 목적이 있기 때문에 반드시 값을 반환할 필요는 없습니다. 반면 함수는 입력값을 받아 처리한 뒤 그 결과를 반환하는 데에 초점이 맞춰져 있습니다.</p>
<p>이를 코드로 비교해보면 다음과 같습니다.</p>
<pre><code class="language-java">void printHello() {
    System.out.println(&quot;Hello&quot;);
}</code></pre>
<pre><code class="language-java">int add(int a, int b) {
    return a + b;
}</code></pre>
<p>첫 번째 코드는 단순히 “Hello”를 출력하는 동작을 수행하는 프로시저이고 두 번째 코드는 두 값을 더한 결과를 반환하는 함수입니다.</p>
<p><del><em>함수는 값을 반환하는 프로시저라 볼 수 있어 두 번째 코드도 프로시저에도 포함이 되긴 합니다 ㅎㅎ</em></del></p>
<p>정리하자면 프로시저는 <strong>어떤 일을 수행하는 것</strong>, 함수는 <strong>결과 값을 만들어내는 것</strong> 에 더 초점을 둔다고 볼 수 있습니다.</p>
<hr>
<h3 id="java에서는-왜-구분이-흐려질까">Java에서는 왜 구분이 흐려질까</h3>
<p>앞서 살펴본 것처럼 이론적으로는 프로시저와 함수가 <strong>반환값 유무</strong>에 따라 구분됩니다. 하지만 Java에서는 이러한 구분이 거의 드러나지 않습니다. 그 이유는 Java가 <strong>객체지향 프로그래밍 언어</strong>이기 때문입니다.</p>
<p>Java에서는 모든 동작이 반드시 클래스 내부에 정의되어야 합니다. 즉, 어떤 기능을 구현하더라도 독립적인 함수나 프로시저 형태로 존재할 수 없고 항상 특정 클래스에 속한 형태로 작성됩니다.</p>
<pre><code class="language-java">class Calculator {
    void printHello() {   // 프로시저 st
        System.out.println(&quot;Hello&quot;);
    }

    int add(int a, int b) {  // 함수 st
        return a + b;
    }
}</code></pre>
<p>위 코드에서 printHello와 add는 각각 프로시저와 함수의 특징을 가지고 있지만 Java에서는 둘 다 단순히 메서드(Method)로 취급됩니다.</p>
<p>이처럼 Java에서는 반환값이 있는 경우와 없는 경우 모두를 하나의 개념으로 묶어 메서드라고 부르기 때문에 프로시저와 함수의 구분이 자연스럽게 흐려지게 됩니다.</p>
<p>결국 Java에서는 함수냐 프로시저냐를 구분하는 것보다,
<strong>“이 메서드가 어떤 객체의 책임인가”</strong> 를 고민하는 것이 더 중요해집니다.</p>
<hr>
<h3 id="그래서-절차지향에서-프로시저란">그래서 절차지향에서 프로시저란?</h3>
<p>지금까지 내용을 정리해보면 절차지향 프로그래밍에서의 프로시저는 <strong>프로그램의 흐름을 구성</strong>하는 기본 단위라 할 수 있습니다.</p>
<p>절차지향에서는 프로그램을 하나의 <strong>큰 흐름</strong>으로 보고 이를 여러 개의 프로시저로 나눈 뒤 순서대로 실행하는 방식으로 동작합니다. 즉, 각 프로시저는 <strong>특정 기능(행동)</strong> 을 담당하고 이들이 모여 <strong>전체 로직</strong>을 구성하게 됩니다.</p>
<p>예를 들어 다음과 같은 구조를 생각해볼 수 있습니다.</p>
<pre><code class="language-java">checkBalance();
withdraw(500);
checkBalance();</code></pre>
<p>이처럼 절차지향에서는 “무엇을 할 것인가”를 순서대로 나열하고 각각의 프로시저를 호출하면서 프로그램이 진행됩니다.</p>
<p>또한 중요한 특징은 이러한 프로시저들이 데이터와 분리되어 독립적으로 존재한다는 점입니다.
즉, 데이터를 중심으로 묶는 것이 아니라 동작(프로시저)의 흐름을 중심으로 프로그램이 구성됩니다.</p>
<p>결국 절차지향에서 프로시저란 단순한 함수가 아니라 프로그램의 흐름을 이루는 <strong>실행 단위</strong>이며 이들을 어떤 순서로 호출하느냐가 프로그램의 핵심이 됩니다.</p>
<hr>
<h3 id="정리하며">정리하며</h3>
<p>이번 글은 절차지향 프로그래밍을 공부하는 과정에서 등장한 “프로시저”라는 개념에 대한 궁금증에서 시작되었습니다. 처음에는 함수와 같은 개념이라고 생각했지만 살펴보면서 두 개념 사이에는 분명한 차이가 있다는 것을 알게 되었습니다.</p>
<p>프로시저는 특정 동작을 수행하는 데 초점을 둔 개념이고 함수는 값을 반환하는 데 초점을 둔 개념입니다. 다만 Java와 같은 객체지향 언어에서는 이러한 구분이 크게 드러나지 않고 모든 기능이 메서드라는 형태로 통합되어 사용됩니다.</p>
<p>또한 메서드는 객체에 속한 동작인 반면 프로시저는 독립적으로 존재하는 동작이라는 차이가 있습니다.</p>
<p>결국 중요한 것은 용어의 차이 자체보다는
프로그램을 어떤 기준으로 나누고 설계하느냐에 있습니다.</p>
<p>절차지향에서는 프로시저를 중심으로 흐름을 구성하고
객체지향에서는 객체와 그 안의 메서드를 중심으로 역할을 나누게 됩니다.</p>
<p>이러한 관점의 차이를 이해하는 것이 이후 객체지향 설계나 구조를 학습하는 데 중요한 기반이 된다고 생각합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis에 대한 나의 첫 오해]]></title>
            <link>https://velog.io/@fire_918/Redis%EC%97%90-%EB%8C%80%ED%95%9C-%EB%82%98%EC%9D%98-%EC%B2%AB-%EC%98%A4%ED%95%B4</link>
            <guid>https://velog.io/@fire_918/Redis%EC%97%90-%EB%8C%80%ED%95%9C-%EB%82%98%EC%9D%98-%EC%B2%AB-%EC%98%A4%ED%95%B4</guid>
            <pubDate>Thu, 09 Apr 2026 11:10:15 GMT</pubDate>
            <description><![CDATA[<h3 id="아무-생각-없이-사용한-redis">아무 생각 없이 사용한 Redis</h3>
<p>Redis를 처음 사용한 건 Refresh Token 저장 때문이었습니다.
JWT 기반 인증 구조를 구성하면서 Refresh Token을 어디에 저장할지 고민했고, 많은 예제에서 Redis를 사용하고 있었기 때문에 별다른 의심 없이 그대로 도입했습니다.</p>
<p><img src="https://velog.velcdn.com/images/fire_918/post/de5b4c65-46ed-419e-98bb-db47fde920ae/image.png" alt=""></p>
<p><em>간략히 위와 같은 흐름으로 도입했었습니다.</em></p>
<p>당시에는 Redis가 왜 필요한지 깊게 생각하지 않았습니다. 그저 key-value 형태로 데이터를 저장하는 NoSQL 데이터베이스 정도로만 이해하고 있었고, Refresh Token을 저장하는 용도 외에는 활용할 수 있다는 생각도 하지 못했습니다.</p>
<p>이때의 Redis는 단순히 &quot;빠른 저장소&quot; 정도의 인식이었습니다. 관계형 데이터베이스 대신 사용할 수 있는 또 하나의 데이터베이스라고 생각했고, 실제로도 Refresh Token을 저장하고 조회하는 용도로만 사용했습니다.</p>
<p>하지만 Redis를 다시 공부하면서, 내가 Redis를 꽤 단편적으로 이해하고 있었다는 걸 알게 되었습니다. Redis는 단순한 NoSQL 저장소가 아니라 인메모리 기반의 자료구조 저장소였고, 캐시, 세션, 큐, 랭킹 시스템 등 다양한 상황에서 활용되는 도구였습니다.</p>
<p>이 글에서는 Refresh Token 저장소로만 사용하던 Redis에 대해, 공부하면서 새롭게 이해하게 된 내용들을 정리해보려 합니다.</p>
<hr>
<h3 id="redis는-인메모리-데이터-저장소였다">Redis는 인메모리 데이터 저장소였다</h3>
<p><img src="https://velog.velcdn.com/images/fire_918/post/9d693e18-9bac-49e9-ae5b-f33af556bab6/image.png" alt=""></p>
<p>Redis를 이해하려면 먼저 &quot;인메모리&quot;라는 개념부터 알아야 합니다. 인메모리는 데이터를 디스크가 아니라 메모리(RAM)에 저장하는 방식을 의미합니다.</p>
<p>일반적인 데이터베이스는 데이터를 디스크에 저장합니다. 디스크는 전원이 꺼져도 데이터가 유지되는 장점이 있지만, 데이터를 읽고 쓰는 속도가 메모리에 비해 상대적으로 느립니다. 반면 인메모리 방식은 데이터를 RAM에 저장하기 때문에 훨씬 빠른 속도로 접근할 수 있습니다.</p>
<p>즉, Redis는 데이터를 디스크가 아니라 메모리에 저장하는 데이터 저장소입니다. 이 구조 때문에 Redis는 일반적인 데이터베이스보다 빠른 성능을 제공할 수 있습니다. 대신 메모리에 저장되기 때문에 영구 저장소보다는 캐시, 세션, 토큰과 같은 일시적인 데이터를 처리하는 데 더 적합합니다.</p>
<hr>
<h3 id="redis가-빠른-이유">Redis가 빠른 이유</h3>
<p>Redis가 빠른 가장 큰 이유는 메모리에 데이터를 저장하기 때문입니다. RAM은 디스크보다 훨씬 빠르게 데이터에 접근할 수 있고, 별도의 디스크 I/O 과정이 필요하지 않습니다.</p>
<p>일반적인 데이터베이스의 경우 데이터를 조회할 때 디스크 접근이 발생합니다. 이 과정은 상대적으로 시간이 오래 걸리며, 요청이 많아질수록 성능에 영향을 줍니다. 반면 Redis는 메모리에 저장된 데이터를 바로 조회할 수 있기 때문에 매우 빠른 응답 속도를 제공합니다.</p>
<p>또한 Redis는 단순한 구조로 데이터를 저장하기 때문에 추가적인 연산 비용이 적습니다. 복잡한 조인이나 쿼리 파싱 과정 없이 key 기반으로 바로 접근할 수 있어 성능상 이점이 큽니다.</p>
<p>이러한 이유로 Redis는 캐시, 세션 저장소, 토큰 저장소와 같이 빠른 조회가 중요한 상황에서 자주 사용됩니다.</p>
<hr>
<h3 id="ram은-왜-디스크보다-빠를까">RAM은 왜 디스크보다 빠를까?</h3>
<p>RAM은 메모리 기반 저장소이기 때문에 CPU가 데이터를 바로 읽고 쓸 수 있습니다. 디스크는 데이터를 읽거나 쓰기 위해 물리적인 이동(헤드 이동, 회전 등)이 필요하고, 이 과정이 RAM보다 훨씬 느립니다. 그래서 같은 데이터를 다룰 때 RAM은 디스크보다 접근 속도가 훨씬 빠릅니다.</p>
<hr>
<h3 id="redis는-단순-key-value-저장소가-아니었다">Redis는 단순 Key-Value 저장소가 아니었다</h3>
<p>처음에는 Redis를 단순한 key-value 저장소라고 생각했지만, 실제로는 다양한 자료구조를 지원하는 데이터 저장소였습니다. Redis는 value에 단순 문자열뿐만 아니라 List, Set, Hash, Sorted Set 같은 자료구조를 저장할 수 있습니다.</p>
<p>이 구조 덕분에 Redis는 단순히 데이터를 저장하는 용도를 넘어 다양한 방식으로 활용할 수 있습니다. 예를 들어 List는 큐처럼 사용할 수 있고, Set은 중복 제거 용도로 사용할 수 있으며, Sorted Set은 점수 기반 랭킹 시스템을 구현할 때 사용할 수 있습니다.</p>
<p>즉 Redis는 단순한 저장소가 아니라 자료구조 기반으로 데이터를 처리할 수 있는 도구에 가깝습니다. 이 점은 일반적인 데이터베이스와 Redis를 구분하는 중요한 특징이었습니다.</p>
<p><em>이 부분은 추후에 구현을 해보며 더 공부해보겠습니다.</em></p>
<hr>
<h3 id="refresh-token-저장소로-redis가-적합한-이유">Refresh Token 저장소로 Redis가 적합한 이유</h3>
<p>이제 다시 처음으로 돌아가 보면, Refresh Token 저장소로 Redis를 사용하는 이유도 자연스럽게 이해할 수 있습니다. Refresh Token은 일정 시간이 지나면 만료되는 일시적인 데이터입니다. 또한 빠른 조회가 필요하고, 만료된 데이터는 자동으로 정리되는 것이 이상적입니다.</p>
<p>Redis는 TTL(Time To Live) 기능을 제공하기 때문에 데이터를 저장할 때 만료 시간을 함께 설정할 수 있습니다. 이렇게 설정된 데이터는 시간이 지나면 자동으로 삭제되기 때문에 별도의 정리 작업이 필요하지 않습니다.</p>
<p>또한 메모리 기반 저장소이기 때문에 토큰 조회 속도가 빠르고, 데이터 구조도 단순하여 인증 흐름에서 효율적으로 사용할 수 있습니다. 이러한 특징 때문에 Refresh Token 저장소로 Redis가 자주 사용된다는 것을 이해하게 되었습니다.</p>
<hr>
<h3 id="redis를-다시-이해하게-되면서">Redis를 다시 이해하게 되면서</h3>
<p>처음에는 Redis를 단순한 NoSQL 데이터베이스라고 생각했습니다. 하지만 Redis는 인메모리 기반으로 동작하며, 다양한 자료구조를 제공하는 데이터 저장소였습니다. 단순히 데이터를 저장하는 역할이 아니라, 성능과 데이터 처리 방식까지 고려된 도구였습니다.</p>
<p>이제는 Redis를 사용할 때 단순히 &quot;빠르니까 쓴다&quot;가 아니라, 어떤 데이터를 어떤 방식으로 처리하기 위해 Redis를 사용하는지 고민하게 되었습니다. Refresh Token 저장소로만 사용하던 Redis를 다시 이해하면서, Redis의 활용 범위가 훨씬 넓다는 것을 알게 되었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주석은 달았는데... 왜 아직도 읽기 어려울까?]]></title>
            <link>https://velog.io/@fire_918/%EC%A3%BC%EC%84%9D%EC%9D%80-%EB%8B%AC%EC%95%98%EB%8A%94%EB%8D%B0...-%EC%99%9C-%EC%95%84%EC%A7%81%EB%8F%84-%EC%9D%BD%EA%B8%B0-%EC%96%B4%EB%A0%A4%EC%9A%B8%EA%B9%8C</link>
            <guid>https://velog.io/@fire_918/%EC%A3%BC%EC%84%9D%EC%9D%80-%EB%8B%AC%EC%95%98%EB%8A%94%EB%8D%B0...-%EC%99%9C-%EC%95%84%EC%A7%81%EB%8F%84-%EC%9D%BD%EA%B8%B0-%EC%96%B4%EB%A0%A4%EC%9A%B8%EA%B9%8C</guid>
            <pubDate>Mon, 06 Apr 2026 13:26:14 GMT</pubDate>
            <description><![CDATA[<h3 id="좋은-주석이란-무엇일까">좋은 주석이란 무엇일까?</h3>
<p>프로젝트를 진행하다 보면 한 번쯤 이런 생각을 하게 됩니다.</p>
<blockquote>
<p>&quot;나중에 유지보수할 때를 대비해서, 주석을 꼼꼼하게 달아두자.&quot;</p>
</blockquote>
<p>그래서 코드 한 줄 한 줄마다 설명을 붙이기 시작합니다. 그런데 어느 날 다시 코드를 열어보면, 주석이 오히려 코드를 읽는 흐름을 끊고 있다는 걸 깨닫게 됩니다. 설명이 많은데 오히려 이해하기가 더 어렵고, 마치 AI가 자동으로 생성한 것처럼 부자연스럽게 느껴지기도 합니다.</p>
<h4 id="그렇다면-주석은-많을수록-좋은-걸까요">그렇다면 주석은 많을수록 좋은 걸까요?</h4>
<p>결론부터 말하면 그렇지 않습니다. 좋은 주석은 양이 아니라 <strong>위치와 내용</strong>이 결정합니다. 코드가 스스로 말할 수 있는 부분까지 주석으로 덮어버리면, 오히려 읽어야 할 텍스트만 늘어날 뿐입니다.</p>
<p>그래서 이 글에서는 좋은 주석과 나쁜 주석의 차이, 그리고 실무에서 실제로 쓰이는 주석 스타일까지 정리해보려 합니다.</p>
<hr>
<h3 id="좋은-주석이란">좋은 주석이란?</h3>
<p>좋은 주석의 핵심은 하나입니다. <strong>&quot;무엇&quot;이 아니라 &quot;왜&quot;를 설명한다.</strong></p>
<p>코드는 무엇을 하는지 스스로 말할 수 있어야 합니다. 주석은 코드만으로는 알 수 없는 의도, 맥락, 배경을 전달할 때 비로소 가치가 생깁니다.</p>
<pre><code class="language-python"># 좋지 않은 주석 — 코드를 그대로 번역
i = i + 1  # i에 1을 더한다

# 좋은 주석 — 이유를 설명
i = i + 1  # 인덱스는 0-based이므로 실제 순번 표시를 위해 1 보정</code></pre>
<p>두 줄의 코드는 동일하지만 주석의 질은 완전히 다릅니다. 첫 번째는 코드를 읽을 수 있는 사람이라면 누구나 아는 내용이고, 두 번째는 코드만 봐서는 알 수 없는 <strong>이유</strong>를 담고 있습니다.</p>
<p>좋은 주석은 대체로 이런 상황에서 등장합니다.</p>
<ul>
<li><strong>의도 주석</strong> — 왜 이 방식을 선택했는지 다른 방법을 쓰지 않은 이유</li>
<li><strong>경고 주석</strong> — 이 코드를 건드리면 생길 수 있는 부작용</li>
<li><strong>복잡한 로직 설명</strong> — 비즈니스 규칙이나 수식의 출처와 맥락</li>
<li><strong>TODO / FIXME</strong> — 의도적으로 남긴 미완성과 그 이유</li>
</ul>
<hr>
<h3 id="나쁜-주석은-어떤-형태일까">나쁜 주석은 어떤 형태일까?</h3>
<p>나쁜 주석은 단순히 &quot;없는 것보다 못한&quot; 수준에 그치지 않습니다. 잘못된 주석은 코드를 읽는 사람을 오히려 혼란스럽게 만들고, 유지보수를 더 어렵게 만듭니다.</p>
<p><strong>1. 코드를 그대로 번역한 주석</strong></p>
<pre><code class="language-python"># 리스트를 순회한다
for item in items:
    pass</code></pre>
<p>코드를 읽을 수 있는 사람이라면 이미 아는 내용입니다. 노이즈만 추가될 뿐입니다.</p>
<p><strong>2. 오래되어 코드와 어긋난 주석</strong></p>
<pre><code class="language-python"># 사용자 이름을 반환한다
def get_user_email():
    ...</code></pre>
<p>함수는 바뀌었지만 주석은 그대로입니다. 이런 주석은 없는 것보다 훨씬 위험합니다. 다음 사람이 주석을 믿고 잘못된 방향으로 이해할 수 있기 때문입니다.</p>
<p><strong>3. 주석 처리된 코드</strong></p>
<pre><code class="language-python"># old_function()
# legacy_process(data)</code></pre>
<p>&quot;나중에 쓸 수도 있어서&quot;라는 이유로 남겨두는 경우가 많지만, 그 역할은 Git이 대신합니다. 주석 처리된 코드는 맥락 없이 쌓이면 코드베이스를 읽기 어렵게 만듭니다.</p>
<p><strong>4. 감정이나 넋두리가 담긴 주석</strong></p>
<pre><code class="language-python"># 왜 이렇게 짰는지 모르겠음
# 건드리면 터짐 ㅠ</code></pre>
<p>작성하는 순간엔 속이 시원할 수 있지만, 맥락 없는 불안감만 남깁니다. 
차라리 왜 위험한지를 설명하는 편이 훨씬 낫습니다.</p>
<hr>
<h3 id="실무에서-쓰이는-주석-스타일">실무에서 쓰이는 주석 스타일</h3>
<p><strong>JSDoc / Docstring — 공개 API와 함수에는 필수</strong></p>
<p>팀원이 함수를 사용할 때 코드 내부를 열어보지 않아도 되도록 입력과 출력, 예외 상황을 명시합니다.</p>
<pre><code class="language-javascript">/**
 * 사용자 인증 토큰을 검증합니다.
 * 만료된 토큰은 자동으로 재발급을 시도합니다.
 *
 * @param {string} token - Bearer 토큰
 * @returns {Promise} 인증된 사용자 객체
 * @throws {AuthError} 토큰이 유효하지 않을 경우
 */
async function verifyToken(token) { ... }</code></pre>
<p><strong>인라인 주석 — 복잡한 조건이나 규칙에만</strong></p>
<pre><code class="language-python"># 비밀번호 정책: 대소문자 + 숫자 + 특수문자 각 1개 이상 (보안팀 정책 v2.3)
PASSWORD_REGEX = r&#39;^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&amp;])&#39;</code></pre>
<p>정규식처럼 코드 자체로는 의도를 파악하기 어려운 경우, 왜 이 규칙인지를 한 줄로 남겨두면 나중에 수정할 때 큰 도움이 됩니다.</p>
<p><strong>TODO / FIXME / HACK — 팀 내 컨벤션으로 통일</strong></p>
<p>// TODO(홍길동): 캐싱 레이어 추가 필요 — 현재 매 요청마다 DB 조회 발생
// FIXME: 입력값이 null일 때 크래시 발생 가능, 예외 처리 필요
// HACK: 라이브러리 버그 우회 — v3.2 업그레이드 후 제거 예정</p>
<p>단순히 <code>TODO</code>만 남기는 것보다 <strong>누가 / 왜 / 언제까지</strong>를 함께 적으면 나중에 정리하기 훨씬 수월합니다.</p>
<hr>
<h3 id="마치며">마치며</h3>
<p>주석을 쓰기 전에 스스로에게 한 번 물어보는 습관이 도움이 됩니다.</p>
<blockquote>
<p>이 주석이 없으면 다음 사람이 이 코드를 이해하기 어려울까?
코드 자체를 더 명확하게 고쳐서 주석 없이도 읽힐 수 있을까?
&quot;무엇&quot;이 아니라 &quot;왜&quot;를 설명하고 있나?</p>
</blockquote>
<p>주석은 코드가 말하지 못하는 이야기를 담는 곳입니다. 많이 달수록 좋은 게 아니라, <strong>꼭 필요한 곳에 정확하게</strong> 있을 때 비로소 가치가 생깁니다. 다음 사람이 코드를 읽으며 고마워할 주석, 그게 좋은 주석의 기준이 아닐까요?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM 내부 구조 완벽 이해하기]]></title>
            <link>https://velog.io/@fire_918/JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@fire_918/JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 04 Apr 2026 09:57:21 GMT</pubDate>
            <description><![CDATA[<h3 id="글을-시작하며">글을 시작하며</h3>
<p>최근 코틀린 위주로 개발하다 보니 Java 지식이 점점 희미해지고 있었습니다. 문법만 다시 익히는 것으론 부족하다는 생각이 들었고, Java를 제대로 이해하려면 내부 구조부터 파악해야 한다고 판단했습니다. 이 글에서는 <strong>JVM의 내부 구조</strong>를 중심으로 Java 프로그램이 어떻게 실행되는지 정리해보겠습니다.</p>
<hr>
<h3 id="jdkjava-development-kit의-구성-요소">JDK(Java Development Kit)의 구성 요소</h3>
<p>JDK(Java Development Kit)는 자바 개발에 필요한 전체 도구 모음입니다. JRE와 javac 같은 개발 도구를 포함하고 있으며, 구성 요소 간의 포함 관계는 다음과 같습니다.
<img src="https://velog.velcdn.com/images/fire_918/post/06ab5b57-1207-48b5-8e0b-fac631f716f9/image.png" alt=""></p>
<p><strong>JDK:</strong> 자바 개발 도구 모음
<strong>JRE:</strong> JVM + 자바 표준 라이브러리
<strong>JVM:</strong> 바이트코드 실행 가상 머신
<strong>javac:</strong> .java → .class 바이트코드 변환</p>
<hr>
<h3 id="jvmjava-virtual-machine">JVM(Java Virtual Machine)</h3>
<p>JVM은 자바 바이트코드를 실행하는 가상 머신입니다. 운영체제 위에서 동작하며, Java 프로그램이 어떤 OS에서도 실행될 수 있도록 플랫폼 독립성을 제공합니다.</p>
<p><strong>주요 역할</strong></p>
<ul>
<li>바이트코드 <code>(.class)</code> 실행</li>
<li>메모리 관리 (Heap, Stack 등)</li>
<li>가비지 컬렉션(GC)</li>
<li>인터프리터 &amp; JIT 컴파일 실행</li>
</ul>
<h4 id="jvm이-없는-언어와-비교">JVM이 없는 언어와 비교</h4>
<p>C, C++ 같이 JVM이 없는 언어는 특정 OS에 맞게 빌드된 실행 파일을 생성합니다. 따라서 Mac에서 빌드한 바이너리는 Linux에서 실행되지 않으며, OS마다 별도로 빌드해야 합니다.</p>
<p>반면 Java는 JVM이 중간에서 OS의 차이를 흡수하기 때문에, 한 번 컴파일한 .class 파일을 어떤 환경에서든 동일하게 실행할 수 있습니다.</p>
<hr>
<h3 id="빌드build와-컴파일compile">빌드(Build)와 컴파일(Compile)</h3>
<p>두 개념은 비슷해 보이지만 범위가 다릅니다.</p>
<ul>
<li><strong>컴파일(Compile):</strong> 소스코드 <code>(.java)</code> 를 JVM이 이해할 수 있는 <strong>바이트코드</strong> <code>(.class)</code> 로 변환하는 작업</li>
<li><strong>빌드(Build):</strong> 컴파일 + 테스트 + 라이브러리 포함 + 패키징까지, <strong>실행 가능한 결과물</strong> <code>(.jar)</code> 을 만드는 전체 과정</li>
</ul>
<p>즉, <strong>컴파일은 빌드 과정의 일부</strong>입니다.</p>
<pre><code># 컴파일
javac Hello.java   → Hello.class

# 빌드
./gradlew build   → app.jar</code></pre><p>생성된 app.jar는 .class 파일과 라이브러리, 리소스를 하나로 묶은 배포용 패키지입니다. JVM만 설치되어 있다면 OS에 관계없이 아래 명령 하나로 실행할 수 있습니다.</p>
<pre><code>java -jar app.jar</code></pre><hr>
<h3 id="jvm-내부-흐름">JVM 내부 흐름</h3>
<p>이제부터가 이 글의 핵심입니다. JVM이 바이트코드를 어떻게 실행하고 메모리를 관리하는지, 그 내부 흐름을 단계별로 살펴보겠습니다.</p>
<pre><code>.class (바이트코드)
   ↓
1. Class Loader        → 클래스 정보를 JVM에 적재
   ↓
2. Runtime Data Area   → 메모리에 데이터 저장
   ↓
3. Execution Engine    → 바이트코드 실행
   ├─ 인터프리터
   └─ JIT 컴파일러
   ↓
4. GC (필요 시)        → 불필요한 메모리 정리</code></pre><h4 id="1-class-loader">1. Class Loader</h4>
<p><code>.class</code> 파일을 JVM 메모리에 올리는 역할을 합니다. 프로그램 실행 전에 클래스 정보를 JVM에 적재하며, 필요한 시점에 동적으로 로딩합니다.</p>
<h4 id="2runtime-data-area-jvm-메모리">2.Runtime Data Area (JVM 메모리)</h4>
<p>로드된 클래스와 객체가 실제로 저장되는 공간입니다. 크게 세 영역으로 나뉩니다.</p>
<p><strong>Heap (힙):</strong> 객체와 인스턴스 변수가 저장됩니다. 모든 스레드가 공유하며, GC가 주기적으로 불필요한 메모리를 정리합니다.</p>
<pre><code>User user = new User(); // user 객체는 Heap에 저장</code></pre><p><strong>Stack (스택):</strong> 메서드 실행 시 생성되는 공간으로, 지역 변수가 저장됩니다. 메서드가 종료되면 자동으로 제거되며, 스레드마다 독립적으로 생성됩니다.</p>
<pre><code>void test() {
    int a = 10; // a는 Stack에 저장
}</code></pre><p><strong>Method Area:</strong> 클래스 구조, static 변수, 메서드 코드 등 클래스 수준의 정보가 저장됩니다. 모든 스레드가 공유합니다.</p>
<pre><code>class User {
    static int count = 0; // count는 Method Area에 저장
}</code></pre><h4 id="3execution-engine-실행-엔진">3.Execution Engine (실행 엔진)</h4>
<p>바이트코드를 실제 기계어로 변환해 실행하는 핵심 엔진입니다. 인터프리터와 JIT 컴파일러 두 가지 방식을 조합해 사용합니다.</p>
<blockquote>
<p>javac가 문법 검사와 바이트코드 변환을 담당한다면, Execution Engine은 그 바이트코드를 빠르게 실행하는 것이 목적입니다.</p>
</blockquote>
<p><strong>인터프리터:</strong> 바이트코드를 한 줄씩 읽어 실행합니다. 시작이 빠르지만, 같은 코드를 반복 실행할 때 매번 해석해야 하므로 속도가 느립니다.</p>
<p><strong>JIT 컴파일러:</strong> 자주 실행되는 코드(Hot Spot)를 감지해 기계어로 미리 컴파일합니다. 이후 동일한 코드가 반복될 때는 컴파일된 기계어를 바로 실행하므로 성능이 크게 향상됩니다.</p>
<p>JVM은 이 두 가지를 혼합해 사용합니다. 처음엔 인터프리터로 빠르게 시작하고, 반복 실행이 많은 구간은 JIT로 최적화하는 방식입니다.</p>
<h4 id="4gc-garbage-collector">4.GC (Garbage Collector)</h4>
<p>더 이상 참조되지 않는 Heap 메모리를 자동으로 정리합니다. 개발자가 직접 메모리를 해제할 필요 없이, GC가 주기적으로 동작하며 메모리 누수를 방지하고 안정적인 실행 환경을 유지합니다.</p>
<hr>
<h3 id="마치며">마치며</h3>
<p>JVM의 내부 흐름을 정리하면 다음과 같습니다.</p>
<blockquote>
<p><code>.class</code> → Class Loader → Runtime Data Area → Execution Engine(인터프리터 + JIT) → GC</p>
</blockquote>
<p>JVM이라는 단어는 Java를 처음 배울 때부터 익숙하게 들어왔지만, 막상 내부를 들여다보니 그동안 표면만 알고 있었다는 걸 깨달았습니다. Class Loader부터 GC까지, 각각의 개념이 따로 노는 것이 아니라 하나의 흐름 안에서 맞물려 돌아간다는 것을 이번 정리를 통해 이해할 수 있었습니다.</p>
<p>코틀린을 쓰더라도 결국 JVM 위에서 동작한다는 사실은 변하지 않는 만큼, 이번 정리가 앞으로의 개발에도 단단한 기반이 되어줄 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ERD를 먼저 그려야 하는 이유]]></title>
            <link>https://velog.io/@fire_918/ERD%EB%A5%BC-%EB%A8%BC%EC%A0%80-%EA%B7%B8%EB%A0%A4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@fire_918/ERD%EB%A5%BC-%EB%A8%BC%EC%A0%80-%EA%B7%B8%EB%A0%A4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sat, 03 Jan 2026 12:02:43 GMT</pubDate>
            <description><![CDATA[<h3 id="erd를-먼저-그려야-하는-이유">ERD를 먼저 그려야 하는 이유</h3>
<p>처음엔 이런 생각을 했습니다.</p>
<blockquote>
<p>시간 없는데 그냥 코드 작성부터 해서 테이블 만들고 수정하면 되지 않나?</p>
</blockquote>
<hr>
<h3 id="erd-없이-시작하면-생기는-일">ERD 없이 시작하면 생기는 일</h3>
<p>처음엔 빠르게 진행되는 것처럼 보였습니다.</p>
<pre><code>User 테이블 만들고
Post 테이블 만들고
Comment 테이블 추가하고
엥? 좋아요는 어디 넣지?</code></pre><p><strong>그리고 곧 이런 상황들이 찾아왔습니다</strong></p>
<ul>
<li>외래키 관계가 머릿속에서만 존재함</li>
<li>컬럼 추가할 때마다 영향 범위 파악이 어려움</li>
<li>테이블 이름·관계를 사람마다 다르게 이해하는 문제가 생김</li>
<li><blockquote>
<p>나는 comment를 게시글에 대한 댓글이라 생각하고 구현했지만 다른 사람은 댓글에 대한 답글이라 생각할 수 있음</p>
</blockquote>
</li>
<li><strong>이거 왜 이렇게 설계했지?</strong> 하며 과거의 나 자신과 싸우게 됨</li>
</ul>
<p>ERD를 안 그린 대가는 나중에 <strong>구조적 혼란</strong>으로 돌아왔습니다.</p>
<hr>
<h3 id="erd의-진짜-목적">ERD의 진짜 목적</h3>
<p>많이들 이렇게 생각합니다.</p>
<blockquote>
<p>ERD = 문서용, 형식적인 산출물</p>
</blockquote>
<p>하지만 실제로 느낀 목적은 달랐습니다.</p>
<blockquote>
<p>관계를 시각화해서 사고 비용을 줄이는 도구</p>
</blockquote>
<p>ERD는 코드를 대신하는 게 아니라 <strong>코드보다 먼저 생각을 정리하는 수단</strong>입니다.</p>
<hr>
<h3 id="erd를-그리면-달라지는-것들">ERD를 그리면 달라지는 것들</h3>
<p><strong>관계가 강제됩니다</strong></p>
<ul>
<li>1:N인지, N:M인지, 중간 테이블이 필요한지 코드 작성 전에 실수가 걸러집니다.</li>
</ul>
<p><strong>도메인 이해가 빨라집니다</strong></p>
<ul>
<li>새로 온 팀원이 ERD를 보면 서비스 구조를 빠르게 파악할 수 있습니다.</li>
</ul>
<p><strong>수정 비용이 줄어듭니다</strong></p>
<p>ERD 단계에서 수정하면</p>
<pre><code>선 하나 지우는 것으로 끝</code></pre><p>DB/코드 단계에서 수정하면</p>
<pre><code>마이그레이션
데이터 보정
API 수정
테스트 수정</code></pre><p>초기 설계 비용보다 사후 수정 비용이 훨씬 크다는 걸 직접 경험했습니다.</p>
<hr>
<h3 id="그렇다면-언제-erd를-안-그려도-될까">그렇다면 언제 ERD를 안 그려도 될까?</h3>
<p>솔직히 안 그려도 되는 경우도 있다고 생각합니다.</p>
<ul>
<li>테이블 2~3개짜리 실험용 토이 프로젝트</li>
<li>단기 스크립트성 작업</li>
<li>이미 검증된 구조를 그대로 구현하는 경우</li>
</ul>
<p>이전에 여러 번 구현해봤고 구조가 머릿속에 이미 완성돼 있다면 ERD를 그리는 게 오히려 비용일 수 있습니다.</p>
<p>이런 경우엔 <strong>머릿속 구조로도 충분히 가능</strong>합니다.</p>
<hr>
<h3 id="회고">회고</h3>
<p>이번 경험을 통해 <strong>ERD는 필수 문서가 아니라 필수 사고 과정</strong>이라는 걸 느꼈습니다.
빨리 만들고 싶으면 안 그릴 수 있습니다. 하지만 잘 만들고 싶다면 그려야 한다고 생각합니다.
대부분의 경우 나중에 고치느니 미리 그리는 게 비용·시간·스트레스 모든 면에서 낫습니다.
긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 개발자가 알아야 할 서버와 WAS]]></title>
            <link>https://velog.io/@fire_918/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%84%9C%EB%B2%84%EC%99%80-WAS</link>
            <guid>https://velog.io/@fire_918/%EC%9B%B9-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-%EC%84%9C%EB%B2%84%EC%99%80-WAS</guid>
            <pubDate>Tue, 23 Dec 2025 11:38:46 GMT</pubDate>
            <description><![CDATA[<h3 id="웹-개발을-하다-보면-이런-질문을-자주-접하게-됩니다">웹 개발을 하다 보면 이런 질문을 자주 접하게 됩니다</h3>
<blockquote>
<p>웹 서버와 WAS는 무엇이 다를까?</p>
<p>톰캣은 왜 필요할까?</p>
</blockquote>
<p>이번 글에서는 웹 요청이 클라이언트에서 서버까지 도달하여 처리되는 과정 중에서 서버와 WAS의 개념을 중심으로 살펴보겠습니다.</p>
<hr>
<h3 id="서버란-무엇일까">서버란 무엇일까?</h3>
<p>웹 개발을 하면서 서버라는 말은 자주 쓰지만 막상 정의를 떠올리면 애매할 때가 많습니다.</p>
<p>제가 느끼기에 서버는 브라우저에서 요청을 보내면 그 요청을 받아 처리하고 다시 응답을 돌려주는 시스템 전체를 의미합니다.</p>
<p><strong>클라이언트와 소통하며 애플리케이션 로직까지 실행할 수 있는 존재</strong>라 볼 수 있습니다.</p>
<hr>
<h3 id="wasweb-application-server란">WAS(Web Application Server)란?</h3>
<p>그런 관점에서 보면 WAS는 서버 중에서도 <strong>웹 애플리케이션을 실행하고 관리하는 서버</strong>라 할 수 있습니다.</p>
<p>쉽게 말해 서버의 한 종류이지만 현대 웹 개발에서는 대부분의 서버가 바로 WAS라 이해하면 쉽습니다.</p>
<p>여기서 중요한 포인트가 있습니다.</p>
<blockquote>
<p>WAS는 설치하거나 사용하는 소프트웨어가 아닌 <strong>개념 자체</strong>입니다</p>
</blockquote>
<p>즉 웹 애플리케이션을 실행하고 관리할 수 있는 서버라는 역할과 기능을 정의한 개념이 WAS이고 실제로 WAS를 쓰려면 <strong>Tomcat, Jetty, JBoss</strong> 같은 소프트웨어를 설치해 활용하게 됩니다.</p>
<hr>
<h3 id="was가-서버-중-하나라면-서버는-여러-종류가-존재할까">WAS가 서버 중 하나라면 서버는 여러 종류가 존재할까?</h3>
<p>한 가지 궁금증이 생길 수 있습니다.</p>
<blockquote>
<p>웹 애플리케이션이 아닌 서버도 존재할까요?</p>
</blockquote>
<p>답은 네, 존재합니다.</p>
<p>단순히 데이터를 전달하거나 파일만 제공하는 서버라면 굳이 WAS가 아니라 웹 서버(Web Server) 역할만으로 충분합니다.</p>
<p>즉 모든 서버가 WAS일 필요는 없고 서버라는 큰 범주 안에서 역할과 목적에 따라</p>
<ul>
<li>웹 서버</li>
<li>WAS</li>
<li>데이터베이스 서버</li>
</ul>
<p>이렇게 나뉘어 운영된다고 볼 수 있습니다.</p>
<p>이렇게 생각하면 서버라는 개념이 훨씬 명확해지고 WAS와 웹 서버 그리고 다른 서버들의 차이를 이해하는 데 도움이 됩니다.</p>
<hr>
<h3 id="회고">회고</h3>
<p>이번 글에서는 서버의 개념과 WAS를 중심으로 간략히 정리해보았습니다.</p>
<p>부족한 부분이나 궁금하신 점이 있다면 편하게 댓글로 남겨주세요.</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트에서 계층형 아키텍처가 아닌 핵사고날 아키텍처 적용]]></title>
            <link>https://velog.io/@fire_918/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EA%B0%80-%EC%95%84%EB%8B%8C-%ED%95%B5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@fire_918/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EA%B0%80-%EC%95%84%EB%8B%8C-%ED%95%B5%EC%82%AC%EA%B3%A0%EB%82%A0-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Wed, 01 Oct 2025 01:09:25 GMT</pubDate>
            <description><![CDATA[<h3 id="왜-계층형-아키텍처가-아닌-헥사고날을-사용했나">왜 계층형 아키텍처가 아닌 헥사고날을 사용했나?</h3>
<p>과거 백엔드 팀 프로젝트에 바로 투입되었을 때, 계층형 아키텍처와 헥사고날 아키텍처의 장단점을 충분히 구분하지 못한 채 단순히 &quot;헥사고날이 좋다&quot;라는 생각만으로 매번 헥사고날 아키텍처를 사용했습니다.</p>
<p>이번 개인 프로젝트를 진행할 때엔 기초부터 차근차근 학습하며 아키텍처를 이해하고 적용해보고자 합니다.</p>
<hr>
<h3 id="계층형-아키텍처의-문제점">계층형 아키텍처의 문제점</h3>
<p>계층형 아키텍처에서는 도메인 계층이 데이터베이스에 직접 의존하게 됩니다.</p>
<p>즉, 데이터베이스 구조나 구현에 변화가 생기면 도메인 계층 코드도 함께 수정해야 합니다.</p>
<hr>
<h3 id="헥사고날-아키텍처를-선택한-이유">헥사고날 아키텍처를 선택한 이유</h3>
<p>이러한 문제를 해결하기 위해 내부(core)와 외부(infra)를 명확히 구분하는 헥사고날 아키텍처를 선택했습니다.</p>
<p><img src="https://velog.velcdn.com/images/fire_918/post/df7a1957-0590-42a7-95e3-f442deea63e9/image.png" alt=""></p>
<p>이 방식은 도메인 계층이 외부 의존성에 직접적으로 영향을 받지 않도록 설계되어 데이터베이스 변경이나 외부 시스템 변경에 유연하게 대응할 수 있습니다.</p>
<hr>
<p><strong>아래는 핵사고날 프로젝트 패키지 구조 세팅 시 참고한 자료입니다</strong></p>
<p><a href="https://github.com/rbailen/Hexagonal-Architecture">https://github.com/rbailen/Hexagonal-Architecture</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API Gateway에 외/내부 토큰 적용기]]></title>
            <link>https://velog.io/@fire_918/API-Gateway%EC%97%90-%EC%99%B8%EB%82%B4%EB%B6%80-%ED%86%A0%ED%81%B0-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@fire_918/API-Gateway%EC%97%90-%EC%99%B8%EB%82%B4%EB%B6%80-%ED%86%A0%ED%81%B0-%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Mon, 22 Sep 2025 04:59:49 GMT</pubDate>
            <description><![CDATA[<p>마이크로서비스 아키텍처에서 Gateway를 통한 인증/인가 구현 과정에서 겪었던 보안 이슈들과 해결 방안을 단계별로 정리해보겠습니다.</p>
<hr>
<p><strong>가장 처음 시도했던 방식</strong></p>
<p>처음에는 가장 직관적인 방식으로 Gateway에서 JWT 토큰을 검증한 뒤 사용자 정보를 HTTP 헤더에 담아 백엔드 서비스로 전달하는 방식을 택했습니다.</p>
<pre><code>Client → Gateway (JWT 검증) → Backend Service
                ↓
            X-User-Id: 12345
            X-School-Id: 67890
            X-Authority: STUDENT
</code></pre><p>이 방식은 빠르게 구현할 수 있었지만 보안 문제는 고려하지 못한 채로 진행했습니다 </p>
<hr>
<p><strong>발견된 문제점</strong>
구현을 완료하고 테스트해보니 치명적인 보안 취약점을 발견했습니다</p>
<pre><code class="language-bash"># 공격자가 Gateway를 우회하여 직접 호출 가능
curl -H &quot;X-User-Id: 99999&quot; \
     -H &quot;X-School-Id: 11111&quot; \
     -H &quot;X-Authority: ADMIN&quot; \
     http://backend-service/api/admin/users
</code></pre>
<p>Gateway를 거치지 않고도 백엔드 서비스에 직접 접근할 수 있었고, 임의의 사용자 정보를 헤더에 넣어서 권한 상승이 가능한 상황이었습니다</p>
<p>Gateway가 <strong>필수적인 관문</strong>이 되지 못하는 심각한 문제였습니다</p>
<hr>
<p><strong>다음으로 선택한 방법</strong>
이를 보완하기 위해 Gateway에서만 붙일 수 있는 인증 토큰(X-Gateway-Token)을 추가했습니다</p>
<pre><code>Client → Gateway (JWT 검증) → Backend Service
                ↓
            X-User-Id: 12345
            X-School-Id: 67890
            X-Authority: STUDENT
            X-Gateway: Gateway # 예시 코드입니다.
</code></pre><p>하지만 이 역시 정적인 토큰이 탈취되면 그대로 악용될 수 있었습니다</p>
<pre><code class="language-bash"># 여전히 조작 가능한 보안 이슈
curl -H &quot;X-User-Id: 99999&quot; \
     -H &quot;X-Gateway: Gateway&quot; \
     http://backend-service/api/admin/users
</code></pre>
<p>이 방법도 근본적인 해결책은 되지 못했습니다</p>
<hr>
<p><strong>최종 해결 (외부 내부 토큰을 적용한 인증/인가)</strong>
팀원이 공유해준 글을 읽던 중 외부 토큰 + 내부 토큰 조합을 사용하는 해결책을 발견했습니다</p>
<p><strong>구현 플로우</strong></p>
<pre><code>    A[Client] --&gt;|JWT Token| B[Gateway]
    B --&gt;|Token 검증| B
    B --&gt;|User 존재 확인| C[Auth Service]
    C --&gt;|Passport 발급| B
    B --&gt;|Passport 전달| D[Backend Services]
    D --&gt;|Passport 검증| D
</code></pre><p><strong>단계 별 처리 과정</strong></p>
<ul>
<li><p>Gateway에서의 처리</p>
<pre><code class="language-kotlin">// JWT 토큰 유효성 검사 (유효기간, 시크릿키)
if (jwtService.validateToken(token)) {
  // Auth Service로 사용자 검증 요청
  val user = authService.validateUser(userId)

  // Passport 발급
  val passport = passportService.generatePassport(user)

  // 백엔드 서비스로 라우팅
  routeWithPassport(passport)
}
</code></pre>
</li>
</ul>
<pre><code>* Passport 구조
```kotlin
data class Passport(
    val user: PassportUser,
    val userIntegrity: String  // HMAC 기반 무결성 검증값
)

data class PassportUser(
    val id: UUID,
    val schoolId: UUID,
    val authority: Authority
)
</code></pre><ul>
<li>무결성 검증 로직<pre><code class="language-kotlin">fun generateIntegrity(user: PassportUser): String {
  val data = &quot;${user.id}|${user.schoolId}|${user.authority}&quot;
  val hmac = Mac.getInstance(&quot;HmacSHA256&quot;)
  hmac.init(SecretKeySpec(secretKey, &quot;HmacSHA256&quot;))
  return Base64.getEncoder().encodeToString(hmac.doFinal(data.toByteArray()))
}
</code></pre>
</li>
</ul>
<p>```</p>
<p>이 방식을 통해 Gateway를 반드시 거치지 않으면 Passport가 생성되지 않으므로 백엔드 서비스는 위조된 요청을 차단할 수 있게 되었습니다</p>
<hr>
<p><strong>추후 리팩토링을 하며</strong></p>
<p>userId를 main의 DB에서 확인 후 인증하고 passport를 발급하며 main 서버가 장애가 생기면 gateway와 그 외의 서브 모듈에도 피해가 간다는 것을 깨달았습니다</p>
<p>저희 프로젝트 특성 상 없는 userId가 사용되는 일은 없었고, userId 조회를 제거하고 passport 발급을 main 모듈에서 하는 방향으로 리팩토링 하였습니다.</p>
<p>userId가 있는지 없는지 필수로 조회해야하는 프로젝트였다면 auth 모듈을 분리할 계획이었습니다.</p>
<hr>
<p><strong>회고</strong>
이번 과정을 통해 <strong>&quot;보안은 구현 이후가 아니라 설계 단계에서부터 고려해야 한다&quot;</strong> 는 중요한 교훈을 얻었습니다 앞으로도 Gateway뿐만 아니라 다른 서비스 전반에서도 보안 이슈를 사전에 검토하고 점진적으로 개선해 나가려 합니다</p>
<p><strong>긴 글 읽어주셔서 감사합니다</strong></p>
<hr>
<p><strong>구현 시에 참고한 글 입니다.</strong></p>
<ul>
<li><a href="https://velog.io/@sheltonwon/MSA-API-Gateway-%ED%8C%A8%ED%84%B4%EC%97%90%EC%84%9C%EC%9D%98-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80">Gateway의 인증/인가를 구현할 때 참고한  글입니다.</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>