<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>danielyang-95.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 15 Mar 2026 08:59:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>danielyang-95.log</title>
            <url>https://images.velog.io/images/danielyang-95/profile/50a6bd24-194d-4407-a52c-f18da4712921/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. danielyang-95.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/danielyang-95" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Claude Code]]></title>
            <link>https://velog.io/@danielyang-95/Claude-Code</link>
            <guid>https://velog.io/@danielyang-95/Claude-Code</guid>
            <pubDate>Sun, 15 Mar 2026 08:59:59 GMT</pubDate>
            <description><![CDATA[<h2 id="context-관리--token-아끼기">Context 관리 == Token 아끼기</h2>
<p>opusplan</p>
<ul>
<li>/model opusplan</li>
</ul>
<p>/compact</p>
<ul>
<li>뭘 버릴지는 claude가 알아서 판단</li>
</ul>
<p>Extended Thinking</p>
<ul>
<li>답변 전에 깊게 생각 but 토큰 많이 잡아먹는다.</li>
<li>끄거나 환경변수로 제한을 걸자</li>
</ul>
<p>프롬프트 습관</p>
<ul>
<li>파일명 명시, 함수명 명시, 구체적인 요구사항 작성</li>
</ul>
<p>claude.md 관리</p>
<ul>
<li>500줄 이하 권장</li>
<li>나머지는 Skill.md로 분리</li>
</ul>
<h2 id="작업-관리-도구">작업 관리 도구</h2>
<ul>
<li>Claude Task Master(PRD, 체계적, TASKS) vs Shrimp Task Master(유연하게, MVP, TASKS + CONTEXT)</li>
</ul>
<h2 id="skills">Skills</h2>
<ul>
<li><a href="https://skillsmp.com/ko/categories">https://skillsmp.com/ko/categories</a></li>
</ul>
<h2 id="qa-에이전트에서-mcp로-나아가는-논리-구조">Q&amp;A 에이전트에서 MCP로 나아가는 논리 구조</h2>
<ul>
<li><strong>1단계: 도메인 지식의 객체화 (RAG 에이전트)</strong></li>
<li>회사의 복잡한 Call 인프라 설정, AICC 가이드라인 등 파편화된 정보를 AI가 이해할 수 있는 벡터 데이터로 전환합니다. 이는 MCP의 <strong>Resources</strong> 개념과 연결됩니다.</li>
</ul>
<ul>
<li><strong>2단계: Claude Code Skills를 통한 기능의 추상화</strong></li>
<li>현재 하고 계신 Claude Code 기반의 Skill 개발은 특정 목적을 위한 &#39;함수(Function)&#39;를 정의하는 과정입니다. 이는 MCP의 <strong>Tools</strong>를 만드는 논리적 연습이 됩니다.</li>
</ul>
<ul>
<li><strong>3단계: MCP Server로의 확장 (기여의 핵심)</strong></li>
<li>특정 IDE나 챗봇에 종속된 Skill이 아니라, 어떤 LLM 클라이언트(Claude Desktop, Cursor, 자체 솔루션)에서도 우리 회사의 도메인 데이터와 기능을 즉시 호출할 수 있는 <strong>표준 인터페이스(MCP Server)</strong>를 구축하는 것입니다</li>
</ul>
<p>확장 로드맵: Claude Code에서 MCP로</p>
<ul>
<li><strong>현재 (Skill 개발):</strong> 특정 환경(Claude Code)에서만 동작하는 로컬 자동화 스크립트 단계.</li>
<li><strong>미래 (MCP화):</strong> <code>npx</code>나 <code>docker</code>로 실행 가능한 독립적인 MCP 서버를 구축</li>
</ul>
<h2 id="cmux">cmux</h2>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/6c785935-3fa6-4088-8fef-6e9caa3df0aa/image.png" alt=""></p>
<blockquote>
<p>출처: <a href="https://goddaehee.tistory.com/557">https://goddaehee.tistory.com/557</a></p>
</blockquote>
<p>좌상단 : 오케스트레이션 Agent - Claude Code
우상단 : Cmux 내장 브라우저
좌하단 : 디자인 전문 Agent - Claude Code
우하단 : 디자인 전문 Agent - Codex </p>
<h2 id="ultraplan">ultraplan</h2>
<ul>
<li>refine with Ultraplan on Claude Code on the web</li>
</ul>
<blockquote>
<p>계획은 web에서 구현은 session</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA['육각형 개발자'을 읽고]]></title>
            <link>https://velog.io/@danielyang-95/%EC%9C%A1%EA%B0%81%ED%98%95-%EA%B0%9C%EB%B0%9C%EC%9E%90</link>
            <guid>https://velog.io/@danielyang-95/%EC%9C%A1%EA%B0%81%ED%98%95-%EA%B0%9C%EB%B0%9C%EC%9E%90</guid>
            <pubDate>Sat, 03 Jan 2026 07:40:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>정신없이 일하고 개발하다보니 기초를 잊어버릴 때가 종종 있는 것 같다. 다시 한번 내 상태를 점검하고 review하기 위해 도서관을 구경하다가 꽂힌 책을 읽어보았다.</p>
</blockquote>
<p><a href="https://www.yes24.com/Product/Goods/120215040?pid=123487&amp;cosemkid=go16903580734456008&amp;utm_source=google_pc&amp;utm_medium=cpc&amp;utm_campaign=book_pc&amp;utm_content=ys_240530_google_pc_cc_book_pc_12306%EB%8F%84%EC%84%9C&amp;utm_term=%EC%9C%A1%EA%B0%81%ED%98%95%EA%B0%9C%EB%B0%9C%EC%9E%90&amp;gad_source=1&amp;gad_campaignid=6762605740&amp;gbraid=0AAAAAD79IrqvoJcqSw_VTTgtJOWADZFE_&amp;gclid=Cj0KCQiA9t3KBhCQARIsAJOcR7ydWIw_er01hz8d72LTEezrphQhxSPQng34hq5m5p1O38nWfqN2ZuEaAky5EALw_wcB">육각형개발자 책</a></p>
<h1 id="개발이란">개발이란</h1>
<p>똑같은 패턴? 어디까지가 개발인가?</p>
<ul>
<li>코딩-구현 기술은 개발의 일부</li>
<li>새로운 구현기술 != 성장</li>
<li>서비스 회사<ul>
<li>고객 경험이 좋아야 수익-투자가 높다. 그래서 지속가능성이 높음</li>
</ul>
</li>
</ul>
<h3 id="개발에-필요한-것역량">개발에 필요한 것(역량)</h3>
<ul>
<li>구현 기술</li>
<li>설계 역량 -&gt; 시장 변화에 쉽게 해당하는 구조 -&gt; 이게 결정적이다.</li>
<li>업무 관리와 요구 분석, 공유, 리드&amp;팔로우</li>
</ul>
<br/>

<h1 id="구현-기술">구현 기술</h1>
<ul>
<li><p>학습 전략, 유행 상관없는 기초</p>
</li>
<li><p>학습대상 기준</p>
<ol>
<li>현재 사용중인 기술</li>
<li>문제를 해결하기위한 기술</li>
</ol>
</li>
<li><p>ex) </p>
<ul>
<li>스프링으로 된 API 지식 - Controller, Json 변환 처리, 트랜잭션 처리(원하는 대로 롤백이 안됨)</li>
<li>Database: 최대 커넥션 유지시간, 개수, 계정 권한과 같은 주요 설정 수정</li>
<li>메시징 프로그램: kafka 사용 시 주요 구성 요소인 파티션, 레플리카, 프로듀서, 컨슈머의 동작 이해 =&gt; 데이터 손실과 서비스 중단 최소화 가능</li>
<li>운영체제: 부팅 시 자동으로 PG 시작하기, 권한 설정, 스케줄링, 운영체제 상태보기 등</li>
</ul>
</li>
<li><p>구현기술 학습에는 끝이 없다. 주기적으로 탐색하고 학습하자</p>
</li>
<li><p>유행 상관없는 개발 기초</p>
<ul>
<li>HTTP 프로토콜, 네트워크 프로그래밍 기초, 동시성 처리, 프로그래밍 언어 등</li>
</ul>
</li>
</ul>
<h3 id="구현-기술-적용">구현 기술 적용</h3>
<ul>
<li>기술 도입 시 고려사항<ol>
<li>신뢰 구축: 신뢰받아야한다. </li>
<li>함께 할 동료: 기술에 대해 함께 논의하고 공감대를 형성하는 동료</li>
<li>타당성: 답이 아닌 질문을 따라하라. 즉, 다른 곳에서 해당 기술 사용하는 맥락을 따라해야!</li>
<li>점진적 적용</li>
<li>시장 상황: 해당 기술에 능숙한 인력풀이 많은가?</li>
</ol>
</li>
</ul>
<ul>
<li><mark style="background: #FF5582A6;">POC: 개념 증명</mark></li>
</ul>
<br/>

<h1 id="소프트웨어-가치와-비용">소프트웨어 가치와 비용</h1>
<ul>
<li>SW 버전이 올라가면 개발 비용이 올라간다. <ul>
<li>세상의 변화에 맞춰 같이 변해야하기 때문</li>
<li>이게 유지보수의 중요!
  =&gt; SW의 가치를 유지한다. 유지보수를 지루하다고만 보지말라</li>
</ul>
</li>
</ul>
<h3 id="유지보수-비용-낮추기">유지보수 비용 낮추기</h3>
<ul>
<li>코드품질은 유지보수 비용과 관련이 있다.<ol>
<li>프로그래밍 패러다임을 알맞게 적용</li>
<li>코드 가독성 높이고 전형적인 디자인패턴 사용, 요구에 알맞은 아키텍처 적용</li>
<li>프로세스와 문화</li>
</ol>
</li>
</ul>
<br/>

<h1 id="코드-이해">코드 이해</h1>
<ul>
<li>코드 개발-변경에는 코드 이해 시간이 많이 요구된다. 그래서 이를 줄이려면<ol>
<li>코드 제대로 이해하는 역량<ul>
<li>코드 시각화 ex) UML<ul>
<li>클래스 다이어그램</li>
<li>Activity 다이어그램: 논리적 단위</li>
<li>시퀀스 다이어그램: 런타임. 시간 흐름에 따라 구성요소 간 연동 과정</li>
<li>상태 다이어그램</li>
</ul>
</li>
<li>스크래치 리팩토링: 이해용 리팩토링. 끝나면 원복</li>
</ul>
</li>
<li>이해하기 쉬운 코드 작성 역량<ul>
<li>가독성 ex) 켄트백의 구현코드, 클린 코드 =&gt; <mark style="background: #FF5582A6;">규칙을 만든 상황</mark>을 이해하자</li>
<li>작성법<ul>
<li>이름, if 줄이기, 변수 줄이기, 값 변경 최소화, 길지않은 코드</li>
<li>알맞은 파라미터 사용
  ex) MAP: 코드양 적고, 초기에는 좋으나 개발 생산성이 점점... 자동완성x, 코드 수정 시..</li>
<li>추상화 수준 맞추기<ul>
<li>메서드 추출할때. 즉 depth를 맞추라. </li>
<li>메서드 계속 이동하는 것도 흐름이 끊긴다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<br/>

<h1 id="응집도와-결합도">응집도와 결합도</h1>
<ul>
<li><p>응집도: 관련 도메인이 얼마나 같은 모듈에 존재 =&gt; 수정시 같은 곳에서만 수정되도록!</p>
<ul>
<li>목표<ol>
<li>가독성</li>
<li>수정 비용 낮추기</li>
<li>단일 책임: 구성요소를 수정할 이유는 하나여야한다. <mark style="background: #FF5582A6;">캡슐화</mark>를 통해 충족된다!</li>
</ol>
</li>
</ul>
</li>
<li><p>결합도: 두 모듈이 서로 의존하는 정도 =&gt; 한 쪽 수정하면 다른 쪽도 수정해야하면 결합도가 높아지는 것</p>
<ul>
<li>수정대상이 많으면 가독성 저하, 수정비용 증가</li>
<li>해결 방법: 추상화 타입(구현체 별도), 이벤트, 상속보다는 조립</li>
</ul>
</li>
</ul>
<br/>

<h1 id="리팩토링">리팩토링</h1>
<ul>
<li>legacy에 대한 정의: 정의는 다양하나, 공통점은 수정이 어렵다는 것</li>
<li>참고책: 리팩터링 2판(2020)</li>
<li>리팩토링의 목표: 코드 수정 비용 낮추기 위해 코드를 수정하기 쉬운 구조로 변경<ul>
<li>기존 동작 유지, 내부 구조 변경<ul>
<li>Test Code 필요</li>
<li>미사용코드는 todo 삭제예정(날짜)로 주석 처리하자</li>
</ul>
</li>
</ul>
</li>
<li>리팩토링 방법: 미사용 삭제, 매직넘버, 이름 변경, 메서드 추출(가독성-응집도), 클래스 추출, 클래스 분리, 메서드 분리, 파라미터값 정리
  +같은 for문 내 작업 분리<pre><code>  - for문 더 돈다고 걱정하지만 대부분 성능문제 상관없다. 문제가 될 때에 최적화하면 되는 것. 오히려 이해하기 좋은 코드로서의 이점이 더 있다.</code></pre></li>
</ul>
<br/>

<h1 id="테스트">테스트</h1>
<ul>
<li>테스트 코드 작성이 어려우면 테스트 카능성 높이기만이라도 해야한다. </li>
<li>리팩토링을 위한 Test Code</li>
<li>다양한 테스트 코드를 작성하면서 <mark style="background: #FF5582A6;">업무에 대한 이해도</mark>가 높아진다!</li>
<li>TDD: 코드 작성 - 구현 - 코드 정리의 과정을 반복<ul>
<li>예외 case 먼저. 그리고 정상 case</li>
<li>테스트 코드는 다른 테스트 추가를 쉽게한다. </li>
<li>edge case를 분별하기 쉽다.</li>
<li>TDD는 설계 지원<ul>
<li>테스트 대상의 이름, 메서드, 파미터 등을 결정해야한다. 또한 테스트 대상이 직접 기능을 구현하는 게 아니라면 다른 타입에 구현을 구현을 미루는 형태로 역할 분리 =&gt; 이게 설계 과정
<img src="https://velog.velcdn.com/images/danielyang-95/post/bd0468d6-f48c-42f1-8834-dcd4c46db921/image.png" alt=""></li>
</ul>
</li>
<li>처음에는 코드 작성시간이 높지만 반복되는 테스트 시간이 줄어든다. 추가로, 일부만 검증하면 되기 때문에 디버깅 시간이 줄어든다.</li>
</ul>
</li>
<li>외부 연동은 별도 타입으로 분리하자</li>
</ul>
<br/>

<h1 id="아키텍처---패턴">아키텍처 - 패턴</h1>
<ul>
<li>아키텍처 역량: 주니어-시니어 구분 기준</li>
<li>아키텍처: SW 추상적인 구조<ul>
<li>결정요소<ol>
<li>기능 요구사항: SW로 해결하고자하는 문제</li>
<li>품질 속성(비기능 요구사항): 성능, 확장성, 도메인 준수, 가용성, 보안, 유지보수성<ul>
<li>품질 속성의 trade off: 품질속성이 높아지면 시스템 복잡도가 높아진다.</li>
</ul>
</li>
</ol>
</li>
</ul>
</li>
<li>품질 속성
  <img src="https://velog.velcdn.com/images/danielyang-95/post/d22c3f61-bf3e-448f-b87e-52f3270143b3/image.png" alt=""></li>
</ul>
<p>=&gt; 아키텍처에 따라 적합, 부적합 case 구분하기!</p>
<p>빅뱅방식: 아키텍처를 바꾸기 위해 모든 걸 다시 개발 ex) 차세대 프로젝트</p>
<ul>
<li>최신 장비와 최신 SW를 사용해서 기존 시스템을 새로 구현하는 것</li>
<li>단점: to-be 신규 요구사항. as-is 기능 누락. 시간이 오래 걸린다. 
  =&gt; 점진적으로 구조 변경: 모놀리식 1개 -&gt; 모놀리식 2개 등 서비스 분리 + 비동기 연동 by 메시징 시스템</li>
</ul>
<h3 id="패턴-익히기">패턴 익히기</h3>
<ul>
<li>패턴: 특정 맥락에서 반복되는 문제해결법</li>
<li>패턴의 종류: 아키텍처 패턴, 디자인 패턴, 기업 통합 패턴, 결함 허용 패턴<ul>
<li>아키텍처 패턴: 아키텍처 수준에서의 패턴
  ex) 포트-어댑터, 마이크로서비스, 이벤트 기반<ul>
<li>이벤트 기반은 탄력성과 성능에 장점 but 트랜잭션 처리 복잡, 테스트 어려움</li>
</ul>
</li>
<li>디자인 패턴: ex) GoF 디자인 패턴 - 전략, 커맨드, 싱글톤, 템플릿, 팩토리 메서드, 프록시 등</li>
<li>기업 통합 패턴: 파일 전송부터 메시징에 이르기까지 시스템 간 통합을 위한 패턴<ul>
<li>최근에는 기업 간 연동뿐 아니라 내부 시스템 간 연동도 증가하는 추세이기 때문</li>
</ul>
</li>
<li>결함 허용 패턴 ex) 하트비트, 재시작, 재시도 제한, 서킷 브레이커 등 =&gt; 주로 MSA</li>
</ul>
</li>
</ul>
<br/>

<h1 id="업무-관리">업무 관리</h1>
<ul>
<li>업무 관리의 기초: 업무 나누기, 위험 관리, 요구 사항</li>
<li>점진적-반복적 개발, 수작업 줄이기, 이유와 목적</li>
</ul>
<h4 id="업무-나누기">업무 나누기</h4>
<ul>
<li>개발 계획<ul>
<li>업무의 크기에 따라 일하자. 일은 하루에서 수일 이내로 끝낼 수 있는 크기로 나누자. </li>
<li>계획이 있어야 진행상태 파악. 변화에 대응.</li>
<li>계획을 세우려면 일의 규모를 파악해야한다.</li>
<li>규모를 파악하려면 작업 목록이 필요하다. </li>
<li>완료는 개발 + 검증까지</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/140a88e0-4ab9-47ce-af66-2f411e7f1f39/image.png" alt=""></p>
<h4 id="위험-관리">위험 관리</h4>
<ul>
<li>스스로 위험 신호 감지시 즉시 공유</li>
<li>겪었던 경험으로 나중에 시니어가 되면 주니어 개발자가 놓치기 쉬운 위험요소를 확인하고 해소 필요</li>
<li>미리 위험 목록 작성 + 등급 매김</li>
</ul>
<h3 id="변경되는-요구사항">변경되는 요구사항</h3>
<ul>
<li>바뀌는 요구사항 단계 : 초기 &gt; 중간 &gt; QA 과정에서</li>
<li>요구사항의 변동 폭을 줄이려면 <ol>
<li>왜 이런 요구사항을 원하는지 이해하려는 노력 필요 
 =&gt; 이를 고민하면 이해관계자가 원하는 결과에 가까운 산출물을 얻기 쉬워진다. </li>
<li>요구사항을 나눠서 분석하자<ul>
<li>초반에 모든 것을 세세하게 분석 x. 개략적으로.</li>
<li>전체 요구사항 중 절반을 초반에 집중 개발. 그 다음에 나머지 요구사항 정리</li>
<li>agile 역시 비슷하다. 스크럼에서 스프린트를 진행할 때 앞으로 진행할 2~3번의 스프린트에서 개발할 요구사항만 자세하게 도출. 다른 요구사항은 상세하게 기술하지않고 이름과 개요 정도만 도출. 필요한 시점에 세부 요구사항을 만들면 불필요한 시간 낭비 줄여준다. </li>
</ul>
</li>
</ol>
</li>
<li>우선 순위 세우기 ex) 이번 오픈은 어디까지. 다음 오픈 때 이걸 하자는 둥</li>
</ul>
<h3 id="점진적---반복적-개발">점진적 - 반복적 개발</h3>
<ul>
<li>점진적 개발: 결과물을 구분되는 조각으로 나누고 각 조각을 점진적으로 완성하는 방식 ex) 스크럼의 스프린트<ul>
<li>스프린트: 고정된 기간을 정하고 그 기간에 선택한 작업을 완료하는 것을 목표로 진행하는 작업 단위</li>
<li>개발 기간동안 이런 스프린트를 지속하며 하나의 스프린트가 끝나면 출시할 수 있는 결과물이 나온다.
  =&gt; 스프린트마다 가치를 만든다.</li>
<li>점진적 개발의 핵심은 작업을 분할해서 더 빨리 가치를 제공하는 데 있다.</li>
</ul>
</li>
<li>반복적 개발: 사용자 요구사항 또는 제품 일부분을 반복해서 개발하여 목표로 하는 결과를 만드는 방식 <ul>
<li>주로 난이도가 높은 개발 진행시에 주로 사용 ex) 60% -&gt; 80% -&gt; 100% 수준</li>
</ul>
</li>
</ul>
<h3 id="이유와-목적-생각하기">이유와 목적 생각하기</h3>
<ul>
<li>요청 시에 이유와 목적을 설명해야 목표한 것을 이루기 쉽다.</li>
<li>뿐만 아니라 맡은 업무에 대해서도 이유와 목적을 알아야!</li>
</ul>
<br/>

<h1 id="정리하고-공유하기">정리하고 공유하기</h1>
<ul>
<li>글로 정리해서 공유하기, 발표하기</li>
</ul>
<h3 id="글로-정리해서-공유하기">글로 정리해서 공유하기</h3>
<ul>
<li>글의 주제, 개요, 목적, 대상 =&gt; 목차</li>
<li>SCQA 프레임워크<ul>
<li>Situation, Complication, Question, Answer</li>
</ul>
</li>
<li>모호한 표현 줄이기</li>
<li>배경, 정보 제공하기(Context)</li>
<li>5가지 글쓰기 팁<ul>
<li>문장 짧게 쓰기</li>
<li>글머리 기호 목록-번호 목록 사용하기</li>
<li>표나 그래프 사용하기</li>
<li>그림 사용하기</li>
<li>시각적 효과 사용하기</li>
</ul>
</li>
<li>평소 실천<ul>
<li>출퇴근 이동 시간: 글의 주제나 내용 흐름 등을 정리한다.</li>
<li>저녁: 자기 전에 20분~1시간 정도 시간을 내서 글을 쓴다.</li>
<li>점심시간: 점심을 먹고나서는 남는 시간을 활용해서 글을 쓴다.</li>
</ul>
</li>
<li>글쓰기 연습 주제<ul>
<li>일기: 그 날 있었던 일 중에서 기억나는 일을 기록한다.</li>
<li>시스템 구조 설명: 담당하는 시스템 구조를 설명하는 글을 써본다. 시스템을 시각적으로 표시하는 연습도</li>
<li>문제 해결 방안: 운영 중인 시스템의 문제 해결방안을 글로 적어본다.</li>
</ul>
</li>
<li>마인드맵 ex) XMind</li>
</ul>
<h3 id="발표하기">발표하기</h3>
<ul>
<li>사내에 도입하고 싶은 기술 또는 프로세스가 있다면 설득하기위해, 내 의견에 동조하기위해서도 발표</li>
<li>외래어 남용 x</li>
</ul>
<br/>

<h1 id="리더와-팔로워">리더와 팔로워</h1>
<h3 id="리더-연습하기">리더 연습하기</h3>
<ul>
<li>우리는 서로에게 영향을 주기 때문에 직위, 직급 상관없이 언제든 리더가 될 수 있다.</li>
<li>문제 해결 리더십</li>
<li>사람이 아닌 프로세스-시스템 변화시키기<ul>
<li>사람은 쉽게 바뀌지않으므로</li>
<li>프로세스, 시스템도 마찬가지다. 그러니 본인이 모범 사례로</li>
</ul>
</li>
<li>기술력 상실의 두려움 없애기<ul>
<li>리더, 관리자가 되면 코딩에서 멀어진다. 하지만 넓은 시야, 깊은 수준 또한 중요한 역량
  ex)아키텍처 설계, 복잡한 시스템을 알맞은 단위로 분해, 진행계획 세우기, 적합한 기술 선택</li>
</ul>
</li>
<li>리더로서 대신하지않기, 자율성 보장, 요청하기</li>
<li>SW의 규모의 비경제(사공이 많으면 배가 산으로 간다)<ul>
<li>대규모 프로젝트는 반드시 여러 개의 프로젝트로 나눠야 생산성이 높다. 인력이 많다고 빨리 끝나는 게 아니다. 독립성에 중점을 둬서 프로젝트를 나눈다.<ul>
<li><mark style="background: #FF5582A6;">도메인</mark>: SW로 해결하고자 하는 문제 영역</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="팔로워십">팔로워십</h3>
<ul>
<li>리더를 따르는 것만이 아니라 리더와 조화를 이루고 능동적으로 일을 수행하면서 리더가 성공할 수 있도록 지원하는 것. 리더와 소통하고 공감하며 문제를 발견하고 의견을 제시해서 리더와 함께 조직의 목표를 달성하는데 기여한다.</li>
<li>좋은 팔로워가 되려면 전문성 + 리더의 결정에 대한 맥락 파악</li>
</ul>
<h3 id="겸손-존중-신뢰">겸손, 존중, 신뢰</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[무슨 문제가 있고, 어떻게 해결해보았는가?]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AC%B4%EC%8A%A8-%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%9E%88%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@danielyang-95/%EB%AC%B4%EC%8A%A8-%EB%AC%B8%EC%A0%9C%EA%B0%80-%EC%9E%88%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EA%B2%B0%ED%95%B4%EB%B3%B4%EC%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Tue, 04 Nov 2025 10:11:56 GMT</pubDate>
            <description><![CDATA[<h3 id="ai-agent의-비결정성">AI agent의 비결정성</h3>
<ul>
<li>프롬프트 ai</li>
<li>워크플로우 관련 요구사항 md</li>
<li>테스트 코드가 먼저 준비될 수 있도록</li>
</ul>
<h3 id="카톡방에-매일-올라오는-면접-질문들을-어떻게-쉽게-정리할-수-있을까">카톡방에 매일 올라오는 면접 질문들을 어떻게 쉽게 정리할 수 있을까?</h3>
<ul>
<li>대화내용 내보내기</li>
<li>notebooklm 활용<ul>
<li>근데 붙여넣은 텍스트는 수정이 안되는 문제...</li>
</ul>
</li>
</ul>
<h3 id="geeknews-같은-트렌드와-인사이트들을-어떻게-꾸준히-익힐-수-있을까">Geeknews 같은 트렌드와 인사이트들을 어떻게 꾸준히 익힐 수 있을까?</h3>
<ul>
<li>notebooklm 활용</li>
</ul>
<h3 id="claude-code-vs-github-copilot">Claude code vs Github Copilot</h3>
<p>Claude Code 사용 권장:</p>
<ul>
<li>대규모 리팩토링</li>
<li>여러 파일에 걸친 작업</li>
<li>복잡한 아키텍처 변경</li>
<li>긴 대화가 필요한 작업</li>
<li>프로젝트 전체 이해가 필요한 경우</li>
</ul>
<p>GitHub Copilot 사용 권장:</p>
<ul>
<li>빠른 코드 자동완성</li>
<li>단일 파일 내 작업</li>
<li>간단한 함수 생성</li>
<li>주석 기반 코드 생성</li>
<li>IDE를 떠나지 않고 작업</li>
</ul>
<h3 id="주간보고-작성">주간보고 작성</h3>
<p>ㅡ 이거 커밋기반 안되나?</p>
<h3 id="코드리뷰-agent">코드리뷰 agent</h3>
<h3 id="회사-지식-관리-agent">회사 지식 관리 agent</h3>
<h3 id="모바일에서-youtube-링크-던지면-빠르게-요약하고-시각화">모바일에서 youtube 링크 던지면 빠르게 요약하고 시각화?</h3>
<h3 id="절대-보안을-지키는-ai-agent">절대 보안을 지키는 AI agent</h3>
<h3 id="개발하기-전-준비를-자동화">개발하기 전 준비를 자동화</h3>
<h3 id="외국인들과-일하는-때가-온다면">외국인들과 일하는 때가 온다면?</h3>
<ul>
<li>youtube로 내가 직접 영어로 프로젝트 설계하고 설명하는 영상으로 미리 연습?</li>
</ul>
<h3 id="도메인-지식-쌓기">도메인 지식 쌓기</h3>
<ul>
<li>내 스스로 app을 만들어서 쉽게 도메인 지식들을 학습하는 자동화 과정?</li>
</ul>
<h3 id="나는-노션과-obsidian에-많은-it-기술-문서들을-만들었다-그렇다보니-너무-분산되서-정리가-좀-필요한데-어떻게-하면-좋을까">나는 노션과 obsidian에 많은 IT 기술 문서들을 만들었다. 그렇다보니 너무 분산되서 정리가 좀 필요한데, 어떻게 하면 좋을까?</h3>
<ul>
<li><p>notebooklm에게 다 던질까?</p>
</li>
<li><p>obsidian에 통일하자</p>
<ol>
<li>코드 블록 하이라이팅, 로컬 파일 검색, 마크다운 기반의 관리가 Obsidian에서 훨씬 빠르고 쾌적</li>
<li>모든 문서를 로컬 마크다운으로 변환하여 Obsidian에서 관리<ul>
<li>오프라인 접근성, 빠른 속도, 향후 Local LLM(RAG)을 붙여 나만의 AI 비서를 만들기에 유리</li>
</ul>
</li>
</ol>
</li>
<li><p>정리 방법론: PARA 시스템 도입
  <img src="https://velog.velcdn.com/images/danielyang-95/post/640fec8b-1510-4e1a-95eb-f8e3e72ac93b/image.png" alt=""></p>
</li>
<li><p>마이그레이션
  Step 1. Notion 데이터 가져오기 (Importer)</p>
<ul>
<li>일일이 복사/붙여넣기 하지 마세요.</li>
<li>Notion 공식 내보내기: Notion 페이지 우상단 ... -&gt; 내보내기 -&gt; Markdown &amp; CSV 선택 -&gt; 다운로드.</li>
<li>Obsidian Importer 플러그인: Obsidian 커뮤니티 플러그인에서 Importer 설치 -&gt; Notion에서 받은 Zip 파일 혹은 HTML 파일을 선택하여 일괄 변환.</li>
</ul>
<p>Step 2. &#39;연결&#39;을 통한 정리 (MOC)</p>
<ul>
<li>폴더에 집착하기보다 MOC (Map of Content) 페이지를 하나 만드세요.
예: 000_Dev_Home.md 파일을 만들고, 거기에 [[Docker]], [[Reactflow]], [[AICC]] 처럼 주요 키워드를 링크로만 걸어두세요. 문서가 어디에 있든 검색(Ctrl/Cmd + O)과 링크로 접근하는 습관을 들이면 정리에 대한 강박이 사라집니다.</li>
</ul>
</li>
</ul>
<p>추천 구조</p>
<ul>
<li>Obsidian: 기술 레퍼런스, 아키텍처, 연구 문서, 코드 스니펫, RFC</li>
<li>Notion: 프로젝트 정리, 진행 관리, 팀 공유 문서</li>
</ul>
<br/>

<blockquote>
<p>기술 문서는 주제 기반 폴더링 + 태그의 조합이 제일 유지보수성 좋다.</p>
</blockquote>
<p>템플릿 도입</p>
<ul>
<li>형식 통일하면 검색/관리 효율 크게 올라간다.</li>
<li>예: 기술 문서 템플릿<ul>
<li>Summary</li>
<li>Use Cases</li>
<li>Pros / Cons</li>
<li>관련 키워드</li>
<li>관련 대안 기술</li>
<li>아키텍처/시퀀스</li>
<li>참고 링크</li>
</ul>
</li>
</ul>
<blockquote>
<p>다니엘의 업무 특성상 특히 AICC, Call Infra, WebRTC, SIP, 음성봇/챗봇 아키텍처
같이 자주 쓰는 템플릿을 따로 만들어두면 유지보수 편함.</p>
</blockquote>
<p>검색 중심 운영</p>
<ul>
<li>폴더보다 중요한 게 검색 최적화다. 태그/백링크 적극 활용하면 분산문서라도 서로 연결된다.</li>
<li>Obsidian은 다음 플러그인 추천:<ul>
<li>Dataview: 기술 문서 쿼리 기반 리스트업</li>
<li>Calendar + Periodic Notes: 공부 기록/리뷰 자동화 </li>
<li>Full-Text Search Enhancer: 검색 정확도 향상</li>
</ul>
</li>
</ul>
<p>실행 플랜 (요약)</p>
<ul>
<li>메인 저장소 결정(Obsidian 추천)</li>
<li>구조/태그/템플릿 확정</li>
<li>Notion → Obsidian 마이그레이션</li>
<li>중복 제거 및 폴더 재정비</li>
<li>자동화(Dataview 등) 도입</li>
<li>리뷰 루틴 설정</li>
</ul>
<hr>
<p>GPT/Gemini/Perplexity로 “문서 분류 + 카테고리 구조 제안”만 맡기기</p>
<ul>
<li>문서들을 그대로 던져서:<ul>
<li>이 문서들의 기술 카테고리 뽑기</li>
<li>중복 문서 리스트업</li>
<li>전체 지식맵 제안
→ 이 단계까지는 AI가 매우 잘한다.</li>
</ul>
</li>
</ul>
<h3 id="ide와-obsidian-간의-context-switching-비용">IDE와 Obsidian 간의 context switching 비용</h3>
<ul>
<li>VSC에서 foam, excalidraw 활용<ul>
<li>Obsidian vs. VS Code 파일 호환 문제 해결 필요</li>
</ul>
</li>
</ul>
<h3 id="노트-정리할-때마다-이-내용을-알아서-정리해서-특정-키워드에-던져줄-수-있다면">노트 정리할 때마다 이 내용을 알아서 정리해서 특정 키워드에 던져줄 수 있다면?</h3>
<ul>
<li>QuickAdd 플러그인 +@ Text Generator</li>
</ul>
<h3 id="excalidraw-많은-기능들">excalidraw 많은 기능들</h3>
<ul>
<li>Excalidraw Automate (자동화 API)</li>
<li>*&quot;스크립트로 그림을 제어할 것인가?&quot;**에 대한 설정입니다.<ul>
<li>핵심 기능: Templater나 QuickAdd 같은 다른 플러그인에서 Excalidraw를 조작할 수 있는 API를 노출합니다.</li>
<li>개발자 팁: 예를 들어, &quot;매일 아침 데일리 노트가 생성될 때, 빈 캔버스를 자동으로 만들고 오늘 날짜를 박아넣는 스크립트&quot;를 짤 때 이 기능이 켜져 있어야 합니다.</li>
</ul>
</li>
</ul>
<h3 id="youtube-무료-요약">Youtube 무료 요약</h3>
<ol>
<li>브라우저 확장 프로그램: YouTube Summary with ChatGPT &amp; Claude (Glasp) - 영상에 자막(CC)이 없으면 작동 X</li>
<li>웹사이트형 (한국어 특화): 릴리스 AI, Daglo(스크립트 추출용)</li>
<li>AI 직접 사용 (별도 설치 X): Gemini - 구글(유튜브) 자사 모델이므로 영상 접근 권한이 가장 좋다.<ul>
<li>프롬프트창에 @YouTube를 입력하거나 그냥 링크를 붙여넣고 &quot;이 영상 내용을 개발자 관점에서 요약해줘&quot;라고</li>
<li>특정 부분(예: &quot;코드 나오는 부분만 찾아줘&quot;)에 대해 추가 질문 가능</li>
</ul>
</li>
</ol>
<h3 id="ide-속도-줄이기">IDE 속도 줄이기</h3>
<ul>
<li>Intellij: 커밋 시 불필요한 자동 검사 끄기, 메모리 할당량 늘리기, 맞춤법/오타 검사 끄기 등 CPU와 메모리 자원 아끼기</li>
</ul>
<h3 id="주간일간-보고-자동화">주간/일간 보고 자동화</h3>
<h3 id="obsidian이나-notion에서-매일-특정-시간에-특정-문서-내용을-분석해서-다른-여러-문서에-요약된-내용을-정리하게끔-batch를-돌릴-수-있는가">obsidian이나 notion에서 매일 특정 시간에 특정 문서 내용을 분석해서 다른 여러 문서에 요약된 내용을 정리하게끔 batch를 돌릴 수 있는가</h3>
<ol>
<li><p>Obsidian: &quot;OS 스케줄러 + Python 스크립트&quot;</p>
</li>
<li><p>Notion: &quot;Make.com (iPaaS) + Notion API&quot;</p>
</li>
<li><p>Obsidian 플러그인 활용 (반자동 Batch)</p>
</li>
</ol>
<ul>
<li>Templater 플러그인<ul>
<li>&quot;Startup Template&quot; 기능을 사용하여, Obsidian을 켤 때 특정 스크립트가 실행되게</li>
</ul>
</li>
</ul>
<blockquote>
<p>로컬 LLM을 사용하려고 하는데, ollama 에서 제공하는 것들로 markdown 내용 요약하고 분리하는데 충분할까? 만약 그러하다면 맥북프로 m1 RAM 32gb SSD 512gb 에서 충분히 돌릴만한 모델을 추천해달라</p>
</blockquote>
<ul>
<li>Qwen 2.5 (32B), Gemma 2 (27B), Llama 3.1 (8B)</li>
</ul>
<h3 id="보통-특정-키워드와-관련된-뉴스-기사-youtube-등을-주기적으로-스크랩하여-이를-보고서-형태로-만드는-과정을-자동화하려면-어떻게-하면-좋을까-예를-들어-aicc">보통 특정 키워드와 관련된 뉴스 기사, youtube 등을 주기적으로 스크랩하여 이를 보고서 형태로 만드는 과정을 자동화하려면 어떻게 하면 좋을까? 예를 들어, AICC</h3>
<ul>
<li>대상: 뉴스, Youtube</li>
<li>키워드 선택</li>
<li>부가 작업</li>
<li>프롬프트</li>
<li>배포: github actions를 통해 script 자동 실행</li>
</ul>
<h3 id="obsidian">Obsidian</h3>
<ul>
<li>gemini 도입하여 문서 검색 용이</li>
</ul>
<p>출처
<a href="https://brunch.co.kr/@ecf5a021600147f/25">https://brunch.co.kr/@ecf5a021600147f/25</a></p>
<h3 id="강의-사이트-빠르게-공부">강의 사이트 빠르게 공부</h3>
<p>가장 효율적인 3단계 루틴입니다.</p>
<ol>
<li>Skip &amp; Scan: 전체 자막을 다운로드하여 AI에게 <strong>&quot;목차별 핵심 요약&quot;</strong>을 시킵니다.</li>
<li>Targeting: 모르는 키워드가 있는 챕터만 영상을 켭니다. (이때 OCR로 코드만 탈취)</li>
<li>Archiving: AI가 정리해준 내용을 본인의 노트 앱(Obsidian, Notion 등)에 복사하여 검색 가능한 상태로 만듭니다.</li>
</ol>
<h3 id="claude-code-작업을-모아볼수있는-모니터링-관리-ui--가독성-높이기">claude code 작업을 모아볼수있는 모니터링, 관리 UI? =&gt; 가독성 높이기</h3>
<h3 id="agent-builder">Agent Builder</h3>
<h3 id="인강-목차-공부용-agent">인강 목차 공부용 Agent</h3>
<ul>
<li>맥북프로 m1 ram 32gb에서 돌릴 예정이다. 그리고 용도는 로컬 LLM을 사용하는 agent를 만들고 그것을 가지고 내가 특정 인강 사이트의 목차를 주면 여기서 필요없는 정보 ex)강의시간 을 제거하고 정리한다음, 각 목차별로 중요한 내용들을 찾고 요약하는 것이다. 산출물은 마크다운을 만들어주는 것이다. 이를 통해 내가 굳이 강의를 사지않고도 강의자가 잘 짜놓은 목차로 해당 기술 주제를 공부하기위함이다.</li>
</ul>
<p>하드웨어 적합성</p>
<ul>
<li>메모리 할당: 32B 모델을 4-bit 양자화(q4_k_m)로 구동 시 약 18~20GB의 VRAM(통합 메모리)을 점유합니다. 시스템 운영체제와 브라우저를 띄운 상태에서도 원활하게 동작 가능한 수준입니다.</li>
</ul>
<p>에이전트 구축 전략 (Workflow)</p>
<ul>
<li>Step 1. 데이터 전처리 (Cleaner Agent)</li>
<li>Step 2. 지식 탐색 및 요약 (Researcher Agent)</li>
<li>Step 3. 마크다운 생성 (Writer Agent)</li>
</ul>
<p>Ollama와 LangChain을 조합하여 간단하게 구현</p>
<p>실무 활용 팁</p>
<ul>
<li>목차의 계층화: 섹션과 개별 강의를 구분하여 마크다운의 헤더 레벨(#, ##)을 자동으로 지정하게 하면 Obsidian에서 그래프 뷰로 보기 좋습니다.</li>
<li>기술 스택 맞춤화: 현재 업무 중이신 AICC나 Call Infra와 연관된 키워드가 목차에 있다면, 해당 도메인 지식을 우선적으로 반영하도록 페르소나를 설정(예: &quot;너는 10년차 Call Infra 전문 아키텍트야&quot;)하면 더 깊이 있는 요약이 가능합니다.</li>
</ul>
<h3 id="개발했던-내용들을-노션에-자동-반영-feat-notion-mcp">개발했던 내용들을 노션에 자동 반영 feat: Notion MCP</h3>
<p><a href="https://crowrish.com/blog/claude-code-notion-mcp-guide/">https://crowrish.com/blog/claude-code-notion-mcp-guide/</a></p>
<h3 id="내-노트북에서-작업하고-있는-모든-repository들의-git-history-를-볼-수-잇는-방법">내 노트북에서 작업하고 있는 모든 repository들의 git history 를 볼 수 잇는 방법</h3>
<ol>
<li>커스텀 셸 스크립트 활용</li>
<li>git-standup 도구 사용</li>
<li>VS Code 확장 프로그램: Project Dashboard / GitLens</li>
</ol>
<ul>
<li>코드 에디터 내에서 시각적으로 확인하고 싶을 때 유용합</li>
</ul>
<h3 id="나는-하나의-솔루션을-여러-repository-여러-branch에-나눠-작업했다-그랬다보니-정신없이-개발한-것-같아서-어떠한-과정을-거쳐서-발전해왔는지-기억이-안난다-claude-code에서-repository의-git-commit-message와-git-diff들을-살펴서-어떻게-발전해왔는지-분석하고-요약-정리할-수-있는가">나는 하나의 솔루션을 여러 repository, 여러 branch에 나눠 작업했다. 그랬다보니 정신없이 개발한 것 같아서 어떠한 과정을 거쳐서 발전해왔는지 기억이 안난다. claude code에서 repository의 git commit message와 git diff들을 살펴서 어떻게 발전해왔는지 분석하고 요약 정리할 수 있는가</h3>
<ul>
<li>Claude Code 터미널에서 해당 repository 디렉토리로 이동한 뒤, 자연어로 요청<ul>
<li>&quot;이 레포지토리의 모든 브랜치의 커밋 히스토리를 분석해서, 시간순으로 어떤 기능이 추가되고 어떤 변경이 있었는지 요약해줘&quot;</li>
<li>구체적으로<ul>
<li>&quot;이 레포의 모든 브랜치를 확인하고, 각 브랜치별로 커밋 메시지와 주요 변경사항을 정리해줘. 특히 (1) 각 브랜치의 목적이 뭐였는지, (2) 시간순으로 어떤 순서로 작업이 진행됐는지, (3) 주요 아키텍처 변경이나 기능 추가가 언제 있었는지를 중심으로 요약해줘&quot;</li>
</ul>
</li>
<li>Claude Code는 내부적으로 git log, git diff, git branch -a, git show 같은 명령어를 실행하면서 분석</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그림으로 배우는 서버 구조]]></title>
            <link>https://velog.io/@danielyang-95/%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@danielyang-95/%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sat, 11 Oct 2025 14:31:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>당장 실무에 쓰일 것 같지않지만, 개발자로서 기본 소양으로 조금은 알아야할 것 같아서 정리해보았다. </p>
</blockquote>
<h2 id="서버의-기본">서버의 기본</h2>
<p>서버의 역할</p>
<ul>
<li>하드웨어의 중심적인 역할</li>
<li>애플리케이션 SW 동작시키는 주역</li>
</ul>
<blockquote>
<p>서버와 산하 컴퓨터 사이에는 라우터와 허브 등의 네트워크 기기가 있다.
=&gt; 네트워크 기기들은 질서 있고 효율적으로 목적지까지 잘 찾아가도록 도와주는 필수적인 중간 관리자</p>
</blockquote>
<blockquote>
<p>현대의 거의 모든 유선 LAN은 &#39;허브&#39;가 아닌 &#39;스위치&#39;를 사용!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/c077a078-bf03-4a2a-8341-8a7e8b4b0033/image.png" alt=""></p>
<p>서버의 3가지 이용 형태</p>
<ul>
<li>클라이언트의 요청에 대응하여 처리하는 형태(수동적) ex) 파일, 프린트, 메일, 웹 서버 등</li>
<li>서버에서 능동적으로 처리하는 형태 ex) 운용 감시, RPA, BPMS 서버 등</li>
<li>높은 성능을 활용하는 형태(PC에서 할 수 없는) ex) AI, 빅데이터 서버</li>
</ul>
<p>서버에 접속하는 기기(클라이언트의 다양화)</p>
<ul>
<li>각각 유선 또는 무선 네트워크를 통해 접속한다.</li>
<li>ex) Desktop PC, 노트북 PC, 태블릿, 스마트폰, 카메라, 안테나(IC 태그), 마이크, 드론, 로봇 등</li>
</ul>
<br/>

<h2 id="하드웨어로서의-서버">하드웨어로서의 서버</h2>
<p>PC와의 차이</p>
<ul>
<li>구성 특징: 서버는 신뢰성(24시간. 기본적으로 멈춤X), 확장성(운용 정지않고 교환/증설 쉽게), 가용성, 내결함성 더 필요</li>
<li>성능: 서버는 표시 성능뿐만 아니라 처리(I/O) 성능도 중요. PC는 표시(키보드/마우스) 성능 중시</li>
</ul>
<p>서버 OS</p>
<ul>
<li>현재 서버용 OS는 윈도우, Linux, Unix 계열 주류</li>
</ul>
<p>서버 사양</p>
<ul>
<li>지금은 &#39;사용자 수&#39;, &#39;용도&#39; 등으로도 검토 가능</li>
<li>항목: 형태-크기, CPU 수-종류, 메모리 용량, 내장디스크 용량, 전원 유닛, 다중화 냉각팬 등</li>
</ul>
<p>다양한 서버의 형태</p>
<ol>
<li>폼팩터 &#39;어떻게 설치하고 관리할까?&#39;: 타워, 랙 마운트, 블레이드, 고밀도</li>
<li>특정 임무를 위한 아키텍처 &#39;어떤 문제를 해결할까?&#39;: 메인프레임, 슈퍼텀퓨터</li>
</ol>
<p>서버의 표준</p>
<ul>
<li>PC 서버(x86)가 표준이었으나... ARM이 치고 올라오고 있다.(Cloud 기업들. 저전력)</li>
<li>CPU 아키텍처<ul>
<li>x86 (CISC 방식) </li>
<li>ARM (RISC 방식)</li>
</ul>
</li>
</ul>
<p>네트워크의 기본은 LAN</p>
<ul>
<li>TCP/IP로 불리는 네트워크의 공통 언어(프로토콜)로 통신</li>
<li>이외 네트워크: Bluetooth통신, WAN(멀리 떨어진 LAN과 LAN을 서로 연결해주는 거대한 네트워크) =&gt; 모두 끊임없이 고속 처리 통신에는 적합x</li>
</ul>
<p>서버 설치 장소</p>
<ul>
<li>온프레미스: 자유 설정 but 직접 유지 보수, 비용이 낮지않을 수도</li>
<li>데이터 센터: 유지보수 편리 but 데이터가 외부 네트워크로 나간다.</li>
</ul>
<p>클라우드 서비스</p>
<ul>
<li>종류<ul>
<li>IaaS ex) EC2, S3, VPC 등</li>
<li>PaaS ex) ElasticBeanstalk, RDS, EKS 등</li>
<li>SaaS: &#39;완성된 소프트웨어&#39;를 구독</li>
</ul>
</li>
<li>주의: <strong>데이터를 어떻게 다루는가?</strong></li>
</ul>
<p>서버 전용 소프트웨어와 미들웨어</p>
<ul>
<li>서버 전용 SW: 클라이언트 상호작용 없이 서버에서만 사용하는 SW</li>
<li>미들웨어: 서버 전용 SW가 운영체제나 네트워크 위에서 안정적으로 실행되도록 도와주는 중간 계층
  =&gt; 기능보다는 “다른 시스템을 연결하고 지원하는 역할”에 초점</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>정의</th>
<th>비유</th>
</tr>
</thead>
<tbody><tr>
<td><strong>서버 전용 소프트웨어</strong></td>
<td>특정 서비스를 제공하기 위해 <strong>서버에서 실행되는 애플리케이션</strong></td>
<td>Apache, Nginx, Tomcat, Spring Boot, Node.js, MySQL Server 등</td>
</tr>
<tr>
<td><strong>미들웨어</strong></td>
<td>애플리케이션과 OS/네트워크 사이에서 <strong>데이터·요청을 중개하거나 통신을 도와주는 소프트웨어 계층</strong></td>
<td>WAS (WebLogic, JBoss), MQ, Kafka, Redis, API Gateway 등</td>
</tr>
</tbody></table>
<p>ex)
    - Spring Boot → Tomcat/WAS 위에서 실행
    - 웹 서버(Apache) ↔ WAS(Tomcat) : Tomcat이 미들웨어 역할
    - Spring Boot (서버 애플리케이션) ↔ Kafka (메시징 미들웨어)
    - ERP 서버 ↔ Oracle DB (DB 미들웨어)</p>
<br/>

<h2 id="서버로-무엇을-하는가">서버로 무엇을 하는가?</h2>
<p>서버는 산하의 컴퓨터를 어떻게 보고 있는가?</p>
<ul>
<li>IP 주소로 서로 호출<ul>
<li>IP 주소: 컴퓨터 SW가 인식하는 컴퓨터 주소</li>
<li>MAC 주소: HW가 인식하는 주소. 네트워크 내에서 기기 특정</li>
</ul>
</li>
</ul>
<p>컴퓨터 간 데이터 통신
<img src="https://velog.velcdn.com/images/danielyang-95/post/aab8efb4-0b7b-401a-9fd0-c6a259c1c8a9/image.png" alt=""></p>
<p>라우터의 역할</p>
<ul>
<li>전송되는 데이터를 자신이 목적지로 보낼 것인지, 아니면 다음 라우터로 중계할 것인지 항상 생각
=&gt; 서로 다른 네트워크들을 연결하고, 데이터가 가야 할 최적의 경로를 찾아 보내주는 것</li>
</ul>
<p>서버 가상화와 데스크톱 가상화</p>
<ul>
<li>서버 가상화: 서버 자원 효율성. IT관리자를 위해 ex) VMware vSphere, KVM, Hyper-V, Xen</li>
<li>데스크톱 가상화(VDI): 중앙에서 모든 데스크톱 환경을 관리 가능(보안, 원격 등). 사용자를 위해</li>
</ul>
<p>Telework(원격근무)와 Thin Client(도구)</p>
<ul>
<li>Thin client: 최소한의 기능만 갖춘 간단한 컴퓨터. 중요한 것은 중앙 서버에서만 수행하니 클라이언트 PC 보안문제 생겨도 피해 적다.</li>
</ul>
<p>네트워크 가상화</p>
<ul>
<li>하나의 물리적인 네트워크 장비(스위치, 라우터 등)를 여러 개의 독립된 가상 네트워크로 나누거나, 여러 개의 장비를 하나처럼 보이게 합치는 기술
ex) Fabric Network(=Ethernet Fabric)</li>
</ul>
<p>어플라이언스 서버: 특정 작업만 빠르고 안정적으로 처리하도록 HW와 SW가 처음부터 하나로 합쳐져 최적화된 서버</p>
<p>서버의 디스크: PC와 비교하면 성능과 신뢰성이 높은 제품 요구</p>
<ul>
<li>지표: Latency, Throughput, TPS<blockquote>
<p>현재 고성능 서버는 NVMe 인터페이스(연결 방식)를 사용하는 SSD(저장 매체) 여러 개를 RAID(운영 방식)로 묶어, 낮은 Latency와 높은 Throughput/IOPS를 확보하는 것이 일반적이다.</p>
</blockquote>
</li>
<li>SAS vs NVMe, HDD vs SSD</li>
</ul>
<br/>

<h2 id="클라이언트에-대응하는-역할">클라이언트에 대응하는 역할</h2>
<ul>
<li>사용자의 시선</li>
<li>파일 서버(접근권한설정), 프린트 서버(무선LAN을 활용한 프린터의 네트워크화), NTP 서버(시각 동기화)</li>
<li>IP 주소 할당: 서버 OS에 있는 DHCP 서비스로 클라이언트의 IP 주소를 동적 관리</li>
<li>SIP 서버(IP 전화 제어), SSO 서버(인증 지원), 업무 시스템 서버, ERP, IoT 서버<ul>
<li>IP 전화: 인터넷 프로토콜을 사용한 전화 by VoIP</li>
<li>SSO 방법: reverse proxy vs agent<ul>
<li>큰 차이: 인증을 처리하는 위치</li>
<li>reverse proxy: 모든 애플리케이션 앞단에서 요청을 가로챔. 기존 앱 수정 불필요 but 단일장애지점</li>
<li>agent: 해당 앱 서버 내에 설치된 에이전트가 요청을 받아 인증 여부를 확인. 세밀한 접근 제어 but 유지보수 어려움</li>
</ul>
</li>
<li>ERP: 생산, 경리, 물류 드으이 다양한 업무 통합 시스템. 어떤 업무에서 데이터 변경 -&gt; 연계된 다른 업무 데이터도 갱신</li>
<li>IoT 서버: 수많은 device들을 인터넷에 연결 
  ① 상태를 감시하고 데이터를 수집/처리/저장
② 원격으로 제어하는 핵심 시스템<ul>
<li>일반 시스템 검토와는 달리 불안정한 네트워크 환경(디바이스 위치), 다양한 통신 방식과 데이터, 대규모 트래픽 처리 등 다른 관점이 요구된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<h2 id="메일과-인터넷">메일과 인터넷</h2>
<ul>
<li>메일과 인터넷을 지원하는 서버: 메일, 인터넷 서버는 보안상의 이유로 사무 공간 등에서 보여선 안된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/9a755025-4419-456b-81e8-3fed1dfb1751/image.png" alt=""></p>
<p>SSL 흐름</p>
<ul>
<li>클라이언트 PC가 서버에 접속</li>
<li>서버는 인증서와 공개키 보냄</li>
<li>클라이언트 PC는 인증서 확인 + 통신에 사용할 비밀번호(세션 키) 생성 -&gt; 공개키로 암호화해서 전송</li>
<li>서버는 개인키로 비밀번호 확인 
=&gt; 이제 이걸로 통신</li>
</ul>
<p>Proxy 서버(=대리)</p>
<ul>
<li>문제가 될 수 있는 사이트 차단</li>
<li>외부에서의 부적절한 접근에 대해 클라이언트 보호</li>
</ul>
<br/>

<h2 id="서버로부터의-처리와-고성능-처리">서버로부터의 처리와 고성능 처리</h2>
<ul>
<li>시스템 운용 감시(리소스, health) =&gt; 서버의 안정적 기동</li>
<li>IoT로부터 서버가 데이터 수집</li>
<li>RPA: 업무 자동화 실현 도구</li>
<li>BPMS: 업무 프로세스를 분석 및 개선하면서 관리</li>
<li>AI 서버, 빅데이터 서버(비구조화 데이터까지. 대량의 데이터 분석)<ul>
<li>Hadoop: 빅데이터의 실용화 뒷받침 기술. 오픈소스 미들웨어로, 대량의 방대한 데이터 고속 처리</li>
</ul>
</li>
</ul>
<br/>

<h2 id="보안과-장애-대책">보안과 장애 대책</h2>
<p>시스템에 무엇을 지키고 싶은가?</p>
<ul>
<li>정보 자산 ex) HW, SW, 데이터, 인적 자산, 서비스 등</li>
<li>데이터 분류: 공개 정보/비밀 정보</li>
<li>위협: 기술적 위협, 인적 위협</li>
</ul>
<p>보안 위협과 대책 사례</p>
<table>
<thead>
<tr>
<th>대상</th>
<th>기술적/인적</th>
<th>보안 위협</th>
<th>대책 사례</th>
</tr>
</thead>
<tbody><tr>
<td><strong>시스템이나 서버</strong></td>
<td>기술적 위협</td>
<td>외부에서의 부정 액세스</td>
<td>- 방화벽 <br/> - DMZ <br/> - 디바이스 간 통신 암호화</td>
</tr>
<tr>
<td><strong>사용자</strong></td>
<td>인적 위협</td>
<td>내부에서의 부정 액세스</td>
<td>- 사용자 관리 <br/> - 액세스 로그 확인 <br/> - 디바이스 조작 감시</td>
</tr>
<tr>
<td><strong>데이터</strong></td>
<td>기술적 위협</td>
<td>데이터 유출</td>
<td>이동식 매체 안의 데이터 암호화</td>
</tr>
</tbody></table>
<br/>

<ol start="0">
<li><p>정보 보안 정책: 기본 방침 &gt; 대책 기준 &gt; 실시 순서</p>
</li>
<li><p>완충 지대(DMZ): 방화벽이 있다고 내부 네트워크 안전하다 보장 x</p>
<ul>
<li>내부 네트워크로의 침입을 막기 위해 방화벽과 내부 네트워크 상이에 설치하는 완충 지대</li>
</ul>
</li>
<li><p>서버 내 보안: Access 제어 매커니즘 ex) 사용자 관리 서비스 </p>
</li>
<li><p>바이러스 대책</p>
</li>
<li><p>서버 장애 대책: Fault Tolerance System(장애허용시스템) </p>
<ul>
<li>기술적인 관점: 다중화(ex - 클러스터링), 부하 분산(ex - 로드밸런싱)</li>
</ul>
</li>
<li><p>네트워크 장애 대책: 티밍(출입구가 되는 네트워크 카드에 장애가 발생해서 통신 불가 상황 방지하는 기술)</p>
</li>
<li><p>디스크 장애 대책: RAID </p>
</li>
<li><p>데이터 백업: Full 백업, 차등 백업</p>
<ul>
<li>물리적인 구현 형태: 메인-&gt;대기 시스템으로의 백업, 서버 안 예비 디스크 준비 백업, DVD나 테이프 저장 장치 등의 외부 매체 활용</li>
</ul>
</li>
<li><p>전원 백업</p>
<ul>
<li>UPS: 갑작스런 정전이나 전압의 급격한 변화로부터 서버나 네트워크 기기 등을 보호하는 장비<ol>
<li>정전 시 전원 공급 기능</li>
<li>안전하게 셧다운 하는 기능</li>
</ol>
</li>
</ul>
</li>
</ol>
<br/>

<h2 id="서버의-도입">서버의 도입</h2>
<ol>
<li><p>서버 도입 시 검토 과정: 어떤 시스템? &gt; 서버가 어떤 처리? &gt; 클라우드 &gt; 온프레미스인가, 임대인가?
=&gt; 이제는 새로운 시스템은 거의 클라우드부터 검토하는 게 기본
=&gt; 클라우드나 어플라이언스 서버의 등장으로 서버 관련 작업 공수 감소 
=&gt; 비즈니스에 집중!</p>
</li>
<li><p>서버의 성능 견적</p>
<ul>
<li>3가지 관점: 탁상 계산에 의한 견적, 동종 사례와 메이커 추천 참고, 툴을 이용한 성능/부하 측정</li>
<li>CPU 탁상 계산: 이전에는 클록 주파수였으나, 이제는 CPU 코어와 스레드 수 중심</li>
</ul>
</li>
<li><p>서버 설치 장소(온프레미스 시)</p>
</li>
<li><p>IT 전략과의 정합성 확인</p>
</li>
<li><p>서버는 누가 관리하는가?</p>
<ul>
<li>용무: 사용자 관리, 자산 관리, 운용 관리 등</li>
</ul>
</li>
<li><p>서버 사용자는 누구인가?</p>
<ul>
<li>시스템이나 서버의 사용자는 워크그룹으로 관리(조직 단위나 역할 등으로)</li>
<li>사용자의 권한</li>
</ul>
</li>
</ol>
<br/>

<h2 id="서버의-운용-관리">서버의 운용 관리</h2>
<p>가동 후 관리</p>
<ul>
<li>운용 관리 by 시스템 운용 담당자 : 운용 감시, 성능 관리, 변경/장애 대응 - 정형적/메뉴얼화된 운용 등</li>
<li>시스템 보수 by시스템 엔지니어 : 성능 관리, 레벨업, 기능 추가, 버그/장애 대응 - 비정형/메뉴얼화 되지않은 운용 등</li>
</ul>
<p>장애의 영향: 영향 범위와 영향도(크기)로 검토</p>
<p>서버의 성능 관리: CPU, 메모리, 디스크 등의 순으로 사용률 확인, 대처</p>
<p>SW 업데이트: 성능 향상(시스템 기능 추가, 버전 업), 정상 운용(버그 수정, 필수 SW 업데이트)</p>
<p>장애 대응</p>
<ul>
<li>장애: 시스템이 정지하거나 서버가 보이지 않는 등 정상으로 기능하지 않는 것</li>
<li>기본적인 절차: CPU, 메모리, 디스크 순으로 살펴본다.<ul>
<li>전용 관리 툴 혹은 Command 사용(ping, ipconfig, tracert, arp)</li>
</ul>
</li>
</ul>
<p>서비스 수준 체계(SLA): 시스템 이용자를 고객으로 인식하고 높은 품질의 서비스를 제공해야한다는 사고방식</p>
<ul>
<li>주요지표: 가용성(시스템 가동시간), 복구 시간<ul>
<li>가용성: 시스템을 멈춰선 안 된다는 원칙 하</li>
<li>복구시간: MTTR(평균 복구 시간) - 시스템에 고장이 발생하고나서 일정 시간 이내로 복구(평균 시간)</li>
</ul>
</li>
</ul>
<br/>

<h2 id="사례와-미래">사례와 미래</h2>
<ul>
<li>미래 키워드: 소형화, 집적화, 가상화, 다양화, 클라우드</li>
</ul>
<br/>



<h2 id="얻어가야하는-것">얻어가야하는 것</h2>
<blockquote>
<p>과정: 어떤 시스템을 만들고 싶다(구체화) --- 시스템 규모 ---&gt; 서버 검토</p>
</blockquote>
<ul>
<li><p>시스템 모델화 및 구성 =&gt; <strong>관계자 간의 공통된 인식</strong></p>
<ul>
<li>SW, HW 요구사항 고려</li>
<li>무슨 시스템/접속 기기 종류/네트워크/기능/이용형태(3가지 중)</li>
</ul>
</li>
<li><p>서버 검토</p>
<ul>
<li>성능 견적 후, 사이징</li>
<li>서버가 정말 필요한가? (투자 대비 효과)</li>
</ul>
</li>
<li><p>CPU 아키텍처</p>
<ul>
<li>x86 (CISC 방식) </li>
<li>ARM (RISC 방식)</li>
</ul>
</li>
<li><p>서버 전용 소프트웨어와 미들웨어</p>
</li>
<li><p>Proxy</p>
</li>
<li><p>SSL </p>
</li>
<li><p>컴퓨터 간 데이터 통신, 데이터 캡슐화</p>
</li>
<li><p>완충 지대(DMZ)</p>
</li>
<li><p>미래 키워드: 소형화, 집적화, 가상화, 다양화, 클라우드</p>
</li>
</ul>
<br/>]]></description>
        </item>
        <item>
            <title><![CDATA[IntelliJ IDEA Conf 2025]]></title>
            <link>https://velog.io/@danielyang-95/IntelliJ-IDEA-Conf-2025</link>
            <guid>https://velog.io/@danielyang-95/IntelliJ-IDEA-Conf-2025</guid>
            <pubDate>Fri, 19 Sep 2025 23:54:16 GMT</pubDate>
            <description><![CDATA[<h1 id="java-30주년">Java 30주년</h1>
<ul>
<li>Duke</li>
<li>Java: 한번 작성하면 어디서나 구동</li>
</ul>
<br/>

<h4 id="지난-5년간의-자바-현황">지난 5년간의 자바 현황</h4>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/43c2222a-9f8d-4c0d-acae-6be698366cef/image.png" alt="지난 5년간의 자바 현황"></p>
<blockquote>
<p>JVM: 더 나은, 더 빠른, 더 견고하고 안전하게 </p>
</blockquote>
<ul>
<li>ZGC, Virtual Threads, Generational ZGC, Region Pinning for G1, AOT, Sync Virtual Threads w/o Pinning, Structured Concurrency, Value Classes and Objects, Null-restricted Value class Types, Enhanced Primitive Boxing</li>
</ul>
<blockquote>
<p>데이터 지향 프로그래밍 모델 도입에도 투자</p>
</blockquote>
<ul>
<li>Records, Pattern Matching, Sealed Classes, Text Blocks, Simple Web Server, The Java Playground, Unnamed Vars &amp; Patterns, Multi-file Source Launcher, Simple Source Files and Instance Main Methods, (Primitive Types in Patterns, instanceof, and switch), Flexible Constructor Bodies, Derived Record Creation</li>
</ul>
<blockquote>
<p>대형 APP말고도 Script로 빠르게 만들어보는 프로토타입에도 적합하도록
Open JDK의 Project Amber: 프로그래밍언어 단순화</p>
</blockquote>
<blockquote>
<p>보안 문제</p>
</blockquote>
<ul>
<li>Key Encapsulation API, FFM API, Quantum-resistant Algos, Key Derivation Function API</li>
</ul>
<blockquote>
<p>AI 준비(Inference &amp; Training)</p>
</blockquote>
<ul>
<li>Records, Pattern Matching, Sealed Classes, FFM API, Value Classes and Objects, Null-restricted Value Class Types, Enhances Primitive Boxing, Derived Record Creation</li>
</ul>
<br/>

<h4 id="release를-6개월로-한-이유">Release를 6개월로 한 이유</h4>
<ul>
<li>매 release마다 수백 개의 기능을 떠안지 않아도됨. 점진적으로 개선된 byte단위로 제공</li>
<li>미리보기 기능을 포함해 사용자들이 직접 사용해보고 Java 팀에 피드백을 줄 수 있다는 점 =&gt; 약간 code review 같은 느낌?</li>
<li>스프링은 이제 최소 java 17, 가능하면 21 시도해보라</li>
</ul>
<p>Java 커뮤니티</p>
<ul>
<li>OpenJDK</li>
</ul>
<p>빅테크 java사용</p>
<ul>
<li>Uber, Netflix(java로 돌아감), Linkedin</li>
</ul>
<p>Java 자격증?</p>
<p>dev.java : 튜토리얼
inside.java : 모든 게시물
learn.java</p>
<br/>


<h1 id="프로덕션급-ai-애플리케이션을-위한-langchain4j의-차세대-기능">프로덕션급 AI 애플리케이션을 위한 Langchain4j의 차세대 기능</h1>
<p>Langchain4j</p>
<ul>
<li>출시 2주년. 1.0 안정화 버전</li>
<li>주로 API를 통해 대형 언어 모델에 연결하는 도구<ul>
<li>데이터나 ML주기는 신경x</li>
<li>로컬 옵션: Ollama, LocalAI, Llama 등</li>
</ul>
</li>
<li>빠르게 배우기 : Langchain4j github repo에 가서 examples 받아보기</li>
</ul>
<p>좋은 점
<img src="https://velog.velcdn.com/images/danielyang-95/post/d5913d56-8dcc-47ad-8689-3f115a34c92b/image.png" alt=""></p>
<ul>
<li>내부적으로는 파싱이 많이 일어나지만 최종적으로는 실제로 다룰 수 있는 Java 객체를 받는다.</li>
</ul>
<h4 id="더-많은-문맥context을-얻기위한-효율적인-방법들">더 많은 문맥(Context)을 얻기위한 효율적인 방법들</h4>
<ul>
<li>Multimodal 아키텍처</li>
<li>MCP</li>
</ul>
<h3 id="llm-state-machine">LLM State Machine</h3>
<ul>
<li>LLM을 사용해 다음 상태를 &#39;추론&#39;하게 만드는 방식</li>
<li>LLM이 대화나 작업의 현재 &#39;상태&#39;를 기억하고 그 상태에 따라 다음에 할 행동을 결정하도록 만드는 프로그래밍 모델</li>
<li>크게 <strong>상태(State)</strong>와 <strong>전이(Transition)</strong>라는 두 가지 핵심 요소로 구성</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/eb88171e-e245-4f21-b56c-ebbdefe2ac51/image.png" alt=""></p>
<blockquote>
<p>하지만 보안이 강하지 않고, 의도치않은 일이 가끔 발생한다. 그리고 높은 Latency, 디버깅의 어려움, API 호출 비용
=&gt; 이에 대한 대안으로 Code State Machine</p>
</blockquote>
<h3 id="code-state-machine">Code State Machine</h3>
<ul>
<li>코드가 상태 기계를 관리하게하고 LLM을 오직 특정단계들에서만 실행되게
  = 흐름 제어(Flow Control)는 코드로 하고, 언어 처리(Language Processing)가 필요할 때만 LLM을 도구로 사용하자</li>
</ul>
<blockquote>
<p>LLM State Machine은 간단한 프로토타입이나 유연성이 극도로 중요한 일부 시나리오에서는 유용. 하지만 안정성, 신뢰성, 성능이 중요한 상용 애플리케이션을 구축할 때는 Code State Machine이 훨씬 더 적합한 아키텍처. 이는 LLM의 강력한 언어 능력과 코드의 견고함 및 예측 가능성이라는 두 세계의 장점을 모두 취하는 현명한 접근 방식.</p>
</blockquote>
<p>Basic RAG -&gt; Advanced RAG</p>
<p>Model Quality</p>
<ul>
<li>swebench.com : SW tasks</li>
<li>livebench.ai : General tasks</li>
</ul>
<p>Model Pricing</p>
<ul>
<li>llm-price.com</li>
</ul>
<h3 id="langchain4j-10">Langchain4j 1.0</h3>
<ul>
<li>upcoming<ul>
<li>Few-Shot support in AI services(AI서비스 예제)</li>
<li>Prompt caching</li>
<li>Audio Support for more models</li>
<li>Extended MCP support(MCP 확장)</li>
<li>AiServices as Tool</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/6607873c-d735-424c-872f-f87e3aa2a86a/image.png" alt=""></p>
<h3 id="quarkus">Quarkus</h3>
<ul>
<li>quarkus vs springboot<ul>
<li>Quarkus는 빠른 시작 시간과 낮은 메모리 소비에 최적화된 최신 쿠버네티스 네이티브(Kubernetes-native) 프레임워크로, 마이크로서비스 및 서버리스 환경에 이상적입니다. 반면, Spring Boot는 방대한 생태계를 갖춘 성숙하고 범용적인 프레임워크로, 특히 대규모 엔터프라이즈 시스템과 같은 광범위한 애플리케이션에서 뛰어난 성능</li>
<li>가장 근본적인 차이는 컴파일 및 런타임 전략에 있습니다. Quarkus는 <strong>GraalVM을 사용한 AOT(Ahead-Of-Time) 컴파일</strong>을 위해 설계. 반면, Spring Boot는 전통적으로 <strong>JVM 내의 JIT(Just-In-Time) 컴파일에 의존</strong>합니다. JVM의 JIT 컴파일러는 시간이 지남에 따라 코드를 최적화하여 최고 성능을 낼 수 있지만, 느린 시작 시간과 높은 메모리 사용량의 원인</li>
</ul>
</li>
</ul>
<h3 id="spring-ai-vs-langchain4j">Spring AI vs Langchain4j</h3>
<p>Spring AI는 Spring 프레임워크에 AI 기능을 &#39;Spring다운 방식&#39;으로 자연스럽게 녹여내는 것을 목표. 반면, Langchain4j는 파이썬 LangChain의 성공에 영감을 받아, 어떤 자바 프로젝트에서든 LLM(거대 언어 모델) 기반 애플리케이션을 쉽게 구축할 수 있도록 돕는 독립적인 AI 오케스트레이션 라이브러리.</p>
<p>쉽게 비유하자면, Spring AI는 &quot;Spring 애플리케이션을 위한 공식 AI 확장팩&quot;과 같고, Langchain4j는 &quot;어떤 자바 프로젝트에도 장착할 수 있는 다목적 AI 엔진&quot;과 같다.</p>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/b1ad94ae-e63c-4a1b-a125-fb8ebad61f4b/image.png" alt=""></p>
<br/>

<h1 id="bootiful-intellij-idea">Bootiful Intellij IDEA</h1>
<ul>
<li><p>프로젝트 생성할 때, Devtools와 GraalVM Native Support 사용</p>
</li>
<li><p>스프링부트 3.5 추천</p>
<ul>
<li>올해 말 spring7, springboot4 11월 출시. </li>
</ul>
</li>
<li><p>편리한 기능
<img src="https://velog.velcdn.com/images/danielyang-95/post/afae7a9e-5538-4b0c-a568-d9d3cbff3d44/image.png" alt=""></p>
</li>
<li><p>리버스 엔지니어링 기능</p>
<ul>
<li>오른쪽 plugin에서 DB 연결된 스키마를 기반으로 Entity 만들기</li>
</ul>
</li>
<li><p>Spring Debugger </p>
<ul>
<li>logical 보기 
<img src="https://velog.velcdn.com/images/danielyang-95/post/32f22ba0-5a31-4b59-a15b-a095c0ea0b3d/image.png" alt=""></li>
</ul>
</li>
<li><p>Spring Modulith</p>
</li>
<li><p>Spring Data AOT</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 6장]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-6%EC%9E%A5</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-6%EC%9E%A5</guid>
            <pubDate>Thu, 29 May 2025 05:10:16 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-18-함수형-관점으로-생각하기"><strong>Chapter 18 함수형 관점으로</strong> 생각하기</h1>
<blockquote>
<p>함수형 프로그래밍은 <strong>&quot;데이터의 변환&quot;</strong> 에 집중하여 더 안정적이고 유지보수하기 쉬운 코드를 만든다.</p>
</blockquote>
<h2 id="1-시스템-구현과-유지보수">1. 시스템 구현과 유지보수</h2>
<ul>
<li>예측 가능한 동작과 높은 유지보수성을 제공</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/d2ff7658-ed0f-41b3-b5c7-29f9d1af4577/image.png" alt=""></p>
<ul>
<li><strong>명령형 vs 선언형</strong>:<ul>
<li><strong>명령형</strong>: <em>&quot;어떻게&quot;</em> 처리할지 명시 (루프, 조건문)</li>
<li><strong>선언형</strong>: <em>&quot;무엇을&quot;</em> 원하는지 표현 (스트림 API) → 가독성 ↑, 병렬화 쉬움</li>
</ul>
</li>
</ul>
<br/>

<h2 id="2-함수형-프로그래밍이란-무엇인가">2. 함수형 프로그래밍이란 무엇인가?</h2>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/97e0d50c-5b8f-4ec1-be87-5522edae7f46/image.png" alt=""></p>
<br/>

<h2 id="3-재귀와-반복">3. 재귀와 반복</h2>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/0f8c553a-becf-4c8e-aa46-af6fcd7f6fa9/image.png" alt=""></p>
<p>실무에서 재귀가 유용한 경우</p>
<ul>
<li>트리 구조 데이터 처리, 백트래킹 알고리즘, 분할 정복 알고리즘</li>
</ul>
<h4 id="꼬리-재귀-최적화"><strong>꼬리 재귀 최적화</strong></h4>
<ul>
<li>누적값(<code>acc</code>)을 인자로 전달 → 스택 프레임 재사용 가능<pre><code class="language-java">// 일반 재귀 (스택 오버플로우 위험)
static long factorial(long n) {
  return n == 1 ? 1 : n * factorial(n - 1);
}
</code></pre>
</li>
</ul>
<p>// 꼬리 재귀 (Java는 최적화 미지원, but 패턴 적용 가능)
static long factorialTail(long n, long acc) {
    return n == 1 ? acc : factorialTail(n - 1, acc * n);
}</p>
<pre><code>

### 함수형 실전 예제: 부분집합 생성
- **문제**: 리스트의 모든 부분집합 생성
- **해결**: 재귀 + 불변 데이터 구조 활용
    - subsets 메서드는 주어진 리스트의 모든 부분집합을 재귀적으로 구한다.
    - insertAll은 각 부분집합 앞에 현재 원소를 추가한 새로운 부분집합 리스트를 만든다.
    - concat은 두 리스트를 합쳐 새로운 리스트로 반환



&lt;br/&gt;

## + 추가
- **객체지향**은 상태를 가진 객체와 그 객체의 메서드를 통해 문제를 해결.
- **함수형**은 상태를 바꾸지 않고, 순수 함수와 불변 데이터로 문제를 해결.  
    - Java는 두 방식을 모두 지원하며, 스트림 API나 람다를 통해 함수형 스타일을 점점 더 많이 쓸 수 있다.

&lt;br/&gt;

# CHAPTER 19 함수형 프로그래밍 기법

&gt; 자바 8의 함수형 프로그래밍은 코드의 안정성과 재사용성을 높이는 다양한 기법을 제공
&gt; ** 불변성**과 **함수 조합**을 통해 다음과 같은 이점
&gt; - 동시성 문제 최소화
&gt; - 코드 가독성 향상
&gt; - 버그 발생 가능성 감소
&gt; - 테스트 용이성 증가

&lt;br/&gt;

## 1. 함수는 모든 곳에 존재한다
### 일급 함수

- 함수를 변수에 할당하거나 인수/반환값으로 사용 가능
    ```java
    Function&lt;String, Integer&gt; strToInt = Integer::parseInt;  // 메서드 참조 활용
    ```
- **Comparator.comparing**처럼 함수를 조립하는 연산 파이프라인 구성 가능

### 고차원 함수
- 함수를 인수로 받거나 반환하는 함수
- 람다식이나 메서드 참조를 활용하여 구현
    ```java
    Function&lt;A, C&gt; compose(Function&lt;B, C&gt; g, Function&lt;A, B&gt; f) {
        return x -&gt; g.apply(f.apply(x));  // 함수 조합
     }
    ```

&lt;br/&gt;

## 2. 영속 자료구조

### 불변성 원칙
- 기존 자료구조를 변경하지 않고 새로운 객체 생성 =&gt; 여러 곳에서 같은 자료구조를 참조해도 안전
    ```java
    List&lt;Integer&gt; newList = new ArrayList&lt;&gt;(originalList);  // 방어적 복사
    ```
- 트리 구조에서 노드 추가 시 기존 노드 재활용(효율적 메모리 관리)

&lt;br/&gt;

## 3. 스트림과 게으른 평가

### 게으른 연산
- 스트림 연산은 최종 결과 필요 시점에 실행
     ```java
     IntStream.range(1, 10).filter(n -&gt; n%2 == 0);  // 중간 연산만 정의
    ```
- **Supplier&lt; T&gt;** 를 이용한 무한 수열 생성
    ```java
    Supplier&lt;Double&gt; rand = Math::random;  // 호출 시마다 값 생성
    ```
    - Supplier&lt; T &gt;를 활용해 게으른 리스트를 직접 구현도 가능

&lt;br/&gt;

## 4. 패턴 매칭

### 다중 분기 처리
- 자바에서 람다를 활용한 패턴 매칭 흉내
- 자바는 패턴 매칭을 직접 지원하지 않지만, 람다와 함수형 인터페이스를 이용해 비슷한 효과를 낼 수 있다
```java
    static String patternMatch(Object obj) {
        if (obj instanceof String s) return &quot;String: &quot; + s;
        if (obj instanceof Integer i) return &quot;Integer: &quot; + i;
        return &quot;Unknown&quot;;  // 자바 16 패턴 매칭
    }</code></pre><br/>

<h2 id="5-기타-정보">5. 기타 정보</h2>
<h3 id="커링">커링</h3>
<ul>
<li>여러 개의 인수를 받는 함수를, 일부 인수만 고정해서 새로운 함수를 만드는 기법</li>
<li>다중 인수 함수를 단일 인수 함수 체인으로 변환<pre><code class="language-java">  // 변환 요소와 기준치만 고정해두고 변환할 값만 입력받는 함수
  DoubleUnaryOperator convertCtoF = curriedConverter(9.0/5, 32);  // 섭씨→화씨 변환</code></pre>
</li>
</ul>
<h3 id="메모이제이션캐싱">메모이제이션(캐싱)</h3>
<ul>
<li>참조 투명성 활용한 결과 캐싱</li>
<li>함수 결과를 저장해두고, 같은 인수가 들어오면 다시 계산하지 않고 저장된 값을 반환하는 기법<pre><code class="language-java">  Map&lt;String, Function&lt;Double, Double&gt;&gt; cache = new HashMap&lt;&gt;();  // 계산 결과 저장</code></pre>
</li>
</ul>
<h3 id="콤비네이터함수-조합">콤비네이터(함수 조합)</h3>
<ul>
<li>여러 함수를 조합해서 새로운 함수를 만드는 고차 함수 패턴</li>
</ul>
<br/>

<h1 id="chapter-20-oop와-fp의-조화--자바와-스칼라-비교">CHAPTER 20 OOP와 FP의 조화 : 자바와 스칼라 비교</h1>
<blockquote>
<p>프로그래밍 언어는 점점 더 &quot;함수형&quot;으로 진화하고 있으며, Java도 8부터 도입된 람다, 스트림, Optional 등으로 함수형 프로그래밍(FP)의 장점을 받아들이기 위한 변화를 꾀했다.
하지만 함수형 프로그래밍이 언어에 깊이 뿌리내리려면 문법 자체가 간결하고 불변성, 일급 함수, 고차 함수 등의 개념이 자연스럽게 녹아 있어야한다. 
FP를 자연스럽게 지원하는 언어, <strong>스칼라(Scala)</strong> 를 통해 자바와 비교하며 OOP와 FP의 조화를 살펴본다.</p>
</blockquote>
<h2 id="1-스칼라-소개">1. 스칼라 소개</h2>
<ul>
<li>스칼라는 객체지향(OOP)과 함수형(FP)을 모두 지원하는 JVM 기반 언어 </li>
<li>자바처럼 클래스 기반 구조를 가지고 있지만, <strong>함수를 일급 시민으로 대우</strong>하며 <strong>불변성과 표현식 중심 문법</strong>을 강하게 지향한다.<ul>
<li>일급 시민으로 대우 : <strong>함수를 변수, 데이터 구조, 연산의 대상으로 자유롭게 다룰 수 있음</strong>을 의미</li>
</ul>
</li>
</ul>
<p>간단한 예:</p>
<pre><code class="language-java">// 1. 함수를 변수에 할당
val greet: String =&gt; String = (name: String) =&gt; s&quot;Hello, $name!&quot;

// 2. 함수를 인자로 전달
def printFormatted(formatFunc: String =&gt; String, name: String): Unit = {
  println(formatFunc(name))
}

// 3. 함수를 반환값으로 사용
def createGreeter(prefix: String): String =&gt; String = {
  (name: String) =&gt; s&quot;$prefix $name!&quot;
}</code></pre>
<br/>

<h2 id="2-함수형-스타일을-쉽게-만드는-스칼라의-기능들">2. 함수형 스타일을 쉽게 만드는 스칼라의 기능들</h2>
<h3 id="문자열-보간-string-interpolation">문자열 보간 (String Interpolation)</h3>
<ul>
<li><strong>스칼라</strong>: <code>s&quot;Hello ${n} bottles&quot;</code><ul>
<li>스칼라는 문자열 안에 변수를 직접 넣을 수 있어 코드가 깔끔하다.</li>
</ul>
</li>
<li><strong>자바</strong>: <code>&quot;Hello &quot; + n + &quot; bottles&quot;</code><ul>
<li>자바는 최근(JDK 21)에서야 미리보기로 유사 기능이 도입</li>
</ul>
</li>
</ul>
<hr>
<h3 id="컬렉션--기본이-불변">컬렉션 – 기본이 불변</h3>
<ul>
<li><p><strong>스칼라</strong>:</p>
<pre><code class="language-scala">  val list = List(1, 2, 3)  // 스칼라의 `List`, `Set`, `Map`은 기본적으로 불변</code></pre>
</li>
<li><p><strong>자바</strong>:</p>
<pre><code class="language-java">  List&lt;Integer&gt; list = Arrays.asList(1, 2, 3);  // 변경 가능한 경우 많음</code></pre>
</li>
</ul>
<hr>
<h3 id="변수-선언-val-vs-var">변수 선언: val vs var</h3>
<ul>
<li><code>val</code>: 자바의 <code>final</code>처럼 재할당 불가</li>
<li><code>var</code>: 가변 변수 (지양)</li>
</ul>
<pre><code class="language-scala">val name = &quot;Alice&quot;  // 불변
var count = 10      // 가변</code></pre>
<h3 id="튜플-tuple">튜플 (Tuple)</h3>
<ul>
<li>자바에는 튜플이 없지만, 스칼라는 여러 값을 하나로 묶는 튜플을 지원<pre><code class="language-scala">val book = (2018, &quot;Modern Java in Action&quot;, &quot;Manning&quot;)
// 자바에서는 클래스를 별도로 만들어야 하는 작업이다.</code></pre>
</li>
</ul>
<hr>
<h3 id="option-자바의-optional과-유사">Option (자바의 Optional과 유사)</h3>
<ul>
<li>스칼라의 <code>Option</code>은 값의 존재/부재를 안전하게 표현<pre><code class="language-scala">val maybeName: Option[String] = Some(&quot;Alice&quot;)  // 또는 None</code></pre>
</li>
</ul>
<hr>
<br/>

<h2 id="3-함수형-프로그래밍-특성-스칼라-vs-자바">3. 함수형 프로그래밍 특성: 스칼라 vs 자바</h2>
<h3 id="일급-함수와-고차-함수">일급 함수와 고차 함수</h3>
<pre><code class="language-java">// 스칼라는 함수 자체를 변수로 다룰 수 있다.
val add = (x: Int, y: Int) =&gt; x + y


// 자바도 람다로 비슷하게 할 수 있지만, 함수 자체를 값처럼 다루는 것이 아니라 
// Function&lt;T, R&gt; 같은 인터페이스 구현이 기반
BiFunction&lt;Integer, Integer, Integer&gt; add = (x, y) -&gt; x + y;
</code></pre>
<h3 id="익명-함수와-클로저">익명 함수와 클로저</h3>
<pre><code class="language-scala">// 스칼라는 람다(익명 함수)에서 외부의 변수를 자유롭게 참조할 수 있으며, 
// 이때 람다가 외부 환경을 &quot;포착&quot;하여 클로저가 된다.
val add = (x: Int) =&gt; x + externalVar</code></pre>
<ul>
<li>자바:  외부 변수는 <code>final</code> 또는 사실상 final이어야 합니다.</li>
</ul>
<pre><code class="language-java">// # scala
var externalVar = 10
val add = (x: Int) =&gt; x + externalVar  // 클로저 생성 시점의 externalVar 캡처

println(add(5))  // 15 (10 + 5)

externalVar = 20  // 외부 변수 값 변경
println(add(5))  // 25 (20 + 5) → 캡처된 변수의 최신 값 반영

// # java
int external = 10;
Function&lt;Integer, Integer&gt; add = x -&gt; x + external;
external = 20;  // Error: 외부 변수 수정 불가</code></pre>
<hr>
<h3 id="커링currying">커링(Currying)</h3>
<ul>
<li><p>스칼라</p>
<ul>
<li>커링(여러 인자를 받는 함수를 인자 하나씩 받는 함수의 체인으로 변환) 문법을 지원<pre><code class="language-scala">def add(x: Int)(y: Int): Int = x + y
 val add5 = add(5)_  // 새로운 함수 생성</code></pre>
</li>
</ul>
</li>
<li><p>자바: 커링을 직접 구현하려면 복잡한 람다 체인이 필요</p>
</li>
</ul>
<hr>
<br/>

<h2 id="4-클래스와-트레이트-trait">4. 클래스와 트레이트 (Trait)</h2>
<h3 id="클래스-선언">클래스 선언</h3>
<ul>
<li>스칼라: 생성자, 게터/세터 등을 자동 생성 =&gt; 매우 간결<pre><code class="language-scala">  // 생성자와 불변 필드를 동시에 선언
  class Book(val title: String, val year: Int)</code></pre>
</li>
<li>자바: 모든 필드, 생성자, 메서드를 수동으로 작성해야 합니다.</li>
</ul>
<hr>
<h3 id="트레이트-유연한-다중-상속">트레이트: 유연한 다중 상속</h3>
<ul>
<li>스칼라의 <code>trait</code>는 자바의 <code>interface</code>보다 강력하다.<ul>
<li>메서드 구현 포함 가능</li>
<li>상태(필드)도 가질 수 있음</li>
<li>다중 상속 가능</li>
</ul>
</li>
</ul>
<pre><code class="language-scala">trait A { def hello() = println(&quot;Hello from A&quot;) }
trait B { def hello() = println(&quot;Hello from B&quot;) }

class C extends A with B  // 트레이트 다중 상속
</code></pre>
<br/>

<p>요약</p>
<blockquote>
<p><strong>스칼라는 자바보다 간결하고 함수형 스타일을 자연스럽게 지원하며, 불변 컬렉션, 튜플, 일급 함수, 트레이트 등 
다양한 기능을 제공해 객체지향과 함수형 프로그래밍의 조화를 이룬 언어다.</strong></p>
</blockquote>
<br/>

<h1 id="chapter-21-결론-그리고-자바의-미래">CHAPTER 21 결론 그리고 자바의 미래</h1>
<h2 id="자바-8-이후-무슨-일이-있었을까">자바 8 이후, 무슨 일이 있었을까?</h2>
<h3 id="1-함수형-프로그래밍이-들어왔다--람다--메서드-참조">1. 함수형 프로그래밍이 들어왔다 – <strong>람다 &amp; 메서드 참조</strong></h3>
<pre><code class="language-java">list.sort((a, b) -&gt; a.compareTo(b));
list.sort(String::compareTo);</code></pre>
<ul>
<li>함수(코드 블록)를 값처럼 다룰 수 있게 되었다.</li>
<li>익명 클래스로 쓰던 코드를 훨씬 간결하게 바꿀 수 있다.</li>
<li>반복되는 코드를 줄이고, 더 읽기 쉽게 만들었다.</li>
</ul>
<hr>
<h3 id="2-선언형-데이터-처리--stream-api">2. 선언형 데이터 처리 – <strong>Stream API</strong></h3>
<pre><code class="language-java">List&lt;String&gt; result = list.stream()
    .filter(s -&gt; s.startsWith(&quot;J&quot;))
    .map(String::toUpperCase)
    .collect(Collectors.toList());</code></pre>
<ul>
<li>데이터를 흐름(Pipeline)처럼 처리합니다.</li>
<li><code>filter</code>, <code>map</code>, <code>collect</code> 등의 메서드 체이닝으로 가독성 ↑</li>
<li>병렬 처리도 <code>.parallelStream()</code>으로 간단하게</li>
</ul>
<hr>
<h3 id="3-null을-대체하는-안전한-방식--optional">3. null을 대체하는 안전한 방식 – <strong>Optional</strong></h3>
<pre><code class="language-java">Optional&lt;String&gt; name = Optional.ofNullable(user.getName());
name.ifPresent(System.out::println);</code></pre>
<ul>
<li>null 체크 지옥에서 벗어날 수 있는 구조.</li>
<li><code>ifPresent</code>, <code>orElse</code>, <code>map</code>, <code>filter</code> 등으로 안전하고 깔끔하게 처리 가능.</li>
</ul>
<hr>
<h3 id="4-비동기-처리를-위한-새-도구--completablefuture">4. 비동기 처리를 위한 새 도구 – <strong>CompletableFuture</strong></h3>
<pre><code class="language-java">CompletableFuture.supplyAsync(() -&gt; getData())
    .thenApply(data -&gt; process(data))
    .thenAccept(result -&gt; display(result));</code></pre>
<ul>
<li>비동기/병렬 처리도 이제는 복잡하지 않다!</li>
<li><code>thenCompose</code>, <code>thenCombine</code> 등으로 여러 작업을 유연하게 조합 가능.</li>
</ul>
<hr>
<h3 id="5-실시간-데이터-흐름--flow-api">5. 실시간 데이터 흐름 – <strong>Flow API</strong></h3>
<ul>
<li>자바 9에서 리액티브 프로그래밍을 위한 표준 <code>Flow</code> 인터페이스가 도입됨.</li>
<li>생산자-소비자 간 데이터 속도 차이를 조절하는 <strong>백프레셔</strong>(Backpressure)를 지원.</li>
<li>RxJava, Project Reactor 같은 외부 라이브러리와도 연동 가능.</li>
</ul>
<hr>
<h3 id="6-인터페이스도-진화했다--디폴트-메서드">6. 인터페이스도 진화했다 – <strong>디폴트 메서드</strong></h3>
<pre><code class="language-java">interface MyInterface {
    default void hello() {
        System.out.println(&quot;Hello from interface!&quot;);
    }
}</code></pre>
<ul>
<li>인터페이스도 기본 구현을 가질 수 있게 됨.</li>
<li>기존 코드에 영향을 주지 않고 새로운 기능 추가 가능</li>
</ul>
<br/>

<h2 id="자바-910-모듈화와-간결한-코드">자바 9~10: 모듈화와 간결한 코드</h2>
<h3 id="자바-9--모듈-시스템">자바 9 – <strong>모듈 시스템</strong></h3>
<ul>
<li><code>module-info.java</code>를 사용해 코드 구조를 명확히 나누고, 의존성을 명시할 수 있게 됨.</li>
<li>JDK 자체도 모듈화되어 가볍고 보안도 강화.</li>
</ul>
<h3 id="자바-10--지역-변수-타입-추론-var">자바 10 – <strong>지역 변수 타입 추론 (var)</strong></h3>
<pre><code class="language-java">var list = new ArrayList&lt;String&gt;();</code></pre>
<ul>
<li>타입을 자동 추론해서 변수 선언을 더 간결하게 작성 가능.</li>
</ul>
<h3 id="그-외에도">그 외에도...</h3>
<ul>
<li><code>List.of()</code>, <code>Set.of()</code> → 불변 컬렉션 생성</li>
<li>try-with-resources 개선</li>
<li>Stream API 기능 확장</li>
</ul>
<br/>

<h2 id="자바의-미래는">자바의 미래는?</h2>
<h3 id="1-패턴-매칭">1. <strong>패턴 매칭</strong></h3>
<ul>
<li><code>if</code>나 <code>instanceof</code>를 더 직관적으로 사용할 수 있게 하는 문법이 추가되고 있다.<pre><code class="language-java">if (obj instanceof String s) {
  System.out.println(s.toUpperCase());
}</code></pre>
</li>
</ul>
<h3 id="2-값-타입-value-types--project-valhalla">2. <strong>값 타입 (Value Types)</strong> – Project Valhalla</h3>
<ul>
<li>객체처럼 쓰지만 메모리를 훨씬 효율적으로 사용하는 구조. 성능 향상 기대</li>
</ul>
<h3 id="3-더-강력한-제네릭--project-amber">3. <strong>더 강력한 제네릭</strong> – Project Amber</h3>
<ul>
<li>제네릭 타입의 한계를 넘기 위한 다양한 기능이 실험되고 있다.</li>
</ul>
<br/>

<h2 id="요약">요약</h2>
<blockquote>
<p>자바는 람다, 스트림, Optional, CompletableFuture 등으로 <strong>간결하고 안전하며</strong>,  
모듈화, 타입 추론 등으로 <strong>더 유연해졌고</strong>,  
앞으로도 <strong>패턴 매칭, 값 타입, 제네릭 강화</strong> 등 혁신은 계속되고 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 5장]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-5%EC%9E%A5</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-5%EC%9E%A5</guid>
            <pubDate>Fri, 16 May 2025 04:32:10 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-15-completablefuture와-리액티브-프로그래밍-컨셉의-기초">CHAPTER 15 CompletableFuture와 리액티브 프로그래밍 컨셉의 기초</h1>
<blockquote>
<p>여러 웹서비스에 접근해야하는데, 응답을 기다리는 동안 연산이 블록되거나 CPU 자원을 낭비하지 않고, 비동기적으로 작업을 처리하고 싶다.
CompletableFuture: 비동기 작업 조합의 핵심 도구 → thenCombine, thenCompose 활용</p>
</blockquote>
<br/>


<h3 id="왜-동시성이-중요한가">왜 동시성이 중요한가?</h3>
<ul>
<li><strong>MSA 시대</strong>: 서비스는 작아졌지만, 그만큼 <strong>네트워크 호출</strong>은 많아짐 → 외부 자원을 기다리는 시간이 많아짐.</li>
<li><strong>현대 앱 구조</strong>: 하나의 독립 앱보다는 여러 소스를 조합하는 <strong>Mash-up</strong> 형태가 많음.</li>
<li>단순히 CPU 병렬성(parallelism)이 아니라, <strong>동시성(concurrency)</strong> — 즉, <strong>외부 자원을 기다리며 CPU를 놀리지 않는 구조</strong>가 중요함.</li>
</ul>
<blockquote>
<p>앞 장들에서는 포크/조인 프레임워크와 병렬 스트림으로 간단하게 병렬 실행 가능</p>
</blockquote>
<p>동시성 기술의 필요성</p>
<blockquote>
<p>병렬성이 아니라 동시성을 필요로 하는 상황 = 조금씩 연관된 작업을 같은 CPU에서 동작하는 것 또는 애플리케이션을 생산성을 극대화할 수 있도록 코어를 바쁘게 유지하는 것이 목표라면, 원격 서비스나 데이터베이스 결과를 기다리는 스레드를 볼록함으로 연산 자원을 낭비하는 일은 피해야 한다.
=&gt; Future 인터페이스로 CompletableFuture 구현, 플로 API</p>
</blockquote>
<ul>
<li>동시성은 단일 코어 머신에서 발생할 수 있는 프로그래밍 속성으로 실행이 서로 겹칠 수 있는 반면</li>
<li>병렬성은 병렬 실행을 하드웨어 수준에 지원한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/7f8fde05-ae0f-460b-8021-86c7339f4437/image.png" alt=""></p>
<blockquote>
<p>동시성 제어 기법들은 단일 스레드가 여러 작업을 번갈아 처리하는 상황뿐 아니라, 실제로 여러 스레드가 동시에 작업을 처리하는 멀티스레드 환경(즉, 병렬 처리)에서도 똑같이 적용되어 데이터의 일관성과 프로그램의 안정성을 보장 
ex) 데이터 불일치, 경쟁 조건(race 컨디션), 데드락 같은 문제를 해결하는 기법</p>
</blockquote>
<br/>


<h2 id="동시성을-구현하는-자바-지원의-진화">동시성을 구현하는 자바 지원의 진화</h2>
<ul>
<li>스레드, 고수준 추상화, 스레드 풀, Futures</li>
</ul>
<h3 id="자바-동시성-지원의-진화-과정">자바 동시성 지원의 진화 과정</h3>
<p>자바의 동시성 프로그래밍 지원은 하드웨어 발전(멀티코어 CPU 등)과 프로그래밍 패러다임 변화에 맞춰 단계적으로 발전해왔습니다. 주요 단계를 <strong>시대별</strong>로 정리하면 다음과 같습니다:</p>
<h4 id="1-초기--thread--runnable-jdk-1x">1. 초기 : Thread &amp; Runnable (JDK 1.x)</h4>
<pre><code class="language-java">new Thread(() -&gt; System.out.println(&quot;실행&quot;)).start();</code></pre>
<ul>
<li>기본적인 스레드 생성/관리 기능 제공</li>
<li><strong>한계</strong>: 직접 스레드 생성/관리 필요 → 복잡성 증가, 자원 낭비<ul>
<li>직접 스레드를 만들고, 직접 실행</li>
</ul>
</li>
</ul>
<h4 id="2-executorservice--future-java-5">2. ExecutorService + Future (Java 5)</h4>
<pre><code class="language-java">ExecutorService executor = Executors.newFixedThreadPool(4);
Future&lt;Integer&gt; future = executor.submit(() -&gt; 42);
Integer result = future.get(); // 블로킹 호출</code></pre>
<ul>
<li><strong>ExecutorService</strong> 도입<ul>
<li>스레드 풀 도입 → 태스크 제출과 실행을 분리: <code>executor.submit(task)</code><ul>
<li><strong>작업(태스크)을 실행하는 &#39;방법&#39;과 &#39;시점&#39;을 개발자가 직접 관리하지 않고, 작업을 단순히 제출만 하면 실제 실행은 ExecutorService(즉, 스레드 풀)가 알아서 처리하도록 맡기는 구조</strong></li>
</ul>
</li>
<li>스레드 풀 관리 자동화</li>
</ul>
</li>
<li><strong>Callable &amp; Future</strong><ul>
<li>결과 반환 가능한 비동기 작업 처리</li>
<li>Future로 <strong>비동기 결과 조회</strong> 가능하지만 여전히 <code>get()</code> 호출 시 블로킹</li>
</ul>
</li>
</ul>
<h4 id="3-forkjoin-프레임워크-java-7">3. Fork/Join 프레임워크 (Java 7)</h4>
<pre><code class="language-java">class SumTask extends RecursiveTask&lt;Long&gt; {
  protected Long compute() {
    // 작업 분할 및 병합 로직
  }
}</code></pre>
<ul>
<li><strong>RecursiveTask</strong>  : 분할-정복 알고리즘 지원<ul>
<li><strong>장점</strong>: 작업 도둑질(work-stealing) 알고리즘으로 멀티코어 활용 최적화</li>
</ul>
</li>
<li>vs ExecutorService : 모든 스레드가 중앙 큐에서 작업 가져옴<ul>
<li>Fork/Join 프레임워크 : 각 스레드가 전용 Deque(덱) 보유<ul>
<li>불규칙한 작업 크기 분포의 경우 더 낫다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="4-completablefuture--병렬-스트림-java-8-2014">4. CompletableFuture &amp; 병렬 스트림 (Java 8, 2014)</h4>
<ul>
<li><strong>병렬 스트림</strong><pre><code class="language-java">list.parallelStream().map(...).collect(...); // 간단한 병렬 처리</code></pre>
</li>
<li><strong>CompletableFuture</strong>  : 비동기 작업 체이닝 및 조합 가능<pre><code class="language-java">CompletableFuture.supplyAsync(() -&gt; &quot;데이터&quot;)
    .thenApplyAsync(s -&gt; s + &quot; 처리&quot;)
    .thenAccept(System.out::println); // 논블로킹[3]</code></pre>
</li>
</ul>
<h4 id="5flow-api-java-9-2017---리액티브-스트림-지원">5.Flow API (Java 9, 2017) - 리액티브 스트림 지원</h4>
<pre><code class="language-java">SubmissionPublisher&lt;String&gt; publisher = new SubmissionPublisher&lt;&gt;();
publisher.subscribe(new Subscriber&lt;&gt;() { ... });</code></pre>
<ul>
<li><strong>Flow API</strong>  : 발행-구독 모델 구현(Pub/Sub)</li>
<li><strong>의의</strong>: 역압력(백프레셔(backpressure)) 지원으로 과부하 방지</li>
</ul>
<br/>

<p>✅ 1. <strong>추상화 수준</strong></p>
<table>
<thead>
<tr>
<th>도구</th>
<th>추상화 수준</th>
<th>관리 방식</th>
</tr>
</thead>
<tbody><tr>
<td>Thread/Runnable</td>
<td>Low-Level</td>
<td>직접 스레드 생성/관리</td>
</tr>
<tr>
<td>ExecutorService</td>
<td>Mid-Level</td>
<td>스레드 풀 자동 관리</td>
</tr>
<tr>
<td>Fork/Join</td>
<td>High-Level</td>
<td>작업 분할 자동화</td>
</tr>
<tr>
<td><strong>CompletableFuture</strong></td>
<td>High-Level</td>
<td>스레드 풀 + 비동기 파이프라인</td>
</tr>
<tr>
<td><strong>Flow API (Reactive Streams)</strong></td>
<td>Very High-Level</td>
<td>비동기 데이터 흐름 자동 관리 (Backpressure 포함)</td>
</tr>
</tbody></table>
<hr>
<p>✅ 2. <strong>주요 사용 사례</strong></p>
<table>
<thead>
<tr>
<th>도구</th>
<th>적합한 작업 유형</th>
<th>최적화 포인트</th>
</tr>
</thead>
<tbody><tr>
<td>Thread/Runnable</td>
<td>간단한 단일 작업</td>
<td>-</td>
</tr>
<tr>
<td>ExecutorService</td>
<td>독립적이고 균일한 다수 작업</td>
<td>스레드 재사용</td>
</tr>
<tr>
<td>Fork/Join</td>
<td>재귀적 분할 가능한 대규모 작업</td>
<td>작업 도둑질 (Work-Stealing)</td>
</tr>
<tr>
<td><strong>CompletableFuture</strong></td>
<td>의존 관계가 있는 비동기 작업</td>
<td>체이닝(thenX), 예외 처리</td>
</tr>
<tr>
<td><strong>Flow API (Reactive Streams)</strong></td>
<td>데이터 스트림 처리 (무한/이벤트 기반)</td>
<td>Backpressure, 연산자 기반 조합</td>
</tr>
</tbody></table>
<hr>
<p>✅ 3. <strong>선택 가이드</strong></p>
<table>
<thead>
<tr>
<th>상황</th>
<th>추천 도구</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>간단한 비동기 작업</td>
<td>ExecutorService</td>
<td>사용 편의성</td>
</tr>
<tr>
<td>균등한 작업 + 성능 중요</td>
<td>ThreadPoolExecutor</td>
<td>객체 생성 오버헤드 최소화</td>
</tr>
<tr>
<td>재귀적 분할 작업</td>
<td>Fork/Join</td>
<td>Work-Stealing 최적화</td>
</tr>
<tr>
<td>I/O 집중 작업</td>
<td>ExecutorService</td>
<td>블로킹 대응에 유리</td>
</tr>
<tr>
<td><strong>의존 관계 있는 비동기 연쇄 작업</strong></td>
<td><strong>CompletableFuture</strong></td>
<td>직관적 체이닝 + 예외 처리 지원</td>
</tr>
<tr>
<td><strong>데이터 스트림 + 반응형 UI/서버</strong></td>
<td><strong>Flow API (Reactor, RxJava 등)</strong></td>
<td>이벤트 기반 설계 최적화, backpressure 대응</td>
</tr>
</tbody></table>
<br/>

<h4 id="진화의-핵심-방향">진화의 핵심 방향</h4>
<ol>
<li><strong>추상화 수준 향상</strong> (<mark style="background: #FF5582A6;">스레드 사용 패턴을 추상화</mark>)
 저수준 스레드 관리 → 고수준 API(ExecutorService, Stream 등)</li>
<li><strong>하드웨어 활용 최적화</strong><br> 멀티코어 CPU 효율적 사용(Fork/Join, 병렬 스트림)</li>
<li><strong>비동기 프로그래밍 강화</strong><br> Future → CompletableFuture → Flow로 점진적 개선</li>
<li><strong>함수형 패러다임 통합</strong><br> 람다/스트림과의 시너지 효과</li>
</ol>
<blockquote>
<p>&quot;동시성 처리는 이제 라이브러리 수준에서 추상화되어, 개발자는 비즈니스 로직에 집중할 수 있게 되었습니다.&quot;</p>
</blockquote>
<p>이러한 변화들은 클라우드 환경과 대규모 분산 시스템 요구사항에 대응하기 위한 자바의 지속적인 진화를 보여준다.</p>
<br/>

<h3 id="스레드풀">스레드풀</h3>
<p><strong>1. 스레드의 문제점</strong></p>
<ul>
<li><p><strong>비용 문제</strong>:  </p>
<ul>
<li>자바 스레드는 직접 운영체제 스레드에 접근한다</li>
<li>운영체제 스레드 생성/종료는 <strong>고비용</strong> (메모리, CPU 자원 소모).<br>  → 과도한 스레드 생성 시 시스템 자원 고갈 가능성.</li>
</ul>
</li>
<li><p><strong>하드웨어 제약</strong>:  </p>
<ul>
<li>CPU 코어 수에 따라 최적 스레드 수 결정 (예: 8코어 → 8~16스레드).<br>  → 코어 수를 초과하는 스레드는 컨텍스트 스위칭 오버헤드 발생.</li>
</ul>
</li>
</ul>
<p><strong>2. 스레드 풀의 장점</strong></p>
<ul>
<li><strong>자원 재사용</strong>:  <ul>
<li>미리 생성된 스레드 재활용 → 생성/종료 오버헤드 감소.</li>
</ul>
</li>
<li><strong>작업 큐 관리</strong>:  <ul>
<li>대기 작업을 큐에 저장 → 작업 처리 순서 제어 가능.</li>
</ul>
</li>
<li><strong>설정 유연성</strong>: 큐 크기, 거부 정책, 우선순위 등 세밀한 제어 가능.</li>
</ul>
<p><strong>3. 스레드 풀의 단점</strong></p>
<ul>
<li>블로킹 작업 시 효율 저하</li>
<li>데드락 위험 : 상호 의존적 작업 시 교착 상태 발생 가능</li>
<li>종료 누락 : 종료하지 않으면 애플리케이션 무한 대기</li>
</ul>
<br/>

<p>스레드 풀 동작 원리</p>
<pre><code class="language-text">// 이 구조 덕분에, 과도한 스레드 생성 없이 효율적인 작업 처리가 가능
1. 작업 제출
2. 코어 스레드가 비어 있다면 → 즉시 실행
3. 코어 스레드가 모두 사용 중이라면
   → 작업 큐에 저장 (큐가 안 찼다면)
   → 큐가 가득 찼다면
      → 최대 스레드 수 초과 여부 판단
         → 초과 안 했으면 스레드 추가 생성
         → 초과했다면 거부 정책 발동 (예: 예외 발생)</code></pre>
<h4 id="스레드의-다른-추상화--중첩되지-않은-메서드-호출">스레드의 다른 추상화 : 중첩되지 않은 메서드 호출</h4>
<ul>
<li>7장(병렬 스트림 처리와 포크/조인 프레임워크)에서 사용한 동시성과의 비교 <ul>
<li>포크/조인<ul>
<li><em>엄격한 포크/조인</em> : 스레드 생성과 join()이 한 쌍처럼 충첩된 메서드 호출. 순서가 있다.</li>
<li>여유로운 포크/조인: 시작된 태스크를 내부 호출이 아니라 외부 호출에서 종료하도록. 순서 상관 x<ul>
<li>비동기 메서드</li>
</ul>
</li>
<li>메서드가 반환된 후에도 만들어진 태스크 실행이 계속되는 메서드를 비동기 메서드</li>
<li>주의사항<ul>
<li>스레드 실행은 메서드를 호출한 다음의 코드와 동시에 실행되므로 데이터 경쟁 문제를 일으키지 않도록해야 한다.</li>
<li>기존 실행 중이던 스레드가 종료되지 않은 상황에서 자바의 main() 메서드가 반환되면 문제가 발생할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<h2 id="비동기-api-기법">비동기 API 기법</h2>
<ol>
<li>Runnable 사용<ul>
<li>복잡한 코드</li>
</ul>
</li>
<li><strong>ExecutorService + Future (자바 5~)</strong><ul>
<li>스레드 재사용으로 성능 향상,<code>Future.get()</code>으로 결과를 구조화된 방식으로 수신</li>
<li>But 여전히 명시적인 submit 메서드 호출 같은 불필요한 코드로 오염<ol start="3">
<li>Future 형식 API :  (Future 반환 메서드)</li>
</ol>
</li>
<li>메서드 자체가 <code>Future</code>를 반환하도록 설계<ul>
<li><strong>API 수준에서의 비동기 지원 시작</strong> =&gt; <strong>추상화</strong>함으로써,  호출자가 실행 메커니즘을 알 필요 없이 결과만 기다리게 하는 것이 목표</li>
</ul>
</li>
<li>호출 즉시 자신의 원래 바디를 평가하는 task를 포함하는 Future 반환<ul>
<li>실제로 작업이 끝났는지(완료됐는지) 여부를 <strong>get()을 호출하는 시점에서야 알 수 있다.</strong></li>
</ul>
</li>
</ul>
</li>
<li>리액티브 형식 API<ul>
<li>메서드들의 시그니처를 바꿔 콜백 형식의 프로그래밍을 이용하는 것</li>
<li>메서드에 추가 인수로 콜백(람다)을 전달해서 메서드의 바디에서는 <code>return 문</code>으로 결과를 반환하는 것이 아니라 결과가 준비되면 이를 람다로 호출하는 태스크를 만드는 것</li>
<li>보완 사항<ul>
<li>if-then-else를 이용해 적절한 락을 이용해 두 콜백이 모두 호출되었는지 확인 후 수행</li>
<li>리액티브 형식의 API는 보통 한 결과가 아니라 일련의 이벤트에 반응하도록 설계되었으므로 Future를 이용하는 것이 더 적절</li>
</ul>
</li>
</ul>
</li>
<li><strong>리액티브 프로그래밍 (자바 9+)</strong><ul>
<li><code>CompletableFuture</code> 조합 → <code>java.util.concurrent.Flow</code> (발행-구독 모델)</li>
<li><strong>완전한 비동기 체인</strong> 구성 가능</li>
</ul>
</li>
</ol>
<br/>

<h3 id="실제-개발-시-마주칠-주요-문제블로킹-예외-처리와-해결-전략">실제 개발 시 마주칠 주요 문제(블로킹, 예외 처리)와 해결 전략</h3>
<h4 id="잠자기그리고-기타-블로킹-동작는-해로운-것으로-간주--블로킹-동작의-위험성">잠자기(그리고 기타 블로킹 동작)는 해로운 것으로 간주 = <strong>블로킹 동작의 위험성</strong></h4>
<ul>
<li>스레드 풀에서 sleep중인 태스크는 다른 태스크가 시작되지 못하게 막으므로 자원을 소비한다.<ul>
<li>이상적으로는 절대 태스크에서 기다리는 일을 만들지 말거나 코드에서 예외를 일으키는 방법으로 이를 처리할 수 있다.
=&gt; 비효율적 자원 사용 방지 해야한다.</li>
</ul>
</li>
</ul>
<h4 id="현실성-확인">현실성 확인</h4>
<ul>
<li>현실적으로 &#39;모든 것은 비동기&#39;라는 설계 원칙을 지킬 수는 없으니, 고려하자. 대체 방안으로 네트워크 서버의 블록/비블록 API를 일관적으로 제공하는 Netty 같은 새로운 라이브러리를 사용하는 것도 추천</li>
</ul>
<h4 id="비동기-api에서-예외는-어떻게-처리할까">비동기 API에서 예외는 어떻게 처리할까?</h4>
<ul>
<li>비동기 API에서 호출된 메서드의 실제 바디는 별도의 스레드에서 호출되며 이때 발생하는 어떤 에러는 이미 호출자의 실행 범위와는 관계가 없는 상황이 된다.<ul>
<li>Future를 구현한 CompletableFuture에서는 런타임 get() 메서드에 예외를 처리할 수 있는 기능을 제공하며 예외에서 회복할 수 있도록 exceptionally()  같은 메서드도 제공한다.</li>
</ul>
</li>
</ul>
<br/>

<h2 id="박스와-채널-모델">박스와 채널 모델</h2>
<ul>
<li>프로그램의 일부를 채널을 이용해 소통하는 박스로 표시하는 다이어그램 방식 설명
  = 박스와 채널 모델은 동시성을 설계하고 계념화하기 위한 모델
  =&gt; 이를 통해 생각, 코드 구조화로 대규모 시스템 구현의 추상화 수준을 높일 수 있다.</li>
<li>박스(또는 프로그램의 콤비네티어) : 원하는 연산 표현</li>
</ul>
<p>고려사항</p>
<ul>
<li><p>병렬성을 극대화하기 위해 모든 함수를 Future 로 감싸려고 한다면, 시스템이 커지고 각자의 박스, 채널이 등장한다면 많은 task가 get() 호출해서 Future 끝나기를 기다리는 상황 =&gt; 하드웨어의 병렬성 저조 혹은 데드락 가능성
  =&gt; CompletableFuture와 콤비네이터로 문제 해결</p>
</li>
<li><p>목적: 동시성 프로그램을 추상화하여 구조화</p>
</li>
<li><p>핵심 요소</p>
<ul>
<li>박스: 연산 단위 (예: CompletableFuture)</li>
<li>채널: 박스 간 데이터 흐름 (예: 비동기 스트림)</li>
</ul>
</li>
<li><p>위험: 많은 task의 과도한 Future.get() 사용 → 병렬성 저하/데드락</p>
</li>
<li><p>해결책: CompletableFuture와 콤비네이터로 문제 해결</p>
</li>
</ul>
<br/> 

<h2 id="completablefuture와-콤비네이터를-이용한-동시성">CompletableFuture와 콤비네이터를 이용한 동시성</h2>
<ul>
<li>일반적으로 Future는 실행해서 get()으로 결과를 얻을 수 있는 Callable로 만들어진다</li>
<li>CompletableFuture는 실행할 코드 없이 Future를 조합할 수 있는 기능이 있다.</li>
<li>complete() 메서드를 이용해 다른 스레드가 완료한 후에 get()으로 값을 얻을 수 있도록 허용한다.</li>
</ul>
<p>CompletableFuture와 <code>thenCombine</code>을 이용한 비동기 작업 조합</p>
<p><strong>기본 개념</strong></p>
<ul>
<li><strong>CompletableFuture</strong>: Java 8에서 도입된 비동기 작업 처리 클래스<ul>
<li><code>Future</code>의 확장판으로, <strong>작업 체이닝</strong>과 <strong>조합</strong> 기능을 제공</li>
<li><strong>Non-blocking</strong> 방식으로 여러 작업을 병렬 처리 가능</li>
</ul>
</li>
</ul>
<p>문제 상황</p>
<pre><code class="language-java">CompletableFuture&lt;Integer&gt; a = new CompletableFuture&lt;&gt;(); 
executorService.submit(() -&gt; a.complete(f(x))); 
int b = g(x); System.out.println(a.get() + b); // ❌ a.get()에서 블로킹 발생</code></pre>
<ul>
<li><code>g(x)</code>는 <strong>동기 실행</strong> → <code>f(x)</code>와 병렬 처리되지 않음</li>
</ul>
<p><strong>해결책: <code>thenCombine</code> 사용</strong></p>
<pre><code class="language-java">CompletableFuture&lt;Integer&gt; a = CompletableFuture.supplyAsync(() -&gt; f(x), executor);
CompletableFuture&lt;Integer&gt; b = CompletableFuture.supplyAsync(() -&gt; g(x), executor);

// 결과를 추가하는 세 번째 연산c는 다른 두 작업이 끝날 때까지 실행되지 않는다.
CompletableFuture&lt;Integer&gt; c = a.thenCombine(b, (y, z) -&gt; y + z);
System.out.println(c.get());  // ✅ 최종 결과만 블로킹
</code></pre>
<br/> 


<h2 id="발행-구독-그리고-리액티브-프로그래밍">발행-구독 그리고 리액티브 프로그래밍</h2>
<h3 id="future-vs-리액티브-프로그래밍">Future vs 리액티브 프로그래밍</h3>
<p>Java에서 <code>Future</code>는 <em>한 번</em> 실행해서 결과를 한 번 <em>반환_하는 비동기 방식, 하지만 리액티브 프로그래밍은 _시간이 지나면서 계속해서</em> 데이터를 발행하고, 그 결과에 _반응_하는 프로그래밍이다. </p>
<blockquote>
<p>즉, Future는 &quot;한 방에 끝&quot;, 리액티브는 &quot;계속 반응하면서 처리&quot;</p>
</blockquote>
<p>Java 9부터는 <code>java.util.concurrent.Flow</code> API를 통해 이 개념을 지원하며, <strong>발행-구독(Publisher-Subscriber)</strong> 모델을 기반</p>
<p>📈 발행-구독(Pub-Sub) 모델의 데이터 흐름 방향:<br><code>Publisher → Subscription → Subscriber</code></p>
<p>데이터 흐름 방향: 업스트림과 다운스트림</p>
<ul>
<li><strong>업스트림 (Upstream)</strong>: 데이터가 위쪽에서 내려옴 → 예: <code>onNext(newValue)</code></li>
<li><strong>다운스트림 (Downstream)</strong>: 아래쪽으로 흐름 → 예: <code>notifyAllSubscribers()</code></li>
</ul>
<p>예제</p>
<pre><code class="language-java">// 이를 통해 SimpleCell은 구독자 혹은 발행자가 될 수 있다.
public class SimpleCell implements Publisher&lt;Integer&gt;, Subscriber&lt;Integer&gt; {}

subscribe : 구독자 추가

onNext : 구독자들에게 전파
</code></pre>
<h4 id="역압력">역압력</h4>
<ul>
<li>압력 상황 <ul>
<li>매 초마다 수천개의 메시지가 onNext로 전달된다면 빠르게 전달되는 이벤트를 아무 문제 없이 처리할 수 있을까? =&gt; 이럴때는 정보의 흐름 속도를 제어하는 역압력 기법이 필요</li>
<li>역압력 : Subscriber가 Publisher에게 <em>&quot;하나씩만 줘!&quot;</em> 라고 요청하는 방식</li>
</ul>
</li>
</ul>
<pre><code class="language-java">// Flow.Subscriber의 주요 메서드
void onSubscribe(Subscription subscription); // Subscription은 Publisher와 Subscriber 사이의 커넥션을 나타냅니다

@Override
public void onNext(Integer item) {
    // 처리...
    subscription.request(1); // request(n)을 호출해 몇 개를 받을지 명시
}
</code></pre>
<p>역압력 설계 시 고민할 점 ex) 실시간 처리 vs 중요한 로그 저장 등</p>
<ul>
<li>느린 Subscriber 때문에 전체 속도를 늦출 것인가?</li>
<li>각 Subscriber에게 별도 큐를 둘 것인가?</li>
<li>큐가 넘치면 데이터를 버릴 것인가?</li>
<li>중요한 데이터라면 버리면 안 되는데?</li>
</ul>
<br/> 

<h2 id="리액티브-시스템-vs-리액티브-프로그래밍">리액티브 시스템 vs 리액티브 프로그래밍</h2>
<h4 id="리액티브-시스템이란">리액티브 시스템이란?</h4>
<p><strong>리액티브 시스템</strong>은 <em>애플리케이션 전체 구조나 아키텍처</em> 차원의 개념
시스템이 <strong>외부 환경의 변화</strong>, <strong>오류</strong>, <strong>부하 증가</strong>에 <strong>잘 반응하고 견디도록</strong> 설계되어야 한다.</p>
<ul>
<li>속성은 반응성(responsive), 회복성(resilient), 탄력성(elastic)<ul>
<li>반응성: 사용자 요청에 빠르게 응답하는 시스템. 오류나 부하 상황에서도 응답성이 유지됨</li>
<li>회복성: 일부 컴포넌트가 실패해도 전체 시스템이 무너지지 않고 복구 가능</li>
<li>탄력성: 부하가 커져도 유연하게 확장하거나 축소해 처리 가능 (예: 수평 확장)</li>
</ul>
</li>
<li>이러한 속성을 구현하는 방법 중 하나로 리액티브 프로그래밍을 이용할 수 있다.</li>
</ul>
<h4 id="리액티브-프로그래밍이란">리액티브 프로그래밍이란?</h4>
<p><strong>리액티브 프로그래밍</strong>은 <em>데이터의 변화나 이벤트 흐름에 따라</em> 자동으로 반응하는 <strong>코딩 방식</strong></p>
<p>예를 들어, 버튼을 클릭하면 자동으로 이벤트 핸들러가 실행되거나, 실시간 데이터가 들어오면 UI가 즉시 업데이트되는 경우</p>
<blockquote>
<p>✅ 코드 단위에서 비동기 흐름과 이벤트 기반 반응을 구현하는 기술입니다.</p>
</blockquote>
<ul>
<li><strong>예</strong>: <code>RxJava</code>, <code>Project Reactor</code>, <code>Flow API (Java 9+)</code></li>
<li><strong>기술 관점</strong>: <code>onNext</code>, <code>subscribe</code>, <code>stream</code>, <code>event</code> 등으로 표현됨</li>
<li><strong>역할</strong>: 데이터 흐름에 대한 반응형 처리</li>
</ul>
<blockquote>
<p>즉, 리액티브 프로그래밍은 <strong>구현 기술</strong>이고, 리액티브 시스템은 <strong>전체 시스템의 설계 철학</strong>이다.
하지만 단지 리액티브 프로그래밍을 썼다고 해서 전체 시스템이 리액티브 시스템이 되는 것은 아니다. 장애 대응, 부하 분산, 서비스 간 독립성, 모니터링까지 포함돼야 <em>리액티브 시스템</em></p>
</blockquote>
<br/>


<h1 id="chapter-16-completablefuture--안정적-비동기-프로그래밍">CHAPTER 16 CompletableFuture : 안정적 비동기 프로그래밍</h1>
<h2 id="future의-단순-활용-java-8-이전">Future의 단순 활용 (java 8 이전)</h2>
<p><strong>Future란?</strong></p>
<ul>
<li><strong>비동기 작업의 결과를 나타내는 객체</strong>입니다. = 비동기 작업의 <strong>결과를 나중에 제공받는 약속(Promise)</strong></li>
<li>예: 오래 걸리는 계산을 별도의 스레드에서 실행하고 결과를 Future로 받음.</li>
</ul>
<pre><code class="language-java">
ExecutorService executor = Executors.newCachedThreadPool();

// 1. 비동기 작업 제출 (오래 걸리는 계산)
Future&lt;Double&gt; future = executor.submit(() -&gt; doSomeLongComputation());

// 2. 다른 작업 수행
doSomethingElse();

try {
    // 3. 최대 1초 동안 결과 기다림
    Double result = future.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    // 타임아웃 처리
} catch (InterruptedException | ExecutionException e) {
    // 예외 처리
}
</code></pre>
<blockquote>
<p>💡 get() 메서드는 결과가 나올 때까지 현재 스레드를 블록시킴. 오래 걸리는 작업이 영원히 끝나지 않을 수 있는 문제가 있으므로 get 메서드를 오버로드해서 스레드가 대기할 최대 타임아웃 시간을 설정하는 것이 좋다.</p>
</blockquote>
<h4 id="future의-한계"><strong>Future의 한계</strong></h4>
<ul>
<li><strong>두 비동기 작업의 결과 조합</strong>이나, <strong>가장 빠른 작업 선택</strong> 등 고급 비동기 흐름을 구성하기 어렵다.</li>
<li>예외 처리나 수동 완료 기능이 제한적
→ 이를 보완하기 위해 Java 8에서 <strong>CompletableFuture</strong>가 도입됨.</li>
</ul>
<br/>

<h2 id="completablefuture로-비동기-api-구현하기">CompletableFuture로 비동기 API 구현하기</h2>
<ul>
<li>동기 메서드 -&gt; 비동기 메서드로 변환<ul>
<li>비동기 계산과 완료 결과를 포함하는 CompletableFuture 인스턴스 활용</li>
<li>결과가 준비되기 전에도 <code>Future</code>를 반환해 다른 작업을 병행할 수 있게 한다</li>
</ul>
</li>
</ul>
<p>기본 패턴</p>
<pre><code class="language-java">public Future&lt;Double&gt; getPriceAsync(String product) {
  CompletableFuture&lt;Double&gt; futurePrice = new CompletableFuture&lt;&gt;();
  new Thread(() -&gt; {
    double price = calculatePrice(product);
    futurePrice.complete(price); // 계산 완료 시 결과 전달
  }).start();
  return futurePrice;
}</code></pre>
<p>사용 예시</p>
<pre><code class="language-java">// 비동기로 처리되므로 즉시 Future 반환하고, 그 사이에 다른 작업 처리
Shop shop = new Shop(&quot;BestShop&quot;);

long start = System.nanoTime();
Future&lt;Double&gt; futurePrice = shop.getPriceAsync(&quot;my favorite product&quot;); //제품 가격 요청
long invocationTime = ((System.nanoTime() - start) / 1_000_000);

//제품의 가격을 계산하는 동안
doSomethingElse();

//다른 상점 검색 등 작업 수행
try {
  double price = futurePrice.get(); //가격정보를 받을때까지 블록
} catch (Exception e) {
  throw new RuntimeException(e);
}
long retrievalTime = ((System.nanoTime() - start) / 1_000_000);</code></pre>
<ul>
<li>에러 처리 방법<ul>
<li><code>CompletableFuture</code>에서는 <code>completeExceptionally()</code>로 비동기 내부 예외를 외부에 전달 가능</li>
<li>비동기 로직에서 에러가 발생한다면?<ul>
<li>예외가 발생하면 해당 스레드에만 영향을 미치기 때문에 클라이언트는 get 반환때까지 영원히 기다릴 수 있다.</li>
<li>따라서 타임아웃을 활용해 예외처리를 하고, completeExceptionally 메서드를 이용해 CompletableFuture 내부에서 발생한 에외를 클라이언트로 전달해야 한다</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public Future&lt;Double&gt; getPriceAsync(String product) {
  CompletableFuture&lt;Double&gt; futurePrice = new CompletableFuture&lt;&gt;();
  new Thread(() -&gt; {
    try {
      double price = calculatePrice(product);
      futurePrice.complete(price);
    } catch {
      futurePrice.completeExceptionally(ex); //에러를 포함시켜 Future를 종료
    }
  }).start();
  return futurePrice;
}</code></pre>
<ul>
<li>팩토리 메서드 supplyAsync 로 간단히 CompletableFuture 만들 수도 있다.<pre><code class="language-java">  CompletableFuture&lt;Double&gt; future = CompletableFuture.supplyAsync(() -&gt; calculatePrice(&quot;product&quot;));</code></pre>
</li>
</ul>
<br/>

<h2 id="비블록-방식으로-동작-개선하기">비블록 방식으로 동작 개선하기</h2>
<h4 id="stream의-게으름--completablefuture의-병렬성">Stream의 게으름 + CompletableFuture의 병렬성</h4>
<ul>
<li><strong>Stream은 &quot;게으른(lazy)&quot; 연산</strong>이다<ul>
<li><code>stream.map(...)</code>을 쓴다고 바로 실행되지 않고, <strong>최종 연산(<code>collect</code>, <code>forEach</code>)이 있어야 실행됨</strong>.</li>
<li>중간 연산(<code>map</code>, <code>filter</code> 등)은 <strong>데이터를 바로 처리하지 않고, 파이프라인만 만든다.</strong></li>
</ul>
</li>
<li><strong><code>CompletableFuture.join()</code>은 결과를 기다리는 동기 메서드</strong>이다.<ul>
<li>이걸 호출하면 해당 <code>CompletableFuture</code>의 작업이 <strong>완료될 때까지 기다림</strong>.</li>
</ul>
</li>
</ul>
<p>예제</p>
<ul>
<li>Stream 사용해서 List에서 각 객체를 돌면서 블록되는 시간 발생<pre><code class="language-java">  shops.stream()
   .map(shop -&gt; shop.getPrice(product))
   .map(Discount::applyDiscount)
   .collect(toList()); // 동기, 순차 처리</code></pre>
</li>
</ul>
<br/>

<p>해결</p>
<ol>
<li><p>병렬 스트림으로 요청 병렬화하기 : parallelStream()</p>
</li>
<li><p>CompletableFuture로 비동기 호출 구현하기 : CompletableFuture.supplyAsync</p>
<ul>
<li>추가로, List&lt;CompletableFuture&lt; String&gt;&gt; -&gt; List&lt; String&gt;</li>
</ul>
</li>
<li><p>개선 : CompletableFuture를 사용한 결과는 순차 방식보단 빠르지만 병렬 스트림보단 느림</p>
<ul>
<li><strong>커스텀 Executor 사용</strong></li>
<li>병렬스트림 버전에 비해 작업에 이용할 수 있는 다양한 <strong>Executor</strong>를 지정할 수 있다는 장점이 있다. Executor로 스레드 풀 크기를 조정할수도 있고 애플리케이션에 맞는 최적화 설정이 가능(스레드 수, 큐 정책 등 제어 가능 → 성능 향상)</li>
</ul>
</li>
</ol>
<pre><code class="language-java">List&lt;CompletableFuture&lt;String&gt;&gt; priceFutures = shops.stream()
    .map(shop -&gt; CompletableFuture.supplyAsync(() -&gt; shop.getPrice(product), executor))
    .map(future -&gt; future.thenApply(Quote::parse))
    .map(future -&gt; future.thenCompose(
        quote -&gt; CompletableFuture.supplyAsync(() -&gt; Discount.applyDiscount(quote), executor)))
    .collect(toList());

return priceFutures.stream()
    .map(CompletableFuture::join)
    .collect(toList());</code></pre>
<br/>


<p>중요</p>
<ul>
<li>두 map 연산을 하나의 스트림 처리 파이프라인이 아닌, 두 개의 파이프라인으로 처리함!<ul>
<li>스트림 연산은 게으른 특성이 있으므로 하나의 파이프라인으로 처리했다면 모든 가격 정보 요청 동작이 동기적, 순차적으로 이루어지게 된다.
  =&gt; <mark style="background: #FF5582A6;"><code>map(...).map(join)</code> 형태로 사용하면 순차 처리됨</mark></li>
<li>파이프라인 2단계 : 먼저 <code>CompletableFuture</code>로 비동기 작업 생성 → 나중에 <code>join()</code>으로 결과 모음</li>
</ul>
</li>
</ul>
<pre><code class="language-json">// 잘못된 접근
shop1.getPrice() → 기다림(join)
shop2.getPrice() → 기다림(join)
shop3.getPrice() → 기다림(join)

// 올바른(병렬) 처리
shop1.getPrice()
shop2.getPrice()
shop3.getPrice()
  ↓
  기다림(join)
  ↓
결과 모음
</code></pre>
<br/>

<h2 id="비동기-작업-파이프라인-만들기">비동기 작업 파이프라인 만들기</h2>
<blockquote>
<p>CompletableFuture을 이용한 예제
<strong>하나의 주제(가격 정보 조회 및 처리)</strong> 안에서 <strong>점점 복잡한 기능</strong>을 단계적으로 확장</p>
</blockquote>
<p>1단계: 기본적인 가격 조회 (동기, 순차적 처리)</p>
<ul>
<li>모든 작업이 <strong>순차적이고 동기적</strong>으로 처리됨 → <strong>느림</strong></li>
<li>상점 수가 늘어나면 처리 시간도 늘어남 (<code>n * (가격 + 할인)</code> 시간)</li>
</ul>
<p>2단계: <code>CompletableFuture</code>를 이용한 비동기 처리 (병렬화)</p>
<ul>
<li>① 가격 정보 <strong>비동기</strong>로 요청</li>
<li>② 응답 문자열을 <code>Quote</code>로 파싱</li>
<li>③ <strong>비동기로 할인 서비스</strong> 호출</li>
<li>결과를 <code>join()</code>으로 한꺼번에 모음<br>  ✅ 훨씬 빠르고 효율적인 병렬 처리</li>
</ul>
<p>3단계: 독립적인 두 작업 합치기 (<code>thenCombine</code>)</p>
<ul>
<li><code>getPrice</code>와 <code>getRate</code>는 <strong>서로 독립적이므로 동시에 실행 가능</strong></li>
<li><code>thenCombine</code>으로 서로 <strong>독립적인 비동기 작업</strong>을 동시에 실행한 두 결과를 <strong>결합하여 새로운 결과 생성</strong></li>
<li>비동기 API를 효과적으로 조합하는 방식</li>
</ul>
<p>4단계: 타임아웃 처리 (<code>orTimeout</code>, <code>completeOnTimeout</code>)</p>
<ul>
<li><code>orTimeout</code>: 지정 시간 초과 시 예외 발생</li>
<li><code>completeOnTimeout</code>: <strong>시간 초과 시 기본값으로 자동 완료</strong><br>  ✅ 안정적인 비동기 처리를 위한 <strong>실전 전략</strong></li>
</ul>
<pre><code class="language-java">// # 1단계
shops.stream()
     .map(shop -&gt; shop.getPrice(product))
     .map(Quote::parse)
     .map(Discount::applyDiscount)
     .collect(toList());

// # 2단계
List&lt;CompletableFuture&lt;String&gt;&gt; priceFutures =
  shops.stream()
       .map(shop -&gt; CompletableFuture.supplyAsync(() -&gt; shop.getPrice(product), executor)) // ①
       .map(future -&gt; future.thenApply(Quote::parse)) // ②
       .map(future -&gt; future.thenCompose(quote -&gt; 
             CompletableFuture.supplyAsync(() -&gt; Discount.applyDiscount(quote), executor))) // ③
       .collect(toList());

return priceFutures.stream()
                   .map(CompletableFuture::join) // 결과 모으기
                   .collect(toList());

// # 3단계
Future&lt;Double&gt; futurePriceInUSD =
  CompletableFuture.supplyAsync(() -&gt; shop.getPrice(product)) // 가격 요청
                   .thenCombine(
                     CompletableFuture.supplyAsync(() -&gt; exchangeService.getRate(EUR, USD)), // 환율 요청
                     (price, rate) -&gt; price * rate); // 결과 조합

// # 4단계
// 실패할 수 있는 네트워크 호출에 타임아웃 설정
CompletableFuture.supplyAsync(() -&gt; shop.getPrice(product))
  .thenCombine(CompletableFuture.supplyAsync(() -&gt; exchangeService.getRate(EUR, USD))
    .completeOnTimeout(DEFAULT_RATE, 1, TimeUnit.SECONDS), // 환율 기본값 사용
    (price, rate) -&gt; price * rate)
  .orTimeout(3, TimeUnit.SECONDS); // 전체 작업 제한 시간
</code></pre>
<br/>


<h2 id="completablefuture의-종료에-대응하는-방법">CompletableFuture의 종료에 대응하는 방법</h2>
<ul>
<li>16.4 예제는 <code>CompletableFuture</code>로 비동기 파이프라인을 구성하여 병렬 처리 성능을 향상.
  =&gt; 이 흐름을 <strong>스트림으로 리팩터링하여 결과가 준비되자마자 즉시 처리하는 방식</strong>으로 발전 가능</li>
<li>주요 초점: <strong>&quot;모든 결과 기다리기 vs 하나라도 처리하기&quot;</strong> 전략을 유연하게 다룰 수 있도록 개선.</li>
</ul>
<p>모든 결과 vs 하나의 결과</p>
<ul>
<li><code>CompletableFuture.allOf()</code>: 모든 작업 완료 후 실행</li>
<li><code>CompletableFuture.anyOf()</code>: 하나라도 완료되면 즉시 실행 → 반응형 UX, 알림 처리 등<ul>
<li>실시간 반응형 UI 또는 알림 서비스 등에 유용.<pre><code class="language-java">CompletableFuture.anyOf(futures).thenAccept(System.out::println);</code></pre>
</li>
</ul>
</li>
</ul>
<br/>

<h1 id="chapter-17-리액티브-프로그래밍">CHAPTER 17 리액티브 프로그래밍</h1>
<ul>
<li>애플리케이션 수준, 시스템 수준의 리액티브 프로그래밍</li>
<li>리액티브 스트림, 자바 9 플로 API를 사용한 예제 코드</li>
<li>널리 사용되는 리액티브 라이브러리 RxJava 소개</li>
</ul>
<p>요약</p>
<table>
<thead>
<tr>
<th>구성요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Publisher</code></td>
<td>데이터를 발행하는 주체 (뉴스 발행사)</td>
</tr>
<tr>
<td><code>Subscriber</code></td>
<td>데이터를 소비하는 주체 (뉴스 구독자)</td>
</tr>
<tr>
<td><code>Subscription</code></td>
<td>데이터 흐름 조절 (얼마나 받을지 설정)</td>
</tr>
<tr>
<td><code>Processor</code></td>
<td>데이터 가공 (중간 필터 역할)</td>
</tr>
</tbody></table>
<br/>


<br/>

<h2 id="리액티브-프로그래밍-패러다임의-중요성-왜-필요할까">리액티브 프로그래밍 패러다임의 중요성: 왜 필요할까?</h2>
<p>변화한 환경</p>
<ul>
<li><strong>빅데이터 시대</strong>: 데이터는 페타바이트(PB) 단위로 폭증하고 있으며, 계속해서 쏟아지고 있음.</li>
<li><strong>다양한 장치</strong>: 모바일, IoT, 클라우드 서버 등 다양한 환경에서 데이터가 생성되고 수신됨.</li>
<li><strong>사용자 기대치</strong>: 사용자는 24시간 언제든지 밀리초 단위의 빠른 응답을 원함.</li>
</ul>
<p>➡️ 이처럼 <mark style="background: #FF5582A6;">빠르고 안정적인 처리</mark>를 위해 <mark style="background: #FF5582A6;">비동기적으로 데이터 스트림</mark>을 다루는 <strong>리액티브 프로그래밍</strong>이 중요해짐.</p>
<br/>

<h2 id="리액티브-매니패스토">리액티브 매니패스토</h2>
<ul>
<li>소프트웨어 아키텍쳐에 대한 선언문으로 Reactive System의 특성을 강조한 가이드라인</li>
</ul>
<p>리액티브 시스템의 핵심 원칙 4가지</p>
<table>
<thead>
<tr>
<th>원칙</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>반응성 (Responsive)</td>
<td>항상 일정하고 예측 가능한 속도로 반응</td>
</tr>
<tr>
<td>회복성 (Resilient)</td>
<td>장애가 발생해도 반응성을 유지하며 복구 가능</td>
</tr>
<tr>
<td>탄력성 (Elastic)</td>
<td>트래픽이 몰리면 시스템을 자동으로 확장</td>
</tr>
<tr>
<td>메시지 주도 (Message-driven)</td>
<td>비동기 메시지를 사용하여 컴포넌트 간의 느슨한 결합 유지</td>
</tr>
</tbody></table>
<h4 id="애플리케이션-vs-시스템-수준-리액티브"><strong>애플리케이션 vs 시스템 수준 리액티브</strong></h4>
<table>
<thead>
<tr>
<th>구분</th>
<th>애플리케이션 수준</th>
<th>시스템 수준</th>
</tr>
</thead>
<tbody><tr>
<td><strong>초점</strong></td>
<td>단일 앱의 비동기 처리</td>
<td>분산 시스템 전체 아키텍처</td>
</tr>
<tr>
<td><strong>주요 기술</strong></td>
<td>Reactor, RxJava</td>
<td>카프카, Akka, 쿠버네티스</td>
</tr>
<tr>
<td><strong>예시</strong></td>
<td>웹 서버의 논블로킹 IO</td>
<td>마이크로서비스 간 이벤트 드리븐 통신</td>
</tr>
</tbody></table>
<br/>

<h3 id="애플리케이션-수준의-리액티브">애플리케이션 수준의 리액티브</h3>
<ul>
<li><strong>이벤트 기반 처리</strong>: 이벤트 루프가 데이터를 감지하고 이벤트를 비동기로 처리</li>
<li><strong>멀티코어 활용 극대화</strong>: 스레드를 효율적으로 공유하여 처리량 증가</li>
<li>전제조건 - <strong>블로킹 금지</strong>: 데이터베이스, 네트워크 등 모든 I/O는 비동기 처리 필수</li>
<li><strong>콜백, 퓨처, 액터</strong> 등으로 구성된 이벤트 시스템을 활용</li>
</ul>
<br/>

<h3 id="시스템-수준의-리액티브">시스템 수준의 리액티브</h3>
<ul>
<li><strong>애플리케이션 조합</strong>: 여러 리액티브 앱이 하나의 안정적인 플랫폼으로 통합</li>
<li><strong>고립성 유지</strong>: 한 컴포넌트 장애가 전체 시스템에 영향을 미치지 않음</li>
<li><strong>위치 투명성</strong>: 서비스 간 통신은 위치에 구애받지 않음</li>
<li><strong>수평 확장 가능</strong>: 시스템 복제 및 확장이 용이함</li>
</ul>
<br/>

<h2 id="리액티브-스트림과-플로-api">리액티브 스트림과 플로 API</h2>
<blockquote>
<p><strong>리액티브 프로그래밍</strong>은 리액티브 스트림을 사용하는 프로그래밍이다. <strong>리액티브 스트림</strong>은 잠재적으로 무한의 비동기 데이터를 순서대로 그리고 블록하지 않는 역압력을 전제해 처리하는 표준 기술이다.</p>
</blockquote>
<h3 id="📌-리액티브-스트림의-특징">📌 리액티브 스트림의 특징</h3>
<ul>
<li><strong>비동기 데이터 처리</strong></li>
<li><strong>무한 스트림 가능</strong></li>
<li><strong>역압력 (Backpressure)</strong>: 소비자가 감당할 수 없는 속도로 데이터를 받지 않도록 조절</li>
</ul>
<h3 id="🧩-javautilconcurrentflow-api-구성">🧩 <code>java.util.concurrent.Flow</code> API 구성</h3>
<table>
<thead>
<tr>
<th>인터페이스</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Publisher&lt;T&gt;</code></td>
<td>구독자에게 데이터를 발행</td>
</tr>
<tr>
<td><code>Subscriber&lt;T&gt;</code></td>
<td>발행자로부터 데이터를 수신</td>
</tr>
<tr>
<td><code>Subscription</code></td>
<td>데이터 요청/취소를 관리</td>
</tr>
<tr>
<td><code>Processor&lt;T, R&gt;</code></td>
<td>데이터 변환 중개자 (Publisher + Subscriber 역할 모두 수행)</td>
</tr>
</tbody></table>
<br/>

<p>흐름 요약</p>
<ul>
<li><code>Subscriber</code>는 <code>Publisher</code>에 구독(<code>subscribe</code>)을 요청함</li>
<li><code>Publisher</code>는 <code>onSubscribe()</code>로 <code>Subscription</code>을 전달함</li>
<li><code>Subscriber</code>는 <code>request(n)</code>으로 몇 개의 데이터를 받을지 요청함</li>
<li><code>Publisher</code>는 최대 <code>n</code>개의 데이터를 <code>onNext()</code>로 전달</li>
<li>완료되면 <code>onComplete()</code>, 에러 나면 <code>onError()</code></li>
</ul>
<pre><code class="language-java">
//Publisher가 발행한 리스너로 Subscriber에 등록할 수 있다.
@FunctionalInterface
public interface Publisher&lt;T&gt; {
  void subscribe(Subscriber&lt;? super T&gt; s);
}

//Publisher가 관련 이벤트를 발행할 때 호출할 수 있도록 콜백 메서드 네 개를 정의
public interface Subscriber&lt;T&gt; {
  void onSubscribe(Subscription s);
  void onNext(T t);
  void onError(Throwable t);
  void onComplete();
}

// Subscription은 Publisher와 Subscriber 사이의 제어 흐름, 역압력을 관리
public interface Subscription {
  void request(long n); //publisher에게 이벤트를 처리할 준비가 되었음을 알림
  void cancel(); //publisher에게 이벤트를 받지 않음을 통지
}

//리액티브 스트림에서 처리하는 이벤트의 변환단계를 나타냄
//에러나 Subscription 취소 신호 등을 전파
public interface Processor&lt;T, R&gt; extends Subscriber&lt;T&gt;, Publisher&lt;R&gt; { }
</code></pre>
<ul>
<li><code>Processor</code>는 중간 처리자로써, 예를 들어 온도를 섭씨로 바꾸는 등의 변환 작업 수행</li>
<li><code>Subscriber</code>이자 <code>Publisher</code>이므로 중간에서 가공 가능</li>
</ul>
<pre><code class="language-java">public class SimpleReactiveExample {

    public static void main(String[] args) throws InterruptedException {
        // 1. Publisher 생성
        SubmissionPublisher&lt;String&gt; publisher = new SubmissionPublisher&lt;&gt;();

        // 2. Subscriber 생성
        Flow.Subscriber&lt;String&gt; subscriber = new Flow.Subscriber&lt;&gt;() {
            private Flow.Subscription subscription;

            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                this.subscription = subscription;
                System.out.println(&quot;✔ 구독 시작!&quot;);
                subscription.request(1); // 먼저 1개 요청
            }

            @Override
            public void onNext(String item) {
                System.out.println(&quot;📦 받은 데이터: &quot; + item);
                subscription.request(1); // 처리 후 다음 1개 요청
            }

            @Override
            public void onError(Throwable throwable) {
                System.err.println(&quot;❌ 에러 발생: &quot; + throwable.getMessage());
            }

            @Override
            public void onComplete() {
                System.out.println(&quot;✅ 모든 데이터 수신 완료!&quot;);
            }
        };

        // 3. Subscriber를 Publisher에 등록
        publisher.subscribe(subscriber);

        // 4. 데이터 발행
        publisher.submit(&quot;Hello&quot;);
        publisher.submit(&quot;Reactive&quot;);
        publisher.submit(&quot;Streams&quot;);

        // 5. 마무리
        publisher.close(); // onComplete() 호출됨
        Thread.sleep(1000); // 비동기이므로 대기
    }
}

// result
✔ 구독 시작!
📦 받은 데이터: Hello
📦 받은 데이터: Reactive
📦 받은 데이터: Streams
✅ 모든 데이터 수신 완료!

- `onSubscribe()`에서 구독을 시작하고 첫 데이터를 요청
- `onNext()`에서 데이터 수신 후 다시 1개 요청 → 역압력(backpressure)
- `close()`를 호출했기 때문에 모든 데이터 전송 후 `onComplete()` 호출됨
</code></pre>
<p>자바는 왜 Flow API 구현체를 직접 제공하지 않을까?</p>
<ul>
<li>여러 라이브러리의 표준화를 위한 인터페이스로 활용</li>
<li>실제 구현은 외부 리액티브 라이브러리에 맡김 → 유연성 확보</li>
</ul>
<br/>

<h2 id="리액티브-라이브러리-rxjava">리액티브 라이브러리 RxJava</h2>
<h3 id="rxjava의-두-가지-핵심-클래스">RxJava의 두 가지 핵심 클래스</h3>
<table>
<thead>
<tr>
<th>클래스</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Flowable</code></td>
<td>역압력 지원</td>
</tr>
<tr>
<td><code>Observable</code></td>
<td>역압력 미지원 (GUI 이벤트 등 가벼운 스트림에 적합)</td>
</tr>
</tbody></table>
<h3 id="권장-사용-가이드">권장 사용 가이드</h3>
<ul>
<li><strong><code>Flowable</code></strong>: 데이터가 많고(대량 데이터 처리), 처리 속도가 불균형할 때<pre><code class="language-java">  Flowable.range(1, 1000000)
           .onBackpressureDrop()
           .subscribe(System.out::println);</code></pre>
</li>
<li><strong><code>Observable</code></strong>: 클릭, 마우스 이동 등 빠른 응답이 필요한 GUI 이벤트 처리<pre><code class="language-java">  Observable.just(&quot;Hello&quot;, &quot;World&quot;)
     .subscribe(System.out::println);</code></pre>
</li>
</ul>
<br/>

<h2 id="추가-그럼에도-리액티브-프로그래밍이-널리-사용되지-않는-이유">추가: 그럼에도 리액티브 프로그래밍이 널리 사용되지 않는 이유</h2>
<ol>
<li><strong>학습 곡선이 높고, 코드 가독성이 떨어짐</strong></li>
</ol>
<ul>
<li>리액티브 프로그래밍은 전통적인 명령형 방식에 익숙한 개발자에게 <strong>익숙하지 않은 추상화</strong>를 요구함</li>
<li><code>map()</code>, <code>flatMap()</code>, <code>subscribe()</code> 등의 체이닝 구조는 <strong>코드를 추적하고 디버깅하기 어려움</strong></li>
<li>특히 <code>onError</code>, <code>onComplete</code> 등 다양한 상태 처리를 명확히 이해해야 함</li>
</ul>
<pre><code class="language-java">Flowable.fromPublisher(publisher)
    .map(data -&gt; transform(data))
    .flatMap(result -&gt; process(result))
    .subscribe(System.out::println);
// → 이런 코드는 처음 접하는 사람에게는 “무슨 일이 일어나는지” 직관적이지 않음.</code></pre>
<hr>
<ol start="2">
<li><strong>디버깅과 에러 추적이 어려움</strong></li>
</ol>
<ul>
<li><strong>콜 스택이 비동기적으로 분리되기 때문에</strong>, 예외 발생 시 어디서 문제가 생겼는지 추적이 어려움</li>
<li><code>NullPointerException</code>, <code>TimeoutException</code> 등이 체이닝 중간에서 발생하면 <strong>로그만 봐서는 원인 파악이 힘듦</strong>
💡 해결하려면 <code>.doOnError()</code>, <code>.onErrorResume()</code> 등을 잘 다뤄야 함</li>
</ul>
<hr>
<ol start="3">
<li><strong>블로킹 I/O와의 궁합 문제</strong></li>
</ol>
<ul>
<li><mark style="background: #FF5582A6;">기존 레거시 시스템(예: JDBC, JPA, 외부 REST API 등)은 대부분 블로킹 API</mark></li>
<li>리액티브 시스템은 <strong>블로킹을 허용하지 않음</strong>, <mark style="background: #FF5582A6;">따라서 중간에 하나라도 블로킹 호출이 있으면</mark> <strong>전체 리액티브 체인이 무의미</strong></li>
<li>완전한 리액티브 시스템을 만들려면 DB, WebClient, 메시지 큐 등 모든 요소가 비동기 API여야 함 → <strong>도입 장벽이 큼</strong></li>
</ul>
<hr>
<ol start="4">
<li><strong>개발 속도 저하 및 생산성 감소 가능성</strong></li>
</ol>
<ul>
<li>작은 비즈니스 로직을 구현하는 데도 체이닝과 콜백 지옥에 빠질 수 있음</li>
<li>팀 전체가 리액티브에 익숙하지 않다면, <strong>프로젝트의 생산성과 유지보수성이 떨어질 수 있음</strong></li>
<li>코드 리뷰/테스트 시에도 진입장벽이 생김</li>
</ul>
<hr>
<ol start="5">
<li><strong>운영 및 모니터링 도구 부족</strong></li>
</ol>
<ul>
<li>전통적인 APM 툴이나 로그 분석 도구는 <strong>리액티브 컨텍스트 추적에 한계가 있음</strong></li>
<li>예: 스레드 기반 트랜잭션 추적이 불가능 → <strong>Context Propagation</strong>을 위한 추가 설정이 필요함</li>
</ul>
<hr>
<h3 id="그럼에도-불구하고-리액티브가-유리한-상황">그럼에도 불구하고 리액티브가 유리한 상황</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th>리액티브 유리</th>
</tr>
</thead>
<tbody><tr>
<td>실시간 데이터 스트리밍</td>
<td>예: IoT 센서, 주식 데이터</td>
</tr>
<tr>
<td>수천 개의 동시 연결</td>
<td>예: 채팅 앱, SSE, WebSocket</td>
</tr>
<tr>
<td>외부 API 호출이 많고 비동기 처리 가능</td>
<td>예: API 게이트웨이, API 집계 서버</td>
</tr>
<tr>
<td>마이크로서비스 간 호출</td>
<td>메시지 기반 처리와 궁합이 좋음</td>
</tr>
<tr>
<td>#### <strong>리액티브의 미래</strong> = 현대 소프트웨어 아키텍처의 필수 사고방식 중 하나</td>
<td></td>
</tr>
<tr>
<td>- <strong>클라우드 네이티브</strong>: 쿠버네티스 + 서비스 메시 조합</td>
<td></td>
</tr>
<tr>
<td>- <strong>실시간 데이터 파이프라인</strong>: 플링크, 스파크 스트리밍</td>
<td></td>
</tr>
<tr>
<td>- <strong>IoT</strong>: 수백만 디바이스의 실시간 데이터 처리</td>
<td></td>
</tr>
</tbody></table>
<h3 id="가상-스레드가-어떻게-리액티브-프로그래밍을-대체하는가">가상 스레드가 어떻게 리액티브 프로그래밍을 대체하는가</h3>
<blockquote>
<p>리액티브 프로그래밍의 복잡성을 줄이면서도 높은 처리량(throughput)을 달성할 수 있는 대안
예전 자바 스레드는 &quot;실제 운영체제 스레드&quot;와 1:1로 매칭됬으나, 가상 스레드는 JVM이 직접 관리하는 초경량 스레드로서 1만 개의 가상 스레드를 만들어도 실제 운영체제는 10~20개 정도의 진짜 스레드만 관리</p>
</blockquote>
<ol>
<li><p>기존 리액티브 프로그래밍의 문제점</p>
<ul>
<li>복잡한 코드 구조(콜백 지옥(callback hell)과 체인형 API)</li>
<li>디버깅 어려움</li>
<li>스레드 공유 모델(스레드 로컬 변수 사용이 제한)</li>
</ul>
</li>
<li><p>가상 스레드의 동작 원리
가상 스레드는 경량 스레드로, JVM이 직접 관리하며 OS 스레드(캐리어 스레드)에 매핑된다.</p>
<ul>
<li>블로킹의 경량화: I/O 작업 중 블로킹이 발생하면 가상 스레드가 자동으로 일시 중단되고, 캐리어 스레드는 다른 가상 스레드를 실행. 이를 통해 OS 스레드 수십 개로 수만 개의 가상 스레드를 효율적으로 처리 가능. </li>
<li>스레드-per-request 모델 복원: 기존처럼 각 요청마다 전용 스레드를 할당하되, 블로킹 비용이 극히 낮아져 동시성 문제 없이 확장 가능</li>
</ul>
</li>
<li><p>리액티브 프로그래밍 대체 가능성</p>
</li>
</ol>
<ul>
<li>가상 스레드는 리액티브 프로그래밍의 핵심 목표인 높은 처리량을 더 간단한 방식으로 달성한다.<ul>
<li>동기식 코드 작성 가능: 블로킹 코드를 그대로 사용해도 내부적으로 논블로킹으로 동작. 예를 들어, synchronized 블록이나 Thread.sleep()을 사용해도 캐리어 스레드는 다른 작업을 처리합니다.</li>
<li>Structured Concurrency: ExecutorService 대신 StructuredTaskScope를 사용해 관련 작업을 하나의 단위로 묶어 예외 처리 및 취소를 단순화</li>
<li>Scoped Values: 가상 스레드 간 안전한 데이터 공유를 위해 ThreadLocal 대신 ScopedValue를 사용한다.</li>
</ul>
</li>
</ul>
<ol start="4">
<li><p>성능 비교</p>
<ul>
<li>I/O 집약적 작업: 가상 스레드는 리액티브 프로그래밍과 유사한 처리량을 제공하지만, 코드 복잡성은 크게 낮습니다. 실제 벤치마크에서 리액티브가 10~20% 더 높은 성능을 보이지만, 대부분의 애플리케이션에서는 차이가 미미하다.</li>
<li>CPU 집약적 작업: 가상 스레드는 성능 향상을 목표로 하지 않으며, 이 경우 여전히 플랫폼 스레드 사용이 권장된다</li>
</ul>
</li>
<li><p>남은 리액티브 프로그래밍의 활용처</p>
<ul>
<li>극한의 성능 요구: 초고속 트래픽 처리나 역압력 관리가 필요한 시스템(예: 실시간 스트리밍)에서는 리액티브가 유리할 수 있다.</li>
<li>기존 리액티브 코드베이스: Spring WebFlux나 RxJava로 구축된 시스템은 당분간 유지될 것</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 4장]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-4%EC%9E%A5</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-4%EC%9E%A5</guid>
            <pubDate>Thu, 08 May 2025 05:51:41 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-11-null-대신-optional-클래스">CHAPTER 11 null 대신 Optional 클래스</h1>
<blockquote>
<p>NPE를 방지하고 더 명시적이고 안전한 API를 설계하기 위해 등장한 것이 바로 <code>java.util.Optional</code> 클래스</p>
</blockquote>
<h3 id="왜-null을-멀리해야-할까">왜 null을 멀리해야 할까?</h3>
<ul>
<li><code>null</code>은 참조형 변수에 값이 없다는 것을 의미하지만, <strong>명확한 의미 전달이 어렵고 예외 발생 가능성</strong>이 크다.</li>
<li>특히 연쇄적인 호출이 많은 도메인 모델에서는 <code>if</code> 조건문이 반복되는 &quot;if문 지옥&quot;으로...</li>
<li><code>data?.property</code>처럼 ? (안전 내비게이션 연산자)로 안전하게 접근하는 방법이 없을까?</li>
</ul>
<p>→ 그래서 등장한 것이 선택형값 <code>Optional&lt;T&gt;</code>. Optional은 &quot;값이 있을 수도 있고, 없을 수도 있는 선택형 값을 캡슐화&quot;한 클래스</p>
<h3 id="optional-특징">Optional 특징</h3>
<ul>
<li><p><mark style="background: #FF5582A6;">최대 한 개의 값을 가지는 컬렉션</mark></p>
</li>
<li><p>값이 없을 때 Optional 반환하는 Optional.empty 는 Optional의 특별한 싱글톤 인스턴스 반환하는 정적 팩토리 메서드</p>
</li>
<li><p>코드 간결화</p>
<pre><code class="language-java">  Optional&lt;Insurance&gt; optInsurance = Optional.ofNullable(insurance);

  // null 체크 없이 이름 추출
  Optional&lt;String&gt; name = optInsurance.map(Insurance::getName);</code></pre>
</li>
</ul>
<h3 id="optional-객체-만들기">Optional 객체 만들기</h3>
<pre><code class="language-java">// 빈 Optional
Optional&lt;Car&gt; optCar = Optional.empty()

// null이 아닌 값으로 Optional 만들기
Optional&lt;Car&gt; optCar = Optional.of(car); // car가 null이라면 즉시 NPE이 발생한다.

// null값으로 Optional 만들기
Optional&lt;Car&gt; optCar = Optional.ofNullable(car); // car가 null이면 빈 Optional 객체가 반환된다.
</code></pre>
<h3 id="맵으로-optional의-값을-추출하고-변환하기">맵으로 Optional의 값을 추출하고 변환하기</h3>
<blockquote>
<p>보통 객체의 정보를 추출할 때는 Optional을 사용할 때가 많다.
null을 확인하느라 조건 분기문을 추가해서 코드를 쉽게 이해할 수 있는 코드 가능</p>
</blockquote>
<ul>
<li>Map 반복 : 하지만 일반 map 연속되면 Optional&lt;&gt; 반복이므로 중첩</li>
<li>Flatmap : 중첩된 Optional 문제를 flatMap으로 해결<ul>
<li>flatMap 연산으로 Optional을 평준화</li>
<li>평준화: 두 Optional을 합치는 기능을 수행하면서 둘 중 하나라도 null이면 빈 Optional을 생성하는 연산. flatMap을 빈 Optional에 호출하면 아무 일도 일어나지 않고 그대로 반환된다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">// map()은 Optional에 값이 있을 때만 실행
// insurance가 null이면 map()은 실행되지 않고, 그대로 빈 Optional을 반환
Optional&lt;Insurance&gt; optInsurance = Optional.ofNullable(insurance);
Optional&lt;String&gt; name = optInsurance.map(Insurance::getName); 

// 개선 시도 - 실패
Optional&lt;String&gt; name = 
        optPerson.map(Person::getCar)  // Optional&lt;Car&gt;
             .map(Car::getInsurance)   // Optional&lt;Optional&lt;Insurance&gt;&gt;
             .map(Insurance::getName); // 컴파일 오류!

// flatMap() 사용 : Optional&lt;Optional&lt;Car&gt;&gt; → Optional&lt;Car&gt;
// 값이 없으면 자동으로 빈 Optional
public String getCarInsuranceName(Optional&lt;Person&gt; person) {
    return person.flatMap(Person::getCar)
                 .flatMap(Car::getInsurance)
                 .map(Insurance::getName)
                 .orElse(&quot;Unknown&quot;);
}
</code></pre>
<br/>

<h3 id="optional을-도메인-모델-필드로-사용하면-안-되는-이유">Optional을 도메인 모델 필드로 사용하면 안 되는 이유</h3>
<ul>
<li>도메인 모델 클래스(예: <code>Person</code>, <code>Car</code>, <code>Insurance</code>)에서 <code>Optional</code>을 필드로 직접 사용하는 것은 직렬화(serialization) 관점에서 문제가 될 수 있다.</li>
<li>why? <ul>
<li><code>Optional</code>은 Java 8에서 *&quot;메서드 반환값에서 null을 안전하게 처리하기 위한 용도&quot;*로 만듬</li>
<li>Optional는 필드 형식으로 사용될 것을 가정하지 않았으므로 Serializable 인터페이스를 구현 x</li>
<li>따라서 도메인 모델에 Optional을 사용한다면 직렬화 모델을 사용하는 도구나 프레임워크에서 문제가 생길 수 있다. </li>
</ul>
</li>
<li>방법: 만약 직렬화 모델이 필요하다면 변수는 일반 객체로 두되, Optional로 값을 반환받을 수 있는 메서드를 추가하는 방식이 권장된다.<pre><code class="language-java">public class Person {
  private Car car;
  public Optional&lt;Car&gt; getCarAsOptional() {
      return Optional.ofNullable(car);
  }
}</code></pre>
</li>
</ul>
<h3 id="optional--stream-조합-활용">Optional + Stream 조합 활용</h3>
<ul>
<li><code>Optional.stream()</code>이 추가되어 Stream과 결합이 쉽다.<pre><code class="language-java">  // Optional::stream은 값이 있으면 1개의 스트림으로, 없으면 빈 스트림으로 바꿔준다.
  public Set&lt;String&gt; getCarInsuranceNames(List&lt;Person&gt; persons) {
      return persons.stream()
                    .map(Person::getCar)
                    .map(optCar -&gt; optCar.flatMap(Car::getInsurance))
                    .map(optIns -&gt; optIns.map(Insurance::getName))
                    .flatMap(Optional::stream)  // Optional&lt;String&gt; → Stream&lt;String&gt;
                    .collect(Collectors.toSet());
  }</code></pre>
</li>
</ul>
<h3 id="optional에-저장된-값을-확인하는-방법">Optional에 저장된 값을 확인하는 방법</h3>
<pre><code class="language-java">// 피해야할 방법
optional.get(); // 값이 없으면 예외 발생!

// 안전한 방법
- orElse(&quot;기본값&quot;): 값이 없을 때 기본값 리턴
- orElseGet(() -&gt; &quot;기본값&quot;): 지연 계산 (Supplier 사용)
- orElseThrow(() -&gt; new IllegalArgumentException()): 직접 예외 던지기
- ifPresent(value -&gt; {...}): 값이 있을 때만 실행
- ifPresentOrElse(action, emptyAction): Java 9부터 제공
</code></pre>
<h3 id="optional에서-filter-사용해서-특정-값-거르기">Optional에서 filter 사용해서 특정 값 거르기</h3>
<pre><code class="language-java">// filter() 조건을 만족하지 않으면 빈 Optional을 반환
Optional&lt;Insurance&gt; optInsurance = ...;
optInsurance
    .filter(ins -&gt; &quot;CambridgeInsurance&quot;.equals(ins.getName()))
    .ifPresent(ins -&gt; System.out.println(&quot;ok&quot;));
</code></pre>
<br/>

<h3 id="optional을-사용한-실용-예제">Optional을 사용한 실용 예제</h3>
<ul>
<li>잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기</li>
<li>예외와 Optional 클래스<ul>
<li>예외가 발생할 가능성이 있는 연산을 Optional로 감싸면 훨씬 깔끔한 API를 제공 가능<pre><code class="language-java">    // ex) 정수로 변환할 수 없는 문자열 문제를 빈 Optional로 해결 가능
    public static Optional&lt;Integer&gt; stringToInt(String s) {
        try {
            return Optional.of(Integer.parseInt(s));
        } catch (NumberFormatException e) {
                return Optional.empty();
        }
    }</code></pre>
</li>
</ul>
</li>
<li>기본형 Optional 사용 자제<ul>
<li><strong>성능 개선 효과 미미</strong> : Optional은 <strong>최대 1개의 값만 포함</strong>. 단일 값 처리에서는 박싱 오버헤드가 무시할 수준</li>
<li>기능 제한 : 기본형 Optional(OptionalInt, OptionalLong 등)은 <strong><code>map</code>, <code>flatMap</code>, <code>filter</code>와 같은 고차 함수를 지원</strong> x</li>
<li>호환성 문제 : 일반 <code>Optional&lt;T&gt;</code>와 기본형 Optional은 서로 <strong>변환 메서드가 없어</strong> 혼용 시 코드 복잡도 증가</li>
</ul>
</li>
</ul>
<br/>

<h3 id="추가">추가</h3>
<h4 id="optional을-잘못-사용하는-사례">Optional을 잘못 사용하는 사례</h4>
<table>
<thead>
<tr>
<th>잘못된 패턴</th>
<th>대안</th>
</tr>
</thead>
<tbody><tr>
<td>필드에 Optional 사용</td>
<td>nullable 필드 + 게터에서 Optional 처리</td>
</tr>
<tr>
<td>Optional.of(null) 사용</td>
<td>Optional.ofNullable 사용</td>
</tr>
<tr>
<td>Optional.get() 무조건 사용</td>
<td>orElse, ifPresent 등 안전 API 사용</td>
</tr>
<tr>
<td>파라미터에 Optional 사용</td>
<td>nullable 인자 + 내부 Optional 처리</td>
</tr>
<tr>
<td>List&lt;Optional<T>&gt; 사용</td>
<td>List<T> 사용 + flatMap(Optional::stream)</td>
</tr>
<tr>
<td>직렬화 목적으로 Optional 사용</td>
<td>DTO/엔티티에는 사용하지 않기</td>
</tr>
</tbody></table>
<br/>

<h4 id="기본형-optional-왜-만듬">기본형 Optional 왜 만듬?</h4>
<ul>
<li>박싱 비용 제거</li>
<li>메모리 사용 최적화<ul>
<li>객체를 생성하지않는게 GC 부담 감소 및 <strong>더 적은 힙 메모리 사용</strong>.</li>
</ul>
</li>
<li><strong>성능 민감한 코드에서 Null-Safety 보장</strong><ul>
<li>Java의 스트림 API 등에서 <strong>기본형 스트림</strong>(<code>IntStream</code>, <code>DoubleStream</code>)을 처리할 때,<br><code>.reduce()</code>와 같은 연산 결과를 <code>OptionalInt</code>로 반환할 수 있음.</li>
<li>성능과 안전성 둘 다 확보</li>
</ul>
</li>
</ul>
<br/>

<h1 id="chapter-12-새로운-날짜와-시간-api">CHAPTER 12 새로운 날짜와 시간 API</h1>
<blockquote>
<p>기존에 에러가 많이 발생했던 날짜와 시간 관련 API
java.time 패키지는 LocalDate, LocalTime, LocalDateTime, Instant, Duration, Period 등 새로운 날짜와 시간에 관련된 클래스를 제공 =&gt; 불변 객체 =&gt; 스레드 안전성, 값 일관성</p>
</blockquote>
<h3 id="자바-8-이전의-날짜와-시간-api의-문제들">자바 8 이전의 날짜와 시간 API의 문제들</h3>
<ul>
<li>Date 클래스는 직관적이지 못하며 자체적으로 시간대 정보를 알고 있지 않다.</li>
<li>Date를 deprecated 시키고 등장한 Calendar 클래스 또한 쉽게 에러를 일으키는 설계 문제.</li>
<li>날짜와 시간을 파싱하는데 등장한 DateFormat은 Date에만 지원되었으며, 스레드에 안전 X.</li>
<li>Date와 Calendar는 모두 가변 클래스이므로 유지보수가 아주 어렵다.</li>
</ul>
<br/>

<h2 id="자바-8-이후의-날짜-시간-api">자바 8 이후의 날짜, 시간 API</h2>
<h3 id="localdate-localtime">LocalDate, LocalTime</h3>
<p>LocalDate는 시간을 제외한 날짜를 표현한 불변 객체, LocalTime은 시간을 표혀한 불변 객체</p>
<ul>
<li>정적팩토리 메서드 of로 인스턴스 생성 가능</li>
<li>TemporalField는 시간 관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스<ul>
<li>ChronoField는 TemporalField의 구현체<pre><code class="language-java">// 다음 월요일 구하기
LocalDate nextMonday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY));</code></pre>
</li>
</ul>
</li>
<li>parse 메서드에 DateTimeFormatter 전달 가능</li>
<li>LocalDateTime = LocalDate + LocalTime</li>
</ul>
<h3 id="instant">Instant</h3>
<ul>
<li>java.time.Instant 클래스에서는 기계적인 관점에서 시간을 표현</li>
</ul>
<blockquote>
<p>위 클래스들은 Temporal 인터페이스를 구현하는데, Temporal 인터페이스는 특정 시간을 모델링하는 객체의 값을 어떻게 읽고 조작할지 정의한다.</p>
</blockquote>
<h3 id="duration-period">Duration, Period</h3>
<ul>
<li>Duration 클래스를 사용하면 두 시간 객체 사이의 지속시간을 만들 수 있다</li>
<li>초와 나노초로 시간 단위를 표현함<ul>
<li>년, 월, 일로 시간을 표현할 때는 Period 클래스를 사용!</li>
</ul>
</li>
</ul>
<p>Instant과 Duration 실용 예) API 응답 시간 측정</p>
<pre><code class="language-java">Instant start = Instant.now();
// some logic
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
System.out.println(&quot;Elapsed time: &quot; + timeElapsed.toMillis() + &quot; ms&quot;);</code></pre>
<br/>

<h2 id="날짜-조정-파싱-포매팅">날짜 조정, 파싱, 포매팅</h2>
<h3 id="날짜-조정">날짜 조정</h3>
<ul>
<li>시간, 날짜 클래스 불변이나 withAttribute 메서드를 사용하면 일부 속성이 수정된 상태의 새로운 객체를 반환 가능<ul>
<li>get과 with 메서드로 Temporal 객체의 필드 값을 읽거나 수정 가능</li>
</ul>
</li>
<li>TemporalAdjusters<ul>
<li>간단한 날짜 기능이 아닌 더 복잡한 날짜 조정기능이 필요할 때</li>
</ul>
</li>
</ul>
<h3 id="날짜-파싱-및-포매팅">날짜 파싱 및 포매팅</h3>
<ul>
<li>날짜와 시간 객체 출력과 파싱<ul>
<li>java.time.format 패키지가 이를 지원한다. 가장 중요하게 알아야 할 클래스는 DateTimeFormatter이다. 정적 팩토리 메서드와 상수를 이용해서 손쉽게 포매터를 만들 수 있다</li>
</ul>
</li>
</ul>
<h3 id="다양한-시간대와-캘린더-활용-방법">다양한 시간대와 캘린더 활용 방법</h3>
<ul>
<li>ZoneId<ul>
<li>기존의 java.util.TimeZone을 대체 가능</li>
<li>불변 클래스</li>
<li>ZoneId를 이용하면 서머타임 같은 복잡한 사항이 자동으로 처리<pre><code class="language-java">ZoneId romeZone = ZoneId.of(&quot;Europe/Rome&quot;);</code></pre>
</li>
</ul>
</li>
</ul>
<br/>


<h3 id="추가-1">추가</h3>
<h4 id="instant와-localdatetime의-차이">Instant와 LocalDateTime의 차이</h4>
<ul>
<li>Instant는 UTC 기준의 절대적인 &quot;순간&quot;을 나타내므로, 시스템 간의 시간 비교와 정확한 경과 시간 측정에 매우 적합</li>
<li>LocalDateTime은 시간대 정보가 없기 때문에, 서버와 클라이언트가 다른 타임존에 있거나, 서버 자체의 타임존 설정이 바뀌면 정확한 비교가 불가능</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>Instant</th>
<th>LocalDateTime</th>
</tr>
</thead>
<tbody><tr>
<td>기준</td>
<td>UTC(협정 세계시) 기준의 타임스탬프</td>
<td>시간대 정보 없이 날짜와 시간만 표현</td>
</tr>
<tr>
<td>용도</td>
<td>시스템 간 시간 비교, 기록, 연산, DB 저장</td>
<td>사용자에게 보여주는 시간, 단일 리전 서비스, UI 표시 등</td>
</tr>
<tr>
<td>시간대 정보</td>
<td>없음 (항상 UTC)</td>
<td>없음 (로컬 시간, 타임존 불명확)</td>
</tr>
<tr>
<td>대표적 사용 예시</td>
<td>API 응답 시간 측정, 로그 타임스탬프, 데이터 직렬화</td>
<td>화면 표시, 특정 날짜/시간 입력 등</td>
</tr>
<tr>
<td>기계/사람 구분</td>
<td>기계 친화적 (정확한 순간)</td>
<td>사람 친화적 (달력/시계 개념)</td>
</tr>
<tr>
<td><br/></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h4 id="불변-객체를-왤케-강조하는가"><strong>불변 객체를 왤케 강조하는가?</strong></h4>
<ul>
<li>멀티스레드 환경에서 안전<ul>
<li>여러 스레드가 동시에 같은 객체를 읽더라도, 객체의 상태가 변하지 않으므로 동기화 작업 x 없이 안전한 공유 가능</li>
</ul>
</li>
<li>원본 변경 없이 메서드 체이닝으로 새로운 객체 생성 가능<ul>
<li>불변 객체의 메서드는 내부 상태를 바꾸지 않고, 항상 새로운 객체(복사본)를 반환 =&gt; 원본 잘못 건드림 x</li>
</ul>
</li>
</ul>
<br/>

<h1 id="chapter-13-디폴트-메서드">CHAPTER 13 디폴트 메서드</h1>
<blockquote>
<p>디폴트 메서드가 무엇이며, 어떻게 디폴트 메서드로 변화할 수 있는 API를 만들 수 있는지
실용적인 디폴트 메서드 사용 패턴과 효과적으로 디폴트 메서드를 사용하는 방법
디폴트 메서드는 API 진화와 다중 상속 문제 해결의 열쇠 이다.</p>
</blockquote>
<h3 id="배경">배경</h3>
<p>기존 문제점</p>
<ul>
<li>자바 8 이전에는 인터페이스에 메서드를 추가하면, 그것을 구현한 <strong>모든 클래스에 구현을 강제</strong>해야 했다. 이는 유지보수나 API 확장 시 매우 불편하고 위험한 요소였다.</li>
</ul>
<p>자바 8의 해결책</p>
<ul>
<li>자바 8은 다음 두 가지 방식으로 <strong>기본 구현</strong>을 인터페이스에 제공할 수 있게 했다</li>
</ul>
<ol>
<li>인터페이스 내부에 정적 메서드(static method) 사용</li>
<li>인터페이스의 기본 구현을 제공할 수 있도록 디폴트 메서드(default method) 기능 사용</li>
</ol>
<blockquote>
<p>정적 메서드 vs 디폴트 메서드</p>
</blockquote>
<ul>
<li><strong>정적 메서드</strong>는 유틸리티/헬퍼 메서드 제공, 객체 생성 없이 기능 제공. override 불가</li>
<li><strong>디폴트 메서드</strong>는 구현체에 &quot;공통 동작의 기본값&quot;을 제공하고 싶을 때. 구현 클래스에서 override 가능</li>
</ul>
<p>결과적으로 기존 인터페이스를 구현하는 클래스는 자동으로 인터페이스에 추가된 새로운 메서드의 디폴트 메서드를 상속받게 된다. 이렇게 하면 기존의 코드 구현을 바꾸도록 강요하지 않으면서도 인터페이스를 바꿀 수 있다.</p>
<br/>

<h3 id="디폴트-메서드">디폴트 메서드</h3>
<ul>
<li>디폴트 메서드는 <code>default</code> 키워드를 사용하여 <strong>인터페이스 내부에 구현체가 포함된 메서드</strong>. </li>
<li>이제 인터페이스는 자신을 구현하는 클래스에서 메서드를 구현하지 않을 수 있는 새로운 메서드 시그니처를 제공한다. </li>
<li>덕분에 다음과 같은 특징을 갖는다<ul>
<li><strong>소스 호환성</strong> 유지<br>  → 인터페이스에 새로운 메서드를 추가해도 기존 구현체는 영향을 받지 않음<ul>
<li><strong>바이너리 호환성</strong><br>→ 컴파일된 클래스 파일을 그대로 사용해도 문제 없음</li>
<li><strong>동작 호환성</strong><br>→ 호출된 경우에만 디폴트 메서드의 동작이 적용됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>


<h4 id="디폴트-메서드-vs-추상-클래스">디폴트 메서드 vs 추상 클래스</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>추상 클래스</th>
<th>인터페이스 (디폴트 메서드)</th>
</tr>
</thead>
<tbody><tr>
<td>다중 상속 지원</td>
<td>❌ 불가능</td>
<td>✅ 가능</td>
</tr>
<tr>
<td>인스턴스 변수 사용</td>
<td>✅ 가능(공통 상태)</td>
<td>❌ 불가능 (상수만 가능)</td>
</tr>
<tr>
<td>생성자 사용</td>
<td>✅ 가능</td>
<td>❌ 불가능</td>
</tr>
<tr>
<td>메서드 구현</td>
<td>✅ 일부 가능</td>
<td>✅ default로 구현 가능</td>
</tr>
</tbody></table>
<br/>


<h3 id="디폴트-메서드-활용-패턴">디폴트 메서드 활용 패턴</h3>
<p>디폴트 메서드를 이용하는 두 가지 방식은 선택형 메서드(optional method)와 동작 다중 상속(multiple inheritance of behavoir)이다.</p>
<h4 id="1-선택형-메서드">1. 선택형 메서드</h4>
<ul>
<li><p>클래스가 꼭 구현하지 않아도 되는 기능을 인터페이스에 기본 제공할 수 있다.</p>
<pre><code class="language-java">  // 자바 8의 Iterator 인터페이스
  interface Iterator&lt;T&gt; {
      boolean hasNext();
      T next();

      // `remove()`는 구현하지 않아도 된다.
      default void remove() {
          throw new UnsupportedOperationException();
      }
  }</code></pre>
</li>
</ul>
<h4 id="2-동작-다중-상속">2. 동작 다중 상속</h4>
<ul>
<li>기존 클래스가 여러 인터페이스에서 메서드를 상속받아도, <strong>디폴트 메서드 덕분에 코드 중복 없이 기능을 재사용</strong>할 수 있다. <pre><code class="language-java">  public class ArrayList&lt;E&gt; extends AbstractList&lt;E&gt;
      implements List&lt;E&gt;, RandomAccess, Cloneable, Serializable {
  }</code></pre>
</li>
</ul>
<br/>


<h3 id="해석-규칙">해석 규칙</h3>
<ul>
<li>인터페이스는 다중 상속이 가능하다. 만약 같은 시그니처를 갖는 디폴트 메서드를 상속받는 경우 자바 컴파일러가 충돌을 어떻게 해결하는가?</li>
</ul>
<h4 id="1-세가지-규칙">1. 세가지 규칙</h4>
<pre><code class="language-java">interface A {
    default void hello() { System.out.println(&quot;A&quot;); }
}

interface B {
    default void hello() { System.out.println(&quot;B&quot;); }
}

class C implements A, B {
    public void hello() {
        B.super.hello(); // 명시적으로 B 선택
    }
}</code></pre>
<ol>
<li>클래스가 항상 이긴다.<ul>
<li>클래스나 슈퍼클래스에서 정의한 메서드가 디폴트 메서드보다 우선한다.</li>
</ul>
</li>
<li>1번 규칙 이외의 상황에서는 서브 인터페이스가 이긴다.<ul>
<li>상속관계를 갖는 인터페이스에서 같은 시그니처를 갖는 메서드를 정의할 때는 서브 인터페이스가 이긴다. (B 가 A 를 상속받으면 B 우선)</li>
</ul>
</li>
<li>여러 인터페이스를 상속받는 클래스에서 명시적으로 호출<ul>
<li>디폴트 메서드의 우선순위가 정해지지 않았으면 클래스가 명시적으로 디폴트 메서드를 오버라이드하고 호출해야 한다.<pre><code class="language-java">public class C implements B, A {
  void hello() {
      B.super.hello();
  }
}</code></pre>
</li>
</ul>
</li>
</ol>
<h4 id="2-발생할-수-있는-문제주의점">2. 발생할 수 있는 문제(주의점)</h4>
<ul>
<li>충돌 그리고 명시적인 문제 해결<ul>
<li>인터페이스 간의 상속관계가 없어 2번 규칙을 적용할 수 없는 경우 자바 컴파일러는 컴파일 에러가 발생한다. 이럴 때 개발자는 사용하려는 메서드를 명시적으로 선택해야 된다</li>
</ul>
</li>
<li>다이아몬드 문제<ul>
<li>여러 인터페이스에서 같은 메서드를 상속받았을 때 충돌 가능성 존재</li>
</ul>
</li>
</ul>
<br/>


<h3 id="추가-2">추가</h3>
<h4 id="추상클래스--디폴트-메서드-case">추상클래스 &gt; 디폴트 메서드 case</h4>
<ul>
<li>상태를 가지는 동작(메서드) 구현이 필요한 경우 + 공통 상태(필드)-동작 함께 제공</li>
<li><strong>접근 제어자 및 보호된 메서드/필드가 필요한 경우</strong><ul>
<li>인터페이스의 디폴트 메서드는 기본적으로 <code>public</code>만 허용</li>
</ul>
</li>
<li>여러 구현체가 동일한 로직, 필드, 유틸리티 메서드 등을 공유해야 할 때 추상 클래스가 유리</li>
</ul>
<h4 id="디폴트-메서드-과다-사용-시-문제">디폴트 메서드 과다 사용 시 문제</h4>
<ul>
<li>모호한 메서드 해석(다이아몬드 문제 등)</li>
<li><strong>인터페이스 복잡성 증가 및 유지보수 어려움</strong><ul>
<li>디폴트 메서드 과다 -&gt; 원래 의도했던 &quot;작고 명확한 역할&quot;의 인터페이스 설계가 무너질 가능성 -&gt; <strong>Interface Segregation Principle(인터페이스 분리 원칙) 위반</strong> 가능성 = 많은 책임</li>
</ul>
</li>
<li>구현체와 디폴트 메서드 간의 혼란</li>
</ul>
<br/>


<h1 id="chapter-14-자바-모듈-시스템">CHAPTER 14 자바 모듈 시스템</h1>
<h2 id="자바-모듈-시스템-도입-배경">자바 모듈 시스템 도입 배경</h2>
<ul>
<li>클래스, 패키지, JAR만으로는 소프트웨어의 구조적 복잡도를 관리하기에 부족.</li>
<li><code>SoC</code>(관심사 분리)와 <code>정보 은닉</code>이 필요하지만 기존 Java에는 한계 존재.</li>
<li>모듈화된 시스템 필요 → 자바 9에서 <code>모듈 시스템</code> 도입.</li>
</ul>
<h2 id="압력-소프트웨어-유추">압력: 소프트웨어 유추</h2>
<ul>
<li>앞 내용들을 이해하고 유지보수하기 쉬운 코드 but 저수준 코드</li>
<li>하지만 소프트웨어 아키텍처(고수준) 에서는 기반 코드를 바꿔야할 때 유추하기 쉽게 생산성을 높일 수 있는 SW 프로젝트 필요</li>
</ul>
<h3 id="관심사분리soc-separation-of-concerns">관심사분리(SoC, separation of concerns)</h3>
<p>컴퓨터 프로그램을 고유의 기능으로 나누는 동작을 권장하는 원칙</p>
<ul>
<li>SoC를 적용함으로 기능들을 모듈이라는 각각의 부분 즉, 코드 그룹으로 분리할 수 있다. </li>
<li>Soc 원칙은 모델, 뷰, 컨트롤러 같은 아키텍처 관점 그리고 복구 기법을 비즈니스 로직과 분리하는 등의 하위 수준 접근 등의 상황에 유용하다.</li>
</ul>
<p>1. 개별 기능을 따로 작업할 수 있으므로 팀이 쉽게 협업할 수 있다.
2. 개별 부분을 재사용하기 쉽다.
3. 전체 시스템을 쉽게 유지보수할 수 있다.</p>
<h3 id="정보은닉information-hiding">정보은닉(information hiding)</h3>
<p>캡슐화</p>
<ul>
<li>특정 코드 조각이 애플리케이션의 다른 부분과 고립되어 있음을 의미한다. <strong><mark style="background: #FF5582A6;">캡슐화된 코드의 내부적인 변화가 의도치 않게 외부에 영향을 미칠 가능성이 줄어든다</mark></strong>. </li>
<li>하지만 자바 9 이전까지는 <strong>클래스와 패키지가 의도된 대로 공개되었는지</strong>를 컴파일러로 확인할 수 있는 기능이 없었다.</li>
</ul>
<h3 id="자바-소프트웨어에-적용한다면">자바 소프트웨어에 적용한다면?</h3>
<ul>
<li>관심사 분리<ul>
<li>자바는 객체 지향언로서 패키지, 클래스, 인터페이스로 코드 그룹화</li>
</ul>
</li>
<li>정보 은닉<ul>
<li>접근제한자 but 여전히 부족</li>
</ul>
</li>
</ul>
<br/>

<h2 id="자바-모듈-시스템을-설계한-이유">자바 모듈 시스템을 설계한 이유</h2>
<h3 id="1-자바의-기존-모듈화-한계">1. 자바의 기존 모듈화 한계</h3>
<ul>
<li>자바 9 이전에는 <strong>클래스, 패키지, JAR 수준</strong>의 그룹화만 제공됨.</li>
<li>클래스 수준에서는 <code>private</code> 등으로 캡슐화 가능하지만, <strong>패키지나 JAR 수준에서는 캡슐화 불가능</strong>.</li>
<li>다른 패키지에 기능을 제공하려면 <code>public</code>으로 공개해야 했고, 이로 인해 <strong>내부 구현이 외부에 노출되는 문제</strong> 발생.</li>
</ul>
<h3 id="2-클래스패스classpath의-한계">2. 클래스패스(classpath)의 한계</h3>
<ul>
<li>클래스패스는 단순한 JAR 나열 방식이라 <strong>의존성 충돌</strong> 문제 발생 (ex. commons-lang 중복 로딩).</li>
<li>Maven/Gradle이 이를 어느 정도 해결하지만, <strong>언어 수준의 의존성 제어는 불가능</strong>.</li>
</ul>
<h3 id="3-무거운-jdk와-캡슐화-부족">3. 무거운 JDK와 캡슐화 부족</h3>
<ul>
<li><strong>자바 개발 키트(JDK)</strong> 는 자바 프로그램을 만들고 실행하는데 도움을 주는 도구의 집합</li>
<li>JDK는 모든 API를 포함해 <strong>불필요하게 무거움</strong>.</li>
<li>자바 8에서 도입한 <strong>Compact Profile</strong>로 일부 경량화했지만, <strong>내부 API 노출 문제</strong>는 여전.</li>
</ul>
<h3 id="4-osgi의-한계">4. OSGi의 한계</h3>
<ul>
<li>자바 9 이전에도 <strong>OSGi</strong> 같은 모듈 시스템이 있었지만, <strong>복잡하고 표준이 아님</strong>.</li>
</ul>
<br/>

<h2 id="자바-모듈--큰-그림">자바 모듈 : 큰 그림</h2>
<h3 id="뒷-내용-개요">뒷 내용 개요</h3>
<p>자바 9의 <code>module</code> 시스템은 기존 자바의 구조적 한계를 해결하기 위해 아래와 같은 기능을 도입함</p>
<ol>
<li><strong>명시적인 의존성 선언 (<code>requires</code>)</strong><br> → 모듈 간 관계를 컴파일 타임에 명확하게 정의</li>
<li><strong>정확한 API 공개 범위 제어 (<code>exports</code>)</strong><br> → 어떤 패키지만 외부에 노출할지 명확히 지정 (캡슐화 강화)</li>
<li><strong>캡슐화 강화</strong><br> → 모듈 외부에서 내부 구현 클래스를 직접 참조할 수 없도록 차단</li>
<li><strong>더 작은 JDK 구성 가능 (런타임 경량화)</strong><br> → 필요 없는 모듈을 제거하고 필요한 모듈만 포함하는 구조 가능 (예: jlink)</li>
<li><strong>모듈 간 충돌 방지</strong><br> → 같은 패키지를 여러 모듈에 동시에 정의할 수 없도록 하여 구조적 충돌 방지</li>
</ol>
<br/>

<h3 id="1-모듈">1. 모듈</h3>
<ul>
<li>자바 8에서 제공하는 자바 프로그램 구조 단위</li>
<li>module이라는 새 키워드에 이름과 바디를 추가해서 정의한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/0183cfe8-6567-4d74-a935-70e61c0248bb/image.png" alt=""></p>
<h3 id="2-모듈-디스크립터">2. 모듈 디스크립터</h3>
<ul>
<li>module-info.java라는 특별한 파일에 저장된다.</li>
<li>보통 패키지와 같은 폴더에 위치하며 한 개 이상의 패키지를 서술하고 캡슐화할 수 있지만 단순한 상황에서는 이들 패키지 중 한 개만 외부로 노출시킨다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/70171bff-12da-4ea4-8966-a885c466c17a/image.png" alt=""></p>
<br/>

<h2 id="자바-모듈-시스템으로-애플리케이션-개발하기">자바 모듈 시스템으로 애플리케이션 개발하기</h2>
<blockquote>
<p>과정 설명</p>
</blockquote>
<h3 id="모듈화-설계">모듈화 설계</h3>
<ul>
<li>초기에는 모듈 분리가 비용이 크지만, 규모가 커질수록 <strong>캡슐화와 유지보수성</strong>이 좋아짐.</li>
<li><strong>세부 vs. 거친 모듈화</strong>: 기능 단위로 작게 나눌수록 유연하지만 복잡도 증가.</li>
</ul>
<p>자바 모듈 시스템의 기초 </p>
<ul>
<li><p>ex) 메인 애플리케이션을 지원하는 한 개의 모듈만 갖는 기본적인 모듈화 애플리케이션 </p>
<pre><code class="language-java">  |- expenses.application
      |- module-info.java // 모듈 디스크리터(모듈의 소스 코드 파일 root 위치)
      |- com
          |- example 
              |- expenses
                  |- application
                      |- ExpensesApplication.java</code></pre>
</li>
<li><p>모듈 기초 구조 : module-info.java</p>
<ul>
<li>모듈 = 패키지를 포함하는 논리적 단위</li>
<li>어떤 패키지를 <strong>공개(export)</strong> 하고, 어떤 모듈에 <strong>의존(require)</strong> 하는지 명시<pre><code class="language-java">module expesnses.application {
  // @@@
}</code></pre>
</li>
</ul>
</li>
<li><p>모듈화 애플리케이션 실행</p>
<ul>
<li>모듈 소스 디렉토리에서 실행</li>
<li>(어떤 폴더와 클래스 파일이 생성된 JAR에 포함되어있는지 결과 출력)</li>
<li>생성된 JAR를 모듈화 애플리케이션으로 실행</li>
</ul>
</li>
</ul>
<br/>

<h3 id="모듈-간-상호작용"> 모듈 간 상호작용</h3>
<blockquote>
<p>두 모듈 간의 상호작용은 자바 9에서 지정한 export, requires 이용해서 진행
=&gt; 좀 더 정교하게 클래스 접근 제어 가능 =&gt; 제한 클래스만 공개, 한 모듈의 내에서만 공개 가능</p>
</blockquote>
<h4 id="exports-구문">exports 구문</h4>
<ul>
<li>exports는 다른 모듈에서 사용할 수 있도록 특정 패키지를 공개 형식으로 만든다.<ul>
<li>기본적으로 모듈 내의 모든 것이 캡슐화된다.</li>
<li>모듈 시스템은 whitelist 기법을 이용해 강력한 캡슐화 제공 -&gt; 다른 모듈에서 사용할 수 있는 기능이 무엇인지 명시해야한다.</li>
</ul>
</li>
</ul>
<h4 id="requires-구문">requires 구문</h4>
<ul>
<li>의조하고 있는 모듈 지정(java.base 외 모듈 import시)</li>
</ul>
<h4 id="모듈-이름-정하기">모듈 이름 정하기</h4>
<ul>
<li>오라클에서 패키지명처럼 인터넷 도메인명을 역순으로 지정하도록 권고</li>
<li>노출된 주요 API 패키지와 이름이 같아야 한다는 규칙도 따라야 한다</li>
</ul>
<h3 id="컴파일과-패키징">컴파일과 패키징</h3>
<ul>
<li>빌드 도구로 프로젝트 컴파일
  ex) maven<ul>
<li>각 모듈은 독립적으로 컴파일 되므로 부모 모듈과 함께 각 모듈에 pom.xml 을 추가한다</li>
<li>올바른 모듈 소스 경로를 이용하도록 메이븐이 javac 설정을 한다.</li>
</ul>
</li>
</ul>
<br/>

<h3 id="자동-모듈">자동 모듈</h3>
<blockquote>
<p>메이븐 컴파일러 플러그인은 module-info.java를 포함하는 프로젝트를 빌드할 때 모든 의존성 모듈을 경로에 놓아 적정한 JAR를 내려받고 이들이 프로젝트에 인식되도록한다.</p>
</blockquote>
<ul>
<li>모듈화가 되어있지 않은 라이브러리를 사용할 경우 자바는 JAR를 자동 모듈이라는 형태로 적절하게 변환한다.</li>
<li><strong>module-info.java 없는 JAR</strong>도 자동 모듈로 변환됨.</li>
<li>JAR 파일명을 기반으로 모듈 이름 생성됨.</li>
<li>자동 모듈은 <strong>모든 패키지를 암묵적으로 공개</strong>하므로 보안/캡슐화에 주의.</li>
</ul>
<h3 id="모듈-정의와-구문들">모듈 정의와 구문들</h3>
<ul>
<li>requires<ul>
<li>컴파일 타임과 런타임에 한 모듈이 다른 모듈에 의존함을 정의하며, <strong>모듈명을 인수</strong>로 받는다</li>
</ul>
</li>
<li>exports<ul>
<li>지정한 패키지를 다른 모듈에서 이용할 수 있도록 공개 형식으로 만든다. <strong>패키지명을 인수</strong>로 받는다</li>
</ul>
</li>
<li>require transitive<ul>
<li>다른 모듈이 제공하는 공개 형식을 한 모듈에서 사용할 수 있다고 지정할 수 있다</li>
</ul>
</li>
<li>exports to<ul>
<li>사용자에게 공개할 기능을 제한함으로 가시성을 좀 더 정교하게 제어할 수 있다</li>
</ul>
</li>
<li>open과 opens<ul>
<li>모듈 선언에 open 한정자를 이용하면 모든 패키지를 다른 모듈에 반사적으로 접근을 허용할 수 있다 </li>
</ul>
</li>
<li>uses와 provides<ul>
<li>자바 모듈 시스템에서는 provides 구문으로 서비스를 제공하고, uses 구문으로 서비스를 소비하는 기능을 제공</li>
</ul>
</li>
</ul>
<h3 id="추가-3">추가</h3>
<h4 id="여전히-대부분의-프로젝트에서-module-system을-안-쓰는-이유는">여전히 대부분의 프로젝트에서 module system을 안 쓰는 이유는?</h4>
<blockquote>
<p>Java Module System은 매우 이상적인 설계지만, 현실에서는 <strong>프레임워크·레거시·학습 비용</strong>에 의해 보편화되지 못함. 대부분의 실무에서는 classpath 기반 구조가 지배적
=&gt; <strong>Java Module System(JPMS)을 사용하지 않는다면</strong> 현재의 프로젝트는 <strong>그냥 classpath 기반 구조</strong></p>
</blockquote>
<ol>
<li>레거시 라이브러리 호환성 문제</li>
<li>빌드 툴 복잡성</li>
<li><strong>Spring 등 프레임워크의 반모듈적 구조</strong><ul>
<li>Spring이 의존하는 <strong>자동성, 리플렉션, 동적 프록시</strong>는 JPMS의 <strong>정적이고 제한적인 설계 철학</strong>과 맞지 않다.</li>
</ul>
</li>
<li>학습 비용...</li>
<li>자바 생태계는 classpath 기반으로 20년 이상된 고인물...</li>
</ol>
<h5 id="java-module-system-도입-고려-기준">Java Module System 도입 고려 기준</h5>
<ul>
<li>java 9(아마 java11부터 쓰겠죠?)만 지원하는 순수 java 애플리케이션</li>
<li>보안, 캡슐화, 런타임 최적화가 중요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 3장]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-3%EC%9E%A5</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-3%EC%9E%A5</guid>
            <pubDate>Thu, 01 May 2025 07:24:44 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-8-컬렉션-api-개선">chapter 8. 컬렉션 API 개선</h1>
<blockquote>
<p>새로운 컬렉션 API의 기능</p>
<ol>
<li>작은 리스트, 집합, 맵을 쉽게 만들 수 있도록 자바 9에 새로 추가된 컬 렉션 팩토리를 살펴본다. </li>
<li>자바 8의 개선 사항으로 리스트와 집합에서 요소를 삭제하 거나 바꾸는 관용 패턴을 적용하는 방법을 배운다. </li>
<li>맵 작업과 관련해 추가된 새로 운 편리 기능을 살펴본다.</li>
</ol>
</blockquote>
<h2 id="컬렉션-팩토리">컬렉션 팩토리</h2>
<ul>
<li>리스트 팩토리 List.of()<ul>
<li>데이터 처리 형식을 설정하거나 데이터를 변환할 필요가 없다면, Collectors.toList () 컬렉터 같은 스트림 API 대신 사용하기 간편한 팩토리 메서드를 이용할 것을 권장. </li>
</ul>
</li>
<li>집합 팩토리 Set.of()</li>
<li>맵 팩토리 Map.of(), 많으면 Map.Entry</li>
</ul>
<h2 id="리스트와-집합-처리">리스트와 집합 처리</h2>
<ul>
<li>기존 컬렉션을 바꾸는 동작 추가(vs 스트림은 새로운 결과를 만든다)<ul>
<li>removeIf(Predicate), replaceAll(UnaryOperator), sort(Comparator)</li>
</ul>
</li>
</ul>
<h2 id="맵-처리">맵 처리</h2>
<ul>
<li>Map 인터페이스에 몇가지 디폴트 메서드 추가<ul>
<li>forEach(k, v 반복), Entry.comparingByValue / key, getOrDefault, 계산 패턴(computeIfAbsent, computeIfPresent, compute), 삭제 패턴, 교체패턴, 합침(putAll, merge)</li>
</ul>
</li>
</ul>
<h2 id="개선된-concurrenthashmap">개선된 ConcurrentHashMap</h2>
<p>ConcurrentHashMap 클래스는 멀티스레드 환경에서 안정성과 성능을 모두 고려한 해시맵. 
동시성 친화적이며 최신 기술을 반영한 HashMap 버전. ConcurrentHashap은 내부 자료구조의 특정 부분만 잠궈 동시 추가, 갱신 작업을 허용한다. 따라서 동기화된 Hashtable 버전에 비해 읽기 쓰기 연산 성능이 월등하다(참고로, 표준 HashMap은 비동기로 동작함).</p>
<h3 id="개선된-특징">개선된 특징</h3>
<ul>
<li><strong>분할 잠금 (lock striping)</strong> 으로 성능 향상</li>
<li>병렬 처리 지원: <code>forEach</code>, <code>reduce</code>, <code>search</code> 등의 메서드</li>
<li><strong>집합 뷰(Set view)</strong> 제공</li>
</ul>
<br/>

<h1 id="chapter-9-리팩터링-테스팅-디버깅">chapter 9. 리팩터링, 테스팅, 디버깅</h1>
<p>요약</p>
<ul>
<li>람다 표현식으로 코드 리팩터링하기<ul>
<li>익명 클래스는 람다 표현식으로 바꾸는 것이 좋다. 하지만 이때 this, 변수 섀도 등 미묘하게 의미상 다른 내용이 있음을 주의</li>
</ul>
</li>
<li>람다 표현식이 객체지향 설계 패턴에 미치는 영향<ul>
<li>람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴에서 발생하는 불필요한 코드를 제거할 수 있다</li>
</ul>
</li>
<li>람다 표현식 테스팅</li>
<li>람다 표현식과 스트림 API 사용 코드 디버깅<ul>
<li>스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값을 확인할 수 있다</li>
</ul>
</li>
</ul>
<h2 id="코드-가독성-개선-방법"><strong>코드 가독성 개선 방법</strong></h2>
<ol>
<li>익명 클래스를 람다 표현식으로 리팩토링<ul>
<li>익명 클래스에서 this는 익명 클래스 자신을 가리키지만 람다에서 this는 람다를 감싸는 클래스</li>
<li>익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있으나 람다 표현식으로는 변수를 가릴 수 없다</li>
<li>익명 클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함이 초래 될 수 있다</li>
</ul>
</li>
<li>람다 표현식을 메서드 참조로 리팩토링<ul>
<li>람다 표현식을 메소드 참조로 변환하면 가독성을 높아지고 코드의 의도를 명확하게 표현 가능</li>
</ul>
</li>
<li>명령형 데이터 처리를 스트림으로 리팩토링<ul>
<li>break, continue, return 등의 제어 흐름문을 모두 분석해서 같은 기능을 수행하는 스트림 연산으로 유추해야 하므로 리팩토링이 쉽지는 않다.</li>
</ul>
</li>
</ol>
<pre><code class="language-java">// # 1. 익명 클래스를 람다 표현식으로 리팩토링
// 같은 시그니처를 갖는 함수형 인터페이스를 선언 시,
// 람다 표현식으로는 어떤 인터페이스를 사용하는지 알 수 없다. 
doSomeThing(() -&gt; System.out.println(&quot;Danger danger!!&quot;)); 
// 명시적 형변환을 이용해서 모호함을 제거할 수 있다. 
doSomeThing((Task)() -&gt; System.out.println(&quot;Danger danger!!&quot;));


// # 2. 람다 표현식을 메서드 참조로 리팩토링
.collect( groupingBy(dish -&gt; { 
    if (dish.getCalories() &lt;= 400) return CaloricLevel.DIET; 
    else if (dish.getCalories() &lt;= 700) return CaloricLevel.NORMAL; 
    else return CaloricLevel.FAT;

.collect( groupingBy(dish::getCaloricLevel));


// # 3. 명령형 데이터 처리를 스트림으로 리팩토링
List&lt;String&gt; dishNames = new ArrayList&lt;&gt;(); 
for (Dish dish : menu) { 
    if (dish.getCalories() &gt; 300) { 
        dishNames.add(dish.getName()); 
    } 
} 

// 스트림 API로 변환하면 더 직접적으로 기술 &amp; 쉽게 병렬화 가능 
menu.stream() 
    .filter(d -&gt; d.getCalories() &gt; 300) 
    .map(Dish::getName) 
    .collect(toList());
</code></pre>
<br/>

<h3 id="코드-유연성-개선">코드 유연성 개선</h3>
<ul>
<li>함수형 인터페이스 적용<ul>
<li>람다 표현식을 이용하려면 함수형 인터페이스가 필요하며, 조건부 연기 실행과 실행 어라운드 패턴, 객체지향 디자인 패턴을 람다 표현식으로 간결하게 구현할 수 있다.</li>
</ul>
</li>
</ul>
<ol>
<li><p>조건부 연기 실행 - 코드 가독성, 캡슐화</p>
<ul>
<li><p>함수형 인터페이스는 <strong>코드 실행 시점을 제어</strong>하는 핵심 도구로 작동한다. 특정 조건이 충족될 때만 람다 표현식이 실행되도록 설계.</p>
<pre><code class="language-java">// 1. 행위 캡슐화: Supplier&lt;String&gt; 인터페이스로 로그 생성 로직을 감싸기
logger.log(Level.FINER, () -&gt; &quot;Problem : &quot; + generateDiagnostic()); // 실제 실행 지연

public void log(Level level, Supplier&lt;String&gt; msgSupplier){  
  if(logger.isLoggable(level)){  // 2. 조건 검증: 로그 레벨 활성화 상태 확인
      // 3. 필요시 실행: 조건 충족 시에만 get() 호출로 실제 로그 생성
      log(level, msgSupplier.get()); // 람다 실행  
  }  
}</code></pre>
</li>
</ul>
</li>
<li><p>실행 어라운드 - 재사용성</p>
<ul>
<li><p>함수형 인터페이스를 통해 <strong>동작 파라미터화 구현</strong></p>
<ul>
<li><strong>설정/정리 코드 재사용</strong></li>
<li><strong>가변 처리 로직 분리</strong>: 실제 작업 내용을 함수형 인터페이스로 추상화</li>
</ul>
</li>
<li><p>매번 같은 준비, 종료 과정을 반복적으로 수행하는 코드를 람다로 변환하여 코드 중복을 줄이고 재사용성을 높인다.</p>
<pre><code class="language-java">String oneLine = processFile((BufferedReader b) -&gt; b.readline()); //람다 전달  

public static String processFile(BufferedReaderProcessor p) throws IOException {  
      try(BufferedReader br = new BufferedReader(new fileReader(&quot;data.txt&quot;)))                  {  
          return p.process(br); //인수로 전달된 BufferedReaderProcessor 실행  
      }  
}  

public interface BufferedReaderProcessor {  
      string process(BufferedReader b) throws IOException;  
}</code></pre>
</li>
</ul>
</li>
</ol>
<br/>

<h2 id="람다로-객체지향-디자인-패턴-리팩토링하기">람다로 객체지향 디자인 패턴 리팩토링하기</h2>
<blockquote>
<p><strong>람다 표현식</strong>과 <strong>함수형 인터페이스</strong>를 이용하면 일부 디자인 패턴은 더 간결하게 표현 가능</p>
</blockquote>
<h4 id="1-전략-패턴">1. 전략 패턴</h4>
<ul>
<li><p>전략 패턴은 런타임에 알고리즘을 유연하게 바꿔야 할 때 사용합니다. 예를 들어 문자열의 조건을 다르게 검증</p>
</li>
<li><p><code>ValidationStrategy</code>가 함수형 인터페이스이므로 람다로 간결하게 대체 가능</p>
</li>
<li><p>ex) Validator </p>
<pre><code class="language-java">  // 기존
  public interface ValidationStrategy {
      boolean execute(String s);
  }

  public class IsNumeric implements ValidationStrategy {
      public boolean execute(String s) {
          return s.matches(&quot;\\d+&quot;);
      }
  }

  public class Validator {
      private final ValidationStrategy strategy;
      public Validator(ValidationStrategy strategy) {
          this.strategy = strategy;
      }

      public boolean validate(String s) {
          return strategy.execute(s);
      }
  }

  // 람다로 리팩토링
  Validator numericValidator = new Validator(s -&gt; s.matches(&quot;\\d+&quot;));    </code></pre>
</li>
</ul>
<h4 id="2-템플릿-메서드-패턴">2. 템플릿 메서드 패턴</h4>
<ul>
<li><p>알고리즘의 뼈대를 정의하고, 일부 로직만 서브 클래스에서 변경할 수 있도록 할 때</p>
</li>
<li><p>비즈니스 로직을 매개변수로 전달하면 템플릿 메서드 패턴 없이도 유연함을 유지 가능</p>
<pre><code class="language-java">// 기존
abstract class OnlineBanking {
  public void processCustomer(int id) {
      Customer c = Database.getCustomerWithId(id);
      makeCustomerHappy(c);
  }

  abstract void makeCustomerHappy(Customer c);
}
</code></pre>
</li>
</ul>
<p>// 람다로 리팩토링
public class OnlineBankingLambda {
    public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(c);
    }
}</p>
<p>new OnlineBankingLambda().processCustomer(1337, 
    c -&gt; System.out.println(&quot;Hello &quot; + c.getName()));</p>
<pre><code>
#### 3. 옵저버 패턴
- 이벤트가 발생했을 때 여러 구독자에게 알림을 전달해야 할 때 사용
- 옵저버 로직이 단순할 경우, 람다를 통해 불필요한 클래스를 제거 가능
```java
// 기존 구현
interface Observer {  
    void notify(String tweet);  
}

class NYTimes implements Observer {  
    public void notify(String tweet) {  
        if(tweet != null &amp;&amp; tweet.contains(&quot;money&quot;)) {  
            System.out.println(&quot;Breaking news in NY! &quot; + tweet);  
        }  
    }  
}

// Subject 인터페이스의 정의다.  
interface Subject {  
    void registerObserver(Observer o);  
    void notifyObservers(String tweet);  
}

class Feed implements Subject {  
    private final List&lt;Observer&gt; observers = new ArrayList&lt;&gt;();  

    // 새로운 옵저를 등록한다.  
    public void registerObserver(Observer o) {  
        this.observers.add(o);  
    }  

    // 트윗을 등록한 옵저들에게 알린다.  
    public void notifyObservers(String tweet) {  
        observers.forEach(o -&gt; o.notify(tweet));  
    }  
}

Feed f = new Feed();

f.registerObserver(new NYTimes());
f.notifyObservers(&quot;The queen said her favorite book is Java 8 in Action!&quot;);

// 람다로 리팩토링
Feed f = new Feed(); 
feed.registerObserver(tweet -&gt; {
    if (tweet.contains(&quot;money&quot;)) {
        System.out.println(&quot;Breaking news in NY! &quot; + tweet);
    }
});
</code></pre><h4 id="4-의무-체인책임-연쇄-패턴">4. 의무 체인(책임 연쇄 패턴)</h4>
<ul>
<li>한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 해야 할 작업을 처리한 다음에 또 다른 객체로 전달하는 식</li>
<li>작업처리 객체를 Function, 더 정확히 표현하자면 UnaryProcessingObject 형식의 인스턴스로 표현할 수 있다. =&gt; <code>Function</code>의 <code>andThen</code> 메서드를 활용하면 체인 구조를 명확하게 표현</li>
</ul>
<h4 id="5-팩토리-패턴">5. 팩토리 패턴</h4>
<ul>
<li>인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 </li>
<li><code>Supplier</code>를 활용하면 조건문 없이 동적으로 객체를 생성할 수 있다.<pre><code class="language-java">// 기존
public class ProductFactory {
  public static Product createProduct(String name) {
      switch (name) {
          case &quot;loan&quot;: return new Loan();
          case &quot;stock&quot;: return new Stock();
          case &quot;bond&quot;: return new Bond();
          default: throw new RuntimeException(&quot;No such product &quot; + name);
      }
  }
}
</code></pre>
</li>
</ul>
<p>// 람다로 리팩토링
Map&lt;String, Supplier<Product>&gt; productMap = new HashMap&lt;&gt;();
productMap.put(&quot;loan&quot;, Loan::new);
productMap.put(&quot;stock&quot;, Stock::new);
productMap.put(&quot;bond&quot;, Bond::new);</p>
<pre><code>
&lt;br/&gt;

## 테스팅
1. 보이는 람다 표현식의 동작 테스팅
    - 람다는 익명이므로 테스트 코드 이름을 호출할 수 없다. 따라서 필요하다면 람다를 필드에 저장해 테스트 할 수 있다.
2. 람다를 사용하는 메서드의 동작에 집중하라
3. 고차원 함수 테스팅
    - 함수를 인수로 받거나 다른 함수를 반환하는 메서드를 고차원 함수

#### 정리
- 람다 자체를 직접 테스트하지 않고 **람다가 사용된 메서드/함수의 결과를 검증**하는 것이 핵심
   - 구현 세부사항 노출 없이 **비즈니스 로직 검증**
   ```java
   public static List&lt;Point&gt; moveAllPointsRightBy(List&lt;Point&gt; points, int x) {  
       return points.stream().map(p -&gt; new Point(p.getX() + x, p.getY())).collect(toList());  
    }</code></pre><ul>
<li>함수를 인자로 받는 메서드는 <strong>다양한 람다 케이스로 검증</strong>하자<pre><code class="language-java">    // 필터 조건을 달리한 여러 테스트 케이스
    filter(numbers, i -&gt; i%2==0)  // 짝수 검증
    filter(numbers, i -&gt; i&lt;3)     // 3 미만 검증</code></pre>
</li>
</ul>
<br/>

<h2 id="디버깅">디버깅</h2>
<ul>
<li><p>람다 표현식과 스트림은 기존 디버깅 무시</p>
</li>
<li><p>stacktrace 확인</p>
<ul>
<li>람다 표현식은 이름이 없기 때문에 조금 복잡한 스택 트레이스가 생성된다. 따라서 람다 표현식과 관련한 스택 트레이스는 이해하기 어려울 수 있다는 점</li>
</ul>
</li>
<li><p>정보 logging</p>
<ul>
<li>각각의 스트림 연산이 어떤 결과를 도출하는지 확인 </li>
<li><mark style="background: #FF5582A6;">peek</mark>이라는 스트림 연산을 활용</li>
<li>peek은 자신이 확인한 요소를 파이프라인의 다음 연산으로 그대로 전달한다.</li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; result =  numbers.stream()  
    .peek(x -&gt; System.out.println(&quot;From stream: &quot; + x))  
    .map(x -&gt; x + 17)  
    .peek(x -&gt; System.out.println(&quot;after map: &quot; + x))  
    .filter(x -&gt; x % 2 == 0)  
    .peek(x -&gt; System.out.println(&quot;after filter: &quot; + x))  
    .lmit(3)  
    .peek(x -&gt; System.out.println(&quot;after limit: &quot; + x))  
    .collect(toList());  

// result  
from stream: 2  
after map: 19  
from stream: 3  
after map: 20  
after filter: 20
after limit: 20  
from stream: 4  
after map: 21  
from stream: 5  
after map: 22  
after filter: 22  
after limit: 22</code></pre>
</li>
</ul>
<br/>


<h1 id="chapter-10-람다를-이용한-도메인-전용-언어">chapter 10. 람다를 이용한 도메인 전용 언어</h1>
<blockquote>
<p>프로그래밍에서 &quot;코드가 마치 문장처럼 읽히면 좋겠다&quot;는 바람.
개발팀과 도메인 전문가가 이해할 수 있는 코드는 생산성과 직결되기 때문에 코드는 읽기 쉽고 이해하기 쉬워야 한다.
=&gt; 도메인 전용 언어(Domain-Specific Language)**는 이런 요구를 해결하기 위한 방법 중 하나</p>
</blockquote>
<h2 id="도메인-전용-언어">도메인 전용 언어</h2>
<ul>
<li>DSL은 범용 프로그래밍 언어가 아니라 특정 비즈니스 도메인의 문제를 해결하려고 만든 언어</li>
<li>자바는 기본적으로 DSL 작성에 불리한 문법 구조를 가지고 있지만, <strong>람다와 메서드 참조, 메서드 체이닝 기법</strong> 등을 활용하면 비교적 깔끔한 DSL 스타일의 API를 만들 수 있다.</li>
</ul>
<p>장단점</p>
<ul>
<li>장점: 간결함(<mark style="background: #FF5582A6;">캡슐화</mark>), 가독성, 유지보수, 높은 수준의 추상화, 집중, 관심사 분리</li>
<li>단점: DSL 설계 어려움, 개발 비용, 추가 우회 계층, 러닝 커브, 호스팅 언어 한계</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>내부 DSL</th>
<th>외부 DSL</th>
<th>다중 DSL</th>
</tr>
</thead>
<tbody><tr>
<td><strong>의존성</strong></td>
<td>호스트 언어에 종속 (예: Java/Kotlin)</td>
<td>독립적인 문법 체계 (예: SQL)</td>
<td>JVM 호환 언어 혼용 (예: Scala+Java)</td>
</tr>
<tr>
<td><strong>구현</strong></td>
<td>호스트 언어의 API 확장</td>
<td>별도 파서/컴파일러 필요</td>
<td>언어 간 인터페이스 브리징</td>
</tr>
<tr>
<td><strong>예시</strong></td>
<td>Querydsl, Kotlin DSL</td>
<td>SQL, HTML/CSS</td>
<td>Scala+Java 조합</td>
</tr>
</tbody></table>
<pre><code class="language-java">// 외부 DSL (SQL)
SELECT name FROM users WHERE age &gt; 18;

// 내부 DSL (JOOQ)
DSLContext create = DSL.using(SQLDialect.POSTGRES);
Result&lt;Record&gt; result = create.select(USER.NAME)
                              .from(USER)
                              .where(USER.AGE.gt(18))
                              .fetch();
</code></pre>
<br/>

<h2 id="최신-자바-api의-작은-dsl">최신 자바 API의 작은 DSL</h2>
<ul>
<li>자바 8 이후의 몇몇 API는 DSL 스타일로 설계되었다. </li>
<li>ex) Stream API, DateTimeFormatter<ul>
<li>메서드 체이닝, 빌더 패턴, 람다 등을 조합해 코드가 <strong>의미론적으로 자연스럽고 직관적</strong><pre><code class="language-java">// Stream API
List&lt;String&gt; highCaloricDishes = menu.stream()
.filter(d -&gt; d.getCalories() &gt; 300)
.map(Dish::getName)
.collect(toList());
</code></pre>
</li>
</ul>
</li>
</ul>
<p>// DateTimeFormatter
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendText(ChronoField.DAY_OF_MONTH)
    .appendLiteral(&quot;. &quot;)
    .appendText(ChronoField.MONTH_OF_YEAR)
    .appendLiteral(&quot; &quot;)
    .appendText(ChronoField.YEAR)
    .toFormatter();</p>
<pre><code>
&lt;br/&gt;

## 자바로 DSL을 만드는 패턴과 기법
&gt; 마치 미니 언어를 자바 위에 얹은 듯한 효과를 준다.

#### 1. 메서드 체이닝 (Fluent API)

각 메서드가 자신을 반환하면 연속 호출이 가능합니다.
```java
builder.add(&quot;milk&quot;).add(&quot;sugar&quot;).add(&quot;coffee&quot;);</code></pre><h4 id="2-빌더-패턴">2. 빌더 패턴</h4>
<p>객체 생성 과정을 단계적으로 표현할 수 있습니다.</p>
<pre><code class="language-java">Order order = new OrderBuilder()
    .forCustomer(&quot;BigBank&quot;)
    .buy(100).stock(&quot;IBM&quot;).on(&quot;NYSE&quot;).at(125.00)
    .sell(50).stock(&quot;GOOGLE&quot;).on(&quot;NASDAQ&quot;).at(375.00)
    .end();</code></pre>
<h4 id="3-중첩-함수-호출-방식">3. 중첩 함수 호출 방식</h4>
<p>람다로 계층 구조를 표현할 수 있습니다.</p>
<pre><code class="language-java">Order order = order(o -&gt; {
    o.forCustomer(&quot;BigBank&quot;);
    o.buy(t -&gt; {
        t.quantity(100);
        t.stock(&quot;IBM&quot;);
        t.market(&quot;NYSE&quot;);
        t.price(125.00);
    });
});</code></pre>
<h4 id="4-스태틱-임포트와-도우미-메서드">4. 스태틱 임포트와 도우미 메서드</h4>
<pre><code class="language-java">Order order = order(
    forCustomer(&quot;BigBank&quot;),
    buy(100, stock(&quot;IBM&quot;, on(&quot;NYSE&quot;)), at(125.00)),
    sell(50, stock(&quot;GOOGLE&quot;, on(&quot;NASDAQ&quot;)), at(375.00))
);</code></pre>
<br/>

<h2 id="실생활의-자바-8-dsl">실생활의 자바 8 DSL</h2>
<ul>
<li><p>자바로 작성된 유명 프레임워크나 라이브러리에서도 DSL 스타일이 자주 활용된다.</p>
</li>
<li><p>AssertJ (테스트용 DSL)</p>
<pre><code class="language-java">  assertThat(frodo.getName()).isEqualTo(&quot;Frodo&quot;);
  assertThat(frodo.getAge()).isBetween(30, 40);

  // 추가로
  // RestAssured DSL 스타일
  given()
      .param(&quot;query&quot;, &quot;dsl&quot;)
  .when()
      .get(&quot;/search&quot;)
  .then()
      .statusCode(200)
      .body(&quot;results.size()&quot;, greaterThan(0));</code></pre>
</li>
<li><p>그 외 ex) JOOQ, Cucumber, </p>
</li>
</ul>
<h2 id="정리">정리</h2>
<p>자바는 원래 DSL을 만들기에는 문법적 제약이 많은 언어였지만 자바 8부터는 <strong>람다, 메서드 참조, 스트림 API, 메서드 체이닝</strong> 등 덕분에 DSL 스타일의 API 작성이 훨씬 수월해졌다.</p>
<p>DSL은 다음과 같은 상황에서 유용하다:</p>
<ul>
<li>도메인 전문가가 코드를 쉽게 이해해야 할 때</li>
<li>API 사용자의 학습 비용을 줄이고 싶을 때</li>
<li>반복되는 구성/설정을 추상화하고 싶을 때</li>
</ul>
<p>자바에서 DSL을 구현할 때는 <strong>유연성과 가독성의 균형</strong>을 신중히 고려해야한다. 너무 복잡하게 설계하면 오히려 코드가 읽기 어려워질 수 있기 때문.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 2장]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-2%EC%9E%A5</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94-%EC%9D%B8-%EC%95%A1%EC%85%98-2%EC%9E%A5</guid>
            <pubDate>Wed, 23 Apr 2025 05:38:01 GMT</pubDate>
            <description><![CDATA[<h1 id="4장-스트림-소개">4장 스트림 소개</h1>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/fd8303b2-1878-4e35-b7bd-c42a7aa8b2d7/image.png" alt=""></p>
<blockquote>
<p>개발자는 &quot;무엇을 할 것인가&quot;에 집중하고, &quot;어떻게 처리할 것인가&quot;는 Stream API 내부에 맡긴다.</p>
</blockquote>
<h2 id="왜-스트림이-필요한가">왜 스트림이 필요한가?</h2>
<ul>
<li>Java는 기존에 루프 기반 처리 위주였기 때문에, 함수형 프로그래밍 트렌드에 맞춰 Lambda와 함께 Stream API가 도입됨<ul>
<li>많은 요소를 담는 컬렉션 데이터를 처리할 때 기존 방식은 코드가 복잡하고 가독성이 떨어질수도</li>
</ul>
</li>
<li>멀티코어 CPU를 효과적으로 활용하기 위해 병렬 처리 필요</li>
<li>복잡한 루프 기반 코드는 유지보수와 디버깅이 어려움</li>
</ul>
<p>👉 Java 8의 스트림 API는 이러한 문제를 선언형 방식으로 간결하고 효율적으로 해결 가능</p>
<h3 id="주의할-점과-한계">주의할 점과 한계</h3>
<ul>
<li>스트림은 1회용: 재사용 불가. 다시 사용하려면 다시 생성해야 함.</li>
<li>디버깅 어려움: 스트림 파이프라인 내부는 디버깅이 쉽지 않음 → 로그나 peek()을 활용</li>
<li>무한 스트림 주의: generate, iterate는 제한 (limit) 없이 사용 시 무한 루프 발생 가능</li>
<li>병렬 스트림 주의: 모든 상황에서 병렬이 빠르지는 않음. 오히려 성능 저하될 수도 있음 ex) I/O 작업</li>
</ul>
<hr>
<h2 id="스트림이란">스트림이란?</h2>
<ul>
<li><strong>Java 8에 새롭게 도입된 기능</strong></li>
<li><strong>선언형(declarative)</strong> 방식으로 컬렉션 데이터를 처리<ul>
<li>데이터 처리 로직을 명시적 구현 없이 <strong>질의(query)</strong> 형태로 표현</li>
</ul>
</li>
<li>멀티스레드나 동기화 코드 없이 <strong>병렬 처리</strong> 가능<ul>
<li><code>stream()</code> → <code>parallelStream()</code>으로 쉽게 병렬화</li>
</ul>
</li>
<li><strong>라이브러리 내부에서 데이터 처리 로직 추상화</strong><ul>
<li>복잡한 로직 없이 간결한 선언형 코드로 표현 가능</li>
<li>다양한 빌딩 블록 연산 (<code>filter</code>, <code>map</code>, <code>sorted</code>, <code>collect</code>)을 조합해 <strong>데이터 처리 파이프라인</strong> 구성</li>
</ul>
</li>
</ul>
<h2 id="선언형-vs-명령형">선언형 vs 명령형</h2>
<ul>
<li>선언형: <strong>무엇을 할 것인지</strong>만 기술 (SQL처럼)</li>
<li>명령형: <strong>어떻게 할 것인지</strong>를 명확히 기술<ul>
<li>예: “삼겹살 사와” (선언형) vs “집 나가서 CU 가서 삼겹살 사” (명령형)</li>
</ul>
</li>
<li>스트림 API는 선언형 코드 작성이 가능하도록 <strong>명령형 로직을 추상화</strong>함</li>
</ul>
<pre><code class="language-java">// 명령형 방식
List&lt;Dish&gt; lowCaloricDishes = new ArrayList&lt;&gt;();
for (Dish d : menu) {
    if (d.getCalories() &lt; 400) {
        lowCaloricDishes.add(d);
    }
}
Collections.sort(lowCaloricDishes, Comparator.comparing(Dish::getCalories));
List&lt;String&gt; names = new ArrayList&lt;&gt;();
for (Dish d : lowCaloricDishes) {
    names.add(d.getName());
}

// 선언형 방식 (Java 8 스트림)
List&lt;String&gt; names = menu.stream()
    .filter(d -&gt; d.getCalories() &lt; 400)
    .sorted(comparing(Dish::getCalories))
    .map(Dish::getName)
    .collect(toList());
</code></pre>
<h2 id="스트림-api의-특징">스트림 API의 특징</h2>
<ul>
<li><strong>선언형</strong>: 코드가 간결하고 가독성이 뛰어남</li>
<li><strong>조합성</strong>: 연산들을 파이프라인 형태로 조합 가능</li>
<li><strong>병렬성</strong>: 병렬 스트림으로 성능 향상 가능</li>
</ul>
<hr>
<h2 id="스트림의-정의와-동작-원리">스트림의 정의와 동작 원리</h2>
<ul>
<li>스트림은 &quot;<strong>데이터 처리 연산을 지원하는, 소스로부터 추출된 연속된 요소</strong>&quot;</li>
<li><strong>연속된 요소</strong>: 순차적 데이터 접근</li>
<li><strong>소스</strong>: 컬렉션, 배열, I/O 리소스 등</li>
<li><strong>지연 연산(lazy evaluation)</strong>: 최종 연산 전까지 실제 처리되지 않음 (게으른 계산)</li>
</ul>
<h3 id="파이프라이닝">파이프라이닝</h3>
<ul>
<li>대부분의 중간 연산(<code>filter</code>, <code>map</code>, <code>sorted</code>)은 새로운 스트림을 반환 → 파이프라인 구성 가능</li>
<li>지연 연산 + <strong>쇼트서킷(short-circuiting)</strong> 최적화 지원</li>
</ul>
<hr>
<h2 id="내부-반복과-외부-반복">내부 반복과 외부 반복</h2>
<ul>
<li><strong>외부 반복</strong>: for-each, iterator 등 명시적 반복</li>
<li><strong>내부 반복</strong>: 스트림에서 사용하는 추상화된 반복</li>
</ul>
<pre><code class="language-java">// 외부 반복
for (Dish dish : menu) {
    names.add(dish.getName());
}

// 내부 반복
List&lt;String&gt; names = menu.stream()
    .map(Dish::getName)
    .collect(toList());
</code></pre>
<p>👉 내부 반복은 병렬 처리 최적화와 선언형 코드 작성을 가능하게 함</p>
<h2 id="스트림-vs-컬렉션">스트림 vs 컬렉션</h2>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>컬렉션(Collection)</th>
<th>스트림(Stream)</th>
</tr>
</thead>
<tbody><tr>
<td>목적</td>
<td>데이터 저장 및 조작</td>
<td>데이터 처리(변환, 필터링, 매핑 등)</td>
</tr>
<tr>
<td>데이터 저장</td>
<td>가능</td>
<td>불가능</td>
</tr>
<tr>
<td>재사용성</td>
<td>가능</td>
<td><strong>불가능 (1회 사용 후 폐기)</strong></td>
</tr>
<tr>
<td>동시 사용</td>
<td>여러 소비자 가능</td>
<td><strong>단일 소비자 전용</strong></td>
</tr>
<tr>
<td>처리 방식</td>
<td>내부 저장 후 전처리</td>
<td><strong>필요 시 처리 (지연 계산)</strong></td>
</tr>
<tr>
<td>병렬 처리</td>
<td>직접 구현 필요</td>
<td><strong>parallelStream()으로 병렬 처리 지원</strong></td>
</tr>
<tr>
<td>무한 데이터</td>
<td>불가능</td>
<td>가능 (<code>Stream.generate</code>, <code>Stream.iterate</code>)</td>
</tr>
</tbody></table>
<h2 id="스트림-연산">스트림 연산</h2>
<h3 id="중간-연산">중간 연산</h3>
<ul>
<li>스트림을 변형하거나 필터링</li>
<li>예: <code>filter</code>, <code>map</code>, <code>limit</code>, <code>distinct</code>, <code>sorted</code></li>
<li>지연 처리 → 최종 연산이 수행될 때 실행</li>
</ul>
<h3 id="최종-연산">최종 연산</h3>
<ul>
<li>스트림을 닫고 결과를 반환</li>
<li>예: <code>collect</code>, <code>forEach</code>, <code>count</code>, <code>reduce</code></li>
</ul>
<pre><code class="language-java">List&lt;String&gt; names = menu.stream()
    .filter(d -&gt; d.getCalories() &gt; 300)
    .map(Dish::getName)
    .limit(3)
    .collect(toList());</code></pre>
<p>👉 최종 연산 전까지는 아무 연산도 수행하지 않음 </p>
<br/>

<h1 id="5장-스트림-활용">5장 스트림 활용</h1>
<h2 id="필터링-슬라이싱-매핑">필터링, 슬라이싱, 매핑</h2>
<h3 id="1-필터링-filtering">1. <strong>필터링 (Filtering)</strong></h3>
<ul>
<li>Predicate를 이용한 필터링 : <code>filter</code>는 <code>Predicate&lt;T&gt;</code>를 받아 해당 조건을 만족하는 요소들만으로 구성된 새로운 스트림을 반환</li>
<li>중복 제거 distinct : <code>distinct</code>는 요소의 <code>equals</code>와 <code>hashCode</code>를 기반으로 중복 제거를 <strong>수행</strong></li>
</ul>
<blockquote>
<p>Predicate 대신 람다 함수를 넣으면 투명하게 관리할 수 있다. </p>
</blockquote>
<h3 id="2-슬라이싱-slicing">2. <strong>슬라이싱 (Slicing)</strong></h3>
<ul>
<li>요소 제한 (<code>limit</code>)과 건너뛰기 (<code>skip</code>)</li>
<li>조건 기반 슬라이싱 : taskWhile(조건이 참인 동안), dropWhile(조건이 거짓이 될 때까지)</li>
</ul>
<h3 id="3-매핑-mapping">3. <strong>매핑 (Mapping)</strong></h3>
<ul>
<li>속성 추출 (map) : 각 요소에 함수를 적용하여 새로운 값을 생성</li>
<li>중첩 map(중복해서) : 연속적인 변환이 가능</li>
<li>평면화 (flatMap) : 여러 개의 스트림을 하나로 병합</li>
</ul>
<br/>

<h2 id="검색-매칭-리듀싱">검색, 매칭, 리듀싱</h2>
<h3 id="1-검색-및-매칭">1. <strong>검색 및 매칭</strong></h3>
<ul>
<li>anyMatch, allMatch, noneMatch : 조건에 따라 불리언 반환 (쇼트서킷 처리 = 불필요한 연산을 생략함으로써 성능을 개선하는 연산 방식)</li>
<li>findAny, findFirst : 조건을 만족하는 요소를 Optional로 반환</li>
</ul>
<h3 id="2-리듀싱-reducing">2. <strong>리듀싱 (Reducing)</strong></h3>
<ul>
<li>모든 요소를 하나의 결과로 결합(합산, 최댓값 등)한다. </li>
<li>합계 구하기, 최댓값 구하기</li>
<li>장점<ul>
<li>공유 상태 없이 병렬 처리에 유리</li>
<li>내부적으로 fold(접기) 연산처럼 작동</li>
</ul>
</li>
</ul>
<h3 id="3-상태-여부에-따른-스트림-연산-분류">3. <strong>상태 여부에 따른 스트림 연산 분류</strong></h3>
<table>
<thead>
<tr>
<th>연산 유형</th>
<th>상태 없음 (Stateless)</th>
<th>상태 있음 (Stateful)</th>
</tr>
</thead>
<tbody><tr>
<td>예시</td>
<td>map, filter, flatMap</td>
<td>sorted, distinct, limit</td>
</tr>
<tr>
<td>특징</td>
<td>각 요소만으로 결과 결정 가능</td>
<td>이전 요소 또는 전체 필요</td>
</tr>
<tr>
<td>병렬처리 유리성</td>
<td>매우 유리</td>
<td>비효율 가능성 있음</td>
</tr>
</tbody></table>
<br/>

<h2 id="숫자-스트림-사용하기">숫자 스트림 사용하기</h2>
<ul>
<li><code>IntStream</code>, <code>DoubleStream</code> 등은 박싱/언박싱 오버헤드를 줄이고 성능을 높인다.</li>
<li>박싱/언박싱 비용 피하고 성능 향상 </li>
<li><code>mapToInt</code>, <code>summaryStatistics</code>, <code>boxed</code> 등 사용<pre><code class="language-java">int totalCalories = menu.stream()
  .mapToInt(Dish::getCalories)
  .sum();</code></pre>
</li>
</ul>
<h2 id="다중-소스로부터-스트림-만들기">다중 소스로부터 스트림 만들기</h2>
<p>여러 방식으로 스트림 생성 가능:</p>
<ul>
<li><code>Stream.of(...)</code>, <code>Arrays.stream(...)</code></li>
<li>파일, 컬렉션, 함수로 생성</li>
<li>두 스트림 합치기: <code>Stream.concat</code></li>
<li>다중 소스 평면화: <code>flatMap</code><pre><code class="language-java">Stream&lt;String&gt; stream1 = Stream.of(&quot;A&quot;, &quot;B&quot;);
Stream&lt;String&gt; stream2 = Stream.of(&quot;C&quot;, &quot;D&quot;);
Stream&lt;String&gt; merged = Stream.concat(stream1, stream2);</code></pre>
</li>
</ul>
<h2 id="무한-스트림">무한 스트림</h2>
<ul>
<li>반드시 <code>limit()</code>과 함께 사용하지 않으면 무한 실행<pre><code class="language-java">Stream&lt;Integer&gt; evenNumbers = Stream.iterate(0, n -&gt; n + 2);
Stream&lt;Double&gt; randoms = Stream.generate(Math::random);</code></pre>
</li>
</ul>
<br/>

<h1 id="6장-스트림으로-데이터-수집">6장 스트림으로 데이터 수집</h1>
<h2 id="요약">요약</h2>
<ul>
<li>스트림으로 수집할 데이터를 어떻게 반환할 것인가?</li>
<li>스트림을 사용하면 복잡한 데이터 가공도 간결하고 선언적으로 표현할 수 있는데, <strong>결과를 만들어내는 최종 연산의 핵심</strong>은 바로 <code>collect()</code>이고, 이를 유연하게 만들어주는 게 바로 <strong>Collector</strong>이다. <ul>
<li>Java 스트림과 컬렉터를 활용하면 반복문과 조건문으로 가득했던 데이터 처리 코드를 간결하고 선언적으로 바꿀 수 있다.</li>
</ul>
</li>
<li>collector 인터페이스에 정의된 메서드를 구현해서 <strong><code>커스텀 컬렉터를 개발</code></strong> 가능.</li>
<li>범위<ul>
<li>스트림의 데이터 수집을 책임지는 컬렉터의 개념과 활용법, 그리고 커스텀 컬렉터 구현</li>
</ul>
</li>
</ul>
<br/>

<h2 id="컬렉터란-무엇인가">컬렉터란 무엇인가?</h2>
<ul>
<li>컬렉터(Collector)는 스트림의 요소를 리스트, 맵, 문자열, 통계 등으로 <strong>어떻게</strong> 수집할지 정의하는 객체 =&gt; 전략 객체<ul>
<li>스트림에서 <code>collect</code> 메서드는 <strong>스트림의 요소들을 원하는 형태로 모아주는 최종 연산</strong>.  이때 &quot;어떻게 모을지&quot;를 결정하는 것이 바로 <strong>컬렉터(Collector)</strong></li>
</ul>
</li>
<li>스트림의 <code>collect</code> 메서드에 컬렉터를 전달하면,  <code>collect</code> 메서드는 내부적으로 리듀싱 연산을 수행하며, 최종 결과를 List, Array, Map, 문자열, 통계 등 다양한 형태로 뽑아낼 수 있다.<ul>
<li>명령형 코드에서 반복문과 조건문으로 직접 구현하던 데이터 가공 과정을 컬렉터로 간단하게 대체 가능</li>
</ul>
</li>
</ul>
<h4 id="리듀싱">리듀싱</h4>
<ul>
<li><strong>스트림 요소를 반복적으로 처리해 하나의 결과로 축소</strong>하는 과정</li>
</ul>
<blockquote>
<p>컬렉터는 이 리듀싱 과정을 유연하게 설계할 수 있게 해준다. 즉, 개발자는 &quot;무엇을 원하는지&quot;만 명확히 명시하면, 스트림은 내부적으로 알아서 각 요소를 누적(축소)해서 최종 결과를 만든다.</p>
</blockquote>
<br/>

<h2 id="자주-쓰는-미리-정의된-컬렉터">자주 쓰는 미리 정의된 컬렉터</h2>
<ul>
<li>Collectors에서 제공하는 메서드의 기능은 크게 세 가지로 구분할 수 있다.</li>
</ul>
<ol>
<li>스트림의 요소를 하나의 값으로 리듀스 하고 요약<ul>
<li>최대-최솟값 검색, 요약 연산, 문자열 연결</li>
<li>범용 리듀싱 요약 연산<ul>
<li><code>reducing()</code>은 <code>reduce()</code>를 Collector로 확장한 고급 기능</li>
</ul>
</li>
</ul>
</li>
<li>요소 그룹화<ul>
<li>그룹화된 요소 조작</li>
<li>다수준 그룹화</li>
<li>서브 그룹으로 데이터 수집<ul>
<li><code>groupingBy()</code> + <code>toSet()</code> 같은 조합으로 그룹 내 결과 형태도 조절 가능</li>
</ul>
</li>
</ul>
</li>
<li>요소 분할<ul>
<li>조건 기반의 true/false 분류</li>
<li>필터링과 다르게 <strong>true/false 양쪽 데이터를 동시에 수집</strong></li>
</ul>
</li>
</ol>
<br/>

<h2 id="collector-인터페이스">Collector 인터페이스</h2>
<blockquote>
<p>이걸로 나만의 Collector 가능. 이를 통해 성능 최적화도 가능하다. </p>
</blockquote>
<h4 id="collector-인터페이스-메서드">Collector 인터페이스 메서드</h4>
<ul>
<li><code>supplier()</code>: 결과 컨테이너 생성</li>
<li><code>accumulator()</code>: 각 요소를 결과에 누적</li>
<li><code>combiner()</code>: 병렬 처리 시 부분 결과 병합</li>
<li><code>finisher()</code>: 최종 결과 변환</li>
<li><code>characteristics()</code>: 병렬 가능 여부, 병합 방식 등 힌트 제공</li>
</ul>
<p>예시</p>
<pre><code class="language-java">public Supplier&lt;List&lt;String&gt;&gt; supplier() {
    return ArrayList::new;  // 빈 ArrayList 생성
}

public BiConsumer&lt;List&lt;String&gt;, String&gt; accumulator() {
    return List::add;  // 요소를 리스트에 추가
}

public BinaryOperator&lt;List&lt;String&gt;&gt; combiner() {
    return (list1, list2) -&gt; {
        list1.addAll(list2);  // 두 리스트 합치기
        return list1;
    };
}

public Function&lt;List&lt;String&gt;, List&lt;String&gt;&gt; finisher() {
    return Function.identity();  // IDENTITY_FINISH인 경우
}

public Set&lt;Characteristics&gt; characteristics() {
    return Set.of(Characteristics.IDENTITY_FINISH);  // 변환 없음을 명시
}
</code></pre>
<br/>

<h2 id="reduce-collect-collectorsreducing">reduce, collect, Collectors.reducing</h2>
<blockquote>
<p>reduce()는 스트림 요소를 하나의 결과로 축소하는 가장 단순한 방식이고, Collectors.reducing()은 이를 Collector 형태로 확장한 고급 전략. 그리고 collect()는 다양한 Collector 전략 객체를 활용해 결과를 원하는 구조로 만들어내는 스트림의 핵심 최종 연산</p>
</blockquote>
<p>1️⃣ reduce() – 스트림 요소를 하나의 결과로 축소</p>
<ul>
<li>가장 기본적인 리듀싱 연산</li>
<li>누산자(accumulator) 함수와 초기값을 명시</li>
<li>단순한 합계, 곱, 최대값/최솟값 계산 등에 적합<ul>
<li><strong>불변성(Immutable)</strong> 기반: 매번 새로운 값을 반환</li>
<li>병렬 처리 시 적절한 연산자를 사용해야 안전하게 작동</li>
</ul>
</li>
</ul>
<pre><code class="language-java">int sum = numbers.stream().reduce(0, Integer::sum);</code></pre>
<p>2️⃣ Collectors.reducing() – 고급 리듀싱 Collector</p>
<ul>
<li>reduce()의 기능을 Collector 형태로 추상화한 것</li>
<li>collect()에서 사용 가능 → 조합성과 가독성 향상</li>
</ul>
<pre><code class="language-java">// collect(reducing(...))은 reduce(...)와 동일한 동작이지만, Collector 문맥 안에서 사용할 수 있는 확장형
int totalCalories = menu.stream()
    .collect(Collectors.reducing(0, Dish::getCalories, Integer::sum));</code></pre>
<p>3️⃣ collect() – Collector 전략을 통해 다양한 결과물 생성</p>
<ul>
<li>스트림 결과를 List, Map, 통계, 문자열 등 복합 구조로 수집 가능<ul>
<li><strong>가변(Mutable) 구조</strong>를 이용해 중간 상태를 유지하며 데이터 누적</li>
<li>병렬 스트림에서 <code>Collector</code>가 <code>CONCURRENT</code> 설정되어 있다면 병렬성 확보 가능</li>
</ul>
</li>
<li>reducing() 외에도 groupingBy(), partitioningBy(), summarizingInt() 등과 함께 조합 가능</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; names = menu.stream()
    .map(Dish::getName)
    .collect(Collectors.toList());

Map&lt;Type, List&lt;Dish&gt;&gt; dishesByType = 
    menu.stream().collect(Collectors.groupingBy(Dish::getType));</code></pre>
<br/>

<h1 id="7장-병렬-데이터-처리와-성능">7장 병렬 데이터 처리와 성능</h1>
<ul>
<li>병렬 스트림으로 데이터를 병렬 처리하기</li>
<li>병렬 스트림의 성능 분석</li>
<li>포크/조인 프레임워크</li>
<li>Spliterator로 스트림 데이터 쪼개기</li>
</ul>
<blockquote>
<p>병렬 스트림은 선언적으로 병렬 처리를 가능하게 해주며, <strong>Fork/Join 프레임워크</strong>와 <strong>Spliterator</strong>와 같은 기반 기술을 통해 동작한다. 하지만, 무조건 빠르지 않으며 <strong>적절한 상황</strong>, <strong>자료구조</strong>, <strong>작업 종류</strong>에 따라 현명하게 사용하는 것이 중요!  <strong>성능을 높이기 위해 병렬화를 쓰는 것이지, 단순히 코드를 짧게 하기 위해 사용하는 것은 아니다.</strong></p>
</blockquote>
<h2 id="병렬-스트림"><strong>병렬 스트림</strong></h2>
<p>정리</p>
<ol>
<li><strong>적절한 경우에만 사용</strong>: 100만 개 이상 데이터, CPU 집약적 작업</li>
<li><strong>자료구조 신중 선택</strong>: <code>ArrayList</code> &gt; <code>LinkedList</code></li>
<li><strong>상태 공유 금지</strong>: 순수 함수형 프로그래밍 원칙 준수</li>
<li><strong>성능 측정 필수</strong>: 실제 환경에서 벤치마크 진행</li>
<li><strong>병렬 처리 친화적 연산</strong>: <code>range()</code>, <code>filter()</code>, <code>map()</code></li>
</ol>
<h4 id="1-병렬-스트림이란"><strong>1. 병렬 스트림이란?</strong></h4>
<ul>
<li>기존의 순차 스트림에 <code>.parallel()</code> 메서드를 호출하면 간단히 변환할 수 있다.</li>
<li>내부적으로 데이터를 여러 조각(청크)으로 나눈 후, <strong>멀티코어 CPU</strong>에서 병렬로 처리한다.</li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5); 
// 순차 처리 
long sumSequential = numbers.stream().mapToInt(i -&gt; i).sum(); 
// 병렬 처리 
long sumParallel = numbers.parallelStream().mapToInt(i -&gt; i).sum();</code></pre>
<h4 id="2-병렬-스트림의-작동-원리"><strong>2. 병렬 스트림의 작동 원리</strong></h4>
<ol>
<li><strong>분할</strong>: 전체 데이터를 여러 청크(조각)로 <strong>분할</strong></li>
<li> <strong>처리</strong>: 각 청크를 별도 스레드에서 처리  </li>
<li> <strong>결합</strong>: 부분 결과를 최종 결과로 합침</li>
</ol>
<blockquote>
<p>병렬 스트림의 작동 원리는 SI 프로젝트의 업무 분할·병렬 진행·통합 과정에 비유 가능.
데이터 = 작업, 스레드 = 사람</p>
</blockquote>
<h4 id="3-성능-벤치마크-순차-vs-병렬"><strong>3. 성능 벤치마크: 순차 vs 병렬</strong></h4>
<p> <strong>잘못된 사용 사례</strong></p>
<ul>
<li><code>Stream.iterate()</code>처럼 <strong>순차적으로 생성되는 스트림은 병렬 처리에 부적합</strong><pre><code class="language-java">// 병렬 처리가 비효율적인 예 (Stream.iterate 사용)
long badParallelSum = Stream.iterate(1L, i -&gt; i + 1)
                        .limit(10_000_000)
                        .parallel()
                        .reduce(0L, Long::sum);
</code></pre>
</li>
</ul>
<pre><code>- **문제점**: `iterate`는 순차적 생성 → 청크 분할 어려움 =&gt; 순차 처리보다 몇 배는 느리다.


**올바른 사용 사례**
- **대용량 데이터 (예: 100만 건 이상)** 처리
- 연산이 **CPU 집약적**이고 각 작업이 서로 **독립적**일 때
- 예: 이미지 필터 처리, 수치 계산, 웹 크롤링 등
```java
// 병렬 처리에 적합한 예 (LongStream.rangeClosed 사용)
long goodParallelSum = LongStream.rangeClosed(1, 10_000_000)
                                .parallel()
                                .sum();</code></pre><ul>
<li><strong>장점</strong>: 쉽게 분할 가능한 범위 생성</li>
<li><strong>결과</strong>: 순차 처리보다 빠름!</li>
</ul>
<blockquote>
<p>실제로는 <strong>JMH</strong>와 같은 도구로 벤치마크 후 사용하는 것이 가장 정확</p>
</blockquote>
<h4 id="4-병렬-스트림-사용-시-주의사항"><strong>4. 병렬 스트림 사용 시 주의사항</strong></h4>
<p><strong>공유 상태 수정 금지</strong></p>
<ul>
<li>공유 상태(전역 변수 등)를 변경하는 연산은 병렬 처리 시 오류 발생 가능 (ex. race condition)<pre><code class="language-java">// 위험한 예: 공유 변수 사용
class Accumulator {
 public long total = 0;
 public void add(long value) { total += value; }
}
</code></pre>
</li>
</ul>
<p>Accumulator acc = new Accumulator();
LongStream.rangeClosed(1, 10_000).parallel().forEach(acc::add);</p>
<pre><code>- **문제**: `total`에 대한 데이터 레이스 발생
- **해결**: 상태 없는(stateless) 연산 사용

**적절한 자료구조 선택**

|자료구조|병렬 적합성|이유|
|---|---|---|
|`ArrayList`|⭐️⭐️⭐️⭐️⭐️|랜덤 액세스, 쉬운 분할|
|`LinkedList`|⭐️|순차 접근 필요|
|`IntStream.range`|⭐️⭐️⭐️⭐️⭐️|고정 크기, 예측 가능한 분할|

#### **5. 병렬 스트림 최적화 전략**

**1. 기본형 스트림 사용**
```java

`// 박싱/언박싱 오버헤드 제거 
LongStream.rangeClosed(1, 1_000_000)  // 기본형          
                .parallel()         
                .sum();`</code></pre><p><strong>2. 순서 의존성 피하기</strong></p>
<pre><code class="language-java">`// 비효율적 
list.parallelStream()    
    .sorted()  // 전체 데이터 수집 필요    
    .forEach(System.out::println); 

// 효율적 
list.parallelStream()     
    .unordered()  // 순서 무시   
    .forEach(System.out::println);`
</code></pre>
<p> <strong>3. 적절한 작업 크기 설정</strong></p>
<ul>
<li><strong>소규모 데이터</strong>(1,000개 미만): 순차 처리 권장</li>
<li><strong>대규모 데이터</strong>(100만개 이상): 병렬 처리 유리</li>
</ul>
<h4 id="6-병렬-처리에-적합한-연산-예시"><strong>6. 병렬 처리에 적합한 연산 예시</strong></h4>
<p><strong>CPU 집약적 작업</strong></p>
<pre><code class="language-java">// 이미지 처리
images.parallelStream()
      .map(img -&gt; applyFilter(img))  // 고비용 연산
      .collect(Collectors.toList());</code></pre>
<p><strong>독립적인 작업</strong></p>
<pre><code class="language-java">// 웹 페이지 크롤링
urls.parallelStream()
    .map(url -&gt; downloadContent(url))  // I/O 작업
    .collect(Collectors.toList());</code></pre>
<h4 id="7-성능-측정-팁"><strong>7. 성능 측정 팁</strong></h4>
<p><strong>JMH를 이용한 정확한 측정</strong></p>
<pre><code class="language-java">
`@BenchmarkMode(Mode.AverageTime) 
@OutputTimeUnit(TimeUnit.MILLISECONDS) 
public class MyBenchmark {     
    @Benchmark    
    public void testMethod() {        // 성능 측정할 코드    
    }
}`</code></pre>
<ul>
<li><strong>주의</strong>: JVM 웜업 시간 고려, 여러 번 실행 후 평균값 사용</li>
</ul>
<hr>
<h2 id="forkjoin-framework">Fork/Join Framework</h2>
<ul>
<li>자바 7부터 도입된 <strong>ForkJoinPool</strong>을 이용해 <strong>작업을 분할(Fork)</strong> 하고, 결과를 <strong>합침(Join)</strong> 으로써 병렬 실행을 가능하게 한다.</li>
<li>병렬 스트림도 내부적으로 이 프레임워크를 사용한다.</li>
</ul>
<p>예시: 1GB 넘는 JSON 로그 파일이 있음 (배열 형태로 수천 개 객체가 들어 있음). 이걸 한 번에 파싱하면 메모리도 터지고, 시간도 오래 걸림.</p>
<ul>
<li>이 리스트가 너무 길면 → <strong>작게 나누고 (Fork)</strong> → 각각 파싱한 뒤 → <strong>합친다 (Join)</strong>.</li>
<li>결과적으로 파싱 속도 <strong>향상</strong></li>
</ul>
<h4 id="병렬-처리의-작업-분할-및-실행을-담당하는-클래스">병렬 처리의 <strong>작업 분할 및 실행</strong>을 담당하는 클래스</h4>
<pre><code class="language-java">// RecursiveTask&lt;T&gt;는 결과를 반환하는 병렬 작업 클래스
class JsonParseTask extends RecursiveTask&lt;List&lt;MyData&gt;&gt; {
    private List&lt;String&gt; lines; // 파싱 대상인 JSON 문자열 목록

    protected List&lt;MyData&gt; compute() {
        // 작업 크기가 작다면 그냥 순차적 처리
        // Fork/Join은 너무 잘게 쪼개면 오히려 오버헤드가 크다. 
            // 작업 분할과 병합, 스레드 관리 등 병렬 처리 자체에 드는 부가 비용
        if (lines.size() &lt;= 1000) {
            return lines.stream().map(JsonUtils::parseLine).toList();
        }

        // 리스트를 절반으로 나눠서 왼쪽/오른쪽 나눈다. 
        int mid = lines.size() / 2;
        JsonParseTask left = new JsonParseTask(lines.subList(0, mid));
        JsonParseTask right = new JsonParseTask(lines.subList(mid, lines.size()));

        // 모든 작업을 fork 하면 스레드가 과하게 생성될 수 있음
        left.fork(); // 왼쪽 작업 비동기 시작!
        List&lt;MyData&gt; rightResult = right.compute(); // 오른쪽은 지금 이 스레드에서 처리
        List&lt;MyData&gt; leftResult = left.join(); // 왼쪽 작업이 끝날 때까지 기다림

        leftResult.addAll(rightResult);
        return leftResult;
    }
}
</code></pre>
<h4 id="이를-실행하는-스레드-풀forkjoinpool-을-제공">이를 실행하는 <strong>스레드 풀(ForkJoinPool)</strong> 을 제공</h4>
<pre><code class="language-java">// 1. ForkJoinPool을 명시적으로 설정
public static void main(String[] args) {
    List&lt;String&gt; lines = ... // JSON 라인 데이터 준비

    // ForkJoinPool을 명시적으로 설정 (예: 4개의 스레드 사용)
    ForkJoinPool forkJoinPool = new ForkJoinPool(4);

    // JsonParseTask 실행
    JsonParseTask task = new JsonParseTask(lines);
    List&lt;MyData&gt; result = forkJoinPool.invoke(task); // ForkJoinPool에서 병렬 실행

    // 결과 처리
    result.forEach(System.out::println);
}

// 2. parallelStream() 사용하기
// - parallelStream() 활용하면 ForkJoinPool을 내부적으로 사용하여 병렬 처리를 쉽게 구현
public static void main(String[] args) {
    List&lt;String&gt; lines = ... // JSON 라인 데이터 준비

    // parallelStream()을 사용해서 스트림을 병렬 처리
    List&lt;MyData&gt; result = lines.parallelStream()
                                .map(JsonUtils::parseLine)
                                .collect(Collectors.toList());

    // 결과 처리
    result.forEach(System.out::println);
}
</code></pre>
<h2 id="spliterator-인터페이스">Spliterator 인터페이스</h2>
<h3 id="spliterator란">Spliterator란?</h3>
<ul>
<li>&quot;Split + Iterator&quot;의 합성어</li>
<li><code>Spliterator</code>는 Java Stream이 데이터를 병렬로 <strong>쪼갤 수 있도록 지원하는 인터페이스</strong></li>
<li>커스텀 분할, 스트림 병렬 처리 최적화</li>
</ul>
<p>예시: 아주 큰 log 파일 한 줄씩 병렬 처리</p>
<pre><code class="language-java">// `Spliterator`를 커스터마이징해서 병렬 처리에 필요한 제어를 직접 수행
public class LogFileSpliterator implements Spliterator&lt;String&gt; {
    private final BufferedReader reader;

    public LogFileSpliterator(BufferedReader reader) {
        this.reader = reader;
    }
    // `Stream`이 데이터를 처리할 때 `BufferedReader`로 한 줄씩 읽어서 `Stream`에 넘긴다.
    // 이 메서드는 Stream 내부에서 계속 호출된다. 
    @Override
    public boolean tryAdvance(Consumer&lt;? super String&gt; action) {
        try {
            String line = reader.readLine();
            if (line == null) return false; // 다음 줄이 없다면 
            action.accept(line);
            return true;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    // 병렬 처리를 위해 일을 나눠주는 메서드
    // 예시: 현재 reader 상태에서 일정 줄 수(1000줄)만큼 분할해서 새로운 Spliterator 생성
    // =&gt; 이 덩어리를 Java가 병렬로 처리할 수 있도록 Spliterator로 반환
    @Override
    public Spliterator&lt;String&gt; trySplit() {
        List&lt;String&gt; chunk = new ArrayList&lt;&gt;();
        try {
            for (int i = 0; i &lt; 1000; i++) {
                String line = reader.readLine();
                if (line == null) break;
                chunk.add(line);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        if (chunk.isEmpty()) return null;
        return chunk.spliterator(); // chunk는 Stream에서 병렬 분할 가능
    }

    // 대충. `Stream`이 내부적으로 &quot;데이터가 얼마나 있지?&quot;를 예측할 수 있도록 힌트 
    // =&gt; Stream이 적절히 분할(split) 하려면 대략적인 데이터 양을 알아야 효율적으로 스레드를 배분할 수 있다.
    @Override
    public long estimateSize() {
        return Long.MAX_VALUE; 
    }

    @Override
    public int characteristics() {
        return ORDERED | NONNULL;
    }
}

// 호출
BufferedReader reader = Files.newBufferedReader(Paths.get(&quot;large-log.txt&quot;));
Stream&lt;String&gt; lines = StreamSupport.stream(new LogFileSpliterator(reader), true); // 병렬 처리
lines.filter(line -&gt; line.contains(&quot;ERROR&quot;))
     .forEach(System.out::println);

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바 인 액션 - 1장 기초]]></title>
            <link>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98%EC%9E%90%EB%B0%94%EC%9D%B8%EC%95%A1%EC%85%98-1%EC%9E%A5-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@danielyang-95/%EB%AA%A8%EB%8D%98%EC%9E%90%EB%B0%94%EC%9D%B8%EC%95%A1%EC%85%98-1%EC%9E%A5-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sat, 19 Apr 2025 00:23:02 GMT</pubDate>
            <description><![CDATA[<h2 id="chapter-1-자바-8-9-10-11--무슨-일이-일어나고-있는가">Chapter 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?</h2>
<blockquote>
<p>정리
쉽게 말해 멀티코어, 병렬, 간결한 코드라는 요구사항 스트림 API를 도입해서 해결한다. 
이는 동작 파라미터화와 인터페이스의 디폴트 메서드를 기반으로 가능 
어떻게 병렬처리? 쉽게 말하면, 라이브러리 내부에서 멀티 CPU를 이용해서 병렬 처리 걱정되는 부분: 데이터 가변성 문제 ⇒ 공유된 데이터에 접근 x</p>
</blockquote>
<h3 id="🌟-자바-8에서-스트림stream-api가-왜-등장했을까">🌟 자바 8에서 스트림(Stream) API가 왜 등장했을까?</h3>
<p>현대 애플리케이션 개발 환경은 예전과는 많이 달라졌는데, 특히 두 가지 큰 변화</p>
<ol>
<li><strong>멀티코어 CPU의 대중화</strong><br> → 성능을 더 끌어올리기 위해 병렬처리가 점점 중요해졌다.</li>
<li><strong>간결하고 유지보수하기 쉬운 코드의 필요성 증가</strong><br> → 반복문, 조건문 등으로 장황하게 짜던 로직을 더 선언적으로 표현하고 싶다.</li>
</ol>
<p>이런 흐름 속에서 자바 8은 &quot;더 간결한 코드&quot;, &quot;멀티코어를 쉽게 활용하는 병렬성&quot;이라는 목표를 가지고 <strong>스트림 API</strong>를 도입했다. </p>
<p>자바 8 설계의 밑바탕을 이루는 세가지 프로그래밍 개념</p>
<ul>
<li>스트림, 메서드 코드 전달, 인터페이스의 디폴트 메서드</li>
</ul>
<hr>
<h3 id="🚀-스트림-api란">🚀 스트림 API란?</h3>
<p>간단히 말하면, <strong>데이터를 조립 라인처럼 처리할 수 있게 도와주는 도구</strong></p>
<ul>
<li>데이터를 하나씩 전달하면서 여러 작업(필터링, 매핑, 정렬 등)을 단계적으로 처리한다.</li>
<li>외부 반복(<code>for-each</code>) 대신 내부 반복을 사용하여 코드를 훨씬 간결하게 만든다.</li>
<li>그리고 <strong><code>parallelStream()</code>을 사용하면 병렬 처리도 손쉽게 가능</strong><ul>
<li>멀티코어 CPU를 잘 활용하고 싶은데, <strong>멀티스레딩 코드는 너무 복잡</strong></li>
</ul>
</li>
</ul>
<h4 id="✅-예시-비교">✅ 예시 비교</h4>
<pre><code class="language-java">기존 방식 (자바 8 이전):
for (Transaction t : transactions) {
    if (t.getPrice() &gt; 1000) {
        // 그룹화 처리
    }
}


자바 8 스트림 방식 → 훨씬 간결
Map&lt;Currency, List&lt;Transaction&gt;&gt; grouped =
    transactions.stream()
                .filter(t -&gt; t.getPrice() &gt; 1000)
                .collect(Collectors.groupingBy(Transaction::getCurrency));
</code></pre>
<h4 id="🧠-병렬-처리">🧠 병렬 처리</h4>
<p>스트림의 진짜 강점 중 하나는 병렬 처리를 <strong>직접 스레드를 다루지 않고도</strong> 가능하게 해준다는 점</p>
<ul>
<li>라이브러리 내부에서 자동으로 <strong>스트림을 분할(fork)</strong> 해서 여러 CPU 코어가 병렬로 처리하고, 마지막에 결과를 <strong>합치는(join)</strong> 방식.</li>
<li>개발자는 병렬화를 위해 복잡한 코드나 <code>synchronized</code> 같은 동기화 코드를 직접 작성할 필요가 없다.</li>
</ul>
<h4 id="💡-유닉스-파이프-예시로-이해하기">💡 유닉스 파이프 예시로 이해하기</h4>
<pre><code class="language-bash">cat file1 file2 | tr &quot;[A-Z]&quot; &quot;[a-z]&quot; | sort | tail -3</code></pre>
<ul>
<li>파일 읽고 → 소문자로 변환 → 정렬 → 끝에서 3줄 출력</li>
<li>각 단계가 <strong>파이프로 연결된 조립 라인</strong>처럼 동작함</li>
<li>자바 스트림도 이처럼 <code>.filter()</code>, <code>.map()</code>, <code>.sorted()</code> 등으로 연결함</li>
</ul>
<hr>
<h4 id="🔧-함수형-프로그래밍-기법들">🔧 함수형 프로그래밍 기법들</h4>
<p>스트림은 자바 8에서 도입된 <strong>함수형 프로그래밍 기능들</strong>과 연관된다.</p>
<h4 id="1-람다-표현식과-메서드-참조">1. <strong>람다 표현식</strong>과 <strong>메서드 참조</strong></h4>
<p>메서드를 값처럼 전달할 수 있게 됐다. 스트림 안에서 <code>.filter()</code>, <code>.map()</code> 같은 메서드를 사용할 때, 조건이나 행동을 함수로 넘기는데 그걸 <strong>람다</strong>나 <strong>메서드 참조</strong>로 작성한다.</p>
<pre><code class="language-java">// 자바 8 이전
File[] hiddenFiles = new File(&quot;.&quot;).listFiles(new FileFilter() {
    public boolean accept(File f) {
        return f.isHidden();
    }
});

// 자바 8 이후
File[] hiddenFiles = new File(&quot;.&quot;).listFiles(File::isHidden);</code></pre>
<ul>
<li><code>File::isHidden</code>은 메서드 참조이고, <code>f -&gt; f.isHidden()</code> 같은 람다 표현식도 쓸 수 있다.</li>
<li><strong>동작을 파라미터로 넘긴다</strong>는 개념입니다.</li>
</ul>
<h4 id="2-predicate-function-등-함수형-인터페이스">2. <strong>Predicate, Function 등 함수형 인터페이스</strong></h4>
<p>스트림에서 흔히 쓰는 <code>filter</code>, <code>map</code> 같은 메서드는 함수형 인터페이스를 인자로 받는다.</p>
<pre><code class="language-java">inventory.stream()
         .filter(apple -&gt; apple.getWeight() &gt; 150) // Predicate&lt;Apple&gt;
         .collect(Collectors.toList());</code></pre>
<hr>
<h3 id="🧩-디폴트-메서드-인터페이스의-변화-인터페이스에서-메서드-정의-가능">🧩 디폴트 메서드: 인터페이스의 변화 (인터페이스에서 메서드 정의 가능)</h3>
<p>자바 8 이전에는 인터페이스에 메서드를 추가하면 모든 구현 클래스에서 강제로 구현해야 했다. 그래서 <strong>디폴트 메서드</strong>가 생겼다.</p>
<pre><code class="language-java">interface MyInterface {
    default void sayHello() {
        System.out.println(&quot;Hello from default method!&quot;);
    }
}</code></pre>
<ul>
<li>이렇게 하면 기존 구현에 영향 없이 기능을 추가할 수 있고, 대표적인 예로 <code>List.sort()</code>가 자바 8부터는 인터페이스 안에 들어갔다. </li>
</ul>
<hr>
<h3 id="☢️-병렬성과-가변-상태">☢️ 병렬성과 가변 상태</h3>
<p>자바의 병렬성에서 중요한 건 <strong>공유 가변 데이터(Shared Mutable Data)</strong> 를 조심해야 한다.</p>
<ul>
<li>병렬로 동작하는 함수들이 같은 변수에 접근하거나 수정하면, 충돌과 에러가 발생할 수 있다.</li>
<li>그래서 <strong>순수 함수(pure function)</strong>, 즉 <strong>부작용(side effect)이 없는 함수</strong>가 병렬 처리에 유리.</li>
<li>스트림에서 사용하는 <code>filter</code>, <code>map</code> 같은 함수는 가능한 순수하게 만들어야 안전하다.</li>
</ul>
<p><strong>1. 공유 가변 데이터의 위험성</strong></p>
<pre><code class="language-java">List&lt;String&gt; list1 = Arrays.asList(&quot;A1&quot;,&quot;A2&quot;,&quot;B1&quot;,&quot;B2&quot;);
List&lt;String&gt; list2 = new ArrayList&lt;&gt;(); // 비-스레드 세이프 컬렉션

list1.parallelStream().forEach(t -&gt; list2.add(t)); 
// 50% 확률로 ConcurrentModificationException 발생
</code></pre>
<ul>
<li><strong>원인</strong>: 여러 스레드가 동시에 <code>add()</code> 호출시 내부 배열 구조가 파괴됨</li>
<li><strong>해결책</strong>: <code>Collections.synchronizedList()</code> 또는 <code>CopyOnWriteArrayList</code> 사용</li>
</ul>
<p><strong>2. 순수 함수 vs 상태 변경 함수</strong></p>
<pre><code class="language-java">// 병렬 실행시 결과 예측 가능 (스레드 세이프)
public int pureSum(int a, int b) {
    return a + b; // 입력값만 사용, 외부 상태 변경 없음
}

// 10,000회 병렬 호출시 결과값이 9,500~10,000 사이 무작위 출력
public class Counter {
    private int value = 0;

    public int unsafeIncrement() {
        return ++value; // 멀티스레드 환경에서 값 덮어쓰기 발생 가능
    }
}</code></pre>
<p><strong>3. 스레드 세이프 구현 기법</strong></p>
<p><strong>동기화(Synchronization) 방식</strong></p>
<pre><code class="language-java">public class SafeCounter {
    private int value = 0;

    public synchronized void increment() {
        value++;
    }
}</code></pre>
<p><strong>Atomic 변수 활용</strong></p>
<pre><code class="language-java">public class AtomicCounter {
    private AtomicInteger value = new AtomicInteger(0);

    public void increment() {
        value.incrementAndGet(); // CAS(Compare-And-Swap) 연산 사용
    }
}</code></pre>
<p> <strong>병렬 처리 성능 비교표</strong></p>
<table>
<thead>
<tr>
<th>방식</th>
<th>스레드 안정성</th>
<th>코드 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td>기본 for-loop</td>
<td>낮음</td>
<td>간단</td>
</tr>
<tr>
<td>synchronized 블록</td>
<td>높음</td>
<td>복잡</td>
</tr>
<tr>
<td>AtomicInteger</td>
<td>높음</td>
<td>중간</td>
</tr>
<tr>
<td>병렬 스트림</td>
<td>높음</td>
<td>간단</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>결론</strong>: Java 8의 병렬 스트림은 <code>ForkJoinPool</code>을 활용해 작업을 자동 분할하지만, 공유 가변 데이터 사용시 명시적인 동기화가 필요하다. 함수형 프로그래밍 패러다임을 통해 상태 변경을 최소화하고, <code>Atomic</code> 클래스나 동시성 컬렉션을 활용하는 것 추천. 병렬 처리 성능 향상과 안정성 보장을 위해 순수 함수 설계와 불변 객체 사용을 권장</p>
<hr>
<h3 id="📌-정리">📌 정리</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>컬렉션 (for-each 등)</th>
<th>스트림 API</th>
</tr>
</thead>
<tbody><tr>
<td>반복 방식</td>
<td>외부 반복</td>
<td>내부 반복</td>
</tr>
<tr>
<td>병렬 처리</td>
<td>직접 스레드 다뤄야 함</td>
<td><code>parallelStream()</code>으로 쉽게</td>
</tr>
<tr>
<td>가독성</td>
<td>반복문 많아 장황</td>
<td>간결한 선언형 코드</td>
</tr>
<tr>
<td>병렬 안정성</td>
<td>synchronized 필요</td>
<td>순수 함수 기반 병렬 가능</td>
</tr>
</tbody></table>
<p><strong><code>Predicate란?</code></strong></p>
<ul>
<li>메서드의 인수로 받아 true나 false를 반환하는 함수를 프레디케이트라고 한다.</li>
</ul>
<h2 id="chapter-2장-동작-파라미터화-코드-전달하기">Chapter 2장 동작 파라미터화 코드 전달하기</h2>
<blockquote>
<p>&quot;왜 이런 방식이 필요한지&quot;, &quot;기존 코드에서 어떤 문제를 해결하려고 했는지&quot;</p>
</blockquote>
<h3 id="✅-핵심-요약">✅ 핵심 요약</h3>
<ul>
<li><strong>동작 파라미터화</strong>는 코드를 추상화하고 재사용성을 높여준다.</li>
<li><strong>전략 패턴</strong>은 동작을 캡슐화해서 전달하는 가장 대표적인 방식입</li>
<li>Java 8의 <strong><code>Predicate</code></strong> 와 <strong>람다 표현식</strong>은 동작 파라미터화를 손쉽게 구현할 수 있도록 도와준다.</li>
<li>이 흐름을 잘 이해하고 있으면, <strong>유연하고 유지보수하기 쉬운 코드를 작성</strong> 가능</li>
</ul>
<h3 id="💡-동작-파라미터화란">💡 동작 파라미터화란?</h3>
<blockquote>
<p>어떤 기능을 구현할 때, <strong>&quot;어떻게 실행할 것인가?&quot;</strong> 를 미리 정하지 않고 나중에 결정하도록 코드를 작성하는 방식</p>
</blockquote>
<p>동작 파라미터화 패턴은 동작을 캡슐화한 다음에 메서드로 전달해서 메서드의 동작을 파라미터화
쉽게 말해, <strong>실행될 행동(동작)을 파라미터처럼 전달</strong>할 수 있게 만드는 것. 변하는 요구사항에 유연하게 대처할 수 있도록 도와준다.</p>
<h4 id="필요성">필요성</h4>
<blockquote>
<p><strong>동작을 &quot;데이터처럼 전달&quot;할 수 있는 구조</strong>, 즉 <strong>동작을 추상화</strong></p>
</blockquote>
<p>초기에는 하나의 동작만 처리해도 충분했을 수 있지만, 시간이 지나고 요구사항이 바뀌면서 <strong>더 많은 조건</strong>이 추가된다. 이때마다 새로운 메서드를 만들거나 코드를 복사해서 붙이면 유지보수가 어렵고 코드가 지저분해진다. </p>
<hr>
<h3 id="🧱-추상화를-통한-해결-방식">🧱 추상화를 통한 해결 방식</h3>
<ol>
<li><strong>인터페이스를 통해 공통된 동작을 정의</strong>하고,</li>
<li>각 조건(전략)을 별도로 <strong>구현 클래스</strong>로 만들며,</li>
<li>이 구현들을 하나의 메서드에 <strong>전달</strong>하여 실행되도록 합니다.</li>
</ol>
<p>이렇게 하면, 메서드는 <strong>&quot;무엇을 실행할지&quot;</strong> 에 대해 알 필요 없이, 전달받은 동작(전략)을 수행하기만 하면 된다. 이게 바로 <strong>전략 패턴 (Strategy Pattern)</strong></p>
<hr>
<h3 id="🧠-전략-패턴과-predicate">🧠 전략 패턴과 Predicate</h3>
<ul>
<li>전략 패턴(Strategy Pattern)은 디자인 패턴 중 하나로, 알고리즘을 정의하고 각각을 별도의 클래스로 캡슐화한 다음, 이들을 교체할 수 있게 만드는 패턴</li>
<li>전략 패턴을 사용하면 알고리즘을 쉽게 교체하고 확장할 수 있으며, 알고리즘의 사용자와 구현체를 분리할 수 있다.</li>
<li>용어<ul>
<li>알고리즘: 어떤 문제를 해결하기 위한 일련의 명령들의 집합. <strong>사실상 각 전략이 알고리즘이다</strong> ⇒ 특정 문제를 해결하는 방법 또는 특정 작업을 수행하는 방법을 정의</li>
<li>행위: 실제로 알고리즘이나 작업을 수행하는 메서드. 여러 알고리즘 또는 전략을 캡슐화한 각 클래스가 이 행위를 구현  ex) 결제</li>
<li>전략: 특정 행위를 구현하는 방법을 나타냅니다. 이것은 일반적으로 인터페이스 또는 추상 클래스로 정의되며, 각 알고리즘을 나타내는 구체적인 클래스가 이를 구현
ex) 결제방법(신용카드 결제, 비트코인 결제)</li>
</ul>
</li>
</ul>
<p>Java 8부터는 이를 더 간결하게 처리할 수 있도록 <code>Predicate</code> 함수형 인터페이스가 도입되었다. <code>Predicate</code>는 입력값을 받아 <strong>boolean을 반환하는 조건식</strong>을 의미. 주로 컬렉션의 항목에 대한 필터링, 검증, 조건적인 처리 등에 사용된다.
=&gt; 조건에 따라 filter가 다르게 동작 =&gt; 이러한 전략 패턴은 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 전략을 바꿔주기만 함으로써 행위를 유연하게 확장할 수 있도록 한다.</p>
<hr>
<h3 id="🚀-동작-파라미터화-발전-단계"><strong>🚀 동작 파라미터화 발전 단계</strong></h3>
<h4 id="1-동작-하드코딩"><strong>1. 동작 하드코딩</strong></h4>
<ul>
<li><strong>특징</strong>: 각 요구사항마다 별도 메서드 작성</li>
<li><strong>문제점</strong>: 중복 코드 증가, 유지보수 어려움<pre><code class="language-java">public List&lt;Apple&gt; filterGreenApples() { /*...*/ }
public List&lt;Apple&gt; filterHeavyApples() { /*...*/ }</code></pre>
</li>
</ul>
<h4 id="2-조건별-메서드-분리"><strong>2. 조건별 메서드 분리</strong></h4>
<ul>
<li><strong>특징</strong>: 색상/무게 등 조건을 인자로 전달</li>
<li><strong>문제점</strong>: 인자 조합이 복잡해지면 관리 난항<pre><code class="language-java">public List&lt;Apple&gt; filterApples(Color color, int weight) { /*...*/ }</code></pre>
</li>
</ul>
<h4 id="3-전략-패턴-적용"><strong>3. 전략 패턴 적용</strong></h4>
<ul>
<li><strong>특징</strong>: 조건을 객체화하여 유연성 확보</li>
<li><strong>장점</strong>: 확장성 ↑, 단일 책임 원칙 준수<pre><code class="language-java">interface ApplePredicate { boolean test(Apple apple); }
class HeavyApplePredicate implements ApplePredicate { /*...*/ }</code></pre>
</li>
</ul>
<h4 id="4-익명-클래스-활용"><strong>4. 익명 클래스 활용</strong></h4>
<ul>
<li><strong>특징</strong>: 클래스 선언 없이 즉석 구현</li>
<li><strong>단점</strong>: 코드 가독성 저하, 장황함<pre><code class="language-java">filterApples(new ApplePredicate() {
  @Override public boolean test(Apple a) { return a.weight &gt; 150; }
});</code></pre>
</li>
</ul>
<h4 id="5-람다-표현식-도입"><strong>5. 람다 표현식 도입</strong></h4>
<ul>
<li><strong>특징</strong>: 간결한 함수형 프로그래밍</li>
<li><strong>장점</strong>: 코드 길이 감소, 의도 명확화<pre><code class="language-java">`filterApples(a -&gt; a.weight &gt; 150 &amp;&amp; a.color == RED);`</code></pre>
</li>
</ul>
<h4 id="6-제네릭-확장--추상화를-통한-문제해결"><strong>6. 제네릭 확장</strong> =&gt; 추상화를 통한 문제해결</h4>
<ul>
<li><strong>특징</strong>: 타입을 일반화하여 범용적 사용</li>
<li><strong>적용 예</strong>: 리스트, 스트림, Optional 등<pre><code class="language-java">`public &lt;T&gt; List&lt;T&gt; filter(List&lt;T&gt; list, Predicate&lt;T&gt; p) { /*...*/ }`</code></pre>
</li>
</ul>
<h4 id="과정">과정</h4>
<pre><code class="language-java">// # 첫번째 시도
public static void main(String[] args) {  
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();  
    for (Apple apple : Apples){  
        if (apple.color.equals(Apple.Color.GREEN)){  
            result.add(apple);  
        }  
    }  
    result.stream().forEach(apple -&gt; System.out.println(apple.color));  
}  

// # 두번째 시도
public static void main(String[] args) {  
    appleColorFilter(Apples,Apple.Color.GREEN).stream().forEach(apple -&gt; System.out.println(apple.color));  
}  

// # 세번째 시도 : 무게도 인수화해서 유연하게 대처 but 중복 로직
public static List&lt;Apple&gt; appleWeightFilter(List&lt;Apple&gt; apples, int weight){
    ...
}

// # 네번째 시도 : 중복 코드를 하나의 메서드로 정리
public static List&lt;Apple&gt; appleFilter(List&lt;Apple&gt; apples, Apple.Color color, int weight){}
// 이렇듯 의미없는 0이 들어 가버린다.
appleFilter(apples, Apple.Color.Green, 0)

// # 다섯번째 시도 : 동작 파라미터화 도입

// 필터에 따른 선택 조건을 결정하는 인터페이스를 정의  
interface ApplePredicate{  
    public boolean filter(Apple apple);  
}  

/*컬러 필터*/  
class AppleColorFilter implements ApplePredicate{  
    @Override  
    public boolean filter(Apple apple) {  
        return apple.color.equals(Apple.Color.GREEN);  
    }  
}  
/*무게 필터*/  
class AppleWeightFilter implements ApplePredicate{  
    @Override  
    public boolean filter(Apple apple) {  
        return apple.weight &gt;= 150;  
    }  
}

// 이처럼 각 항목에 적용할 동작을 분리할 수 있는다는 것은 동작 파라미터화의 강점
public static void main(String[] args) {  
    appleFilter(Apples,new AppleColorFilter()).stream().forEach(apple -&gt; System.out.println(apple.color));  
    appleFilter(Apples,new AppleWeightFilter()).stream().forEach(apple -&gt; System.out.println(apple.color));  
}

// # 여섯번째 시도 : 익명 클래스를 통해 인스턴스화 과정 생략(클래스 선언과 인스턴스화를 동시에)

public static void main(String[] args){  
    appleFilter(Apples,new ApplePredicate(){ // 익명 객체 활용  
        @Override  
        public boolean filter(Apple apple){  
            return apple.color.equals(Apple.Color.GREEN);  
        }  
    }).stream().forEach(apple-&gt;System.out.println(apple.color));  
}

// # 일곱번째 시도 : 람다 표현식
public static void main(String[] args) {  
    appleFilter(Apples,(Apple apple) -&gt; Apple.Color.GREEN.equals(apple.color));  
}

// 추상화
interface ItemPredicate&lt;T&gt;{  
    public boolean filter(T item);  
}  

----  

public static &lt;T&gt; List&lt;T&gt; filter(List&lt;T&gt; list, ItemPredicate&lt;T&gt; p){  
    List&lt;T&gt; result = new ArrayList&lt;&gt;();  
    for (T item : list){  
        if (p.filter(item)){  
            result.add(item);  
        }  
    }  
    return result;  
}
</code></pre>
<h2 id="chapter-3-람다-표현식">Chapter 3. 람다 표현식</h2>
<h4 id="중요한-부분">중요한 부분</h4>
<ul>
<li>람다 표현식 특징</li>
<li>람다 표현식과 함수형 인터페이스</li>
<li>람다 캡쳐링 및 제약조건</li>
</ul>
<h3 id="람다-표현식-도입-이유">람다 표현식 도입 이유</h3>
<ul>
<li>간결한 방식으로 익명 함수를 표현할 수 있게 한다.</li>
<li><strong>기존 익명 클래스의 장황함을 줄이고</strong>, 가독성을 높이기 위해 도입.</li>
<li>Java가 <strong>함수형 프로그래밍 패러다임</strong>을 지원하기 위한 핵심 요소.</li>
<li>자바 컴파일러의 추론에 의존</li>
</ul>
<p>특징</p>
<ul>
<li>익명성, 함수, 전달, 간결성</li>
</ul>
<p><strong>예시 설명</strong></p>
<pre><code class="language-java">// 기존 익명 클래스 방식
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;Hello&quot;);
    }
};

// 람다 표현식 방식
// 파라미터 리스트, 화살표, 람다 바디로 구성된다.
Runnable r2 = () -&gt; System.out.println(&quot;Hello&quot;);
// 바디 : 람다의 반환값에 해당하는 표현식
</code></pre>
<h4 id="장점"><strong>장점</strong></h4>
<ul>
<li><strong>코드의 간결성</strong> - 람다를 사용하면 불필요한 반목문을 삭제할 수 있고 복잡한 식을 단순하게 표현할 수 있습니다.</li>
<li><strong>지연연상 수행</strong> - 람다는 지연연상을 수행함으로써 불필요한 연산을 최소화할 수 있습니다.</li>
<li><strong>병렬처리 가능</strong> - 멀티쓰레드를 활용하여 병렬처리할 수 있습니다.</li>
</ul>
<h4 id="단점"><strong>단점</strong></h4>
<ul>
<li>람다식의 호출이 어렵습니다.</li>
<li>불필요하게 너무 많이 사용하면 오히려 <strong>가독성을 떨어 뜨릴 수</strong> 있습니다.</li>
</ul>
<hr>
<h3 id="실행-어라운드-패턴과-람다">실행 어라운드 패턴과 람다</h3>
<blockquote>
<p>변화가 잦은 <code>Process</code> 부분을 람다로 분리하여 <strong>유연하고 확장 가능한 구조</strong>를 만들 수 있다. 이것이 실행 어라운드 패턴의 핵심</p>
</blockquote>
<ul>
<li>람다 표현식은 <strong>반복되는 설정-처리-정리 작업의 공통 구조</strong>를 간결하게 표현하는 데 유용합니다. 그 대표적인 사례가 <strong>실행 어라운드 패턴 (Execute Around Pattern)</strong></li>
</ul>
<p>예를 들어, 파일 처리 작업은 다음과 같은 구조를 가진다:</p>
<ol>
<li>자원 열기 (Open)</li>
<li>작업 처리 (Process)</li>
<li>자원 닫기 (Close)</li>
</ol>
<p>이러한 공통 패턴을 일반화하면 다음과 같이 표현할 수 있다:</p>
<ul>
<li><strong>SetUp</strong>: 자원 준비</li>
<li><strong>Process</strong>: 실제 처리 로직</li>
<li><strong>CleanUp</strong>: 자원 정리</li>
</ul>
<p><code>Process</code> 부분만 다르고 나머지는 동일하다면, 이 부분만 외부에서 <strong>파라미터로 전달</strong>하면 재사용성이 높아진다. 이때 활용할 수 있는 것이 바로 <strong>람다 표현식</strong>!!!</p>
<h4 id="단계">단계</h4>
<ul>
<li>동작파라미터화를 위해 함수형 인터페이스를 통해 동작 전달 -&gt; 동작 실행 -&gt; 람다 전달</li>
</ul>
<pre><code class="language-java">// #전통 방식
public String processFile() throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(&quot;data.txt&quot;))) {
        return br.readLine(); // 핵심 작업 (변경 가능한 부분)
    }
}

// #함수형 인터페이스 정의
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader br) throws IOException;
}

// #공통 로직 분리
public String executeAround(BufferedReaderProcessor processor) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(&quot;data.txt&quot;))) {
        return processor.process(br); // 람다로 핵심 작업 전달
    }
}

// #람다 실행

// 한 줄 읽기
String line1 = executeAround(br -&gt; br.readLine());

// 두 줄 읽기
String line2 = executeAround(br -&gt; br.readLine() + br.readLine());

// 가장 긴 줄 찾기
String longestLine = executeAround(br -&gt; 
    br.lines().max(Comparator.comparingInt(String::length)).orElse(&quot;&quot;)
);

</code></pre>
<blockquote>
<p>그런데 내가 사용하고 싶은 람다를 위해, 매번 함수형 인터페이스(시그네처)를 만들어 줘야하는걸까?</p>
</blockquote>
<h4 id="기본-제공하는-함수형-인터페이스">기본 제공하는 함수형 인터페이스</h4>
<ul>
<li>람다 표현식을 사용하기 위해선, 그에 대응하는 함수 디스크립터가 필요하다.  그런데 람다 표현식을 사용할 때 마다 함수 디스크립터를 정의하기는 귀찮다. 그래서 자바는 <code>java.util.function</code> 패키지로 여러 가지 함수형 인터페이스를 제공한다</li>
</ul>
<pre><code class="language-java">// Predicate
@FunctionalInterface
public interface Predicate&lt;T&gt; {

    boolean test(T t);

    ...
}

// Consumer
@FunctionalInterface
public interface Consumer&lt;T&gt; {

    void accept(T t);

    ...

// Function
@FunctionalInterface
public interface Function&lt;T, R&gt; {

    R apply(T t);

    ...

// 그 외로, autoboxing을 피할 수 있는 특별한 함수형 인터페이스를 제공
</code></pre>
<table>
<thead>
<tr>
<th>함수형 인터페이스</th>
<th>함수 디스크립터</th>
</tr>
</thead>
<tbody><tr>
<td><code>Predict&lt;T&gt;</code></td>
<td>T -&gt; boolean</td>
</tr>
<tr>
<td><code>Consumer&lt;T&gt;</code></td>
<td>T -&gt; void</td>
</tr>
<tr>
<td><code>Function&lt;T, R&gt;</code></td>
<td>T -&gt; R</td>
</tr>
<tr>
<td><code>Supplier&lt;T&gt;</code></td>
<td>() -&gt; T</td>
</tr>
<tr>
<td><code>UnaryOperator&lt;T&gt;</code></td>
<td>T -&gt; T</td>
</tr>
<tr>
<td><code>BinaryOperator&lt;T&gt;</code></td>
<td>(T, T) -&gt; T</td>
</tr>
<tr>
<td><code>BiPredicate&lt;L, R&gt;</code></td>
<td>(T, U) -&gt; boolean</td>
</tr>
<tr>
<td><code>BiConsumer&lt;T, U&gt;</code></td>
<td>(T, U) -&gt; void</td>
</tr>
<tr>
<td><code>BiFunction&lt;T, U, R&gt;</code></td>
<td>(T, U) -&gt; R</td>
</tr>
</tbody></table>
<hr>
<h3 id="어디서-어떻게-사용하나">어디서 어떻게 사용하나?</h3>
<blockquote>
<p>Java 8부터 도입된 람다 표현식(lambda expression)은 코드를 더욱 간결하고 유연하게 만들어 주는 기능이다. 특히, <strong>함수형 인터페이스</strong>와 함께 사용할 때 그 진가를 발휘하는데, 자바에서 람다 표현식을 <strong>어디에, 어떻게 사용하면 좋은지</strong>, 그리고 그 배경 개념인 <strong>함수 디스크립터, 실행 어라운드 패턴, 형식 검사와 추론</strong>까지 함께 정리한다.</p>
</blockquote>
<h4 id="1-함수형-인터페이스와-람다-표현식">1. 함수형 인터페이스와 람다 표현식</h4>
<p>람다 표현식은 <strong>함수형 인터페이스</strong>의 구현을 간단하게 표현하는 문법입니다.</p>
<ul>
<li><strong>함수형 인터페이스</strong>란, <strong>정확히 하나의 추상 메서드</strong>를 가지는 인터페이스를 말합니다.<ul>
<li>이를 명시적으로 보장하기 위해 <code>@FunctionalInterface</code> 어노테이션을 사용할 수 있습니다.</li>
<li>이 어노테이션을 붙이면, 컴파일러는 해당 인터페이스에 <strong>추상 메서드가 2개 이상 정의되어 있을 경우 오류</strong>를 발생시켜 줍니다.</li>
</ul>
</li>
</ul>
<p>람다 표현식을 사용하면 이 함수형 인터페이스의 추상 메서드 구현을 <strong>익명 객체처럼 간단하게</strong> 작성할 수 있습니다. 람다 표현식은 이 인터페이스의 구현체처럼 사용되며, <strong>해당 추상 메서드의 시그니처</strong>(파라미터와 반환 타입)를 기준으로 컴파일러가 타입을 추론합니다. 이를 <strong>함수 디스크립터(Function Descriptor)</strong> 라고 부릅니다.</p>
<pre><code class="language-java">Runnable r = () -&gt; System.out.println(&quot;Hello, world!&quot;);</code></pre>
<h4 id="2-함수-디스크립터function-descriptor란">2. 함수 디스크립터(Function Descriptor)란?</h4>
<p>람다 표현식이 어떤 함수형 인터페이스에 사용될 수 있는지를 결정하는 중요한 개념이 **함수 디스크립터</p>
<ul>
<li>함수 디스크립터란, <strong>함수형 인터페이스의 추상 메서드 시그니처</strong>를 의미합니다.</li>
<li>함수 디스크립터는 &quot;이 함수형 인터페이스가 어떤 입력을 받아 어떤 출력을 내는가&quot;를 간단히 표현한 것
람다 표현식에서의 시그니처는 해당 람다 표현식이 구현하려는 함수형 인터페이스의 추상 메서드 시그니처와 반드시 일치해야 합니다.</li>
</ul>
<pre><code class="language-java">// 자바의 `Predicate&lt;T&gt;` 인터페이스는 다음과 같은 추상 메서드 시그니처를 가진다.
boolean test(T t);

// `Predicate&lt;String&gt;` 타입의 람다 표현식은 `(String s) -&gt; boolean` 형태여야 하며, 이 시그니처가 일치하지 않으면 컴파일 오류가 발생
Predicate&lt;String&gt; isNotEmpty = s -&gt; !s.isEmpty();</code></pre>
<h4 id="3-시그니처signature와-함수-디스크립터-구분하기">3. 시그니처(Signature)와 함수 디스크립터 구분하기</h4>
<ul>
<li><p>예를 들어, <code>Predicate&lt;T&gt;</code>는 <code>boolean test(T t)</code> 라는 하나의 추상 메서드를 가지고 있습니다.</p>
<ul>
<li>따라서 이 시그니처, 즉 <code>T -&gt; boolean</code> 형태가 바로 함수 디스크립터입니다.
```java</li>
<li><code>boolean test(T t);</code> // 함수 디스크립터를 결정하는 함수형 인터페이스의 추상 메서드 시그니처</li>
<li><code>T -&gt; boolean</code> // 이 시그니처를 람다식 형태로 요약한 함수 디스크립터<pre><code></code></pre></li>
</ul>
</li>
<li><p><strong>시그니처</strong>란, 메서드의 이름과 매개변수 타입 정보를 말합니다. 반환 타입은 포함되지 않습니다.</p>
</li>
<li><p>예시:</p>
<pre><code class="language-java">  void print(String msg)
  void print(int number)</code></pre>
</li>
<li><p>위 두 메서드는 이름은 같지만 매개변수 타입이 다르므로 서로 다른 시그니처를 가집니다. 이는 <strong>메서드 오버로딩</strong>과 밀접한 관련이 있다. <strong>람다 표현식에서의 시그니처</strong>는 해당 표현식이 구현할 <strong>함수형 인터페이스의 추상 메서드 시그니처</strong>와 일치해야 하며, 이를 통해 <strong>형식 검사와 추론이 가능</strong>해진다.</p>
</li>
</ul>
<p>함수형 인터페이스의 추상 메서드 시그니처(함수 디스크립터)와 람다 표현식의 시그니처가 일치하여</p>
<ul>
<li>컴파일러는 람다 표현식이 올바른 타입인지 검사할 수 있고,</li>
<li>매개변수 타입을 명시하지 않아도 문맥을 통해 타입을 추론할 수 있으며,</li>
<li>코드의 안전성과 명확성이 보장됩니다.</li>
</ul>
<p>이것이 람다 표현식에서 시그니처가 중요한 이유이며, 함수형 인터페이스와 람다 표현식 간의 타입 일치를 통해 자바의 함수형 프로그래밍이 가능해진다.</p>
<hr>
<h3 id="형식-검사type-checking와-형식-추론type-inference-제약">형식 검사(Type Checking)와 형식 추론(Type Inference), 제약</h3>
<p>형식 검사 (Type Checking)</p>
<ul>
<li>람다가 어떤 인터페이스에 사용될 수 있는지 결정하기 위해, 자바는 <strong>대상 형식(Target Type)</strong> 을 기준으로 람다의 시그니처를 검사합니다.</li>
</ul>
<pre><code class="language-java">List&lt;Company&gt; companies = filter(list, (Company company) -&gt; company.hasCafeteria());
// `filter()` 메서드의 정의를 확인해보면, 두 번째 인자는 `Predicate&lt;Company&gt;`
// `Predicate&lt;Company&gt;`는 `boolean test(Company c)` 추상 메서드를 가진다.
// 람다 `(Company company) -&gt; company.hasCafeteria()`는 `Company → boolean` 구조이므로 시그니처가 일치
// 따라서 형식 검사가 통과</code></pre>
<p>형식 추론 (Type Inference)</p>
<ul>
<li>자바 컴파일러는 문맥(Context)을 활용하여 람다 표현식의 타입을 <strong>자동으로 추론</strong>할 수 있다.<pre><code class="language-java">// 명시적 타입 지정
filter(list, (Company company) -&gt; company.hasCafeteria());
</code></pre>
</li>
</ul>
<p>// 추론 가능하므로 생략 가능
filter(list, company -&gt; company.hasCafeteria());</p>
<pre><code>
이처럼, **컴파일러는 대상 형식의 함수 디스크립터를 기준으로 람다의 파라미터 타입을 추론**한다. 덕분에 더 깔끔한 코드 작성이 가능

람다 표현식은 자바의 코드를 더 간결하고 유연하게 만들어주는 강력한 도구가 된 것이,
- 함수형 인터페이스와의 결합
- 실행 어라운드 패턴을 통한 재사용성 향상
- 형식 검사와 추론을 통한 안전성과 간결함 확보


자유변수 사용에 제약이 있다. 이는 람다 캡처링 참고!

---

### 메서드 참조
- 메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있다.
- 메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 `축약형`이다. 메서드 레퍼런스를 새로운 기능이 아니라 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법으로 간주 할 수 있다.

|람다 표현식|메서드 참조 형태|
|---|---|
|`(Apple a) -&gt; a.getWeight()`|`Apple::getWeight`|
|`() -&gt; Thread.currentThread().dumpStack()`|`Thread.currentThread()::dumpStack`|
|`(s) -&gt; System.out.println(s)`|`System.out::println`|

#### 참조 유형
- **정적 메서드**: `Integer::parseInt`
- **특정 객체 인스턴스 메서드**: `myObject::instanceMethod`
- **클래스의 임의 객체 메서드**: `String::length`


#### 람다, 메소드 참조 활용하기 - 코드 발전 과정 예시
```java
class Orange {
    private Integer weight;
    public Orange(Integer weight) { this.weight = weight; }
    public Integer getWeight() { return weight; }
}

// 동작 파라미터화
oranges.sort(new OrangeComparator());

// 익명 클래스
oranges.sort(new Comparator&lt;Orange&gt;() {
    public int compare(Orange o1, Orange o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
});

// 람다 표현식
oranges.sort((o1, o2) -&gt; o1.getWeight().compareTo(o2.getWeight()));

// comparing 사용
oranges.sort(Comparator.comparing(o -&gt; o.getWeight()));

// 메서드 참조
oranges.sort(Comparator.comparing(Orange::getWeight));

</code></pre><p>생성자 참조</p>
<ul>
<li>함수형 인터페이스를 사용하면, 정적 메서드 참조를 사용하는 것과 유사한 형식으로 생성자도 참조할 수 있다.</li>
</ul>
<hr>
<h3 id="람다-표현식을-조합할-수-있는-유용한-메서드">람다 표현식을 조합할 수 있는 유용한 메서드</h3>
<blockquote>
<p>자바 8 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 제공한다.  이를 통해 람다 표현식을 조합하여 복잡한 람다 표현식을 만들 수 있다. 이는 함수형 인터페이스의 <code>default method</code>가 있기 때문</p>
</blockquote>
<ul>
<li>Comparator 조합 : comparing, 역정렬, thenComparing</li>
<li>Predicate 조합 : negate, and, or</li>
<li>Function 조합 : andThen, compose</li>
</ul>
<h3 id="람다-캡쳐링"><strong>람다 캡쳐링</strong></h3>
<p>람다의 바디(구현부)에는 파라미터를 제외하고도 <strong>바디 외부에 있는 변수</strong>를 참조할 수 있습니다.</p>
<p>이렇게 람다 시그니처의 파라미터로 넘겨진 변수가 아닌 <strong>외부에서 정의된 변수</strong>를 <strong><code>자유 변수(Free Variable)</code></strong>이라고 부릅니다.이런 <strong>자유 변수를 참조하는 행위</strong>를 <strong><code>람다 캡쳐링(Lambda Capturing)</code></strong>이라고 합니다.</p>
<h4 id="람다-캡쳐링의-제약-조건"><strong>람다 캡쳐링의 제약 조건</strong></h4>
<ol>
<li>지역 변수는 final로 선언되어 있어야 한다.</li>
<li>final로 선언되지 않은 지역변수는 final처럼 동작해야 한다.<ul>
<li><strong>값의 재할당</strong>이 일어나면 안됩니다.</li>
</ul>
</li>
</ol>
<p>1️⃣ <strong>&quot;지역 변수는 final로 선언되어 있어야 한다&quot;</strong> 예시 코드</p>
<pre><code class="language-java">final String email = &quot;dia0312@naver.com&quot;;
int age4 = getAge(users, user -&gt; email.equals(user.getEmail()));</code></pre>
<p>2️⃣ <strong>&quot;final로 선언되지 않은 지역변수는 final처럼 동작해야 한다.&quot;</strong> 예시 코드</p>
<pre><code class="language-java">**// 값이 한번 초기화되고 재할당 되지 않음 -&gt; final 처럼 동작함.**
String email = &quot;dia0312@naver.com&quot;;
int age4 = getAge(users, user -&gt; email.equals(user.getEmail()));</code></pre>
<p>❌ <strong>&quot;final로 선언되지 않은 지역변수는 final처럼 동작해야 한다.&quot;</strong> 예시 실패 코드</p>
<pre><code class="language-java">String email = &quot;dia0312@naver.com&quot;;
**// 값을 재할당하였기 때문에 -&gt; final 처럼 동작하지 않음.**
email = &quot;sonny@naver.com&quot;;
int age4 = getAge(users, user -&gt; email.equals(user.getEmail()));</code></pre>
<p>다음과 같이 <code>&quot;Variable used in lambda expression should be final or effectively final&quot;</code>에러 메시지를 확인할 수도 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/ca3f6c95-d99b-417d-b6ee-81614fedb975/image.png" alt=""></p>
<h4 id="이유chatgpt">이유(chatGPT)</h4>
<p>람다 표현식에서 외부 변수를 <code>final</code> 또는 <code>effectively final</code>로 제한하는 이유는 다음과 같은 핵심 원리에 기반한다.</p>
<p>1. <strong>스레드 안전성 보장</strong></p>
<ul>
<li><strong>문제점</strong>: 람다는 다른 스레드에서 실행될 수 있습니다. 변수가 변경 가능하면 한 스레드에서 변수를 수정하는 동안 다른 스레드가 이 값을 읽을 때 예측 불가능한 결과가 발생할 수 있습니다.</li>
<li><strong>해결책</strong>: <code>final</code> 또는 <code>effectively final</code> 변수만 허용함으로써, 람다가 캡처한 값이 <strong>변경되지 않음</strong>을 보장합니다. 이는 스레드 간 데이터 경쟁을 방지하고 안정성을 확보합니다.</li>
</ul>
<p>2. <strong>함수형 프로그래밍의 불변성 원칙</strong></p>
<ul>
<li><strong>불변 데이터</strong>: 함수형 프로그래밍은 부작용을 최소화하기 위해 데이터의 불변성을 강조합니다. 람다가 외부 변수를 변경하지 않으면 코드 추론이 쉬워지고 버그 가능성이 줄어든다.</li>
<li><strong>순수 함수</strong>: 람다가 외부 상태에 의존하지 않도록 하여, 입력에 따른 출력이 항상 일관되게 유지된다.</li>
</ul>
<p>3. <strong>변수의 수명과 일관성</strong></p>
<ul>
<li><strong>지역 변수의 복제</strong>: 지역 변수는 스택에 저장되며, 스레드마다 별도의 스택이 존재합니다. 람다는 <strong>변수의 복사본</strong>을 생성하여 사용하므로, 원본 변수가 메서드 종료 후 소멸되더라도 복사본으로 작업할 수 있다.<pre><code class="language-java">`int localVar = 10; Runnable r = () -&gt; System.out.println(localVar); // 복사본 생성`</code></pre>
</li>
<li><strong>복사본의 일관성</strong>: 복사된 값이 변경되지 않아야 람다 실행 시 예상치 못한 오류를 방지할 수 있다.</li>
</ul>
<p>4. <strong>힙(Heap) vs 스택(Stack) 메모리 영역</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>지역 변수</th>
<th>인스턴스/정적 변수</th>
</tr>
</thead>
<tbody><tr>
<td><strong>저장 위치</strong></td>
<td>스택 (스레드별 독립적)</td>
<td>힙 (모든 스레드에서 공유)</td>
</tr>
<tr>
<td><strong>접근 방식</strong></td>
<td>복사본 사용 (원본 접근 불가)</td>
<td>직접 참조 (동기화 필요)</td>
</tr>
<tr>
<td><strong>제약 사유</strong></td>
<td>복사본과 원본의 불일치 방지</td>
<td>공유 자원 관리의 복잡성</td>
</tr>
</tbody></table>
<ul>
<li><strong>지역 변수</strong>: 람다가 다른 스레드에서 실행될 때 원본 변수의 스택 프레임이 사라질 수 있으므로, 복사본을 사용합니다. 복사본의 불변성이 필수적입니다.</li>
<li><strong>인스턴스 변수</strong>: 힙에 저장되어 모든 스레드가 공유하므로 <code>final</code> 제약이 없지만, 동시 수정 시 명시적 동기화가 필요합니다.</li>
</ul>
<p>5. <strong>예측 가능성과 코드 단순화</strong></p>
<ul>
<li><strong>동작 예측</strong>: 람다가 참조하는 변수의 값이 변하지 않으면, 실행 시점에 따른 결과 차이가 발생하지 않습니다.</li>
<li><strong>복잡성 감소</strong>: 가변 변수를 허용하면 락 관리나 동기화 로직이 추가되어 코드가 복잡해집니다. <code>final</code> 제약으로 이를 방지합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그 관리]]></title>
            <link>https://velog.io/@danielyang-95/%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@danielyang-95/%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Fri, 31 Jan 2025 12:37:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>로그를 남긴다는 것은 모니터링과 바로 직결되는 이야기이기 때문에 무엇을 남길지는 직접 생각해야한다.
&quot;이 로그가 없다면 문제 발생 시 원인을 파악할 수 있는가?&quot;를 기준 삼기</p>
</blockquote>
<h2 id="고려-사항">고려 사항</h2>
<h3 id="보안-민감-정보">보안, 민감 정보</h3>
<ul>
<li>반드시 로그로 남겨야 하는가?<ul>
<li>시스템 분석이나 트러블슈팅에 필요한 정보인가?</li>
<li>법적 보존 의무가 있는 정보인가?</li>
<li>불필요한 정보까지 무조건 남기면 오히려 보안 위험이나 디버깅 방해 요소가 될 수 있다.</li>
</ul>
</li>
<li>로그 마스킹 정책<ul>
<li>민감한 정보는 평문 저장 금지</li>
</ul>
</li>
<li>대상<ul>
<li>고객 정보: 개인정보보호법 등 법적 이슈 → 반드시 마스킹</li>
<li>애플리케이션 로직: 파라미터, 조건문 분기 확인용</li>
<li>SQL 쿼리: 성능 튜닝 또는 디버깅 목적으로 기록 가능하지만, 바인딩 값까지 노출할 경우 주의</li>
</ul>
</li>
<li>중요성<ul>
<li>단순 error 레벨로 남겨선 안 되는 실시간 대응 필요한 이벤트는 별도로 알림을 보낼 수 있다. (ex) Slack, 이메일, PagerDuty 등과 연동)</li>
</ul>
</li>
</ul>
<h3 id="가독성-디버깅">가독성, 디버깅</h3>
<ul>
<li>로그 메시지는 누가 보더라도 빠르게 상황을 파악할 수 있도록 작성해야 한다.</li>
<li>로그 Convention =&gt; 팀원들과 상의<ul>
<li>분쟁 point<ul>
<li>사용자 식별자</li>
<li>RequestBody</li>
<li>StackTrace</li>
<li>Info level 몰아주기</li>
<li>로그 필드 표준화<ul>
<li>pros: JSON 형식은 키-값으로 구조화 =&gt; 로그 집계 시스템 연동 유리</li>
<li>cons: 가독성, 속도 저하</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>

<h4 id="추천-방식">추천 방식</h4>
<table>
<thead>
<tr>
<th>로그 대상</th>
<th>포맷</th>
<th>목적</th>
</tr>
</thead>
<tbody><tr>
<td><strong>파일 로그</strong> (<code>logs/app.log</code>)</td>
<td>사람이 보기 쉬운 형식</td>
<td>CLI, 실시간 tail, 긴급 디버깅</td>
</tr>
<tr>
<td><strong>시스템 로그</strong> (<code>stdout</code> or <code>logs/structured.json</code>)</td>
<td>JSON</td>
<td>수집 시스템 연동, 검색, 대시보드</td>
</tr>
</tbody></table>
<blockquote>
<p>기계가 처리할 로그는 JSON, 사람이 읽을 로그는 단순 텍스트로 구분하는 것이 실무에서 가장 안정적인 전략</p>
</blockquote>
<br/>

<h3 id="로그-파일-size">로그 파일 Size</h3>
<ul>
<li>불필요한 로그를 제외</li>
<li>로그 설정<ul>
<li>로그가 일정 크기를 넘기면 새 파일로 교체 ex) 100MB 단위</li>
<li>일정 기간 이상된 로그는 자동 삭제 ex) 7~30일 </li>
</ul>
</li>
</ul>
<br/>

<h2 id="대상">대상</h2>
<ul>
<li>request, response </li>
<li>error, exception</li>
<li>사용자 활동 로그(보안 감사 추적. 주로 법적)</li>
<li>시스템 상태 로그</li>
<li>보안 로그</li>
<li>Debug(local, dev 환경)</li>
</ul>
<br/>

<h2 id="흐름">흐름</h2>
<ul>
<li>Request가 들어오는 Flow는 Filter -&gt; DispatcherServlet -&gt; HandlerInterceptor -&gt; controller 순으로 들어온다.</li>
<li>Controller로 넘어가기 전에 Interceptor 같은데서 로그?</li>
</ul>
<br/>

<h2 id="로그-레벨">로그 레벨</h2>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/702012f5-0b02-4db3-b7f3-2878d5fed16b/image.png" alt=""></p>
<p>Debug</p>
<ul>
<li>상세한 내부 흐름 확인 (운영 환경에는 출력 X)</li>
</ul>
<br/>

<h2 id="설정">설정</h2>
<blockquote>
<p>스프링부트 기준</p>
</blockquote>
<h3 id="logback로깅-프레임워크">logback(로깅 프레임워크)</h3>
<ul>
<li>Appender 분리: 콘솔 vs. 파일 vs. JSON</li>
<li>RollingPolicy: 파일 사이즈/날짜 기반 로테이션</li>
<li>Pattern: 로그 포맷 구성 (환경별 다르게)</li>
</ul>
<h3 id="mdc">MDC</h3>
<ul>
<li>요청 단위의 traceId, userId 등을 로그마다 자동으로 남기기 위한 ThreadLocal 기반 컨텍스트
ex) Filter, Interceptor, AOP에서 활용</li>
</ul>
<h3 id="log-filter--interceptor">Log Filter / Interceptor</h3>
<ul>
<li>모든 HTTP 요청/응답/Exception 등에 대한 로그 필터링 및 MDC 설정</li>
</ul>
<h3 id="로그-마스킹--필터링">로그 마스킹 / 필터링</h3>
<ul>
<li>AOP + Regex, 또는 logback 필터에서 직접 처리
=&gt; 개인정보, 패스워드, 토큰 등이 로그에 노출되지 않도록 처리</li>
</ul>
<h3 id="그-외">그 외</h3>
<ul>
<li>tomcat war 배포 시<ul>
<li>톰캣은 기본적으로 catalina.out에 System.out/err 로그를 저장</li>
<li>Spring Boot를 WAR로 배포하면 톰캣의 로깅과 Spring의 로깅이 혼합될 수 있음 → 경계 명확히</li>
</ul>
</li>
<li>Log Format 및 구조화 (JSON 등)</li>
<li>중앙 Logging 시스템 연동</li>
</ul>
<br/>

<h2 id="도구">도구</h2>
<ul>
<li>ELK Stack (Elasticsearch + Logstash + Kibana)</li>
<li>Prometheus + Grafana + Loki</li>
<li>CloudWatch / Stackdriver / Datadog / Sentry 등 외부 SaaS</li>
</ul>
<h2 id="기타">기타</h2>
<ul>
<li>CheckedException(예외 처리 강제) vs UncheckedException(강제 x)<ul>
<li>UnCheckException 추천. Checked는 throws 지옥...</li>
</ul>
</li>
</ul>
<br/>

<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://huisam.tistory.com/entry/springlogging">https://huisam.tistory.com/entry/springlogging</a></li>
<li><a href="https://yangbongsoo.gitbook.io/study/undefined/log">https://yangbongsoo.gitbook.io/study/undefined/log</a></li>
<li><a href="https://velog.io/@jsj3282/10.-%EB%A1%9C%EA%B7%B8%EB%8A%94-%EB%B0%98%EB%93%9C%EC%8B%9C-%ED%95%84%EC%9A%94%ED%95%9C-%EB%82%B4%EC%9A%A9%EB%A7%8C-%EC%B0%8D%EC%9E%90">https://velog.io/@jsj3282/10.-로그는-반드시-필요한-내용만-찍자</a></li>
<li>인프런 강의 정리글[이준형 FOO: 개발자에게 필요한 로그 관리]</li>
<li><a href="https://elsboo.tistory.com/86">간단하게 로그에 클래스, 메소드명 자동 출력</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Fixture Monkey]]></title>
            <link>https://velog.io/@danielyang-95/Fixture-Monkey</link>
            <guid>https://velog.io/@danielyang-95/Fixture-Monkey</guid>
            <pubDate>Sun, 10 Nov 2024 12:33:59 GMT</pubDate>
            <description><![CDATA[<h3 id="나의-결론">나의 결론</h3>
<blockquote>
<p><strong>단순함</strong>. 특히, 테스트 코드도 길어지면 한숨이 나오는데, 가독성이 크게 향상되었다. 
무작위 데이터를 쉽게 만들 수 있는게 맘에 들었다. 불필요한 것까지 하드코딩하는 것보다 set()을 활용해 필요한 필드만 명시할 수 있어 유지보수가 쉬워보인다.
테스트 데이터를 많이 생성해야할 때 좋아보인다.</p>
</blockquote>
<br/>

<h3 id="정의">정의</h3>
<p>Fixture Monkey는 테스트 객체를 쉽게 생성하고 조작할 수 있도록 고안된 Java 및 Kotlin 라이브러리. Naver 내부 라이브러리로 처음 개발되서 테스트 객체 생성 간소화에 핵심 역할</p>
<br/>

<h3 id="사용-이유">사용 이유</h3>
<blockquote>
<p>사이드 프로젝트를 하면서 테스트 코드 작성 시 Stub을 생성하는 과정에서 테스트용 데이터를 쉽게 만들 수 있는 방법에 대해 고민해보다가 해당 라이브러리를 활용하게 되었다. Fixture Monkey는 이를 간단한 코드로 처리할 수 있어 개발 생산성을 높이는 데 기여했다.</p>
</blockquote>
<br/>

<h3 id="키워드">키워드</h3>
<ul>
<li>간단함: 한 줄로. 익숙한 Builder 패턴으로. Properties 사용자 정의 가능</li>
<li>재사용성: Fixture Monkey를 활용하면 여러 테스트에서 인스턴스 명세를 재사용할 수 있어 시간과 노력을 절약 가능 =&gt; 더 높이는 기능: ArbitraryBuilder 등록, InnerSpec</li>
<li>무작위: 중요한 properties 이외는 알아서 채워준다.</li>
<li><strong>Property-based Testing</strong>: 테스트에서 입력값을 무작위로 생성하여 다양한 케이스를 자동으로 실행하고, 특정 속성(Property)이 항상 성립하는지 검증하는 기법
=&gt; 수많은 랜덤 데이터로 예상치 못한 경계 케이스를 발견</li>
</ul>
<br/>

<h3 id="사용환경">사용환경</h3>
<ul>
<li>JDK 1.8 이상 (또는 Kotlin 1.8 이상), JUnit 5 platform, jqwik 1.7.3</li>
</ul>
<br/>

<h3 id="내부동작">내부동작</h3>
<ul>
<li>Java Reflection 기반</li>
<li>Arbitrary Value Generation (랜덤 데이터 생성)<ul>
<li>Java의 타입 정보(예: int, String, List&lt;@@&gt;)를 기반으로 기본값을 자동 생성</li>
</ul>
</li>
<li>재귀적 객체 생성 (Nested Object 지원)<ul>
<li>Product 내부에 Category 같은 다른 객체가 있다면, 이를 자동으로 생성합니다.</li>
</ul>
</li>
</ul>
<br/>


<h3 id="랜덤하지만-현실적인-데이터를-원한다면">랜덤하지만 현실적인 데이터를 원한다면?</h3>
<ul>
<li>Faker 라이브러리를 활용하여 현실적인 데이터 생성<ul>
<li>현실적인 제품명, 주소, 이메일 등을 자동 생성 가능</li>
</ul>
</li>
<li>필드 값을 특정 규칙에 맞게 변경하기 manipulate()<ul>
<li>manipulate()를 사용하면 특정 조건을 만족하는 데이터만 필터링하거나 가공할 수도 있다. </li>
</ul>
</li>
</ul>
<br/>

<h3 id="주의사항">주의사항</h3>
<ul>
<li>Reflection 사용에 따른 오버헤드: Fixture Monkey는 테스트 환경용으로 설계. 운영 코드에는 포함하지 않는 것을 권장. Reflection 기반이기 때문에 객체 직접 생성보다 느릴 수 있다. + 고성능이 중요한 대규모 테스트도 좀...</li>
<li>set(), giveMeBuilder() 등 방법에 따라 불필요한 데이터 생성되지 않도록 주의할 필요</li>
</ul>
<br/>

<h3 id="레퍼런스">레퍼런스</h3>
<p><a href="https://naver.github.io/fixture-monkey/v1-1-0/">네이버 공식 레퍼런스</a>
<a href="https://codinghejow.tistory.com/419">[Java] Fixture Monkey 사용해보기</a>
<a href="https://oliveyoung.tech/2024-04-01/testcode-use-fixture-monkey/">올리브영 테크블로그</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드 통신 디자인패턴]]></title>
            <link>https://velog.io/@danielyang-95/%EB%B0%B1%EC%97%94%EB%93%9C-%ED%86%B5%EC%8B%A0-%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@danielyang-95/%EB%B0%B1%EC%97%94%EB%93%9C-%ED%86%B5%EC%8B%A0-%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 14 Oct 2024 14:42:23 GMT</pubDate>
            <description><![CDATA[<h2 id="short-polling"><strong>Short Polling</strong></h2>
<ul>
<li>클라이언트가 주기적으로 서버에 요청을 보내 최신 데이터를 받음</li>
<li>연결은 요청마다 새로 생성</li>
<li>실시간성이 낮고, 트래픽이 많아질 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/c07990f5-ca91-46d9-8a10-2f3cb89f3353/image.png" alt=""></p>
<ul>
<li>Http Request Connection<ul>
<li>Request를 계속 보내기 때문에 클라이언트가 많아진다면?</li>
<li>연결, 끊기 자체가 부담스럽다</li>
<li>실시간 정도의 빠른 응답 기대하기 어렵다</li>
</ul>
</li>
<li>Http 오버헤드 발생</li>
<li>유용: 일정하게 갱신되는 서버 데이터의 경우</li>
</ul>
<br/>

<p>💡 Http OverHead? = 이 사람이 좋은 사람인지 알아보는 시간</p>
<ul>
<li>정보의 신뢰성을 판단하기 위한 보내지는 헤더 정보(전송 데이터와 관련없는 정보)로 데이터량, 처리시간이 증가함</li>
</ul>
<h2 id="long-polling"><strong>Long Polling</strong></h2>
<ul>
<li>클라이언트가 서버에 요청을 보내고, 서버는 새 데이터가 생길 때까지 연결을 유지     </li>
<li>데이터가 생기면 응답 후 연결 종료, 클라이언트는 다시 요청     </li>
<li>실시간성이 높으나, 연결 재설정 오버헤드 존재</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/37acdb6a-5825-46d3-bf3f-b426659a9f9b/image.png" alt=""></p>
<h2 id="sse-server-sent-events"><strong>SSE (Server-Sent Events)</strong></h2>
<ul>
<li>서버가 클라이언트에 일방향으로 지속적으로 데이터를 푸시   </li>
<li>HTTP 기반, 브라우저 지원, 자동 재연결     </li>
<li>실시간 피드, 알림 등에 적합</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/244d50eb-1bf7-49db-bc28-b6cb1cc6c622/image.png" alt=""></p>
<h2 id="websocket"><strong>WebSocket</strong></h2>
<ul>
<li>클라이언트-서버 간 양방향, 지속적 연결  </li>
<li>실시간 채팅, 게임 등 빠른 반응이 필요한 서비스에 적합</li>
</ul>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/76fbd497-ffd7-4424-911e-35fa81461924/image.png" alt=""></p>
<h2 id="요청-응답rest-api-등"><strong>요청-응답(REST API 등)</strong></h2>
<pre><code>- 요청 후 응답을 기다리는 동기적 방식

- 단순하고 범용적, CRUD 작업에 적합</code></pre><h2 id="publish-subscribe-pubsub"><strong>Publish-Subscribe (Pub/Sub)</strong></h2>
<pre><code>- 메시지 브로커를 통해 여러 서비스가 비동기적으로 데이터 교환

- 대규모, 분산 시스템에서 이벤트 기반 처리에 적합</code></pre><br/>


<h2 id="주요-차이점">주요 차이점</h2>
<h3 id="pollingshort-vs-long-polling">Polling(short) vs Long polling</h3>
<ol>
<li><p>응답 대기 시간
Polling: 즉시 응답을 받는다. (데이터의 유무와 관계없이)
Long Polling: 새 데이터가 있을 때까지 기다린다.</p>
</li>
<li><p>서버 부하
Polling: 주기적인 요청으로 서버 부하가 높다.
Long Polling: 불필요한 요청을 줄여 서버 부하를 감소시킨다.</p>
</li>
<li><p>실시간성
Polling: 주기적 요청으로 인한 지연이 있다.
Long Polling: 데이터 발생 즉시 응답하여 더 실시간에 가깝다</p>
</li>
</ol>
<br/>


<h3 id="http-vs-websocket">HTTP vs WebSocket</h3>
<ol>
<li><p>통신 방식<br>HTTP는 단방향 통신으로, 클라이언트가 요청을 보내면 서버가 응답한다. WebSocket은 양방향 통신이 가능해서, 클라이언트와 서버가 실시간으로 데이터를 주고받을 수 있다.</p>
</li>
<li><p>연결 유지<br>HTTP는 비연결성 프로토콜이라서, 각 요청-응답 후 연결이 종료된다. WebSocket은 한 번 연결을 맺으면 그 연결을 계속 유지해서 실시간 통신이 가능하다.</p>
</li>
<li><p>오버헤드<br>HTTP는 각 요청마다 헤더 정보를 포함해야 해서 오버헤드가 크다. WebSocket은 초기 연결 설정 이후에는 최소한의 오버헤드로 데이터를 주고받는다.</p>
</li>
<li><p>실시간성<br>HTTP는 실시간 업데이트를 하려면 폴링 같은 추가 기술이 필요하다. WebSocket은 기본적으로 실시간 통신을 지원한다.</p>
</li>
<li><p>사용 사례<br>HTTP는 웹 페이지 로딩, RESTful API 등 전통적인 웹 애플리케이션에 적합하다. WebSocket은 실시간 채팅, 온라인 게임, 주식 시세 업데이트 등 실시간 데이터 교환이 필요한 애플리케이션에 적합하다.[</p>
</li>
<li><p>성능<br>HTTP는 연결 설정과 해제 때문에 지연이 발생할 수 있다. WebSocket은 연결을 계속 유지하기 때문에 지연이 적고 처리량이 높다</p>
</li>
</ol>
<br/>


<h2 id="reference">Reference</h2>
<p><a href="https://velog.io/@dev_jazziron/Polling-Long-Polling-SSE-WebSocket">https://velog.io/@dev_jazziron/Polling-Long-Polling-SSE-WebSocket</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코테 준비를 통한 알고리즘 찍먹]]></title>
            <link>https://velog.io/@danielyang-95/%EC%BD%94%ED%85%8C-%EC%A4%80%EB%B9%84%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B0%8D%EB%A8%B9</link>
            <guid>https://velog.io/@danielyang-95/%EC%BD%94%ED%85%8C-%EC%A4%80%EB%B9%84%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%B0%8D%EB%A8%B9</guid>
            <pubDate>Mon, 23 Sep 2024 11:44:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>알고리즘을 어떻게 공부할지 몰라 일단은 코딩테스트 연습을 하면서 익히고 있다.</p>
</blockquote>
<h1 id="프로그래머스-1단계-ing">프로그래머스 1단계 ing</h1>
<h2 id="stream-vs-arraylist-for문">Stream vs ArrayList, for문</h2>
<ul>
<li>stream을 사용했을 때, 코드의 가독성, 유지보수성은 좋아보인다. 하지만, 문제를 풀다보면 그냥 ArrayList 가지고 for문 로직으로 했을 때보다 속도가 많이 느리기도 했다.</li>
</ul>
<h3 id="stream">Stream</h3>
<ul>
<li>Stream은 데이터의 필터링, 매핑, 변환 등을 수행하기 위한 함수형 프로그래밍 API</li>
<li>ArrayList보다 유리한 경우</li>
</ul>
<ol>
<li>대용량 데이터를 병렬 처리할 때 훨씬 빠를 수 있다. ex) parallelStream()</li>
<li>다양한 필터링 및 변환 작업<ul>
<li>Stream API는 필터링, 매핑, 변환 등 복잡한 작업을 함수형 스타일로 간결하게 표현할 수 있으며, JVM이 최적화할 수 있는 기회를 제공</li>
</ul>
</li>
<li>코드의 가독성과 유지보수성</li>
</ol>
<h3 id="arraylist">ArrayList</h3>
<ul>
<li>ArrayList는 데이터를 저장하고 수정하는 데 사용되는 컬렉션 클래스</li>
<li>Stream보다 유리한 경우</li>
</ul>
<ol>
<li>단순 반복 및 액세스 작업</li>
<li>오버헤드가 적은 경우<ul>
<li>Stream은 중간 연산 (map, filter 등)과 종단 연산 (forEach, reduce 등)을 필요로 하는데, 이러한 과정에서 추가적인 오버헤드가 발생할 수 있습니다. </li>
<li>반면에 ArrayList는 별도의 중간 연산 없이 데이터를 다룰 수 있으므로 오버헤드가 적습니다.   <ol start="3">
<li>간단한 처리 작업</li>
</ol>
</li>
</ul>
</li>
</ol>
<h2 id="직접-알고리즘으로-푸는-것과-이미-구현된-함수로-푸는-것-중-무엇이-나을까">직접 알고리즘으로 푸는 것과 이미 구현된 함수로 푸는 것 중 무엇이 나을까?</h2>
<h2 id="조심해야할-에러">조심해야할 에러</h2>
<ul>
<li>Exception in thread &quot;main&quot; java.util.ConcurrentModificationException<ul>
<li>for문에 쓰이는 arrayList에 새롭게 add하려고 하기에</li>
</ul>
</li>
<li>NumberFormatException<ul>
<li>주어진 n이 너무 크면 int의 범위를 벗어난다 =&gt; long 타입 활용</li>
</ul>
</li>
</ul>
<h2 id="문제-풀면서-발견한-공식">문제 풀면서 발견한 공식?</h2>
<ul>
<li>홀수와 짝수의 구분: x % 2 == 0</li>
<li>숫자로만 이루어졌는지 확인<ol>
<li>Double.parseDouble(), Integer.parseInt(s) 메소드 try-catch</li>
<li>str.matches(&quot;[+-]?\d<em>(\.\d+)?&quot;), s.matches(&quot;(^[0-9]</em>$)&quot;)</li>
<li>str.chars().allMatch(Character::isDigit)</li>
</ol>
</li>
<li>소수 찾기: </li>
<li>약수 구하기: 제곱근까지 % i == 0 인 것들을 모으고, 나머지 뒷부분인 n / x 들을 더해준다.</li>
<li>숫자의 각 자리 구하기: n % 10 을 해서 나머지를 구하고, n = n / 10</li>
<li>char를 활용하여 숫자 문자열 -&gt; 숫자 : char - 48 ex) &quot;0&quot; -&gt; 0</li>
<li>제곱인지 아닌지 : Math.sqrt(n)가 double타입인데, if(Math.sqrt(n)%1 ==0) ⇒ 완벽한 제곱근</li>
</ul>
<h2 id="지향점">지향점</h2>
<ul>
<li>변수를 할당하는데, 1번만 사용된다면 할당하지않고 사용해보자.</li>
<li>속도: 불필요한 변환 제거</li>
</ul>
<h2 id="알아두면-좋은-함수-클래스">알아두면 좋은 함수, 클래스</h2>
<ul>
<li>날짜: LocalDate, DayOfWeek</li>
<li>숫자: 제곱근(Math.sqrt), 제곱(Math.pow), 대소비교(Math.min)</li>
<li>Char<ul>
<li>배열: 문자열.chars(), 문자열.toCharArray()</li>
<li>특정: 문자열.charAt()</li>
<li>대소문자 구분: Character.isLowerCase(ch)</li>
</ul>
</li>
<li>배열<ul>
<li>정렬: Arrays.sort()</li>
</ul>
</li>
<li>문자열<ul>
<li>정렬: StringBuilder.reverse()</li>
<li>대소문자 구분 x: equalsIgnoreCase()</li>
<li>반복: repeat()</li>
</ul>
</li>
<li>형변환<ul>
<li>int[] -&gt; List<Integer> : Arrays.stream(arr).boxed().collect(Collectors.toList())<ul>
<li>Long.parseLong, Integer.parseInt()</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="stream-1">Stream</h3>
<ul>
<li>collect(): Collectors.toList(), StringBuilder 대신 Collectors.joining()</li>
<li>정렬: sorted()</li>
</ul>
<h2 id="용어">용어</h2>
<ul>
<li>약수: divisor</li>
<li>배수: multiple</li>
<li>소수: prime</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[토비의 스프링 이해와 원리]]></title>
            <link>https://velog.io/@danielyang-95/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9D%B4%ED%95%B4%EC%99%80-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@danielyang-95/%ED%86%A0%EB%B9%84%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9D%B4%ED%95%B4%EC%99%80-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 21 Aug 2024 03:25:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>공부하기 전 나의 상황</p>
</blockquote>
<ul>
<li>스프링부트에 중독되어 스프링의 동작 원리가 가물가물해졌다.</li>
<li>스프링의 다양한 기술을 사용해보았으나, 여기에 적용된 공통적인 동작 원리까지 생각하진 못했다.</li>
</ul>
<blockquote>
<p>결론</p>
</blockquote>
<ul>
<li>스프링이 개발자에게 무엇을 요구하고 어떤 코드를 기대하는지 알게 되었다.</li>
<li>더 격하게 테스크 코드를 활용하고 싶어졌다.</li>
<li>라이브러리와 프레임워크 사용 시, 문제 제기 &gt; 해결 시도 &gt; 리팩토링 &gt; 라이브러리와 프레임워크가 어떻게 이를 적용하는지 생각!</li>
<li>스프링이 유용한 이유는 여러 설계 원칙, 그리고 디자인 패턴들이 우리가 작성하는 코드에 잘 녹아져서 우리가 만드는 코드의 품질을 높이고 유지보수성을 뛰어나게하고 OCP에 부합하도록 잘 이끄도록 한다.</li>
<li>다른 회사에서 우리 코드를 라이브러리 형태로 가져다쓰고 싶을 것을 가정하여 리팩토링하자.</li>
</ul>
<br/>

<h2 id="관심사">관심사</h2>
<ul>
<li>여러 관심사가 섞혀있는 코드를 읽으면 보기가 어렵다.</li>
<li>언젠가 변경될 수 있는 가능성을 염두해야한다.</li>
</ul>
<h4 id="변경되는-시점이유에-따라-분리하자">변경되는 시점/이유에 따라 분리하자</h4>
<ol>
<li>사용하는 방법(기술)이 달라지거나</li>
<li>비즈니스 룰/로직이 달라지거나</li>
</ol>
<h4 id="관심사-분리-방법점진적-개선">관심사 분리 방법(점진적 개선)</h4>
<ul>
<li>메서드 분리</li>
<li>상속을 통한 확장 ex) abstract<ul>
<li>만약 로직이 추가된다면?</li>
</ul>
</li>
<li>클래스 분리<ul>
<li>클래스마다 제공하는 메서드명이 달라질 수 있다.</li>
</ul>
</li>
<li><strong>인터페이스 도입</strong>(메서드명 통일)<ul>
<li>여전히 클래스 생성시, 코드 수정해야하는 문제</li>
</ul>
</li>
<li><strong>관계설정 책임의 분리</strong><ul>
<li>생성자 주입을 통해 결합 느슨하게</li>
<li>제어권 이전: 관계설정 책임을 다른 곳에서 진행 ex) 클라이언트</li>
<li>허나, 클라이언트가 메인 메서드와 관계설정 두가지 관심사 갖고있다.</li>
</ul>
</li>
<li><strong>오브젝트 팩토리</strong> : 스프링의 핵심</li>
</ul>
<br/>

<h2 id="오브젝트와-의존관계">오브젝트와 의존관계</h2>
<ul>
<li>스프링이 다루는 것 중 제일 관심있는 것은 오브젝트며, 우리를 가장 많이 도와주는 것은 의존관계이다.</li>
<li>오브젝트 사이에서 대신 해달라는 의존관계들이 런타임에서 발생한다.</li>
</ul>
<br/>

<h4 id="스프링-컨테이너와-의존관계-주입">스프링 컨테이너와 의존관계 주입</h4>
<ul>
<li>스프링 컨테이너<ul>
<li>가장 핵심, 가장 중요하고 모든 것에 기반이 되는 스프링의 기능</li>
<li>특징: IoC, DI 를 제공</li>
<li>구성정보(Configuration MetaData)을 통해 생성 ex) config, @Component, @ComponentScan</li>
<li><strong>BeanFactory</strong>: Bean을 한번 생성해서 리턴해주고 끝나는게 아니라 얘를 계속 담아두는 컨테이너이기도 하다. = IoC 컨테이너, 스프링 컨테이너, 스프링
  =&gt; 싱글톤이 쉽다.</li>
</ul>
</li>
</ul>
<br/>




<h2 id="기타">기타</h2>
<h4 id="ocp-개방-폐쇄-원칙">OCP: 개방 폐쇄 원칙</h4>
<ul>
<li>클래스나 모듈은 확장에는 열려 있어야하고 변경에는 닫혀있어야한다.
  ⇒ <strong><code>어떤 클래스는 이 클래스의 기능을 확장할 때 그 클래스의 코드는 변경되면 안 된다.</code></strong></li>
</ul>
<br/>

<h4 id="전략-패턴">전략 패턴</h4>
<ul>
<li>자신의 기능 맥락(context)에서, 필요에 따라서 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴</li>
</ul>
<br/>

<h4 id="데코레이터-패턴">데코레이터 패턴</h4>
<ul>
<li>구성정보만 바뀌었지, 기존 코드는 변경점이 없도록</li>
<li>A---&gt;B 의존 시, 새로운 로직이 필요하다면 A나 B 변경 없이 A---&gt;C---&gt;B 처럼 C를 통해 A는 B의 메서드 그대로 사용가능하고, C는 B의 메서드에 로직 추가 등 진행할 수 있다.</li>
<li>DIP 원칙을 실현하는 방법 중 하나</li>
</ul>
<br/>

<h4 id="의존성-역전-원칙dip">의존성 역전 원칙(DIP)</h4>
<ul>
<li>기존 구조는 아래 모듈에서 변경이 일어날 때 상위 모듈에서 변경이 필요해진다. </li>
<li>고수준 모듈: 중요한 정책을 정의하거나 비즈니스 로직을 담당하는 모듈.</li>
<li>저수준 모듈: 실제로 작업을 수행하는 구현 세부 사항을 가진 모듈.</li>
<li>목표: DIP는 시스템의 유연성과 확장성을 높이기 위해, 고수준 모듈이 저수준 모듈에 직접 의존하지 않고, 추상화된 인터페이스를 통해 의존성을 주입받도록 설계합니다.</li>
<li>인터페이스를 이를 구현한 클래스 쪽 모듈에 두는 것이 아니라 사용하는 클라이언트 쪽에 역전한다.(인터페이스 소유권의 이전)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[타임리프를 쓰면서 느낀점]]></title>
            <link>https://velog.io/@danielyang-95/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EB%A5%BC-%EC%93%B0%EB%A9%B4%EC%84%9C-%EB%8A%90%EB%82%80%EC%A0%90</link>
            <guid>https://velog.io/@danielyang-95/%ED%83%80%EC%9E%84%EB%A6%AC%ED%94%84%EB%A5%BC-%EC%93%B0%EB%A9%B4%EC%84%9C-%EB%8A%90%EB%82%80%EC%A0%90</guid>
            <pubDate>Sun, 30 Jun 2024 10:46:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전 회사에서는 풀스택 개발자로서 UI 개발은 SPA 기반인 React나 Next.js로 개발을 주로 했었다. 하지만, 이번 회사에서는 기술스택으로서 타임리프를 사용하게 되었다. 타임리프를 제대로 사용해본 것은 처음이기에 배웠던 점, 느꼈던 점 위주로 작성한다.</p>
</blockquote>
<h3 id="vs-spa-프레임워크">vs SPA 프레임워크</h3>
<ul>
<li><p>동기 통신</p>
<ul>
<li>SPA는 비동기로 모든통신을 해야하지만 타임리프는 동기화방식으로 통신</li>
</ul>
</li>
<li><p>개발 방식</p>
<ul>
<li>MPA vs SPA</li>
</ul>
</li>
<li><p>HTML 호출 방식</p>
<h3 id="mpa">MPA</h3>
<ul>
<li>브라우저(크롬) 주소 창에 url 을 입력 </li>
<li>그럼 컨트롤러(C)에서 디비 데이터 가져와 모델(M)에 넣은 후, 뷰(타임리프, JSP/JSTL) 에서 모델에 담긴 데이터를 출력 </li>
<li>스프링은 이걸 HTML 문서로 만들어 반환</li>
<li>크롬은 HTML 문서를 받았으니 화면에 출력. 
  =&gt; 이걸 Spring MVC 라 하고, 서버에서 HTML 문서를 만들었으니 SSR라 한다. <h3 id="spa">SPA</h3>
</li>
<li>자바 스크립트로 데이터(json) 만 요청. ex) fetch, axios, ajax </li>
<li>컨트롤러는 디비 데이터를 가져와 json 문서로 만들어 반환. 
  =&gt; 모델, 뷰 가 필요없다. </li>
<li>클라이언트는 Data를 반환 받아 조합해서 html 문서를 만든다. 클라이언트에서 html 문서를 만들었으니 CSR 이라한다. 이 과정을 프레임워크로 만든 게 react, vue 등.</li>
</ul>
</li>
</ul>
<h3 id="장단점-비교">장단점 비교</h3>
<p><strong>- Thymeleaf</strong>
장점: 서버 사이드 렌더링, SEO 친화성, 간단한 설정 및 통합, 전통적인 웹 애플리케이션에 적합
단점: 동적 상호작용 및 클라이언트 측 성능에 한계.</p>
<p><strong>- SPA 프레임워크</strong>
장점: 동적이고 복잡한 UI, 빠른 사용자 경험, 모듈성, 재사용성, 풍부한 생태계
단점: SEO 문제, 초기 설정의 복잡성, 번들 크기</p>
<br/>

<h3 id="고민점">고민점</h3>
<ul>
<li>모듈화<ul>
<li>Thymeleaf에서 재사용 가능한 컴포넌트를 만들기 위해 템플릿 조각(template fragments)을 사용 가능. 
ex) th:fragment, th:insert, th:replace 등</li>
</ul>
</li>
<li>동적 랜더링(되도록 새로고침없이) 문제</li>
<li>디자이너와의 협업: 문법이 jsp보다 더 html에 가까워서 퍼블이랑 협업하기 용이하다?</li>
<li>JSP보다 프론트 작업 편이: 서버사이드에서 동적으로 생성되는 부분을 제외하면 서버 없이도 일단 페이지를 브라우저로 열어볼수 있다. 스프링부트에서 밀어주는거라 설정이 jsp보다 간편하다.</li>
<li>데이터를 어디까지 ModelAttribute로 가져올지, 아니면 비동기로 가져올 것인지? 비동기로 가져올 때 UX 개선을 위해 Skeleton 애니메이션 혹은 Loading 등을 활용할 수 있다.</li>
</ul>
<br/>

<h3 id="알아두면-용이한-문법">알아두면 용이한 문법</h3>
<pre><code class="language-html"># 1. 에러메시지
&lt;form action=&quot;#&quot; th:action=&quot;@{/submit}&quot; th:object=&quot;${user}&quot; method=&quot;post&quot;&gt;
    &lt;div&gt;
        &lt;label&gt;Name:&lt;/label&gt;
           // &quot;*{name}&quot; =&gt; user.name
        &lt;input type=&quot;text&quot; th:field=&quot;*{name}&quot; /&gt;
        &lt;div th:if=&quot;${#fields.hasErrors(&#39;name&#39;)}&quot; th:errors=&quot;*{name}&quot;&gt;Name Error&lt;/div&gt;
    &lt;/div&gt;
    &lt;div&gt;
        &lt;label&gt;Email:&lt;/label&gt;
        &lt;input type=&quot;text&quot; th:field=&quot;*{email}&quot; /&gt;
        // parameter에 따른 에러메시지 관리 가능
        &lt;div th:if=&quot;${#fields.hasErrors(&#39;email&#39;)}&quot; th:errors=&quot;*{email}&quot;&gt;Email Error&lt;/div&gt;
    &lt;/div&gt;
    &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;

# 2. th:field
# th:field를 사용하면 HTML 태그의 id, name, value 속성을 자동 처리해준다.
- 렌더링 전
&lt;input type=&quot;text&quot; th:field=&quot;*{itemName}&quot; /&gt;

- 렌더링 후
&lt;input type=&quot;text&quot; id=&quot;itemName&quot; name=&quot;itemName&quot; th:value=&quot;*{itemName}&quot; /&gt;


# 3. $, # 같이 쓰기
```html
// 이렇게 $와 #를 동시에 사용가능
&lt;td
    class=&quot;@@&quot;
    th:text=&quot;${a[0].type} == 3 ? 
            #{b.c} : 
            #{b.d}&quot;
&gt;&lt;/td&gt;
</code></pre>
<p>``` </p>
<br/>

<h3 id="참고">참고</h3>
<p><a href="https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#introducing-thymeleaf">타임리프 공식 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이메일 유효성 검증 by MX 레코드]]></title>
            <link>https://velog.io/@danielyang-95/%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D-by-MX-%EB%A0%88%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@danielyang-95/%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D-by-MX-%EB%A0%88%EC%BD%94%EB%93%9C</guid>
            <pubDate>Mon, 24 Jun 2024 13:59:53 GMT</pubDate>
            <description><![CDATA[<h1 id="이메일-검증의-필요성">이메일 검증의 필요성</h1>
<blockquote>
<h3 id="이메일-검증이-왜-필요할까">이메일 검증이 왜 필요할까?</h3>
<p>이메일 검증을 통해 해결할 수 있는 문제들이 있다.</p>
</blockquote>
<ul>
<li>유저의 이메일 기재 실수</li>
<li>이메일을 통해 뭔가 하려고 할 때</li>
<li>악의적인 이메일 방지 등</li>
</ul>
<br/>

<h1 id="이메일-검증은-어디까지-해야할까">이메일 검증은 어디까지 해야할까?</h1>
<ul>
<li>이메일 형식 유효성 검사</li>
<li>이메일 길이 유효성 검사</li>
<li>이메일 도메인 주소가 유효한지</li>
<li>이메일이 실제 유효한 이메일인지? (메일을 수신할 수 있는지)</li>
</ul>
<br/>


<h2 id="이메일-검증-방법">이메일 검증 방법</h2>
<h3 id="이메일-형식-유효성-검증">이메일 형식 유효성 검증</h3>
<h4 id="1-정규-표현식-사용">1. 정규 표현식 사용</h4>
<ul>
<li>Java: 자바에서는 정규식을 활용해 문자열을 검증, 탐색을 돕는 Pattern, Matcher 클래스를 제공
  (1) Pattern : 정규 표현식이 컴파일된 클래스. 정규 표현식에 대상 문자열을 검증하거나, 활용하기 위해 사용되는 클래스
  (2) Matcher : Pattern클래스를 받아 대상 문자열과 패턴이 일치하는 부분을 찾거나 전체 일치 여부 등을 판별하기 위해 사용된다. </li>
</ul>
<h4 id="2-라이브러리-활용">2. 라이브러리 활용</h4>
<p>JavaScript: validator
Java: Apache Commons Validator
    - Validator 인터페이스를 상속받아 구현체를 작성</p>
<h3 id="이메일-서버-확인-mx-레코드-검증">이메일 서버 확인 (MX 레코드 검증)</h3>
<p>이메일 서버 확인 = MX 레코드(Mail Exchange Record) 검증</p>
<ul>
<li><p>MX 레코드는 도메인 이름 시스템(DNS)에서 특정 도메인에 대해 <em><strong>이메일을 수신할 메일 서버를 지정</strong></em> 하는 데 사용한다. 즉, 어떤 이메일이 특정 도메인으로 발송될 때, 해당 이메일을 어떤 메일 서버가 처리할지를 결정합니다. 
=&gt; 이를 통해 이메일이 제대로 전달될 수 있음을 보장한다.</p>
</li>
<li><p>MX 레코드를 조회하는 방법
Python: dnspython 라이브러리
Java: JNDI (Java Naming and Directory Interface) </p>
</li>
</ul>
<br/>

<h3 id="smtp-프로토콜을-이용한-이메일-존재-여부-확인">SMTP 프로토콜을 이용한 이메일 존재 여부 확인</h3>
<ul>
<li>SMTP(Simple Mail Transfer Protocol)는 인터넷 표준 전자 메일 전송 프로토콜이다. 클라이언트가 메일 서버와 통신하여 이메일을 전송하는 데 사용. TCP 포트 25를 사용하며, 클라이언트가 서버에 접속하여 메일 전송 요청을 보내고, 서버는 이를 처리하여 수신자에게 전달한다. 또한, SMTP 프로토콜은 이메일 주소의 존재 여부를 확인하는 데도 사용할 수 있다. </li>
</ul>
<br/>

<h4 id="smtp를-이용한-이메일-주소-존재-여부-확인-방법">SMTP를 이용한 이메일 주소 존재 여부 확인 방법</h4>
<p>단계</p>
<ul>
<li>SMTP 서버에 연결: 이메일 주소의 도메인에 해당하는 메일 서버에 연결합니다.</li>
<li>HELO/EHLO 명령: 서버와의 초기 통신을 설정합니다.</li>
<li>MAIL FROM 명령: 이메일 발신자를 지정합니다.</li>
<li>RCPT TO 명령: 검증하고자 하는 이메일 수신자를 지정합니다.</li>
<li>서버 응답 확인: 서버의 응답 코드를 확인하여 이메일 주소의 존재 여부를 판단합니다.</li>
<li>QUIT 명령: 서버와의 연결을 종료합니다.</li>
</ul>
<br/>

<h4 id="한계chatgpt">한계(chatGPT)</h4>
<ul>
<li>서버의 제한: 많은 이메일 서버는 스팸 방지를 위해 SMTP 검증을 제한하거나 차단할 수 있습니다. 일부 서버는 연결 시도를 감지하고 차단할 수도 있습니다.</li>
<li>프라이버시 및 법적 이슈: 이메일 주소의 유효성을 확인하기 위해 SMTP 검증을 사용하는 것은 프라이버시 침해로 간주될 수 있습니다. 사용자 동의 없이 이 방법을 사용하는 것은 법적 문제가 발생할 수 있습니다.</li>
<li>정확성: 일부 서버는 모든 RCPT TO 명령에 대해 긍정적인 응답을 반환하여 이메일 주소의 유효성을 실제로 확인할 수 없도록 합니다.</li>
</ul>
<br/>

<h3 id="이메일-검증-api-사용">이메일 검증 API 사용</h3>
<ul>
<li>실제 유효한 이메일인지 검증하려면 결국 상용 API를 쓸 수밖에 없다. Hunter.io나 ZeroBounce 같은 것들이 해당된다. </li>
</ul>
<br/>





<h2 id="mx-레코드를-사용하게-된-이유">MX 레코드를 사용하게 된 이유</h2>
<blockquote>
<p>프로젝트를 진행하고 있는데, 상용 API 신청이 가장 쉬워보이나, 신청 + 개발 + 테스트 + 금액 이슈로 인해 상용 API는 사용할 수 없게 되었다. 따라서, 실제 유효한 이메일인지는 체크하기 어려워진 상황이다. 이메일 형식, 길이 체크는 너무나 간단하니, 남은 것은 이메일 도메인이 유효한 지와 SMTP 프로토콜을 통해 이메일 존재를 확인하는 것이었다. 하지만, SMTP 프로토콜은 스팸 문제로 인해 특정 이메일 도메인들은 그냥 전부 success, fail 등으로 내고 있다. 그래서, 
이메일 도메인이 유효한 지 체크하는 방법만이 남았다. </p>
</blockquote>
<pre><code>public static boolean checkMXRecord(String domain) {
        try {
            Hashtable&lt;String, String&gt; env = new Hashtable&lt;&gt;();
            env.put(&quot;java.naming.factory.initial&quot;, &quot;com.sun.jndi.dns.DnsContextFactory&quot;);
            DirContext ictx = new InitialDirContext(env);
            Attributes attrs = ictx.getAttributes(domain, new String[]{&quot;MX&quot;});
            Attribute attr = attrs.get(&quot;MX&quot;);

            if (attr == null) {
                return false;
            }

            return true;
        } catch (NamingException e) {
            return false;
        }
    }</code></pre><ul>
<li>위와 같은 코드로 금방 MX 레코드를 조회할 수 있다. </li>
<li>하지만, 가끔 사이트들을 보면 naver.co, gmail.co 등 도메인 주소를 전부 입력하지않았음에도 자연스럽게 리다이렉트되도록 하는 주소들이 있다. <ul>
<li>이 경우에는 attr이 null이 아니지만 MX: 0이 나와버려 이메일 수신과 거리가 먼 상황이다. 따라서, MX:0을 포함하는 경우에는 false가 되도록 처리가 되어야할 것이다.</li>
</ul>
</li>
</ul>
<h4 id="gmailcom-vs-gmailco">gmail.com vs gmail.co</h4>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/665e9214-4306-4b6d-89dc-a1df31d0d873/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/danielyang-95/post/e5dfe499-15e6-41bc-907f-a1912cacd268/image.png" alt=""></p>
<br/>

<h1 id="참고자료">참고자료</h1>
<p><a href="https://intodns.com/gmail.com">intoDNS - check DNS server and mail server health</a>
<a href="https://likedev.tistory.com/entry/MX%EB%A0%88%EC%BD%94%EB%93%9C-%ED%99%95%EC%9D%B8-%EB%B0%A9%EB%B2%95">MX레코드 확인하는 방법</a>
<a href="https://girawhale.tistory.com/77">java pattern, matcher</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[직렬화와 역직렬화]]></title>
            <link>https://velog.io/@danielyang-95/%EC%A7%81%EB%A0%AC%ED%99%94-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94</link>
            <guid>https://velog.io/@danielyang-95/%EC%A7%81%EB%A0%AC%ED%99%94-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94</guid>
            <pubDate>Fri, 07 Jun 2024 07:03:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>위 내용은 스프링 개발자로서의 관점에서 작성하였습니다. </p>
</blockquote>
<h2 id="직렬화와-역직렬화란">직렬화와 역직렬화란?</h2>
<p>서로 다른 비즈니스 서비스 간의 통신은 인터페이스를 통해 구현되어야 합니다. 두 서비스 간에 데이터 객체를 공유하려면 객체를 이진 스트림으로 변환한 후 네트워크를 통해 다른 서비스로 전송하고, 다시 객체로 변환하여 서비스 메서드에서 사용할 수 있어야 합니다. 이 인코딩 및 디코딩 과정을 직렬화(Serialization) 및 역직렬화(Deserialization).</p>
<h3 id="📌-직렬화serialization가-사용되는-경우"><strong>📌 직렬화(Serialization)가 사용되는 경우</strong></h3>
<p>➡ <strong>객체 데이터를 저장하거나 전송할 때 필요</strong></p>
<h3 id="📌-역직렬화deserialization가-사용되는-경우"><strong>📌 역직렬화(Deserialization)가 사용되는 경우</strong></h3>
<p>➡ <strong>저장된 객체 데이터를 다시 사용하거나, 전송받은 데이터를 객체로 변환할 때 필요</strong></p>
<table>
<thead>
<tr>
<th>사용 사례</th>
<th>직렬화</th>
<th>역직렬화</th>
</tr>
</thead>
<tbody><tr>
<td><strong>캐싱</strong></td>
<td>객체 데이터를 Redis에 저장</td>
<td>Redis에서 불러와 객체로 변환</td>
</tr>
<tr>
<td><strong>네트워크 통신</strong></td>
<td>객체 → JSON 변환 (API 응답)</td>
<td>JSON → 객체 변환 (API 요청 처리)</td>
</tr>
<tr>
<td><strong>파일 저장</strong></td>
<td>객체 데이터를 파일에 저장</td>
<td>파일에서 객체를 불러와 복구</td>
</tr>
</tbody></table>
<br/>

<h2 id="어떤-직렬화를-써야하는가">어떤 직렬화를 써야하는가?</h2>
<h4 id="자바-직렬화는-x">자바 직렬화는 x</h4>
<ul>
<li>간단한 구현, 자바 간 통신에 적합, 클래스 정의 포함</li>
<li>But 언어 호환성, 보안 취약, 큰 직렬화 stream 크기, 낮은 직렬화 성능</li>
<li>특징<ul>
<li>직렬화된 데이터는 클래스 메타데이터와 객체 상태를 포함 -&gt; 출력 크기가 커질 수 있음.</li>
<li>JVM(Java Virtual Machine) 환경에 종속적</li>
<li>직렬화된 데이터는 클래스의 구조에 강하게 의존 =&gt; 역직렬화 시 데이터 호환성 문제, serialVersionUID로 귀찮게 관리</li>
</ul>
</li>
<li>보안 문제<ul>
<li>역직렬화 공격: 신뢰할 수 없는 데이터를 역직렬화할 때 보안 취약점을 노출</li>
<li>캡슐화 침해: private 필드까지 포함...</li>
</ul>
</li>
<li>그래도 써야한다면? 커스텀 직렬화 사용. 그리고 <code>Serializable</code> 을 구현하도록 클래스에 선언하면 직렬화 가능한 클래스가 된다. 데이터 호환성 문제 때문에 SerialVersionUID 명시하는게 좋다.</li>
<li><strong>JSON</strong>(Text 포맷) 혹은 <strong>Protobuf</strong>(구글, binary 포맷) 직렬화 추천</li>
</ul>
<br/>

<h4 id="자바-커스텀-직렬화">자바 커스텀 직렬화</h4>
<p>구현 핵심 원칙</p>
<ol>
<li><p>transient 필드 선언 : 직렬화 대상에서 제외</p>
<pre><code class="language-java">public class SecureData implements Serializable {
 private transient String secretKey;  // 직렬화 제외
 private String publicData;
}</code></pre>
</li>
<li><p>writeObject/readObject 메서드</p>
<pre><code class="language-java">private void writeObject(ObjectOutputStream oos) throws IOException {
 oos.defaultWriteObject();
 oos.writeObject(encrypt(publicData));  // 데이터 암호화
}
</code></pre>
</li>
</ol>
<p>private void readObject(ObjectInputStream ois) 
    throws ClassNotFoundException, IOException {
    ois.defaultReadObject();
    publicData = decrypt((String) ois.readObject());
}</p>
<pre><code>
3. 버전 관리 : 직렬 버전 UID를 명시적으로 선언하여 버전 관리 및 호환성을 유지
```java
private static final long serialVersionUID = 20230704L;  // 명시적 버전 지정</code></pre><br/>

<h4 id="json-장단점">JSON 장단점</h4>
<p>장점</p>
<ul>
<li>가독성: 사람이 읽을 수 있는 텍스트 형식으로 디버깅과 문제 해결이 용이</li>
<li>언어 독립성: 여러 플랫폼 및 언어 간 데이터 교환에 적합</li>
<li>웹 친화적</li>
</ul>
<p>단점</p>
<ul>
<li>텍스트 기반 형식이라 바이너리 형식보다 직렬화 및 역직렬화 속도가 느릴 수 있다.</li>
<li>Protobuf와 비교했을 때, 데이터 크기가 더 클 수 있다. </li>
</ul>
<br/>

<h4 id="protobuf-protocol-buffers">Protobuf (Protocol Buffers)</h4>
<p>장점</p>
<ul>
<li>고효율: 바이너리 형식으로 직렬화되어 JSON보다 3~5배 빠르고 데이터 크기가 작다.</li>
<li>다중 언어 지원: 플랫폼 간 호환성이 뛰어남.</li>
<li>스키마 기반: .proto 파일을 통해 데이터 구조를 정의하고 버전 관리를 지원합니다.</li>
<li>대규모 데이터 처리에 적합: 대량의 데이터를 효율적으로 처리할 수 있습니다.</li>
</ul>
<p>단점</p>
<ul>
<li>읽기 어려움: 바이너리 형식이라 사람이 읽기 어렵고 디버깅이 복잡할 수도.</li>
<li>설정 복잡성: .proto 파일 작성 및 컴파일 단계가 필요하며 초기 설정이 복잡할 수도</li>
</ul>
<br/>


<h2 id="싱글턴-방법">싱글턴 방법</h2>
<h3 id="readsolve-vs-enum">ReadSolve vs Enum</h3>
<h4 id="readresolve란">readResolve란?</h4>
<ul>
<li>직렬화된 객체를 역직렬화할 때 기존 인스턴스를 반환하게 만드는 메서드</li>
<li>보안 취약점과 복잡성이 존재<ul>
<li>readResolve가 호출되기 전에 필드가 먼저 역직렬화됨</li>
<li>공격자가 필드를 조작하여 싱글턴을 깨트릴 수 있</li>
</ul>
</li>
</ul>
<p>=&gt; transient 키워드로 민감한 정보나 참조를 직렬화에서 제외 가능</p>
<pre><code class="language-java">public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}

    private Object readResolve() {
        return INSTANCE; // 싱글턴 보장
    }
}</code></pre>
<h4 id="가장-안전한-싱글턴-방법-enum">가장 안전한 싱글턴 방법: enum</h4>
<ul>
<li>Java가 자체적으로 싱글턴을 관리</li>
<li>readSolve가 필요없으므로 직렬화 안전</li>
<li>간결함</li>
</ul>
<pre><code class="language-java">public enum Elvis {
    INSTANCE;
    public void sing() {
        System.out.println(&quot;🎤 Hound Dog&quot;);
    }
}</code></pre>
<br/>

<h2 id="직렬화-보안--직렬화-proxy-패턴">직렬화 보안 : 직렬화 Proxy 패턴</h2>
<p>추천 사례</p>
<ul>
<li>보안이나 불변 객체가 중요한 도메인 객체</li>
</ul>
<p>장점</p>
<ul>
<li>불변성 보장 : final 필드를 유지한 채 직렬화 가능</li>
<li>직렬화 제어: 원하지 않는 필드 포함 방지 가능</li>
<li>보안 강화 : 직접 역직렬화 방지로 공격 차단</li>
</ul>
<p>단점</p>
<ul>
<li>클라이언트 확장 불가</li>
<li>속도: 프록시 통해 객체를 생성하고 변환하기 때문에 방어적 복사보다 상대적 느릴 수 있다.</li>
<li>객체 그래프 순환에는 적용 불가</li>
</ul>
<pre><code class="language-java">import java.io.*;  

(원래 직렬화할 객체)
class Person implements Serializable {  
    private final String name;  
    private final int age;  

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

    📌 **이게 핵심! 직렬화를 직접 하지 않고, 대신 이 &quot;프록시&quot; 클래스를 이용함.**
    🔹 `Person` 객체를 **그대로 직렬화하면 보안 취약점**이 생길 수도 있음.  
    🔹 대신 **&quot;프록시&quot;**를 사용하면 안전하게 직렬화 가능!  
    🔹 프록시는 `name`, `age` 필드만 가지고 있음. (`Person` 객체와 동일한 데이터)
    // 직렬화 프록시 클래스  
    private static class SerializationProxy implements Serializable {  
        private static final long serialVersionUID = 1L;  
        private final String name;  
        private final int age;  

        SerializationProxy(Person p) {  
            this.name = p.name;  
            this.age = p.age;  
        }  

        // 역직렬화 시 원래 객체로 변환  
        private Object readResolve() {  
            return new Person(name, age);  
            }  
        }  

    // 직렬화할 때 호출되는데, 프록시를 반환하게 하고 있다.  
    // 이로 인해 바깥 클래스의 직렬화된 인스턴스를 생성할 수 없다.  
    private Object writeReplace() {  
        return new SerializationProxy(this);  
    }  

    // 직접 역직렬화 방지  
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {  
        throw new InvalidObjectException(&quot;프록시를 사용해야 합니다.&quot;);  
    }  

    @Override  
    public String toString() {  
        return &quot;Person{name=&#39;&quot; + name + &quot;&#39;, age=&quot; + age + &quot;}&quot;;  
    }  
}  

public class Main {  
    public static void main(String[] args) throws IOException, ClassNotFoundException {  
        Person person = new Person(&quot;Alice&quot;, 30);  

        // 직렬화  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream out = new ObjectOutputStream(bos);  
        out.writeObject(person);  
        out.close();  

        // 역직렬화  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream in = new ObjectInputStream(bis);  
        Person deserializedPerson = (Person) in.readObject();  
        in.close();  

        System.out.println(deserializedPerson);  
    }  
}

</code></pre>
<table>
<thead>
<tr>
<th>단계</th>
<th>실행되는 메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>1. 직렬화 시작</td>
<td><code>writeObject(person)</code></td>
<td><code>Person</code> 객체를 직렬화하려고 함</td>
</tr>
<tr>
<td>2. 직렬화 프록시 반환</td>
<td><code>writeReplace()</code></td>
<td><code>Person</code> 대신 <code>SerializationProxy</code>를 직렬화함</td>
</tr>
<tr>
<td>3. 파일 저장</td>
<td>-</td>
<td><code>SerializationProxy</code> 데이터가 파일(<code>person.ser</code>)에 저장됨</td>
</tr>
<tr>
<td>4. 역직렬화 시작</td>
<td><code>readObject()</code></td>
<td><code>SerializationProxy</code> 객체가 파일에서 읽힘</td>
</tr>
<tr>
<td>5. 원래 객체로 변환</td>
<td><code>readResolve()</code></td>
<td><code>SerializationProxy</code> → <code>Person</code> 객체로 변환</td>
</tr>
<tr>
<td>6. 복원된 객체 반환</td>
<td>-</td>
<td>최종적으로 <code>Person</code> 객체가 반환됨</td>
</tr>
</tbody></table>
<br/>

<h2 id="문제가-되는-곳">문제가 되는 곳</h2>
<p>오늘날 대부분의 백엔드 서비스는 마이크로서비스 아키텍처를 기반으로 구현된다. 서비스는 비즈니스 기능에 따라 분리되어 디커플링을 실현하지만, 이로 인해 새로운 과제가 생긴다</p>
<p>동시 요청이 많은 상황에서는 직렬화가 느리면 요청 응답 시간이 길어질 수 있으며, 직렬화된 데이터 크기가 크면 네트워크 처리량이 감소할 수 있다. 따라서 뛰어난 직렬화 프레임워크는 시스템의 전체 성능을 향상할 수 있다.</p>
<br/>


<h4 id="주의사항">주의사항</h4>
<ul>
<li><code>serialVersionUID</code>는 <strong>클래스 버전 확인용 출입증</strong></li>
<li>readObject 메서드는 방어적으로 작성<ul>
<li>readObject는 byte stream을 받아 객체를 생성하는 또 다른 생성자 역할(=숨은 생성자)</li>
<li>역직렬화가 생성자를 우회하여 객체를 생성하며 불변식이 깨질 수 있다. 생성자에서 수행되는 초기화나 유효성 검사가 이루어지지않는다. 
=&gt; readObject에서 방어적 복사, 유효성 검사 등 진행해야한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class Person implements Serializable {  
  private final String name; // 불변 필드  

  public Person(String name) {  
    this.name = name;  
    // 생성자에서 초기화 및 유효성 검사  
    if (name == null) {  
        throw new NullPointerException(&quot;Name cannot be null&quot;);  
  }  
}  

// 역직렬화 시 생성자가 호출되지 않으므로, name이 null일 수 있음  
}  


private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject(); // 기본 직렬화 수행

    // ✅ 방어적 복사 (기존 객체를 그대로 사용하지 않음)
    start = new Date(start.getTime());
    end = new Date(end.getTime());

    // ✅ 유효성 검사 (이상한 값이 들어오면 예외 발생)
    if (start.compareTo(end) &gt; 0) {
        throw new InvalidObjectException(&quot;시작 날짜가 종료 날짜보다 늦음!&quot;);
    }
}

</code></pre>
<br/>

<h2 id="기타">기타</h2>
<h4 id="binary-형식">Binary 형식</h4>
<ul>
<li>데이터를 컴퓨터가 바로 처리할 수 있는 형태로 저장하여 다음과 같은 이점이 있다.</li>
</ul>
<ol>
<li>작은 데이터 크기: 바이너리는 데이터를 압축된 형태로 표현합니다</li>
<li>빠른 파싱 속도: 컴퓨터는 바이너리를 직접 읽고 쓸 수 있으므로, 텍스트를 파싱하고 변환하는 과정이 필요 없어서 처리 속도 빠름</li>
<li>스키마 기반 설계: .proto 파일에서 데이터 구조(스키마)를 미리 정의<ul>
<li>필드 tag 사용</li>
<li>데이터 타입 최적화</li>
</ul>
</li>
<li>불필요한 공백 및 메타데이터 제거: 가독성 불필요</li>
<li>데이터를 직렬화할 때 고정된 순서와 최적화된 구조를 사용하여 처리 속도를 높인다. </li>
</ol>
<br/>


<h4 id="주요-직렬화-라이브러리-비교표-ai-추천">주요 직렬화 라이브러리 비교표 (AI 추천)</h4>
<table>
<thead>
<tr>
<th>라이브러리</th>
<th>주요 특징 및 장점</th>
<th>주요 단점/제약 사항</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Protobuf</strong></td>
<td>- 다중 언어 지원<br>- 고성능/소형 데이터<br>- 스키마 기반 구조<br>- gRPC 표준</td>
<td>- 스키마 사전 정의 필수<br>- 복잡한 구조 변환 시 번거로움</td>
</tr>
<tr>
<td><strong>JSON (Jackson)</strong></td>
<td>- 인간 친화적 포맷<br>- 디버깅 용이<br>- 웹 API 표준<br>- 언어 독립성</td>
<td>- 바이너리 대비 속도 느림<br>- 데이터 크기 큼<br>- 타입 안전성 낮음</td>
</tr>
<tr>
<td><strong>Kryo</strong></td>
<td>- JVM 내 최고 성능<br>- 최소 크기 데이터<br>- 커스텀 직렬화 지원</td>
<td>- Java 전용<br>- 스키마/버전 관리 미흡<br>- 비JVM 환경 비호환</td>
</tr>
<tr>
<td><strong>Avro</strong></td>
<td>- 빅데이터 시스템 표준<br>- 스키마 진화 지원<br>- 분산 시스템 최적화<br>- 다중 언어</td>
<td>- 복잡한 스키마 관리 필요<br>- 러닝 커브 존재<br>- 실시간 처리 비효율적</td>
</tr>
</tbody></table>
<p>다중 언어 환경 → Protobuf/Avro
JVM 내 고성능 → Kryo
웹 API/간편성 → JSON
스키마 버전 관리 → Protobuf/Avro
대용량 분산 처리 → Avro</p>
<h2 id="reference">Reference</h2>
<p><a href="https://devloo.tistory.com/entry/%EC%9E%90%EB%B0%94-%EC%A7%81%EB%A0%AC%ED%99%94-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">https://devloo.tistory.com/entry/%EC%9E%90%EB%B0%94-%EC%A7%81%EB%A0%AC%ED%99%94-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</a>
<a href="https://www.aladin.co.kr/shop/wproduct.aspx?itemid=200069290&amp;srsltid=afmbooqhttwvyfftpjzih8pv79kc2qffi5bsxu0jctys-gwouebkx7yd">Effective Java 도서 12장</a></p>
<p><a href="https://youtu.be/qrQZOPZmt0w?si=fu3KnpCbA0lfnklQ">https://youtu.be/qrQZOPZmt0w?si=fu3KnpCbA0lfnklQ</a>
<a href="https://youtu.be/Qi2LJ_NfzC0?si=3FR5CY1Rgqv9PsDo">https://youtu.be/Qi2LJ_NfzC0?si=3FR5CY1Rgqv9PsDo</a>
<a href="https://youtu.be/M0TtOja18M8?si=iLAOR722UXDoYnSa">https://youtu.be/M0TtOja18M8?si=iLAOR722UXDoYnSa</a>
<a href="https://youtu.be/o5rspNdJ-fE?si=jrW9btKjFkxJvdZH">https://youtu.be/o5rspNdJ-fE?si=jrW9btKjFkxJvdZH</a>
<a href="https://youtu.be/0qYA_vTb7Lw?si=kT4wc1yE0bX59z_z">https://youtu.be/0qYA_vTb7Lw?si=kT4wc1yE0bX59z_z</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Polling과 Redis]]></title>
            <link>https://velog.io/@danielyang-95/Polling%EA%B3%BC-Redis</link>
            <guid>https://velog.io/@danielyang-95/Polling%EA%B3%BC-Redis</guid>
            <pubDate>Sun, 19 May 2024 11:24:47 GMT</pubDate>
            <description><![CDATA[<h1 id="redis">Redis</h1>
<ul>
<li><p>key 수신이벤트</p>
<ul>
<li><p>Redis에는 키 및 값의 변경 사항을 실시간으로 수신할 수 있는 Pub/Sub 기능을 제공</p>
<p><a href="http://redisgate.kr/redis/server/event_notification.php">Redis EVENT NOTIFICATION</a></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>RedisMsgListener</p>
<ul>
<li>onMessage에서 만료되었을 때 메시지</li>
</ul>
</li>
<li><p>설정</p>
</li>
</ul>
<pre><code class="language-jsx">  session:
    store-type: redis
    redis:
      flush-mode: immediate
      namespace: @@@@@@ #같은 redis를 참조하는 컨테이너 존재시 변경
      configure-action: notify-keyspace-events #key이벤트 수신 알림 설정</code></pre>
<p><a href="https://ubot.tistory.com/entry/Spring-session-data-redis-accept">Spring session data redis 서비스 적용 후기</a></p>
<ul>
<li>만료된 key에 대한 redis keyspace 알림 수신</li>
</ul>
<p><a href="https://moonsiri.tistory.com/87">[SpringBoot] Redis Key Expired Event Notification</a></p>
<p><a href="https://px201226.github.io/redis-event-notifications/">Redis Keyspace Notifications에 대해 알아보자</a></p>
<p><a href="https://velog.io/@betalabs/%ED%99%98%EC%83%81%EC%9D%98-%EC%A1%B0%ED%95%A9ElastiCache-Spring-Session-Spring-Security-Memory-Leak">환상(장)의 (ElastiCache + Spring Session + Spring Security) Memory Leak</a></p>
<h1 id="eventsource">EventSource</h1>
<p>위의 SSE의 장점에서 보듯이 SSE는 구현이 아주 간편합니다. 클라이언트는 서버로부터 스트림을 받아 <code>EventSource</code> 객체를 통해 서버가 푸시하는 데이터를 받아 처리하기만 하면 됩니다.</p>
<p>먼저 클라이언트 측의 코드입니다.</p>
<pre><code class="language-jsx">var es = new EventSource(stream_url);

es.onmessage = function (event) {
    // 이벤트 설정이안된 기본 데이터 처리
};
es.addEventListener(&#39;myevent&#39;, function(e) {
    // &#39;myevent&#39; 이벤트의 데이터 처리
}, false);</code></pre>
<p><code>EventSource</code> 객체의 속성은 다음과 같습니다.</p>
<ul>
<li><strong><code>onmessage</code></strong> 기본 메시지가 왔을 때 호출</li>
<li><strong><code>onopen</code></strong> 접속이 맺어졌을 때 호출</li>
<li><strong><code>onerror</code></strong> 오류 발생 시 호출</li>
</ul>
<p><code>EventSource</code>의 <code>addEventListener()</code>를 사용하면 위 3개의 이벤트뿐만 아니라 따로 지정된 이벤트의 데이터도 받아 처리할 수 있습니다.</p>
<h2 id="promise">Promise</h2>
<p>프로미스는 비동기 작업을 조금 더 편하게 처리 할 수 있도록 ES6 에 도입된 기능입니다. 이전에는 비동기 작업을 처리 할 때에는 콜백 함수로 처리를 해야 했었는데요, 콜백 함수로 처리를 하게 된다면 비동기 작업이 많아질 경우 코드가 쉽게 난잡해지게 되었습니다.</p>
<ul>
<li><p>resolve, reject</p>
<ul>
<li><p>프로미스의 <code>resolve</code>, <code>reject</code>는 비동기 작업의 <code>처리</code>과정에서 <code>성공</code>/<code>실패</code>를 구분하는 방법이다.</p>
</li>
<li><p>성공 할 때에는 resolve 를 호출해주면 되고, 실패할 때에는 reject 를 호출해주면 됩니다.</p>
</li>
<li><p>resolve 를 호출 할 때 특정 값을 파라미터로 넣어주면, 이 값을 작업이 끝나고 나서 사용 할 수 있습니다. 작업이 끝나고 나서 또 다른 작업을 해야 할 때에는 Promise 뒤에 <code>.then(...)</code> 을 붙여서 사용하면 됩니다.</p>
<pre><code class="language-jsx">const myPromise = new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
  resolve(1);
}, 1000);
});

myPromise.then(n =&gt; {
console.log(n);
});

const myPromise = new Promise((resolve, reject) =&gt; {
setTimeout(() =&gt; {
  reject(new Error());
}, 1000);
});

myPromise
.then(n =&gt; {
  console.log(n);
})
.catch(error =&gt; {
  console.log(error);
});</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="eventlistener">EventListener</h3>
<blockquote>
<p>eventTarget.addEventListener(&#39;eventType&#39;, function)</p>
<p>이벤트를실행할타겟.addEventListener(&#39;이벤트타입&#39;, 실행할함수)</p>
</blockquote>
<ul>
<li><strong>eventTarget</strong>(이벤트 타겟)은 해당 이벤트를 적용할 DOM을 가져와 준다.</li>
<li><strong>eventType</strong>(이벤트 타입)은 말 그대로 어떤 타입의 이벤트를 적용할 것인지 써주면 된다. 대표적으로 <code>click</code> <code>DOMContentLoad</code>, <code>scroll</code> <code>submit</code>등등이 있다. 타입을 써줄 때는 따옴표를 잊지 말고 감싸줘야 한다.<ul>
<li><code>click</code>: 요소를 클릭하면 이벤트 발생.</li>
<li><code>DOMContentLoaded</code>: <strong>페이지가 새로 로딩될 때마다</strong> 이벤트 발생.</li>
<li><code>scroll</code>: 스크롤을 하면 이벤트 발생.</li>
<li><code>submit</code>: 양식을 전송하면 이벤트 발생. (ex: input type이 submit일 때 submit 버튼을 누르는 것과 같음)</li>
</ul>
</li>
<li><strong>function</strong>(실행할함수)에는 이벤트를 발생시켰을 시 실행할 동작을 가져와 준다.</li>
</ul>
<h1 id="sse-통신-방법">SSE 통신 방법</h1>
<ul>
<li>백엔드<ul>
<li>WebFlux</li>
<li>SseEmitter</li>
</ul>
</li>
</ul>
<h3 id="sseemitter">SseEmitter</h3>
<ul>
<li><p>Spring에서 SSE 프로토콜을 지원하기 위한 클래스이므로</p>
<p>  ⇒ 실시간으로 업데이트되는 데이터나 알림과 같은 이벤트를 클라이언트에게 전달 가능</p>
</li>
<li><p>비동기적으로 이벤트 전송 가능</p>
<p>  ⇒ 서버에서 이벤트가 발생하면 해당 이벤트를 즉시 클라이언트에게 전송 가능</p>
</li>
<li><p><strong>클라이언트의 재시도 및 연결 관리</strong></p>
<ul>
<li>SseEmitter는 클라이언트의 연결 상태를 관리하고, 클라이언트와의 연결이 끊어지는 경우에도 재시도 등의 처리를 지원</li>
<li>클라이언트가 알림을 구독한 후 연결이 끊어지면, 클라이언트는 다시 연결을 시도하고 이전에 미수신한 이벤트를 잃지 않도록 처리 가능</li>
</ul>
</li>
<li><p>확장성</p>
<ul>
<li><p>SseEmitter는 여러 클라이언트와 동시에 통신이 가능</p>
<p>  ( 즉, 동일한 알림을 여러 클라이언트에게 전송 가능 )</p>
</li>
<li><p>다수의 클라이언트에게 알림을 전달해야하는 경우에 유용</p>
</li>
</ul>
</li>
</ul>
<p><a href="https://velog.io/@wnguswn7/Project-SseEmitter%EB%A1%9C-%EC%95%8C%EB%A6%BC-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">[Project] SSE(Servcer-Sent-Events)로 실시간 알림 기능 구현하기 !</a></p>
<h3 id="주의">주의</h3>
<p><strong>1. SseEmitter 생성</strong></p>
<ul>
<li>emitter를 생성할 때는 타임아웃을 설정해줄 수 있는데, 기본 값은 30초인 것으로 알고 있다.</li>
</ul>
<p><strong>2. Emitter handling</strong></p>
<ul>
<li>onTimeout: emitter가 timeout이 되었을 때,</li>
<li>onError: 에러가 났을 때</li>
<li>onCompletion: onCompletion은 완료된 emitter에 대한 처리인데, emitter 생성 후 저장해줬던 값을 삭제한다.</li>
</ul>
<p>SseEmitter가 만료되면 클라이언트가 재연결 요청을 보내는데, 그때마다 새로운 emitter를 생성하게 되니 만료된 값은 삭제 처리를 하는 것이다. (emitter를 저장할 때 Map을 사용하고, 키가 동일하다면 굳이 삭제해줄 필요는 없겠지만 명시적으로는 괜찮다고 본다.)</p>
<p><strong>3. dummy data 전송</strong></p>
<p>SseEmitter 생성 이후 어떠한 데이터도 전송하지 않으면 타임아웃 되면서 503에러를 뱉는다고 한다.</p>
<p>구현 전 SSE에 대해 찾아보는 과정에서 주의 사항에 대해 미리 확인했었기 때문에 이 부분을 직접 겪진 않았다.</p>
<p>send() 메서드에서 예외처리하는 IllegalStateException은 만료된 emitter로 send() 메서드를 호출하면 발생하는 에러이다. (내부 코드에서 확인 가능) 에러 발생 시 해당 emitter를 지워준다.</p>
<p>(+) 추가로 이벤트를 전송할 때 .name(&quot;event-name&quot;)으로 이벤트 이름을 지정할 수 있고, 클라이언트에서는 지정된 이름의 알림을 받을 수 있다.</p>
<p>💡 <strong><code>subscribe()</code> 와 <code>send()</code> 의 차이</strong></p>
<ul>
<li><code>subscribe()</code> 메서드는 클라이언트와의 SSE 스트림 통신을 유지하면서 연결을 생성하고 유지⠀</li>
<li><code>send()</code> 메서드는 알림을 생성하고 해당 알림을 수신하는 모든 클라이언트에게 전송⠀👉 <code>subscribe()</code> 메서드는 클라이언트의 요청에 응답하여 SSE 스트림을 제공하고, <code>send()</code> 메서드는 서버에서 알림을 생성하여 클라이언트에게 전송하는 역할</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>