<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>J Lab</title>
    <link>https://j1alee.tistory.com/</link>
    <description>개발 실험실입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 04:35:29 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>j1adev</managingEditor>
    <item>
      <title>AI 쓰면서 하는 책임 분리 고민</title>
      <link>https://j1alee.tistory.com/9</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI로 코드 쓰기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 개발을 할 때는 지피티로 코드를 써가면서 기능을 구현해가기 시작했다. 그때는 좋은 설계가 뭔지에 대해 감이 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 프로젝트가 진행될수록, &amp;lsquo;코드를 잘 쓰는 것&amp;rsquo;과 &amp;lsquo;설계를 잘하는 것&amp;rsquo;은 전혀 다른 문제라는 걸 느끼게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계문서를 쓰고 구현을 시작하면서, 내가 만든 서비스이니 누구보다 도메인 이해가 잘 되어 있을 거라 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;엔티티를 재설계하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 엔티티 A, 엔티티 B를 다른 책임이라 보고 다른 엔티티로 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 외부 api를 호출하고 이를 객체로 매핑하는 과정에서 A, B는 생명주기가 같다는 점을 발견했고 이후 도메인을 재설계했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 분리는 역할 기준이었지만, 실제 변경 이유는 항상 함께 움직이고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SRP를 다시 이해하게 되다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 SRP란 무작정 다른 것이라 생각하고 분리하는게 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;같이 변한다면 하나로 묶어야한다&lt;/b&gt;는 것이다. 즉 같이 변하고, 생명주기가 같다면 의존성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예시 &amp;ndash; 상태와 행동이 함께 변하는 경우&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;class Category {

    private final List&amp;lt;Item&amp;gt; items = new ArrayList&amp;lt;&amp;gt;();

    public void addItem(Item item) {
        items.add(item);
    }

    /**
     * 카테고리의 상태(items)에 의해
     * 자연스럽게 계산되는 값이므로
     * 별도 서비스가 아닌 엔티티 책임으로 둔다.
     */
    public double averageScore() {
        int totalScore = items.stream()
                .mapToInt(Item::score)
                .sum();

        return items.isEmpty() ? 0 : (double) totalScore / items.size();
    }
}

record Item(int score) {}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 이유를 기준으로 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;averageScore 계산은 Category의 상태 변화에만 영향을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경험 이후로, &lt;b&gt;SRP는 &amp;lsquo;역할을 나누는 것&amp;rsquo;이 아니라 &amp;lsquo;같이 변하는 것을 묶는 것&amp;rsquo;&lt;/b&gt;이라는 말이 내게는 더 정확하게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 도메인의 이해 + OOP 사고를 해야 더 알맞은 설계, 알맞은 구현을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 시니어가 될 수록 더 중요한 사고라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 코드만 잘 쓰는게 아니라 설계도 하고 고객에게 제품 설명도 하고, 기술 선정도 해야한다. 이러려면 결국 도메인의 이해 + 서비스 이해가 잘 이루어져야 하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시니어가 될 수록 소통, 협업, 도메인 이해, OOP적 사고가 중요해져서 결국 기본기, 코드 외적인 부분이 중요해지는 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소프트웨어 장인 책을 회고하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 장인 이라는 책을 읽을 당시에는 장인 정신의 핵심이 열정이라고 강조되었는데, 이제 열정의 의미가 다르게 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 내가 이해하게 된 장인 정신은 열정 그 자체라기보다, &lt;b&gt;설계와 판단에 책임을 지려는 태도&lt;/b&gt;였다. 그리고 설계와 판단을 잘하기 위해서는 도메인에 대한 이해와 본인이 어떤 기능을 만드는지에 대한 이해가 중요한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 쓰는 지금일수록, 무엇을 자동화하고 무엇을 직접 판단해야 하는지에 대한 기준이 개발자의 역량이 된다고 느낀다.&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>SRP</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/9</guid>
      <comments>https://j1alee.tistory.com/9#entry9comment</comments>
      <pubDate>Sat, 7 Feb 2026 23:36:25 +0900</pubDate>
    </item>
    <item>
      <title>Spring DataSource, config server 에러 : Access denied for user</title>
      <link>https://j1alee.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &amp;ldquo;로컬 실행이 안된다&amp;rdquo;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입사 한 달 차, 로컬에서 테스트하려는데 앱이 뜨질 않았다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Access denied for user ''@'123.456.789.10' (using password: NO)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username, password가 인식이 안되었다.&lt;br /&gt;&amp;ldquo;비밀번호가 틀렸다&amp;rdquo;가 아니라,&lt;br /&gt;&amp;ldquo;비밀번호를 보내지도 않았다&amp;rdquo;는 뜻이었다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 문제 파악 : 로그를 찍자&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application.yml에 username, password를 정확히 넣고,&lt;br /&gt;Config Server도 꺼봤지만 여전히 안 됐다.&lt;br /&gt;그래서 결국 Bean에 로그를 박았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1761365713917&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostConstruct
public void debugDatasource() {
    HikariDataSource ds = (HikariDataSource) dataSource();
    System.out.println(&quot;==== MariaDB DataSource ====&quot;);
    System.out.println(&quot;URL: &quot; + ds.getJdbcUrl());
    System.out.println(&quot;USER: &quot; + ds.getUsername());
    System.out.println(&quot;================================&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여전히 콘솔엔 username이 공백이었다.&lt;br /&gt;심지어 jdbc-url을 바꿔도 계속 log4jdbc가 붙어서 찍혔다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;URL: jdbc:log4jdbc:mysql://123.456.789.10
USER:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 원인 분석 &amp;mdash; &amp;ldquo;두 개의 함정&amp;rdquo;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;529&quot; data-start=&quot;445&quot; data-ke-size=&quot;size16&quot;&gt;이 문제에는 함정이 두 개 숨어 있었다.&lt;br /&gt;하나는 &lt;b&gt;코드 내부의 자동 설정 충돌&lt;/b&gt;,&lt;br /&gt;다른 하나는 &lt;b&gt;환경 설정의 공유 부재&lt;/b&gt;였다.&lt;/p&gt;
&lt;h4 data-end=&quot;615&quot; data-start=&quot;534&quot; data-ke-size=&quot;size20&quot;&gt;1. Spring Boot의 바인딩 충돌 (@ConfigurationProperties + DataSourceBuilder), 즉 local yml vs config server&lt;/h4&gt;
&lt;p data-end=&quot;1363&quot; data-start=&quot;1297&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 log4jdbc 문제인 줄 알았다.&lt;br /&gt;하지만 원인은 &lt;b&gt;Spring Boot의 자동 바인딩 순서&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-end=&quot;1430&quot; data-start=&quot;1365&quot; data-ke-size=&quot;size16&quot;&gt;나는 아래처럼 @ConfigurationProperties를 붙여&amp;nbsp; yml의 DB 설정을 읽게 했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1761367744231&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ConfigurationProperties(prefix = &quot;spring.maria-db.datasource&quot;) 
public DataSource dataSource() {
	return DataSourceBuilder.create().type(HikariDataSource.class).build(); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1733&quot; data-start=&quot;1620&quot; data-ke-size=&quot;size16&quot;&gt;그런데 DataSourceBuilder.create()도 원격의config server의 같은 prefix(spring.maria-db.datasource)를 기반으로&lt;br /&gt;username/password를 읽는다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;1851&quot; data-start=&quot;1735&quot; data-ke-size=&quot;size16&quot;&gt;즉, 같은 prefix의 값이&amp;nbsp;중복 바인딩이 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;1952&quot; data-start=&quot;1853&quot; data-ke-size=&quot;size16&quot;&gt;그 결과, DataSourceBuilder가 세팅한 username/password가&lt;br /&gt;나중에 @ConfigurationProperties에 의해 null로 덮였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2066&quot; data-start=&quot;2004&quot; data-ke-size=&quot;size16&quot;&gt;@ConfigurationProperties를 제거하고 강제로 &lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;DataSourceBuilder 빈에 usernmae, password설정해서&lt;/span&gt;&amp;nbsp;연결이 바로 성공했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;h4 data-end=&quot;1324&quot; data-start=&quot;1296&quot; data-ke-size=&quot;size20&quot;&gt;2. DB 주소 변경 (팀 내 소통 부재)&lt;/h4&gt;
&lt;p data-end=&quot;1439&quot; data-start=&quot;1327&quot; data-ke-size=&quot;size16&quot;&gt;두 번째 함정은 환경이었다.&lt;br /&gt;누군가가 jdbc-url을 새롭게 바꿔 master에 머지했는데&lt;br /&gt;이건 내가 권한이 없는 DB였다.&lt;br /&gt;팀원 중 아무도 그 변경을 공유하지 않았고, 나는 &lt;b&gt;권한도 없는 DB에 접속 시도만 하고 있었다.&lt;/b&gt; &lt;/p&gt;
&lt;p data-end=&quot;1600&quot; data-start=&quot;1546&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;코드가 맞아도, 소통이 되지 않으면 그게 버그다.&lt;/p&gt;
&lt;p data-end=&quot;1613&quot; data-start=&quot;1605&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ 해결 : &lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;@ConfigurationProperties 제거, 강제 설정&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애노테이션 제거, DataSource 빈도 수동 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;@Bean
@Primary
public DataSource dataSource() {
    return DataSourceBuilder.create()
        .driverClassName(&quot;...&quot;)
        .url(&quot;jdbc:mysql:...&quot;)
        .username(&quot;...&quot;)
        .password(&quot;...&quot;)
        .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ 회고 &amp;mdash; 소통도 디버깅이다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위의 문제는 대부분의 서비스회사는 마주치지 않을 가능성이 높다.&amp;nbsp;&lt;br /&gt;@ConfigurationProperties 기반 수동 Bean 등록 + Config Server 방식은 sm 회사가 쓰는 오래된 기술이기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버전이 낮은 프레임워크, 구식 기술은 &amp;lsquo;문제 해결의 난이도&amp;rsquo;를 올린다. 그리고 그것보다 더 큰 문제는 소통 없는 환경이다.&lt;/li&gt;
&lt;li&gt;Config나 DB 설정처럼 작은 변경일수록 문서화와 커뮤니케이션이 필수인데,&lt;br /&gt;SM 회사는 &amp;ldquo;각자 맡은 일만 하는 문화&amp;rdquo;가 강해서 소통이 잘안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>datasource</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/8</guid>
      <comments>https://j1alee.tistory.com/8#entry8comment</comments>
      <pubDate>Sat, 25 Oct 2025 14:21:09 +0900</pubDate>
    </item>
    <item>
      <title>Spring WebClient block() vs subscribe(): 계층별 사용법 정리</title>
      <link>https://j1alee.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux나 WebClient를 처음 사용할 때 가장 많이 고민하게 되는 포인트 중 하나는 바로 &lt;b&gt;block()과 subscribe() 중 무엇을 써야 하는가&lt;/b&gt;이다. 특히 테스트 코드, 서비스 코드, 컨트롤러, API 응답부 등 **계층마다 요구되는 특성이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스트에서는 &lt;b&gt;WebClient를 사용하는 각 계층에서의 전략적 선택 기준&lt;/b&gt;을 정리해본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본 개념: block() vs subscribe()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 설명 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;block()&lt;/td&gt;
&lt;td&gt;Mono/Flux의 결과를 &lt;b&gt;동기적으로 대기&lt;/b&gt;하여 반환&lt;/td&gt;
&lt;td&gt;&lt;b&gt;블로킹&lt;/b&gt; (스레드 점유), 흐름 제어 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;subscribe()&lt;/td&gt;
&lt;td&gt;Mono/Flux를 &lt;b&gt;비동기적으로 실행&lt;/b&gt;하고 콜백으로 결과를 처리&lt;/td&gt;
&lt;td&gt;&lt;b&gt;논블로킹&lt;/b&gt;, 백그라운드 실행, 흐름 관리 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계층별 전략 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층 사용 추천 이유&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ 단위 테스트&lt;/td&gt;
&lt;td&gt;block()&lt;/td&gt;
&lt;td&gt;테스트는 완료 여부가 중요함. subscribe()는 타이밍 이슈 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ 통합 테스트&lt;/td&gt;
&lt;td&gt;block()&lt;/td&gt;
&lt;td&gt;외부 연동 흐름이 확실히 끝났는지 확인해야 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ 서비스 계층&lt;/td&gt;
&lt;td&gt;상황에 따라 선택**(핵심 로직이면 block() 허용)**&lt;/td&gt;
&lt;td&gt;요약 처리, 외부 응답 필수 로직 등은 block()으로 동기화 필요할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ 컨트롤러 (@RestController)&lt;/td&gt;
&lt;td&gt;subscribe() (또는 Mono 반환)&lt;/td&gt;
&lt;td&gt;클라이언트 요청을 처리하는 HTTP 스레드를 절대 블로킹하면 안 됨. 요청 처리 스레드 낭비로 서버 부하 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;❌ API 응답부 (HandlerFunction 등)&lt;/td&gt;
&lt;td&gt;subscribe()&lt;/td&gt;
&lt;td&gt;요청 처리 스레드 낭비로 서버 부하 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 컨트롤러와 응답부에서는 block()을 쓰면 안 될까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 이유: &lt;b&gt;애플리케이션이 병목에 걸리고 결국 다운될 수 있기 때문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux는 다음 철학을 바탕으로 만들어졌다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;하나의 스레드로도 수천 개의 요청을 처리할 수 있도록 하자&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이를 위해 모든 I/O, HTTP, DB 호출 등을 **논블로킹(reactive)**으로 설계&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗ block()을 쓰는 순간 무슨 일이 벌어지냐면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 요청을 처리하던 스레드가 &lt;b&gt;결과를 기다리며 멈춤&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;다른 요청이 들어와도 스레드는 아직 이전 요청 때문에 사용 중&lt;/li&gt;
&lt;li&gt;병렬 요청이 많아지면?&amp;rarr; &lt;b&gt;새 요청은 처리되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; 서버가 멈춘 것처럼 느려짐 &amp;rarr; &lt;b&gt;다운&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;사용 가능한 스레드가 모두 블로킹됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 &quot;&lt;b&gt;스레드 고갈(thread exhaustion)&lt;/b&gt;&quot; 또는 &quot;&lt;b&gt;리액터 데드락&lt;/b&gt;&quot;이라 부른다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;subscribe()는 언제 쓰나?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부적으로 &lt;b&gt;로그 전송&lt;/b&gt;, &lt;b&gt;알림 메시지&lt;/b&gt;, &lt;b&gt;모니터링 이벤트 전송&lt;/b&gt; 등&lt;/li&gt;
&lt;li&gt;결과를 굳이 기다리지 않아도 되는 비핵심 로직&lt;/li&gt;
&lt;li&gt;예: 슬랙 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;slackWebClient.post()
    .uri(&quot;/chat.postMessage&quot;)
    .body(...)
    .retrieve()
    .bodyToMono(String.class)
    .subscribe(); // 결과는 중요하지 않음

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보너스: block()은 &amp;ldquo;컨텍스트 안에서만&amp;rdquo; 써라&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Scheduled, Batch, Retry Processor, Message Listener &amp;rarr; block() 허용 가능&lt;/li&gt;
&lt;li&gt;HTTP 요청/응답 컨텍스트 (Controller) &amp;rarr; 절대 block() 금지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebClient는 강력한 도구지만, 사용 위치에 따라 전략적으로 다뤄야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비동기를 고집하다 테스트가 깨지고&lt;/b&gt;, &lt;b&gt;동기를 남발하다 서버가 느려지는 일&lt;/b&gt;은 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 계층별로 다음을 기억하자:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  테스트는 block(), 실서비스는 subscribe(), 컨트롤러는 절대 block() 금지!&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Spring</category>
      <category>block()</category>
      <category>subscribe()</category>
      <category>webclient</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/7</guid>
      <comments>https://j1alee.tistory.com/7#entry7comment</comments>
      <pubDate>Wed, 18 Jun 2025 16:01:39 +0900</pubDate>
    </item>
    <item>
      <title>Spring 테스트가 끝나지 않던 이유: subscribe(), @Value, MockWebServer</title>
      <link>https://j1alee.tistory.com/6</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 프로젝트에서 Slack 알림 기능을 WebClient로 구현하고, 그에 대한 단위 테스트를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack API를 직접 호출하지 않기 위해 MockWebServer를 활용했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 로직에서는 Spring의 @Value를 이용해 외부 설정값을 주입했다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Value(&quot;${slack.enabled:true}&quot;)
private boolean slackEnabled;

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 테스트를 실행하자 &lt;b&gt;문제가 발생했다.&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 테스트는 통과하지 못하고 무한히 실행 중인 상태로 멈춰 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ GitHub Actions에서는 6시간 제한 초과로 build가 &lt;b&gt;자동 취소&lt;/b&gt;되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 로그에는 특별한 에러도 없고, WebClient 요청도 정상처럼 보였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도대체 왜 테스트가 끝나지 않았던 걸까?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SlackNotificationService는 비동기 방식(subscribe())으로 WebClient 호출&lt;/li&gt;
&lt;li&gt;MockWebServer.takeRequest()가 응답을 받지 못하고 무한 대기 상태 에 빠질 수 있다. &amp;rarr; 테스트가 끝나지 않음&lt;/li&gt;
&lt;li&gt;게다가 @Value가 제대로 주입되지 않음 &amp;rarr; 조건문 우회도 실패&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문 우회 로직 :&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (!slackEnabled) {
    return;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다시 정리하자면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트에서 &lt;b&gt;WebClient.subscribe() 비동기 방식&lt;/b&gt;으로 슬랙에 메시지를 전송&lt;/li&gt;
&lt;li&gt;요청은 &lt;b&gt;백그라운드 쓰레드에서 실행&lt;/b&gt;됨.
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;var recordedRequest = mockWebServer.takeRequest();

&lt;/code&gt;&lt;/pre&gt;
&amp;rarr; &lt;b&gt;이 줄에서 요청이 올 때까지 기다림&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;하지만 테스트 코드에서는:&lt;/li&gt;
&lt;li&gt;그런데 비동기 요청이 끝나기 전에, 테스트 메인 쓰레드는 이미 takeRequest()에 도달해서 &lt;b&gt;요청을 기다리는 중&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;비동기 요청이 &lt;b&gt;끝나지 않거나 실패&lt;/b&gt;하면&amp;rarr; 테스트는 &lt;b&gt;절대 종료되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; takeRequest()는 &lt;b&gt;요청을 받지 못해서 계속 대기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;GitHub Actions는 워크플로 실행 시간이 &lt;b&gt;6시간을 초과&lt;/b&gt;하면 자동으로 테스트를 &lt;b&gt;취소(cancelled)&lt;/b&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;The job has exceeded the maximum execution time of 6h0m0s

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&amp;rarr; 그래서 다음 메시지가 떴던 것:&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실패한 시도들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ application.yml 설정 수정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test/resources/application.yml에 slack.enabled: true를 명시&lt;/li&gt;
&lt;li&gt;@ActiveProfiles(&quot;test&quot;) 도입&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;b&gt;문제 해결되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ @SpringBootTest 도입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring이 @Value 주입을 포함한 전체 빈 구성 및 컨텍스트를 로딩해줌&lt;/li&gt;
&lt;li&gt;하지만 Spring이 생성한 WebClient는 여전히 application.yml의 Slack base URL을 사용&lt;/li&gt;
&lt;li&gt;별도로 WebClient 설정을 override하지 않았기 때문에,&lt;/li&gt;
&lt;li&gt;테스트에서 사용 중인 MockWebServer와는 연결되지 않았고, 요청은 Slack으로 날아감&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ System.setProperty() 시도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebClient의 baseUrl을 시스템 프로퍼티로 override하려 했다.&lt;/li&gt;
&lt;li&gt;그러나 WebClient Bean 생성시 이걸 반영하지 않았어서(즉 override할게 없었다) 또 이걸 소스코드에 수정하려니 코드가 복잡해진다고 느꼈다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 최종 해결: 직접 객체 생성 + setter 주입 + block()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2292&quot; data-start=&quot;2253&quot;&gt;WebClient와 서비스 객체를 &lt;b&gt;테스트 코드에서 직접 생성&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2317&quot; data-start=&quot;2293&quot;&gt;설정값은 &lt;b&gt;setter로 수동 주입&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2363&quot; data-start=&quot;2318&quot;&gt;subscribe() 대신 block()으로 테스트용 동기 메서드 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;slackNotificationService = new SlackNotificationService(webClient);
slackNotificationService.setSlackEnabled(true); // ✅ 수동 주입

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;public void sendSummaryToUserSync(...) {
    ...
    slackWebClient.post()
        ...
        .block(); // ✅ 테스트용
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비슷한 문제: SlackRequestVerifier&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Value를 쓰는 또 다른 클래스에서도 동일한 문제가 있었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 역시 setter 방식으로 테스트에서 직접 주입하여 해결했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;slackRequestVerifier = new SlackRequestVerifier();
slackRequestVerifier.setSigningSecret(&quot;dummy_secret&quot;);
slackRequestVerifier.setVerifySignature(true);

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언제 setter 방식이 좋은가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 세 가지이다. 다른 해결방법이 좀 지저분하다 싶으면(webclient와의 연동이 지저분해진다던지...) setter를 쓰는 것이 괜찮은 듯하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SpringBootTest가 너무 무거운 경우&lt;/li&gt;
&lt;li&gt;@Value나 @Autowired를 사용하는 컴포넌트를 단위 테스트하고 싶을 때&lt;/li&gt;
&lt;li&gt;Spring 컨텍스트 없이 빠르게 검증하고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트에서 block() vs subscribe()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황 block() subscribe()&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실패 요청이 왔을 때&lt;/td&gt;
&lt;td&gt;테스트가 바로 실패함&lt;/td&gt;
&lt;td&gt;테스트는 몰라서 통과한 것처럼 보이기도 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;요청이 너무 느릴 때&lt;/td&gt;
&lt;td&gt;테스트가 timeout으로 실패&lt;/td&gt;
&lt;td&gt;테스트는 이미 끝났는데 요청은 백그라운드에서 계속 처리 중일 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;takeRequest() 대기 중&lt;/td&gt;
&lt;td&gt;요청이 오면 바로 깨짐&lt;/td&gt;
&lt;td&gt;요청이 안 오면 무한 대기 (성공 여부 보장 X)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 block()이라면 명확하게 실패, 성공을 알려줄 수 있다. 하지만 subscribe()는 무한 대기할 수 있는 문제가 생기는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경험은 나에게 &lt;b&gt;Spring 테스트 환경에서 설정 주입이 어떻게 동작하는지&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;WebClient 테스트 시 비동기 처리의 함정이 무엇인지&lt;/b&gt;를 깨닫게 했다. WebClient, @Value, Mock 서버 간의 연동 흐름이 정확히 일치하지 않으면 테스트는 hang되거나 실패한다는 점을 겪었다.&lt;/p&gt;</description>
      <category>프로젝트/디버깅 경험</category>
      <category>Block</category>
      <category>MockWebServer</category>
      <category>setter</category>
      <category>subscribe</category>
      <category>webclient</category>
      <category>무한대기</category>
      <category>비동기테스트</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/6</guid>
      <comments>https://j1alee.tistory.com/6#entry6comment</comments>
      <pubDate>Wed, 18 Jun 2025 14:57:31 +0900</pubDate>
    </item>
    <item>
      <title>AWS Docker와 JMeter 연동 문제(빈 등록 안 됨) 디버깅</title>
      <link>https://j1alee.tistory.com/5</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JMeter를 활용해 Spring Boot 프로젝트를 AWS EC2 위 Docker 환경에서 테스트하는 중, 예상치 못한 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Test Group 안에는 현재 Thread Group1, Thread Group2가 있는데 하나는 성공, 하나는 실패하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JMeter 시나리오 구성:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Thread Group 1: Slack 메시지 수신 API 테스트( /slack/events ) &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;성공, 200 OK &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Thread Group 2: 스케줄러 수동 트리거 API 테스트( /test/run-daily-scheduler )&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실패, 500 Internal Server Error &lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker 로그에는 해당 컨트롤러( TestSchedulerController 라는 이름이다) 관련 로그가 &lt;b&gt;전혀 출력되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그러나 &lt;b&gt;IDE(IntelliJ)에서는 문제없이 정상 동작&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 특이사항 및 증상&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/test/run-daily-scheduler 호출 시 500 에러&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker 환경에서는 로그가 아예 출력되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;@PostConstruct, System.out.println, static {} 블록을 넣어도 전혀 동작하지 않음&lt;/li&gt;
&lt;li&gt;IDE에서 실행하면 @PostConstruct 로그가 잘 출력됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;  이 시점에서 의심했다. &lt;/span&gt;&lt;span&gt;&lt;b&gt;클래스 자체가 메모리에 올라가지 않았고, Spring의 빈으로 등록되지 않았다는 것&lt;/b&gt;&lt;/span&gt;&lt;span&gt;이다. 단순히 메서드가 호출되지 않은 게 아니라, 애초에 컨트롤러 자체가 생성되지 않은 상태였다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 원인 추정 및 진단&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 프로파일 미적용 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile에 다음처럼 ENTRYPOINT로 프로파일을 지정했으나:&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;ENTRYPOINT [&quot;java&quot;, &quot;-Dspring.profiles.active=docker,mock-openai&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이렇게 하면 프로파일 설정이 실제로 적용되지 않는다. 즉 application-docker.yml이 무시된다. 그러면 이 속의 spring.data.redis.host: redis도 무시되어서 Redis의 도커 연결도 해지되니 더 문제가 심각해진다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;안정적으로 프로파일을 적용하려면 아래처럼 환경변수로 프로파일을 전달하자. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조치:&lt;/b&gt; ENTRYPOINT 설정을 제거하고, docker run 시 환경 변수로 프로파일 전달:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;docker run -e SPRING_PROFILES_ACTIVE=docker,mock-openai ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이후 application-docker.yml 정상 반영 + Redis 연결도 복구됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 컨트롤러가 빈으로 등록되지 않음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker 로그에는 아무런 로그조차 찍히지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;static {} 안 찍힘&lt;/td&gt;
&lt;td&gt;JVM이 아예 클래스를 로딩하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@PostConstruct 안 찍힘&lt;/td&gt;
&lt;td&gt;빈 등록조차 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 컨트롤러는 com.jia.study_tracker.controller라는 별도 패키지에 위치했었다. 이는 다른 컨트롤러와는 다른 곳이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 컨트롤러가 &lt;b&gt;스캔 대상에서 누락&lt;/b&gt;되어 빈으로 등록되지 않았던 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 컨트롤러는 다른 정상작동되는 컨트롤러와 같은 위치에 놓아두었고 그제서야 해결했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조치:&lt;/b&gt; 문제되는 그 해당 컨트롤러( TestSchedulerController )를 다른 컨트롤러가 있는 곳으로 패키지 이동 &amp;rarr; 로그 정상 출력, API 정상 응답 확인&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 해결 결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/test/run-daily-scheduler API: 200 OK 응답&lt;/li&gt;
&lt;li&gt;Redis 연결 정상화 &amp;rarr; /actuator/health: UP&lt;/li&gt;
&lt;li&gt;JMeter 부하 테스트 성공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 내가 배운 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로파일은 ENTRYPOINT보다 환경변수로 설정하자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-Dspring.profiles.active=... 방식은 Docker 내에서 예측 불가능한 문제가 발생할 수 있음&lt;/li&gt;
&lt;li&gt;-e SPRING_PROFILES_ACTIVE=...는 Spring이 공식 지원하는 방식이며 신뢰도 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 로그가 없다 = 아예 실행되지 않은 것일 수 있다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그가 하나도 없다면, 애초에 빈 등록이 안 된 것일 가능성이 높다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는&amp;nbsp;해당&amp;nbsp;컨트롤러가&amp;nbsp;`com.jia.study_tracker.controller`&amp;nbsp;패키지에&amp;nbsp;있었다.&amp;nbsp;&amp;nbsp; &lt;br /&gt;이&amp;nbsp;위치는&amp;nbsp;`@SpringBootApplication`&amp;nbsp;기준으로&amp;nbsp;볼&amp;nbsp;때&amp;nbsp;스캔&amp;nbsp;대상이&amp;nbsp;맞았지만,&amp;nbsp;&amp;nbsp; &lt;br /&gt;이상하게도&amp;nbsp;Docker&amp;nbsp;환경에서는&amp;nbsp;아무&amp;nbsp;로그도&amp;nbsp;출력되지&amp;nbsp;않았다.&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;그러나 같은 코드를 `com.jia.study_tracker.slack` 패키지( 정상작동되는 다른 컨트롤러가 있는 곳)로 옮기자마자&amp;nbsp;&amp;nbsp; &lt;br /&gt;바로&amp;nbsp;`@PostConstruct`가&amp;nbsp;실행되고,&amp;nbsp;API&amp;nbsp;응답도&amp;nbsp;200&amp;nbsp;OK가&amp;nbsp;나왔다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;이는&amp;nbsp;단순한&amp;nbsp;컴포넌트&amp;nbsp;스캔&amp;nbsp;문제라기보다는,&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;빌드&amp;nbsp;과정에서&amp;nbsp;특정&amp;nbsp;패키지의&amp;nbsp;클래스가&amp;nbsp;JAR에&amp;nbsp;포함되지&amp;nbsp;않았거나,&amp;nbsp;&amp;nbsp; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;복사/로드&amp;nbsp;누락이&amp;nbsp;있었던&amp;nbsp;것으로&amp;nbsp;보인다.&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script&gt;
const _0x5eef=['classList','92935nhtnYq','setAttribute','push','innerHTML','getElementById','toLowerCase','tt_adsense_top','another_category','style','//p[contains(text(),\x27[목차여기]\x27)]','1954669aacfHB','div','appendChild','toc-ym','title','forEach','DOMContentLoaded','call','addEventListener','length','insertBefore','firstElementChild','log','27309qNoTHN','62SuwPRc','parentNode','querySelector','revenue_unit_wrap','tagName','23736mMyuUa','singleNodeValue','trim','17723tUfPMr','textContent','1STKGDu','getAttribute','contains','nextSibling','791846eKKEom','createElement','outerText','FIRST_ORDERED_NODE_TYPE','querySelectorAll','72wJWnLP','hasAttribute','669103LLOFBD','toc'];function _0x330c(_0x5d40d0,_0x4afdad){_0x5d40d0=_0x5d40d0-0xec;let _0x5eef71=_0x5eef[_0x5d40d0];return _0x5eef71;}const _0x2078d2=_0x330c;(function(_0xbea334,_0x392453){const _0x2c3076=_0x330c;while(!![]){try{const _0x5a087d=-parseInt(_0x2c3076(0x117))+parseInt(_0x2c3076(0xf7))+parseInt(_0x2c3076(0xfa))+-parseInt(_0x2c3076(0x11a))*-parseInt(_0x2c3076(0xf5))+parseInt(_0x2c3076(0x112))*parseInt(_0x2c3076(0x111))+parseInt(_0x2c3076(0xec))*-parseInt(_0x2c3076(0xf0))+-parseInt(_0x2c3076(0x104));if(_0x5a087d===_0x392453)break;else _0xbea334['push'](_0xbea334['shift']());}catch(_0x47ff63){_0xbea334['push'](_0xbea334['shift']());}}}(_0x5eef,0xea9e9),document[_0x2078d2(0x10c)](_0x2078d2(0x10a),function(){const _0x7eb51e=_0x2078d2;try{const _0x591681=document[_0x7eb51e(0x114)]('.contents_style'),_0x1762f9=document[_0x7eb51e(0xfe)](_0x7eb51e(0x107));if(_0x591681&amp;&amp;!_0x1762f9)htmlTableOfContents();else return![];}catch(_0x250abc){console[_0x7eb51e(0x110)]('');}}));function htmlTableOfContents(_0x4f1c99){const _0x388803=_0x2078d2;var _0x4f1c99=_0x4f1c99||document;const _0x44fb35=document[_0x388803(0xf1)]('div');_0x44fb35[_0x388803(0xfb)]('id',_0x388803(0x107));const _0x2117e2=document['querySelector']('.contents_style');var _0x35e549=_0x388803(0x103),_0x552a33=document['evaluate'](_0x35e549,document,null,XPathResult[_0x388803(0xf3)],null)[_0x388803(0x118)];let _0x407aa0;_0x552a33?(_0x407aa0=_0x552a33,_0x407aa0[_0x388803(0x11b)]='',_0x407aa0[_0x388803(0x106)](_0x44fb35)):(_0x407aa0=_0x2117e2[_0x388803(0x10f)],_0x407aa0['classList'][_0x388803(0xee)](_0x388803(0x100))||_0x407aa0[_0x388803(0xf9)]['contains'](_0x388803(0x115))?_0x2117e2['insertBefore'](_0x44fb35,_0x407aa0[_0x388803(0xef)]):_0x407aa0[_0x388803(0x113)][_0x388803(0x10e)](_0x44fb35,_0x407aa0));const _0x3e06b5=document['getElementById'](_0x388803(0x107)),_0x5ee2f2=[]['slice'][_0x388803(0x10b)](_0x2117e2[_0x388803(0xf4)]('h1,\x20h2,\x20h3,\x20h4,\x20h5,\x20h6')),_0x454032=[];for(i=0x0;i&lt;_0x5ee2f2[_0x388803(0x10d)];i++){if(_0x5ee2f2[i][_0x388803(0xf2)][_0x388803(0x119)]()==='')continue;else{if(_0x5ee2f2[i][_0x388803(0xf9)][_0x388803(0xee)](_0x388803(0x108)))continue;else{if(_0x5ee2f2[i][_0x388803(0x113)]['classList'][_0x388803(0xee)](_0x388803(0x101)))continue;else _0x454032[_0x388803(0xfc)](_0x5ee2f2[i]);}}}_0x454032[_0x388803(0x109)](function(_0x5d97e0,_0x2112a5){const _0x4b3465=_0x388803;var _0x94aa2e=_0x4b3465(0xf8)+_0x2112a5;if(_0x5d97e0[_0x4b3465(0xf6)]('id'))_0x94aa2e=_0x5d97e0[_0x4b3465(0xed)]('id');else _0x5d97e0[_0x4b3465(0xfb)]('id',_0x94aa2e);var _0x34278b=_0x4f1c99[_0x4b3465(0xf1)]('a');_0x34278b[_0x4b3465(0xfb)]('href','#'+_0x94aa2e),_0x34278b['textContent']='•\x20'+_0x5d97e0[_0x4b3465(0x11b)];var _0x118edf=_0x4f1c99[_0x4b3465(0xf1)](_0x4b3465(0x105));_0x118edf[_0x4b3465(0xfb)]('class',_0x5d97e0[_0x4b3465(0x116)][_0x4b3465(0xff)]()),_0x118edf[_0x4b3465(0x106)](_0x34278b),_0x3e06b5[_0x4b3465(0x106)](_0x118edf);});const _0xd72dc='\x0a\x20\x20\x20\x20#toc-ym\x20div.h1\x20{\x20margin-left:\x200em\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div.h2\x20{\x20margin-left:\x200.5em\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div.h3\x20{\x20margin-left:\x201em\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div.h4\x20{\x20margin-left:\x201.5em\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div.h5\x20{\x20margin-left:\x202em\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div.h6\x20{\x20margin-left:\x202.5em\x20}\x0a\x20\x20\x20\x20\x0a\x20\x20\x20\x20#toc-ym\x20{\x0a\x20\x20\x20\x20\x20\x20margin:\x2030px\x200px\x2030px\x200px;\x0a\x20\x20\x20\x20\x20\x20padding:\x2020px\x2020px\x2010px\x2015px;\x0a\x20\x20\x20\x20\x20\x20border:\x201px\x20solid\x20#dadada;\x0a\x20\x20\x20\x20\x20\x20background-color:\x20#ffffff;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym::before\x20{\x0a\x20\x20\x20\x20\x20\x20content:\x20\x22목\x20\x20차\x22;\x0a\x20\x20\x20\x20\x20\x20display:\x20block;\x0a\x20\x20\x20\x20\x20\x20width:\x20120px;\x0a\x20\x20\x20\x20\x20\x20background-color:\x20rgb(255,\x20255,\x20255);\x0a\x20\x20\x20\x20\x20\x20text-align:\x20center;\x0a\x20\x20\x20\x20\x20\x20font-size:\x2018px;\x0a\x20\x20\x20\x20\x20\x20font-weight:\x20bold;\x0a\x20\x20\x20\x20\x20\x20margin:\x20-40px\x20auto\x200px;\x0a\x20\x20\x20\x20\x20\x20padding:\x205px\x200px;\x0a\x20\x20\x20\x20\x20\x20border-width:\x201px;\x0a\x20\x20\x20\x20\x20\x20border-style:\x20solid;\x0a\x20\x20\x20\x20\x20\x20border-color:\x20rgb(218,\x20218,\x20218);\x0a\x20\x20\x20\x20\x20\x20border-image:\x20initial;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div{\x0a\x20\x20\x20\x20\x20\x20margin:\x205px\x200px;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div:first-child{\x0a\x20\x20\x20\x20\x20\x20margin-top:\x2015px;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div:last-child{\x0a\x20\x20\x20\x20\x20\x20margin-bottom:\x2015px;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div\x20a\x20{\x0a\x20\x20\x20\x20\x20\x20text-decoration:\x20none;\x0a\x20\x20\x20\x20\x20\x20color:\x20#337ab7;\x0a\x20\x20\x20\x20\x20\x20transition:\x20all\x20ease\x200.2s;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20#toc-ym\x20div\x20a:hover\x20{\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20color:\x20#333333;\x0a\x20\x20\x20\x20\x20\x20background-color:\x20#ecc7ff;\x0a\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20/*\x0a\x20\x20\x20\x20.contents_style\x20h3{\x0a\x20\x20\x20\x20\x20\x20margin-bottom:7px;\x0a\x20\x20\x20\x20\x20\x20padding:\x2010px\x2015px;\x0a\x20\x20\x20\x20\x20\x20border-left:\x205px\x20solid\x20#757575;\x0a\x20\x20\x20\x20\x20\x20background-color:\x20#e5e5e5;\x0a\x20\x20\x20\x20\x20\x20font-weight:\x20500;\x0a\x20\x20\x20\x20\x20\x20color:\x20#000000\x20!important;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20*/\x0a\x20\x20\x20\x20',_0x3ed036=document[_0x388803(0xf1)](_0x388803(0x102));_0x3ed036[_0x388803(0xfd)]=_0xd72dc,_0x2117e2[_0x388803(0x10e)](_0x3ed036,_0x407aa0);}
&lt;/script&gt;</description>
      <category>프로젝트/디버깅 경험</category>
      <category>aws</category>
      <category>Docker</category>
      <category>jmeter</category>
      <category>jmeter 연동</category>
      <category>Redis</category>
      <category>디버깅</category>
      <category>빈 등록</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/5</guid>
      <comments>https://j1alee.tistory.com/5#entry5comment</comments>
      <pubDate>Thu, 29 May 2025 13:41:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot + Prometheus] /actuator/prometheus 안 뜰 때 해결</title>
      <link>https://j1alee.tistory.com/4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 &lt;b&gt;애플리케이션 메트릭을 Prometheus로 수집&lt;/b&gt;하기 위해 Spring Boot에 Prometheus를 연동했습니다. 이 글에서는 제가 마주했던 문제와 그 원인, 그리고 어떻게 해결했는지를 기록합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작: Spring Boot Actuator만으로는 충분할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 Prometheus와 Spring Boot를 연동할 때는 다음 의존성을 추가합니다:&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-actuator'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 application.yml에 아래와 같이 설정하면 /actuator/metrics 등의 엔드포인트가 활성화됩니다:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  참고로 include: &quot;*&quot;로 모든 엔드포인트를 노출할 수도 있지만, 운영 환경을 고려해 필요한 항목만 명시적으로 포함했습니다. actuator는 편리하지만 보안 리스크가 있어, 전체 노출보다는 필요한 항목만 선택적으로 관리하는 것이 더 안전한 운영 방식이라 판단했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Prometheus 서버가 http://localhost:8080/actuator/prometheus를 스크랩하도록 설정하면 된다고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는 여기서부터였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❌ 예상치 못한 장애&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 애플리케이션은 정상적으로 기동되었고 /actuator 페이지도 잘 열렸습니다. 하지만&amp;hellip;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http://localhost:8080/actuator/prometheus 요청 &amp;rarr; &lt;b&gt;404 에러&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;콘솔 로그에 다음과 같은 메시지 출력:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;No static resource actuator/prometheus.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이 메시지를 보고 &lt;b&gt;정적 리소스 설정 문제&lt;/b&gt;라고 오해했습니다. 즉 /static/actuator/prometheus 같은 파일이 없는 것처럼 보여서 의존성에 문제가 있다고 생각했습니다(추후 디버깅 삽질로 web, webflux 의존성 충돌 의심을 합니다). 단순히 정적 파일이 없다고 생각했고, actuator를 잘 몰랐으니 이 사태가 납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 actuator 경로는 일반적인 정적 리소스가 아닌, &lt;b&gt;Spring이 노출하는 동적 엔드포인트&lt;/b&gt;입니다. 따라서 이 메시지는 /actuator/prometheus가 &lt;b&gt;아예 등록되지 않았다는 뜻&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;원인 분석: Prometheus registry가 없었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면, 아래 의존성을 추가하지 않았기 때문입니다:&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'io.micrometer:micrometer-registry-prometheus'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 내부적으로 Micrometer를 통해 다양한 메트릭을 수집하지만, Prometheus와의 연동은 &lt;b&gt;별도의 모듈로 분리&lt;/b&gt;되어 있습니다. 이 모듈이 없으면 /actuator/prometheus 엔드포인트 자체가 생성되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;Spring Boot 2.0 이후부터는 Micrometer가 기본 메트릭 수집 도구로 사용되며, Prometheus export 기능은 별도 registry로 제공됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  디버깅 삽질기: Web vs WebFlux 의존성 문제?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 의심을 잘못된 곳에 했습니다. spring-boot-starter-web과 webflux 의존성을 동시에 쓰고 있었고, 그게 원인일 수 있다고 생각했죠.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-webflux'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Web과 WebFlux는 각각 &lt;b&gt;DispatcherServlet&lt;/b&gt;과 &lt;b&gt;WebHandler&lt;/b&gt; 기반의 서로 다른 웹 엔진을 사용하기 때문에 충돌 가능성이 있습니다. 실제로 이 둘을 함께 쓰는 건 일반적으로 권장되지 않습니다. actuator 관련 빈의 해석 순서나 우선순위가 꼬일 수 있다는 것도 문서에서 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번 이슈는 &lt;b&gt;의존성 충돌 문제가 아니라, registry가 빠졌기 때문&lt;/b&gt;이었습니다. webflux를 제거해도 문제가 해결되진 않았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  최종 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Prometheus registry 모듈을 추가하고 애플리케이션을 재기동하자 /actuator/prometheus가 정상적으로 열렸습니다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'io.micrometer:micrometer-registry-prometheus'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus 서버에서도 상태가 UP으로 바뀌었고, 메트릭이 수집되기 시작했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 &lt;b&gt;Spring Boot Actuator만으로는 Prometheus 연동이 되지 않는다&lt;/b&gt;는 걸 체감했습니다. 또한:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Micrometer는 유연한 메트릭 수집 시스템이지만, 타 시스템(Prometheus, Datadog 등) 연동은 별도 모듈이 필요하다&lt;/li&gt;
&lt;li&gt;Web과 WebFlux는 동시에 사용하지 않는 것이 좋다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;단순히 작동하는 코드 이상으로, &lt;b&gt;왜 이렇게 동작하는지 이해하는 것이 중요하다&lt;/b&gt;는 걸 다시 느꼈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>도구, 설정</category>
      <category>actuator</category>
      <category>prometheus</category>
      <category>prometheus registry</category>
      <category>prometheus 설치</category>
      <category>spring boot actuator</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/4</guid>
      <comments>https://j1alee.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 22 May 2025 00:17:45 +0900</pubDate>
    </item>
    <item>
      <title>에프랩(F-Lab) 자바 코스 2개월 후기</title>
      <link>https://j1alee.tistory.com/3</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;뭐하는지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1달 동안은 주로 이론을 공부했다. 그리고 2달째 부터 현재까지 지금은 프로젝트를 만들고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;초보자, 비전공자이고 처음 프로젝트 만드는건데 괜찮을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 깃헙을 써보지 않은, pr도 모르던 비전공자였다. 프로젝트는 아주 간단한거 정도 만든 상태였다. 생각보다 깃헙을 다루고, 프로젝트 하는 것이 크게 어렵지는 않았다! 에프랩에는 깃헙을 어떻게 다루는지에 대해서 알려줘서 이 부분에서 큰 도움을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃헙 가이드라인은 에프랩에 상세하게 잘 나와 있으니까, 꼭 확인하길 바란다!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BDqrf/btsNSwQTbWH/WpAVz1ARtv2x2A7qEApfNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BDqrf/btsNSwQTbWH/WpAVz1ARtv2x2A7qEApfNK/img.png&quot; data-alt=&quot;많은 가이드가 있음!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BDqrf/btsNSwQTbWH/WpAVz1ARtv2x2A7qEApfNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBDqrf%2FbtsNSwQTbWH%2FWpAVz1ARtv2x2A7qEApfNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;644&quot; height=&quot;435&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;많은 가이드가 있음!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GiD0K/btsNQYOBOwk/tUkosx7hU29lc9aZmvvDj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GiD0K/btsNQYOBOwk/tUkosx7hU29lc9aZmvvDj0/img.png&quot; data-alt=&quot;이 영상으로 겨우 깃헙에 pr 성공했다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GiD0K/btsNQYOBOwk/tUkosx7hU29lc9aZmvvDj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGiD0K%2FbtsNQYOBOwk%2FtUkosx7hU29lc9aZmvvDj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;967&quot; height=&quot;614&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 영상으로 겨우 깃헙에 pr 성공했다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내가 한 실수들 - 자기주도적 생각의 중요성을 몰라서 생긴 실수들&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ai에 너무 과하게 의지하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지피티가 만들어주는 코드를 대부분은 이해하고 프로젝트에 넣으려하지만 급한 상황이나, 무의식 중에 완전한 이해없이 그냥 소스코드에 넣는 경우가 있다. 그래서 스스로 의문하고, 이게 정말로 필요한 것인지, 로직 수정을 할 수 있는지에 대해 생각해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;멘토가 다해주고, 정답을 알려줄거란 과한 기대하지 말기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 @DisplayName을 테스트 메소드에 다 써야하는지, BDD 스타일 모키토 문법이 최신이니까, 이걸로 전부 수정해야할지 등에 대해서 &lt;b&gt;멘토님이 전부 예, 아니오로 깔끔히 답해주실 줄 알았다.&lt;/b&gt; 나는 비전공자이고, 학생이니까 라는 마인드가 강했다. 하지만 멘토님은 실제로는 &amp;ldquo;그걸 함으로써 장점은 뭐라고 생각하시나요?, tradeoff가 있으니 생각해보세요, 저라면 이렇게 했을것 같은데, 멘티님도 생각해보시죠&amp;rdquo; 이런식으로 멘티가 스스로 생각할 수 있게 도와준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멘토님은 물고리를 잡아서 주시는게 아니라, 물고기 잡는 법을 알려주시는&lt;/b&gt; 것 같달까. 물론 물고리를 주셔서 걱정안해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생각하고, 스스로 사고하는 습관을 가르치는게 에프랩의 철학같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;멘토가 프로젝트 만드는 데에 얼마나 도움이 되었나?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 큰 도움이 되었다. 지피티 보다 훨씬 나았다. ai를 이용해서 코딩을 하면 hallucination(오해의 소지가 있는 정보((인공 지능 알고리즘에 의해 그럴 법하게 사실처럼 생성된 정보)) 등이 섞여 있다. 그리고 나무가 아니라 숲을 보는 능력이 부족해서 전체 테크스펙, 아키텍처에 대한 도움을 줄 수없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;멘토님이 특히 도와주신 점!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주제선정 - 내가 몇개 주제 후보를 가져오면, 멘토님이 이런 주제에는 어떤 기술이 필요할 거 같고~ 이렇게 즉석에서 말씀해주시는게 도움이되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처 아이디어 - 외부 api에 대한 아이디어를 멘토님이 도와주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 - 어떤 기술을 쓸지도 도와주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 로직 - 역시 경력자의 눈은 속일 수 없다 짱짱&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그래서 2달째 한 후기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 내가 비전공자, 초조라서 걱정이 많았는데, 멘토님이 잘 도와주셨다. 그 방법이 막 정답을 떠먹여주시고, 암기주입식 교육이 아니라, 생각하게 만들고, 정답을 유됴하고, 힌트를 주시는 방법이라 에프랩이 끝나도 좋은 개발자가 될 것 같다는 느낌이 든다.&lt;/p&gt;</description>
      <category>개발자 커리어</category>
      <category>f-lab</category>
      <category>f-lab 2개월 후기</category>
      <category>에프랩</category>
      <category>에프랩 2개월 후기</category>
      <category>자바 백엔드</category>
      <category>자바 백엔드 코스</category>
      <category>자바 코스</category>
      <category>후기</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/3</guid>
      <comments>https://j1alee.tistory.com/3#entry3comment</comments>
      <pubDate>Sun, 11 May 2025 10:37:17 +0900</pubDate>
    </item>
    <item>
      <title>에프랩(F-lab) 자바 백엔드 코스 1개월 후기 : 비전공자 리뷰</title>
      <link>https://j1alee.tistory.com/2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TLDR : 좋았다. 어렵지만 그만큼 배우는게 많다. 내가 포기하지 않아서 다행이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 F-Lab을 신청했는지 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 비전공자이다. 전공이 정말 정말 지루했고 뭘 공부해야하는지 고민이었다. 그러다가 우연히 컴퓨터를 공부해봤고, html, css, 그리고 자바 공부가 꽤 잘맞는다고 생각했다. 그렇게 독학을 몇달 하던 중에, 온라인에 있는 사람들이 추천하는 커리큘럼을 따라가자니, 위험성도 있고, 내 자신도 믿을 수가 없었다. 그리고 그 커리큘럼을 다라갔다가, 내가 책을 이해하지 못하기도 했다. 특히 나는 영어에 자신있다고 생각해서 영어로도 공부해봤는데, 책이 어려웠어서 땅을 많이 팠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 국비학원에 가기는 싫었다. 너무 많은 국비 학원 중 좋은 학원은 어떻게 찾을 것인가? 나는 너무 게으르고 서칭에 지쳐 있었다. 그냥 돈 좀 투자하고 비싸지만 유명한 것 같은 에프랩에 가는게 더 낫다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요약 : 개인적으로 독학을 하다가, 헤매는 느낌이 들었고, 이때 누군가가 나를 붙잡아 줬으면 했다. 그래서 에프랩에 들어왔다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번달 동안 무엇을 배웠는지 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바를 처음부터 공부했다. 나는 일단 주니어 개발자가 목표이고, 경력이 없어서 처음부터 공부한것 같기도 하다. 그런데 커리큘럼은 멘토분과 상의해서 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 기본서만 읽어서는 안된다는 걸 배웠다. &lt;b&gt;숨겨진 원리, 방대한 지식&lt;/b&gt; 등을 배웠다. 블로그, 지피티 등을 이용해 다양한 지식을 머리에 넣으려 했다. 자바의 기본서만 읽고 자바를 좀 안다고 자신했던 나는 참 오만했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토님은 어떻게 뽑는지, 어땠는지? :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 여러명 후보를 준다. 그 중에서 당신이 고를 수 있다. 멘토님들의 경력을 보고 잘 뽑으시길! 웬만하면 대부분 사람들이 안바꾼다고 들었다. 그만큼 &lt;b&gt;멘토분들이 다 좋으시단 뜻 같다!&lt;/b&gt; 적어도&lt;b&gt; 내 멘토분은 정말 정말 최고고 천사고 똑똑하시고(생략)&lt;/b&gt; 아무튼 좋다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 질문과 피드백에 대해서는&amp;hellip; 할말이 없다. 내가 질문을 잘 안하는 타입이라서^^;; 그런데 피드백이 늦을 거라는 걱정은 안해도 될 것 같다! 멘토분이 참 열정적이고 좋은 분이시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링에서 좋았던 점 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 독학을 하다 보니 참 막막했다. 내가 잘하는지, 못하는지도 모른다. 그런데 멘토링 수업이 끝날 때마다 멘토분이 나를 평가해주시고, 그러시니 좋다. 그리고 커리큘럼과 읽어야할 책도 척척 구해다 주시니, &lt;b&gt;독학의 단점이 확 사라진다.&lt;/b&gt; 혼자서 커리큘럼을 책임지고 걱정하는 거랑 누군가가 서포트 해주는 거랑 차원이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각오 : 솔직히 참 쉽지 않은 난이도이고 수업이다. 그래서 사실 초반에 에프랩에 돈 낭비했나? 내 수준이 너무 멍청한 것 같다고 생각을 했다. 그런데 나는 절박해서 여기 들어왔다. 에프랩 그만두고 어딜 갈 수 있는지 모르겠고, 나는 계획도 없었고, 개발자를 그만두면 뭘 해먹고 살지 막막했다. 그리고 무엇보다 개발자가 하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래에 실패하든, 후회하든 그건 미래의 일이다. 지금 무섭다고, 돈 낭비 같다고 그만두면, 나는 내가 후회할 것을 안다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패하면 어떤가? 그냥 하는 거다. 그러니 입다물고 에프랩에 계속 있으려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 총 감상 및 후기 : 일대일 멘토링이니까 사기가 줄어들지 않을까? 오프라인 대형 학원에 가서 사람들이랑 소통하며 배워야해!라는 마인드를 가지셨다면, 글쎄요, 전그렇게 생각 안해요. 일대일이고 온라인 멘토링이더라도 개인이 얼마나 동기부여되어 있고, 열심히 하느냐에 따라 결과는 다릅니다. &lt;b&gt;온라인이고 일대일이라는 특징에 걱정하실 필요는 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 감상은 좋다라는 것입니다. 특히 본인의 의지, 열정이 있다면, 좋은 프로그램이 될것 같아요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난이도 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 쉽지 않습니다 에프랩. 왜냐면 사실 경력자 분들도 많이 오시는 프로그램이거든요. 근데 나는 비전공자 신입 개발자가 되고 싶은 취준생이라는 점. 그래서 배경지식이 없다보니 수업 따라가는게 쉽지 않다. &lt;b&gt;하지만 성장하는건 원래 쉽지 않죠^^&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 에프랩 좋은 점 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;녹음이랑 스크립트 기능이 있어서 메모 안해도 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ai 멘토가 있어서 ai로도 공부가 된다. 아래 사진은 ai가 만든 질문이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X7cYN/btsMZX90tC3/Hojqbw8LWbHSv8AbazAlFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X7cYN/btsMZX90tC3/Hojqbw8LWbHSv8AbazAlFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X7cYN/btsMZX90tC3/Hojqbw8LWbHSv8AbazAlFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX7cYN%2FbtsMZX90tC3%2FHojqbw8LWbHSv8AbazAlFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;921&quot; height=&quot;272&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>개발자 커리어</category>
      <category>f-lab</category>
      <category>f-lab 1개월 후기</category>
      <category>에프랩</category>
      <category>에프랩 1개월 후기</category>
      <category>자바 백엔드</category>
      <category>후기</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/2</guid>
      <comments>https://j1alee.tistory.com/2#entry2comment</comments>
      <pubDate>Fri, 28 Mar 2025 14:39:04 +0900</pubDate>
    </item>
    <item>
      <title>소프트웨어 장인</title>
      <link>https://j1alee.tistory.com/1</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC43Mx/btsMsOZ3d9h/dVUO5wOZtk3DDwqnuuoms0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC43Mx/btsMsOZ3d9h/dVUO5wOZtk3DDwqnuuoms0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC43Mx/btsMsOZ3d9h/dVUO5wOZtk3DDwqnuuoms0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC43Mx%2FbtsMsOZ3d9h%2FdVUO5wOZtk3DDwqnuuoms0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;271&quot; height=&quot;400&quot; data-origin-width=&quot;271&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 장인을 한단어로 요약하면 &lt;b&gt;&lt;i&gt;&amp;lsquo;열정&amp;rsquo;&lt;/i&gt;&lt;/b&gt;이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가는 모든 직장, 프로젝트 하나하나가 투자이다. 커리어의 큰 목표를 향해 다가가는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은 인재를 채용하는 방법에 대해서도 기술한다. 나중에 팀 리더가 되어서 팀 운영, 면접을 해야할때 한번 더 읽으면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감상평 :&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 신입 자바 개발자가 되기 전에 개발자가 무엇인지에 대해 알 수 있었던 좋은 책이었다. 코딩은 혼자 하는게 아니라 여럿이서 하는 것이다. 고객을 만족시키는 전문가가 되고 싶다고 동기부여가 되었다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;1 소프트웨어 개발&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들은 다음과 같은 여럿을 할 수 있어야한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객과 대화하기&lt;/li&gt;
&lt;li&gt;테스트 / 배포 자동화하기&lt;/li&gt;
&lt;li&gt;전체 비즈니스에 영향을 미칠 기술 선정하기&lt;/li&gt;
&lt;li&gt;지리적으로 분산된 팀들과 협업하기&lt;/li&gt;
&lt;li&gt;고객을 도와 필요한 작업을 정의하기&lt;/li&gt;
&lt;li&gt;우선순위 선정하기&lt;/li&gt;
&lt;li&gt;진척 상황 보고하기&lt;/li&gt;
&lt;li&gt;변경사항과 기대일정 관리하기&lt;/li&gt;
&lt;li&gt;잠재 고객 및 파트너에게 제품 소개하기&lt;/li&gt;
&lt;li&gt;사전 영업 활동 지원하기&lt;/li&gt;
&lt;li&gt;개발 일정과 비용 산출하기&lt;/li&gt;
&lt;li&gt;채용 면접하기&lt;/li&gt;
&lt;li&gt;아키텍처 설계하기&lt;/li&gt;
&lt;li&gt;비기능적 요구사항과 계약 조건(SLAS) 검토하기&lt;/li&gt;
&lt;li&gt;사업 목표 이해하기&lt;/li&gt;
&lt;li&gt;주어진 여건에서 최적의 결정하기&lt;/li&gt;
&lt;li&gt;새로운 기술 주시하기&lt;/li&gt;
&lt;li&gt;더 나은 업무 방식 찾기&lt;/li&gt;
&lt;li&gt;고객에게 가치 있는 상품이 전달되고 있는지 고민하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애자일 매니페스토&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://agilemanifesto.org/iso/ko/manifesto.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://agilemanifesto.org/iso/ko/manifesto.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1740202470934&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;애자일 소프트웨어 개발 선언&quot; data-og-description=&quot;애자일 소프트웨어 개발 선언 우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고 있다. 이 작업을 통해 우리는 다음을 가치 있게 &quot; data-og-host=&quot;agilemanifesto.org&quot; data-og-source-url=&quot;https://agilemanifesto.org/iso/ko/manifesto.html&quot; data-og-url=&quot;https://agilemanifesto.org/iso/ko/manifesto.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://agilemanifesto.org/iso/ko/manifesto.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://agilemanifesto.org/iso/ko/manifesto.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;애자일 소프트웨어 개발 선언&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;애자일 소프트웨어 개발 선언 우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고 있다. 이 작업을 통해 우리는 다음을 가치 있게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;agilemanifesto.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 애자일 매니페스토와 함께, 열두 원칙들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2 소프트웨어 장인정신&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 장인정신이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 장인정신은 소프트웨어 개발의 프로페셔널리즘에 대한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이는 마음가짐이자 삶의 자세이다. 커리어에 주인의식을 가지고 프로페셔널하게 행동한다. 다른 개발자들에게 배우고 지식을 나눈다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소프트웨어 장인정신 매니페스토&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작하는 소프트웨어뿐만 아니라,&amp;nbsp;&lt;b&gt;정교하고 솜씨 있게 만들어진 작품을,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변화에 대응하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;계속해서 가치를 더하는 것을,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별적으로 협력하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;프로페셔널 커뮤니티를 조성하는 것을,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객과 협업하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;생산적인 동반자 관계를,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 앞의 항목들(일반 굵기)을 추구하는 과정에서, 다음 항목들(볼드체)이 꼭 필요함을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;동작하는 소프트웨어뿐만 아니라,&amp;nbsp;&lt;b&gt;정교하고 솜씨 있게 만들어진 작품을,&lt;/b&gt;&amp;nbsp;&amp;rsquo;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소스 코드는 예측 가능하고 유지보수될 수 있는 상태여야 한다. 그래야 애플리케이션을 수정하고 진화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;변화에 대응하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;계속해서 가치를 더하는 것을,&lt;/b&gt;&amp;nbsp;&amp;rsquo;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;계속 가치를 더한다는 의미는 신규 기능 추가, 버그 수정만 뜻하지는 않는다. 코드를 깔금히 정리하고 구조를 개선하며 확장성을 높이고, 테스트 가능하게 하고, 쉽게 유지보수 할 수 있게 하는 것을 포함한다.&lt;/li&gt;
&lt;li&gt;소트프웨어 생명을 늘리며 변화에 빠르게 대응할 수 있도록 유지하는 것이 주요 목표다. 그래서 좋은 설계, 자동화된 테스트 기술이 필요.&lt;/li&gt;
&lt;li&gt;보이스카웃이 캠핑 장소를 처음 발견했을 때보다 더 깨끗하게 남겨두라는 규율이 있다. 코드도 처음 발견했을때 보다 더 깨끗하게 관리해야 한다. - 로버트 마틴&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;개별적으로 협력하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;프로페셔널 커뮤니티를 조성하는 것을,&lt;/b&gt;&amp;nbsp;&amp;rsquo;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멘토링과 공유&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;고객과 협업하는 것뿐만 아니라,&amp;nbsp;&lt;b&gt;생산적인 동반자 관계를,&amp;rsquo;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우리는 고용자/피고용자 관계가 아니다. 적극적으로 프로젝트의 성공에 기여한다. 질문하고 비즈니스 이해하고, 개선사항 제시하고 고객과 동반자 관계이어야 한다 . &lt;b&gt;그리고 고용주를 잘 고르자.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3 소프트웨어 장인의 태도&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;끊임없는 자기계발&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;독서, 많은 독서&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기슬 웹사이트&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;팔로우할 리더 찾기&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;계속 훈련&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;카타&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;펫 프로젝트&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;오픈 소스&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;페어 프로그래밍&lt;/p&gt;
&lt;h1&gt;4 영웅, 선의, 프로페셔널리즘&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패할 것 같은데, 성공해서 영웅이되겠다는 말도 안되는 생각 하지말기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;아니오&amp;rsquo;라고 못하는 것은 선의가 아니다. 정확히 정보 전달하고 요구해라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로페셔널리즘은 나와 다른사람들에게 정직함을 의미.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;아니오&amp;rsquo;라고 할때 대안을 제시하면 좋다. 대안을 모르면 최선의 노력을 한다고 보여주기, 문제 대응이 어떻게 되는지 공유하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실용 주의 장인정신&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kent beck's four rules of simple design&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DO6NP/btsMruVLV2a/FIkNWm3tbI1cVlKGpWifK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DO6NP/btsMruVLV2a/FIkNWm3tbI1cVlKGpWifK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DO6NP/btsMruVLV2a/FIkNWm3tbI1cVlKGpWifK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDO6NP%2FbtsMruVLV2a%2FFIkNWm3tbI1cVlKGpWifK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;459&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 테스트의 통과&lt;/li&gt;
&lt;li&gt;중복의 최소화&lt;/li&gt;
&lt;li&gt;명료성의 최대화&lt;/li&gt;
&lt;li&gt;구성 요소의 최소화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용 말고도 이 책에는 높은 품질의 코드의 중요성, 커리어 정하기, 채용 방법, 면접자로서의 태도 등에 대해 많은 토픽이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책 리뷰</category>
      <author>j1adev</author>
      <guid isPermaLink="true">https://j1alee.tistory.com/1</guid>
      <comments>https://j1alee.tistory.com/1#entry1comment</comments>
      <pubDate>Sat, 22 Feb 2025 14:45:36 +0900</pubDate>
    </item>
  </channel>
</rss>