<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Eugenius1st.log</title>
        <link>https://velog.io/</link>
        <description>자신만의 속도로</description>
        <lastBuildDate>Tue, 22 Sep 2026 08:25:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Eugenius1st.log</title>
            <url>https://velog.velcdn.com/images/angel_eugnen/profile/b9287067-f0b6-4bab-a461-9a8a62e31518/KakaoTalk_20220408_210325466.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Eugenius1st.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/angel_eugnen" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[IoC(제어의 역전), DI(의존성 주입) 이해하기]]></title>
            <link>https://velog.io/@angel_eugnen/IoC%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84-DI%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@angel_eugnen/IoC%EC%A0%9C%EC%96%B4%EC%9D%98-%EC%97%AD%EC%A0%84-DI%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 22 Sep 2026 08:25:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/5b66d80f-019d-4b7a-b152-01b50a2208a8/image.png" alt=""></p>
<h1 id="ioc제어의-역전-di의존성-주입-이해하기">IoC(제어의 역전), DI(의존성 주입) 이해하기</h1>
<h2 id="1-spring의-ioc와-di">1. Spring의 IoC와 DI</h2>
<h3 id="💡-ioc-di-누구냐-넌">💡 IoC, DI… 누구냐 넌?</h3>
<p>Java를 배우고 Spring을 처음 접하면 가장 먼저 듣게 되는 용어 중 하나가 <strong>IoC와 DI</strong>입니다.</p>
<p>Spring으로 개발을 시작한 분들이 가장 많이 오해하는 부분은 <strong>IoC와 DI가 Spring에서 처음 만들어진 기능</strong>이라고 생각하는 것입니다.</p>
<p>하지만 IoC와 DI는 Spring만의 개념이 아닙니다.</p>
<ul>
<li><strong>IoC(Inversion of Control)</strong> → 설계 원칙</li>
<li><strong>DI(Dependency Injection)</strong> → 디자인 패턴</li>
</ul>
<p>즉, 객체지향적인 좋은 코드를 만들기 위한 개념이라고 이해하면 좋습니다.</p>
<p>설계 원칙과 디자인 패턴이라는 용어가 낯설다면 요리에 비유해보겠습니다.</p>
<hr>
<h2 id="2-설계-원칙과-디자인-패턴">2. 설계 원칙과 디자인 패턴</h2>
<h3 id="🧑🍳-김치볶음밥-맛있게-만드는-방법--설계-원칙">🧑‍🍳 김치볶음밥 맛있게 만드는 방법 — 설계 원칙</h3>
<p>맛있는 김치볶음밥을 만들기 위한 <strong>원칙</strong>은 다음과 같습니다.</p>
<ul>
<li>신선한 재료를 사용한다.</li>
<li>신 김치를 사용한다.</li>
<li>밥과 김치의 비율을 잘 맞춘다.</li>
<li>볶을 때 재료를 넣는 순서가 중요하다.</li>
</ul>
<p>이처럼 <strong>좋은 결과물을 만들기 위해 지켜야 하는 기본적인 원칙</strong>이 설계 원칙이라고 볼 수 있습니다.</p>
<h3 id="🍳-김치볶음밥-레시피--디자인-패턴">🍳 김치볶음밥 레시피 — 디자인 패턴</h3>
<p>맛있는 김치볶음밥을 만들기 위한 구체적인 레시피는 다음과 같습니다.</p>
<ol>
<li>오일을 두른 팬에 채 썬 파를 볶아 파 기름을 만든다.</li>
<li>준비한 햄을 넣고 볶다가 간장 한 스푼을 넣어 풍미를 낸다.</li>
<li>설탕에 버무린 김치를 넣고 함께 볶는다.</li>
<li>미리 식혀 둔 밥을 넣어 함께 볶는다.</li>
<li>참기름 한 스푼을 넣어 마무리한다.</li>
</ol>
<p>즉, <strong>원칙을 실제 코드나 작업 과정에서 적용하기 위한 구체적인 방법</strong>이라고 생각하면 됩니다.</p>
<hr>
<h2 id="3-좋은-코드를-위한-spring의-ioc와-di">3. 좋은 코드를 위한 Spring의 IoC와 DI</h2>
<p>그렇다면 좋은 코드란 무엇일까요?</p>
<ul>
<li>논리가 간단해야 한다.</li>
<li>중복을 제거하고 표현을 명확하게 해야 한다.</li>
<li>코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.</li>
<li>의존성을 최소화해야 한다.</li>
<li>새로운 기능을 추가하더라도 기존 구조를 크게 변경하지 않아야 한다.</li>
<li>...</li>
</ul>
<p>좋은 코드를 작성하기 위해서는 신경 써야 할 부분이 정말 많습니다.</p>
<p>Spring은 개발자가 Java를 사용하여 이러한 좋은 코드를 작성할 수 있도록 도와주는 프레임워크입니다.</p>
<p>실생활에 비유한다면 <strong>요리 도구와 재료가 어느 정도 준비되어 있는 밀키트</strong>라고 볼 수 있습니다.</p>
<p>그리고 그중에서도 <strong>IoC와 DI는 Spring의 핵심 개념</strong>이라고 할 수 있습니다.</p>
<p>Spring에서는 <strong>IoC Container</strong>를 통해 객체의 생성과 관리 등을 담당합니다.</p>
<p>Spring Docs에서는 IoC를 <strong>DI(Dependency Injection)와 같은 의미로 설명</strong>하기도 합니다.</p>
<p>이를 간단하게 정리하면 다음과 같습니다.</p>
<blockquote>
<p><strong>DI 패턴을 사용하여 IoC라는 설계 원칙을 구현한다.</strong></p>
</blockquote>
<p>Spring의 IoC Container와 Bean에 대해서는 이후에 자세하게 알아보겠습니다.</p>
<hr>
<h1 id="4-의존성이란">4. 의존성이란?</h1>
<h3 id="💡-di를-이해하려면-먼저-의존성을-이해해야-합니다">💡 DI를 이해하려면 먼저 &#39;의존성&#39;을 이해해야 합니다.</h3>
<p>우리가 다리를 다쳐서 목발을 사용하여 걷게 되었다고 생각해봅시다.</p>
<p>이 경우 우리는 <strong>걷기 위해 목발에 의존</strong>하고 있습니다.</p>
<p>코드에서도 마찬가지입니다.</p>
<p>어떤 객체가 다른 객체를 사용해야만 정상적으로 동작한다면, <strong>한 객체가 다른 객체에 의존하고 있다</strong>고 표현할 수 있습니다.</p>
<hr>
<h2 id="4-1-consumer와-chicken의-강한-결합">4-1. Consumer와 Chicken의 강한 결합</h2>
<pre><code class="language-java">public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat();
    }
}

class Chicken {

    public void eat() {
        System.out.println(&quot;치킨을 먹는다.&quot;);
    }
}</code></pre>
<p>위 코드에서 <code>Consumer</code>와 <code>Chicken</code>의 관계를 살펴보겠습니다.</p>
<p><code>Consumer</code>가 직접 <code>Chicken</code> 객체를 생성하고 있습니다.</p>
<pre><code class="language-java">Chicken chicken = new Chicken();</code></pre>
<p>즉, <code>Consumer</code>가 <strong>Chicken이라는 구체적인 클래스에 강하게 의존</strong>하고 있습니다.</p>
<p>현재 코드가 실행되는 데에는 문제가 없습니다.</p>
<p>하지만 만약 <code>Consumer</code>가 치킨이 아니라 <strong>피자를 먹고 싶어진다면</strong> 어떻게 해야 할까요?</p>
<pre><code class="language-java">Chicken chicken = new Chicken();</code></pre>
<p>이 부분을 <code>Pizza</code>로 변경해야 합니다.</p>
<p>만약 음식의 종류가 계속 추가된다면 <code>Consumer</code>의 코드도 계속 수정해야 합니다.</p>
<p>이러한 관계를 <strong>강한 결합</strong> 또는 <strong>강한 의존성</strong>이라고 합니다.</p>
<hr>
<h1 id="5-interface를-이용한-약한-결합">5. Interface를 이용한 약한 결합</h1>
<p>그렇다면 어떻게 하면 결합을 약하게 만들 수 있을까요?</p>
<p>Java의 <strong>Interface와 다형성</strong>을 활용할 수 있습니다.</p>
<pre><code class="language-java">public class Consumer {

    void eat(Food food) {
        food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();

        consumer.eat(new Chicken());
        consumer.eat(new Pizza());
    }
}

interface Food {
    void eat();
}

class Chicken implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;치킨을 먹는다.&quot;);
    }
}

class Pizza implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;피자를 먹는다.&quot;);
    }
}</code></pre>
<p>이번에는 <code>Consumer</code>가 <code>Chicken</code>이나 <code>Pizza</code>에 직접 의존하지 않습니다.</p>
<p>대신 <code>Food</code>라는 <strong>인터페이스에 의존</strong>하고 있습니다.</p>
<pre><code class="language-java">void eat(Food food) {
    food.eat();
}</code></pre>
<p><code>Chicken</code>과 <code>Pizza</code>는 모두 <code>Food</code>를 구현하고 있기 때문에 <code>Consumer</code>는 어떤 음식이 들어오더라도 사용할 수 있습니다.</p>
<pre><code class="language-java">consumer.eat(new Chicken());
consumer.eat(new Pizza());</code></pre>
<p>이처럼 <strong>인터페이스를 활용한 다형성</strong>을 사용하면 구체적인 구현체에 대한 의존성을 줄일 수 있습니다.</p>
<p>이러한 관계를 <strong>약한 결합(Loose Coupling)</strong> 또는 <strong>약한 의존성</strong>이라고 합니다.</p>
<hr>
<h1 id="6-주입이란">6. 주입이란?</h1>
<h3 id="💡-그렇다면-주입은-무엇일까요">💡 그렇다면 &#39;주입&#39;은 무엇일까요?</h3>
<p>우리가 주사기를 통해 백신을 몸속에 주입하듯이,</p>
<p>코드에서의 <strong>주입(Injection)</strong>도 필요한 객체를 해당 객체에 전달하는 것을 의미합니다.</p>
<p>대표적인 주입 방법에는 다음과 같은 것들이 있습니다.</p>
<ol>
<li>필드 주입</li>
<li>메서드 주입</li>
<li>생성자 주입</li>
</ol>
<hr>
<h2 id="6-1-필드에-직접-주입">6-1. 필드에 직접 주입</h2>
<pre><code class="language-java">public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();

        consumer.food = new Chicken();
        consumer.eat();

        consumer.food = new Pizza();
        consumer.eat();
    }
}

interface Food {

    void eat();
}

class Chicken implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;치킨을 먹는다.&quot;);
    }
}

class Pizza implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;피자를 먹는다.&quot;);
    }
}</code></pre>
<p><code>Consumer</code>가 직접 <code>Chicken</code>을 생성하여 사용하는 것이 아니라,</p>
<p>외부에서 <code>Food</code> 객체를 전달받아 사용하고 있습니다.</p>
<pre><code class="language-java">consumer.food = new Chicken();</code></pre>
<p>또는</p>
<pre><code class="language-java">consumer.food = new Pizza();</code></pre>
<p>즉, 필요한 객체를 <code>Consumer</code>의 <code>food</code>에 <strong>주입</strong>하고 있습니다.</p>
<hr>
<h2 id="6-2-메서드를-통한-주입">6-2. 메서드를 통한 주입</h2>
<p>이번에는 <code>setter</code> 메서드를 이용해 객체를 주입해보겠습니다.</p>
<pre><code class="language-java">public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();

        consumer.setFood(new Chicken());
        consumer.eat();

        consumer.setFood(new Pizza());
        consumer.eat();
    }
}

interface Food {

    void eat();
}

class Chicken implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;치킨을 먹는다.&quot;);
    }
}

class Pizza implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;피자를 먹는다.&quot;);
    }
}</code></pre>
<p>이번에는 다음과 같은 <code>setFood()</code> 메서드를 만들었습니다.</p>
<pre><code class="language-java">public void setFood(Food food) {
    this.food = food;
}</code></pre>
<p>그리고 외부에서 원하는 객체를 전달합니다.</p>
<pre><code class="language-java">consumer.setFood(new Chicken());</code></pre>
<p>또는</p>
<pre><code class="language-java">consumer.setFood(new Pizza());</code></pre>
<p>이처럼 <strong>setter와 같은 메서드를 통해 필요한 객체를 전달하는 방식</strong>도 의존성 주입입니다.</p>
<hr>
<h1 id="6-3-생성자를-통한-주입">6-3. 생성자를 통한 주입</h1>
<p>마지막으로 생성자를 이용해 객체를 주입할 수 있습니다.</p>
<pre><code class="language-java">public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {

        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();

        consumer = new Consumer(new Pizza());
        consumer.eat();
    }
}

interface Food {

    void eat();
}

class Chicken implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;치킨을 먹는다.&quot;);
    }
}

class Pizza implements Food {

    @Override
    public void eat() {
        System.out.println(&quot;피자를 먹는다.&quot;);
    }
}</code></pre>
<p><code>Consumer</code>의 생성자를 살펴보면 다음과 같습니다.</p>
<pre><code class="language-java">public Consumer(Food food) {
    this.food = food;
}</code></pre>
<p>그리고 객체를 생성할 때 필요한 <code>Food</code> 객체를 전달합니다.</p>
<pre><code class="language-java">Consumer consumer = new Consumer(new Chicken());</code></pre>
<p>또는</p>
<pre><code class="language-java">Consumer consumer = new Consumer(new Pizza());</code></pre>
<p>이렇게 <strong>생성자를 통해 의존 객체를 전달하는 방식</strong>을 생성자 주입이라고 합니다.</p>
<hr>
<h1 id="7-제어의-역전ioc이란">7. 제어의 역전(IoC)이란?</h1>
<h3 id="💡-마지막으로-제어의-역전은-무엇일까요">💡 마지막으로 제어의 역전은 무엇일까요?</h3>
<p>처음 코드를 다시 살펴보겠습니다.</p>
<pre><code class="language-java">public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }
}</code></pre>
<p>여기서는 <code>Consumer</code>가 직접 <code>Chicken</code> 객체를 생성합니다.</p>
<p>즉,</p>
<pre><code class="language-text">Consumer
   ↓
Chicken 생성
   ↓
Chicken 사용</code></pre>
<p><strong>객체의 생성과 사용에 대한 제어권이 Consumer에게 있습니다.</strong></p>
<p>따라서 새로운 음식이 필요하다면 <code>Consumer</code>의 코드를 직접 수정해야 합니다.</p>
<hr>
<h2 id="7-1-의존성-주입을-사용하면">7-1. 의존성 주입을 사용하면?</h2>
<p>이번에는 <code>Consumer</code>가 필요한 <code>Food</code>를 외부에서 전달받도록 변경했습니다.</p>
<pre><code class="language-java">public class Consumer {

    void eat(Food food) {
        food.eat();
    }
}</code></pre>
<p>이제 <code>Consumer</code>는 어떤 구체적인 음식이 만들어지는지 직접 알 필요가 없습니다.</p>
<p>외부에서 만들어진 객체를 전달받아 사용하기만 하면 됩니다.</p>
<pre><code class="language-java">consumer.eat(new Chicken());
consumer.eat(new Pizza());</code></pre>
<p>즉, 객체의 생성과 전달에 대한 제어가 <code>Consumer</code> 밖으로 이동했습니다.</p>
<p>이를 <strong>제어의 역전(IoC, Inversion of Control)</strong>이라고 합니다.</p>
<hr>
<h1 id="8-ioc와-di-정리">8. IoC와 DI 정리</h1>
<p>전체 흐름을 정리하면 다음과 같습니다.</p>
<h3 id="기존-방식">기존 방식</h3>
<pre><code class="language-text">Consumer
   │
   ├── Chicken 객체 생성
   │
   └── Chicken 사용</code></pre>
<p><code>Consumer</code>가 직접 필요한 객체를 생성하기 때문에 구체적인 구현체에 강하게 결합되어 있습니다.</p>
<h3 id="di를-적용한-방식">DI를 적용한 방식</h3>
<pre><code class="language-text">외부
 │
 ├── Chicken 생성
 │
 └── Consumer에게 전달
          │
          ↓
       Food 사용</code></pre>
<p><code>Consumer</code>는 필요한 객체를 직접 생성하지 않고 외부에서 전달받습니다.</p>
<p>따라서 객체 생성에 대한 <strong>제어권이 Consumer에서 외부로 이동</strong>하게 됩니다.</p>
<p>이것이 <strong>제어의 역전(IoC)</strong>입니다.</p>
<p>그리고 외부에서 필요한 객체를 전달해주는 구체적인 방법이 <strong>의존성 주입(DI)</strong>입니다.</p>
<hr>
<h2 id="⭐-핵심-정리">⭐ 핵심 정리</h2>
<table>
<thead>
<tr>
<th>개념</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>의존성</strong></td>
<td>한 객체가 다른 객체를 필요로 하는 관계</td>
</tr>
<tr>
<td><strong>약한 결합</strong></td>
<td>구체적인 구현체보다 인터페이스 등에 의존하는 구조</td>
</tr>
<tr>
<td><strong>주입(Injection)</strong></td>
<td>필요한 객체를 외부에서 전달하는 것</td>
</tr>
<tr>
<td><strong>DI</strong></td>
<td>객체가 필요한 의존성을 외부에서 주입받는 디자인 패턴</td>
</tr>
<tr>
<td><strong>IoC</strong></td>
<td>객체의 생성·사용 등에</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 설계와 연관관계]]></title>
            <link>https://velog.io/@angel_eugnen/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%84%A4%EA%B3%84%EC%99%80-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@angel_eugnen/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%84%A4%EA%B3%84%EC%99%80-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84</guid>
            <pubDate>Thu, 17 Sep 2026 13:12:42 GMT</pubDate>
            <description><![CDATA[<pre><code>```sql
-- orders 테이블
CREATE TABLE orders
(
    id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    user_id     BIGINT         NOT NULL,
    total_price DECIMAL(10, 2) NOT NULL,
    status      VARCHAR(20)    NOT NULL,
    created_at  TIMESTAMP               DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP      NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```</code></pre><p>orders가 user_id를 가짐으로서 관계 정의가 되고 join이 가능한 것이 연관관계이다.
user입장에서는 order의 id 여러개를 가질 수 있다.</p>
<p>1:N 이라고한다. 하나의 row가 N개의 row를 가지는 것을 말한다.</p>
<p>이를 엔티티에서 표현해야한다.</p>
<h2 id="1-user관점에서-order-표현">1. user관점에서 order 표현</h2>
<h2 id="2-order관점에서-user-표현">2. order관점에서 user 표현</h2>
<h2 id="nn-을-표현하는-설계-원칙">N:N 을 표현하는 설계 원칙</h2>
<p>user 과 order 가 N:N이라면
1:N 을 중첩시킬 수 있는 mapping table을 만들어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA Entity]]></title>
            <link>https://velog.io/@angel_eugnen/JPA-Entity</link>
            <guid>https://velog.io/@angel_eugnen/JPA-Entity</guid>
            <pubDate>Wed, 16 Sep 2026 13:03:28 GMT</pubDate>
            <description><![CDATA[<h2 id="핵심-어노테이션">핵심 어노테이션</h2>
<ul>
<li><strong><code>@Entity</code></strong><ul>
<li>이 클래스가 JPA가 관리해야 하는 엔티티임을 표시합니다. <strong>필수 어노테이션</strong>입니다.</li>
<li>주의: 기본 생성자(no-args constructor)가 반드시 필요하며, final 클래스에는 사용할 수 없습니다.</li>
</ul>
</li>
<li><strong><code>@Table(name = &quot;...&quot;)</code></strong><ul>
<li>엔티티 클래스가 매핑될 데이터베이스 테이블의 이름을 지정합니다.</li>
<li>만약 생략하면, 엔티티 클래스 이름을 테이블 이름으로 사용합니다. (예: <code>User</code> -&gt; <code>user</code>)</li>
</ul>
</li>
</ul>
<h2 id="네이밍-규칙">네이밍 규칙</h2>
<p>DB는 무조건 스테이크
자바는 카멜 케이스가 표준이다.</p>
<p>DB예약어는 주의해야 한다.
<code>USER</code>, <code>ORDER</code>, <code>GROUP</code> 등은 데이터베이스의 예약어일 수 있다. 따라서 컬럼명이나 테이블명으로 사용하면 예기치 않은 오류가 발생할 수 있으므로 피하는것이 좋다. 부득이 한경우
@Table(name = &quot;백틱order백틱&quot;) 로 백틱으로 감싸서 처리하라.</p>
<p>그렇다고 order_info 이런식으로 하지말라. 명시적인 하나의 단어로 하라. 만약 저렇게 하면 상위에 order 에 있다고 느껴진다. order가 존재하지도 않는데 order_info로 만들면 개발자들이 헷갈린다.</p>
<h2 id="테이블명-단수vs복수">테이블명 단수VS복수</h2>
<blockquote>
<p>ORM에서만 단수형을 써라</p>
</blockquote>
<ul>
<li><strong>전통적인 방식 (복수형 - <code>users</code>)</strong><ul>
<li>데이터베이스 관점에서는 테이블을 데이터의 &#39;집합(Collection)&#39;으로 봅니다. 따라서 &#39;사용자들의 모음&#39;이라는 의미로 <code>users</code>, <code>orders</code>처럼 복수형을 사용하는 것이 일반적이었습니다.</li>
</ul>
</li>
<li><strong>ORM/JPA 관점 (단수형 - <code>user</code>)</strong><ul>
<li>최근 ORM이 널리 쓰이면서 생긴 경향입니다. ORM은 <code>User</code>라는 엔티티 클래스(객체) 하나가 <code>user</code>라는 테이블 하나와 1:1로 매핑된다고 봅니다. 즉, 객체의 관점을 따라 단수형으로 이름을 일치시키는 것이 더 직관적이라는 것입니다.</li>
</ul>
</li>
</ul>
<p>가끔가다 테이블을 ORM처럼 단수형으로 쓰는 회사도 있긴 함</p>
<ul>
<li><strong>상태(Boolean) 타입 필드 네이밍</strong><ul>
<li><strong>Java/API 네이밍 (<code>is-</code> / <code>has-</code> / <code>can-</code>)</strong><ul>
<li>Boolean 타입의 필드명은 그 자체로 &#39;참/거짓&#39;으로 답할 수 있는 질문이 되도록 짓는 것이 가장 좋습니다. <code>is~</code>로 시작하는 네이밍은 <strong>자바빈 표준 명세</strong>에 따른 규칙으로, 여러 프레임워크(Lombok, Jackson 등)가 이를 자동으로 인지하여 가독성과 개발 편의성을 높여줍니다.</li>
<li><strong>가독성</strong>: <code>if (user.isActive())</code> 처럼 코드가 자연스러운 영어 문장처럼 읽힙니다.</li>
</ul>
</li>
<li><strong>대표 접두사</strong><ul>
<li><strong><code>is~</code></strong>: 상태를 나타낼 때 (e.g., <code>isActive</code>, <code>isDeleted</code>, <code>isVisible</code>)</li>
<li><strong><code>has~</code></strong>: 소유나 포함 여부를 나타낼 때 (e.g., <code>hasProfileImage</code>, <code>hasCoupon</code>)</li>
<li><strong><code>can~</code></strong>: 가능/권한 여부를 나타낼 때 (e.g., <code>canPurchase</code>, <code>canComment</code>)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Yn쓰는 경우도 있다. 요새는 잘 안쓴다.</p>
<hr>
<h2 id="entity-역할">Entity 역할?</h2>
<p>엔티티는 데이터를 사용하는게 아니다. 그냥 표현해 놓은 것 뿐.
클래스 기반의 데이터 set이다.</p>
<h2 id="repository">Repository</h2>
<p>Entity를 기반으로 메소드를 정의해둔 것이다.
interface채로 entity들을 추상화해둘 수 있다.</p>
<p>즉, User Table(특정 테이블 전용) SQL명령어 모음집 이다.
@Repository를 붙여야만 한다.</p>
<p>@Service, @Repository는 스프링이 실행할때 인스턴스화 해서 갖고있다.</p>
<pre><code class="language-java">@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {

  // 이건 인터페이스다. 인터페이스는 이 자체만으로는 실행 불가하고 &#39;구현체가 필요하다.&#39;
  Optional&lt;User&gt; findByEmail(String email);
  // 이건 구현체를 만들어주고 이 안에 쿼리가 들어가는 것이다.즉 뒷단에서 움직이는 것이고 우리가 하는건 구현체일뿐. 볼 수 없다. 빌드 클래스 파일 열어야할듯

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ORM과 Hibernate]]></title>
            <link>https://velog.io/@angel_eugnen/ORM%EA%B3%BC-Hibernate</link>
            <guid>https://velog.io/@angel_eugnen/ORM%EA%B3%BC-Hibernate</guid>
            <pubDate>Tue, 15 Sep 2026 14:01:24 GMT</pubDate>
            <description><![CDATA[<h1 id="orm이란">ORM이란</h1>
<p>객체와 테이블을 자동 매핑하여 SQL없이 객체 중심으로 데이터 다루게 해줌.
SQL보단 객체 중심의 코드로 비즈니스 로직에 집중할 수 있다. 객체 모델만 수정하면 되므로 유지보수에도 용이하다.</p>
<h1 id="hibernate">Hibernate</h1>
<p>ORM개념을 구현한 대표적인 프레임워크</p>
<h1 id="jpa">JPA</h1>
<p>여러개의 데이터베이스를 호환시킬 수 있는 규칙</p>
<h2 id="orm-트렌드">ORM 트렌드</h2>
<p>스몰 쿼리를 이용, 중복쿼리 조합 사용.
객체베이스로 올라오니 쿼리 베이스가 아니라 list, set, map과 같은 자료구조 연산자들을 사용해서 최적화 할 수 있는 여지가 남았다.</p>
<p>과거 프로젝트 보면, 서비스로직이 텅 비어있고 xml파일에 수십줄의 코드가 쓰여있는 경우가 많다. 이는 어떤 코드인지 해석할 수가 없다. 그리고 중복 쿼리를 조합해서 쓸 수 없다.
강사님: 아예 mybatis 자체가 없어져도 된다고 생각한다.</p>
<h1 id="entity">Entity</h1>
<p>자바가 이해할수있는 파일</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Layered 패턴]]></title>
            <link>https://velog.io/@angel_eugnen/Layered-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@angel_eugnen/Layered-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 15 Sep 2026 13:10:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/14854ce3-e749-45f5-9857-f06182f2396e/image.png" alt=""></p>
<h1 id="layered-패턴">Layered 패턴</h1>
<p>API동작 방식은 아래와 같다.</p>
<p>API /member=&gt; 동작 기대: member테이블에서 member 정보를 받아온다.</p>
<ol>
<li>API정의 =&gt; /member</li>
<li>Member 목록 조회</li>
<li>/SELECT * FROM MEMBER</li>
<li>DB 쿼리 실행</li>
<li>MEMBER 데이터 습득</li>
<li>데이터 가공</li>
<li>Response</li>
</ol>
<h2 id="layered-패턴이란-이-일련의-행위를-3개로-나누는-것이다">Layered 패턴이란, 이 일련의 행위를 3개로 나누는 것이다.</h2>
<ol>
<li>controller =&gt; API 정의(핸들러)</li>
<li>service =&gt; API 요청 가공, 쿼리의 응답 가공, 에러처리, 트랜젝션 처리, 필터처리 등등..(로직)</li>
<li>repository = &gt; 쿼리를 정의, DB와 소통(자원 제공)</li>
</ol>
<h2 id="디렉토리-구조-예시">디렉토리 구조 예시</h2>
<pre><code>src
└── main
    ├── java
    │   └── com.example.demo
    │       ├── domain               # 📌 도메인별 패키지
    │       │   └── member
    │       │       ├── controller   # Member 관련 HTTP 요청 처리 (API 엔드포인트)
    │       │       ├── service      # Member 관련 비즈니스 로직 구현
    │       │       ├── dto          # Member 관련 데이터 전송 객체 (Request/Response)
    │       │       ├── entity       # Member 데이터베이스 테이블과 매핑되는 객체
    │       │       └── repository   # Member 데이터베이스 접근 로직 (JPA 인터페이스)
    │       │
    │       ├── common               # 📂 여러 도메인에서 공통으로 사용하는 코드
    │       │   └── util             # 유틸리티 클래스 (e.g., 날짜, 암호화 등)
    │       │
    │       └── global               # 🌍 애플리케이션 전역에 적용되는 코드
    │           └── config           # 각종 설정 클래스 (e.g., SecurityConfig)
    │
    └── resources
        ├── db/migration/            # DB 마이그레이션 스크립트 (Flyway, Liquibase)
        ├── application.yml          # 애플리케이션 주요 설정 파일
        └── static/                  # CSS, JS, 이미지 등 정적 리소스</code></pre><h2 id="yml-파일">yml 파일</h2>
<p>들여쓰기가 중요하다, 이걸로 설정을 작성한다.</p>
<h2 id="이상적인-개발-환경">이상적인 개발 환경</h2>
<p>회사가서 해야하는 부분</p>
<ul>
<li>local =&gt; 개인 pc (local DB) =&gt; stage or dev 데이터를 개인pc 로컬로 덤프 / 편의상 DEV 보라는 회사도 있지만, 좋지 않음.
프론트는 ? =&gt; 원칙상 local에 붙어야 한다. qa가 제대로 확인이 안되므로 개발 되기 전까진 로컬에 덤프뜨라고 해도 할 수 있지 못하기 때문에 안한다. 그래서 K8s(쿠버네티스 사용) 별도의 브랜치마다 서버 환경 설정</li>
<li>dev =&gt; dev 서버(spring) -&gt; DEV DB</li>
<li>stage =&gt; stage 서버 + 운영 DB</li>
<li>prod =&gt; 운영 서버 + 운영 DB</li>
</ul>
<h2 id="build-gradle">build gradle</h2>
<p>dependencies 는 외부 라이브러리를 뭘 쓸거다라는 설정이다.</p>
<ul>
<li>implementation: import 하는 것(spring-boot-starter-web을 땡겨와야 spring boot프로젝트가 된다, starter-undertow, starter-data-jpa orm관련한 걸 땡겨온다, starter-security 로 인증인가 필터처리 라이브러리를 땡겨온다. starter-validation 은 api 특정 속성,패턴 들에 기인해서 정의하고 싶을 때 검증하기 위한 라이브러리, starter-acurator 은 어떤 부하가 오는지 헬스 체크다.이는 생각보다 중요하다. querydsl 데이터베이스 쿼리를 작성할 수 있게 도와주는 프레임워크, springdoc 
OPEN API사양을 바탕으로 API문서 자동 생성해주는 라이브러리, flyway 는 자동버전관리 프레임워크, mapstructure 추상화 라이브러리, jjwt는 json을 파싱해주는 것이다. lombok 은 어노테이션 패키지)</li>
<li>compileOnly: 컴파일 할때만 실행하는 라이브러리(lombok), 따라서 이 아래부터 순서가 중요하다. 정상 동작이 안된다. 라이브러리를 땡기는 작업들을 먼저 한다음에 진행해줘야 한다. 아래는 다 실행할 것을을 적재한다.</li>
<li>runtimeOnly: </li>
<li>annotationProcessor: 프로그램이 실행될때 특정 코드를 자동화해서 만들어준다. </li>
</ul>
<h2 id="configuration">@Configuration</h2>
<p>@Configuration 어노테이션을 붙이면 설정 파일이 된다.</p>
<h2 id="형상관리-파일">형상관리 파일</h2>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/8742f15a-f2d1-423b-8f37-d16eeaf87d01/image.png" alt="">
이게 파일명 규칙이다.</p>
<p>db.migration이라는건 yml파일 안에 있는 locations에 있는 Flyway설정 위치로 맞춰야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring]]></title>
            <link>https://velog.io/@angel_eugnen/Spring</link>
            <guid>https://velog.io/@angel_eugnen/Spring</guid>
            <pubDate>Tue, 15 Sep 2026 11:47:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/14854ce3-e749-45f5-9857-f06182f2396e/image.png" alt=""></p>
<h1 id="왜-스프링인가">왜 스프링인가?</h1>
<p>죽어도 바뀌지 않아서, 일관성이 있다.
=&gt; 그래서 욕먹긴 한다고 함.</p>
<p>또한 넓은 범위의 회사를 대응할 수 있다.</p>
<h2 id="톰캣-서블릿-그리고-스프링-컨테이너의-구조">톰캣, 서블릿, 그리고 스프링 컨테이너의 구조</h2>
<h2 id="톰캣-tomcat---웹-서버--서블릿-컨테이너">톰캣 (Tomcat) - 웹 서버 &amp; 서블릿 컨테이너</h2>
<p>역할: 클라이언트(웹 브라우저)로부터 오는 HTTP 요청을 가장 먼저 받는 관문입니다. 마치 건물의 &quot;안내 데스크&quot;와 같습니다.</p>
<p>웹 서버 VS 웹 어플리케이션 이 둘에 대한 설명을 명확하게 할 수 있는가?</p>
<p>웹 서버는 ?
톰캣이다.
웹 어플리케이션은?
스프링이다.+ 톰캣 포함.</p>
<p>웹 어플리케이션은 웹사이트 전체를 포함한 하나의 서비스이다.
일반 사용자들이 사용할 수 있는 프로그램이 웹 어플리케이션이고 이 안에서 통신을 위해 사용하는 프로그램이 웹 서버이다.</p>
<h3 id="전통적인-웹의-라우팅-방식php">전통적인 웹의 라우팅 방식(php)</h3>
<p>사용자 -&gt; API요청 -&gt; DNS(<a href="http://eugenius.com/member-&gt;192.168.0.1">http://eugenius.com/member-&gt;192.168.0.1</a>) -&gt; PC 
PC(Tomcat(웹서버) /member 탐색)</p>
<p>이는 톰캣에 파일 넣는 전형적인 방식이라 복잡한 엔드포인트를 만들어 내는데 한계점이 있다.
그래서 하나로 퉁치고 분기처리 해주는 프로그램이 필요했다. 그래서 생긴것이 &quot;서블릿&quot; 이다.</p>
<p>사용자 -&gt; API요청 -&gt; DNS(<a href="http://eugenius.com/member-&gt;192.168.0.1">http://eugenius.com/member-&gt;192.168.0.1</a>) -&gt; PC 
PC(Tomcat(웹서버) -&gt; <strong>서블릿</strong> -&gt; 스프링)</p>
<h2 id="서블릿">서블릿</h2>
<p>역할: 톰캣으로부터 요청을 넘겨받는 스프링의 중앙 통제실 또는 &quot;교통 경찰&quot;입니다.</p>
<p>이 공통된 하나의 엔드포인트 서블릿으로 들어오면 여러개의 자원에 접근 가능하도록 분기처리 해준다.
즉, 스프링이 이해할 수 있는 형태로 변환해주는 것이 서블릿이다.</p>
<p>(구조적으로 보면 스프링이 서블릿을 추상화 해놓은것. 어찌보면 한몸이다. 서블릿의 역할은 스프링을 컨트롤 하는 것이다.)</p>
<p><a href="http://eugenius.com/member">http://eugenius.com/member</a> -&gt; tomcat -&gt; 서블릿(/member) -&gt; SPRING(MemberController)</p>
<p>이렇게 하면 member에 대한 코드들을 return 받는다.
쿠키나 헤더는 다 서블릿을 통해 접근을 한다.</p>
<blockquote>
<p>서블릿은 스프링 앞단에서 작동하는 통신 리소스라고 정리!</p>
</blockquote>
<p>서블릿은 나중에 딥다이브 해라. 그래야 http프로토콜 이해하는데 도움이 되고, 면접 때 필요할 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native + 카카오맵 웹뷰 삽질]]></title>
            <link>https://velog.io/@angel_eugnen/React-Native-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-%EC%9B%B9%EB%B7%B0-%EC%82%BD%EC%A7%88</link>
            <guid>https://velog.io/@angel_eugnen/React-Native-%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A7%B5-%EC%9B%B9%EB%B7%B0-%EC%82%BD%EC%A7%88</guid>
            <pubDate>Tue, 30 Jun 2026 02:07:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c718e061-786b-43e5-959f-acffd546938a/image.png" alt=""></p>
<h1 id="healingtrip-카카오맵-webview-스터디"><strong>HealingTrip 카카오맵 WebView 스터디</strong></h1>
<blockquote>
<p>React Native WebView + Kakao Maps JS SDK 로컬 개발 중 겪은 문제와 해결 과정 정리</p>
<p>대상: HealingTrip FE (Expo + react-native-webview + Vite 지도 페이지)</p>
</blockquote>
<hr>
<h2 id="1-배경--목표"><strong>1. 배경 / 목표</strong></h2>
<h3 id="아키텍처-원칙"><strong>아키텍처 원칙</strong></h3>
<p><strong>WebView = 멍청한 지도 렌더러, 데이터·상태·UI = 전부 RN</strong></p>
<table>
<thead>
<tr>
<th><strong>영역</strong></th>
<th><strong>담당</strong></th>
</tr>
</thead>
<tbody><tr>
<td>장소 fetch, 필터, 바텀시트, 검색</td>
<td>React Native</td>
</tr>
<tr>
<td>지도 렌더링, 마커, 카메라</td>
<td>WebView (Kakao JS SDK)</td>
</tr>
<tr>
<td>RN ↔ WebView 통신</td>
<td><code>{ type, payload }</code> 메시지 프로토콜</td>
</tr>
</tbody></table>
<h3 id="구성"><strong>구성</strong></h3>
<p>web/map/          ← Vite로 빌드하는 지도 전용 페이지 (main.js)</p>
<p>components/map/   ← MapWebView.tsx</p>
<p>hooks/            ← useMapBridge.ts (브릿지 + MAP_READY 큐)</p>
<p>app/(tabs)/map/   ← 지도 탭 화면</p>
<p>지도 페이지는 <code>EXPO_PUBLIC_MAP_URL</code> (로컬: <code>http://localhost:5174</code>)로 WebView에 로드.</p>
<hr>
<h2 id="2-겪었던-증상-시간순"><strong>2. 겪었던 증상 (시간순)</strong></h2>
<h3 id="증상-a-빨간빈-화면만-보임"><strong>증상 A: 빨간/빈 화면만 보임</strong></h3>
<ul>
<li>Mac 브라우저 <code>http://localhost:5174</code> → 지도 OK</li>
<li>RN WebView → HTML만 보이거나 지도 없음</li>
</ul>
<p><strong>초기 가설 (틀린 것들)</strong></p>
<ul>
<li>❌ HTTPS가 아니라서? → 같은 HTTP인데 다른 Vite 앱(5174)은 WebView에서 됨</li>
<li>❌ localhost 접속 불가? → <code>load start/end</code> 로그 나옴, HTML 렌더됨</li>
<li>❌ 시뮬레이터 문제? → iOS 시뮬레이터 Safari에서는 동일 URL OK</li>
</ul>
<h3 id="증상-b-html텍스트는-보이는데-지도만-안-보임"><strong>증상 B: HTML/텍스트는 보이는데 지도만 안 보임</strong></h3>
<ul>
<li>WebView에서 <code>&lt;p&gt;</code> 디버그 텍스트는 보임</li>
<li>시뮬레이터 Safari에서는 텍스트 + 지도 둘 다 OK</li>
<li><strong>→ WebView 자체 문제가 아니라, 카카오 SDK 초기화/렌더링 문제</strong></li>
</ul>
<h3 id="증상-c-metro-로그로-단계-추적"><strong>증상 C: Metro 로그로 단계 추적</strong></h3>
<table>
<thead>
<tr>
<th><strong>로그</strong></th>
<th><strong>의미</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>PAGE_BOOT</code></td>
<td>main.js 실행됨</td>
</tr>
<tr>
<td><code>SDK_LOADING</code></td>
<td>SDK script 요청 시작</td>
</tr>
<tr>
<td><code>SDK_SCRIPT_LOADED</code></td>
<td>sdk.js 로드 완료</td>
</tr>
<tr>
<td><code>MAP_INIT_START</code></td>
<td>initMap 진입 시도</td>
</tr>
<tr>
<td><code>MAP_INIT_POLL</code> + <code>hasMapCtor: false</code></td>
<td><strong>kakao.maps는 있는데 Map 생성자가 영원히 안 생김</strong></td>
</tr>
<tr>
<td><code>MAP_INIT_ATTEMPT</code> → <code>MAP_READY</code></td>
<td>드디어 성공</td>
</tr>
</tbody></table>
<hr>
<h2 id="3-근본-원인"><strong>3. 근본 원인</strong></h2>
<h3 id="한-줄-요약"><strong>한 줄 요약</strong></h3>
<p><strong>카카오 SDK 1차 스크립트(<code>dapi.kakao.com</code>)는 로드됐지만, <code>Map</code> 생성자를 등록하는 2차 모듈(<code>daumcdn.net</code> 등) 요청이 iOS WebView ATS에 막혔다.</strong></p>
<h3 id="sdk-로딩-2단계"><strong>SDK 로딩 2단계</strong></h3>
<p>1단계: sdk.js (dapi.kakao.com)  →  kakao.maps + load() 함수만 생김  ✅</p>
<p>2단계: 내부 모듈 (daumcdn.net)  →  kakao.maps.Map 생성자 등록      ❌ (ATS 차단)</p>
<p>그래서 디버그 로그가 이렇게 나왔다:</p>
<p>{</p>
<p>&quot;hasKakao&quot;: true,</p>
<p>&quot;hasMaps&quot;: true,</p>
<p>&quot;hasMapCtor&quot;: false,</p>
<p>&quot;hasLoad&quot;: true,</p>
<p>&quot;mapsKeys&quot;: [&quot;onloadcallbacks&quot;, &quot;readyState&quot;, &quot;load&quot;, &quot;apikey&quot;, ...]</p>
<p>}</p>
<p>껍데기만 있고 <code>Map</code>이 없는 상태. <code>kakao.maps.load()</code> 콜백도 영원히 안 불림.</p>
<h3 id="safari-vs-rn-webview"><strong>Safari vs RN WebView</strong></h3>
<table>
<thead>
<tr>
<th></th>
<th><strong>Safari</strong></th>
<th><strong>RN WebView (WKWebView)</strong></th>
</tr>
</thead>
<tbody><tr>
<td>sdk.js</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>daumcdn.net 모듈</td>
<td>✅</td>
<td>❌ (ATS)</td>
</tr>
<tr>
<td>kakao.maps.Map</td>
<td>✅</td>
<td>❌</td>
</tr>
</tbody></table>
<p><strong>같은 URL, 같은 HTML인데 WKWebView만 실패</strong> → 네트워크 정책(ATS) 차이.</p>
<hr>
<h2 id="4-해결-방법"><strong>4. 해결 방법</strong></h2>
<h3 id="핵심-fix-appjson-ats-설정"><strong>핵심 fix: <code>app.json</code> ATS 설정</strong></h3>
<p>Expo <code>app.json</code> → prebuild → <code>ios/Info.plist</code>에 반영.</p>
<p>&quot;NSAppTransportSecurity&quot;: {</p>
<p>&quot;NSAllowsLocalNetworking&quot;: true,</p>
<p>&quot;NSExceptionDomains&quot;: {</p>
<p>&quot;kakao.com&quot;: {</p>
<p>&quot;NSIncludesSubdomains&quot;: true,</p>
<p>&quot;NSExceptionAllowsInsecureHTTPLoads&quot;: true,</p>
<p>&quot;NSExceptionRequiresForwardSecrecy&quot;: false</p>
<p>},</p>
<p>&quot;daum.net&quot;: { ... 동일 ... },</p>
<p>&quot;daumcdn.net&quot;: { ... 동일 ... }</p>
<p>}</p>
<p>}</p>
<table>
<thead>
<tr>
<th><strong>도메인</strong></th>
<th><strong>용도</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>kakao.com</code></td>
<td>SDK API (<code>dapi.kakao.com</code>)</td>
</tr>
<tr>
<td><code>daum.net</code> / <code>daumcdn.net</code></td>
<td>지도 타일·내부 모듈 CDN</td>
</tr>
</tbody></table>
<p>Android 로컬 dev: <code>&quot;usesCleartextTraffic&quot;: true</code></p>
<h3 id="네이티브-재빌드-필수"><strong>네이티브 재빌드 필수</strong></h3>
<p><code>app.json</code> 변경은 Metro reload로 <strong>반영 안 됨</strong>.</p>
<p>npx expo prebuild --clean --platform ios   <em># Info.plist 재생성</em></p>
<p>npx expo run:ios --no-build-cache          <em># 재빌드</em></p>
<blockquote>
<p><code>--clear</code>는 <code>expo start</code> 전용. <code>run:ios</code>는 <code>--no-build-cache</code>.</p>
</blockquote>
<p>재빌드 후:</p>
<p>MAP_INIT_ATTEMPT → MAP_READY {&quot;width&quot;:402,&quot;height&quot;:690}</p>
<hr>
<h2 id="5-디버깅-과정에서-시도했던-것들"><strong>5. 디버깅 과정에서 시도했던 것들</strong></h2>
<h3 id="효과-있었던-것"><strong>효과 있었던 것</strong></h3>
<table>
<thead>
<tr>
<th><strong>조치</strong></th>
<th><strong>효과</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>app.json</code> ATS (daumcdn.net)</td>
<td><strong>근본 해결</strong></td>
</tr>
<tr>
<td>단계별 <code>postMessage</code> 로그</td>
<td>원인 좁히기 (hasMapCtor)</td>
</tr>
<tr>
<td><code>main.js</code>에서 dynamic script load</td>
<td>HTML <code>onload</code> 속성보다 WebView에서 안정적</td>
</tr>
<tr>
<td><code>#map</code> flex 100% + <code>map.relayout()</code></td>
<td>레이아웃 이슈 예방</td>
</tr>
<tr>
<td><code>domStorageEnabled</code> 등 WebView props</td>
<td>카카오맵 일반 권장 설정</td>
</tr>
</tbody></table>
<h3 id="효과-없었거나-2차적인-것"><strong>효과 없었거나 2차적인 것</strong></h3>
<table>
<thead>
<tr>
<th><strong>조치</strong></th>
<th><strong>판단</strong></th>
</tr>
</thead>
<tbody><tr>
<td>HTTP vs HTTPS 가설</td>
<td>로컬 HTTP 다른 앱은 WebView OK → 원인 아님</td>
</tr>
<tr>
<td><code>messageQueue</code> / 폴링 / maps.load 재시도</td>
<td>ATS 해결 전엔 Map 자체가 안 생김</td>
</tr>
<tr>
<td><code>autoload=true/false</code> 전환</td>
<td>ATS 없으면 둘 다 Map 안 생김</td>
</tr>
<tr>
<td>HTML <code>&lt;script onload=&quot;...&quot;&gt;</code></td>
<td>WKWebView에서 콜백 미호출 cases</td>
</tr>
</tbody></table>
<hr>
<h2 id="6-브릿지-동작-원리"><strong>6. 브릿지 동작 원리</strong></h2>
<p>RN과 WebView는 <strong>서로 다른 JS 런타임</strong> → 함수 직접 호출 불가, <strong>메시지 프로토콜</strong> 사용.</p>
<h3 id="rn-→-webview"><strong>RN → WebView</strong></h3>
<p>mapRef.current?.post({ type: &#39;SET_MARKERS&#39;, payload: markers });</p>
<p>↓ <code>injectJavaScript</code>로 WebView 안에서 실행:</p>
<p>window.__handleNative({&quot;type&quot;:&quot;SET_MARKERS&quot;,&quot;payload&quot;:[...]}); true;</p>
<p>↓ <code>main.js</code>:</p>
<p>window.__handleNative = (msg) =&gt; {</p>
<p>switch (msg.type) {</p>
<p>case &#39;SET_MARKERS&#39;: renderMarkers(msg.payload); break;</p>
<p><em>// ...</em></p>
<p>}</p>
<p>};</p>
<p><strong>HTTP 요청이 아니라, RN이 WebView JS 컨텍스트에 코드를 주입(inject)하는 것.</strong></p>
<h3 id="webview-→-rn"><strong>WebView → RN</strong></h3>
<p>window.ReactNativeWebView.postMessage(JSON.stringify(msg));</p>
<p>↓ RN <code>onMessage</code> → <code>useMapBridge</code> switch</p>
<h3 id="map_ready-큐"><strong>MAP_READY 큐</strong></h3>
<p><code>MAP_READY</code> 전에 보낸 <code>post()</code>는 큐에 쌓았다가 ready 후 flush. RN은 타이밍 신경 안 써도 됨.</p>
<h3 id="webview-안에-react"><strong>WebView 안에 React?</strong></h3>
<p><strong>안 씀.</strong> WebView = 마커 + 카메라만. 검색/바텀시트/필터는 RN. vanilla JS + <code>types/map.ts</code> 프로토콜이 적합.</p>
<hr>
<h2 id="7-최종-파일별-역할"><strong>7. 최종 파일별 역할</strong></h2>
<table>
<thead>
<tr>
<th><strong>파일</strong></th>
<th><strong>역할</strong></th>
<th><strong>되돌리면 안 되는 것</strong></th>
</tr>
</thead>
<tbody><tr>
<td><code>app.json</code></td>
<td>iOS ATS, Android cleartext</td>
<td><strong>daum.net / daumcdn.net 예외</strong></td>
</tr>
<tr>
<td><code>web/map/index.html</code></td>
<td><code>#map</code> 레이아웃, <code>__KAKAO_JS_KEY</code></td>
<td>flex 100%, key 주입</td>
</tr>
<tr>
<td><code>web/map/main.js</code></td>
<td>브릿지 + 카카오 init</td>
<td><code>__handleNative</code>, <code>bootKakao</code>, <code>send</code></td>
</tr>
<tr>
<td><code>web/map/vite.config.js</code></td>
<td>키 치환, port 5174</td>
<td>inject-kakao-key 플러그인</td>
</tr>
<tr>
<td><code>hooks/useMapBridge.ts</code></td>
<td>post/onMessage, MAP_READY 큐</td>
<td>—</td>
</tr>
<tr>
<td><code>components/map/MapWebView.tsx</code></td>
<td>WebView 래퍼</td>
<td>domStorageEnabled, EXPO_PUBLIC_MAP_URL</td>
</tr>
</tbody></table>
<hr>
<h2 id="8-팀-온보딩-체크리스트"><strong>8. 팀 온보딩 체크리스트</strong></h2>
<h3 id="로컬-dev-시작"><strong>로컬 dev 시작</strong></h3>
<ol>
<li><code>web/map/.env</code> → <code>VITE_KAKAO_JS_KEY</code></li>
<li><code>.env</code> → <code>EXPO_PUBLIC_MAP_URL=http://localhost:5174</code></li>
<li>카카오 콘솔 → <code>http://localhost:5174</code> 도메인 등록</li>
<li><code>cd web/map &amp;&amp; npm run dev</code></li>
<li><code>npx expo run:ios</code> (최초 또는 app.json 변경 후 prebuild)</li>
</ol>
<h3 id="지도-안-뜰-때"><strong>지도 안 뜰 때</strong></h3>
<ul>
<li><input disabled="" type="checkbox"> Vite 5174 실행 중?</li>
<li><input disabled="" type="checkbox"> Metro에서 <code>MAP_READY</code> / <code>ERROR</code> 확인?</li>
<li><input disabled="" type="checkbox"> <code>app.json</code> ATS 커밋돼 있고 네이티브 재빌드 했?</li>
<li><input disabled="" type="checkbox"> Safari(시뮬레이터)에서 localhost:5174 OK? → WebView만 fail이면 ATS/rebuild 의심</li>
</ul>
<h3 id="prebuild---clean-하면-ats-날아가"><strong>prebuild --clean 하면 ATS 날아가?</strong></h3>
<p><strong>아님.</strong> <code>app.json</code>이 git에 있으면 clean 후에도 Info.plist에 다시 생성됨.</p>
<p>날아가는 건 Xcode에서 <code>Info.plist</code>만 수기 수정한 경우.</p>
<hr>
<h2 id="9-배운-것-takeaways"><strong>9. 배운 것 (Takeaways)</strong></h2>
<ol>
<li><p><strong>WebView 문제 vs 지도 문제 구분</strong></p>
<p> HTML 렌더 + <code>PAGE_BOOT</code> OK → WebView 연결 OK. 지도만 fail → SDK/ATS/레이아웃.</p>
</li>
<li><p><strong>Safari OK + RN WebView fail</strong></p>
<p> 같은 URL이면 SDK 도메인보다 <strong>iOS ATS / WKWebView 네트워크 정책</strong> 먼저 의심.</p>
</li>
<li><p><strong>카카오 SDK는 2단계 로드</strong></p>
<p> sdk.js만 OK ≠ 지도 OK. <code>hasMapCtor: false</code> = 2차 모듈 실패.</p>
</li>
<li><p><strong>Expo에서 네이티브 설정 = app.json</strong></p>
<p> Info.plist 수기 수정 &lt; app.json 커밋 + prebuild. 실무에서도 흔한 패턴.</p>
</li>
<li><p><strong>디버그는 postMessage 단계 로그가 최고</strong></p>
<p> <code>injectJavaScript</code> / <code>postMessage</code> 경계에서 <code>{ type }</code> 찍으면 원인 빠르게 좁혀짐.</p>
</li>
<li><p><strong>HTTPS 배포 URL은 로컬 WebView 디버깅 우회책</strong></p>
<p> 아키텍처 문서 권장대로 배포 URL 쓰면 ATS 이슈 적음. 로컬 dev는 ATS 설정 필요.</p>
</li>
</ol>
<hr>
<h2 id="10-참고-링크"><strong>10. 참고 링크</strong></h2>
<ul>
<li><strong>react-native-webview — Communicating with JS</strong></li>
<li><strong>Expo — App Transport Security</strong></li>
<li><strong>카카오맵 JS SDK — 도메인 등록</strong></li>
<li>프로젝트 내부: <code>docs/specs/map-webview-architecture.md</code></li>
</ul>
<hr>
<h2 id="11-한-줄-회고"><strong>11. 한 줄 회고</strong></h2>
<blockquote>
<p>WebView + 카카오맵에서 &quot;지도만 안 보인다&quot;는 증상의 80%는 bridge 타이밍이 아니라, <strong>SDK 2차 모듈 CDN이 iOS ATS에 막힌 것</strong>이었다.</p>
<p>브라우저/Safari와 WebView의 차이를 먼저 의심하고, <code>postMessage</code>로 SDK 로딩 단계를 계측하면 빠르게 찾을 수 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[OMC 빠른 참조 카드]]></title>
            <link>https://velog.io/@angel_eugnen/OMC-%EB%B9%A0%EB%A5%B8-%EC%B0%B8%EC%A1%B0-%EC%B9%B4%EB%93%9C</link>
            <guid>https://velog.io/@angel_eugnen/OMC-%EB%B9%A0%EB%A5%B8-%EC%B0%B8%EC%A1%B0-%EC%B9%B4%EB%93%9C</guid>
            <pubDate>Wed, 24 Jun 2026 01:34:26 GMT</pubDate>
            <description><![CDATA[<h1 id="🚀-omc-oh-my-claude-code-빠른-참조-카드-v460">🚀 OMC (Oh My Claude Code) 빠른 참조 카드 v4.6.0</h1>
<h2 id="📌-처음-시작">📌 처음 시작</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/deepinit</code></td>
<td>프로젝트 문서화</td>
</tr>
<tr>
<td><code>/deep-interview &#39;아이디어&#39;</code></td>
<td>요구사항 명확화 (신기능)</td>
</tr>
<tr>
<td><code>/omc-doctor</code></td>
<td>설치 진단</td>
</tr>
</tbody></table>
<hr>
<h2 id="📝-계획">📝 계획</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>plan this: [기능]</code></td>
<td>계획 수립 후 승인</td>
</tr>
<tr>
<td><code>ralplan: [기능]</code></td>
<td>전문가 합의 기반 계획</td>
</tr>
</tbody></table>
<hr>
<h2 id="⚡-실행-강도-순">⚡ 실행 (강도 순)</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>autopilot: [지시]</code></td>
<td>자율 실행 (계획 → 구현 → 검증 자동)</td>
</tr>
<tr>
<td><code>ralph: [지시]</code></td>
<td>완료까지 반복 (PRD 자동 생성)</td>
</tr>
<tr>
<td><code>ulw [지시]</code></td>
<td>최대 병렬 실행</td>
</tr>
<tr>
<td><code>team N:agent [지시]</code></td>
<td>N개 에이전트 팀 협업</td>
</tr>
<tr>
<td><code>eco: [지시]</code></td>
<td>토큰 절약 모드</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔍-분석--리뷰">🔍 분석 &amp; 리뷰</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>analyze: [문제]</code></td>
<td>디버깅 / 원인 분석</td>
</tr>
<tr>
<td><code>review code</code></td>
<td>종합 코드 리뷰</td>
</tr>
<tr>
<td><code>security review</code></td>
<td>보안 취약점 점검</td>
</tr>
<tr>
<td><code>fix build</code></td>
<td>빌드 / 타입 에러 수정</td>
</tr>
<tr>
<td><code>tdd: [기능]</code></td>
<td>테스트 주도 개발</td>
</tr>
</tbody></table>
<hr>
<h2 id="🤖-멀티-ai">🤖 멀티 AI</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/omc-teams N:ai [지시]</code></td>
<td>Codex / Gemini 병렬 활용</td>
</tr>
<tr>
<td><code>/ccg [지시]</code></td>
<td>3개 모델 동시 분석</td>
</tr>
</tbody></table>
<hr>
<h2 id="🛠️-유틸리티">🛠️ 유틸리티</h2>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/note [내용]</code></td>
<td>메모 저장</td>
</tr>
<tr>
<td><code>&lt;remember&gt;...&lt;/remember&gt;</code></td>
<td>7일 기억</td>
</tr>
<tr>
<td><code>&lt;remember priority&gt;...&lt;/remember&gt;</code></td>
<td>영구 기억</td>
</tr>
<tr>
<td><code>/cancel</code></td>
<td>현재 모드 취소</td>
</tr>
<tr>
<td><code>/cancel --force</code></td>
<td>전체 초기화</td>
</tr>
<tr>
<td><code>/trace</code></td>
<td>실행 타임라인</td>
</tr>
<tr>
<td><code>/learner</code></td>
<td>패턴 추출 및 재사용</td>
</tr>
</tbody></table>
<hr>
<h2 id="📊-참고">📊 참고</h2>
<h3 id="실행-강도">실행 강도</h3>
<pre><code class="language-text">대화 &lt; plan &lt; autopilot &lt; ralph &lt; team ralph</code></pre>
<h3 id="비용">비용</h3>
<pre><code class="language-text">haiku &lt; sonnet &lt;&lt; opus</code></pre>
<h3 id="github">GitHub</h3>
<pre><code class="language-text">github.com/Yeachan-Heo/oh-my-claudecode</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 첫 시작]]></title>
            <link>https://velog.io/@angel_eugnen/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@angel_eugnen/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B2%AB-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Wed, 24 Jun 2026 01:14:26 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-첫-시작">프로젝트 첫 시작</h2>
<p>새프로젝트에 들어가면 항상 이것부터 해라</p>
<blockquote>
<p>/deepinit</p>
</blockquote>
<p>OMC가 프로젝트 구조를 분서갛고 AGENTS.md 계층 문서를 자동 생성한다.</p>
<h2 id="모델-라우팅">모델 라우팅</h2>
<p>OMC는 작업 복잡도에 따라 자동으로 모델을 선택한다.</p>
<pre><code>haiku  (저비용, 빠름)
  → 파일 탐색, 구조 분석, 문서 작성, 간단한 검색
  → 예: explore, writer 에이전트

sonnet (중간 비용, 균형)
  → 일반 구현, 테스트, 디버깅, 리팩토링
  → 예: executor, test-engineer, debugger 에이전트

opus   (고비용, 최고 성능)
  → 아키텍처 설계, 복잡한 분석, 종합 리뷰, 비판적 검토
  → 예: architect, code-reviewer, critic 에이전트</code></pre><p>실제로 이렇게 동작한다.
사용자: &quot;autopilot: 인증 시스템 만들어줘&quot;</p>
<p>OMC 내부:</p>
<pre><code>  1. explore    (haiku)  → 현재 코드 구조 파악
  2. planner    (opus)   → 구현 계획 수립
  3. architect  (opus)   → 인터페이스 설계
  4. executor   (sonnet) × 3 → 병렬 구현
  5. test-engineer (sonnet) → 테스트 작성
  6. verifier   (sonnet) → 완료 검증</code></pre><h2 id="autopilot---완전-자율-실행">Autopilot - 완전 자율 실행</h2>
<p>복잡한 단일 기능 구현에 최적, 계획부터 검증까지 자동</p>
<p>트리거 키워드: <code>autopilot:</code>, <code>build me</code>, <code>I want a</code></p>
<pre><code>Phase 1 — 확장 (Expansion)
  &quot;할일 목록&quot;을 구체적 요구사항으로 확장
  → 할일 추가/삭제/완료 토글, 로컬 상태 관리, 반응형 UI

Phase 2 — 계획 (Planning)
  planner 에이전트가 작업 분해
  → Todo 타입 정의 → TodoItem 컴포넌트 → TodoList → 페이지 통합

Phase 3 — 실행 (Execution)
  executor 에이전트들 병렬 구현
  → src/types/todo.ts 생성
  → src/components/TodoItem.tsx 생성
  → src/components/TodoList.tsx 생성
  → src/app/todos/page.tsx 수정

Phase 4 — QA
  qa-tester 가 빌드/린트/테스트 실행
  → npm run build → 통과
  → npm run lint  → 통과

Phase 5 — 검증 (Verification)
  verifier 에이전트가 요구사항 체크리스트 확인
  → ✓ 할일 추가 기능
  → ✓ 삭제 기능
  → ✓ 완료 토글
  → &quot;완료 확인됨&quot;</code></pre><h2 id="ralph-끝까지-멈추지-않는-모드">ralph: 끝까지 멈추지 않는 모드</h2>
<p>v4.6.0부터 Ralph는 자동으로 prd.json을 생성합니다. 모든 수용 기준이 통과할 때까지 반복합니다.</p>
<p>트리거 키워드: ralph:, don&#39;t stop, must complete</p>
<pre><code>사용자: &quot;ralph: 이 앱에 JWT 인증 시스템 전체 구현해줘&quot;

자동 생성: .omc/prd.json
  {
    &quot;title&quot;: &quot;JWT 인증 시스템&quot;,
    &quot;acceptance_criteria&quot;: [
      &quot;회원가입 API (/api/auth/register) 동작&quot;,
      &quot;로그인 API (/api/auth/login) JWT 발급&quot;,
      &quot;보호된 라우트에서 토큰 검증&quot;,
      &quot;리프레시 토큰 갱신&quot;,
      &quot;모든 에러 케이스 핸들링&quot;
    ]
  }

Iteration 1: 기본 구조 + 회원가입 구현
Iteration 2: 로그인 + JWT 발급 구현
Iteration 3: 미들웨어 + 토큰 검증 구현
Iteration 4: 리프레시 토큰 구현
Iteration 5: verifier 검증 → &quot;에러 핸들링 미흡&quot;
Iteration 6: 에러 핸들링 전체 보강
Iteration 7: verifier 검증 → &quot;모든 기준 통과&quot; → 완료</code></pre><p>PRD 없이 시작하려면: &quot;ralph --no-prd: 로그인 기능 만들어줘&quot;</p>
<h2 id="ultrawork--최대-병렬-실행">Ultrawork — 최대 병렬 실행</h2>
<p>5개 이상의 에이전트를 동시에 투입. 독립적인 작업 대량 처리에 최적.</p>
<p>트리거 키워드: ultrawork, ulw</p>
<pre><code>사용자: &quot;ulw: 이 5개 API 엔드포인트 전부 구현해줘
        (users, posts, comments, likes, notifications)&quot;

동시 실행:
  executor #1 → /api/users 구현    ─┐
  executor #2 → /api/posts 구현    ─┤
  executor #3 → /api/comments 구현 ─┤ 병렬
  executor #4 → /api/likes 구현    ─┤
  executor #5 → /api/notifications ─┘

총 소요 시간 ≈ 단일 작업 시간 × 1.2 (병렬 오버헤드 포함)</code></pre><h2 id="ecomode--토큰-효율-최대화">Ecomode — 토큰 효율 최대화</h2>
<p>비용이 중요할 때. 가능한 한 저렴한 모델 우선 사용.</p>
<p>트리거 키워드: eco</p>
<pre><code>사용자: &quot;eco: 린트 에러 전부 수정해줘&quot;

→ haiku 모델로 먼저 시도
→ haiku가 해결 못하면 sonnet으로 에스컬레이션
→ opus는 절대적으로 필요한 경우에만 사용
→ 예상 비용 절감: 30~50%</code></pre><h2 id="team--네이티브-팀-협업">Team — 네이티브 팀 협업</h2>
<p>Claude Code 네이티브 팀 기능을 활용한 조율 방식.</p>
<p>트리거 키워드: team</p>
<pre><code>사용자: &quot;team 3:executor 모든 컴포넌트 테스트 코드 작성&quot;

→ executor 에이전트 3개가 팀으로 조율하며 작업
→ 실시간 메시지로 작업 상태 공유
→ 공유 태스크 리스트에서 각자 작업 픽업

파이프라인:
  team-plan → team-prd → team-exec → team-verify → team-fix</code></pre><h2 id="deep-interview--막연한-아이디어-명확화">Deep Interview — 막연한 아이디어 명확화</h2>
<p>v4.6.0 신기능. 실행 전 소크라테스식 질문으로 모호함을 제거합니다.</p>
<p>트리거 키워드: /deep-interview</p>
<pre><code>사용자: &quot;/deep-interview &#39;대시보드 만들어줘&#39;&quot;

OMC가 수학적 모호성 점수를 계산하고 질문 생성:

Q1: &quot;어떤 종류의 데이터를 보여주는 대시보드인가요?
    (a) 사용자 분석  (b) 매출 현황  (c) 시스템 모니터링  (d) 기타&quot;

Q2: &quot;실시간 업데이트가 필요한가요?&quot;

Q3: &quot;대상 사용자는 누구인가요? 기술적 지식 수준은?&quot;

→ 명확성 점수가 임계값 이상이 되면 실행 시작
→ 애매한 요구사항으로 인한 재작업 방지</code></pre><p>사용 시점: 요구사항이 추상적이거나 규모가 큰 작업 전</p>
<h2 id="plan--전략적-계획-수립">Plan — 전략적 계획 수립</h2>
<p>실행 전 계획을 먼저 세우고 승인을 받는 방식.</p>
<p>트리거 키워드: plan this:, plan the</p>
<pre><code>사용자: &quot;plan this: 결제 시스템 연동&quot;

→ 현재 코드 분석 (explore)
→ 구현 계획 상세 작성
→ 계획을 사용자에게 제시 → 승인 요청

출력 예시:
  구현 계획: 결제 시스템 연동

  Step 1: 환경 변수 설정 (5분)
    - .env에 STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY 추가

  Step 2: Stripe SDK 설치 및 클라이언트 설정 (10분)
    - npm install @stripe/stripe-js stripe
    - src/lib/stripe.ts 생성

  Step 3: 결제 API 라우트 구현 (20분)
    - POST /api/checkout → Stripe 세션 생성
    - GET /api/checkout/success → 결제 확인

  Step 4: 프론트엔드 결제 버튼 구현 (15분)
    - src/components/CheckoutButton.tsx 생성

  Step 5: 웹훅 처리 (15분)
    - POST /api/webhooks/stripe → 결제 완료 처리

  이 계획으로 진행할까요?</code></pre><h2 id="ralplan--합의-기반-계획">Ralplan — 합의 기반 계획</h2>
<p>Planner, Architect, Critic 세 에이전트가 합의에 이를 때까지 반복 검토.</p>
<p>트리거 키워드: ralplan, consensus plan</p>
<pre><code>사용자: &quot;ralplan: 멀티테넌트 SaaS 아키텍처 설계해줘&quot;

Round 1:
  planner   → 초안 작성 (마이크로서비스 제안)
  architect → 검토: &quot;데이터 격리 방식 명시 필요&quot;
  critic    → 비판: &quot;배포 복잡성 과소평가됨&quot;

Round 2:
  planner   → 수정안 (모놀리스 + 모듈식)
  architect → 수정: &quot;테넌트별 스키마 격리 추가&quot;
  critic    → &quot;허용 가능, 스케일링 계획 보강 필요&quot;

Round 3:
  합의 도달 → 최종 아키텍처 문서 생성

옵션: &quot;ralplan --deliberate: ...&quot; 
  → 사전 위험 분석 + 확장된 테스트 계획 포함</code></pre><h2 id="claude-code-모드--에이전트-완전-정리">Claude Code 모드 &amp; 에이전트 완전 정리</h2>
<p>Claude Code는 단순히 코드 생성만 하는 것이 아니라, 작업 규모와 목적에 따라 다양한 실행 모드와 전문 에이전트를 활용할 수 있다.</p>
<hr>
<h3 id="1-작업-규모별-추천-모드">1. 작업 규모별 추천 모드</h3>
<table>
<thead>
<tr>
<th>작업 유형</th>
<th>추천 모드</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>버그 1개 수정</td>
<td>일반 대화</td>
<td>단순하고 빠르게 해결 가능</td>
</tr>
<tr>
<td>컴포넌트 1개 추가</td>
<td><code>plan → 직접 구현</code></td>
<td>구현 전에 구조를 먼저 정리</td>
</tr>
<tr>
<td>기능 1개 개발 (여러 파일 수정)</td>
<td><code>autopilot</code></td>
<td>계획 → 구현 → 검증까지 자동 수행</td>
</tr>
<tr>
<td>대규모 기능 개발</td>
<td><code>ralph</code></td>
<td>완료될 때까지 반복적으로 작업</td>
</tr>
<tr>
<td>풀스택 프로젝트 구축</td>
<td><code>team + ralph</code></td>
<td>여러 에이전트 협업 + 지속 실행</td>
</tr>
<tr>
<td>여러 파일 리팩토링</td>
<td><code>ulw</code></td>
<td>병렬 처리에 강함</td>
</tr>
<tr>
<td>아키텍처 설계</td>
<td><code>ralplan</code></td>
<td>전문가 관점으로 설계 합의</td>
</tr>
<tr>
<td>막연한 아이디어 정리</td>
<td><code>/deep-interview</code></td>
<td>요구사항을 먼저 명확화</td>
</tr>
<tr>
<td>비용 절감 우선</td>
<td><code>eco</code></td>
<td>저비용 모델 중심으로 실행</td>
</tr>
</tbody></table>
<hr>
<h3 id="2-빌드--분석-에이전트">2. 빌드 &amp; 분석 에이전트</h3>
<p>프로젝트를 이해하고 구현하기 위한 핵심 에이전트들이다.</p>
<table>
<thead>
<tr>
<th>에이전트</th>
<th>모델</th>
<th>역할</th>
<th>활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>explore</td>
<td>Haiku</td>
<td>코드 탐색, 파일 구조 분석</td>
<td>&quot;인증 관련 파일 찾아줘&quot;</td>
</tr>
<tr>
<td>analyst</td>
<td>Opus</td>
<td>요구사항 분석, 엣지케이스 정리</td>
<td>&quot;이 기능의 예외 상황 정리해줘&quot;</td>
</tr>
<tr>
<td>planner</td>
<td>Opus</td>
<td>작업 순서 계획 수립</td>
<td>&quot;마이그레이션 순서 알려줘&quot;</td>
</tr>
<tr>
<td>architect</td>
<td>Opus</td>
<td>시스템 설계 및 인터페이스 정의</td>
<td>&quot;확장 가능한 API 구조 설계해줘&quot;</td>
</tr>
<tr>
<td>debugger</td>
<td>Sonnet</td>
<td>버그 원인 분석</td>
<td>&quot;이 에러가 왜 발생하는지 찾아줘&quot;</td>
</tr>
<tr>
<td>executor</td>
<td>Sonnet</td>
<td>구현 및 리팩토링</td>
<td>&quot;이 코드를 TypeScript로 변환해줘&quot;</td>
</tr>
<tr>
<td>deep-executor</td>
<td>Opus</td>
<td>복잡한 다중 파일 구현</td>
<td>대규모 기능 개발</td>
</tr>
<tr>
<td>verifier</td>
<td>Sonnet</td>
<td>구현 결과 검증</td>
<td>&quot;정말 모든 기능이 동작하는지 확인해줘&quot;</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-리뷰-에이전트">3. 리뷰 에이전트</h3>
<p>코드 품질과 안정성을 검토하는 역할을 담당한다.</p>
<table>
<thead>
<tr>
<th>에이전트</th>
<th>모델</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>quality-reviewer</td>
<td>Sonnet</td>
<td>로직 결함, 성능 문제, 안티패턴 점검</td>
</tr>
<tr>
<td>security-reviewer</td>
<td>Sonnet</td>
<td>보안 취약점 분석</td>
</tr>
<tr>
<td>code-reviewer</td>
<td>Opus</td>
<td>종합 코드 리뷰 및 API 계약 검토</td>
</tr>
</tbody></table>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-bash">review code</code></pre>
<p>→ code-reviewer 실행</p>
<pre><code class="language-bash">security review</code></pre>
<p>→ security-reviewer 실행</p>
<hr>
<h3 id="4-도메인-전문가-에이전트">4. 도메인 전문가 에이전트</h3>
<p>특정 분야에 특화된 전문가 역할을 수행한다.</p>
<table>
<thead>
<tr>
<th>에이전트</th>
<th>모델</th>
<th>역할</th>
<th>활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>test-engineer</td>
<td>Sonnet</td>
<td>테스트 전략 및 TDD</td>
<td>&quot;단위 테스트 작성해줘&quot;</td>
</tr>
<tr>
<td>build-fixer</td>
<td>Sonnet</td>
<td>빌드 및 타입 에러 해결</td>
<td>&quot;타입 에러 전부 수정해줘&quot;</td>
</tr>
<tr>
<td>designer</td>
<td>Sonnet</td>
<td>UI/UX 설계</td>
<td>&quot;폼 UX 개선해줘&quot;</td>
</tr>
<tr>
<td>writer</td>
<td>Haiku</td>
<td>문서 작성</td>
<td>README, API 문서 작성</td>
</tr>
<tr>
<td>qa-tester</td>
<td>Sonnet</td>
<td>E2E 및 런타임 검증</td>
<td>&quot;실제로 테스트해봐&quot;</td>
</tr>
<tr>
<td>scientist</td>
<td>Sonnet</td>
<td>데이터 분석</td>
<td>로그 분석, 통계 분석</td>
</tr>
<tr>
<td>document-specialist</td>
<td>Sonnet</td>
<td>공식 문서 조사</td>
<td>Next.js, React 문서 확인</td>
</tr>
<tr>
<td>git-master</td>
<td>Sonnet</td>
<td>Git 관리</td>
<td>커밋 정리, PR 작성</td>
</tr>
<tr>
<td>code-simplifier</td>
<td>Opus</td>
<td>복잡한 코드 단순화</td>
<td>중복 제거, 리팩토링</td>
</tr>
</tbody></table>
<hr>
<h3 id="5-조율-에이전트">5. 조율 에이전트</h3>
<p>다른 에이전트가 만든 결과를 비판적으로 검토한다.</p>
<table>
<thead>
<tr>
<th>에이전트</th>
<th>모델</th>
<th>역할</th>
<th>활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>critic</td>
<td>Opus</td>
<td>설계 및 계획 검토</td>
<td>&quot;이 아키텍처의 문제점을 찾아줘&quot;</td>
</tr>
</tbody></table>
<hr>
<h3 id="추천-활용-패턴">추천 활용 패턴</h3>
<ul>
<li>기능 개발</li>
</ul>
<pre><code class="language-text">analyst
 → planner
 → architect
 → executor
 → verifier</code></pre>
<p>요구사항 분석 → 설계 → 구현 → 검증</p>
<hr>
<ul>
<li>버그 수정</li>
</ul>
<pre><code class="language-text">explore
 → debugger
 → executor
 → verifier</code></pre>
<p>원인 파악 → 수정 → 검증</p>
<hr>
<ul>
<li>코드 리뷰</li>
</ul>
<pre><code class="language-text">quality-reviewer
 → security-reviewer
 → code-reviewer</code></pre>
<p>품질 → 보안 → 최종 리뷰</p>
<hr>
<ul>
<li>대규모 프로젝트</li>
</ul>
<pre><code class="language-text">team
 + ralph
 + architect
 + deep-executor
 + verifier</code></pre>
<p>설계부터 구현, 검증까지 자동화된 워크플로우 구성 가능</p>
<hr>
<h3 id="한-줄-요약">한 줄 요약</h3>
<ul>
<li>작은 작업 → 일반 대화</li>
<li>기능 개발 → autopilot</li>
<li>대규모 개발 → ralph</li>
<li>설계 → architect / ralplan</li>
<li>리뷰 → code-reviewer</li>
<li>보안 → security-reviewer</li>
<li>테스트 → test-engineer</li>
<li>Git 작업 → git-master</li>
</ul>
<p>작업 규모가 커질수록 &quot;계획 → 구현 → 검증&quot;을 자동으로 반복하는 모드가 효율적이다.</p>
<h2 id="6-매직-키워드--슬래시-명령어-레퍼런스">6. 매직 키워드 &amp; 슬래시 명령어 레퍼런스</h2>
<p>Claude Code는 특정 키워드나 슬래시 명령어를 입력하면 자동으로 적절한 실행 모드와 에이전트를 활성화할 수 있다.</p>
<hr>
<h3 id="매직-키워드magic-keywords">매직 키워드(Magic Keywords)</h3>
<p>프롬프트에 특정 키워드를 포함하면 자동으로 해당 모드가 활성화된다.</p>
<h3 id="자율-실행-계열">자율 실행 계열</h3>
<table>
<thead>
<tr>
<th>키워드</th>
<th>실행 모드</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>autopilot:</code></td>
<td>완전 자율 실행</td>
<td><code>autopilot: 검색 기능 추가해줘</code></td>
</tr>
<tr>
<td><code>build me</code></td>
<td>완전 자율 실행</td>
<td><code>build me a REST API</code></td>
</tr>
<tr>
<td><code>ralph:</code></td>
<td>완료까지 반복 실행</td>
<td><code>ralph: 인증 시스템 전체 구현</code></td>
</tr>
<tr>
<td><code>don&#39;t stop</code></td>
<td>완료 시까지 반복</td>
<td><code>don&#39;t stop until all tests pass</code></td>
</tr>
</tbody></table>
<h3 id="언제-사용할까">언제 사용할까?</h3>
<ul>
<li>여러 파일 수정이 필요한 기능 개발</li>
<li>구현 → 테스트 → 수정 과정을 자동으로 돌리고 싶을 때</li>
<li>중간 승인 없이 끝까지 진행시키고 싶을 때</li>
</ul>
<hr>
<h3 id="병렬-작업-계열">병렬 작업 계열</h3>
<table>
<thead>
<tr>
<th>키워드</th>
<th>실행 모드</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>ulw</code></td>
<td>최대 병렬 처리</td>
<td><code>ulw 이 10개 컴포넌트 전부 리팩토링</code></td>
</tr>
<tr>
<td><code>ultrawork</code></td>
<td>최대 병렬 처리</td>
<td><code>ultrawork: API 5개 동시 구현</code></td>
</tr>
</tbody></table>
<h3 id="언제-사용할까-1">언제 사용할까?</h3>
<ul>
<li>많은 파일을 동시에 수정해야 할 때</li>
<li>독립적인 작업을 병렬로 처리할 수 있을 때</li>
<li>대규모 리팩토링 작업</li>
</ul>
<hr>
<h3 id="계획-수립-계열">계획 수립 계열</h3>
<table>
<thead>
<tr>
<th>키워드</th>
<th>실행 모드</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>plan this:</code></td>
<td>작업 계획 수립</td>
<td><code>plan this: 결제 시스템 연동</code></td>
</tr>
<tr>
<td><code>ralplan</code></td>
<td>전문가 합의 기반 설계</td>
<td><code>ralplan: DB 스키마 설계</code></td>
</tr>
</tbody></table>
<h3 id="언제-사용할까-2">언제 사용할까?</h3>
<ul>
<li>구현 전에 설계를 검토하고 싶을 때</li>
<li>시스템 구조를 먼저 정의해야 할 때</li>
<li>대규모 기능 개발 전</li>
</ul>
<hr>
<h2 id="분석-및-디버깅-계열">분석 및 디버깅 계열</h2>
<table>
<thead>
<tr>
<th>키워드</th>
<th>실행 모드</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>analyze</code></td>
<td>분석 및 원인 추적</td>
<td><code>analyze: 메모리 누수 원인 찾아줘</code></td>
</tr>
<tr>
<td><code>debug</code></td>
<td>디버깅 모드</td>
<td><code>debug: API 500 에러 원인 분석</code></td>
</tr>
</tbody></table>
<h3 id="언제-사용할까-3">언제 사용할까?</h3>
<ul>
<li>에러의 근본 원인을 찾고 싶을 때</li>
<li>성능 저하나 메모리 문제 분석</li>
<li>복잡한 버그 조사</li>
</ul>
<hr>
<h3 id="리뷰-및-품질-관리-계열">리뷰 및 품질 관리 계열</h3>
<table>
<thead>
<tr>
<th>키워드</th>
<th>실행 모드</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>review code</code></td>
<td>종합 코드 리뷰</td>
<td><code>review code</code></td>
</tr>
<tr>
<td><code>security review</code></td>
<td>보안 리뷰</td>
<td><code>security review</code></td>
</tr>
<tr>
<td><code>fix build</code></td>
<td>빌드 오류 수정</td>
<td><code>fix build</code></td>
</tr>
<tr>
<td><code>type errors</code></td>
<td>타입 오류 수정</td>
<td><code>fix all type errors</code></td>
</tr>
<tr>
<td><code>tdd</code></td>
<td>테스트 주도 개발</td>
<td><code>tdd: 로그인 기능 구현</code></td>
</tr>
</tbody></table>
<h3 id="언제-사용할까-4">언제 사용할까?</h3>
<ul>
<li>PR 생성 전</li>
<li>배포 직전 점검</li>
<li>TypeScript 마이그레이션</li>
<li>테스트 코드 작성</li>
</ul>
<hr>
<h2 id="슬래시-명령어slash-commands">슬래시 명령어(Slash Commands)</h2>
<p>직접 입력하여 사용할 수 있는 명령어들이다.</p>
<hr>
<h3 id="핵심-워크플로우">핵심 워크플로우</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/deepinit</code></td>
<td>프로젝트용 AGENTS.md 계층 구조 생성</td>
</tr>
<tr>
<td><code>/deep-interview</code></td>
<td>요구사항 명확화 인터뷰</td>
</tr>
<tr>
<td><code>/autopilot</code></td>
<td>자율 실행 모드</td>
</tr>
<tr>
<td><code>/ralph</code></td>
<td>완료 시까지 반복 실행</td>
</tr>
<tr>
<td><code>/ultrawork</code></td>
<td>최대 병렬 작업</td>
</tr>
<tr>
<td><code>/team</code></td>
<td>여러 에이전트 협업</td>
</tr>
<tr>
<td><code>/plan</code></td>
<td>작업 계획 수립</td>
</tr>
<tr>
<td><code>/ralplan</code></td>
<td>합의 기반 설계</td>
</tr>
<tr>
<td><code>/analyze</code></td>
<td>분석 및 디버깅</td>
</tr>
</tbody></table>
<hr>
<h3 id="코드-품질-관리">코드 품질 관리</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/code-review</code></td>
<td>종합 코드 리뷰</td>
</tr>
<tr>
<td><code>/security-review</code></td>
<td>보안 취약점 점검</td>
</tr>
<tr>
<td><code>/build-fix</code></td>
<td>빌드 오류 수정</td>
</tr>
<tr>
<td><code>/tdd</code></td>
<td>테스트 주도 개발</td>
</tr>
<tr>
<td><code>/sciomc</code></td>
<td>데이터 분석 및 통계 작업</td>
</tr>
</tbody></table>
<hr>
<h3 id="멀티-ai-협업">멀티 AI 협업</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/omc-teams</code></td>
<td>Codex, Gemini 등 외부 AI 협업</td>
</tr>
<tr>
<td><code>/ccg</code></td>
<td>Claude + Codex + Gemini 동시 활용</td>
</tr>
</tbody></table>
<hr>
<h3 id="유틸리티">유틸리티</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>/note</code></td>
<td>메모 저장</td>
</tr>
<tr>
<td><code>/cancel</code></td>
<td>현재 작업 취소</td>
</tr>
<tr>
<td><code>/cancel --force</code></td>
<td>모든 상태 초기화</td>
</tr>
<tr>
<td><code>/trace</code></td>
<td>에이전트 실행 로그 확인</td>
</tr>
<tr>
<td><code>/hud</code></td>
<td>HUD 상태바 설정</td>
</tr>
<tr>
<td><code>/learner</code></td>
<td>재사용 가능한 패턴 추출</td>
</tr>
<tr>
<td><code>/skill</code></td>
<td>커스텀 스킬 관리</td>
</tr>
<tr>
<td><code>/configure-notifications</code></td>
<td>알림 설정</td>
</tr>
<tr>
<td><code>/omc-setup</code></td>
<td>OMC 재설정</td>
</tr>
<tr>
<td><code>/omc-doctor</code></td>
<td>설치 진단</td>
</tr>
<tr>
<td><code>/omc-help</code></td>
<td>도움말</td>
</tr>
</tbody></table>
<hr>
<h2 id="추천-사용-흐름">추천 사용 흐름</h2>
<h3 id="기능-개발">기능 개발</h3>
<pre><code class="language-text">/deep-interview
→ /plan
→ /autopilot
→ /code-review</code></pre>
<hr>
<h3 id="버그-수정">버그 수정</h3>
<pre><code class="language-text">/analyze
→ debug
→ fix build
→ review code</code></pre>
<hr>
<h3 id="대규모-프로젝트">대규모 프로젝트</h3>
<pre><code class="language-text">/team
→ /ralplan
→ /ralph
→ /code-review</code></pre>
<hr>
<h3 id="배포-전-점검">배포 전 점검</h3>
<pre><code class="language-text">/security-review
→ review code
→ verifier</code></pre>
<hr>
<h2 id="한-줄-요약-1">한 줄 요약</h2>
<p>Claude Code를 제대로 활용하려면 단순히 &quot;코드 작성&quot;을 요청하는 것이 아니라,</p>
<ul>
<li>설계는 <code>plan</code>, <code>ralplan</code></li>
<li>구현은 <code>autopilot</code>, <code>ralph</code></li>
<li>병렬 작업은 <code>ultrawork</code></li>
<li>리뷰는 <code>review code</code></li>
<li>보안은 <code>security review</code></li>
</ul>
<p>처럼 작업 목적에 맞는 모드와 에이전트를 선택하는 것이 핵심이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OMC 멀티 에이전트의 오케스트레이션]]></title>
            <link>https://velog.io/@angel_eugnen/OMC-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EC%9D%98-%EC%98%A4%EC%BC%80%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@angel_eugnen/OMC-%EB%A9%80%ED%8B%B0-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8%EC%9D%98-%EC%98%A4%EC%BC%80%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%85%98</guid>
            <pubDate>Wed, 24 Jun 2026 00:35:13 GMT</pubDate>
            <description><![CDATA[<h1 id="omc-멀티-에이전트의-오케스트레이션">OMC 멀티 에이전트의 오케스트레이션</h1>
<h2 id="가성비">가성비</h2>
<p>강력한 AI 를 단순 작업에 쓰는 것은 낭비
저렴한 AI를 복잡한 AI 에 쓰는 것은 품질 저하</p>
<h2 id="omc-에이전트-소개">OMC 에이전트 소개</h2>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/d4080806-1c57-4d9f-a511-429c5430dddd/image.png" alt=""></p>
<h2 id="작업에-맞는-올바른-도구">작업에 맞는 올바른 도구</h2>
<ul>
<li>Haiku: 저렴하고 빠름, </li>
<li>Sonnet : 균형</li>
<li>Opus: 성능이 좋은대신, 느리고 비쌈</li>
</ul>
<p>Haiku * 60 = Opus
비용 차이는 60배 이다.</p>
<p>OMC에서 단순 반복 작업은 Haiku, 깊은 사고가 필요하면 Opus를 투입하는 것이다</p>
<h2 id="작업은-어떻게-할당되는가">작업은 어떻게 할당되는가</h2>
<p>그것을 하는것이 &quot;멀티 에이전트 오케스트레이션&quot;이다.</p>
<p>PM처럼 딱 맞는 담당자에게 일을 맡겨주는 것을 의미한다.
<img src="https://velog.velcdn.com/images/angel_eugnen/post/c6558398-f7e9-443f-86ac-17ad03b2ca0a/image.png" alt=""></p>
<h2 id="실제-작동하는-ai-팀">실제 작동하는 AI 팀</h2>
<p>예를 들어 결제 시스템 환불 기능 추가에선</p>
<ol>
<li>planner가 요청 분석</li>
<li>explore 가 코드를 찾고 architect가 통합 전략 구현</li>
<li>excutor 가 환불, api 데이터 모델 등 새 코드를 작성</li>
<li>build-fixer 가 오류를 해결하고 code-reviewer가 품질 검사</li>
<li>writer가 새 기능을 반영하여 API 문서 업데이트</li>
</ol>
<h2 id="ai팀-리딩하기">AI팀 리딩하기</h2>
<ol>
<li>명확한 개요 작성: 모호한 요청 대신 구체적으로 지시하라</li>
<li>적절한 톤 사용: 복잡한 문제에는 &quot;신중하게 검토해줘&quot; 라는 힌트를 추가</li>
<li>구축 전 계획: 대규모 작업은 planner를 먼저 사용하여 낭비 줄이기</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[OMC 슬래시 완벽 가이드]]></title>
            <link>https://velog.io/@angel_eugnen/OMC-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@angel_eugnen/OMC-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Wed, 24 Jun 2026 00:24:09 GMT</pubDate>
            <description><![CDATA[<h2 id="슬래시스킬">슬래시스킬(/)</h2>
<p>테스트 주도 개발(TDD) 같은 특정 워크 플로우전체를 직접 호출하여 실행하는 강력한 명령어</p>
<h2 id="autopilot">/autopilot</h2>
<p>: 뼈대완성
아무것도 없는 빈화면에서 실행 화면으로 단숨에 끌어올릴 수 있다.
PM이 일하는 방법과 같다.
우리의 목표를 이해하려고 하는 질문들을 한 후에 코드를 한다. 이후 스스로 수정한다. 이후 완벽히 돌아가는지 확인한다.</p>
<h2 id="code-review">/code-review</h2>
<p>: 가상팀. 단순한 문법 오류를 잡아주는 팀이 아니다. 시니어 개발자가 코드 품질, 가독성, 잠재적 버그를 검토해주며 피드백을 준다.
팀 구성</p>
<ol>
<li>코드리뷰어</li>
<li>보안 전문가</li>
<li>오류검출 테스터</li>
</ol>
<h2 id="security-review">/security-review</h2>
<p>: 품질, 보안검사해주는 프로젝트 경호원</p>
<ol>
<li>보안의 허점을 미리 잡아준다.</li>
</ol>
<h2 id="tdd">/tdd</h2>
<p>: 테트스 주도 개발, 테스트 만듦 -&gt; 테스트 통과 코드 -&gt; 코드 다듬기 이걸 ai가 대신 해주어 튼튼한 코드 작성 가능</p>
<h2 id="온디맨드-ai-팀">온디맨드 AI 팀</h2>
<p>: 디지털 군단으로 확장하기
작업이 너무 커서 ai 하나로 안되면 어떡하지?
=&gt; /swarm</p>
<h2 id="swarm">/swarm</h2>
<p>: 프로젝트 매니저가 에이전트에게 분할하여 독립적으로 처리하고 협력하게 함으로써 큰 프로젝트를 더 빨리 완료</p>
<h2 id="순서">순서</h2>
<ol>
<li>/autopilot 으로 큰 그림을 그린다</li>
<li>/code-review 로 멘토의 조언을 듣고</li>
<li>/tdd 로 코드를 보장하고</li>
<li>문제가 생기면 /swarm으로 해결한다</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[OMC 매직키워드]]></title>
            <link>https://velog.io/@angel_eugnen/OMC-%EB%A7%A4%EC%A7%81%ED%82%A4%EC%9B%8C%EB%93%9C</link>
            <guid>https://velog.io/@angel_eugnen/OMC-%EB%A7%A4%EC%A7%81%ED%82%A4%EC%9B%8C%EB%93%9C</guid>
            <pubDate>Tue, 23 Jun 2026 05:57:01 GMT</pubDate>
            <description><![CDATA[<h1 id="omc-매직-키워드">OMC 매직 키워드</h1>
<h2 id="ralph">ralph</h2>
<p>: 완료 될때까지 자동 반복 실행
    - 분석 &gt; 수정 &gt; 테스트 &gt; 반복</p>
<blockquote>
<p>ralph: 회원가입/로그인/프로필 수정 기능 전부 만들어줘</p>
</blockquote>
<h2 id="uwl">uwl</h2>
<p>: 멀티태스킹 용도. 최대 병렬 처리로 속도 향상, ex) api 를 한번에 5개를 리팩토링 해야 할때</p>
<h2 id="plan">plan</h2>
<p>: 코딩 전 기획 인터뷰 모드
    - 애초에 실수 막고 계획하도록 함</p>
<h2 id="ralpan">ralpan</h2>
<p>: 멀티에이전트와 반복 기획
    - 기획 아이디어가 계속 제련됨</p>
<h2 id="정리">정리</h2>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/d42bb6fb-ba57-447d-b747-97326e221a41/image.png" alt=""></p>
<h2 id="조합">조합</h2>
<p>조합을 하면 더 잘 쓸 수 있다.
최강조합 uwl + ralph 각각의 테스트들을 통과할때까지 계속 반복된다.</p>
<h2 id="실전팁">실전팁</h2>
<ol>
<li>키워드를 너무 남발하지 말아라.</li>
<li>애매하면 plan으로 시작하라</li>
<li>ulw 을 쓸땐 서로 의존성 없는지 확인하고 작업하라</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[OMC 시작하기]]></title>
            <link>https://velog.io/@angel_eugnen/OMC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@angel_eugnen/OMC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 23 Jun 2026 03:58:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/d888e0bb-dec3-4f22-8a32-8a4b0a02a234/image.png" alt=""></p>
<h1 id="omc-이용해서-개발해보기-전-왜-써야-하는가">OMC 이용해서 개발해보기 전 왜 써야 하는가?</h1>
<h2 id="claude-code의-한계">Claude Code의 한계</h2>
<ol>
<li><p>단일 agent라는 점
: 설계, 테스트, 리뷰까지 혼자하는건 벅차다</p>
</li>
<li><p>콘텍스트 손실</p>
</li>
<li><p>작업 실패시 반복적으로 다시 시도하게 명령하는 것</p>
</li>
</ol>
<h2 id="omc가-해결한-것">OMC가 해결한 것</h2>
<ol>
<li><p>여러 전문 agent가 함께 일하는 multi agent Orchestration 을 적용</p>
</li>
<li><p>영구적인 메모리로 해결로 대화의 길을 잃을 일이 없다</p>
</li>
<li><p>자동 반복 실행 기능으로, 알아서 여러번 재시도</p>
</li>
</ol>
<h2 id="omc-3계층-구조">OMC 3계층 구조</h2>
<ol>
<li><p>매직 키워드
: ralph, plan 같은 키워드로 특별 실행모드 활성화(계획을 짬)</p>
</li>
<li><p>슬래시 스킬
: /oh-my-claudecode 처럼 직접 호출하는 전문 기능</p>
</li>
<li><p>플러그인 에이전트
: 굳이 시키지 않아도, 전문 ai(리서치 전문가, 아키텍처 설계 전문가)를 데려와서 작업한다</p>
</li>
</ol>
<h2 id="설치방법">설치방법</h2>
<p>Claude Code에서 다음 명령어를 실행합니다:</p>
<pre><code>/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode
/plugin install oh-my-claudecode</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Remix 걸음마]]></title>
            <link>https://velog.io/@angel_eugnen/Remix-%EA%B1%B8%EC%9D%8C%EB%A7%88</link>
            <guid>https://velog.io/@angel_eugnen/Remix-%EA%B1%B8%EC%9D%8C%EB%A7%88</guid>
            <pubDate>Mon, 15 Dec 2025 07:36:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/9b518cad-8ef6-44c7-bb36-48c66213aadb/image.png" alt=""></p>
<p>GPT생겨서 공부하기 참 쉬워졌쥬..</p>
<h2 id="1️⃣-공식문서-초기-세팅-이건-꼭-해라">1️⃣ 공식문서 초기 세팅 (이건 꼭 해라)</h2>
<p>✔️ 프로젝트 생성
<code>npx create-react-router@latest</code></p>
<p>Remix v2는 이제 React Router에 흡수됐어.
👉 Remix = React Router + 서버 기능 이라고 생각해.</p>
<h2 id="2️⃣-생성-후-구조부터-이해-중요-⭐⭐⭐">2️⃣ 생성 후 구조부터 이해 (중요 ⭐⭐⭐)</h2>
<p>처음 보면 이게 제일 멘붕이야.</p>
<pre><code>app/
 ├─ routes/
 │   ├─ _index.tsx
 │   ├─ login.tsx
 │   └─ posts.$id.tsx
 ├─ root.tsx
 └─ entry.server.tsx</code></pre><p>핵심만 딱 정리해줄게</p>
<ul>
<li>routes/ = 페이지</li>
<li>파일 이름 = URL</li>
</ul>
<p>이거 하나만 기억해도 반은 이해함</p>
<ul>
<li>routes/login.tsx   →  /login</li>
<li>routes/posts.$id.tsx → /posts/123</li>
</ul>
<p>👉 React Router의 <Route /> 안 써도 됨
👉 파일이 라우터다</p>
<h2 id="3️⃣-remix에서-제일-중요한-개념-top-4">3️⃣ Remix에서 제일 중요한 개념 TOP 4</h2>
<h3 id="🧠-①-loader-서버에서-데이터-가져오기">🧠 ① loader (서버에서 데이터 가져오기)</h3>
<pre><code class="language-js">export const loader = async () =&gt; {
  return { message: &#39;hello&#39; };
};</code></pre>
<p>✔️ 이 함수는 브라우저에서 실행 안 됨
✔️ 서버에서만 실행</p>
<pre><code class="language-js">const data = useLoaderData&lt;typeof loader&gt;();</code></pre>
<p>👉 API 호출 + 상태관리 + 로딩을 한 번에 해결</p>
<h3 id="🧠-②-action-폼-submit-처리">🧠 ② action (폼 submit 처리)</h3>
<pre><code class="language-js">export const action = async ({ request }) =&gt; {
  const formData = await request.formData();
  return null;
};</code></pre>
<p>✔️ POST / PUT / DELETE 전용
✔️ 서버에서 실행됨</p>
<p>👉 axios.post() + onSubmit 안 써도 됨</p>
<h3 id="🧠-③-form-remix-전용">🧠 ③ Form (Remix 전용)</h3>
<pre><code class="language-js">&lt;Form method=&quot;post&quot;&gt;
  &lt;input name=&quot;email&quot; /&gt;
  &lt;button type=&quot;submit&quot; /&gt;
&lt;/Form&gt;</code></pre>
<p>👉 submit 하면 자동으로 action() 실행
👉 새로고침 ❌
👉 상태관리 ❌</p>
<h3 id="🧠-④-usenavigation-로딩-상태">🧠 ④ useNavigation (로딩 상태)</h3>
<pre><code class="language-js">const navigation = useNavigation();

navigation.state === &#39;loading&#39;
navigation.state === &#39;submitting&#39;</code></pre>
<p>👉 버튼 로딩 처리, 스피너 여기서 함</p>
<h3 id="4️⃣-아-그래서-뭐가-좋은데-리액트랑-비교">4️⃣ “아 그래서 뭐가 좋은데?” (리액트랑 비교)</h3>
<table>
<thead>
<tr>
<th>리액트</th>
<th>Remix</th>
</tr>
</thead>
<tbody><tr>
<td>useEffect로 API 호출</td>
<td>loader에서 서버 호출</td>
</tr>
<tr>
<td>axios + 상태관리</td>
<td>기본 제공</td>
</tr>
<tr>
<td>폼 submit 직접 처리</td>
<td>action 자동</td>
</tr>
<tr>
<td>로딩 상태 직접 관리</td>
<td>navigation</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS]]></title>
            <link>https://velog.io/@angel_eugnen/NestJS</link>
            <guid>https://velog.io/@angel_eugnen/NestJS</guid>
            <pubDate>Fri, 12 Dec 2025 15:54:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/916c1116-6fe6-44d2-89f3-f2083d53ad91/image.png" alt=""></p>
<h2 id="시작-전-환경-세팅">시작 전 환경 세팅</h2>
<h3 id="1️⃣-필수-도구">1️⃣ 필수 도구</h3>
<p>Insomnia
→ API 테스트용 (Postman 대체)</p>
<h3 id="2️⃣-nestjs-cli-설치-및-프로젝트-생성">2️⃣ NestJS CLI 설치 및 프로젝트 생성</h3>
<p><code>npm i -g @nestjs/cli
nest new project-name</code></p>
<p>nest new 명령어를 사용하면
기본적인 폴더 구조와 설정이 자동으로 구성된다.</p>
<hr>
<h2 id="single-responsibility-principle-srp">Single Responsibility Principle (SRP)</h2>
<p>NestJS는 단일 책임 원칙(SRP) 을 굉장히 강하게 따른다.</p>
<p>하나의 module / class / function 은 하나의 책임만 가져야 한다</p>
<h3 id="기본-구조-예시">기본 구조 예시</h3>
<blockquote>
</blockquote>
<p>AppModule
 ├── AppController
 └── AppService</p>
<h3 id="역할-분리">역할 분리</h3>
<ol>
<li>Controller</li>
</ol>
<ul>
<li>HTTP 요청/응답 처리</li>
<li>URL, Method 정의</li>
</ul>
<ol start="2">
<li>Service</li>
</ol>
<ul>
<li>실제 비즈니스 로직 담당</li>
</ul>
<ol start="3">
<li>Module</li>
</ol>
<ul>
<li>관련 Controller와 Provider를 묶는 단위</li>
</ul>
<p>👉
Controller에서 로직을 처리하지 않고
Service로 위임하는 구조가 기본이다.</p>
<hr>
<h2 id="validationpipe-요청-데이터-검증">ValidationPipe (요청 데이터 검증)</h2>
<p>NestJS는 class 기반 유효성 검사를 공식적으로 지원한다.</p>
<p>1️⃣ 패키지 설치
<code>npm i class-validator class-transformer</code></p>
<p><code>class-validator</code> : 유효성 검사</p>
<p><code>class-transformer</code> : plain object → class 변환</p>
<p>2️⃣ DTO를 위한 패키지
<code>npm i @nestjs/mapped-types</code></p>
<p>DTO 상속 및 변환을 도와주는 유틸 패키지</p>
<p>3️⃣ DTO 예시</p>
<pre><code class="language-js">export class CreateMovieDto {
  @IsString()
  title: string;

  @IsNumber()
  year: number;
}</code></pre>
<p>👉
ValidationPipe를 사용하면</p>
<p>컨트롤러 진입 전에 자동으로 유효성 검증이 이루어진다.</p>
<hr>
<h2 id="dependency-injection-di">Dependency Injection (DI)</h2>
<p>NestJS의 가장 핵심 개념 중 하나.</p>
<h3 id="nestjs의-di-기본-구조--핵심-3요소">NestJS의 DI 기본 구조 – 핵심 3요소</h3>
<p>역할 - 의미
Provider    - 주입 가능한 클래스
Consumer    - 주입받는 쪽
Module    - Provider를 등록하는 장소</p>
<p>예시</p>
<pre><code class="language-js">@Injectable()
export class MoviesService {}

@Module({
  providers: [MoviesService],
})
export class MoviesModule {}

@Controller(&#39;movies&#39;)
export class MoviesController {
  constructor(private readonly moviesService: MoviesService) {}
}</code></pre>
<p>👉
객체를 직접 생성하지 않고
NestJS가 DI 컨테이너를 통해 자동으로 주입해준다.</p>
<hr>
<h2 id="fastify-vs-express">Fastify vs Express</h2>
<p>NestJS는 기본적으로 Express 위에서 동작한다</p>
<p>하지만 Fastify를 사용하면
Express 대비 약 2배 빠른 성능을 낼 수 있다.</p>
<p>Req / Res 직접 접근
getAll(@Req() req, @Res() res): GetMovieDto[] {
  return this.movies;
}</p>
<p>NestJS에서는 @Req(), @Res() 데코레이터를 통해
플랫폼(Express / Fastify)의 객체에 직접 접근할 수 있다.</p>
<h3 id="❗-하지만-권장되지-않는-이유">❗ 하지만 권장되지 않는 이유</h3>
<p>NestJS의 추상화 레이어를 깨게 됨</p>
<p>플랫폼 종속 코드가 됨</p>
<p>테스트 및 유지보수 어려움 증가</p>
<p>👉
가능하면 NestJS 방식(@Body, @Param, @Query 등)을 사용하는 것이 좋다.</p>
<hr>
<h3 id="정리하며-느낀-점">정리하며 느낀 점</h3>
<p>NestJS는 구조와 규칙을 강하게 강제하는 프레임워크</p>
<p>처음엔 번거롭지만 규모가 커질수록 장점이 커진다</p>
<p>특히 DI + Module 구조는 테스트, 유지보수, 협업에 굉장히 유리하다</p>
<p>React 개발자 입장에서는
“왜 이렇게 나눠?” 싶지만
커질수록 이 구조가 왜 필요한지 체감하게 되겠지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TinyMCE webpack 최적화 구현 계획 (하이브리드 방식)]]></title>
            <link>https://velog.io/@angel_eugnen/TinyMCE-webpack-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84-%EA%B3%84%ED%9A%8D-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@angel_eugnen/TinyMCE-webpack-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84-%EA%B3%84%ED%9A%8D-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Tue, 09 Dec 2025 02:29:10 GMT</pubDate>
            <description><![CDATA[<p>기존 코드에서 <code>tinymce/tinymce-react</code> 를 <code>CDN</code>방식으로 이용하고 있었다.</p>
<p>이유는, </p>
<blockquote>
<ul>
<li>프로젝트에 파일을 포함하지 않아도 됨</li>
</ul>
</blockquote>
<ul>
<li>캐싱 활용 가능 (다른 사이트에서도 같은 CDN 사용 시)</li>
<li>초기 설정이 간단
... 가장 큰 이유는 회사에서 빨리 HTML &lt;-&gt; 에디터 읽기쓰기 호환 기능을 가진 에디터를 도입해달라고 했기 때문.</li>
</ul>
<p>하지만 React에서 CDN으로 불러오면 아래와 같은 단점이 있어서 npm방식으로 바꾸려 한다.</p>
<blockquote>
<ul>
<li>CDN 서버 장애 시 사용 불가</li>
</ul>
</blockquote>
<ul>
<li>버전 관리가 명확하지 않음 (URL에 버전이 있어도 관리가 어려움)</li>
<li>보안 이슈 가능성 (외부 스크립트 실행)</li>
<li>번들러 최적화 불가</li>
</ul>
<p>아지만 이렇게 하다보니
예전에 써서 문제가 없던 Quill에디터는 
모노리식 번들(단일번들) 이라 css만 import하면 동작하고, 모든것을 webpack이 처리해서 문제가 없었다고 한다. 
TinyMCE 라는 라이브러리는 모듈러 아키텍처라 필요한 리소스를 런타임에 로드하기 때문에 React 에서 사용시 문제가 생겼다. 이 라이브러리의 유연성과 확장성 때문에 리소스 경로설정 이라는 것이 필요했다.</p>
<p>추가로 번들 크기가 증가할것이다 라는 문제점이 또 보였고..</p>
<p>결국 CRACO/webpack 설정을 하기로 했다.</p>
<hr>
<h1 id="tinymce-webpack-최적화-구현-계획-하이브리드-방식-개요">TinyMCE webpack 최적화 구현 계획 (하이브리드 방식) 개요</h1>
<h2 id="목표">목표</h2>
<ul>
<li>TinyMCE를 npm 패키지로 사용 (CDN 제거)</li>
<li>공통 webpack 설정 + 앱별 오버라이드 구조</li>
<li>webpack으로 리소스 최적화 및 번들 크기 감소</li>
<li>seller-admin에 먼저 적용 (테스트 후 system-admin 확장)
구현 단계</li>
</ul>
<h3 id="1-craco-설치-및-공통-설정">1. CRACO 설치 및 공통 설정</h3>
<ul>
<li>apps/seller-admin/package.json에 @craco/craco 의존성 추가</li>
<li>루트에 공통 설정 생성: craco.base.js</li>
<li>TinyMCE 리소스 복사 설정 (CopyWebpackPlugin)</li>
<li>공통 번들 최적화 설정 (압축, 트리쉐이킹, 코드 스플리팅)</li>
<li>apps/seller-admin/craco.config.js 생성</li>
<li>공통 설정 import 및 seller-admin 특화 오버라이드 (필요시)</li>
<li>apps/seller-admin/package.json의 scripts를 react-scripts → craco로 변경<h3 id="2-nodejs-버전-고정">2. Node.js 버전 고정</h3>
</li>
<li>루트에 .nvmrc 파일 생성 (Node.js 24.6.0)</li>
<li>Jenkins 배포 시 버전 일관성 보장<h3 id="3-tinymce-컴포넌트-수정">3. TinyMCE 컴포넌트 수정</h3>
</li>
<li>packages/ui-components/src/organisms/TinyMCEEditor.tsx 수정</li>
<li>base_url 설정 제거 (webpack이 처리)</li>
<li>tinymceScriptSrc prop 제거 (npm 패키지 사용)</li>
<li>필요한 플러그인만 import하여 트리쉐이킹 최적화<h3 id="4-빌드-스크립트-검증">4. 빌드 스크립트 검증</h3>
</li>
<li>pnpm install 실행</li>
<li>pnpm --filter seller-admin start로 개발 서버 테스트</li>
<li>pnpm --filter seller-admin build로 프로덕션 빌드 테스트</li>
</ul>
<h2 id="주요-파일-변경">주요 파일 변경</h2>
<h3 id="1-appsseller-adminpackagejson">1. apps/seller-admin/package.json</h3>
<ul>
<li>@craco/craco 의존성 추가</li>
<li>scripts 수정: react-scripts → craco<h3 id="2-cracobasejs-신규-루트">2. craco.base.js (신규, 루트)</h3>
</li>
<li>공통 webpack 설정</li>
<li>TinyMCE 리소스 복사 (CopyWebpackPlugin)</li>
<li>공통 번들 최적화 설정<h3 id="3-appsseller-admincracoconfigjs-신규">3. apps/seller-admin/craco.config.js (신규)</h3>
</li>
<li>공통 설정 import</li>
<li>seller-admin 특화 설정 오버라이드 (필요시)<h3 id="4-nvmrc-신규">4. .nvmrc (신규)</h3>
</li>
<li>Node.js 24.6.0 고정<h3 id="5-packagesui-componentssrcorganismstinymceeditortsx">5. packages/ui-components/src/organisms/TinyMCEEditor.tsx</h3>
</li>
<li>base_url 제거</li>
<li>tinymceScriptSrc 제거</li>
</ul>
<h2 id="아키텍처">아키텍처</h2>
<pre><code>루트/
├── craco.base.js          # 공통 webpack 설정
├── .nvmrc                  # Node.js 버전 고정
└── apps/
    └── seller-admin/
        └── craco.config.js # 공통 설정 import + 오버라이드</code></pre><h2 id="예상-효과">예상 효과</h2>
<ul>
<li>번들 크기: 약 20-30% 감소 (트리쉐이킹 및 최적화)</li>
<li>로딩 속도: 개선 (로컬 파일, 번들 최적화)</li>
<li>안정성: 향상 (외부 CDN 의존성 제거)</li>
<li>유지보수성: 향상 (공통 설정 중앙 관리)<h2 id="주의사항">주의사항</h2>
Jenkins 배포 시 Node.js 24.6.0 이상 필요
빌드 메모리 사용량 증가 가능 (webpack 최적화)
seller-admin 테스트 완료 후 system-admin에도 동일 적용</li>
</ul>
<hr>
<h2 id="코드">코드</h2>
<h3 id="1-appssystem-adminpackagejson-수정">1. apps/system-admin/package.json 수정</h3>
<p>script 에서
&#39;start&#39;:&#39;react-script start&#39; 와 같은 것들을 아래처럼 바꾸고</p>
<ul>
<li>&quot;prestart&quot;: &quot;node ../../scripts/copy-tinymce.js seller-admin&quot;,</li>
<li>&quot;start&quot;: &quot;craco start&quot;,</li>
<li>&quot;build&quot;: &quot;craco build&quot;,</li>
<li>&quot;test&quot;: &quot;craco test&quot;,</li>
</ul>
<p>devDependencies에서 아래와 같은 것들을 설치</p>
<ul>
<li>&quot;@craco/craco&quot;: &quot;^7.1.0&quot;,</li>
<li>&quot;copy-webpack-plugin&quot;: &quot;^12.0.2&quot;,</li>
</ul>
<h3 id="2-appssystem-admincracoconfigjs-생성">2. apps/system-admin/craco.config.js 생성</h3>
<pre><code class="language-js">const baseConfig = require(&#39;../../craco.base.js&#39;);

module.exports = {
  ...baseConfig,
  // system-admin 특화 설정이 필요한 경우 여기에 추가
  // 예: 특정 플러그인, 로더 설정 등
};
</code></pre>
<h3 id="3-루트-스크립트에-copy-tinymcejs-생성">3. 루트 스크립트에 copy-tinymce.js 생성</h3>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);

// 루트 디렉토리 찾기 (craco.base.js와 동일한 기준)
const rootDir = path.resolve(__dirname, &#39;../../..&#39;);

// 여러 가능한 경로 시도
const possibleSources = [
  // 1. 루트의 node_modules (craco.base.js와 동일한 방식)
  path.resolve(rootDir, &#39;node_modules/tinymce&#39;),
  // 2. pnpm의 .pnpm 디렉토리에서 찾기
  (() =&gt; {
    try {
      const pnpmStore = path.resolve(rootDir, &#39;node_modules/.pnpm&#39;);
      if (fs.existsSync(pnpmStore)) {
        // tinymce@로 시작하는 디렉토리 찾기
        const entries = fs.readdirSync(pnpmStore);
        for (const entry of entries) {
          if (entry.startsWith(&#39;tinymce@&#39;)) {
            const entryPath = path.join(pnpmStore, entry);
            if (fs.statSync(entryPath).isDirectory()) {
              const tinymcePath = path.join(entryPath, &#39;node_modules/tinymce&#39;);
              if (fs.existsSync(tinymcePath)) {
                return tinymcePath;
              }
            }
          }
        }
      }
      return null;
    } catch (e) {
      return null;
    }
  })(),
  // 3. ui-components의 node_modules
  path.resolve(rootDir, &#39;packages/ui-components/node_modules/tinymce&#39;),
  // 4. system-admin의 node_modules
  path.resolve(__dirname, &#39;../node_modules/tinymce&#39;),
];

// 존재하는 소스 경로 찾기
let source = null;
for (const possibleSource of possibleSources) {
  if (possibleSource &amp;&amp; fs.existsSync(possibleSource)) {
    source = possibleSource;
    break;
  }
}

if (!source) {
  console.error(&#39;❌ TinyMCE not found in node_modules&#39;);
  console.error(&#39;   Tried paths:&#39;);
  possibleSources.forEach((p, i) =&gt; {
    if (p) {
      const exists = fs.existsSync(p) ? &#39;✓&#39; : &#39;✗&#39;;
      console.error(`   ${i + 1}. ${exists} ${p}`);
    } else {
      console.error(`   ${i + 1}. (skipped)`);
    }
  });
  console.error(&#39;\n   Please ensure TinyMCE is installed:&#39;);
  console.error(&#39;   pnpm install&#39;);
  console.error(&#39;\n   Or check if TinyMCE is in packages/ui-components:&#39;);
  console.error(&#39;   ls packages/ui-components/node_modules/tinymce&#39;);
  process.exit(1);
}

const dest = path.resolve(__dirname, &#39;../public/tinymce&#39;);
const checkFile = path.resolve(dest, &#39;plugins/help/js/i18n/keynav/en.js&#39;);

// 파일이 없을 때만 복사 (빠름)
if (!fs.existsSync(checkFile)) {
  console.log(&#39;📦 Copying TinyMCE files (first time only)...&#39;);
  console.log(`   From: ${source}`);
  console.log(`   To: ${dest}`);

  const { execSync } = require(&#39;child_process&#39;);

  if (fs.existsSync(dest)) {
    fs.rmSync(dest, { recursive: true, force: true });
  }

  const isWindows = process.platform === &#39;win32&#39;;
  if (isWindows) {
    execSync(`xcopy /E /I /Y &quot;${source}&quot; &quot;${dest}&quot;`, { stdio: &#39;inherit&#39; });
  } else {
    execSync(`cp -R &quot;${source}&quot; &quot;${dest}&quot;`, { stdio: &#39;inherit&#39; });
  }
  console.log(&#39;✅ Done&#39;);
} else {
  console.log(&#39;⚡ TinyMCE files already exist&#39;);
}
</code></pre>
<h2 id="파일별-용도-설명">파일별 용도 설명</h2>
<h3 id="1-cracobasejs---webpack-설정-파일">1. craco.base.js - Webpack 설정 파일</h3>
<ul>
<li>용도: CRACO를 통한 webpack 설정</li>
<li>역할:
개발/프로덕션 모드에서 webpack이 실행될 때 동작
CopyWebpackPlugin으로 TinyMCE 리소스를 빌드 출력에 복사
프로덕션 빌드 최적화 (코드 스플리팅, 트리쉐이킹, 압축)</li>
<li>작동 시점:</li>
<li>craco start 실행 시 (개발 모드)</li>
<li>craco build 실행 시 (프로덕션 빌드)</li>
<li>🔴 문제점:
개발 모드에서 CopyWebpackPlugin이 초기 복사를 보장하지 않을 수 있음
개발 서버 시작 시 파일이 없으면 에러 발생 가능!!!<h3 id="2-scriptscopy-tinymcejs---파일-복사-스크립트">2. scripts/copy-tinymce.js - 파일 복사 스크립트</h3>
</li>
<li>용도: 개발 서버/빌드 시작 전에 TinyMCE 파일을 수동 복사</li>
<li>역할:
prestart, prebuild 훅에서 실행
TinyMCE 파일이 없을 때만 복사 (조건부)
개발 모드에서 리소스 누락 방지</li>
<li>작동 시점:
pnpm start 실행 전 (prestart 훅)
pnpm build 실행 전 (prebuild 훅)</li>
<li>장점:
개발 서버 시작 전에 파일 존재 보장
파일이 있으면 스킵하여 빠른 시작
에러 메시지로 문제 진단 용이</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Deployment-Vercel]]></title>
            <link>https://velog.io/@angel_eugnen/Deployment-Vercel</link>
            <guid>https://velog.io/@angel_eugnen/Deployment-Vercel</guid>
            <pubDate>Sun, 13 Jul 2025 08:42:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/e1c0ff5f-ccf1-43ea-8656-87013a00da10/image.png" alt=""></p>
<h2 id="deployment">Deployment</h2>
<p>git에 push 를 마쳤다면,
Vercel이 호출할 수 있도록 package.json 에 명령어를 수정한다</p>
<pre><code class="language-bash">  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;,
    &quot;build&quot;: &quot;next build&quot;,
    &quot;start&quot;:&quot;next start&quot;
  },</code></pre>
<p>vercel 은 그냥 deploy 버튼만 누르고 기다리면 된다.
그리고 나타나는 오류들을 해결하면 dashboard에 보인다.</p>
<hr>
<p>추가)
<code>&lt;Link prefetch href={</code>/movies/${id}<code>}&gt;{title}&lt;/Link&gt;</code></p>
<p>배포 시에 prefetch 라는 props 통해  통해 사용자가 클릭하기 전부터 fetch를 진행한다.
스크롤을 내리고 아래쪽에 있는 링크가 보여지면, NextJS는 자동적으로 요청을 보내게 된다.
movie페이지를 자동으로 fetch 하고 있는 것이다. 클릭하지 않았지만 마치 페이지에 들어간 것처럼 요청하는 것이다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS Modules]]></title>
            <link>https://velog.io/@angel_eugnen/CSS-Modules</link>
            <guid>https://velog.io/@angel_eugnen/CSS-Modules</guid>
            <pubDate>Sun, 13 Jul 2025 08:06:41 GMT</pubDate>
            <description><![CDATA[<h2 id="css-module">Css Module</h2>
<ol>
<li><p>application 전체에 적용할 global style 을 만든다.
전체 웹사디트의 bg color 나 폰트를 적용하도록 한다. </p>
</li>
<li><p>app/layout 에서 import global css를 한다.</p>
</li>
<li><p>공통 디자인을 적용해준다.</p>
<pre><code class="language-css">html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: &quot;&quot;;
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
</code></pre>
</li>
</ol>
<p>body {
  font-family: Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;
  background-color: black;
  color: white;
  font-size: 18px;
}</p>
<p>a {
  color: inhefit;</p>
<p>  text-decoration: none;
}</p>
<p>a:hover {
  text-decoration: underline;
}</p>
<pre><code>
4. 특정 디자인에 적용하고 싶다면 이름에 `.module.css`를 넣도록 한다.
![](https://velog.velcdn.com/images/angel_eugnen/post/0bbb9e61-c53d-4b38-860e-eb0e403d3359/image.png)

폴더 위치는 /styles 폴더에 있어도 무관하다.

그리고 오로지 className만 작성할 것이다.

5. 그리고 사용하고 싶은 tsx 파일에서 css 파일을 마치 자바스크립트 처럼 import 해온다.
`import styles from &#39;../style/navigation.module.css&#39;`
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Error Handling]]></title>
            <link>https://velog.io/@angel_eugnen/Error-Handling</link>
            <guid>https://velog.io/@angel_eugnen/Error-Handling</guid>
            <pubDate>Sun, 13 Jul 2025 06:52:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/e66901f8-92eb-4358-aa21-0e93848deae1/image.png" alt=""></p>
<h2 id="error-handling">Error Handling</h2>
<ol>
<li>API 요청이 안되거나</li>
<li>네트워크가 끊기는 등
에러가 생긴 경우의 핸들링은
error.tsx 파일을 만들면 된다.</li>
</ol>
<p>프레임워크 특성상 파일 위치는 페이지 대상이 되는 폴더 안에 넣어야 한다.</p>
<pre><code class="language-js">&quot;use client&quot;;
export default function ErrorOMG() {
  return &lt;h1&gt;Error OMG&lt;/h1&gt;;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Parallel Request]]></title>
            <link>https://velog.io/@angel_eugnen/Parallel-Request</link>
            <guid>https://velog.io/@angel_eugnen/Parallel-Request</guid>
            <pubDate>Sun, 13 Jul 2025 06:37:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c6c583dd-0fe0-4df7-ba78-c49f6c0169a3/image.png" alt=""></p>
<h2 id="parallel-request">Parallel Request</h2>
<pre><code class="language-js">import { API_URL } from &quot;../../../(home)/page&quot;;
async function getMovie(id: string) {
  console.log(`getMovie: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}`);
  const json = await response.json();
  return json;
}
async function getVideo(id: string) {
  console.log(`getVideo: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  const json = await response.json();
  return json;
}
export default async function Text({ params }) {
  const { id } = await params;
  console.log(&quot;startfetching&quot;);
  const movie = await getMovie(id); // 이 함수 응답이 오래걸리면
  const video = await getVideo(id); // 첫줄 함수를 기다리다가 응답이 늦는다. 병렬로 실행해야 한다.
  console.log(&quot;endfetching&quot;);

  return (
    &lt;div&gt;
      testestest
      &lt;h1&gt;{movie.title}&lt;/h1&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이렇게 하면 
start fetching 이 출력되고 end fetching이 순차적으로 출력되며
총 10초 넘게 걸리게 된다. </p>
<pre><code class="language-bash">startfetching
getMovie: 1752386606417
getVideo: 1752386607056
endfetching</code></pre>
<p>병렬 요청으로 바꿔줘야 한다.</p>
<blockquote>
<p><code>promise.all</code>을 사용하면 결과값이 동시에 나올 것이다.</p>
</blockquote>
<pre><code class="language-js">const [movie, videos]=  await Promise.all([getMovie(id), getVideos(id)])</code></pre>
<p>이렇게 요청을 보내면</p>
<pre><code class="language-bash">startfetching
getMovie: 1752386815316
getVideo: 1752386815316 
endfetching</code></pre>
<p>동시에 시작하게 된다.
순차적인 작업이 아니기 때문이다. 이것이 병렬 fetch 이다.</p>
<p>하지만 지금은 둘이 끝나야 보이므로, 둘을 분리하는 것이 좋다.
둘중 어느 한쪽도 기다릴 필요가 없게! 그것이 suspense이다.</p>
<h2 id="suspense">Suspense</h2>
<p>getMovie와 getVideos 만 렌더링 하는 컴포넌트를 만든다.
/components/movie-video.tsx 
/components/movie-info.tsx </p>
<pre><code class="language-js">// 1. components.movie-video.tsx
import { API_URL } from &quot;../app/(home)/page&quot;;
async function getVideos(id: string) {
  console.log(`getVideo: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  const json = await response.json();
  return json;
}

export default async function MovieVideos({ id }: { id: string }) {
  const videos = await getVideos(id);
  return &lt;h6&gt;{JSON.stringify(videos)}&lt;/h6&gt;;
}

//2. components/movie-video.tsx
import { API_URL } from &quot;../app/(home)/page&quot;;
async function getMovie(id: string) {
  console.log(`getMovie: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 3000));
  const response = await fetch(`${API_URL}/${id}`);
  const json = await response.json();
  return json;
}

export default async function MovieInfo({ id }: { id: string }) {
  const movie = await getMovie(id);
  return &lt;h6&gt;{JSON.stringify(movie)}&lt;/h6&gt;;
}
</code></pre>
<p>를 만들고 개별적으로 기다리게 한다.
그리고 page.tsx에서 MovieInfo 와 MovieVideos를 렌더링한다.</p>
<pre><code class="language-js">import { Suspense } from &quot;react&quot;;
import MovieDetail from &quot;../../../../components/movie-info&quot;;
import MovieVideos from &quot;../../../../components/movie-video&quot;;
export default async function Text({ params }) {
  const { id } = await params;



  return (
    &lt;div&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading Movie Info&lt;/h1&gt;}&gt;
        &lt;MovieDetail id={id} /&gt;
      &lt;/Suspense&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading Movie Video&lt;/h1&gt;}&gt;
        &lt;MovieVideos id={id} /&gt;
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>그럼 순차적으로 데이터가 준비되는 순간 사용자는 렌더링된 데이터를 볼 수 있다.
그리고 page.tsx 와 같은 위치에 있는 loading.tsx 는 최적화 한 덕분에 await 하는게 없으므로 전체 로딩하는 일이 없게 된다.</p>
<blockquote>
<p>page 단위 로딩: <code>loading.tsx</code>
서버컴포넌트 단위 로딩: <code>Suspense</code></p>
</blockquote>
<hr>
<p>Next15) 15버전 부터는 기본 캐싱이 안되기 때문에 캐싱을 확인하려면 fetch의 두번째 인자 cache옵션을 <code>force-cache</code> 로 바꿔줘야 한다고 한다.</p>
]]></description>
        </item>
    </channel>
</rss>