<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>growing?</title>
        <link>https://velog.io/</link>
        <description>growing?</description>
        <lastBuildDate>Wed, 15 Jan 2025 06:09:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>growing?</title>
            <url>https://images.velog.io/images/lil_bear/profile/33ed115d-283b-428c-8e81-f5cadc273734/KakaoTalk_20200403_145706555_07.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. growing?. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/lil_bear" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Docker-Compose를 사용하면서, 
ElasticSearch보다 Spring Boot가 먼저 실행을 마쳤을 때]]></title>
            <link>https://velog.io/@lil_bear/Docker-Compose%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4%EC%84%9C-ElasticSearch%EB%B3%B4%EB%8B%A4-Spring-Boot%EA%B0%80-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EC%9D%84-%EB%A7%88%EC%B3%A4%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@lil_bear/Docker-Compose%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4%EC%84%9C-ElasticSearch%EB%B3%B4%EB%8B%A4-Spring-Boot%EA%B0%80-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EC%9D%84-%EB%A7%88%EC%B3%A4%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Wed, 15 Jan 2025 06:09:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/lil_bear/post/7222f3b1-87e2-4b7d-8e64-bec054edf844/image.png" alt=""></p>
<p><code>Caused by: org.springframework.dao.DataAccessResourceFailureException: Connection refused</code></p>
<p>ElasticSearch(ES)와 Spring boot를 묶었을 때 아래와 같이 docker-compose.yml를 작성할 수 있다.
<img src="https://velog.velcdn.com/images/lil_bear/post/0e9b21ad-ada7-44cb-aed3-578c6b6f1d8c/image.png" alt="">
elasticsearch가 실행된 후 Spring boot를 실행했으나, ES와의 연결을 성공하지 못해 오류가 발생한다. 맨 위 에러 메시지 위로 올라가다 보면 아래와 같은 오류 메시지를 볼 수 있을 것이다.
<img src="https://velog.velcdn.com/images/lil_bear/post/b6d5aad3-cce6-4d95-848d-e738eb4d2dc6/image.png" alt="">
<code>Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository]: Constructor threw exception</code></p>
<p>ES가 정상적으로 동작하는 지 확인 한 후, Spring Boot를 실행하도록 하자.
<img src="https://velog.velcdn.com/images/lil_bear/post/9e7eb7c9-866b-4fc8-9149-010072505a6b/image.png" alt=""></p>
<pre><code>services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - &quot;9200:9200&quot;
    networks:
      - elk_network
    healthcheck:
      test: [ &quot;CMD-SHELL&quot;, &quot;curl -fsSL http://localhost:9200/_cluster/health || exit 1&quot; ]
      interval: 10s
      timeout: 5s
      retries: 5

  springboot-app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: springboot-app
    environment:
      - SPRING_PROFILES_ACTIVE=dev
    ports:
      - &quot;8080:8080&quot;
    networks:
      - elk_network
    depends_on:
      elasticsearch:
        condition: service_healthy
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Caused by: java.net.UnknownHostException: http://localhost:9200: nodename nor servname provided, or not known]]></title>
            <link>https://velog.io/@lil_bear/Caused-by-java.net.UnknownHostException-httplocalhost9200-nodename-nor-servname-provided-or-not-known</link>
            <guid>https://velog.io/@lil_bear/Caused-by-java.net.UnknownHostException-httplocalhost9200-nodename-nor-servname-provided-or-not-known</guid>
            <pubDate>Mon, 13 Jan 2025 03:43:48 GMT</pubDate>
            <description><![CDATA[<p><code>application.properties</code> 혹은 <code>application.yml</code> 의 <code>spring.elasticsearch.uris</code>의 값이 <code>http://localhost:9200</code> 이라면 <code>localhost:9200</code>으로 변경하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[월 천원 더 내고 무중단 배포하기]]></title>
            <link>https://velog.io/@lil_bear/%EC%9B%94-%EC%B2%9C%EC%9B%90-%EB%8D%94-%EB%82%B4%EA%B3%A0-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@lil_bear/%EC%9B%94-%EC%B2%9C%EC%9B%90-%EB%8D%94-%EB%82%B4%EA%B3%A0-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 08 Jan 2025 08:41:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/lil_bear/post/4be8ebc0-f590-4e0f-8574-0380f26700e1/image.png" alt="돈이 없네">
운영중인 서비스의 서버를 업데이트하는 것은 생각보다 피곤하다. 로그를 바라보면서 &#39;<strong>오 지금 사용자가 없네?</strong>&#39;인 타이밍에 실행중인 서버를 종료하고, 빌드를 한 뒤, 실행을 하는 이 빈곤한 업데이트는 결국 새벽 세 시에 터미널을 바라보고 있는 나를 보게 된다. 업데이트하는 주간이 되면 라이프사이클이 고장나고, 상쾌하지 못한 아침을 맞이하며, 쌩쌩한 새벽 시간을 낭비하곤 한다.</p>
<p>돈이 없어 인스턴스 업그레이드나 추가를 하지 못하고 현실과 타협한 결과물을 한번 공유해보고자 한다.</p>
<p>아마 이 예시는 메모리가 부족한 상태에서 무중단 배포를 하고 싶은 사람들에게 도움이 될 것이다. 프리티어 사용자들은 micro 인스턴스에서 진행한다면 가벼운 서비스 정도는 무중단으로 배포할 수 있을 것이다.</p>
<p>작성할 예시의 스펙은 다음과 같다.</p>
<ul>
<li>EC2 t3a.small 인스턴스</li>
<li>Ubuntu 운영체제</li>
</ul>
<h1 id="0-일단-구조는-이런-식">0. 일단 구조는 이런 식.</h1>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/1d1defbb-2f94-4ccf-a6a7-d72fe60283a2/image.png" alt="바꿀 구조">
단순하다.
FE에서 8080포트를 사용하여 통신을 하고 있기에, Nginx를 8080으로 실행하고 있다.
또한, https 통신을 하지 않고 있다는 가정이다.</p>
<h1 id="1-ec2의-디스크-용량을-늘리자">1. EC2의 디스크 용량을 늘리자.</h1>
<h2 id="1-aws-ec2에-접속해서-할-일">1) AWS EC2에 접속해서 할 일</h2>
<p>EC2 small 인스턴스의 기본 디스크 용량은 8GB이다. 
다음 단계인 Swap Memory를 사용하기에 추가 용량이 필요했기에, 용량을 증설했다.</p>
<h4 id="용량이-충분한-사람들은-이-과정을-진행하지-않아도-되기에-2로-넘어가면-된다">용량이 충분한 사람들은 이 과정을 진행하지 않아도 되기에, 2)로 넘어가면 된다.</h4>
<p>모든 스크린샷은 이미 증설한 이후이므로, 볼륨 크기가 달라도 무시하면 된다.
<img src="https://velog.velcdn.com/images/lil_bear/post/4291e520-ad4e-458e-b2df-efc93508afe4/image.png" alt="">
AWS EC2 인스턴스 정보다.
아래 스토리지 탭에 들어가면 해당 인스턴스가 사용하는 스토리지를 확인할 수 있다.
볼륨 ID를 클릭하게 되면 스토리지 정보로 넘어가게 된다.
<img src="https://velog.velcdn.com/images/lil_bear/post/0caf0c47-b033-4ee4-bd51-3f33b6ac7175/image.png" alt=""> 
볼륨의 상세 정보에서 우측 상단의 <strong>수정</strong>을 클릭하여 용량을 수정하자.
<img src="https://velog.velcdn.com/images/lil_bear/post/477f4458-a86d-4021-b6c7-f731037ccbff/image.png" alt="">
필요한 만큼 용량을 늘리고 수정을 누르자. 
늘리는 것은 가능하지만, 줄이는 것은 불가능하기에 신중하게 늘리자.
범용 SSD의 경우, 1GB당 월 요금 $0.1이 추가된다.</p>
<p>작업이 완료되면, 해당 인스턴스로 접속해 보자.</p>
<h2 id="2-인스턴스에-접속해서-할-일">2) 인스턴스에 접속해서 할 일</h2>
<blockquote>
<p>파일 시스템명은 <em>/dev/root</em>, 파티션명은 <em>/dev/abcd</em> 로 가정한다.</p>
</blockquote>
<p>아래 명령어를 실행하면, 각 파일 시스템의 정보가 나오는데, <em>/dev/root</em> 의 크기가 그대로임을 볼 수 있다.
파티션을 재할당해야 변경된다. 파티션을 재할당해 보자.</p>
<pre><code>$ df -h</code></pre><p>명령어를 실행하여 우리가 사용하는 파티션을 확인해야 한다.</p>
<pre><code>$ lsblk</code></pre><p>명령어를 실행하면 파티션이 확장된다. 파티션이 확장되었으니, 볼륨을 재할당하자.</p>
<pre><code>$ sudo growpart /dev/abcd 1</code></pre><p>이제 아래 명령어를 실행하면 볼륨이 재할당된다. 이제 각 정보를 다시 <code>lsblk</code> 명령어로 확인하면 용량이 늘어났음을 볼 수 있다.</p>
<pre><code>$ sudo resize2fs /dev/root</code></pre><h1 id="2-swap을-사용해-메모리를-늘리자">2. Swap을 사용해 메모리를 늘리자.</h1>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/86c4cf44-0863-4ed0-9966-1a04aee027ae/image.webp" alt="">
EC2 small의 경우, 메모리 용량은 2GB이다. 100개 이상의 API를 제공하는 Spring Boot를 2개나 실행하려다가 굉장히 행복한 경험을 할 뻔했다. 
medium으로 올리게 되면, <em><strong>메모리도 2배! 가격도 2배!</strong></em> 가 되어버리니 더 저렴한 방법을 찾아야 한다.
위에 언급했다시피 디스크를 활용하는 <strong>Swap</strong> 메모리를 활용할 것이다. 디스크 공간을 가상의 메모리로 사용하는 것이다. 속도가 메모리에 비해 한참 느려 성능 저하가 발생할 수 있고, 모든 메모리가 꽉 찰 경우 위험한 일이 발생할 수 있기 때문에 충분한 용량을 확보하길 바란다.</p>
<h2 id="1-swap-파일-만들기">1) Swap 파일 만들기</h2>
<p>EC2에 접속해서 아래의 명령어를 입력하자.</p>
<pre><code>$ sudo dd if=/dev/zero of=/swapfile bs=128M count=32</code></pre><p><strong>&#39;/dev/zero&#39;</strong> 에 <strong>&#39;swapfile&#39;</strong> 을 128MB의 블록을 32개 생성한다.
해당 명령어를 사용하면 128MB * 32 = 4096MB = 4GB의 Swapfile을 생성하는 것이다. 용량은 잔여 디스크 용량 내에서 잘 조절하기를 바란다.</p>
<h2 id="2-swap-설정">2) Swap 설정</h2>
<p>생성한 swapfile에 읽기 및 쓰기 권한을 주자.</p>
<pre><code>$ sudo chmod 600 /swapfile</code></pre><p>이제 swapfile을 통해 영역을 설정하자.</p>
<pre><code>$ sudo mkswap /swapfile</code></pre><p>Swap 영역에 Swap 파일을 설정하자.</p>
<pre><code>$ sudo swapon /swapfile</code></pre><p>Swap 파일이 설정되어 있는지 확인해보고</p>
<pre><code>$ sudo swapon -s</code></pre><p>부팅 시에 자동으로 활성화 하도록 파일시스템을 수정할 것이다.</p>
<pre><code>$ sudo vi /etc/fstab</code></pre><p>맨 아래에 아래 문장을 추가하고 저장한다.</p>
<pre><code>/swapfile swap swap defaults 0 0</code></pre><p>이제 Swap 메모리가 설정됐을 것이다. 아래 명령어로 메모리 상태를 확인해보면</p>
<pre><code>$ free</code></pre><p>Swap이 생겼음을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/lil_bear/post/c74980e0-2f65-4610-bfd3-97c149b15b8e/image.png" alt="와! 스왑메모리!"></p>
<p>이제 메모리 여유를 확보했으니까, 행복하게 Spring Boot 애플리케이션을 두 개를 켜보자!</p>
<h1 id="3-nginx를-설치해-리버스-프록시를-설정하자">3. Nginx를 설치해 리버스 프록시를 설정하자.</h1>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/93b69789-fb6a-4d88-862c-5a57ccfda6ca/image.png" alt=""></p>
<p>리버스 프록시를 위해 Nginx를 사용할 것이다. 8080 포트로 요청을 받고, 8081과 8082 포트에 각 Spring Boot 애플리케이션을 실행할 것이다. 8081 포트가 구버전이었다면 8082 포트를 신버전으로, 8082 포트가 구버전이었다면 8081 포트를 신버전으로 실행할 것이다.</p>
<p>우선 apt-get을 이용하여 Nginx를 설치하자. <code>apt-get update</code> 등의 내용은 생략한다.</p>
<pre><code>$ sudo apt-get install nginx</code></pre><p>nginx가 설치되었는지 확인해 보자.</p>
<pre><code>$ nginx -v</code></pre><p>버전이 출력됐다면 설치가 된 것이다.</p>
<p>이제 8080 포트가 실행 중인 스프링부트 애플리케이션을 바라보도록 설정할 것이다. 우선 8080 포트로 서버가 실행 중이었다면, 종료한 뒤에 nginx 설정을 하자.
아래 명령어를 입력하면 설정 파일이 실행된다.</p>
<pre><code>$ sudo vi /etc/nginx/sites-available/default</code></pre><p>vi를 이용하여 아래와 같이 수정한다.
<img src="https://velog.velcdn.com/images/lil_bear/post/68e83c9f-b258-4c47-b1e4-4015f3471da9/image.png" alt=""></p>
<p>아래의 수정한 내용을 설명하자면</p>
<ol>
<li>service-url.inc를 읽어온다.</li>
<li>요청이 오면 <code>{service_url}</code>로 전달한다.</li>
<li>proxy_set_header A B =&gt; 실제 요청에 A라는 헤더에 B를 할당한다.</li>
</ol>
<p>service_url을 제외한 변수들은 nginx에서 제공하기 때문에, 구글에 검색해서 알아볼 수 있다.
service_url은 service-url.inc에 기록하자.</p>
<pre><code>$ sudo vi /etc/nginx/conf.d/service-url.inc</code></pre><p>우선은 service_url을 8081 포트로 두자.</p>
<pre><code>set $service_url http://127.0.0.1:8081;</code></pre><p>이 service-url.inc의 위치를 잘 기억해 두고 이제 Spring Boot를 설정하러 가보자.</p>
<h1 id="4-다른-포트로-두-개의-spring-boot를-실행하자">4. 다른 포트로 두 개의 Spring Boot를 실행하자.</h1>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/d2c1c806-f8ec-4415-84ec-bcdca629ce7f/image.png" alt="">
Spring Boot의 포트 기본값은 8080이다. 이를 application.properties (혹은 .yml)을 통해 변경할 수 있다. 로컬과 배포 환경이 다를 경우, 이미 프로필을 나누어 놓았을 것이다. 배포 환경으로 구성된 두 개의 프로필을 만들자. 각 프로필에 <code>server.profiles</code> 값을 8081, 8082로 설정하여 저장하자.</p>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/d9f19410-f2e9-4fdf-a462-42cc9e3da163/image.png" alt="">
두 개의 Spring Boot 애플리케이션을 실행했다고 가정하자.</p>
<ol>
<li>신버전이 실행되었을 때 <code>ps</code> 명령어를 사용해서 &#39;신버전이 실행되었는가?&#39; 확인하고</li>
<li>Nginx가 바라보는 포트를 변경한 뒤</li>
<li>구버전을 종료할 수 있을 것이다.
우리는 이걸 <strong>딸깍</strong> 한 번으로 해결해 보자.</li>
</ol>
<p>이 구식 방법을 활용하기로 했다면, 최대한 구식으로 가보자. 우리는 정말로 <code>ps</code> 명령어를 사용해 일하고 있는 Spring Boot가 사용하는 포트를 확인할 것이다.
실행 중인 Java는 Spring Boot 애플리케이션 하나만 있다고 생각하자.</p>
<pre><code>#!/bin/bash

# java의 PID를 찾는다.
java_process=$(ps -eo pid,comm | grep java | awk &#39;{print $1}&#39; | head -n 1)

# pid가 없을 경우, 8082로 가정. 있을 경우 사용중인 포트 번호를 저장.
if [ -z &quot;$java_process&quot; ]; then
    current_port=8082
else
    current_port=$(lsof -Pan -p $java_process -i | grep LISTEN | awk &#39;{print $9}&#39; | sed &#39;s/.*://&#39;)
fi</code></pre><p>그럼 우리는 현재 사용 중인 포트를 찾았으니, 어떤 프로필의 Spring Boot가 실행 중인지 알 수 있다. 8081의 프로필을 set1, 8082의 프로필을 set2라고 가정하자.
set2를 새로 실행시켰을 때, set2가 정상적으로 작동하는지 확인을 한 뒤, Nginx에 연결하고, set1을 종료하자.</p>
<pre><code>if [ $current_port == 8082]; then
    current_profile=set2
    profile=set1
    port=8081
else
    current_profile=set1
    profile=set2
    port=8082
fi

# 새로운 앱 실행
nohup java -jar -Dspring.profiles.active=$profile $path &amp;

# 10초 후 배포 상태 확인
sleep 10

for count in {1...10}
do
    response=$(curl -s http://localhost:$port/health
    up_cnt=$(echo $response | grep &#39;UP&#39; | wc -l)

    # 배포 상태 확인 시 &#39;UP&#39;이라는 단어가 있는지 확인
    if [ $up_cnt -ge 1 ]
    then
        echo &quot;&gt; 배포 정상&quot;
        break
    else
        echo &quot;&gt; 배포 비정상&quot;
        echo &quot;&gt; Health check: ${response}&quot;
    fi

    # 최대 10회 배포 상태 확인 시도. 10회가 끝나면 스크립트 종료.
    if [ $count -eq 10 ]
    then
        echo &quot;&gt; 배포 실패.&quot;
        echo &quot;&gt; 배포 과정을 종료합니다.&quot;
        exit 1
    fi

    # 10초 후 상태 확인 재시도
    echo &quot;&gt; 배포 상태 확인 재시도&quot;
    sleep 10
done

# 포트 전환
echo &quot;set \$service_url http://127.0.0.1:${port};&quot; | sudo tee /etc/nginx/conf.d/service-url.inc

# Nginx 재시작
sudo service nginx reload

# 이전 버전 Spring Boot 정상 종료
kill -15 $java_process
</code></pre><h1 id="5-뭐여-안되는디">5. 뭐여 안되는디.</h1>
<p><img src="https://velog.velcdn.com/images/lil_bear/post/75c7a0d0-ea80-4d0b-8732-6c7eae7b090b/image.png" alt="">
복붙으로 안된다. 이런 구식으로 무중단 배포를 하는 사람이 있을 리가...</p>
<h4 id="만약-이-구식을-원한다면-gracefult-shutdown-설정-spring-boot-actuator-의존성-추가-배포-스크립트-완성을-하면-된다">만약 이 구식을 원한다면 Gracefult Shutdown 설정, Spring Boot Actuator 의존성 추가, 배포 스크립트 완성을 하면 된다.</h4>
<p>이 방법은 실제 서비스에 23년 11월에 적용한 방법이다. 메모리를 2GB 가까이 잡아먹는 100개 이상(150개에 가까운)의 API를 제공하는 녀석을 어떻게든 낮에도 배포를 하고 싶었다. 당시 나 혼자서 구상할 수 있는 최대한의 방법이었다. 근데 <strong>아무도 이렇게는 안했으면 한다.</strong>
Docker도 쓰고, Jenkins나 Github Actions로 CI도 구현하고, CodeDeploy도 사용해서 더 좋은 방법으로 하면 아마 이거보다 훨씬 더 좋은 글을 쓸 수 있는 사람이 될 것이다.
<em>이런 글(경험담)을 또 쓴다면 &#39;진흙탕 뒹굴기 시리즈&#39; 정도로 칭할 듯</em></p>
<p>참고 링크를 남기고 이만 끝.</p>
<p><em><strong>혹시 진짜 만약에 이 방법을 택하고 하다가 막히면 언제든지 아래 이메일 보내주세요.</strong></em>
growing.lilbear@gmail.com</p>
<h1 id="참고-링크">참고 링크</h1>
<ol>
<li>Swap Memory - <a href="https://repost.aws/ko/knowledge-center/ec2-memory-swap-file">https://repost.aws/ko/knowledge-center/ec2-memory-swap-file</a></li>
<li>무중단 배포 - <a href="https://github.com/jojoldu/springboot-webservice/blob/master/tutorial/7_NGINX_SSL_%EB%AC%B4%EC%A4%91%EB%8B%A8%EB%B0%B0%ED%8F%AC.md">https://github.com/jojoldu/springboot-webservice/blob/master/tutorial/7_NGINX_SSL_%EB%AC%B4%EC%A4%91%EB%8B%A8%EB%B0%B0%ED%8F%AC.md</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optional]]></title>
            <link>https://velog.io/@lil_bear/Optional</link>
            <guid>https://velog.io/@lil_bear/Optional</guid>
            <pubDate>Mon, 08 Jun 2020 05:04:14 GMT</pubDate>
            <description><![CDATA[<h2 id="누구세요">누구세요?</h2>
<ul>
<li>null일 수 있는 콘테이너 객체</li>
<li>쉽게 말하면 그냥 널일수도 있고, 아닐수도 있고</li>
</ul>
<h2 id="왜-있을까요">왜 있을까요?</h2>
<p><a href="https://docs.oracle.com/javase/9/docs/api/java/util/Optional.html">공식 문서</a>을 읽어보면 나온다.</p>
<blockquote>
<p>API Note:
Optional is primarily intended for use as a method return type where there is a clear need to represent &quot;no result,&quot; and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.</p>
</blockquote>
<p>없다는 것을 확실하게 표현할 수 있게 하려고 만든 것으로 표현한 것 같다.</p>
<h2 id="어떻게-쓸까요">어떻게 쓸까요?</h2>
<p>이 친구가 참 괴상한 친구인 것이 사용할 때 주의사항이 26가지나 된다. <a href="https://dzone.com/articles/using-optional-correctly-is-not-optional">링크</a></p>
<p>나는 그래서 가급적 지양하는 편인데, 필요한 경우 아래를 지켜서 사용한다.</p>
<h3 id="1-ispresent---get-대신-orelse--orelseget--orelsethrow">1. <code>isPresent() - get()</code> 대신 <code>orElse() / orElseGet() / orElseThrow()</code></h3>
<pre><code class="language-java">// 안 좋은 코드
Optional&lt;Member&gt; member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    return null;
}

// 안 좋은 코드
Optional&lt;Member&gt; member = ...;
if (member.isPresent()) {
    return member.get();
} else {
    throw new NoSuchElementException();
}

// 좋은 코드
Optional&lt;Member&gt; member = ...;
return member.orElse(null);

// 좋은 코드
Optional&lt;Member&gt; member = ...;
return member.orElseThrow(() -&gt; new NoSuchElementException());</code></pre>
<h3 id="2-orelsenew--대신-orelseget---new-">2. <code>orElse(new ...)</code> 대신 <code>orElseGet(() -&gt; new ...)</code></h3>
<blockquote>
<p><code>orElse(...)</code> 에서 <code>...</code> 는 <code>Optional</code> 에 값이 있든 없든 무조건 실행된다. 따라서 <code>...</code> 가 새로운 객체를 생성하거나 새로운 연산을 수행하는 경우에는 <code>orElse()</code> 대신 <code>orElseGet()</code> 을 써야한다.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
Optional&lt;Member&gt; member = ...;
return member.orElse(new Member()) // member에 값이 있든 없든 new Member()는 무조건 실행됨.

// 좋은 코드
Optional&lt;Member&gt; member = ...;
return member.orElseGet(Member::new);  // member에 값이 없을 때만 new Member()가 실행됨.

// 좋은 코드
Member EMPTY_MEMBER = new Member();
...
Optional&lt;Member&gt; member = ...;
return member.orElse(EMPTY_MEMBER); // 이미 생성됐거나 계산된 값은 orElse()를 사용해도 무방.</code></pre>
<h3 id="3-단지-값을-얻을-목적이라면-optional-대신-null-비교">3. 단지 값을 얻을 목적이라면 <code>Optional</code> 대신 <code>null</code> 비교</h3>
<blockquote>
<p><code>Optional</code> 은 비싸다. 따라서 단순히 값 또는 <code>null</code> 을 얻을 목적이라면 <code>Optional</code> 대신 <code>null</code> 비교를 쓰자.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
return Optional.ofNullable(status).orElse(READY);

// 좋은 코드
return status != null ? status : READY;</code></pre>
<h3 id="4-optional-대신-비어있는-컬렉션-반환">4. <code>Optional</code> 대신 비어있는 컬렉션 반환</h3>
<blockquote>
<p><code>Optional</code> 은 비싸다. 그리고 컬렉션은 <code>null</code> 이 아니라 비어있는 컬렉션을 반환하는 것이 좋을 때가 많다. 따라서 컬렉션은 <code>Optional</code> 로 감싸서 반환하지 말고 비어있는 컬렉션을 반환하자.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
List&lt;Member&gt; members = team.getMembers();
return Optional.ofNullable(members);

// 좋은 코드
List&lt;Member&gt; members = team.getMembers();
return members != null ? members : Collections.emptyList();

// 안 좋은 코드
public interface MemberRepository&lt;Memeber, Long&gt; extends JpaRepository {
    Optional&lt;List &lt;Member&gt;&gt; findAllByNameContaining(String part);
}

// 좋은 코드
public interface MemberRepository&lt;Member, Long&gt; extends JpaRepository {
    List&lt;Mbmer&gt; findAllByNameContaining(String part);
}</code></pre>
<h3 id="5-optional-을-필드로-사용-금지">5. <code>Optional</code> 을 필드로 사용 금지</h3>
<blockquote>
<p><code>Optional</code> 은 필드에 사용할 목적으로 만들어지지 않았으며, <code>Serializable</code> 을 구현하지 않았다. 따라서 <code>Optional</code> 은 필드로 사용하지 말자.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
public class Member {
    private Long id;
    private String name;
    private Optional&lt;String&gt; email = Optional.empty();
}

// 좋은 코드
public class Member {
    private Long id;
    private String name;
    private String Email;
}</code></pre>
<h3 id="6-optional-을-생성자나-메서드-인자로-사용-금지">6. <code>Optional</code> 을 생성자나 메서드 인자로 사용 금지</h3>
<blockquote>
<p><code>Optional</code>을 생성자나 메서드 인자로 사용하면, 호출할 떄마다 <code>Optional</code> 을 생성해서 인자로 전달해줘야 한다. 하지만 호출되는 쪽, 즉 api나 라이브러이 메서드에서는 인자가 <code>Optional</code> 이든 아니든 <code>null</code> 체크를 하는 것이 언제나 안전하다. 그러므로 <code>null</code> 체크를 하자.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
public class HRManager {
    public void increaseSalary(Optional&lt;Member&gt; member) {
        member.ifPresent(member -&gt; memberincreaseSalary(10));
    }
}
hrManager.increaseSalary(Optional.ofNullable(member));

// 좋은 코드
public class HRManager {
    public void increaseSalary(Member member) {
        if (member != null) {
            member.increaseSalary(10);
        }
    }
}
hrManager.increaseSalary(member);</code></pre>
<h3 id="7-optional을-컬렉션의-원소로-사용-금지">7. <code>Optional</code>을 컬렉션의 원소로 사용 금지</h3>
<blockquote>
<p>컬렉션에는 많은 원소가 들어갈 수 있다. 따라서 비싼 <code>Optional</code> 을 원소로 사용하지 말고 원소를 꺼낼 때나 사용할 때 <code>null</code> 체크하는 것이 좋다.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
Map&lt;String, Optional&lt;String&gt;&gt; sports = new HashMap&lt;&gt;();
sports.put(&quot;100&quot;, Optional.of(&quot;BasketBall&quot;));
sports.put(&quot;101&quot;, Optional.ofNullable(someOtherSports));
String basketBall = sports.get(&quot;100&quot;).orElse(&quot;BasketBall&quot;);
String unknown = sports.get(&quot;101&quot;).orElse(&quot;&quot;);

// 좋은 코드
Map&lt;String, String&gt; sports = new HashMap&lt;&gt;();
sports.put(&quot;100&quot;, &quot;BasketBall&quot;);
sports.put(&quot;101&quot;, null);
String basketBall = sports.getOrDefault(&quot;100&quot;, &quot;BasketBall&quot;);
String unknown = sports.computeIfAbsent(&quot;101&quot;, k -&gt; &quot;&quot;);</code></pre>
<h3 id="8-of--ofnullable-혼동-주의">8. <code>of()</code> , <code>ofNullable()</code> 혼동 주의</h3>
<blockquote>
<p><code>of(X)</code> 은 <code>X</code> 가 <code>null</code> 이 아님이 확실할 때만 사용해야 하며, <code>X</code>가 <code>null</code> 이면 NullPointerException이 발생한다.
<code>ofNullable(X)</code> 은 <code>X</code>가 <code>null</code> 일 수도 있을 때만 사용해야 하며, <code>X</code>가 <code>null</code>이 아님이 확실하면 <code>of(x)</code> 를 사용해야 한다.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
return Optional.of(member.getEmail());

// 안 좋은 코드
return Optional.ofNullable(&quot;READY&quot;);

// 좋은 코드
return Optional.ofNullable(member.getEmail());

// 좋은 코드
return Optional.of(&quot;READY&quot;);</code></pre>
<h3 id="9-optionalt-대신-optionalint-optionallong-optionaldouble">9. <code>Optional&lt;T&gt;</code> 대신 <code>OptionalInt</code>, <code>OptionalLong</code>, <code>OptionalDouble</code></h3>
<blockquote>
<p><code>Optional</code> 에 담길 값이 <code>int</code>, <code>long</code>, <code>double</code> 이라면 Boxing/Unboxing 이 발생하는 <code>Optional&lt;Integer&gt;</code>, <code>Optional&lt;Long&gt;</code>, <code>Optional&lt;Double&gt;</code> 을 사용하지 말고, <code>OptionalInt</code>, <code>OptionalLong</code>, <code>OptionalDouble</code> 을 사용하자.</p>
</blockquote>
<pre><code class="language-java">// 안 좋은 코드
Optional&lt;Integer&gt; count Optional.of(38);
for (int i = 0; i &lt; count.get(); i++) { ... }

// 좋은 코드
Optional count = Optional.of(38);
for (int i = 0; i &lt;count.getAsInt(); i++) { ... }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[스트림]]></title>
            <link>https://velog.io/@lil_bear/%EC%8A%A4%ED%8A%B8%EB%A6%BC</link>
            <guid>https://velog.io/@lil_bear/%EC%8A%A4%ED%8A%B8%EB%A6%BC</guid>
            <pubDate>Thu, 04 Jun 2020 12:12:37 GMT</pubDate>
            <description><![CDATA[<h2 id="누구세요">누구세요?</h2>
<ul>
<li>컬렉션, 배열 등의 저장 요소를 하나씩 참조하여 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 있도록 해주는 기능.</li>
<li>그냥 쉽게 말하면 <code>반복문을 대체 할 수 있는 기능</code></li>
<li>자바 8부터 추가됐으니 참고하길.</li>
</ul>
<h2 id="왜-써요">왜 써요?</h2>
<p>처음에는 정말 왜 사용해야 하는지 이해하지 못했다. 메서드들을 알고 있어야 코드의 이해가 가능하고, 성능 또한 for문 혹은 foreach문보다 좋지 않다. 하지만 로직이 복잡하면 for문안에 담는 것보다 훨씬 간결하게 작성할 수 있고, 유지보수가 수월하다. (그리고 쓰다보면 이어지는게 재밌다)</p>
<h2 id="어떻게-생겼어요">어떻게 생겼어요?</h2>
<pre><code class="language-java">Obejct.스트림생성().중개연산().최종연산();</code></pre>
<ul>
<li>최종연산을 하지 않으면 오류가 난다.</li>
</ul>
<h2 id="어떻게-써요">어떻게 써요?</h2>
<p>메서드들을 정리한 <a href="https://www.notion.so/ece8552223d64e38be9d51860c895bbe">자료</a>가 있는데, 진짜 너무 많다. 자주 사용하는 것들의 예시를 들어놓겠다.</p>
<pre><code class="language-java">public class Ball {
    private int number;

    public Ball(int number) {
        this.number = number;
    }

    public void getNumber() {
        return this.number;
    }

    //...
}</code></pre>
<pre><code class="language-java">public class MapSample {
    // 스트림을 이용한 메서드
    public int sumAll(List&lt;Ball&gt; balls) {
        return balls.stream()
                .mapToInt(ball -&gt; ball.getNumber())
                    .sum();
    }
    // for문을 이용한 메서드
    public int sumAllByFor(List&lt;Ball&gt; balls) {
        int sum = 0;
        for (int i = 0; i &lt; balls.size(); i++) {
            sum += balls.get(i).getNumber();
        }
        return sum;
    }
    // iter을 이용한 메서드
    public int sumAllByForEach(List&lt;Ball&gt; balls) {
        int sum = 0;
        for (Ball ball : balls) {
            sum += ball.getNumber();
        }
        return sum;
    }
}</code></pre>
<pre><code class="language-java">public class FilterSample {
    // 스트림을 이용한 메서드
    public Ball findBall(List&lt;Ball&gt; balls, int number) {
        return balls.stream()
                .mapToInt(ball -&gt; ball.getNumber())
                    .filter(ball -&gt; ball.getNumber() == number)
                    .findFirst()
                    .orElseThrow(RuntimeException::new);
    }
    // for문을 이용한 메서드
    public Ball findBallByFor(List&lt;Ball&gt; balls, int number) {
        for (int i = 0; i &lt; balls.size(); i++) {
            if (balls.get(i).getNumber() == number) {
                return balls.get(i);
            }
        }
        throw new RuntimeException();
    }
    // iter을 이용한 메서드
    public Ball findBallByForEach(List&lt;Ball&gt; balls, int number) {
        for (Ball ball : balls) {
            if (ball.getNumber() == number) {
                return ball;
            }
        }
        throw new RuntimeException();
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[일급 컬렉션]]></title>
            <link>https://velog.io/@lil_bear/first-class-collection</link>
            <guid>https://velog.io/@lil_bear/first-class-collection</guid>
            <pubDate>Tue, 02 Jun 2020 08:43:39 GMT</pubDate>
            <description><![CDATA[<h2 id="누구세요">누구세요?</h2>
<p>하나의 콜렉션을 유일한 필드 변수로 갖고 있는 클래스.</p>
<h2 id="뭣하러-만들어요">뭣하러 만들어요?</h2>
<ol>
<li><strong>비즈니스</strong>에 종속적인 <strong>자료구조</strong></li>
<li>Collection의 <strong>불변성</strong>을 보장</li>
<li>상태와 행위를 한 곳에서 관리</li>
<li>이름이 있는 컬렉션</li>
</ol>
<h2 id="누가-쓰라고-그랬어요">누가 쓰라고 그랬어요?</h2>
<p>&#39;객체지향 생활체조 규칙 8 - 일급 콜렉션 사용&#39;</p>
<h2 id="설명">설명</h2>
<h3 id="비즈니스에-종속적인-자료구조">비즈니스에 종속적인 자료구조</h3>
<p>대표적인 예로, 생성 시에 필요한 검증 로직들을 비즈니스 로직을 담당하는 서비스에서 옮겨갈 수 있다. 서비스단에서 그 검증 로직 다 찾아보고 왜 검증해야 하는지 알아내는 것보다, 한 클래스 내에서 확인할 수 있다면? 아니면 그 로직을 알 필요가 없다면? 일급 컬렉션의 강점이 나타나게 되는 것이다. </p>
<pre><code class="language-java">public class LottoService {
    public void createNumber() {
        List&lt;Long&gt; lottoNumbers = createNonDuplicateNumbers();
        validateSize(lottoNumbers);
        validateDuplicate(lottoNumbers);
        // ....
    }
}</code></pre>
<pre><code class="language-java">public class LottoNumbers {
    private List&lt;Long&gt; lottoNumbers;

    public LottoNumbers(List&lt;Long&gt; lottoNumbers) {
        validateSize(lottoNumbers);
        validateDuplicate(lottoNumbers);
        this.lottoNumbers = lottoNumbers;
    }
}</code></pre>
<p>일급 컬렉션을 필요한 조건을 충족하는 새로운 자료구조를 만든다고 생각하면 된다.</p>
<p>후배들이 클래스가 무엇인지 설명해달라고 할 때, 너가 원하는 타입을 만든다고 생각하면 된다고 해준다.</p>
<p>원하는 값을 담는(필드 변수), 필요한 기능을 갖고 있는(메서드) 타입을 만드는 거니까. </p>
<p>그것과 동일하게 새로운 <strong>자료구조</strong>를 만든다고 생각하면 될 것 같다.</p>
<p>대신 필드가 컬렉션 하나인 것이 차이점이겠다.</p>
<h3 id="collection의-불변성을-보장-가능하다">Collection의 불변성을 보장 (가능하다)</h3>
<p>그냥 클래스를 생성할 시에, 필드에 접근을 불가능하게 만들면 된다.</p>
<p>필드의 접근제어자를 private로 설정하고, 변경하는 로직을 전혀 만들지 않으면 끝.</p>
<p>가능하다는 거지, 꼭 그렇다는 것은 아니다.</p>
<p>값을 변경하고 싶으면 아예 새로운 객체를 만들어서 반환해도 된다. </p>
<h3 id="상태와-행위를-한-곳에서-관리">상태와 행위를 한 곳에서 관리</h3>
<p>굳이 설명을 담지는 않겠다. 위에서 언급한 것을 읽으면 당연시된다.</p>
<h3 id="이름이-있는-컬렉션">이름이 있는 컬렉션</h3>
<p>일급 <strong>컬렉션</strong> 이니까 이름이 있는 커스텀된 컬렉션이 된다. 의미를 컬렉션 명에 담을 수 있다. </p>
]]></description>
        </item>
    </channel>
</rss>