<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>낭만 개발하기</title>
        <link>https://velog.io/</link>
        <description>웹개발 도전! 데브옵스 도전!</description>
        <lastBuildDate>Thu, 16 Jan 2025 07:30:36 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>낭만 개발하기</title>
            <url>https://velog.velcdn.com/images/a-white-bit/profile/0869b937-e7aa-4515-9e57-fed6e2230dd6/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 낭만 개발하기. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/a-white-bit" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Boot 멀티 모듈 배포하기]]></title>
            <link>https://velog.io/@a-white-bit/Spring-Boot-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-white-bit/Spring-Boot-%EB%A9%80%ED%8B%B0-%EB%AA%A8%EB%93%88-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 16 Jan 2025 07:30:36 GMT</pubDate>
            <description><![CDATA[<h1 id="모듈-구성">모듈 구성</h1>
<ul>
<li><p>api: Spring Boot의 메인 클래스 (@SpringBootApplication)가 존재</p>
</li>
<li><blockquote>
<p>실행 가능한 JAR 파일을 생성할 수 있다!</p>
</blockquote>
</li>
<li><p>core: 도메인 계층 및 비즈니스 로직</p>
</li>
<li><blockquote>
<p>실행 가능 JAR 파일이 없으므로 독립적으로 실행될 수 없음!
다른 모듈에서 사용하기 위한 라이브러리 형태로 컴파일됨</p>
</blockquote>
</li>
<li><p>infrastructure: 데이터베이스, 외부 API 통신</p>
</li>
<li><blockquote>
<p>실행 가능 JAR 파일이 없으므로 독립적으로 실행될 수 없음!
다른 모듈에서 사용하기 위한 라이브러리 형태로 컴파일됨</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드(Backend) 프로젝트 배포 (3) - GitHub Actions, workflow 작성]]></title>
            <link>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-3-GitHub-Actions-workflow-%EC%9E%91%EC%84%B1</link>
            <guid>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-3-GitHub-Actions-workflow-%EC%9E%91%EC%84%B1</guid>
            <pubDate>Tue, 14 Jan 2025 11:53:45 GMT</pubDate>
            <description><![CDATA[<h1 id="배포-과정">배포 과정</h1>
<h3 id="1-ec2--2-docker-hub">1. EC2 ~ 2. Docker Hub</h3>
<p><a href="https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC2">이전 글 보러가기</a></p>
<h3 id="3-github-actions-자동배포하기">3. GitHub Actions (자동배포하기!)</h3>
<p>먼저, GitHub Repository 환경변수 설정이 필요합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/1ab99ff0-1d47-4584-857a-803215006432/image.png" alt=""></p>
<p>①Secrets와 ②Variables가 있습니다. Secrets는 민감한 정보를 처리할 때, Variables는 민감 정보는 아닌 환경에따라 자주 바뀌는 값들을 등록해줍니다.</p>
<p>Environment secrets는 등록한 환경을 기준으로 변수를 관리하고, Repository secrets는 해당 레포지토리 기준으로 관리합니다. 저는 이 레포지토리에서만 사용할 환경변수(Repository secrets)를 등록하였습니다.</p>
<p>EC2 연결에 필요한 환경변수 등록이 필요합니다. 꼭 이 변수 이름을 따라하실 필요는 없습니다.</p>
<ul>
<li><code>EC2_USER</code></li>
<li><code>EC2_HOST</code></li>
<li><code>EC2_KEY</code></li>
</ul>
<p>USER, HOST, KEY 값은 무엇인가?</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b18325c2-ba6f-4478-8012-4c5284612ae1/image.png" alt=""></p>
<p>AWS EC2 인스턴스 페이지에서 연결 버튼을 클릭</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/48e9f410-a940-4728-9634-4505428103f9/image.png" alt=""></p>
<p><strong>사용자 이름(USER)</strong>을 확인할 수 있습니다. 위처럼 EC2를 생성하였다면 기본적으로 ubuntu일 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/812cf6b2-48c3-4384-80e1-5fedb07b01d6/image.png" alt=""></p>
<p>SSH 클라이언트 탭에서도 확인하실 수 있습니다. 사용자 이름과 서버 <strong>호스트 주소(HOST)</strong>가 @ 표기로 나뉘어 있습니다.
이것은 퍼블릭 IPv4 DNS 주소와 동일합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b56a21ce-e14c-4ee2-a1f0-1dfa55102a97/image.png" alt=""></p>
<p>마지막으로 <strong>키 페어(KEY)</strong> 값은 텍스트 편집기로 열면 텍스트를 확인할 수 있습니다.
또는, CLI 로 파일 내용 읽을 수 있습니다. (<code>cat</code>: Linux/macOS) (<code>type</code>: Windows)</p>
<pre><code class="language-bash">cat goodbite-back.pem</code></pre>
<pre><code class="language-bash">type goodbite-back.pem</code></pre>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/f9e2bdc7-8a8f-4096-a0e7-511a2e6fd1a8/image.png" alt="">
<img src="https://velog.velcdn.com/images/a-white-bit/post/b0d5817e-9978-4957-be4e-b08b2b1a11aa/image.png" alt="">
<img src="https://velog.velcdn.com/images/a-white-bit/post/f057ac33-1f0a-4cd5-91a5-1800575730d2/image.png" alt=""></p>
<p>.pem 파일 내용 전부 넣으시면 됩니다.
EC2 연결에 필요한 환경변수는 끝났습니다.</p>
<p>이번엔 Docker Hub 연결에 필요한 환경변수를 등록하겠습니다. 마찬가지로 이 변수 이름을 따라하실 필요는 없습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/82019864-d8ec-4d5f-9e39-9ba8f1ee16bd/image.png" alt=""></p>
<ul>
<li><code>DOCKERHUB_USERNAME</code> : 계정 사용자 이름</li>
<li><code>DOCKERHUB_PASSWORD</code> : 2번 값 copy</li>
</ul>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/fe51b68d-b0b2-44aa-9f2b-3be8abe368a2/image.png" alt=""></p>
<p>마지막으로, 레포지토리에 올려야 할 GitHub Actions의 설정 파일(yml)을 작성하도록 합니다.
전체적인 순서 흐름을 컨트롤하는 중요한 부분입니다. 워크플로우(workflow)라고 합니다. 이 설정 파일은 애플리케이션과 Docker 이미지를 빌드하고 EC2 인스턴스에 배포하는 일련의 과정을 정의합니다.</p>
<p>여기서 많은 시간과 노력을 쏟았습니다. 계속해서 오류들을 만나더라고요.. 정확한 순서와 이유를 파악해야 해결할 수 있었습니다. 제가 작성한 흐름이 정답은 아닙니다. 배포 방법은 정말 많아서 여러 방법을 참고해보는 것이 좋을 것 같습니다.</p>
<p>제가 작성한 다음 코드의 대략적인 순서 흐름은 다음과 같습니다.</p>
<ol>
<li>Gradle 빌드</li>
<li>QEMU 설정</li>
<li>Docker 이미지 빌드 및 Docker Hub에 push</li>
<li>EC2 인스턴스에 Docker 이미지 pull</li>
<li>EC2 인스턴스의 Docker 컨테이너 실행</li>
</ol>
<p>GitHub Actions의 설정 파일(워크플로우 파일)은 반드시 레포지토리의 <code>.github/workflows</code> 디렉토리에 위치해야 합니다. 그래야 워크플로우 파일을 자동으로 인식합니다. 설정 파일(*.yml) 이름은 상관 없습니다.</p>
<pre><code class="language-yml">on:
  push:
    branches: [ dev ]

env:
  DOCKER_IMAGE_TAG_NAME: good-bite

jobs:
  build-and-docker-push:
    runs-on: ubuntu-20.04

    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: &#39;21&#39;
          distribution: &#39;corretto&#39;

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Decode keystore and save
        run: echo &quot;${{ secrets.PKCS12_BASE64 }}&quot; | base64 --decode &gt; src/main/resources/api.goodbite.site.p12

      - name: Build with Gradle
        run: ./gradlew clean build -x test
        env:
          DB_USERNAME: ${{ vars.DB_USERNAME }}
          ...
          ...

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest
          no-cache: true

  deploy-to-ec2:
    needs: build-and-docker-push
    runs-on: ubuntu-latest

    steps:
      - name: Deploy to EC2
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ vars.EC2_USER }}
          key: ${{ secrets.EC2_KEY }}
          script: |
            sudo apt-get update
            sudo apt-get install -y docker.io
            sudo usermod -aG docker $USER
            newgrp docker

            CONTAINER_ID=$(sudo docker ps -q --filter &quot;publish=80-80&quot;)

            if [ ! -z &quot;$CONTAINER_ID&quot; ]; then
            sudo docker stop $CONTAINER_ID
            sudo docker rm $CONTAINER_ID
            fi

            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest

            sudo docker run -d -p 80:80 \
                -e DB_USERNAME=${{vars.DB_USERNAME}} \
                ... \
            ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest</code></pre>
<p><strong>라벨 간단 설명</strong></p>
<ul>
<li><p><code>on:push:branches</code> 지정 브랜치에 푸시가 발생할 때 워크플로우 이벤트 발생</p>
</li>
<li><p><code>env</code> 변수 지정, label depth에 따라 적용 범위가 다름</p>
</li>
<li><p><code>Jobs: [job-name]:</code> 워크플로우에서 실행할 일련의 작업 과정. 하나의 job은 순차적으로 실행하며, 각각의 job은 병렬적으로 수행</p>
</li>
<li><p><code>runs-on</code> 실행환경</p>
</li>
<li><p><code>steps</code> 작업 순서를 (-) 단위로 구분</p>
</li>
<li><p><code>name: [step-name]</code> 각 작업 단계(step)의 이름</p>
</li>
<li><p><code>uses</code> GitHub Actions의 공식 액션을 사용</p>
<ul>
<li>예시: <code>actions/checkout@v3</code> 워크플로우 실행 시 레포지토리 코드를 가져오는 작업</li>
<li><a href="https://github.com/marketplace?type=actions">GitHub Marketplace</a> - GiHub Actions 의 다양한 액션 확인하기</li>
</ul>
</li>
<li><p><code>with</code> uses의 매개변수</p>
</li>
<li><p><code>${{secrets.~}}</code> , <code>${{vars.~}}</code>, <code>${{env.~}}</code></p>
<ul>
<li>Setting 에 환경변수 지정했던 Secrets(1번)은 <code>secrets</code>,</li>
<li>Variables(2번)은 <code>vars</code>,</li>
<li>yml 파일에(나 같은 경우는 상단에) 지정한 <code>env</code> 를 변수로 사용
<img src="https://velog.velcdn.com/images/a-white-bit/post/1ab99ff0-1d47-4584-857a-803215006432/image.png" alt=""></li>
</ul>
</li>
</ul>
<p><strong>작업 간단 설명</strong></p>
<ul>
<li><p><code>run: chmod +x gradlew</code> gradlew 파일 실행 권한 부여</p>
</li>
<li><p><code>run: echo &quot;${{ secrets.PKCS12_BASE64 }}&quot; | base64 --decode &gt; src/main/resources/api.goodbite.site.p12</code> 디코딩 변환이 필요한 추가 작업을 수행</p>
<ul>
<li>src/main/resources/ 디렉토리에 .p12 파일로 저장 <strong>(Actions가 실행될 때 사용되는 가상머신에 임시로 저장되는 것입니다.)</strong></li>
</ul>
</li>
<li><p><code>run: ./gradlew clean build -x test</code> 테스트 없이 빌드</p>
</li>
<li><p><code>uses: docker/setup-qemu-action@v3</code> QEMU 설정(하드웨어 가상화/에뮬레이터)</p>
<ul>
<li>멀티 아키텍처 지원: x86과 ARM 모두 지원하는 Docker 이미지 빌드하는 경우 필요</li>
</ul>
</li>
<li><p><code>uses: docker/login-action@v3</code> Docker Hub 로그인</p>
</li>
<li><p><code>uses: docker/setup-buildx-action@v3</code> 멀티 아키텍처 이미지 빌드</p>
</li>
<li><p><code>uses: docker/build-push-action@v6</code> Dockerfile을 기반으로 이미지를 빌드하고, Docker Hub로 푸시</p>
</li>
<li><p><code>uses: appleboy/ssh-action@v1.0.3</code> EC2 인스턴스에 SSH로 접속</p>
</li>
<li><p><code>script</code> 실행 스크립트</p>
<pre><code class="language-bash">  # EC2 인스턴스에 필요한 패키지 설치
  sudo apt-get update
  sudo apt-get install -y docker.io
  sudo usermod -aG docker $USER # 현재 사용자를 Docker 그룹에 추가
  newgrp docker # 그룹 변경

  # 기존 컨테이너 중지 및 제거
  CONTAINER_ID=$(sudo docker ps -q --filter &quot;publish=443-443&quot;)
  if [ ! -z &quot;$CONTAINER_ID&quot; ]; then
  sudo docker stop $CONTAINER_ID
  sudo docker rm $CONTAINER_ID
  fi

  # Docker Hub에서 이미지 pull
  sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest

  # 위 이미지로 Docker 컨테이너를 실행
  sudo docker run -d -p 80:80 \ # 컨테이너 실행 포트
      -e DB_USERNAME=${{secrets.DB_USERNAME}} \
      -e ... \ # Docker 에게 환경변수 전달
  ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_TAG_NAME }}:latest</code></pre>
</li>
</ul>
<p>yml 파일을 작성 후, GitHub 저장소 트리거 브랜치에 푸시하게 되면 자동으로 위 스크립트를 실행합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/f0db8a13-7cf2-4e73-91df-f8b1e6f12163/image.png" alt=""></p>
<p>Actions 탭에서 실시간으로 workflow 현황을 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/1b88dc8d-1011-42b4-987f-e71c3e7d4ba1/image.png" alt=""></p>
<p>jobs가 성공했는지, 실패했는지 확인할 수 있고,</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/28a2678c-3482-4fed-9f2b-6d3560df75b7/image.png" alt=""></p>
<p>step 별로 어떤 이유로 실패했는지 로그를 확인할 수 있습니다.
프로젝트 파일 수정이 필요하다면 수정한 후 다시 push 해서 재시도하면 되고</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/11f4e640-ff2f-4846-a014-5379a165434d/image.png" alt=""></p>
<p>파일 변경은 없고 외부 문제(EC2 서버 다운 등)의 경우 [Re-run jobs] 버튼을 사용하면 됩니다.</p>
<hr>
<h2 id="배포-후-관리">배포 후 관리</h2>
<ul>
<li>SSH로 인스턴스 연결</li>
</ul>
<p>배포 서버가 잘 돌아가는지 확인할 필요가 있습니다. 서비스 장애가 발생하면 로그를 확인해야 할 것입니다. AWS를 사용한다면 간편하게 접근할 수 있는 방법을 제공하고 있습니다.</p>
<p>여러 방법이 있지만, 저는 SSH 클라이언트로 연결하였습니다. 인텔리제이에서 개발 작업을 하면서 동시에 배포 서버에 접속하기 가장 편리했던 방법입니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b18325c2-ba6f-4478-8012-4c5284612ae1/image.png" alt=""></p>
<p>AWS EC2 인스턴스 페이지에서 연결 버튼을 클릭</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/812cf6b2-48c3-4384-80e1-5fedb07b01d6/image.png" alt=""></p>
<p>SSH 클라이언트 탭에서 ssh 명령어를 복사하여 터미널에 실행합니다. 이전에 저장했던 pem 파일이 존재해야 합니다. 간혹 퍼미션 문제가 발생하던데, <code>chmod</code> 명령어로 pem 파일에 적절한 권한 부여를 해줘야 합니다. (<strong>400 또는 600</strong> 권장)</p>
<blockquote>
<p><strong>발생 이유</strong>
SSH 명령어로 .pem 파일을 사용할 때 권한 문제가 발생하는 이유는, .pem 파일의 권한이 너무 넓을 경우 SSH가 보안을 위해 경고를 출력하고 접속을 차단합니다. .pem 파일은 개인 키 파일이므로 특정 권한만 허용해야 합니다.</p>
</blockquote>
<hr>
<h2 id="기타사항">기타사항</h2>
<h3 id="ec2-관리">EC2 관리</h3>
<p>클라우드 관리 시에 내가 생성한 인스턴스를 함께하는 팀원 혹은 다른 사람이 리소스를 관리해야 할 사항이 생기기도 합니다. AWS는 이를 위한 서비스로 IAM(Identity and Access Management)을 제공합니다.</p>
<p>AWS에서 제공하는 리소스 접근 권한 서비스는 IAM 말고도 여러 개 있으니 상황에 맞게 적절히 사용하는 것이 좋을 것 같다고 생각합니다.</p>
<ul>
<li>IAM 서비스 사용 <a href="https://velog.io/@a-white-bit/AWS-IAM">https://velog.io/@a-white-bit/AWS-IAM</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드(Backend) 프로젝트 배포 (2) - AWS EC2 인스턴스, Docker Hub 설정]]></title>
            <link>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC2</link>
            <guid>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC2</guid>
            <pubDate>Tue, 14 Jan 2025 11:47:06 GMT</pubDate>
            <description><![CDATA[<h1 id="good-bite-서비스-아키텍처-설계">Good Bite 서비스 아키텍처 설계</h1>
<p><a href="https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-1-GitHub-Actions-Docker-AWS-EC2">이전 글 보러가기</a></p>
<hr>
<h1 id="배포-과정">배포 과정</h1>
<p>AWS 서비스 사용과 CI/CD 구현을 처음 해보았는데요.</p>
<p>AWS는 정말 많은 서비스를 제공하는 만큼 시스템이 복잡하고 모르는 용어도 많이 등장하고.. 네트워크 통신 개념 이해와 보안 개념을 알고 있어야 많은 활용을 할 수 있겠더라구요.</p>
<p>CI/CD 구현은 <strong>전체적인 빌드와 배포 과정을 이해하고 있어야</strong> 설정을 커스텀 할 수 있었습니다.</p>
<p>처음 숙지하는 데 오래걸렸지만, 한 번 구현하고 나면 이렇게 쉬운 게 없습니다. 😎
배포가 처음엔 다들 어렵다더라고요. 저도 그랬습니다.
추후에 다시 활용할 수 있도록 이번에 프로젝트에서 사용한 배포 과정을 기록해보겠습니다.
아직 미숙한 부분도 많아 이상한 부분은 알려주시면 감사하겠습니다!</p>
<p>과정은 크게 4가지로 나눌 수 있습니다. </p>
<blockquote>
</blockquote>
<ul>
<li>Java 애플리케이션</li>
<li>AWS EC2</li>
<li>Docker</li>
<li>GitHub Actions</li>
</ul>
<h3 id="0-java-프로젝트-빌드-확인">0. Java 프로젝트 빌드 확인</h3>
<p>CI/CD 파이프라인에서 자동적으로 빌드하기 때문에 직접 빌드해 보는 것은 필수 과정은 아닙니다.
하지만 우리는 강력한 빌드 도구를 사용하고 있기에 Java 프로젝트 빌드에 어떤 조건이 필요하며 성공적으로 완수되는지 한 번쯤은 수동적으로 확인해보는 것이 좋을 것 같습니다.</p>
<p>gradle 기준으로 다음 명령어를 사용하여 jar 파일을 생성하면 됩니다.</p>
<ul>
<li>Linux/Unix/macOs:
<code>./gradlew clean build</code> 또는 <code>./gradlew build</code></li>
<li>Windows:
<code>gradlew.bat clean build</code> 또는 <code>gradlew.bat build</code></li>
</ul>
<p>clean build는 빌드 디렉토리를 삭제한 후에 새로운 빌드를 시작합니다. 이전 빌드의 잔여물이 남아 있는 경우 이를 제거하기 때문에 더 신뢰성 있는 빌드를 보장합니다.
build는 현재 상태에서 빌드를 시작하며, 이전 빌드의 결과물을 재사용할 수 있습니다. 이로 인해 빌드 시간이 단축될 수 있지만, 이전 빌드의 결과가 남아 있어 문제가 발생할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/41ab132d-3103-481c-bbd4-308225656d76/image.png" alt=""></p>
<p>빌드에 성공하면 build/libs/*.jar 파일이 생성됩니다.
jar 파일의 이름은 gradle.build 파일의 version으로 명시할 수 있습니다.</p>
<pre><code class="language-kotlin">group = &#39;site.mygumi&#39;
version = &#39;0.0.1-SNAPSHOT&#39;</code></pre>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/c0014310-cfdc-43df-ad64-0aeaf167f8a2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/4a423a91-52d8-48fa-94a9-216a99159593/image.png" alt=""></p>
<p><strong>주의!</strong> 기본적으로 테스트 애플리케이션도 검사합니다. 테스트에 필요한 환경변수가 주입되고 있지 않다면 위처럼 빌드 오류가 발생할 수 있습니다.</p>
<p>스크린샷의 경우, GoodbiteApplication에만 환경변수 설정이 되어있고, GoodbiteApplicationTests의 환경변수는 없어서 빌드 오류가 발생합니다.
GoodbiteApplicationTests의 환경변수에 필요한 값을 설정하면 됩니다.</p>
<p><strong>또는,</strong> <code>./gradlew clean build -x test</code> 명령어를 사용하면 테스트를 검사하지 않고 빌드할 수 있습니다.</p>
<h3 id="1-ec2-배포-서버">1. EC2 (배포 서버)</h3>
<p>제 컴퓨터로 애플리케이션을 상시로 돌리기는 부담스러우니 AWS의 EC2 가상화 클라우드 서비스를 선택했습니다.</p>
<blockquote>
<p><a href="https://aws.amazon.com/ko/free/?p=ft&amp;z=subnav&amp;loc=1&amp;refid=fa2d6ba3-df80-4d24-a453-bf30ad163af9&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all">Amazon Web Services 한국어 페이지</a></p>
</blockquote>
<p>AWS 서비스를 처음 사용하니 간단하게 가입하고 콘솔 홈으로 이동합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/0d251e40-5d79-4d28-9c43-8c4b875171c9/image.png" alt=""></p>
<p>처음이라면 스크린샷 ①번이 서울인지 <strong>꼭!</strong> 확인합니다.
AWS 서비스는 모두 리전에 따라 대시보드 내용이 달라집니다. 서울에 인스턴스 하나, 미국에 인스턴스 하나 생성해보시면 알게 됩니다.
리전을 서울로 바꾸는 것이 속도 면에서도 안정적이고 편리한 면이 있습니다. 저는 이것을 확인하지 않고 삽질한 경험이 있죠 🔨 <del>망치질</del></p>
<p>②번에서 EC2 대시보드 페이지로 이동합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/cfb30259-7f52-4c89-b37a-970be4993cc7/image.png" alt=""></p>
<p>인스턴스 시작 버튼을 누릅니다. (인스턴스 생성)</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/c6befbf4-f7b0-42aa-9827-11b53a4a3c23/image.png" alt=""></p>
<p>인스턴스 이름을 설정하고,
가장 중요한 것은 인스턴스의 OS입니다. 내 애플리케이션이 어떤 환경이 필요한지 고려하고 선택하면 좋을 것 같습니다.
이 프로젝트는 Windows에 의존적이지 않기 때문에 굳이 선택할 이유가 없어 Ubuntu를 선택하였습니다.</p>
<blockquote>
<p><strong>이유</strong>: 
리눅스 인스턴스는 일반적으로 윈도우즈 인스턴스보다 비용이 저렴하며, 가벼운 OS로 인해 성능이 더 나은 경우가 많습니다. 그리고 일반적으로 보안과 안정성에서 강점을 보입니다. 특히, 서버 자원을 효율적으로 사용하는 백엔드 애플리케이션이라면 리눅스가 더 나을 수 있습니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/c8dbdebb-fb89-4f01-9127-ad36fadbac84/image.png" alt=""></p>
<p>프리 티어로 부담없이 쓸 수 있는 t2.micro를 선택하였습니다. 메모리가 1GiB인 것에 주의하여야합니다. 개복치가 따로없습니다. 이것을 보완하기 위한 방법은 다음 링크를 참고해주세요.</p>
<blockquote>
<p>[EC2 메모리 부족 현상 보완 페이지]</p>
</blockquote>
<p>키 페어 없이 진행할 수 있지만 호스트 주소만 알아내면 쉽게 접속이 가능하기 때문에 보안상 좋지 않습니다. 키 페어가 아직 없다면 새로 생성합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/69e92a15-7654-4f17-b040-ecbc58f203e9/image.png" alt=""></p>
<p>저는 RSA 암호화 방식과 .pem 키 파일 타입을 선택하였습니다.
키 페어 생성 버튼을 누르면, .pem 파일을 로컬 머신에 저장할 수 있습니다.</p>
<blockquote>
<p><strong>.pem 파일</strong>: Privacy Enhanced Mail 파일 형식의 약자로, 주로 보안 인증서, 개인 키, 공개 키, 그리고 인증서 체인을 저장하는 데 사용됩니다. 데이터를 인코딩하여 일반 텍스트 형식으로 저장합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/fe01a384-fedd-4cbd-870b-6e6156d03a77/image.png" alt=""></p>
<p>저는 프로젝트 폴더 최상단에 저장해주었습니다. 나중에 SSH로 연결할 때 편합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/16e672d9-e03f-47b4-af62-de582c8c25a3/image.png" alt=""></p>
<p>.gitignore 파일을 설정하여 키 파일들은 GitHub 원격저장소에는 노출되지 않도록 하는 것이 좋습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/010de6a2-2740-492e-8aa3-08b52b4d07c7/image.png" alt=""></p>
<p>마지막으로 네트워크 설정입니다. 보안 그룹을 생성해도 되고, 기존 보안 그룹을 선택해도 되는데 처음이라면 보안 그룹을 생성해줍니다.</p>
<p>프로젝트가 HTTP와 HTTPS 통신 중 사용하는 것을 포함시키고,
SSH는 포함하는 것이 개발에 편리합니다. 나 혼자 이 서버를 관리한다면 내 IP만 허용하도록 해도 좋습니다. 저는 팀원을 포함하여 여러 머신에서 접속할 수 있도록 위치 무관을 선택하였습니다.</p>
<p>이제 인스턴스를 시작하면 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/e25d8a62-6993-4f85-904a-2ceab5bccf7a/image.png" alt=""></p>
<p>인스턴스 상태가 실행 중으로 뜬다면 준비는 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/4259cf84-041d-4aab-968e-172d5dd436d8/image.png" alt=""></p>
<p>인스턴스를 클릭하고 보안탭을 보면 설정한 인바운드 규칙을 볼 수 있습니다.
이 규칙을 추가하거나 수정하려면 보안 그룹 이름을 누르거나 아래처럼
네트워크 및 보안 &gt; 보안 그룹에 가면 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/327db6d6-fa8b-48f4-9398-c16d79406f47/image.png" alt=""></p>
<p>서버 배포와 관련하여 보안 설정에 따라 규칙을 수정하는 것이 필요합니다. 예를 들어, https를 설정하지 않아 443 포트를 사용하지 않는다면 인바운드 규칙에서 제거해야 합니다.</p>
<p>source(번역명이 원본 혹은 소스로 혼용)를 모두 허용(0.0.0.0/0)하는 것보다 최소의 바운더리만 허용하는 것이 보안상 좋을 것 같습니다.
그렇지만 지금은 백엔드 서버에 누구나 접속해볼 수 있도록 의도했기 때문에 위와 같이 설정하였습니다.</p>
<h3 id="2-docker-hub">2. Docker Hub</h3>
<p>도커 이미지 생성 + 컨테이너 실행 을 자동으로 실행해주도록 배포하고 싶다면 Docker 원격 저장소인 Docker Hub를 사용하는 것이 편리합니다.</p>
<blockquote>
<p><a href="https://hub.docker.com/">Docker Hub 홈페이지</a></p>
</blockquote>
<p>먼저 Docker Hub 계정을 생성합니다. </p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b6fb58f2-e1ab-4550-a88f-9ff5df085380/image.png" alt=""></p>
<p>계정을 생성하고 로그인하면 우측 상단 Account settings를 클릭합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/91312c94-0500-4144-97b4-d92e256e343c/image.png" alt=""></p>
<p>도커 허브 레포지토리에 접근하려면 계정 이름과 패스워드가 필요합니다.
그런데 내 패스워드를 사용하면 팀원들이 접근을 해야한다면 내 패스워드 노출이 필요하게 됩니다.
이런 점을 보완하기 위해 Personal Access Tokens(PATs)를 사용할 수 있습니다.</p>
<p>Personal access tokens 페이지로 이동합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/7c40aaca-53a5-4619-aa69-6de2eff91886/image.png" alt=""></p>
<p>Generate new token 버튼 클릭</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b9f1a0a6-b2e7-481b-b68d-b7344130bfb2/image.png" alt=""></p>
<p>모든 권한을 부여하도록 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/5bdcf97d-5aab-4c87-9eb8-3d46aa96aa32/image.png" alt=""></p>
<p>1번은 docker hub에 로그인하는 명령어지만 우리는 자동으로 처리할 것입니다. 사용자 이름만 기억하면 됩니다!
2번은 도커 액세스 토큰을 얻기 위한 패스워드입니다.
이 정보는 뒤에 GitHub Actions 섹션에서 활용하겠습니다.</p>
<p><strong>주의!</strong> 2번은 재차 확인할 수 없습니다. 다시 찾을 수 없다면 access token을 다시 생성해야 합니다..</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/02903ea9-fc33-47b3-be5d-8ac92123b3ac/image.png" alt=""></p>
<p>프로젝트 최상단에 Dockerfile 을 작성합니다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/49b9daff-697e-49a8-b23d-60ae281224f2/image.png" alt=""></p>
<pre><code class="language-Dockerfile">FROM openjdk:21-jdk-slim

LABEL authors=&quot;white&quot;

COPY build/libs/goodbite-0.0.1-SNAPSHOT.jar /app/goodbite.jar

EXPOSE 443

ENV ..

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app/goodbite.jar&quot;]</code></pre>
<ul>
<li>FROM: 이미지 생성할 때 베이스가 될 도커 이미지 지정</li>
<li>LABLE: 메타데이터 설정, 키-값 형식, 이미지/컨테이너의 정보 제공, 검색, 관리 등의 역할</li>
<li>COPY: 로컬 파일 시스템의 파일이나 디렉토리를 이미지의 특정 위치로 복사</li>
<li>EXPOSE: 도커의 포트 노출 번호</li>
<li>ENV: 컨테이너 내부에 환경변수를 설정(저는 컨테이너 외부의 환경변수 값을 내부에 전달하였습니다.)</li>
<li>ENTRYPOINT: 명령어 실행</li>
</ul>
<h3 id="3-github-actions">3. GitHub Actions</h3>
<p><a href="https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-3-GitHub-Actions-workflow-%EC%9E%91%EC%84%B1">다음 글 보러가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 삭제 방식 - 논리적 삭제, 소프트 딜리트(Soft delete)]]></title>
            <link>https://velog.io/@a-white-bit/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%82%AD%EC%A0%9C-%EB%B0%A9%EC%8B%9D-%EB%85%BC%EB%A6%AC%EC%A0%81-%EC%82%AD%EC%A0%9C-%EC%86%8C%ED%94%84%ED%8A%B8-%EB%94%9C%EB%A6%AC%ED%8A%B8Soft-delete</link>
            <guid>https://velog.io/@a-white-bit/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%82%AD%EC%A0%9C-%EB%B0%A9%EC%8B%9D-%EB%85%BC%EB%A6%AC%EC%A0%81-%EC%82%AD%EC%A0%9C-%EC%86%8C%ED%94%84%ED%8A%B8-%EB%94%9C%EB%A6%AC%ED%8A%B8Soft-delete</guid>
            <pubDate>Fri, 10 Jan 2025 05:15:01 GMT</pubDate>
            <description><![CDATA[<h1 id="논리적-삭제-소프트-딜리트soft-delete">논리적 삭제, 소프트 딜리트(Soft delete)</h1>
<p>소프트 딜리트(Soft delete)는 데이터베이스나 애플리케이션에서 데이터를 실제로 삭제하지 않고, 삭제된 것으로 표시하는 방법을 의미합니다. 논리적으로 삭제되었다고 하며, 실제 데이터는 남아 있지만 삭제된 것으로 간주됩니다.</p>
<p>다음의 표현들도 이와 유사한 개념입니다.</p>
<ol>
<li><strong>Flagged delete</strong>: 삭제된 데이터를 플래그(표시)하여 관리하는 방법을 의미합니다.</li>
<li><strong>Inactive record</strong>: 비활성화된 레코드라는 의미로, 더 이상 사용되지 않지만 데이터베이스에 남아 있는 상태를 나타냅니다.</li>
<li><strong>Archive</strong>: 데이터를 아카이브하여 보관하는 방법으로, 삭제 대신 보관하는 개념입니다.</li>
</ol>
<p>이 방법은 삭제 상태를 나타내는 플래그를 추가하여 데이터의 물리적 삭제를 피하고, 데이터의 이력을 유지할 수 있는 점을 활용하기 위해 채택할 수 있습니다. 소프트 딜리트는 다음과 같은 장단점이 있습니다.</p>
<h3 id="장점">장점</h3>
<ol>
<li><p><strong>데이터 복구 용이</strong>: 실수로 삭제된 데이터를 쉽게 복구할 수 있습니다. 단순히 <code>is_deleted</code> 플래그를 <code>FALSE</code>로 변경하면 됩니다.</p>
</li>
<li><p><strong>데이터 이력 유지</strong>: 삭제된 데이터의 이력을 유지할 수 있어, 감사 로그나 데이터 분석에 유용합니다.</p>
</li>
<li><p><strong>비즈니스 로직 단순화</strong>: 데이터 삭제에 대한 비즈니스 로직이 단순해지며, 데이터 무결성을 유지할 수 있습니다.</p>
</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li><p><strong>데이터베이스 크기 증가</strong>: 삭제된 데이터도 테이블에 남아 있기 때문에 데이터베이스의 크기가 증가할 수 있습니다. 이는 성능 저하를 초래할 수 있습니다.</p>
</li>
<li><p><strong>복잡한 쿼리</strong>: 모든 조회 쿼리에 <code>is_deleted</code> 등의 조건을 추가해야 하므로 쿼리가 복잡해질 수 있습니다. 이를 관리하기 위해서는 추가적인 쿼리 메서드나 필터링 로직이 필요합니다.</p>
</li>
<li><p><strong>데이터 정합성 문제</strong>: 소프트 딜리트를 사용하면 데이터의 상태가 여러 가지가 될 수 있어, 데이터 정합성을 유지하는 것이 어려울 수 있습니다. 예를 들어, 삭제된 데이터가 다른 테이블과 연관되어 있을 경우, 이를 처리하는 로직이 필요합니다.</p>
</li>
</ol>
<p>소프트 딜리트는 특정 상황에서 유용하게 사용될 수 있지만, 데이터베이스 설계와 비즈니스 요구 사항에 따라 적절한 방법을 선택하는 것이 중요합니다. </p>
<h3 id="소프트-딜리트-구현">소프트 딜리트 구현</h3>
<p>다음은 제가 프로젝트에서 구현해 본 소프트 딜리트 예시입니다. Spring Boot 프레임워크를 사용하였습니다.</p>
<ol>
<li><p><strong>테이블 구조 변경</strong>: 데이터베이스 테이블에 <code>deletedAt</code> 또는 <code>is_deleted</code>와 같은 필드를 추가합니다. 이 필드는 데이터가 삭제되었는지를 나타냅니다.
예시 코드는 <code>is_deleted</code> 없이 <code>deletedAt</code> 값의 존재 여부로 판단합니다.</p>
<pre><code class="language-java">@Entity
public class User {

   private final Long id;
   private final String email;
   private final UserRole role;
   private final OAuth2Provider provider;
   private Instant deletedAt;

     public void softDelete() {
     this.deletedAt = Instant.now();
     }
 }</code></pre>
</li>
<li><p><strong>삭제 로직 수정</strong>: 데이터를 삭제할 때는 <code>User</code>의 헬퍼 메서드 <code>softDelete()</code>를 사용하여 삭제 상태로 변경하고 해당 레코드의 <code>deletedAt</code> 필드에 시간을 기록합니다.</p>
<pre><code class="language-java">public void softDeleteUser(Long userId) {
    User user = userRepository.findById(userId);
    user.softDelete();
    userRepository.save(user);
}</code></pre>
</li>
<li><p><strong>조회 로직 수정</strong>: 데이터를 조회할 때는 <code>deleteAt</code>이 <code>NULL</code>인 데이터만 선택하도록 쿼리를 수정합니다.</p>
<pre><code class="language-java">public Optional&lt;User&gt; findActiveUser(Long userId) {
    return userRepository.findByIdAndDeleteAtIsNull(userId);
}
public List&lt;User&gt; findAllActiveUsers() {
    return userRepository.findByDeleteAtIsNull();
}</code></pre>
</li>
</ol>
<h3 id="활용희망">활용(희망)</h3>
<blockquote>
<p><strong>예시</strong>)</p>
<p>연관 테이블인 <code>Member</code> 에서 데이터를 조작할 때 모든 상황에서 &#39;<code>User</code>가 논리 삭제되었는가?&#39; 를 판단하고 참조해야 한다.</p>
</blockquote>
<p>다음 코드는 <code>Set&lt;Member&gt;</code>를 저장하는 로직입니다.</p>
<pre><code class="language-java">public void saveAll(Long teamId, Set&lt;Member&gt; members) {

    Team team = checkTeamDeleted(teamId);

    members.removeIf(member -&gt; {
        User user = checkUserDeleted(member.getUserId());
        return user == null; // 삭제된 사용자일 경우 제거
    });
    memberRepository.saveAll(members);
}

private User checkUserDeleted(Long userId) {
    return userRepository.findActiveUser(userId)
        .orElseThrow(() -&gt; throw new MemberException());
}

private Team checkTeamDeleted(Long teamId) {
    return teamRepository.findActiveTeam(teamId)
        .orElseThrow(() -&gt; throw new MemberException());
}</code></pre>
<p>소프트 딜리트 방식을 채택함으로써 연관된 데이터 조작할 때 확인하는 로직이 추가되어야 합니다.</p>
<h3 id="활용절망">활용(절망)</h3>
<blockquote>
<ul>
<li><code>memberRepository</code> 에서 <code>userRepository</code>, <code>teamRepository</code>를 직접 참조할 수 없는 상황</li>
<li>Entity와 Domain의 역할 분리 아키텍처</li>
</ul>
</blockquote>
<pre><code class="language-java">    @Override
    public void saveAll(Long teamId, Set&lt;Member&gt; members) {
        TeamEntity team = checkTeamDeleted(teamId);
        Set&lt;MemberEntity&gt; entities = members.stream()
            .map(member -&gt; {
                UserEntity user = checkUserDeleted(member.getUserId());
                if (member.getId() != null) {
                    MemberEntity entity = memberJpaRepository.findById(member.getId())
                        .orElseThrow(() -&gt; new MemberException(
                            MemberErrorCode.MEMBER_NOT_FOUND,
                            member.getId()
                        ));
                    entity.update(member.getRole(), member.isLeader());
                    return entity;
                }
                return MemberEntity.fromDomain(member, user, team);
            })
            .collect(Collectors.toSet());
        memberJpaRepository.saveAll(entities);
    }</code></pre>
<p>같은 기능이지만 좀 더 복잡한 아키텍처와 제한적인 환경에서 개발한 예시 코드입니다.
가독성이 매우 떨어집니다.</p>
<p>희망편에서는 제약사항이 없고 기본적인 구조에서는 간단하게 삭제 여부를 확인하는 <code>findActiveUser()</code>를 실행하면 되었지만 위처럼 복잡해질 수 있습니다.
위에 설명한 단점 &#39;2. <strong>복잡한 쿼리</strong>&#39; 와 &#39;3. <strong>데이터 정합성 문제</strong>&#39; 가 발생하는 것입니다.</p>
<p>위 코드의 경우 코드의 가독성 및 성능이 떨어질 우려가 있어 최적화 작업이 필요합니다.
글의 주제에 벗어나는 것 같아 길게 적진 않으려 합니다.</p>
<p>최적화 방법 고려 중 (최대한 애플리케이션 측에서 해결하는 방법)</p>
<ol>
<li>JPQL 및 QueryDSL 사용</li>
<li><code>@Where</code> 사용</li>
<li><code>@Filter</code> 및 <code>@FilterDef</code> 사용</li>
</ol>
<h3 id="결론">결론</h3>
<p>이처럼 데이터 복구 등 안전한 장치를 위해 소프트 딜리트를 사용하는 것은 데이터 소실이 민감하거나 중요한 데이터에서는 필요하다고 생각합니다.
예시) 사용자 계정 정보</p>
<p>하지만 개발 로직이 복잡해질 수 있으니 개발자가 잘못된 로직을 설계하면 버그 발생 확률이 높아지니 정말 필요한 곳에만 사용하는 것이 프로그램의 일관성 및 안정성을 지킬 수 있을 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 분리된 모듈 설계에서 빌드 설정]]></title>
            <link>https://velog.io/@a-white-bit/Spring-Boot-%EB%B6%84%EB%A6%AC%EB%90%9C-%EB%AA%A8%EB%93%88-%EC%84%A4%EA%B3%84%EC%97%90%EC%84%9C-%EB%B9%8C%EB%93%9C-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@a-white-bit/Spring-Boot-%EB%B6%84%EB%A6%AC%EB%90%9C-%EB%AA%A8%EB%93%88-%EC%84%A4%EA%B3%84%EC%97%90%EC%84%9C-%EB%B9%8C%EB%93%9C-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Tue, 17 Dec 2024 11:39:44 GMT</pubDate>
            <description><![CDATA[<h1 id="의문점">의문점</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/ee170868-2530-4887-9970-20c7601f85e9/image.png" alt=""></p>
<p>현 프로젝트는 <strong>api, core, infrastructure</strong> 세 모듈로 구성되어있다.
IntelliJ IDEA를 사용하여 Spring Boot 모듈을 추가했을 때 기본적으로 <code>build.gradle</code>을 구성해준다.</p>
<p>그래서 프로젝트의 <code>build.gradle</code>과 각 모듈의 <code>build.gradle</code> 3개를 총합하면 4개의 <code>build.gradle</code> 파일이 존재하고 있다.</p>
<p>각 모듈에서만 사용될 의존성 등은 모듈 속 빌드 설정을 하면 되겠지만
<strong>전체 프로젝트의 빌드 설정</strong>을 어떻게 하면 효과적으로 사용할 수 있을지 고민이 되었다.</p>
<p>또한,
빌드 결과물이 각 모듈마다 출력된다. 이것들을 추후 <strong>배포할 때</strong> 어떤 방법을 택해야 할까.</p>
<br>

<h1 id="jar-vs-bootjar">jar vs bootJar</h1>
<hr>
<ul>
<li><code>build.gradle (:api)</code> 예시<pre><code>plugins {
  id &#39;org.springframework.boot&#39; version &#39;3.1.0&#39;
  id &#39;java&#39;
}
</code></pre></li>
</ul>
<p>bootJar {
    enabled = true  // Spring Boot 실행 JAR 생성
}</p>
<p>jar {
    enabled = false  // 표준 JAR 비활성화
}</p>
<p>dependencies {
    implementation project(&#39;:core&#39;)  // 내부 라이브러리 의존성
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
}</p>
<pre><code>
## jar

jar 태스크는 표준 Java 라이브러리 jar 파일을 생성
- 애플리케이션 실행 불가
- **재사용 가능한 패키지, 라이브러리, 유틸 배포 시 사용**
- Spring 실행 환경 미포함

## bootJar

bootJar 태스크는 Spring Boot 실행 가능한 jar 파일을 생성
- **애플리케이션 실행 가능** `java -jar`
- Spring Boot 런타임, 모든 의존성, 애플리케이션 라이브러리 포함

## 권장 빌드 전략

- Spring Boot 애플리케이션 (`api`): `bootJar` ⭕, `jar` ❌ 
- 라이브러리/유틸리티 모듈 (`core`, `infrastructure`): `bootJar` ❌, `jar` ⭕

## ❓ 궁금한 것

- `jar`, `bootJar` 둘 다 활성화하면?
    -&gt; `build/libs/` 디렉토리에 둘 다 생성됨
- `jar` 또는 `bootJar` 태스크가 명시되지 않으면?
    -&gt; 기본적으로 `jar` 태스크를 실행하나,
    -&gt; Spring Boot 플러그인이 있다면 `bootJar` 태스크를 실행 (`jar`❌)

&lt;br&gt;

# 빌드 전략
---

멀티 모듈 프로젝트의 빌드 전략..

크게 두 가지의 선택지가 있다.

1. 통합 단일 JAR 생성
    - 루트 프로젝트에 실행 가능한 JAR 파일 생성
    - `api`, `core`, `infrastructure` 병합됨
    - 단일 서비스의 경우 적합 (REST API만 제공 등)

2. 모듈별 개별 JAR 생성
    - 모듈 간 독립적 배포 가능
    - 마이크로서비스(개별 서비스를 배포), 라이브러리 배포 등 가능

나는 1번을 선택 (REST API 백엔드 서버)

## 통합 JAR 생성 전략

- 프로젝트 구조</code></pre><p>multi-module-project/
├── build.gradle
├── settings.gradle
├── api
│   └── build.gradle
├── core
│   └── build.gradle
├── infrastructure
|   └── build.gradle
...</p>
<pre><code>
- 루트 빌드 설정</code></pre><p>plugins {
    id &#39;org.springframework.boot&#39; version &#39;3.1.0&#39; apply false
    id &#39;io.spring.dependency-management&#39; version &#39;1.1.0&#39;
    id &#39;java&#39;
}</p>
<p>allprojects {
    group = &#39;com.example&#39;
    version = &#39;1.0.0&#39;</p>
<pre><code>repositories {
    mavenCentral()
}</code></pre><p>}</p>
<p>subprojects {
    apply plugin: &#39;java&#39;
    apply plugin: &#39;io.spring.dependency-management&#39;</p>
<pre><code>java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}</code></pre><p>}</p>
<p>dependencies {
    implementation project(&#39;:core&#39;)
    implementation project(&#39;:infrastructure&#39;)
}</p>
<p>bootJar {
    enabled = false
}</p>
<p>jar {
    enabled = false
}</p>
<pre><code>
- `settings.gradle`</code></pre><p>rootProject.name = &#39;multi-module-project&#39;
include(&#39;api&#39;, &#39;core&#39;, &#39;infrastructure&#39;)</p>
<pre><code>
- api 모듈 빌드 설정</code></pre><p>plugins {
    id &#39;org.springframework.boot&#39; version &#39;3.1.0&#39;
    id &#39;java&#39;
}</p>
<p>dependencies {
    implementation project(&#39;:core&#39;)
    implementation project(&#39;:infrastructure&#39;)</p>
<pre><code>implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
implementation &#39;org.springframework.boot:spring-boot-starter-actuator&#39;</code></pre><p>}</p>
<p>bootJar {
    enabled = true
}</p>
<p>jar {
    enabled = false
}</p>
<pre><code>
- core 모듈 빌드 설정</code></pre><p>plugins {
    id &#39;java&#39;
}</p>
<p>dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter&#39;
}</p>
<p>bootJar {
    enabled = false
}</p>
<p>jar {
    enabled = true
}</p>
<pre><code>
- infra 모듈 빌드 설정</code></pre><p>plugins {
    id &#39;java&#39;
}</p>
<p>dependencies {
    implementation project(&#39;:core&#39;)
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-amqp&#39;
    implementation &#39;org.postgresql:postgresql&#39;
}</p>
<p>bootJar {
    enabled = false
}</p>
<p>jar {
    enabled = true
}</p>
<pre><code>

## 트러블슈팅 1

루트 빌드 설정에 다음과 같이 lombok 의존성을 추가한 상태.
* 루트 빌드에 `subprojects`는 하위 프로젝트(`api`, `core`, `infrastructure`)의 공통 설정</code></pre><p>subprojects {
    dependencies {
        compileOnly &#39;org.projectlombok:lombok&#39;
        annotationProcessor &#39;org.projectlombok:lombok&#39;
        ..
    }
}</p>
<pre><code>`build.gradle(:core)` 에 다음 의존성을 추가한 상태</code></pre><p>dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-redis&#39;
}</p>
<p>```</p>
<p>여기서 빌드를 하면 다음과 같은 로그를 볼 수 있다.
<img src="https://velog.velcdn.com/images/a-white-bit/post/efbec6bc-6e9b-4228-889a-c1112edf3508/image.png" alt=""></p>
<blockquote>
<p>Q. 왜 <code>core</code> 에서는 lombok과 redis 의존성을 찾을 수 없나?</p>
<p>A. 버전을 명시해주면 더 이상 해당 오류는 등장하지 않는다.
    예) <code>compileOnly &#39;org.projectlombok:lombok&#39;</code> -&gt; <code>compileOnly &#39;org.projectlombok:lombok:1.18.30&#39;</code></p>
</blockquote>
<blockquote>
<p>Q. 왜 버전을 명시하지 않아도 잘 동작하기도 했었나?</p>
<p>A. 의존성을 자동으로 관리해주는 플러그인 &amp; 의존성이 설정되어있었기 때문이다.</p>
</blockquote>
<blockquote>
<p>Q. 부모 POM이 무엇인가?</p>
<p>A. <strong>Maven</strong> 프로젝트에서 상위 프로젝트의 의존성, 플러그인 등 빌드 설정을 정의하는 POM 파일이다. 부모 POM에서 의존성, 플러그인 등을 정의하면 하위 프로젝트에서 버전을 명시하지 않아도 된다.</p>
</blockquote>
<blockquote>
<p>Q. Maven이 아닌 <strong>Gradle</strong> 설정은 어떻게 하나?</p>
<p>부모 POM 개념은 없지만 <code>io.spring.dependency-management</code> 플러그인 등을 사용하여 자동 버전 관리 필요</p>
</blockquote>
<blockquote>
<p>플러그인 없이 라이브러리만 추가하는 것은 가능하지만, IDE와의 통합 및 빌드 과정에서의 편리함이 떨어질 수 있다.</p>
</blockquote>
<blockquote>
<p>Q. <code>apply plugin:</code> 과, <code>plugins{...}</code>의 차이</p>
<ul>
<li><p><code>apply plugin</code>:
Gradle 스크립트에서 플러그인을 적용하는 전통적인 방법
플러그인을 적용한 후, 해당 플러그인에 대한 추가 설정 필요</p>
</li>
<li><p><code>plugins{...}</code>
Gradle의 새로운 DSL(도메인 특화 언어)
플러그인을 선언적으로 적용하는 방법
현대적이고 안정성이 높은 방법
플러그인의 버전을 명시적으로 지정 가능
플러그인 적용 순서와 관련된 문제를 줄일 수 있음
Gradle의 초기화 단계에서 플러그인을 적용하므로, 더 안전하고 예측 가능한 방식으로 플러그인을 사용가능</p>
</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[소프트웨어 테스트 요약]]></title>
            <link>https://velog.io/@a-white-bit/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9A%94%EC%95%BD</link>
            <guid>https://velog.io/@a-white-bit/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9A%94%EC%95%BD</guid>
            <pubDate>Wed, 11 Dec 2024 08:39:04 GMT</pubDate>
            <description><![CDATA[<h1 id="테스팅의-종류">테스팅의 종류</h1>
<hr>
<p>소프트웨어 공학 이론에서 많은 기준의 테스트를 제시하고 있다. 분류 방법에 따라 나뉘는 기준도 달라질 수 있다.</p>
<p>종류는 크게 <strong>테스트 수준</strong>, <strong>테스트 유형</strong>, <strong>테스트 접근 방식</strong>에 따른 분류로 나뉠 수 있다.</p>
<br>

<h2 id="테스트-수준-분류">테스트 수준 분류</h2>
<p>소프트웨어 개발 과정 각 단계에 따라 실행되는 테스트 종류</p>
<ul>
<li>단위 테스트</li>
<li>통합 테스트</li>
<li>시스템 테스트</li>
<li>인수 테스트</li>
</ul>
<p><strong>단위 테스트</strong>와 <strong>통합 테스트</strong> 이 두 가지를 어떻게 다뤄야 할지 집중적으로 공부할 예정</p>
<br>

<h3 id="단위-테스트">단위 테스트</h3>
<p>클래스나 함수 단위의 프로그램을 원하는 대로 동작하는지 확인
시스템의 가장 작은 코드 단위를 테스트하는 목적</p>
<ul>
<li>대상: 함수, 메서드, 클래스</li>
<li>수행 시점: 개발 초기 단계 혹은 새로운 클래스나 메서드를 작성할 때</li>
<li>수행자: 개발자</li>
<li>자동화 도구: JUnit (Java 기준)</li>
</ul>
<h3 id="통합-테스트">통합 테스트</h3>
<p>단위 테스트에서 검증한 프로그램을 합쳐서 실행
모듈 간의 상호작용 및 데이터 흐름 확인</p>
<ul>
<li>대상: 여러 모듈, API 연동, 서비스 로직</li>
<li>수행 시점: 개발 중간 및 이후 단계, 모듈 간 의미 있는 상호 동작이 될 때</li>
<li>수행자: 개발자, 테스트 팀</li>
<li>자동화 도구: JUnit, Postman 등</li>
</ul>
<h3 id="시스템-테스트">시스템 테스트</h3>
<p>전체 시스템이 요구사항을 충족하는지 확인
사용자 환경과 유사한 조건에서 테스트</p>
<ul>
<li>도구: Selenium 웹 테스트, JMeter 성능 테스트 등</li>
</ul>
<h3 id="인수-테스트">인수 테스트</h3>
<p>제품 릴리스 전 요구사항이 충족되었는지 최종 사용자가 확인
시나리오 기반 테스트</p>
<br>

<h2 id="테스트-유형-분류">테스트 유형 분류</h2>
<p>테스트의 목적에 따른 분류</p>
<ul>
<li>기능 테스트</li>
<li>비기능 테스트<ul>
<li>성능 테스트</li>
<li>보안 테스트</li>
<li>사용성 테스트</li>
</ul>
</li>
<li>회귀 테스트</li>
<li>탐색적 테스트</li>
</ul>
<br>

<h3 id="기능-테스트">기능 테스트</h3>
<p>기능 요구사항에 맞게 동작하는지 확인하는 테스트
기능별 테스트 케이스를 실행</p>
<h3 id="성능-테스트">성능 테스트</h3>
<p>시스템의 반응 속도, 안정성을 확인하는 테스트
부하 테스트 실행</p>
<ul>
<li>도구: JMeter, Gatling 등</li>
</ul>
<h3 id="보안-테스트">보안 테스트</h3>
<p>시스템의 취약점을 식별하는 테스트</p>
<ul>
<li>도구: SonarQube, Burp Suite, vega, SQLMap 등</li>
</ul>
<h3 id="사용성-테스트">사용성 테스트</h3>
<p>UI/UX 사용 편의성 평가</p>
<h3 id="회귀-테스트">회귀 테스트</h3>
<p>코드를 변경한 후에 테스트 케이스를 재사용하여 기존 기능이 정상적으로 동작하는지 확인</p>
<h3 id="탐색적-테스트">탐색적 테스트</h3>
<p>테스트 설계 없이 자유롭게 시스템을 탐색하며 결함 발견</p>
<br>

<h2 id="테스트-접근-방식-분류">테스트 접근 방식 분류</h2>
<p>테스트를 설계하고 수행하는 방식에 따라 분류</p>
<ul>
<li>화이트박스 테스트</li>
<li>블랙박스 테스트</li>
<li>그레이박스 테스트</li>
</ul>
<br>

<h3 id="화이트박스-테스트">화이트박스 테스트</h3>
<p>코드 내부 구조와 로직을 기반으로 테스트</p>
<ul>
<li>단위 테스트, 경로 테스트, 조건 테스트가 해당</li>
<li>예시: <code>if</code> 문이나 <code>for</code> 루프의 모든 분기 조건을 검증</li>
</ul>
<h3 id="블랙박스-테스트">블랙박스 테스트</h3>
<p>내부 구현을 모른 채 외부 입력과 출력만 테스트</p>
<ul>
<li>기능 테스트, 비기능 테스트가 해당</li>
<li>예시: 특정 입력값에 대해 예상 출력값 확인</li>
</ul>
<h3 id="그레이박스-테스트">그레이박스 테스트</h3>
<p>내부 구조를 부분적으로 이해한 상태에서 테스트</p>
<ul>
<li>통합 테스트, 보안 테스트가 해당</li>
</ul>
<br>


<h1 id="테스팅-방법론">테스팅 방법론</h1>
<hr>
<h2 id="tdd란">TDD란?</h2>
<p>TDD(Test Driven Development), 테스트 주도 개발</p>
<ul>
<li><strong>테스트 코드를 먼저 작성</strong>하고 코드를 구현하는 것을 반복적으로 개선하는 개발 방식</li>
<li>짧은 개발 사이클에 사용됨(애자일 등)</li>
<li>코드 품질 향상, 변경에 강한 구조 설계 목표(유지보수, 확장, 리팩토링 용이)</li>
<li>테스트 코드가 기능 명세를 대체</li>
</ul>
<h3 id="tdd-프로세스">TDD 프로세스</h3>
<p>주로 다음 Red-Green-Refactor 주기를 반복</p>
<ol>
<li>Red(실패)<ul>
<li>구현되지 않은 기능의 테스트 코드 작성(구현되지 않았기 때문에 실패 테스트)</li>
</ul>
</li>
<li>Green(성공)<ul>
<li>빠른 테스트 통과를 위한 최소한의 코드를 작성</li>
</ul>
</li>
<li>Refactor(코드 개선)<ul>
<li>이후 코드 품질을 높이기 위한 리팩토링</li>
</ul>
</li>
</ol>
<h3 id="tdd-단점">TDD 단점</h3>
<ul>
<li>시간 소모: 초기 개발 속도에 영향</li>
<li>복잡성 증가: 테스트 코드 작성의 어려움</li>
<li>UI 중심 애플리케이션은 적합하지 않음</li>
</ul>
<br>

<h2 id="bdd란">BDD란?</h2>
<p>BDD(Behavior Driven Development), 행위 주도 개발</p>
<p>TDD(Test Driven Development) + 테스트 케이스 자체가 요구사항이 되도록 하는 개발 방법
(요구사항 = 테스트 케이스)</p>
<ul>
<li><strong>시나리오(사용자 행동)</strong>를 기반으로 테스트 케이스를 작성</li>
<li>비개발자(기획자, QA, 고객)가 이해할 수 있는 언어(자연어 기반)로 작성</li>
<li>사용자 입장에서 사용하는 방식을 중심으로 작성</li>
<li>개발자 &lt;-&gt; 비개발자 소통 중심, 소통 강화</li>
<li>도구: Cucumber, JBehave</li>
</ul>
<h3 id="bdd-작성-패턴">BDD 작성 패턴</h3>
<p><strong>Given-When-Then</strong> 구조로 테스트 정의</p>
<ul>
<li>Given(상황): 시나리오에 필요한 값 (예: 로그인 화면에 접속한 상태에서)</li>
<li>When(행동): 시나리오 진행 시 필요한 조건 (예: 올바른 이메일, 비밀번호를 입력하고 로그인 버튼을 누르면)</li>
<li>Then(결과): 시나리오 완료 시 보장되는 결과 (예: 대시보드 화면이 표시됨)</li>
</ul>
<p>그 외</p>
<ul>
<li>Feature: 테스트에 대상의 기능/책임</li>
<li>Scenario: 테스트 목적 상황을 설명</li>
</ul>
<h3 id="bdd-단점">BDD 단점</h3>
<ul>
<li>초기 도입 비용: BDD 도구 학습, 설정</li>
<li>테스트 관리의 어려움: 프로젝트가 커질 수록 많은 시나리오에 따라 중복 테스트가 발생할 수도 있고, 요구사항 변경 시 시나리오를 전부 수정해야함</li>
<li>추상화의 한계: 복잡한 비즈니스 로직은 매우 긴 시나리오로 이어질 수 있음, 기술적인 로직(데이터베이스 트랜잭션 등)을 자연어로 설명하는 데 제한적</li>
<li>상대적으로 느린 테스트 속도: 사용자 중심으로 UI 연관 테스트가 많아 테스트 속도가 느릴 수 있음</li>
<li>비개발자와 협업 한계: 기술 지식 격차로 인한 의사소통 오류. 시나리오가 추상적이라 비개발자가 테스트 요구사항을 반영한다고 믿을 수 있지만, 실제 구현은 다를 수 있음</li>
<li>테스트 복잡성 증가: 간단한 기능 테스트도 시나리오로 작성하면 장황해질 수 있음</li>
<li>예외 처리 누락 가능성 존재: 예로,  &#39;로그인 성공&#39;은 처리했지만 &#39;로그인 실패&#39; 상황은 없음</li>
</ul>
<p>등이 존재</p>
<p>위 단점들을 커버하기 위해 다음의 노력이 필요</p>
<ul>
<li>팀 전체의 BDD 도구와 프로세스에 대한 이해</li>
<li>테스트 시나리오의 재사용성과 간결성을 유지</li>
<li>기술적 문제와 사용자 요구사항 간의 균형을 맞추는 작업</li>
</ul>
<br>

<h1 id="테스트-커버리지">테스트 커버리지</h1>
<hr>
<ul>
<li>코드 커버리지: 작성한 테스트 코드가 실제 소스 코드의 몇%를 실행했는지 측정</li>
<li>기능 커버리지: 구현된 기능이 테스트에서 얼마나 이루어졌는지</li>
<li>경로 커버리지: 코드의 모든 실행 경로가 테스트되었는지</li>
</ul>
<br>

<h1 id="테스트-프로세스">테스트 프로세스</h1>
<hr>
<ol>
<li>테스트 계획 작성: 테스트의 목표, 범위, 방법 등 문서화</li>
<li><strong>테스트 케이스</strong> 설계: 다양한 입력값, 시나리오 고려</li>
<li>테스트 실행: <strong>자동화 도구</strong>를 사용하거나 수동으로 테스트를 진행</li>
<li>결과 분석 및 버그 리포트</li>
</ol>
<blockquote>
<p>개발자가 흔히 말하는 &quot;<em>테스트 코드를 써야한다</em>&quot;
테스트 코드는 애플리케이션 코드를 검증하기 위해 별도로 작성하는 코드를 말한다.
이 코드는 주어진 <strong>테스트 케이스</strong>에 대해 <strong>자동화 도구</strong>를 사용하여 의도한 대로 동작하는지 확인하는 데 필요하다. </p>
</blockquote>
<br>

<h1 id="테스트-코드는-왜-필요할까">테스트 코드는 왜 필요할까?</h1>
<hr>
<p>목적은 다음과 같다.</p>
<ol>
<li>검증: 프로그램이 올바르게 동작하는지 확인하기 위해</li>
<li>보장: 새로운 기능 추가, 수정에도 기존 코드가 문제없이 동작하는지 보장하기 위해</li>
<li>자동화: 반복적인 수동 테스트를 줄여 빠르고 일관된 검증 제공 가능</li>
</ol>
<p>취할 수 있는 이점은 다음과 같다.
(테스트 장점 + 테스팅 자동화 장점 + 코드 문서화 장점)</p>
<ul>
<li>버그 조기 발견: 코드가 의도대로 동작하는지 확인하여 버그를 미리 발견</li>
<li>품질 보장: 기능의 정확성과 일관성을 보장</li>
<li>유지보수와 확장, 리팩터링 용이: 변경의 위험이 줄어 안정적인 개발 가능(테스트 코드를 통해 코드 검증과 보장이 되어 기존보다 빠르게 문제를 확인하고 대처할 수 있음)</li>
<li>회귀 방지: 새로운 기능을 추가할 때 기존의 기능이 영향받지 않도록 검증</li>
<li>개발 속도 향상: 자동화되어 빠른 결과 확인이 가능</li>
<li>문서화 역할: 코드의 예상 동작을 보여줌</li>
</ul>
<br>

<h1 id="테스트-코드-작성">테스트 코드 작성</h1>
<hr>
<p>TDD, BDD 방법론을 개발에 적용하기 위해 테스트코드를 작성해야 한다.
Java를 기준으로 테스트코드를 작성할 때 내가 알고 넘어가야 하는 것들을 정리하였다.</p>
<h2 id="작성-기본-구조">작성 기본 구조</h2>
<p>테스트코드는 일반적으로 AAA(Arrange, Act, Assert) 패턴으로 작성</p>
<ol>
<li><strong>Arrange</strong>: 테스트에 필요한 초기 설정</li>
<li><strong>Act</strong>: 테스트할 코드 실행</li>
<li><strong>Assert</strong>: 결과가 예상과 같은지 검증</li>
</ol>
<h2 id="자바에서-자주-사용되는-자동화-도구">자바에서 자주 사용되는 자동화 도구</h2>
<ul>
<li>JUnit5(JUnit Jupiter): 자바 테스트 프레임워크. 단위, 통합 테스트 애너테이션 지원</li>
<li>AssertJ: 체이닝 어설션 제공</li>
<li>Hamcrest: 어설션 매처(assertThat 메서드 등) 제공</li>
<li>MockMvc: 스프링 MVC의 컨트롤러 테스트 도구. HTTP 요청/응답 시뮬레이션 제공</li>
<li>Mockito: 모킹 제공</li>
<li>Spring Boot Test: 스프링 부트 전용 테스트 도구 모음(위 라이브러리 포함 JSONassert, Spring Security Test, H2 Database 등 많은 라이브러리 모음집), 애너테이션 지원</li>
</ul>
<h2 id="mock이란">Mock이란?</h2>
<p>Mock은 테스트 대상 코드에서 외부 의존성을 대체하기 위해 필요한 <strong>가짜 객체</strong>다.
실제 객체를 호출하지 않고 예상 동작을 시뮬레이션할 수 있다.</p>
<ul>
<li>외부 의존성을 제거 (예시: DB, 외부 API 호출 mocking)</li>
<li>단위 테스트에 자주 사용됨</li>
<li>단위 테스트 외에도 일부 의존성을 제거하거나 원하는 환경을 인위적으로 만들 때 사용할 수 있음</li>
<li>테스트 속도와 안정성이 향상됨 (외부로 인한 버그 가능성 감소)</li>
</ul>
<h2 id="mockito">Mockito</h2>
<p>Java에서 Mockito 라이브러리로 Mock을 쉽게 구현가능하다.
Mock 의존성 주입을 위한 주요 애노테이션</p>
<ul>
<li><code>@Mock</code> Mock 객체 생성</li>
<li><code>@InjectMock</code> 주입이 필요한 곳에 Mock 객체를 주입할 때 사용</li>
</ul>
<h2 id="stub이란">Stub이란?</h2>
<ul>
<li>상태(값) 검증에 중점을 둔 테스트</li>
<li>어떤 시나리오에서 반드시 나와야 하는 결과 값을 설정</li>
<li>Mockito의 <code>when()</code> 메서드 사용하여 stubbing</li>
<li>고정된 값을 반환하는 테스트이므로 이에 맞지 않는 동작 시뮬레이션은 어려움</li>
</ul>
<br>

<h1 id="버그-리포트-작성">버그 리포트 작성</h1>
<ul>
<li>버그 발생 환경 (OS, 브라우저, 버전 등)</li>
<li>버그 재현 단계 (step-by-step)</li>
<li>기대 결과, 실제 결과</li>
<li>관련 로그 및 스크린샷</li>
</ul>
<br>

<h3 id="참고하면-좋은-도서">참고하면 좋은 도서</h3>
<p>테스트 코드 작성과 좋은 실천법 기술
<em>&quot;Clean Code&quot;</em> - Robert C. Martin</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GitHub Actions로 Slack에 파일 공유 스크립트]]></title>
            <link>https://velog.io/@a-white-bit/GitHub-Actions%EB%A1%9C-Slack%EC%97%90-%ED%8C%8C%EC%9D%BC-%EA%B3%B5%EC%9C%A0-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@a-white-bit/GitHub-Actions%EB%A1%9C-Slack%EC%97%90-%ED%8C%8C%EC%9D%BC-%EA%B3%B5%EC%9C%A0-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Mon, 11 Nov 2024 09:21:32 GMT</pubDate>
            <description><![CDATA[<p>GitHub Actions로 Java Checkstyle 검사 파일 Slack 공유 방법을 찾다가
작성한 워크플로우 스크립트이다.</p>
<p>Slack upload file 기능을 사용해서 공유하는 방법인데,</p>
<p>현재는 GitHub Artifacts로 파일 공유하는 방식이 낫다고 판단해서 사실 아래 스크립트 안 쓴다. 다른 파일 공유할 때 다시 찾게 될 것 같아서 기록한다.</p>
<h4 id="사용-api">사용 API</h4>
<p>files.getUploadURLExternal
files.completeUploadExternal</p>
<blockquote>
<p>Slack API 문서 링크
<a href="https://api.slack.com/methods/files.getUploadURLExternal">https://api.slack.com/methods/files.getUploadURLExternal</a>
<a href="https://api.slack.com/methods/files.completeUploadExternal">https://api.slack.com/methods/files.completeUploadExternal</a></p>
</blockquote>
<p>슬랙에 알림까지는 잘 가고, 링크 퍼미션만 조절하면 될 듯하다. (링크 클릭 시 접근이 불가능 했었다.)</p>
<pre><code class="language-yml">name: Java Lint Check

on:
  pull_request:
    types: [opened, synchronize, reopened] # 처음 생성시점(opened), 커밋 추가/변경 시점(synchronize), 닫힌 PR 재오픈 시점(reopened)
    branches:
      - main
      - dev

jobs:
  lint:
    runs-on: ubuntu-20.04

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: &#39;21&#39;
          distribution: &#39;corretto&#39;

      - name: Install dependencies
        run: ./gradlew build -x test

      - name: Run Checkstyle
        id: checkstyle
        continue-on-error: true
        run: ./gradlew checkstyleMain

      - name: Send Slack notification on Checkstyle Failure
        if: failure()
        env:
          SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
          SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
        run: |
          curl -X POST -H &#39;Content-type: application/json&#39; \
            -H &quot;Authorization: Bearer $SLACK_API_TOKEN&quot; \
            --data &#39;{
              &quot;channel&quot;: &quot;&#39;&quot;$SLACK_CHANNEL_ID&quot;&#39;&quot;,
              &quot;text&quot;: &quot;❗ Checkstyle 실행에 실패했습니다. 워크플로우 설정 또는 환경 구성을 확인하세요.&quot;,
              &quot;attachments&quot;: [
                {
                  &quot;color&quot;: &quot;#ff0000&quot;,
                  &quot;fields&quot;: [
                    { &quot;title&quot;: &quot;Repository&quot;, &quot;value&quot;: &quot;${{ github.repository }}&quot;, &quot;short&quot;: true },
                    { &quot;title&quot;: &quot;Branch&quot;, &quot;value&quot;: &quot;${{ github.ref }}&quot;, &quot;short&quot;: true },
                    { &quot;title&quot;: &quot;Commit Message&quot;, &quot;value&quot;: &quot;${{ github.event.head_commit.message }}&quot;, &quot;short&quot;: false },
                    { &quot;title&quot;: &quot;Actions URL&quot;, &quot;value&quot;: &quot;${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}&quot;, &quot;short&quot;: false }
                  ]
                }
              ]
            }&#39; https://slack.com/api/chat.postMessage

      - name: Get Upload URL from Slack
        if: success()
        id: get_upload_url
        env:
          SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
        run: |
          FILE_SIZE=$(stat -c%s &quot;build/reports/checkstyle/goodbite-checkstyle-result.xml&quot;)
          response=$(curl -s -X POST \
            -H &quot;Authorization: Bearer $SLACK_API_TOKEN&quot; \
            -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
            --data-urlencode &quot;filename=goodbite-checkstyle-result.xml&quot; \
            --data-urlencode &quot;length=$FILE_SIZE&quot; \
            https://slack.com/api/files.getUploadURLExternal)

          echo &quot;Slack API response: $response&quot;
          upload_url=$(echo $response | jq -r &#39;.upload_url&#39;)
          file_id=$(echo $response | jq -r &#39;.file_id&#39;)

          # GitHub Actions 환경 파일을 사용하여 환경 변수 설정
          echo &quot;UPLOAD_URL=$upload_url&quot; &gt;&gt; $GITHUB_ENV
          echo &quot;FILE_ID=$file_id&quot; &gt;&gt; $GITHUB_ENV

      - name: Upload Checkstyle result to Slack URL
        if: success()
        env:
          SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
          UPLOAD_URL: ${{ env.UPLOAD_URL }}
        run: |
          curl -X POST &quot;$UPLOAD_URL&quot; \
          -F file=@build/reports/checkstyle/goodbite-checkstyle-result.xml \
          -H &quot;Authorization: Bearer $SLACK_API_TOKEN&quot;

      - name: Complete Upload in Slack
        if: success()
        env:
          SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
          FILE_ID: ${{ env.FILE_ID }}
        run: |
          curl -s -X POST \
          -H &quot;Authorization: Bearer $SLACK_API_TOKEN&quot; \
          -H &quot;Content-Type: application/x-www-form-urlencoded&quot; \
          --data-urlencode &quot;files=[{\&quot;id\&quot;:\&quot;$FILE_ID\&quot;, \&quot;title\&quot;:\&quot;goodbite-checkstyle-result.xml\&quot;}]&quot; \
          https://slack.com/api/files.completeUploadExternal

      - name: Send Slack notification with success message
        if: success()
        env:
          SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }}
          SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID }}
          FILE_ID: ${{ env.FILE_ID }}
        run: |
          curl -X POST -H &#39;Content-type: application/json&#39; \
            -H &quot;Authorization: Bearer $SLACK_API_TOKEN&quot; \
            --data &#39;{
              &quot;channel&quot;: &quot;&#39;&quot;$SLACK_CHANNEL_ID&quot;&#39;&quot;,
              &quot;text&quot;: &quot;✅ Checkstyle 실행에 성공하였습니다. 결과 파일을 확인하세요.&quot;,
              &quot;attachments&quot;: [
                {
                  &quot;color&quot;: &quot;#36a64f&quot;,
                  &quot;fields&quot;: [
                    { &quot;title&quot;: &quot;Repository&quot;, &quot;value&quot;: &quot;${{ github.repository }}&quot;, &quot;short&quot;: true },
                    { &quot;title&quot;: &quot;Branch&quot;, &quot;value&quot;: &quot;${{ github.ref }}&quot;, &quot;short&quot;: true },
                    { &quot;title&quot;: &quot;Commit Message&quot;, &quot;value&quot;: &quot;${{ github.event.head_commit.message }}&quot;, &quot;short&quot;: false },
                    { &quot;title&quot;: &quot;Actions URL&quot;, &quot;value&quot;: &quot;${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}&quot;, &quot;short&quot;: false }
                  ]
                }
              ],
              &quot;blocks&quot;: [
                {
                  &quot;type&quot;: &quot;section&quot;,
                  &quot;text&quot;: {
                    &quot;type&quot;: &quot;mrkdwn&quot;,
                    &quot;text&quot;: &quot;업로드된 파일을 보려면 &lt;https://slack.com/files/${SLACK_CHANNEL_ID}/files/${FILE_ID}|여기&gt;를 클릭하세요.&quot;
                  }
                }
              ]
            }&#39; https://slack.com/api/chat.postMessage</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSD 마이그레이션 후 windows 부팅문제(0xc000000e, 0xc000000f)]]></title>
            <link>https://velog.io/@a-white-bit/SSD-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%ED%9B%84-windows-%EB%B6%80%ED%8C%85%EB%AC%B8%EC%A0%9C0xc000000e-0xc000000f</link>
            <guid>https://velog.io/@a-white-bit/SSD-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%ED%9B%84-windows-%EB%B6%80%ED%8C%85%EB%AC%B8%EC%A0%9C0xc000000e-0xc000000f</guid>
            <pubDate>Thu, 07 Nov 2024 13:32:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a-white-bit/post/2edaf2f7-9cd9-4262-8567-5f2506d6be21/image.png" alt=""></p>
<p>주변에 이런 SSD 마이그레이션을 진행한다면, 아니면 미래의 내가 같은 짓을 반복한다면...
이 글을 보고 도움을 받고자 남김.</p>
<hr>
<h3 id="발단-시나리오">발단 시나리오</h3>
<p>내 환경: Windows 10, UEFI 시스템</p>
<ol>
<li><p>C드라이브의 용량 부족
현재 윈도우 파일 설치 패러다임상 C드라이브는 정말 빠르게 찬다.
OS 파일이 전부 C드라이브 고정인 것 때문에
제<del>~</del>발 타 프로그램들은 C드라이브를 피하고 싶지만 쉽지 않음. 관련 파일들이 나도 모르는 곳에 깔리기 때문에........</p>
</li>
<li><p>용량이 널널한 다른 SSD로 마이그레이션
(나는 Acronis True Image 툴을 사용)</p>
</li>
<li><p>마이그레이션 완료 후 재부팅 시 바이오스 진입해서 새 SSD로 부팅</p>
</li>
<li><p>이전 SSD 내용을 포맷
오른클릭 해서 간단 포맷시킴
<img src="https://velog.velcdn.com/images/a-white-bit/post/2edea9e3-6b3b-460d-bf7c-c418b359e1de/image.png" alt=""></p>
</li>
</ol>
<p>위 내용이 다이고, 3번에서 부팅이 잘 되길래 안심하고 4번을 진행했음
근데 이후 재부팅 시 썸네일처럼 부팅에 필요한 파일이 없다고 나옴</p>
<p>??</p>
<hr>
<h3 id="왜">왜?</h3>
<p>구글링을 하니 마이그레이션 후 나같은 분들 꽤 있던데
당연히 많은 도움이 되었음..</p>
<p>근데 제일 중요한건 <strong>왜??</strong>임</p>
<p>이건 부딪혀보면서 세워지는 가설로 내린 결론인데
나처럼 마이그레이션하고 포맷한다고 해도
컴퓨터가 부팅할 때 처음 OS 로드하는 파티션은 바뀌지 않고 그대로 같은 위치에서 찾기 때문임</p>
<p>그니까 예전 SSD에서 저 winload.efi 파일을 실행하려는 것 같음
근데 내가 포맷으로 파일을 날려버렸으니까.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/e8f328de-b56c-4249-bfa2-686dca7eb751/image.png" alt=""></p>
<p>혹시 위 화면을 본 적이 있나?
윈도우 디스크 관리 창이다.</p>
<p>위 사진을 기준으로 설명하면</p>
<p><strong>디스크 1</strong> = 이전 SSD
<strong>디스크 2</strong> = 새 SSD</p>
<p>Windows OS가 설치되어있는 디스크는 대게 3분할 되어있음. (아무것도 건든 기억이 없다면? 그럴 것이다)
이게 되게 중요한데,
① EFI 시스템 파티션 (100MB)
② 주 파티션 (우리가 항상 보는 C드라이브)
③ 복구 파티션 (502MB)</p>
<p>크기나 이름이 좀 다를 수 있을 것 같은데
❗ <strong>OS가 설치된 드라이브는 시스템 파티션, 주 파티션, 복구(복원) 파티션</strong> 으로 나뉜다는 것이 포인트</p>
<p>포맷을 해도 이 구성은 그대로 남아있을 것이다</p>
<p>아직 할 일이 남았다는 것</p>
<hr>
<h3 id="해결방법">해결방법</h3>
<ul>
<li>Widnows 설치 미디어(USB 등)를 컴퓨터에 연결</li>
<li>예전 SSD 디스크 초기화</li>
<li><strong>부트 로더를 디스크 2로 설정해야 함</strong></li>
</ul>
<ol>
<li>다른 컴퓨터에서 USB에 윈도우 설치 파일 받아오기
<a href="https://www.microsoft.com/en-us/software-download/windows10">https://www.microsoft.com/en-us/software-download/windows10</a></li>
<li>바이오스에서 USB로 부팅 선택해서 복구 화면 진입</li>
<li>명령 프롬프트
가장 우선적으로 시도해 볼 것은 자동 복구 시동 따위지만 그게 됐으면 이 글 안썼다</li>
<li>예전 SSD 디스크 초기화
UEFI 시스템에서는 <strong>EFI 시스템 파티션(ESP)</strong> 에 부팅 정보가 저장된다고 한다.
이 파티션이 예전 SSD에 남아 있으면 UEFI BIOS가 이를 계속 인식할 수 있기 때문에 디스크를 초기화해주자.<pre><code class="language-bash">diskpart
&gt; list disk
&gt; select disk X  # 예전 SSD의 번호
&gt; clean
&gt; exit</code></pre>
</li>
<li><strong>부트 로더를 새 SSD 디스크로 설정</strong></li>
</ol>
<p>다음 명령들을 순서대로 진행</p>
<pre><code class="language-bash">diskpart
&gt; list vol  # EFI 시스템 파티션을 찾습니다 (보통 FAT32 형식)
&gt; select vol X  # EFI 파티션의 볼륨 번호
&gt; assign letter=Z  # 임시로 드라이브 문자 Z를 할당
&gt; exit</code></pre>
<pre><code class="language-bash">bcdboot C:\Windows /s Z: /f ALL # EFI 시스템 파티션에 부트 파일 복사</code></pre>
<p>이 때 주의할 점은 Windows가 깔린 드라이브를 목록에서 찾아서 입력해야한다. 항상 C가 아님.
크기와 파일 시스템 유형(NTFS, FAT32)를 보고 유추가능</p>
<ul>
<li>NTFS이고 크기가 GB 단위 -&gt; 주 파티션</li>
<li>FAT32이고 100MB -&gt; EFI 시스템 파티션</li>
</ul>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/355cba19-df32-4d01-af9e-c534d2c420c2/image.png" alt=""></p>
<p>나의 경우 볼륨 4(E:)가 Windows 깔린 새 SSD 이고,
볼륨 5번이 새 SSD의 EFI 시스템 파티션이다.
예시) <code>bcdboot E:\Windows /s Z: /f ALL</code></p>
<pre><code class="language-bash">diskpart
&gt; select vol X  # EFI 파티션의 볼륨 번호
&gt; remove letter=Z  # Z 드라이브 할당 취소
&gt; exit</code></pre>
<ol start="6">
<li>커맨드창 닫고 재부팅</li>
</ol>
<hr>
<h3 id="마무리">마무리</h3>
<p>나는 잘 해결되었고
예전 디스크도 잘 초기화되었다.</p>
<p>초기화한 디스크는 사용할 수 있도록 해주자.</p>
<ol>
<li>다시 디스크 관리창을 연다.</li>
<li>할당되지 않은 디스크를 확인한다</li>
<li>마우스 오른쪽 버튼 클릭 &gt; 새 단순 볼륨</li>
<li>파일 시스템 설정 &gt; NTFS, 빠른 포맷</li>
</ol>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b6b95dd2-f414-4f53-8305-c5702b1343f5/image.png" alt=""></p>
<p>끝</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS IAM 활용]]></title>
            <link>https://velog.io/@a-white-bit/AWS-IAM</link>
            <guid>https://velog.io/@a-white-bit/AWS-IAM</guid>
            <pubDate>Tue, 05 Nov 2024 15:59:20 GMT</pubDate>
            <description><![CDATA[<h1 id="iam-활용하기">IAM 활용하기</h1>
<h3 id="iam과-iam-identity-center-차이점">IAM과 IAM Identity Center 차이점</h3>
<hr>
<h3 id="역할-수임하는-여러-방법들">역할 수임하는 여러 방법들</h3>
<ul>
<li><p>CLI 명령어 <code>$ aws sts assume-role --role-arn &quot;RoleArn&quot; --role-session-name &quot;YourSessionName&quot;</code></p>
</li>
<li><p>AWS 브라우저 콘솔 웹 페이지 -&gt; 로그인 후 계정 이름 클릭 -&gt; 역할 전환</p>
</li>
<li><p>IAM Identity Center - 역할 자동 수임 설정</p>
</li>
<li><p>여러 언어에 제공되는 AWS SDK 라이브러리로 코드에서 접근 가능</p>
</li>
</ul>
<hr>
<h3 id="다른-사람-계정의-리소스-접근-역할을-부여받는-방법">다른 사람 계정의 리소스 접근 역할을 부여받는 방법</h3>
<ol>
<li><p>다른 계정의 주인이 IAM 역할을 생성</p>
</li>
<li><p>1.의 IAM 역할 ARN을 받는다.</p>
</li>
</ol>
<blockquote>
<p><strong>ARN?</strong>
Amazon Resource Name, AWS 리소스를 고유 식별하는 이름
arn:[partition]:[service]:[region]:[account-id]:[resource] 형태</p>
</blockquote>
<ol start="3">
<li>AWS CLI 에 sts assume-role 명령어 입력
역할 수임하는 여러 방법들에 따라 다를 수 있다. 공통적으로 모두 ARN이 필요하다.</li>
</ol>
<pre><code class="language-bash">$ aws sts assume-role --role-arn &quot;arn:aws:iam::H_ACCOUNT_ID:role/H_ROLE_NAME&quot;
--role-session-name &quot;YourSessionName&quot; [--duration-seconds 3600]</code></pre>
<p>임시이므로 기본값으로 3600초, 즉 한 시간동안 유효하다.
<code>--duration-seconds</code> 옵션으로 설정 가능함</p>
<ol start="4">
<li>임시 자격 증명 토큰이 생성됨
예시) 임시로 MyS3AccessSession 역할을 수임<pre><code class="language-json">{
 &quot;Credentials&quot;: {
     &quot;AccessKeyId&quot;: &quot;ASIA....&quot;,
     &quot;SecretAccessKey&quot;: &quot;wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&quot;,
     &quot;SessionToken&quot;: &quot;FQoGZXIvYXdzEF4aDDwxxxxxxxxxxxxx&quot;,
     &quot;Expiration&quot;: &quot;2024-11-05T15:04:30Z&quot;
 },
 &quot;AssumedRoleUser&quot;: {
     &quot;AssumedRoleId&quot;: &quot;AROAXXX:MyS3AccessSession&quot;,
     &quot;Arn&quot;: &quot;arn:aws:sts::H_ACCOUNT_ID:assumed-role/H_ROLE_NAME/MyS3AccessSession&quot;
 }
}</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 환경변수 관리하기]]></title>
            <link>https://velog.io/@a-white-bit/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-white-bit/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Nov 2024 05:53:51 GMT</pubDate>
            <description><![CDATA[<h1 id="목적">목적</h1>
<p>지금까지 개발을 해오면서 환경변수 설정을 하라는대로 해왔다.
처음 맥북을 샀을 때 java나 c++ 개발을 하기 위해 구글링해서 나온 개발 환경 세팅 튜토리얼을 따라하며 시스템 환경변수를 설정하기도 하고,
강의를 들으며 프로젝트 환경변수 설정을 따라해보기도 하였다.</p>
<p>직접 환경변수를 만들어 보기도 하여 감이 잡혀가는 중인데..
한 가지, 팀 협업을 하면서 환경변수 설정이 불편한 부분이 많았다. 각 팀원이 설정한 환경변수를 공유해야하는데 매번 수동으로 고치고 있다.</p>
<p>이 글의 이유는 바로 다음과 같다.</p>
<blockquote>
<p>프로젝트의 여러 팀원과 공통된 환경변수를 편리하게 공유하고 싶다.</p>
</blockquote>
<p>먼저 팀원들과 공유하기 전에 환경변수를 로컬에서 어떻게 구성하는 것이 바람직한지 알아보면 좋겠다고 생각했다.</p>
<h1 id="로컬-머신에서-환경변수를-구성하는-방법">로컬 머신에서 환경변수를 구성하는 방법</h1>
<p>환경변수를 구성하는 방법에는 여러 가지가 있고 그 중 대표적인 방법들을 찾아봤다.</p>
<h3 id="터미널에서-임시로-설정하는-방법">터미널에서 임시로 설정하는 방법</h3>
<p>터미널에서 현재 세션 동안만 유효한 환경변수를 설정하여 터미널을 닫으면 설정 초기화</p>
<blockquote>
<p>예시 (bash)</p>
</blockquote>
<pre><code class="language-bash">$ export API_KEY=&quot;your_api_key_here&quot;</code></pre>
<p><strong>장점</strong></p>
<ul>
<li>간단 설정, 테스트 목적 등 단기 작업에 유용</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>세션마다 다시 설정해야 함</li>
</ul>
<hr>
<h3 id="시스템-전역에-설정하는-방법">시스템 전역에 설정하는 방법</h3>
<p>환경변수를 로컬 시스템 전역에 설정하면, 모든 터미널 세션에서 사용 가능</p>
<blockquote>
<p>예시
<strong>macOS/Linux</strong>
<code>~/.bash_profile</code> 또는 <code>~/.bashrc</code> 또는 <code>~/.zshrc</code> 파일에 환경변수 추가 후, 파일을 소스하거나 터미널을 재시작</p>
</blockquote>
<pre><code class="language-bash">$ export API_KEY=&quot;your_api_key_here&quot;
$ source ~/.bash_profile</code></pre>
<p><strong>Windows</strong>: 시스템 속성 &gt; 고급 &gt; 환경 변수에 추가</p>
<p><strong>장점</strong></p>
<ul>
<li>한 번만 설정하면 영구적으로 사용</li>
<li>프로젝트에 종속적이지 않은 환경변수라면 하나의 환경변수만 지정해도 여러 프로젝트에서 사용가능</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>UNIX 계열 사용자별 환경변수 파일(<code>~/.bashrc</code>, <code>~/.bash_profile</code>, <code>~/.zshrc</code>) 또는 macOS <code>/etc/paths</code> 파일 설정이 필요함. 복잡한 관리</li>
<li>전역 환경 변수 관리의 어려움, 여러 프로젝트에서 사용하면 충돌 위험성 존재</li>
</ul>
<hr>
<h3 id="env-파일을-사용하는-방법">.env 파일을 사용하는 방법</h3>
<p>.env 파일에 환경변수를 정의해두고, 애플리케이션에서 이를 로드하는 방식
여러 언어에서 .env 파일을 쉽게 로드할 수 있는 패키지가 제공됨</p>
<p>Python: python-dotenv
Node.js: dotenv
Java (Spring Boot): Dotenv 라이브러리</p>
<blockquote>
<p>예시 (Java)
  .env</p>
</blockquote>
<pre><code class="language-js">  API_KEY=your_api_key_here
  DB_HOST=localhost</code></pre>
<p>  DotenvConfig.java</p>
<pre><code class="language-java">  // Dotenv 객체를 생성하여 환경 변수 파일(.env)을 로드하고 빈으로 등록합니다.
  @Configuration
  public class DotenvConfig {
      @Bean
      public Dotenv dotenv() {
          return Dotenv.configure().load();
      }
  }</code></pre>
<p>  DataSourceConfig.java</p>
<pre><code class="language-java">  // 환경 변수 파일(.env)에서 MySQL 데이터베이스 설정 정보를 가져와 연결을 구성합니다.
  @Configuration
  @RequiredArgsConstructor
  public class DataSourceConfig {
      private final Dotenv dotenv;
      @Bean
      public DataSource dataSource() {
          HikariDataSource dataSource = new HikariDataSource();
          dataSource.setJdbcUrl(
              &quot;jdbc:mysql://&quot; + dotenv.get(&quot;DB_HOST&quot;) + &quot;:&quot; + dotenv.get(&quot;DB_PORT&quot;) + &quot;/&quot;
                  + dotenv.get(&quot;DB_NAME&quot;));
          dataSource.setUsername(dotenv.get(&quot;DB_USERNAME&quot;));
          dataSource.setPassword(dotenv.get(&quot;DB_PASSWORD&quot;));
          dataSource.setDriverClassName(&quot;com.mysql.cj.jdbc.Driver&quot;);
          return dataSource;
      }
  }</code></pre>
<p><strong>장점</strong></p>
<ul>
<li>애플리케이션별로 설정이 가능해 환경변수 충돌 가능성 낮음</li>
<li>여러 언어와 프레임워크에서 지원</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>환경변수 로드를 위한 추가 패키지 설정이 필요함</li>
</ul>
<hr>
<h3 id="ide-제공-프로젝트-설정-파일-사용">IDE 제공 프로젝트 설정 파일 사용</h3>
<p>주요 IDE는 환경변수를 프로젝트 설정 파일에 지정 가능함</p>
<blockquote>
<p>예시
Visual Studio Code: <strong>.vscode/settings.json</strong>
IntelliJ: <strong>Run/Debug Configurations</strong> 에서 환경변수 설정</p>
</blockquote>
<p><strong>장점</strong></p>
<ul>
<li>편리함, 쉬움</li>
<li>프로젝트별 환경변수 설정 가능</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>해당 개발 환경에 종속됨. VSC에서 개발하다가 IntelliJ로 변경한다면 다시 환경변수 설정이 필요함</li>
</ul>
<h3 id="번외-docker-및-docker-compose-사용-시-환경변수-설정">번외) Docker 및 Docker-Compose 사용 시 환경변수 설정</h3>
<p>Docker 위에서 돌아가는 애플리케이션의 경우 OS와 분리되어 있어
Docker에도 환경변수를 전달해주어야 함</p>
<blockquote>
<p>예시 (docker-compose.yml)</p>
</blockquote>
<pre><code class="language-yaml">services:
  web:
    image: your_image
    environment:
      - API_KEY=${API_KEY}</code></pre>
<p>또는 .env 파일을 Docker에 전달</p>
<hr>
<h1 id="내가-한-방법">내가 한 방법</h1>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/fb76e52f-4fc9-4393-82df-9ac98ae33f19/image.png" alt=""></p>
<p>뭘 모를 때는 IntelliJ 환경변수 설정 창에서 모든 환경변수를 관리했었다. (현재는 지워서 없음)
그리고 팀원들과 공유할 때는 Environment variables를 [Ctrl+C] - [Ctrl+V] 하여 일일이 알려주었다. 어쩌다 새로운 환경변수를 빼먹고 알려주지 않으면 팀원들은 왜 서버가 뜨지 않냐며 물어온다.</p>
<p>처음에는 괜찮아도 환경변수가 몇 십개가 되니 불편해졌다.</p>
<p>그래서</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/56c617ae-6126-45ee-849f-22ad5ddc64d3/image.png" alt=""></p>
<p><code>.env</code> 파일을 사용하기로 하였다!
파일을 공유하기만 하면 되기 때문이다.</p>
<p>그런데 문제가 있다.
환경변수는 보안에 민감한 정보를 포함하는 경우가 많아서!
되도록이면 <code>.env</code> 파일을 <code>.gitignore</code>에 추가하여 버전 관리에서 제외하는 것이 좋다.</p>
<p>그렇다면 <code>.env</code> 파일을 팀원들과 어떻게 공유할 것이냐????????
메일이나 카카오톡 같은 메신저에 첨부해버릴까?
뭐 그것도 방법일 수는 있겠는데.</p>
<p>개발자스럽진 않다… 이것도 변경사항이 발생할 때마다 건네주는 건 번거롭다는 것은 똑같음.</p>
<hr>
<h1 id="팀원과-환경변수를-공유하는-방법">팀원과 환경변수를 공유하는 방법</h1>
<h2 id="github-secrets-사용">GitHub Secrets 사용</h2>
<h2 id="env-파일을-artifact로-공유">.env 파일을 Artifact로 공유</h2>
<h2 id="github-codespaces">GitHub Codespaces</h2>
<p>클라우드 환경의 IDE</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[간단한 AWS CLI 명령어 사용과 AWS의 계정 유형을 알아보자]]></title>
            <link>https://velog.io/@a-white-bit/AWS-%EA%B3%84%EC%A0%95-%EC%9C%A0%ED%98%95-%EA%B7%B8%EB%A6%AC%EA%B3%A0-CLI-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@a-white-bit/AWS-%EA%B3%84%EC%A0%95-%EC%9C%A0%ED%98%95-%EA%B7%B8%EB%A6%AC%EA%B3%A0-CLI-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 05 Nov 2024 03:31:34 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 배포 관련 때문에 AWS 홈페이지에 자주 들어가는데
AWS CLI 서비스가 존재한다는 사실을 뒤늦게 알았다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/11d7c597-2999-4203-a11d-75592e223e47/image.png" alt=""></p>
<p>정확히는 CloudShell의 존재는 알았는데, 브라우저 기반이라서 GUI로 조작하는 것이 편하다고 판단했다.
그런데 AWS가 데스크톱 애플리케이션을 제공하고 있어서 브라우저를 사용하지 않고도 AWS를 사용할 수 있다는 것.</p>
<p>이제 데스크톱 애플리케이션을 설치하러 가보자.
AWS 공식 문서에 간단한 설치 방법이 나와있다.</p>
<blockquote>
<p><a href="https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/getting-started-install.html">AWS CLI v2 설치 공식 페이지</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/8199592a-4e96-40a3-afb1-0272110e3ee1/image.png" alt=""></p>
<p>나는 2.19.1 버전이 잘 설치되었다. 앞으로 인텔리제이 창에서 개발 중에 원하는 아마존 웹 서비스를 사용할 수 있게 되었다! 너무 편할 것 같음.</p>
<p>CLI를 사용하려면 사용할 계정을 설정해줘야 한다. AWS는 시스템이 거대해서 계정 종류가 다양하고 복잡하다. 😨 그래서 먼저 어떤 것들이 있는지 공부해보기로 하였다.</p>
<br>

<h2 id="aws-계정-유형">AWS 계정 유형</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/d1fd8f1e-7ed9-421e-bb64-d5a2a2c96bbb/image.png" alt=""></p>
<p>나는 AWS에서 제공하는 [AWS 계정 새로 만들기]를 통해 만든** 루트 사용자 계정(root user)**만을 사용하고 있었다.
로그인 때마다 보았던 IAM 사용자 로그인은 무엇이 다른지? 왜 사용되는지? 궁금했다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/ee77b0a3-62b1-4f95-93c3-d0a2ec49445c/image.png" alt=""></p>
<p>개발 팀 프로젝트를 진행하며 여러 사람과 AWS 리소스를 공유해야했는데 방법을 찾아보다가 IAM을 활용해야한다는 사실을 깨닫고 관리 콘솔에 IAM 서비스를 검색해보았다.</p>
<p>관리 콘솔에는 IAM과 IAM Identity Center 두 가지 서비스가 있는데 차이를 몰라 많이 헤맸었다. 😅 (계정 유형을 찾아보게 된 큰 계기.)</p>
<p>내가 조사한 각 계정의 특징을 나열하면 다음과 같다.</p>
<h3 id="특징">특징</h3>
<ul>
<li>루트 계정<ul>
<li>회사나 같은 팀, 외부 파트너 등 다른 사람으로부터 계정 정보를 받은게 아니라면, 회원가입 시 처음 만들게 되는 유형</li>
<li>모든 AWS 리소스의 최고 권한</li>
<li><strong>보안 강화 필수</strong></li>
</ul>
</li>
</ul>
<blockquote>
<p>루트 사용자는 해킹 위험이 크니까 필요할 때만 접속을 권장하는 듯하다.
내가 계정 주인이라 하더라도 나를 위한 또 다른 IAM 사용자나 역할을 만들어서 그것으로 접속하는 것을 추천하더라.</p>
</blockquote>
<ul>
<li>IAM 사용자<ul>
<li>루트 계정의 리소스에 접근할 수 있는 사용자 계정</li>
<li>각 사용자에게 권한을 할당하여 개별 관리하는 용도</li>
<li><strong>개발자, 관리자, 팀원 등 개인적으로 사용 가능하도록 각자의 자격 증명을 발급하여 사용(최소 권한 정책, 필요한 권한만 부여)</strong></li>
<li>_IAM_에서 설정 가능</li>
<li>소규모에서 가장 일반적이고 자주 사용됨</li>
</ul>
</li>
</ul>
<ul>
<li>IAM 역할 (=임시 자격 증명)<ul>
<li>크게 두 가지 용도가 있음</li>
<li>AWS 리소스가 다른 AWS 리소스에 접근하기 위한 용도(예시: EC2 인스턴스에서 S3 버킷 이미지 필요)</li>
<li>계정 간 접근, 외부 엔터티(다른 AWS 계정, 제3자 서비스, 외부 사용자 및 게스트)가 AWS 리소스에 임시 접근하기 위한 용도<ul>
<li>사람에게 IAM 사용자를 부여하는 것보다 비교적 높은 보안성</li>
<li>STS를 통해 해당 역할의 <strong>임시 자격 증명</strong> 발급</li>
</ul>
</li>
<li>_IAM_에서 설정 가능</li>
</ul>
</li>
</ul>
<blockquote>
<p>** IAM 사용자와 역할, 언제 쓰면 좋을까?**</p>
<ul>
<li>IAM 사용자<ul>
<li>AWS 리소스를 장기적이고 지속적으로 사용하는 팀원, 개발자, 운영자가 사용</li>
<li>고정된 권한, 일관된 접근, 자격 증명을 자주 변경하지 않음</li>
</ul>
</li>
<li>IAM 역할<ul>
<li>(예시)개발자가 특정 작업을 수행하기 위해 임시로 더 높은 권한이 필요할 때</li>
<li>다른 AWS 계정에 일시적으로 접근해야할 때</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li>AWS SSO (IAM Identity Center)<ul>
<li>여러 AWS 계정, 외부 ID 공급 애플리케이션(Azure AD, Okta 등)의 접근을 통합하고 한 번에 관리하는 기능</li>
<li>즉, <strong>한 번의 로그인으로 권한을 부여받은 모든 AWS 계정, 리소스에 접근 가능</strong></li>
<li>_IAM Identity Center_에서 설정 가능
<img src="https://velog.velcdn.com/images/a-white-bit/post/5b1a24bb-e95a-4bb6-b895-c2c07757fc0d/image.png" alt=""></li>
<li>SSO 생성 시 선택 - Organization 연동 / 단일 계정<ul>
<li>AWS Organizations: 여러 AWS 계정 리소스 접근 가능</li>
<li>이 계정에서 활성화: 현재 AWS 계정의 리소스에만 접근 가능</li>
</ul>
</li>
<li>관리자는 사용자별, 역할별, 그룹별 <strong>AWS SSO 계정</strong>을 생성하고 관리<ul>
<li>AWS SSO 포털이 별도로 존재</li>
<li><strong>SSO 포털에서만 사용할 수 있는 계정</strong>으로, AWS 계정과는 다름</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>IAM(사용자/역할) &amp; SSO 사용 사례 비교</strong>
 _개발자 A, B, C가 한 팀이고 A는 개발, B가 테스트, C가 프로덕션을 맡고 있으며 각자 AWS 계정을 사용 중.
 프로젝트 매니저인 나(D)는 모든 리소스를 확인할 권리가 있다. _</p>
</blockquote>
<ul>
<li>IAM을 사용할 경우
A, B, C가 각각 자신의 계정에서 사용자 D를 등록해야 한다.
나는 개발, 테스트, 프로덕션 각 리소스 접근마다 다른 로그인을 해야한다.</li>
<li>SSO를 사용할 경우
Organizations로 A, B, C 계정을 그룹화하고 단일 SSO 포털에 등록한다.
SSO 포털에 사용자 D를 등록한다.
나는 SSO 포털에 로그인하면 모든 리소스에 접근할 수 있다.</li>
</ul>
<h3 id="나의-사례">나의 사례</h3>
<p>내 루트 계정의 리소스를 팀원들과 공유하려고 할 때, 나에게는 세 가지 선택권이 있었다고 본다.
①IAM 사용자 ②IAM 역할 ③SSO</p>
<p>초보자인 내가 선택한 것은 ①IAM 사용자를 생성하고 팀원들에게 부여하기로 하였다.
5명 미만의 소규모 팀이고 내 AWS 계정으로 모든 리소스가 이뤄지고 있기 때문이다.
그리고 팀원들이 원하는 때에 지속적으로 리소스에 접근가능하도록 하고 싶었다. (②IAM 역할 탈락)</p>
<p>③SSO의 경우 처음에는 대규모 프로젝트에 맞다고 보였고 복잡하다고 생각하여...😅 순위에서 미뤘으나
자세히 알아보니 <strong>소규모 프로젝트에도 적용해서 보안성을 강화하고(임시 자격 증명) 한 번의 로그인으로 접근할 수 있다는 편의성</strong>을 가져가기에 채택하는 것이 좋아보인다고 판단.</p>
<p>먼저 ①IAM 사용자를 사용해본 후 차후 ③SSO로 중앙관리하는 방법으로 바꿔야겠다!
이렇게 하면 둘 다 사용해보며 편의성을 체감할 수 있을 것이다.
<br></p>
<h2 id="aws-cli-초기-설정">AWS CLI 초기 설정</h2>
<hr>
<p>AWS CLI 명령어를 사용하기 전, 사용하려는 계정 유형에 따라 초기 설정이 달라질 수 있다.
나는 지속적으로 EC2와 S3에 접근할 IAM 사용자 계정으로 접근하기로 했다.</p>
<ol>
<li>IAM 사용자 &amp; 루트 계정의 경우</li>
</ol>
<ul>
<li>Access Key, Secret Key 필요 -&gt; 없다면 aws 사이트 관리 콘솔에서 생성해야 함</li>
<li>CLI <code>aws configure</code></li>
<li>Region: <code>ap-northeast-2</code> (서울)</li>
<li>Output Format: <code>json</code> <code>yaml</code> <code>text</code> <code>table</code> 중 하나</li>
</ul>
<ol start="2">
<li>SSO의 경우</li>
</ol>
<ul>
<li>IAM Identity Center 콘솔에서 제공되는 URL 필요 <code>https://your-sso-portal.awsapps.com/start</code> 형식 -&gt; 없다면 aws 사이트 관리 콘솔에서 생성해야 함</li>
<li>CLI <code>aws configure sso</code></li>
<li>Region: <code>ap-northeast-2</code> (서울)</li>
<li>접근하려는 AWS 계정 ID</li>
</ul>
<ol start="3">
<li>STS의 경우</li>
</ol>
<ul>
<li><code>aws sts assume-role --role-arn &quot;arn:aws:iam::123456789012:role/ExampleRole&quot; --role-session-name &quot;SessionName&quot;</code></li>
</ul>
<br>

<h2 id="자주-사용하는-명령어">자주 사용하는 명령어</h2>
<hr>
<p>이하 내가 보려고 만든 명령어 모음</p>
<blockquote>
<p>기본 명령어 형태
<code>aws &lt;서비스&gt; &lt;명령어&gt; [옵션]</code>
예시: <code>aws s3 ls</code></p>
</blockquote>
<blockquote>
<p>원하는 값 필터링하기
jq (JSON 파싱 도구) 와 파이프(|) 사용</p>
</blockquote>
<h4 id="계정">계정</h4>
<ul>
<li>계정 설정 및 확인 <code>aws configure</code> 또는 <code>aws configure sso</code></li>
<li>프로파일 목록 <code>aws configure list-profiles</code></li>
<li>특정 프로파일 설정 정보 <code>aws configure list --profile &lt;profile-name&gt;</code></li>
<li>특정 프로파일로 명령어 사용하기 옵션 <code>--profile &lt;profile-name&gt;</code></li>
<li>특정 프로파일을 기본으로 사용하기 (환경변수 설정. 해당 세션 동안 적용)
Windows<code>$env:AWS_PROFILE=&quot;profile-name&quot;</code>
macOS / Linux <code>export AWS_PROFILE=&quot;profile-name&quot;</code></li>
<li>프로파일 초기화 (전체 삭제) <code>rm ~/.aws/config</code>, <code>rm ~/.aws/credentials</code> (둘 다 실행)</li>
<li>프로파일 삭제 (편집기로 수동 삭제) <code>nano ~/.aws/config</code></li>
<li>sso 캐시 삭제 <code>rm -rf ~/.aws/sso/cache</code></li>
<li>sso 프로파일 만료 리프레시 <code>aws sso login</code></li>
</ul>
<p><strong>EC2 인스턴스 ID와 이름 목록</strong>
<code>aws ec2 describe-instances --query &quot;Reservations[*].Instances[*].{Name: Tags[?Key==&#39;Name&#39;].Value | [0], InstanceId: InstanceId}&quot; --output table</code></p>
<p><strong>EC2 인스턴스 이름으로 정보 확인</strong>
<code>aws ec2 describe-instances --filters &quot;Name=tag:Name,Values=MyInstanceName&quot; --output table --no-cli-pager</code></p>
<p><strong>ID로 정보 확인</strong>
<code>aws ec2 describe-instances --instance-ids i-1234567890abcdef0 --output table --no-cli-pager</code></p>
<ul>
<li><code>--output table</code> 보기 편한 table 형식</li>
<li><code>--no-cli-pager</code> 한 번에 모두 출력됨</li>
</ul>
<h4 id="ec2">EC2</h4>
<ul>
<li>AMI 목록 조회 <code>aws ec2 describe-images --owners self</code> (*self = 현재 계정)</li>
<li>키 페어 생성 <code>aws ec2 create-key-pair --key-name MyKeyPair</code></li>
<li>키 페어 삭제 <code>aws ec2 delete-key-pair --key-name MyKeyPair</code></li>
<li>인스턴스 생성 및 시작 <code>aws ec2 run-instances --image-id ami-xxxxxxxx --instance-type t2.micro --key-name MyKeyPair --security-group-ids sg-xxxxxxxx --subnet-id subnet-xxxxxxxx</code></li>
<li>인스턴스 시작 <code>aws ec2 start-instances --instance-ids i-xxxxxxxxxxxx</code></li>
<li>인스턴스 중지 <code>aws ec2 stop-instances --instance-ids i-xxxxxxxxxxxx</code></li>
<li>인스턴스 확인 <code>aws ec2 describe-instances --instance-ids i-xxxxxxxxxxxx</code></li>
<li>인스턴스 종료 <code>aws ec2 terminate-instances --instance-ids i-xxxxxxxxxxxx</code></li>
<li>VPC 목록 조회 <code>aws ec2 describe-vpcs</code></li>
<li>보안 그룹 조회 <code>aws ec2 describe-security-groups --filters Name=vpc-id,Values=vpc-xxxxxxxx</code></li>
<li>보안 그룹 생성 <code>aws ec2 create-security-group --group-name MySecurityGroup --description &quot;My security group&quot;</code></li>
<li>보안 그룹 인바운드 규칙 추가 <code>aws ec2 authorize-security-group-ingress --group-id sg-xxxxxxxx --protocol tcp --port 22 --cidr 0.0.0.0/0</code></li>
<li>아웃바운드 규칙 추가 <code>aws ec2 authorize-security-group-egress --group-id sg-xxxxxxxx --protocol tcp --port 80 --cidr 0.0.0.0/0</code></li>
<li>EBS 볼륨 생성 <code>aws ec2 create-volume --size 10 --availability-zone us-west-2a</code></li>
<li>EBS 볼륨 삭제 <code>aws ec2 delete-volume --volume-id vol-xxxxxxxx</code></li>
<li>볼륨에 인스턴스 연결 <code>aws ec2 attach-volume --volume-id vol-xxxxxxxx --instance-id i-xxxxxxxxxxxx --device /dev/sdf</code></li>
<li>EBS 볼륨 스냅샷 생성 <code>aws ec2 create-snapshot --volume-id vol-xxxxxxxx --description &quot;My snapshot&quot;</code></li>
<li>스냅샷 삭제 <code>aws ec2 delete-snapshot --snapshot-id snap-xxxxxxxx</code></li>
<li>가용 영역 확인 <code>aws ec2 describe-availability-zones</code></li>
</ul>
<h4 id="s3">S3</h4>
<p><code>s3api</code>와 <code>s3</code>가 존재함
<code>s3api</code>는 정책, 권한 등 세부 제어시 필요
<code>s3</code>는 간단함</p>
<ul>
<li>버킷 생성 <code>aws s3 mb s3://my-bucket-name --region ap-northeast-2</code></li>
<li>버킷 삭제 <code>aws s3 rb s3://bucket-name [--force]</code></li>
<li>버킷 목록 <code>aws s3 ls</code></li>
<li>버킷 내 파일 목록 <code>aws s3 ls s3://bucket-name/path/</code></li>
<li>버킷 간 복사 <code>aws s3 cp s3://source-bucket/source-path s3://destination-bucket/destination-path</code></li>
<li>파일 삭제 <code>aws s3 rm s3://bucket-name/path/to/file</code></li>
<li>경로 파일 모두 삭제 <code>aws s3 rm s3://bucket-name/path/ --recursive</code></li>
<li>로컬을 버킷과 동기화 <code>aws s3 sync local-directory/ s3://bucket-name/path/</code></li>
<li>버킷을 로컬과 동기화 <code>aws s3 sync s3://bucket-name/path/ local-directory/</code></li>
<li>파일 단일 업로드 <code>aws s3 cp path/to/local-file s3://bucket-name/path/to/destination</code></li>
<li>디렉터리 전체 업로드 <code>aws s3 cp path/to/local-directory s3://bucket-name/path/to/destination --recursive</code></li>
<li>동기화 업로드(변경된 파일만) <code>aws s3 sync path/to/local-directory s3://bucket-name/path/to/destination</code></li>
<li>파일 단일 다운로드 <code>aws s3 cp s3://bucket-name/path/to/file path/to/local-destination</code></li>
<li>디렉터리 전체 다운로드 <code>aws s3 cp s3://bucket-name/path/to/directory path/to/local-directory --recursive</code></li>
<li>동기화 다운로드 <code>aws s3 sync s3://bucket-name/path/to/directory path/to/local-directory</code></li>
<li>다운로드/업로드 시 특정 파일 제외 옵션 <code>--exclude &quot;*.tmp&quot;</code> 예시(tmp)</li>
</ul>
<h4 id="iam">IAM</h4>
<ul>
<li>사용자 및 역할 관리</li>
<li>관한 정책 설정</li>
</ul>
<h4 id="rds">RDS</h4>
<ul>
<li>데이터베이스 인스턴스 생성</li>
<li>인스턴스 삭제</li>
<li>백업</li>
</ul>
<h3 id=""></h3>
<blockquote>
<p>참조 페이지
<a href="https://aws.amazon.com/ko/cli/">https://aws.amazon.com/ko/cli/</a>
<a href="https://docs.aws.amazon.com/cli/latest/">https://docs.aws.amazon.com/cli/latest/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모와 혼잣말]]></title>
            <link>https://velog.io/@a-white-bit/%EB%A9%94%EB%AA%A8</link>
            <guid>https://velog.io/@a-white-bit/%EB%A9%94%EB%AA%A8</guid>
            <pubDate>Tue, 05 Nov 2024 02:39:35 GMT</pubDate>
            <description><![CDATA[<p>몇 개월 전, 프로젝트를 하면서 배포를 해야하는 상황에 놓여 발을 들이게 된 데브옵스...
처음엔 굉장히 막막했고 배포 마감일이 다가오는 촉박함 속에서 방법을 갈구하며 채찍질 했던 나날들이 떠오른다.
며칠 밤낮 새어가며 어떻게든 배포가 되게끔 만든 나를 수고했다고 셀프쓰담하고.
단기간에 들어온 정보들이 많아 글로 기록하고 싶었지만 엄두가 나지 않았음.
몇 달 후인 현재, 재배포를 시도하려다가 기억이 나지 않는 부분이 많아 멘탈붕괴 중..</p>
<p>특히 보안 때문에 생성하게 된 여러 키 파일들이 장렬한 고민의 흔적으로 남아있다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/7490ec0f-8310-445d-aa1c-92734af37521/image.png" alt=""></p>
<p>(p12 파일과 pem 파일이 난무하고 어떤 파일은 pub 파일이 포함되어있고, p12의 base64버전도 존재한다.)</p>
<p>당시 우리 웹페이지를 https로 만들겠다며 여러가지로 구글링, 튜토리얼 따라도 해보고...
그래서 필요이상으로 생성되고 지우기를 반복해서 현재의 나는 뭘 사용하고 더 이상 사용하지 않는지 헷갈리는 지경이다!</p>
<p>결론은,</p>
<p>내가 한 번 했던 것은 간단하게라도 적어서 기록해야 한다는 것.
나는 금붕어다</p>
<h2 id="적으려는-글">적으려는 글</h2>
<ul>
<li>배포 2탄, 내가 한 프론트 서버 배포 방법</li>
<li>IAM으로 동료들에게 접근 권한 설정하는 방법</li>
<li>AWS CLI로 데브옵스 관리하는 방법</li>
<li>아키텍처에 로드밸런싱, 오토스케일링 적용하는 방법</li>
<li>다중 인스턴스에 코드 배포하는 방법</li>
</ul>
<h2 id="해야되는-것">해야되는 것</h2>
<ul>
<li>프로젝트 환경변수 통일 (GitHub Actions Secrets/Dotenv)</li>
<li>GitHub Actions workflows</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기술면접] Http 요청시 Spring 동작 과정, Spring AOP, Transaction 전파]]></title>
            <link>https://velog.io/@a-white-bit/%EB%AA%A8%EC%9D%98%EB%A9%B4%EC%A0%91-Http-%EC%9A%94%EC%B2%AD%EC%8B%9C-Spring-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95-Spring-AOP-Transaction-%EC%A0%84%ED%8C%8C</link>
            <guid>https://velog.io/@a-white-bit/%EB%AA%A8%EC%9D%98%EB%A9%B4%EC%A0%91-Http-%EC%9A%94%EC%B2%AD%EC%8B%9C-Spring-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95-Spring-AOP-Transaction-%EC%A0%84%ED%8C%8C</guid>
            <pubDate>Fri, 16 Aug 2024 07:32:55 GMT</pubDate>
            <description><![CDATA[<h2 id="사용자가-api-요청에-대한-응답을-할-때-spring-내부-동작에-대해-설명할-수-있는가">사용자가 API 요청에 대한 응답을 할 때 Spring 내부 동작에 대해 설명할 수 있는가?</h2>
<p>디스패처 서블릿, 핸들러 매핑</p>
<p>Spring 프레임워크는 MVC 패턴 기반
클라이언트로부터 HTTP 요청 -&gt; DispatcherServlet이 받음 -&gt; 핸들러 매핑: 요청에 맞는 컨트롤러를 찾아줌 -&gt; 컨트롤러 실행 -&gt; 요청에 맞게 모델에게 필요한 데이터 조작 요청 -&gt; 데이터 처리 결과를 컨트롤러가 받아 렌더링할 페이지를 뷰에게 알리거나 응답 데이터를 직렬화(보통 JSON, XML)하여 전달</p>
<ul>
<li><p>JSON: 가볍고 사람이 읽기 편한 데이터 형식, 데이터의 구조를 표현 가능(객체, 배열, 데이터 타입 분류- 문자열인지 숫자인지)해서 평문보다 파싱에 더 적합함</p>
</li>
<li><p>XML: JSON과 비슷하게 사람이 읽기 편한 데이터 형식 (태그 구조)</p>
</li>
<li><p>Model: 비즈니스 로직, 데이터 관리 로직
도메인 객체, 데이터 전송 객체 DTO, @Service 계층</p>
</li>
<li><p>View: 클라이언트 인터페이스. 화면을 표시, 데이터 입출력 상호작용
JSP, Thymeleaf 다양한 템플릿 엔진</p>
</li>
<li><p>Controller: 클라이언트 요청에 맞게 로직에 따라 모델과 뷰 사이의 데이터 전달
@Controller</p>
</li>
</ul>
<h2 id="spring-aop는-무엇인가">Spring AOP는 무엇인가?</h2>
<p>관점 지향 프로그래밍
핵심 로직에서 부가적인 기능(관심사) 코드를 분리하여 관리하는 방법입니다.
주로 로깅, 트랜잭션 관리, 보안처리, 예외처리 등을 사용합니다.
AOP를 사용하면 코드의 모듈화로 코드 중복을 줄이고 재사용성이 높아지고 유지보수가 쉬워집니다.</p>
<p>프록시를 패턴을 사용함으로서 런타임에 동작하는 특성을 갖고 있고 필요한 시점에만 실행한다는 장점이 있으나 디버깅이 어렵다는 단점이 있습니다. 성능 오버헤드가 발생할 수 있습니다.
프록시 패턴은 공부가 좀 더 필요한 것 같습니다.</p>
<h2 id="transaction-전파-전략을-설명하고-각각-어떤-상황에서-사용되는가">Transaction 전파 전략을 설명하고 각각 어떤 상황에서 사용되는가?</h2>
<p>메서드 간에 이동할 때 이미 트랜잭션이 진행중이면 추가 트랜잭션 진행을 어떻게 할지 결정할 수 있습니다.
Spring에서는 REQUIRED(디폴트), REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTED 속성이 있습니다.</p>
<p>복잡한 트랜잭션 요구사항이 필요할 때 트랜잭션 전파 속성을 적절히 사용하여 데이터 무결성과 일관성을 유지할 수 있습니다.</p>
<p>물리 트랜잭션: 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋/롤백하는 단위
논리 트랜잭션: 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위</p>
<ol>
<li>REQUIRED
의미: 트랜잭션이 필요함(없으면 새로 만듬)
기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
기존 트랜잭션이 있음: 기존 트랜잭션에 참여함</li>
</ol>
<p>REQUIRED는 디폴트 속성으로써 모든 트랜잭션 매니저가 지원하는 속성이다. 별도의 설정이 없다면 REQUIRED로 트랜잭션이 진행된다.</p>
<ol start="2">
<li><p>REQUIRES_NEW
의미: 항상 새로운 트랜잭션이 필요함
기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
기존 트랜잭션이 있음: 기존 트랜잭션을 보류시키고 새로운 트랜잭션을 생성함</p>
</li>
<li><p>SUPPORTS
의미: 트랜잭션이 있으면 지원함(트랜잭션이 없어도 됨)
기존 트랜잭션 없음: 트랜잭션 없이 진행함
기존 트랜잭션이 있음: 기존 트랜잭션에 참여함</p>
</li>
<li><p>NOT_SUPPORTED
의미: 항상 새로운 트랜잭션이 필요함
기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
기존 트랜잭션이 있음: 기존 트랜잭션을 보류시키고 새로운 트랜잭션을 생성함</p>
</li>
</ol>
<ol start="5">
<li><p>MANDATORY
의미: 트랜잭션이 의무임(트랜잭션이 반드시 필요함)
기존 트랜잭션 없음: IllegalTransactionStateException 예외 발생
기존 트랜잭션이 있음: 기존 트랜잭션에 참여함</p>
</li>
<li><p>NEVER
의미: 트랜잭션을 사용하지 않음(기존 트랜잭션도 허용하지 않음)
기존 트랜잭션 없음: 트랜잭션 없이 진행
기존 트랜잭션이 있음: IllegalTransactionStateException 예외 발생</p>
</li>
<li><p>NESTED
의미: 중첩(자식) 트랜잭션을 생성함
기존 트랜잭션 없음: 새로운 트랜잭션을 생성함
기존 트랜잭션이 있음: 중첩 트랜잭션을 만듬</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드(Backend) 프로젝트 배포 (1) - 아키텍처 설계]]></title>
            <link>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-1-GitHub-Actions-Docker-AWS-EC2</link>
            <guid>https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-1-GitHub-Actions-Docker-AWS-EC2</guid>
            <pubDate>Mon, 05 Aug 2024 08:02:33 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/813a6547-4ecd-42ab-9378-1aca18a95b07/image.png" alt=""></p>
<p>&quot;<em>Good Bite</em>&quot; 라는 이름의 팀 프로젝트로, <strong>식당과 손님 간의 테이블링 예약(웨이팅) 웹 애플리케이션</strong>을 만들고 있습니다.
우리 팀은 깃허브로 코드 형상관리를 하고 공유하고 있기 때문에, 연동과 접근성이 좋은 GitHub Actions을 사용하여 편리하게 웹 서버에 CI/CD를 구현하기로 결정!</p>
<hr>
<h1 id="good-bite-서비스-아키텍처-설계">Good Bite 서비스 아키텍처 설계</h1>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/a938330e-3716-4ef2-b0bc-23eaf271f5b1/image.png" alt=""></p>
<h2 id="현재-단계는">현재 단계는..</h2>
<p>백엔드 애플리케이션은 Spring Boot로, 위 그림을 보면 현재 백엔드 서버만 배포한 상태이고 프론트엔드는 React로 개발하였고 로컬 머신에서 테스트 단계 중입니다. 추후에 프론트엔드 서버를 추가 배포한 형태의 아키텍처를 들고 오겠습니다.</p>
<blockquote>
<p>[프론트엔드 추가 배포 과정 페이지]</p>
</blockquote>
<p>목표는 CSR (클라이언트 사이드 렌더링) 방식을 사용하여 사용자에게 뷰를 제공하고, 백엔드에서 서비스 API 처리를 하는 구조입니다. 😁</p>
<p>오류 상황과 스케일 아웃에 유연하게 대처할 수 있도록 프론트와 백엔드 서버를 독립적으로 배포하기로 하였고, 각 서버에서 Dockerfile을 기반으로 애플리케이션 이미지를 빌드하고, 빌드된 이미지를 컨테이너로 실행하여 서버를 운영하도록 하였습니다.</p>
<p>소규모 프로젝트로서 EC2 인스턴스는 현재 하나이지만 최종 아키텍처에서는 로드밸런싱을 추가한 오토 스케일링 전략을 수행하려고 합니다.</p>
<h3 id="csr을-선택한-이유">CSR을 선택한 이유</h3>
<p>CSR은 동적 렌더링 방식으로, 사이트에 처음 접속할 때 로딩 소요시간이 정적 렌더링 방식보다는 조금 더 걸리지만 그 이후 페이지를 전환할 때는 필요한 데이터만 받아오기 때문에 클라이언트의 초기 로딩 속도를 줄이고 빠른 사용자 경험을 제공할 수 있습니다.</p>
<p>프로젝트의 특성을 고려했을 때 사용자 간의 웨이팅 서비스가 실시간으로 이루어져야 하는 것이 핵심이어서 페이지를 동적으로 생성했을 때 사용자 경험에 영향이 클 것으로 판단되어 클라이언트 사이드 렌더링을 채택하였습니다.</p>
<p>초기 로딩 속도가 지연되는 단점은 사용자에게 로딩바 화면을 제공하여 보완할 수 있다고 판단하였습니다.</p>
<h3 id="fe-be-분리한-이유">FE-BE 분리한 이유</h3>
<p>이것은 헤드리스 아키텍처로, 백엔드와 프론트엔드가 명확히 분리된 구조를 가집니다. 각 사이드가 독립적으로 설계되면 역할이 분산되어 서버 부하를 낮출 수 있으며 확장성과 재사용성을 높일 수 있습니다.</p>
<p>백엔드 측면에서 API 서버로서의 역할에만 집중할 수 있어 개발 생산성이 향상되는 점과 다양한 클라이언트에 대응할 수 있기 때문에 헤드리스 아키텍처로 구성해보았습니다.</p>
<h3 id="rest-api를-선택한-이유">REST API를 선택한 이유</h3>
<p>각 서버 간 통신은 REST API로 동작합니다. API에는 여러 종류가 있지만, REST API를 선택한 이유는 웹 브라우저 뿐만 아니라 모바일 앱 버전 확장을 고려했을 때 무리가 없을 것이라 판단했기 때문입니다.</p>
<p>Owner와 Customer 두 가지 유형의 클라이언트가 사용되며, 웹 또는 모바일 환경에서 다양한 장치를 통해 접근할 수 있는 서비스가 자연스럽다고 생각했습니다. REST API는 언어 및 플랫폼에 종속되지 않기 때문에 웹 브라우저뿐만 아니라 모바일 앱, IoT 기기 등 다양한 환경에서 API를 호출할 수 있습니다.</p>
<h3 id="docker-컨테이너-배포를-선택한-이유">Docker 컨테이너 배포를 선택한 이유</h3>
<p>Docker를 사용함으로써 환경 일관성을 유지하고 배포를 간소화하여 개발 측면에서 편리함을 제공해주기 때문입니다. 더불어 확장성과 이식성도 높일 수 있습니다.</p>
<ul>
<li><p>환경 일관성 유지: Docker를 사용하면 개발 환경, 테스트 환경, 배포 환경 모두에서 동일한 애플리케이션 구성을 사용할 수 있습니다. 이를 통해 로컬 환경과 서버 환경 간의 불일치를 줄일 수 있으며, 개발자가 사용하는 환경과 실제 운영 환경의 차이를 최소화할 수 있습니다.</p>
</li>
<li><p>배포 간편화: Docker 이미지를 사용하여 백엔드 애플리케이션을 일관된 방법으로 EC2 서버에 쉽게 배포할 수 있습니다. Docker는 해당 애플리케이션의 모든 종속성, 라이브러리 등을 포함하므로 서버 환경에 상관없이 애플리케이션을 실행할 수 있게 합니다.</p>
</li>
<li><p>CI/CD 파이프라인 통합: CI/CD 도구(GitHub Actions)와의 연계가 용이합니다. Docker 이미지를 자동으로 빌드하고 테스트하며, 새로운 버전을 EC2에 쉽게 배포할 수 있어 배포 프로세스가 자동화됩니다.</p>
</li>
<li><p>확장성: Docker는 컨테이너 기반이므로 애플리케이션을 빠르게 확장하거나 축소할 수 있습니다. 특히 EC2와 같은 클라우드 환경에서 Docker를 사용하면 여러 컨테이너를 쉽게 스케일링하여 유동적인 트래픽을 처리할 수 있습니다.</p>
</li>
<li><p>이식성: Docker를 사용하면 애플리케이션을 컨테이너화하여 어디서든지 실행할 수 있습니다. 로컬 개발 환경, 스테이징 서버, 프로덕션 서버 등 다양한 환경에서 동일하게 동작할 수 있는 것이 큰 장점입니다.</p>
</li>
</ul>
<h3 id="수동-배포-vs-자동-배포">수동 배포 vs 자동 배포</h3>
<p>GitHub Actions 를 사용하기 전에는 수동으로 <code>jar</code> 파일로 빌드하고 EC2 배포 서버에 SSH 통신으로 직접 파일을 전달하고, 필요한 패키지를 설치했었습니다.</p>
<p>여러 배포 방법을 찾아보다가 CI/CD 를 적용하는 것이 매우 편하고 지속적으로 배포할 때 유리하다는 것을 깨닫고 별도의 계정 가입과 설치 과정이 필요 없는 GitHub Actions 를 채택하게 되었고,
workflows 파일 작성법을 공부한 뒤 코드만 작성해주니
이전의 불편한 과정들을 GitHub의 머신이 해결해주어 제가 EC2에 접근하지 않아도 배포가 되어버리는 매직이 발생했습니다.</p>
<p>내가 수동의 방법을 택하는 경우는 최대 성능을 위해 내가 상세한 설정을 컨트롤할 수 있을 경우에 많이 선택했었는데, CI/CD 또한 자동 배포가 되어 편할 뿐만 아니라 원하는 만큼의 과정을 직접 설정하는 것에 있어서 큰 차이를 느끼지 못했습니다.</p>
<p>자동 배포는 한 번 설정하고 난 뒤로 재배포부터는 간단한 경우 코드 몇 줄만 바꿔도 가능하기에 굉장한 편리함을 느꼈고 앞으로도 이 방법을 애용할 것 같습니다.</p>
<hr>
<h1 id="배포-과정">배포 과정</h1>
<p>글의 내용이 길어 여러 포스트에 나누어 작성하도록 수정하였습니다. (2025-01-14)</p>
<p><a href="https://velog.io/@a-white-bit/%EB%B0%B1%EC%97%94%EB%93%9CBackend-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC2">다음 글 보러가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기술면접] 상속, 오버라이딩, 추상클래스, 인터페이스, 프로세스, 쓰레드, Java Optional]]></title>
            <link>https://velog.io/@a-white-bit/%EB%AA%A8%EC%9D%98%EB%A9%B4%EC%A0%91-3%ED%9A%8C</link>
            <guid>https://velog.io/@a-white-bit/%EB%AA%A8%EC%9D%98%EB%A9%B4%EC%A0%91-3%ED%9A%8C</guid>
            <pubDate>Mon, 29 Jul 2024 10:38:31 GMT</pubDate>
            <description><![CDATA[<h1 id="상속-오버라이딩">상속, 오버라이딩</h1>
<h2 id="오버라이딩과-오버로딩의-차이">오버라이딩과 오버로딩의 차이</h2>
<p>둘 다 객체지향 프로그래밍에서 다형성을 구현할 수 있는 개념입니다.</p>
<p>오버라이딩은 자식클래스에서 부모클래스의 메서드를 재정의합니다. 
메서드 시그니처가 동일해야합니다. (메서드이름, 매개변수, 반환타입)
접근 제한자가 부모보다 닫혀있을 수 없습니다.
부모의 예외보다 더 넓은 범위의 예외를 던질 수 없습니다.
@Override라는 애너테이션을 사용해서 명시적으로 표기하여 가독성을 높이고 실수를 방지할 수 있습니다.</p>
<p>오버로딩은 같은 클래스 내에 같은 이름 메서드를 여러 개 정의하는 것이고, 대신 서로 다른 매개변수 리스트를 가져야 합니다.
매개변수의 타입, 개수 또는 순서가 달라야 합니다.
같은 이름의 메서드가 매개변수가 다르다면 반환타입이 다를 수 있습니다.</p>
<p>오버라이딩과 오버로딩은 둘 다 객체지향 프로그래밍에서 다형성을 구현하는 방법이고 코드의 유연성과 재사용성을 높일 수 있습니다.</p>
<h2 id="추상클래스와-인터페이스-차이">추상클래스와 인터페이스 차이</h2>
<p>추상 클래스와 인터페이스는 모두 스스로 객체를 만들 수 없고 상속의 개념을 사용한다는 공통점이 있습니다.</p>
<p>추상 클래스는 클래스의 공통된 속성, 행동을 정의하고
추상메서드를 포함해 일부 동작을 위임할 수 있습니다.
자바에서는 단일 상속만 가능합니다.</p>
<p>인터페이스는 추상 메서드와 public static final 필드로 구성되어 있어 클래스가 구현해야할 특정 행동을 강제할 때 사용합니다. 
자바 8 이후부터 default 메서드, static 메서드를 정의할 수 있습니다.
생성자가 없고, 다중 상속이 가능합니다.</p>
<h1 id="프로세스와-쓰레드">프로세스와 쓰레드</h1>
<ul>
<li><p>프로세스: 실행 중인 프로그램의 인스턴스이며 각 프로세스는 독립적으로 실행됩니다.
프로세스는 메모리에 자신의 주소 공간을 가지고 프로그램 코드, 데이터, 힙, 스택영역으로 구성되어 있습니다.</p>
</li>
<li><p>쓰레드: 프로세스 내부의 작은 실행 단위이며 각 쓰레드는 독립적으로 실행되지만 같은 프로세스의 코드, 데이터, 힙 메모리를 공유합니다.</p>
</li>
</ul>
<p>레지스터와 스택은 자신의 자원을 사용합니다.</p>
<h2 id="컨텍스트-스위칭">컨텍스트 스위칭</h2>
<p>운영체제 스케줄러가 하나의 프로세스에서 다른 프로세스로 전환하는 과정 또는 쓰레드에서 쓰레드일 수도 있습니다.
프로세스 전환은 CPU에 현재 프로세스 상태와 메모리 정보를 저장하고 새 프로세스 데이터를 가져오면서 오버헤드가 발생합니다.
쓰레드는 같은 프로세스 내에서 실행되기 때문에 주소 공간을 전환하지 않아 일반적으로 더 적은 오버헤드가 발생합니다.</p>
<p>순서:</p>
<ol>
<li>현재 실행중인 작업의 상태를 제어블록에 저장합니다.</li>
<li>스케줄러가 선택한 다음 작업의 상태를 제어블록으로부터 작업을 복원합니다.</li>
<li>CPU가 이어서 실행합니다.</li>
</ol>
<p>사용 이유:</p>
<ol>
<li>여러 작업을 동시에 처리하여 사용자의 편의가 증가합니다.</li>
<li>cpu가 유휴상태가 되지 않도록 자원과 프로세스를 효율적으로 사용할 수 있게 합니다.</li>
<li>멀티스레딩을 사용하여 프로그램이 더 빠르고 자원을 효율적으로 사용할 수 있게 합니다.</li>
<li>각 프로세스에게 독립적인 실행 환경을 제공해서 다른 프로세스에게 영향이 없도록 합니다.</li>
</ol>
<hr>
<p>프로세스 컨텍스트 스위칭 순서 </p>
<ol>
<li>현재 프로세스 상태를 제어블록에(프로세스 제어블록 PCB) 저장: 실행 중이던 pc 같은 레지스터값, 메모리관리정보</li>
<li>다음 프로세스 상태를 (PCB) 가져옴</li>
<li>cpu가 다음 프로세스를 실행</li>
</ol>
<p>쓰레드 컨텍스트 스위칭 순서
요약: 쓰레드 제어블록 TCB에 쓰레드가 사용하던 레지스터 값을 저장하고 다음 차례가 됐을 때 다시 CPU에 로드하여 명령을 실행할 수 있게 합니다.</p>
<p>*프로그램 카운터 : 레지스터</p>
<ul>
<li>스케줄링 알고리즘:
FCFS 먼저 도착한 작업을 먼저 실행
SJF 실행시간이 가장 짧은 작업 먼저 실행
라운드로빈 : 각 작업에 동일한 시간 부여</li>
</ul>
<h1 id="java-optional">Java Optional</h1>
<ul>
<li>Java 8부터 도입</li>
</ul>
<h2 id="optional의-주요-메서드-활용">Optional의 주요 메서드 활용</h2>
<p>변수가 값을 가질 수도 있고 아닐 수도 있는 상황을 명시적으로 처리할 때 사용합니다.</p>
<p>장점: 코드의 가독성, 안정성, 유지보수성을 높일 수 있습니다.
Optional을 적절히 사용하면 NPE를 방지할 수 있습니다.
값이 없을 수도 있다는 것을 알릴 수 있습니다.
함수형 프로그래밍을 사용할 수 있습니다.</p>
<p>단점:
Optional 객체를 생성하므로 성능 부하와 메모리 사용량이 증가합니다.
간단한 null검사를 Optional으로 대체하는 것이 불필요할 수 있습니다.
컬렉션 제네릭 타입으로 Optional을 두는 것은 가독성이 안좋고 복잡할 수 있습니다.
JSON 직렬화/역직렬화가 지원되지 않습니다.</p>
<p>따라서 변수의 null값을 확인이 필요할 때에만 사용해야 하고 Optional을 남발하여 사용하는 것은 불필요하다고 생각합니다.</p>
<ul>
<li><p>생성메서드
<code>Optional.empty()</code>
<code>Optional.of(~)</code>
<code>Optional.ofNullable(~)</code></p>
</li>
<li><p>검색
<code>isPresent()</code>
<code>isEmpty()</code></p>
</li>
<li><p>조회
<code>get()</code> : null일 경우 NoSuchElementException 예외
<code>orElse(~)</code>
<code>orElseGet(~)</code> null일 경우 기본값 ~
<code>orElseThrow(~)</code> null일 경우 예외</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅] IntelliJ HTTP Client 쿠키 설정 오류]]></title>
            <link>https://velog.io/@a-white-bit/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-IntelliJ-HTTP-Client-%EC%BF%A0%ED%82%A4-%EC%84%A4%EC%A0%95-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@a-white-bit/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-IntelliJ-HTTP-Client-%EC%BF%A0%ED%82%A4-%EC%84%A4%EC%A0%95-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Fri, 26 Jul 2024 11:33:08 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>Rest API 를 구현하고 테스트할 때 Postman을 사용하는 방법이 있지만,
IntelliJ IDEA에 내장된 HTTP Client 플러그인으로 Rest API 테스트를 쉽게 해볼 수 있다.</p>
<p>그래서 우리 팀은 모두 IntelliJ 환경으로 개발중이었기 때문에 .http 파일을 작성하여 테스트를 진행하였다.</p>
<p>Postman으로 이미 같은 파일로 인증/인가를 테스트 해보았고, 통과하였다.
로직은 문제가 없는 것으로 보였다. 하지만 HTTP Client 테스트는 쿠키를 설정하고 값을 가져오는 데 이상한 부분이 있었다.</p>
<h1 id="문제점">문제점</h1>
<p>다음은 내가 작성한 인증/인가 테스트의 한 부분이다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/cb7a0056-5370-4cbf-9218-3d2135ae3b94/image.png" alt=""></p>
<p>로그인 요청을 하면 응답 쿠키를 저장하고, 로그아웃 요청과 리프레시 요청을 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/65911fe6-6a5a-4864-a0f2-1b04c608ad15/image.png" alt=""></p>
<p>쿠키가 잘 저장되는 것을 확인하였다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/e49d1dc7-ce8d-4a37-9da9-7b71a36be87c/image.png" alt=""></p>
<p>로그아웃은 잘 실행이 되나,</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/e2fe5635-df49-4d76-a823-2a2d98083a04/image.png" alt=""></p>
<p>리프레시 요청은 토큰을 읽지 못하는 것이었다.
디버깅으로 어떤 값이 들어가는지 확인해보기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/5d95fe25-c6d5-4f57-9aea-0f48f3b0cf9c/image.png" alt=""></p>
<p>N o W a y...
맙소사. {{refreshToken}} 값이 들어간다.</p>
<p>오탈자가 들어갔는지, 변수 지정을 잘못해준 것인지, 쿠키 헤더 지정을 잘못해준 것인지 확인해보았지만... 오타는 없었다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/b9ab41ae-d21d-433e-8e5d-b148fc10501b/image.png" alt=""></p>
<p>심지어 지정할 수 없는 변수명을 입력하면 노란 줄을 띄워 알려주기까지 한다. 변수명을 입력하는 중괄호 {{ }} 에 포인터를 두면 구별되어 변수가 연결된다는 것도 쉽게 알 수 있다.</p>
<p>그런데 왜 문자열 {{refreshToken}} 이 들어가는 것일까 ?</p>
<h1 id="해결">해결</h1>
<p>정말 간단한 거였는데 깨닫기까지 오래 걸렸다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/675f1adc-3693-4de0-997f-3b4538a91315/image.png" alt=""></p>
<p>HTTP 요청을 할 때, 한 번 쿠키 파일에 저장된 쿠키는 자동으로 함께 요청에 포함된다.
그러므로 Cookie를 명시해주면 안된다. 같은 헤더를 갖는 쿠키를 덮어 씌워 보내지게 된다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/53654aa3-ed67-45e1-8e3f-a89f3b3896f1/image.png" alt=""></p>
<p>해결되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅] JWT 만료 예외처리]]></title>
            <link>https://velog.io/@a-white-bit/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-JWT-%EB%A7%8C%EB%A3%8C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@a-white-bit/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-JWT-%EB%A7%8C%EB%A3%8C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Thu, 25 Jul 2024 05:35:26 GMT</pubDate>
            <description><![CDATA[<h1 id="트러블슈팅-개요">트러블슈팅 개요</h1>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/3aa01f27-72fd-4b7d-a14a-1f4ca23af690/image.png" alt=""></p>
<p>postman에서 사용자 로그인 기능 테스트를 하던 도중, 만료 기한이 지난 액세스 토큰을 보내면 의도치 않은 response를 받았다.</p>
<p>시큐리티 jwt 인가처리하는 필터를 정의할 때, 만료된 토큰에 대하여 리프레시 토큰을 검증하고 확인되면 액세스 토큰을 재발급하는 코드를 작성했는데, 작동하지 않았다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/fb96185a-ee03-41ed-b45a-efa1c33245a4/image.png" alt=""></p>
<p>ExpiredJwtException 예외가 발생하고있다.</p>
<h1 id="jwt-만료-예외처리-수정-과정">JWT 만료 예외처리 수정 과정</h1>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/684784a8-aba5-4e31-9177-da3334ca39ee/image.png" alt=""></p>
<h2 id="jwt-유효-검증">JWT 유효 검증</h2>
<p>JWT 인가 처리 필터에서 유효하지 않은 액세스 토큰을 검증을 먼저 하고,</p>
<p>유효하다면 만료된 토큰을 검증하는 코드를 썼다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/485f05c6-0837-4c7c-ac34-e26b30b173a7/image.png" alt=""></p>
<p>액세스 토큰 검증 메서드 isTokenValid()는
<code>Jwts.parserBuilder().setSigningKey(JwtConfig.key).build().parseClaimsJws(token);</code> 코드에서 예외 발생으로 확인한다.
여기서 만료 예외인 ExpiredJwtException이 발생하여 잡지 못한채로 필터를 빠져나가는 것을 찾았다.</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/1b55c202-4fbc-4832-a4ef-bdd337b3b20b/image.png" alt=""></p>
<p>그래서 위와 같이 ExpiredJwtException을 잡아 계속 진행할 수 있도록 바꿔주었다.</p>
<h2 id="jwt-만료-검증">JWT 만료 검증</h2>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/8247d82b-e398-4231-bc80-11b92e7e2c0d/image.png" alt=""></p>
<p>isTokenExpired() 에서 검증을 위해 <code>Jwts.parserBuilder().setSigningKey(JwtConfig.key).build().parseClaimsJws(token);</code> 을 거쳐 다시 한 번 파싱하는데, 같은 코드를 반복하기에 줄여볼 수 없을까 생각을 해봤다.</p>
<p>예외발생하지 않고 토큰으로부터 만료기한만 꺼낼 수 없을까 찾아봤는데</p>
<p><img src="https://velog.velcdn.com/images/a-white-bit/post/667d8e79-5c76-4e27-8dd2-f837a43efd40/image.png" alt=""></p>
<p><code>Jwts.parserBuilder().setSigningKey(JwtConfig.key).build().parseClaimsJws(token);</code>
결국 위의 파싱과정을 거치기 때문에 같은 결과일 것이 뻔하다..</p>
<h2 id="다른-방법">다른 방법?</h2>
<ol>
<li><p>JWTs 패키지를 사용하지 않고 직접 토큰 스트링을 BASE64로 디코딩하여 파싱하는 방법이 있다..</p>
<p> 1) BASE64 로 디코딩
 2) JSON 데이터를 Object Mapper를 사용하여 파싱
 3) 파싱데이터에 claim 안에 &quot;exp&quot; 를 검색</p>
</li>
<li><p>JwtUtil 클래스에 변수를 두어 저장값을 사용한다.</p>
<p> 이 방법은 static 클래스로 정의된 JwtUtil 으로서는 전역적으로 사용되는 변수를 두는 것은 좋지 않다고 생각한다.
 static 구조가 아닐 때 사용하는 것이 좋을 것 같다.</p>
</li>
<li><p>isTokenValid() 메서드가 만료에 대한 값을 넘겨준다.</p>
<p> 값을 어떤 형태로 넘겨줄지 선택해야 하는 문제가 생긴다.
 enum type, 상수 리터럴..
 파싱 중복코드를 피하는 방법 중에서는 가장 나은 선택으로 보인다.</p>
</li>
<li><p>JwtUtil 클래스에 재발급 메서드를 생성한다.</p>
</li>
</ol>
<hr>
<p>결국 파싱하는 것은 똑같고, 후자의 방법을 선택해도 내부적으로 같은 과정을 거치기 때문에
1번은 의미가 없다고 생각한다.</p>
<p>2번은 구조를 바꿔야해서 더 많은 작업이 들어갈 것 같다.</p>
<p>3번은 기능상 가장 낫지만 다른 분들에게 충분히 설명되어야 한다. 상수 리터럴로 넘겨주면 간단하지만 명시적이지 않을 수가 있다.</p>
<p>고심 끝에 4번을 실행하기로 했다. 유틸 클래스에 대한 고민을 했는데 유틸의 기능을 해치지 않는다고 판단했고, 서블릿 요청 객체와 응답 객체를 넘겨주면 액세스 토큰과 리프레시 토큰을 검증하고 만료시 새 토큰을 발급해주는 메서드를 작성했다.</p>
<pre><code class="language-java">
  // 액세스토큰 확인 후 만료시 리프레시 토큰을 확인하고 새 토큰 발급
    public static String checkTokens(HttpServletRequest req, HttpServletResponse res) {

        String accessToken = getAccessTokenFromRequest(req);

        if (accessToken != null) {
            // prefix 제거
            accessToken = substringToken(accessToken);

            // JWT 위변조, 만료 검증
            try {
                Jwts.parserBuilder().setSigningKey(JwtConfig.key).build()
                    .parseClaimsJws(accessToken);
                log.debug(&quot;토큰 검증 완료: {}&quot;, accessToken);

            } catch (SecurityException | MalformedJwtException | IllegalArgumentException |
                     UnsupportedJwtException e) {
                log.error(&quot;유효하지 않은 토큰입니다.&quot;);
                return null;

            } catch (ExpiredJwtException e) {
                log.error(&quot;만료된 액세스 토큰입니다. 액세스 토큰을 재발급합니다.&quot;);

                // 리프레시 토큰 검증
                String refreshToken = getRefreshTokenFromRequest(req);
                if (refreshToken != null &amp;&amp; isTokenValid(refreshToken)) {

                    String email = getEmailFromToken(accessToken);
                    String role = getAuthorityFromToken(accessToken);

                    // JWT 재발급
                    String newAccessToken = createAccessToken(email, role);
                    addJwtToCookie(newAccessToken, res);

                    accessToken = JwtUtil.substringToken(newAccessToken);
                } else {

                    // 리프레시 토큰이 만료되거나 존재하지 않습니다.
                    // 액세스 토큰 재발급 불가
                    // 재로그인 요청

                    // 액세스 토큰, 리프레시 토큰 쿠키 삭제
                    deleteAccessTokenFromCookie(res);
                    deleteRefreshTokenFromCookie(res);
                    return null;
                }
            }
        }
        return accessToken;
    }</code></pre>
<p>이 방법의 장점은 시큐리티 Jwt 인가 필터가 가벼워졌다는 것이다. 또한 같은 jwt 토큰 파싱을 중복적으로 실행하지 않는다.</p>
<p>이렇게 끝날 줄 알았으나... 토큰이 만료되면 정보를 꺼내올 수 없다는 원초적인 문제로 다른 부분에서 결국 새로운 토큰으로 승계해줄 때 문제가 생겼다.</p>
<hr>
<h1 id="결론">결론</h1>
<p>내가 구현하려 했던 것은 인가 과정에서</p>
<blockquote>
<p>액세스 토큰이 만료됨을 감지 -&gt; 리프레시 토큰의 유효성을 검증 -&gt; 만료된 액세스 토큰의 정보를 그대로 새로운 액세스 토큰으로 이관 -&gt; 이어서 사용자의 요청 처리</p>
</blockquote>
<p>이었다.</p>
<p>설계 당시 리프레시 토큰에는 권한(authority)정보를 넣지 않고  액세스 토큰에만 넣어 관리하는 것이 기능상 맞다고 생각해서 넣지 않았다.</p>
<p>위 구현은 액세스 토큰이 만료되었을 때, 액세스 토큰의 role을 지정해주려면 액세스 토큰의 authority 정보를 찾아 새로운 액세스 토큰으로 인계해주어야 한다.</p>
<p>하지만 문제는 JWT 토큰을 파싱할 때, 다음과 같은 코드를 사용하며
<code>Jwts.parserBuilder().setSigningKey(JwtConfig.key).build().parseClaimsJws(accessToken);</code>
이미 토큰이 만료된 상태이면 예외처리로 넘어간다.</p>
<p>그래서 이미 만료된 토큰은 저 방법으로 권한(<code>authority</code>)정보를 꺼내올 방법이 없다.</p>
<blockquote>
<p>결론적으로 권한 정보를 리프레시 토큰이 알고 있지 않는 이상 힘들 것 같아 보였으므로,
리프레시 토큰도 사용자 권한 정보(<code>authority</code>)를 갖게 변경하였다.</p>
</blockquote>
<blockquote>
<p>그리고, 액세스 토큰 만료를 감지하면  서버 내부 처리로 바로 토큰을 재생성하는 것이 아니라
사용자에게 만료됨을 알려주고 새 액세스 토큰을 발급할 수 있도록 API를 다시 요청하도록 변경하였다.
기능을 분리함으로서 유지보수와 추가 서비스 연결에 더 적합할 것이라 생각했다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기술면접] Backend TOP 30]]></title>
            <link>https://velog.io/@a-white-bit/Backend-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-TOP-30</link>
            <guid>https://velog.io/@a-white-bit/Backend-%EA%B8%B0%EC%88%A0%EB%A9%B4%EC%A0%91-TOP-30</guid>
            <pubDate>Wed, 17 Jul 2024 11:11:00 GMT</pubDate>
            <description><![CDATA[<h1 id="backend-기술면접-top30">Backend 기술면접 top30</h1>
<p>📢 기술 면접 빈도 높은 질문 30</p>
<blockquote>
<p>답변할 수 있을 정도의 길이(3~4줄)로 말하기
두괄식으로 전달하기</p>
</blockquote>
<h3 id="1-nosql과-rdbms의-특징과-차이점에-대해서-장-단점을-들어-설명해주세요">1. NoSQL과 RDBMS의 특징과 차이점에 대해서 장, 단점을 들어 설명해주세요.</h3>
<blockquote>
<ul>
<li>RDBMS : <code>관계형 데이터베이스</code> <code>Schema</code> <code>Table</code> <code>SQL</code></li>
<li>NoSQL : <code>대용량데이터</code> <code>비규격화</code> <code>Key,Value</code></li>
</ul>
</blockquote>
<p>NoSQL은 스키마가 없기 때문에 유연한 데이터 구조를 가지고있고, 수평적확장이 가능해 분산처리로 대규모 데이터 처리가 용이합니다.
RDBMS 보다 읽기와 쓰기 성능이 높으나, 수정에 걸리는 시간이 비교적 느리고 데이터의 중복이 가능하기때문에 중복된 데이터를 수정하는 경우 여러개의 컬렉션에서 데이터를 변경해야합니다.</p>
<p>RDBMS는 사용하려는 데이터의 구조가 명확할 경우 사용합니다.
외래키를 통해서 테이블간 Join이 가능하며, 데이터의 중복을 허용하지 않습니다.
트랜잭션의 ACID(원자성,일관성,고립성,지속성) 성질로 데이터의 무결성과 일관성을 보장합니다.
단점으로는 외래키로 테이블간 관계를 맺고있기때문에 원하는 데이터가 각테이블에 있을경우 Join문을 다수 사용하여 쿼리가 복잡해질 수 있습니다. 
또한 데이터의 구조가 명확 할 경우에 사용되기 때문에 데이터가 유연하지 않습니다.</p>
<h3 id="2-mvc-패턴에-대해서-설명해주세요">2. mvc 패턴에 대해서 설명해주세요.</h3>
<blockquote>
<h4 id="모델">모델</h4>
<p><code>model</code></p>
<ul>
<li>컨트롤러가 호출을 하면 DB와 연동하여 사용자의 입출력 데이터를 다루는 일과 같은 데이터와 &gt; 연관된 비즈니스 로직을 처리하는 역할</li>
<li>데이터 추출, 저장, 삭제, 업데이트 등의 역할을 수행</li>
</ul>
</blockquote>
<blockquote>
<h4 id="뷰">뷰</h4>
<p><code>view</code></p>
<ul>
<li>사용자와 상호작용을 하며 컨트롤러로부터 받은 모델의 결과값을 사용자에게 화면으로 출력</li>
</ul>
</blockquote>
<blockquote>
<h4 id="컨트롤러">컨트롤러</h4>
<p><code>controller</code></p>
<ul>
<li>Model과 View 사이를 이어주는 인터페이스 역할</li>
<li>Model이 데이터를 어떻게 처리할지 알려주는 역할</li>
</ul>
</blockquote>
<p>MVC 패턴은 사용자 인터페이스로부터 비즈니스 로직을 분리하기 위한 소프트웨어 디자인 패턴으로,  애플리케이션의 구성 요소를 Model-View-Controller로 분리하여 개발하는 방법입니다.</p>
<p>따라서 역할 분리로 인해 낮은 결합도로 모듈화할 수 있고, 코드의 재사용성이 높아지며,
        변경이 필요한 부분을 쉽게 파악할 수 있기 때문에 서비스를 유지보수 비용을 줄일 수 있습니다.</p>
<p>Model
데이터를 관리하는 역할을 합니다. 컨트롤러가 호출을 하면 DB와 연동하여 데이터 검색,  저장, 삭제 업데이트와 같은 비즈니스 로직을 처리하는 역할을 합니다.</p>
<p>View
view는 사용자 인터페이스로, 컨트롤러가 모델로부터 데이터를 요청하면 뷰는 해당 데이터를 가져와서 사용자에게 화면에 출력합니다.</p>
<p>Controller
컨트롤러는 독립적인 모델과 뷰 사이를 이어주는 인터페이스 역할을 합니다.
뷰에서 전달한 이벤트에 따라 모델이 애플리케이션 상태를 관리할 수 있게 해주며,
모델이 가진 데이터를 뷰에게 전달해서 화면을 만듭니다.</p>
<p>정리하면 모델은 데이터를 처리하고 뷰는 데이터를 사용자에게 제공하며 컨트롤러는 둘 사이의 상호 작용을 관리하여 올바른 데이터를 가져와 적절하게 표시하도록 합니다.</p>
<p>MVC 패턴을 사용하여 관심사 분리로 인해 애플리케이션은 더욱 모듈화되고, 유지 관리 및 확장 가능해집니다.</p>
<h3 id="3-rdbms의-정규화에-대해-설명해주세요">3. RDBMS의 정규화에 대해 설명해주세요.</h3>
<blockquote>
<p><code>중복데이터</code> <code>무결성</code></p>
</blockquote>
<p>데이터베이스를 설계할 때 구조를 효율적으로 만들기 위해 중복된 데이터를 최소화하고 무결성을 유지하며 이상현상을 방지할 수 있도록 관계형 테이블을 분해하는 과정입니다. 정규화는 각 단계가 있고 각각 특정한 조건을 만족하는 형태여야 합니다. 제1정규형, 2정규형, 3정규형, 보이스코드 정규형, 4정규형, 5정규형 순서로 분해합니다.
하지만 정규화 단계를 넘어갈 수록 테이블이 많아져 성능 저하가 일어날 수 있으므로 적절한 단계에서 멈추는 것을 선택해야 합니다.</p>
<p>각 정규형 조건 설명:</p>
<ul>
<li>제 1정규형은 테이블의 모든 열이 원자값을 가져야 합니다. 원자값이란 더 이상 쪼갤 수 없는 데이터 값을 말합니다. 한 인스턴스의 필드 안에 데이터가 2개 이상 들어가면 안됩니다.</li>
<li>제 2정규형은 부분 함수 종속성이 없어야 합니다. 기본 키의 부분 집합이 결정자가 되는 것 </li>
<li>제 3정규형은 이 기본 키가 아닌 모든 속성이 기본 키에 대해 이행적 종속성을 가지지 않아야 합니다.</li>
<li>BCNF는 후보키가 아닌 결정자에 의해 발생하는 종속성을 없애야 합니다.</li>
<li>제 4정규형은 다치 종속성을 제거해야 합니다.</li>
<li>제 5정규형은 조인 종속성을 제거해야 합니다.</li>
</ul>
<p>이상현상:
비정규화된 테이블 구조에서 발생할 수 있는 문제를 말합니다. 데이터의 일관성과 무결성을 해칠 수 있기 때문에 문제를 해결해야 합니다. 삽입 이상, 삭제 이상, 수정 또는 갱신 이상이 있습니다.
삽입 이상 - 어떤 데이터를 디비에 삽입할 때 필요없는 다른 데이터도 같이 넣어야 하는 경우입니다.
삭제 이상 - 어떤 데이터를 삭제하면 필요없는 다른 데이터도 함께 삭제해야 하는 경우입니다.
갱신 이상 - 중복 데이터가 여러 곳에 존재해서 한 곳을 수정하면 다른 곳도 수정해주지 않으면 일관성을 해치기 때문에 수정해야 되는 경우입니다.</p>
<p>다른 답변: 정규화란 관계형 데이터베이스의 설계에서
데이터를 중복 없이 효율적으로 저장하기 위한 일련의 과정을 말합니다.
총 5단계의 과정을 거쳐
데이터베이스의 구조가 불필요한 중복을 최소화하고 데이터의 일관성과 무결성을 유지할 수 있도록 도와줍니다. 이때 주의할 점이 있는데
Data의 무결성 보장해주거나 이상현상 등을 막아준다고 하지만 다수의 정규화는 성능 저하를 초래할 수 있습니다.</p>
<h3 id="4-primary-key-foreign-key에-대해-설명해주세요">4. Primary Key, Foreign Key에 대해 설명해주세요.</h3>
<blockquote>
<p><code>1:1</code> <code>1:N</code> <code>N:M</code></p>
<ul>
<li>PK : <code>not null</code> <code>unique</code></li>
<li>FK : <code>PK 참조</code></li>
</ul>
</blockquote>
<p>기본키와 외래키는 데이터의 무결성과 일관성을 유지하기 위해 존재합니다.
기본키는 테이블의 행을 고유식별하는 기능입니다. 유일한 값이며 NULL을 허용하지 않습니다.
외래키는 한 테이블이 다른 테이블을 참조하여 테이블 간의 관계를 정의하는 기능입니다. 참조하는 값이 반드시 존재하여 참조 무결성을 유지해야 합니다.</p>
<p>다른 답변: PK 유일한 값으로 공백을 가질 수 없고, 데이터를 식별하기 위한 값이다. 특징으로는 고유성을 가지고, null 값을 허용할 수 없다. unique 제약 조건을 가진다. FK 관계형 데이터베이스의 데이터 관계를 표현하기 위한 키로, 참조되는 테이블의 pk를 fk 라고 한다. fk는 무결성 제약 조건을 가져 참조 무결성을 유지한다, 삭제 및 갱신시 제약 조건을 가진다.</p>
<h3 id="5-http-메서드에-대해-설명해주세요">5. HTTP 메서드에 대해 설명해주세요.</h3>
<blockquote>
<ul>
<li>주요 메서드 : <code>GET</code> <code>POST</code> <code>PUT</code> <code>PATCH</code> <code>DELETE</code></li>
<li>기타 메서드 : <code>HEAD</code> <code>OPTIONS</code> <code>CONNECT</code> <code>TRACE</code></li>
</ul>
</blockquote>
<p>HTTP 메서드란 클라이언트와 서버 사이에 이루어지는 요청(Request)과 응답(Response) 데이터를 전송하는 방식입니다.
HTTP 메서드를 통해 서버가 수행해야할 동작을 지정하면 url은 리소스만 식별하면 되기 때문에 리소스와 동작을 분리할 수 있습니다.</p>
<p>HTTP의 주요 메서드는 GET, POST, PUT, DELETE, PATCH가 있고,
이 외에도 HEAD, OPTIONS, TRACE, CONNECT가 있습니다.</p>
<p>HTTP 메서드를 사용하는 이유는 웹 애플리케이션에서 클라이언트와 서버 간의 통신을 명확하고 효율적으로 관리하기 위함입니다. 각 HTTP 메서드는 특정한 작업을 수행하도록 정의되어 있으며, 이를 통해 서버와 클라이언트 간의 요청 및 응답 처리를 구조화하고 표준화할 수 있습니다. HTTP 메서드를 사용하는 주요 이유는 다음과 같습니다:</p>
<p>HTTP 메소드의 종류는 총 9가지가 있습니다.</p>
<ul>
<li><p><strong>GET :</strong> 리소스 조회</p>
<pre><code>          - 주로 검색, 게시판 목록에서 검색어로 이용하며, 정상적인 응답이 완료되면 200 staus 코드를 반환합니다.
          - 동적 데이터 조회 하는 경우 쿼리 파라미터 사용해서 데이터를 전달합니다.</code></pre></li>
<li><p><strong>POST:</strong>  요청 데이터 처리, 주로 등록 및 생성 요청에 사용하는 메서드 입니다.</p>
</li>
<li><p>메시지 바디(body)를 통해 서버로 요청 데이터 전달하면 서버는 요청 데이터를 처리하여응답시 201 HTTP 응답을 반환합니다.</p>
</li>
<li><p><strong>PUT :</strong> 리소스를 수정하는 메서드로 데이터가 존재하면 대체하고 존재하지 않으면 생성합니다.</p>
</li>
<li><p>하지만 일부 리소스만 변경할 경우 기존 데이터가 완전히 대체되어 기존 데이터가 삭제되기 때문에 이때는 PATCH 메소드를 이용해야 합니다.</p>
</li>
<li><p><strong>PATCH :</strong> 리소스 일부 부분을 변경하는 메소드 입니다. (PUT이 전체 변경, PATCH는 일부 변경)</p>
</li>
<li><p><strong>DELETE :</strong> 리소스 삭제하는 메서드</p>
</li>
<li><p><strong>HEAD :</strong> GET과 동일하지만 메시지 부분(body 부분)을 제외하고, 상태 줄과 헤더만 반환</p>
</li>
<li><p><strong>OPTIONS :</strong> 예비 요청(Preflight)에 사용되는 HTTP 메소드로 본 요청을 하기 전에 안전한지 미리 검사하는 메서드 입니다.</p>
</li>
<li><p>따라서 서버의 지원 가능한 HTTP 메서드와 출처를 응답 받아 <a href="https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F">CORS 정책Visit Website</a>을 검사하기 위한 요청입니다.</p>
</li>
<li><p><strong>CONNECT :</strong> 대상 자원으로 식별되는 서버에 대한 연결 요청</p>
</li>
<li><p><strong>TRACE :</strong> 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행</p>
</li>
<li><p>서버에 도달 했을 때의 최종 패킷의 요청 패킷 내용을 응답 받을 수 있다.</p>
</li>
<li><p>클라이언트의 요청 패킷이 방화벽, Proxy 서버, Gateway 등을 거치면서 패킷의 변조가 일어날수 있는데, 그래서 TRACE 메서드를 통해 요청했던 패킷 내용과 응답 받은 요청 패킷 내용을 비교하여 변조 유무를 확인 할 수 있다</p>
</li>
</ul>
<h3 id="6-corscross-origin-resource-sharing에-대해-설명해주세요">6. CORS(Cross Origin Resource Sharing)에 대해 설명해주세요.</h3>
<blockquote>
<p><code>SOP</code> <code>CORS 필요 이유</code> <code>CORS 정의</code></p>
</blockquote>
<p>CORS는 웹 브라우저 보안 기능입니다.</p>
<p>기본적으로 브라우저는 보안상의 이유로 동일 출처 정책(Same-Origin Policy)을 따르는데,
 이것은 한 웹 페이지가 다른 출처의 리소스에 접근하는 것을 제한하는 것입니다.
CORS(Cross-Origin Resource Sharing)는 웹 브라우저가 웹 페이지의 리소스를 다른 도메인에서 요청할 수 있도록 허용하여 제한을 완화시킬 수 있는 방법입니다.</p>
<h3 id="7-브라우저의-작동방식에-대해서-설명해주세요">7. 브라우저의 작동방식에 대해서 설명해주세요.</h3>
<blockquote>
<p><code>단계별로 상세한 프로세스를 나눠서 설명</code></p>
</blockquote>
<ol>
<li>사용자가 브라우저에 URL을 입력하거나 하이퍼링크를 클릭합니다.</li>
<li>브라우저가 URL을 해석해서 호스트 IP 주소를 DNS에 요청합니다.</li>
<li>브라우저가 호스트 IP 주소로 웹 서버에 HTTP 또는 HTTPS 요청을 보냅니다.</li>
<li>웹 서버는 그 요청을 처리하고 응답을 반환합니다.</li>
<li>브라우저는 응답을 받아 HTML 문서를 파싱해서 DOM(Document Object Model)을 생성합니다. CSS 내용에 따라 렌더링하고 JavaScript는 실행되어 브라우저 동작을 제어합니다.</li>
<li>최종 렌더링된 페이지가 화면에 출력되고 사용자가 상호작용 할 수 있습니다.</li>
</ol>
<p>다른 답변: 사용자가 웹 브라우저를 열고 URL을 입력하거나 하이퍼링크를 클릭합니다.
브라우저는 URL에서 호스트 이름을 추출하고 DNS(Domain Name System) 서버를 통해 해당 호스트의 IP 주소를 가져옵니다.
브라우저는 서버에 HTTP 요청을 보냅니다. 이 요청에는 메서드(GET, POST 등)와 함께 요청하는 리소스의 경로 등이 포함됩니다.
서버는 요청을 받고 처리한 후에 HTTP 응답을 생성합니다. 응답에는 상태 코드, 헤더 및 리소스(HTML 문서, 이미지 등)가 포함될 수 있습니다.
브라우저는 응답을 받아서 렌더링합니다. HTML은 DOM(Document Object Model)으로 파싱되고, CSS는 스타일 규칙에 따라 렌더링되고, JavaScript는 실행됩니다.
렌더링된 페이지가 사용자에게 표시됩니다. 사용자가 페이지와 상호 작용하면서 브라우저는 추가적인 요청을 생성하고 응답을 처리합니다.</p>
<h3 id="8-쿠키-세션의-개념과-차이를-설명해보세요">8. 쿠키, 세션의 개념과 차이를 설명해보세요</h3>
<blockquote>
<p><code>무상태(stateless)</code> <code>이 기술이 왜? 생겨났는지</code></p>
</blockquote>
<p>Http 프로토콜은 이전 요청의 결과나 상태에 독립적입니다. 이 특징을 &#39;무연결성, 무상태(stateless)&#39;라고 합니다.
웹 서비스를 제공할 때 사용자의 상태를 유지하고 추적해야 할 때 사용할 수 있는 기술이 &#39;쿠키&#39;와 &#39;세션&#39;입니다.</p>
<p>둘 다 사용자의 정보를 저장하고 관리하는 역할을 한다는 공통점이 있습니다.
쿠키는 사용자의 브라우저에 저장되고 같은 서버로의 요청을 보낼 때마다 브라우저가 해당 쿠키를 함께 보냅니다. 쿠키는 만료기한을 설정할 수 있다는 특징이 있습니다. 하지만 300개 정도 저장할 수 있고 4KB 정도까지 저장할 수 있는 한계가 있습니다.
세션은 서버측에 사용자의 상태를 저장하는 데이터입니다. 서버가 세션 ID를 클라이언트에게 알려주면 클라이언트는 요청 시에 세션 ID를 전달해야 합니다. 세션 데이터는 브라우저를 닫으면 종료됩니다. 세션 데이터는 서버에 저장되므로 쿠키보다는 보안도가 높지만 서버에 좀 더 부하를 줄 수 있습니다.</p>
<h4 id="쿠키와-세션">쿠키와 세션</h4>
<p>목적 : 비연결성과 무상태성을 보완하기 위해서 사용한다.</p>
<p>쿠키</p>
<ul>
<li>클라이언트 로컬에 저장되는 키와 값이 들어있는 데이터 이다.</li>
<li>유효기간을 명시할 수 있다.</li>
<li>약 300개 정도 저장할 수 있고, 4KB 정도 까지 저장할 수 있다.</li>
</ul>
<p>세션</p>
<ul>
<li>쿠키에 기반히지만 세션은 서버측에서 관리한다.</li>
<li>서버에서는 클라이언트의 정보를 저장하고 구분하기 위해서 세션 키를 발급하고, 서버 저장소에서 Map의 형태로 관리한다.</li>
<li>클라이언트와의 연결이 종료되는 시점까지 저장한다. 요청이 일정 시간동안 없다면 제거한다.</li>
<li>서버에 저장되다 보니 서버에 부하를 줄 수 있다.</li>
</ul>
<p>둘의 공통은 쿠키를 기반한다는 것이고, 차이점은 저장되는 위치에 있다.</p>
<h3 id="9-tcpudp에-대해서-설명해주세요">9. TCP/UDP에 대해서 설명해주세요.</h3>
<blockquote>
<p><code>TCP와 UDP의 신뢰성 차이가 어떤 프로세스 떄문에 발생하는지</code> <code>3-way handshaking</code></p>
</blockquote>
<p>TCP와 UDP는 네트워크 전송 계층에서 사용되는 인터넷 통신 규약입니다.
TCP는 데이터를 보낼 때 송신 컴퓨터와 수신 컴퓨터의 연결을 먼저 설정합니다. 연결 여부를 확인하기 때문에 송신여부에 대한 신뢰성이 있습니다. TCP는 데이터를 보낼 때 바이트 스트림 형태로 된 패킷 단위로 보내기 때문에 데이터의 순서를 판단할 수 있습니다. 연결 여부를 확인하려면 3-way handshake 연결 과정이 필요합니다. 이것은 송신자와 수신자 사이에 SYN 패킷, SYN-ACK 패킷, ACK 패킷 이렇게 세 번의 통신이 있어 3-way handshake라 불립니다.
TCP는 전송 데이터를 확인하여 잘못된 순서나 데이터가 전송될 경우 재전송하여 복구할 수 있고 속도를 조절할 수 있습니다.</p>
<p>UDP는 TCP와 상반되게 연결 과정이 없어 보다 빠른 데이터 전송이 가능하다는 장점이 있습니다.
UDP는 데이터를 보낼 때 각각 순서와 관련없는 개별적인 데이터를 데이터그램이라 불리기도 하는 패킷 단위로 전송합니다. 각 패킷의 오류 검출을 위한 체크섬 기능이 있지만 복구할 수는 없어 같은 패킷의 재전송이 보장되지 않습니다.</p>
<p>TCP는 신뢰성이 중요한 응용 프로그램에서, UDP는 실시간성과 속도가 중요한 응용 프로그램에서 주로 사용됩니다.</p>
<hr>
<p>다른 답변:
TCP와 UDP는 웹 상에서 데이터를 전송하기 위한 통신규약입니다.</p>
<p>Transmission Control Protocol</p>
<p>인터넷 상에서 데이터를 메세지의 형태로 보내기 위해 사용하는 프로토콜입니다. 3 way handshaking과정을 통해 연결을 설정하며 데이터가 온전하게 도착하도록 보장하기 때문에 신뢰성이 높은 프로토콜입니다.</p>
<p>User Datagram Protocol</p>
<p>UDP는 사용자 데이터그램 프로토콜의 약자이며 비연결 지향적 프로토콜입니다.  TCP와 다르게 사전연결을 하지않아 데이터는 소실될 가능성이 존재하고 순서를 보장하지 않으며 데이터를 보증하지 않지 빠르다는 장점이 존재합니다.  </p>
<h3 id="10-http-https-차이점에-대해-설명해주세요">10. http, https 차이점에 대해 설명해주세요</h3>
<blockquote>
<p><code>HTTP/HTTPS의 정의와 차이점</code> <code>SSL 인증서를 통해서 데이터 조작여부 판단</code></p>
</blockquote>
<p>HTTP와 HTTPS는 모두 인터넷의 데이터 전송 프로토콜입니다. 두 프로토콜은 보안 측면의 차이가 있습니다.</p>
<p>HTTP는 웹 서버-클라이언트 간에 데이터를 암호화하지 않고 전송합니다. 기본적으로 80번 포트를 사용하고 있습니다. 암호화 하지 않아 보안에 취약할 수 있습니다.</p>
<p>HTTPS는 HTTP 형태의 데이터가 암호화되어 전송됩니다. 기본적으로 443번 포트를 사용합니다.
암호화를 위해 핸드셰이크 과정을 통해서 SSL 또는 TLS 인증서를 추가하여 서버의 신원을 보장합니다. 신원을 확인할 때  또한 데이터의 무결성과 기밀성을 보장합니다.
또 HTTPS는 구글 등의 검색 엔진이 HTTPS를 사용하는 웹사이트를 더 높은 순위에 노출시키는 경향이 있고 (SEO, 검색 엔진 최적화 방법/전략들을 말합니다), 최신 대부분 브라우저는 웹사이트에 대해 HTTPS 에 대한 지원을 권장하고 있습니다.</p>
<p>SSL/TLS 는 무엇인가요?
SSL: Secure Sockets Layer, 넷스케이프가 개발한 인터넷 데이터 암호화 프로토콜. 1996년에 출시된 3.0 버전이 많은 보안 취약점이 있어 대부분 TLS를 사용합니다.
TLS: Transport Layer Security, 2018년에 발표된 1.3 버전이 최신버전이고, SSL의 취약점을 보완한 암호화 프로토콜입니다.</p>
<hr>
<p>다른 답변:
HTTP와 HTTPS는 인터넷을(네트워크) 통해 데이터를 전송하는 데 사용되는 두 가지 프로토콜입니다.</p>
<p>HTTP는 HyperText Transfer Protocol의 약자로, 웹 브라우저와 웹 서버 사이에서 웹 페이지를 전송하는 데 사용 되는 통신 프로토콜입니다.
하지만 HTTP는 기본적으로 평문으로 데이터를 전송하기 때문에 데이터가 암호화되지 않고 누구나 읽을 수 있어서, 보안에 취약합니다.
이러한 특징 때문에 보안이 필요하지 않은 정적인 웹 페이지를 전송하는 데 적합합니다.</p>
<p>HTTPS(Hypertext Transfer Protocol Secure)는 HTTP의 보안 버전으로 보안측면에서 더욱 강화된 프로토콜입니다.</p>
<p>HTTPS는 SSL(Secure Socket Layer) 또는 TLS ( Transport Layer Security)프로토콜을 사용해서 데이터를 암호화해서 전송합니다.
이렇게 하면 전송되는 데이터가 보호되어서 중간에서 데이터를 엿보거나 조작하는 것을 방지할 수 있습니다.</p>
<p>즉, HTTP는 평문으로 데이터를 전송하는 프로토콜로 보안이 필요하지 않은 경우에 사용 되고,
HTTPS는 데이터를 암호화하여 안전하게 전송하는 프로토콜로 보안이 필요한 웹 사이트에서 사용됩니다.</p>
<p>HTTP와 HTTPS. 두 프로토콜의 큰 차이점은 SSL/TLS를 사용하는지 여부입니다.
즉, HTTPS는 SSL/TLS를 통해 데이터를 암호화하지만, HTTP는 데이터를 평문으로 전송합니다.</p>
<h3 id="11-di-ioc에-대해-설명해주세요">11. DI, IoC에 대해 설명해주세요.</h3>
<p>DI는 의존성 주입이라고 하는데, 이것은 객체지향 프로그래밍에서
한 객체 A가 다른 객체B를 필요로 할 때, 즉 A가 B에게 의존도가 있을 때, A 내부에서 직접 B를 생성하는 것이 아니고 A의 외부에서 미리 생성된 B를 받는 것을 의존성 주입이라고 합니다.
생성자를 통한 주입방법, Setter 메서드를 통한 주입방법, 직접적으로 멤버 변수에 주입하는 방법이 있습니다.</p>
<p>IoC는 제어의 역전이라고 하고, 프로그래밍을 의존성 주입을 수행함으로서 프로그램 제어권을 프레임워크나 컨테이너에게 맡기는 것입니다. 개발자가 주입 객체를 작성하고 지정해주면 프로그램 실행시 객체를 외부에서 생성하고 주입하는 행동은 프레임워크가 하게 됩니다. 대표적으로 스프링 프레임워크가 있습니다.</p>
<p>DI를 사용함으로써 IoC를 실현하게 되므로 같은 장단점을 가지게 됩니다.
의존성 주입을 하면 객체 간의 결합도를 낮출 수 있으며 코드 재사용성이 높아집니다. 또 테스트할 때 용이하며 코드가 모듈화되어 유지보수성이 증가합니다.
단점으로는 초기의 설정이 복잡하고 처음 개념을 접하면 이해하는 데 러닝커브가 존재합니다. 제어 흐름이 프레임워크가 관리하기 때문에 디버깅에 어려움이 있습니다. 그리고 프로젝트가 프레임워크에 대해 의존성이 증가할 수 있습니다.</p>
<hr>
<p>다른 답변:
IOC (Inversion of Control - 제어의 역전)
IOC는 개발자가 객체의 생성과 의존성 관리를 직접 수행하지 않고, 이를 프레임워크나 컨테이너에 위임하는 디자인 패턴입니다. 이전에는 개발자가 객체를 직접 생성하고 의존성을 관리했지만, IOC를 사용하면 프레임워크가 이러한 작업을 대신합니다. 이는 제어의 흐름을 역전시키는데, 즉 객체의 생성과 의존성 주입이 프레임워크에 의해 결정됩니다. 이를 통해 코드의 유연성을 높이고 재사용성을 향상시킬 수 있습니다.</p>
<p>DI (Dependency Injection - 의존성 주입)
DI는 IOC의 한 형태로, 객체가 다른 객체에 의존할 때 이 의존성을 외부에서 주입하는 디자인 패턴입니다. 주로 생성자, 세터 메서드, 인터페이스 등을 통해 의존성이 주입됩니다. 이를 통해 객체 간의 결합도를 낮추고 유연성을 향상시킵니다. 또한 DI를 통해 객체의 재사용성과 테스트 용이성이 증가하며, 코드의 변경을 최소화할 수 있습니다.</p>
<p>IOC/DI의 주요 이점
유연성: 객체 간의 결합도를 낮추고 코드를 더 유연하게 만듭니다.
재사용성: 의존성을 주입하여 객체를 더 재사용 가능하게 만듭니다.
테스트 용이성: 의존성을 외부에서 주입하므로 모의 객체(mock object)를 사용하여 테스트하기 용이합니다.
유지보수성: 변경이 발생할 때 해당 객체만 수정하면 되므로 코드의 유지보수가 용이합니다.</p>
<h3 id="q-싱글톤-패턴추가질문">Q. 싱글톤 패턴(추가질문)</h3>
<p>싱글톤 패턴은 디자인 패턴 중 하나로, 어떤 클래스가 최초 한 번만 메모리를 할당하고 그 메모리에 객체를 만들어 사용하는 패턴입니다. 이 패턴을 사용하면 애플리케이션 전체에서 해당 클래스의 인스턴스가 하나만 존재하도록 보장할 수 있습니다.</p>
<h3 id="12-객체지향-프로그래밍이란-무엇이고-어떻게-활용할-수-있나요">12. 객체지향 프로그래밍이란 무엇이고 어떻게 활용할 수 있나요?</h3>
<p>객체지향 프로그래밍은 프로그래밍 패러다임 중 하나이고 이 방법은
프로그램을 여러개의 객체로 구성하여 작성합니다. 객체는 데이터를 갖는 필드와 행위를 갖는 메서드로 구성되어 있습니다. 객체지향을 사용하면 복잡한 시스템일 수록 각 객체가 독립적이기 때문에 필요한 부분만 개발, 유지보수할 수 있게 되어 전통 프로그래밍 방식인 절차지향보다 유리하고 개발 과정이 단순화되어서 생산성을 향상시킬 수 있습니다.
객체지향은 상속과 다형성을 통해 코드의 재사용성을 높이고, 캡슐화라는 특성이 있는데 객체의 필드와 메서드를 외부로부터 숨겨서 객체의 인터페이스를 통해서만 접근할 수 있습니다. 이렇게 하면 데이터의 무결성과 보안을 높일 수 있습니다. 추상화 </p>
<hr>
<p>다른답변:
객체지향 프로그래밍이란 실제 사물을 모델링하여 소프트웨어를 개발하는 방법으로서 클래스와 객체, 상속, 다형성, 캡슐화의 개념을 가지며 상속을 통해 코드를 재사용하고, 다형성을 통해 유연하고 확장 가능한 코드를 작성할수 있고, 캡슐화를 통해 데이터를 보호할 수 있습니다. 객체가 독립적으로 작동하여 프로그램의 복잡성이 줄어들어 대규모 프로젝트를 효과적으로 관리 할 수있습니다. 이러한 개념의 이점들로 객체지향 프로그래밍은 코드의 유지 보수성과 확장성을 향상시키고, 개발 과정을 단순화하여 생산성을 향상시킵니다.</p>
<h3 id="13-대용량-트래픽-발생-시-어떻게-대응해야-하나요">13. 대용량 트래픽 발생 시 어떻게 대응해야 하나요?</h3>
<p>대용량 트래픽은 발생 가능성을 염두에 두고 사전에 여러 방책을 마련해두는 것이 좋습니다.
트래픽 패턴을 분석해서 이벤트, 프로모션 등으로 트래픽 증가를 사전에 예측해야 합니다.</p>
<p>스케일 아웃, 로드 밸런싱, 캐싱, db 최적화, 애플리케이션 최적화 모두 고려해보아야 합니다.</p>
<p>스케일 아웃: 서버를 늘려 트래픽 부하를 분산시킵니다. 로드 밸런싱을 통해 여러 서버에 고르게 분산시켜야 합니다.
캐싱: 정적 콘텐츠는 CDN(Content Delivery Network) 서비스를 통해 전 세계에 분산 배포하여 사용자에게 더 빠르게 전달합니다. 캐시를 통해 데이터베이스 및 서버에 가해지는 부하를 줄입니다.
db 최적화: 데이터베이스 쿼리를 최적화하고, 인덱스를 잘 관리하며, 필요에 따라 읽기 전용 복제본(Read Replica)을 사용합니다.
코드를 최적화하고, 병목 현상을 파악하여 해결합니다. 비동기 처리 및 큐를 이용하여 요청을 처리합니다.</p>
<p>그리고 실시간 모니터링을 하여 대용량 트래픽이 발생했을 시 즉시 대응할 수 있도록 합니다.
대용량 트래픽이 발생했을 때 클라우드 서비스를 사용 중이라면 임시로 스케일 아웃을 할 수도 있습니다. 또, 트래픽에 제한을 거는 방법이나 대기 페이지를 제공하는 방법으로 트래픽을 제어할 수 있습니다. 대용량 트래픽으로 인해 서버 장애가 발생하면 서비스가 유연하게 이어질 수 있도록 백업 서버를 두고 전환하는 방법을 사용할 수 있습니다. DR(Disaster Recovery) 복구 방법을 미리 만들어 두고 데이터 유실을 방지해야 합니다.</p>
<hr>
<p>다른답변:</p>
<ol>
<li>스케일 아웃(Scaling Out): 서버 자원을 추가하여 트래픽 부하를 분산시킵니다. 이는 로드 밸런서를 통해 효과적으로 수행할 수 있습니다.</li>
<li>캐싱 사용하기: 정적 콘텐츠를 캐싱하여 서버의 부하를 줄이고 응답 속도를 향상시킵니다.</li>
<li>코드 최적화: 코드를 개선하여 효율적으로 실행되도록 만듭니다. 비효율적인 코드는 서버 부하를 높일 수 있습니다.</li>
<li>데이터베이스 최적화: 데이터베이스 쿼리를 최적화하여 더 빠르고 효율적으로 실행되도록 합니다. 인덱스를 추가하거나 쿼리를 재구성하는 등의 방법을 사용할 수 있습니다.</li>
<li>모니터링 및 스케일 인(Scaling In): 실시간 모니터링을 통해 서버 상태를 지속적으로 확인하고 필요에 따라 서버를 축소시켜 자원을 효율적으로 사용합니다.</li>
<li>클라우드 서비스 활용: 클라우드 기반 인프라를 사용하여 필요에 따라 서버를 신속하게 추가하거나 축소할 수 있습니다.</li>
<li>부하 테스트: 시스템이 어느 정도의 트래픽을 견딜 수 있는지 이해하기 위해 정기적인 부하 테스트를 수행합니다.</li>
<li>이벤트 드리븐 아키텍처 채택: 이벤트 기반 아키텍처를 도입하여 스케일링을 자동화하고 트래픽에 따라 서비스를 유연하게 조정합니다.</li>
<li>백업과 복구 전략 수립: 잠재적인 문제에 대비하여 백업과 복구 전략을 수립하여 데이터 유실을 방지합니다.</li>
</ol>
<h3 id="14-orm을-사용하면서-쿼리가-복잡해지는-경우에는-어떻게-해결하는게-좋을까요">14. ORM을 사용하면서 쿼리가 복잡해지는 경우에는 어떻게 해결하는게 좋을까요?</h3>
<p>쿼리 성능 문제를 해결하기 위해 디버깅이나 로그 분석을 통해서 병목 지점을 발견하여 개선할 방법을 찾을 수 있습니다.</p>
<ol>
<li>쿼리 최적화 기능을 사용하여 필요한 컬럼만 선택하도록 쿼리를 제한할 수 있고, JOIN 시에 불필요한 데이터를 선택하지 않도록합니다.</li>
<li>쿼리 메서드를 체이닝과 서브쿼리를 사용하여 복잡한 쿼리를 단순화할 수 있습니다.</li>
<li>ORM 제공 기능을 사용하지 않고 복잡한 부분을 Raw 쿼리를 사용하여 직접 처리할 수 있습니다.</li>
<li>DB 뷰를 사용하여 복잡한 쿼리를 단순화할 수 있습니다. 뷰는 테이블처럼 사용가능합니다.</li>
<li>데이터베이스 테이블의 적절한 인덱스를 설정하도록 해서 쿼리 성능을 최적화할 수 있습니다.</li>
</ol>
<h3 id="15-get-post의-개념과-함께-데이터-흐름에-대해서-설명해주세요">15. GET, POST의 개념과 함께 데이터 흐름에 대해서 설명해주세요.</h3>
<p>HTTP(Hypertext Transfer Protocol)는 웹 상의 클라이언트와 서버 간의 통신을 위한 프로토콜입니다. HTTP 메서드는 클라이언트가 서버에 요청을 보내는 방법을 정의하며 그 중에서도 GET과 POST가 가장 많이 사용되는 메서드입니다.</p>
<p>GET: 서버에게 웹 데이터를 열람할 수 있도록 요청할 때 사용됩니다.
예를 들어 어떤 서버의 index 페이지를 보고 싶을 때 URL을 통해 브라우저 같은 HTTP 클라이언트 애플리케이션이 GET 메서드의 HTTP 프로토콜을 생성하고 이것을 서버에 보냅니다.
서버는 index 페이지 연관 리소스를 찾고 HTTP 프로토콜에 담아 응답합니다.
클라이언트 애플리케이션은 응답을 해석하고 처리합니다. 예를 들어 브라우저는 HTML 페이지를 렌더링 합니다.
요청 데이터를 함께 보내려면 URL에 담아 보낼 수 밖에 없고 이 때문에 보안에 취약하다는 단점이 있습니다. get 메서드는 길이 제한이 있으며 브라우저에 캐시될 수 있어 post보다 빠르고 안정적으로 통신할 수 있다는 장점이 있습니다. 또한 같은 GET 요청에 대하여 멱등성이 보장됩니다.</p>
<p>POST: 주로 서버에게 리소스를 변경을 요청할 때 사용되며 데이터를 함께 전송합니다.
GET과 마찬가지로 HTTP 클라이언트 애플리케이션이 POST 로 프로토콜을 생성하고 변경 데이터를 함께 서버에 보냅니다. 서버는 이것을 받아 변경 데이터를 요청에 맞게 처리하고 결과를 다시 HTTP 프로토콜로 응답해줍니다. 그리고 클라이언트 애플리케이션은 응답을 해석하고 처리합니다.
요청 데이터를 http 요청 바디에 넣을 수 있어 GET보다는 안전하고 길이 제한이 없어 대용량 데이터를 주고 받을 수 있다는 장점이 있습니다. get보다는 통신 부하가 크다는 단점이 있습니다.</p>
<ul>
<li><p>POST가 PUT/PATCH 대신 변경 요청하는 경우?
POST는 요청 데이터를 처리하는 과정은 서버가 결정하므로 사실 모든 기능을 수행가능합니다.
기본적으로 데이터 변경 요청 메서드는 PUT/PATCH를 사용하는 것이 바람직하지만 환경에 따라 사용할 수 없는 경우도 있습니다. 예를 들어 HTML의 form은 GET과 POST 메서드만 지원합니다. 이처럼 일부 라이브러리, 프레임워크 등이 지원을 제한하는 경우 POST로 대체하여 사용할 수 있고, 보안 설정때문에 PUT/PATCH 요청이 차단되고 있는 환경일 경우 POST 요청을 사용하기도 한다.
하지만 이는 RESTful 규칙을 조금 벗어나므로 엄격한 RESTful API를 설계할 때에는 사용하지 않는 방법입니다.</p>
</li>
<li><p>좋은 API URL 설계
<img src="https://velog.velcdn.com/images/a-white-bit/post/9a6ea5fb-7695-42a6-85c7-5ab839993083/image.png" alt="">
출처: <a href="https://rachel0115.tistory.com/entry/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A0%95%EB%A6%AC-GET-POST-PUT-PATCH-DELETE">https://rachel0115.tistory.com/entry/HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%A0%95%EB%A6%AC-GET-POST-PUT-PATCH-DELETE</a></p>
</li>
</ul>
<h3 id="16-osi-7계층에-대해-아는대로-설명해주세요">16. OSI 7계층에 대해 아는대로 설명해주세요.</h3>
<p>네트워크 통신을 7계층으로 나누고 각 계층이 특정 기능을 수행하도록 설계한 모델입니다.
통신 데이터를 계층으로 구조화하면 계층이 독립적으로 표현되어서 각 계층에 표준화된 프로토콜 기술들을 구별할 수 있다는 장점이 있습니다.</p>
<p>각 계층은 물리-데이터링크-네트워크-전송-세션-표현-응용계층으로 되어있습니다.</p>
<ol>
<li>물리 계층 - 물리적 장치로 컴퓨터 간에 비트 스트림을 전송합니다.</li>
<li>데이터 링크 - 물리 계층에서 데이터를 전송할 수 있도록 프레임 단위로 구성하여 오류 검출과 흐름을 제어하고 MAC 주소로 장치를 식별하는 기능을 합니다.</li>
<li>네트워크 - 논리 주소인 IP 주소를 통해 데이터 패킷을 전달합니다.</li>
<li>전송 - TCP/UDP 프로토콜을 통해 데이터 패킷을 세그멘테이션으로 나눠 전달하여 데이터 전송의 순서와 무결성, 신뢰성을 보장합니다.</li>
<li>세션 - 통신 연결을 유지하고 동기화하고 연결을 끊는 등 논리적 연결을 관리하는 기능을 합니다.</li>
<li>표현 - 데이터를 응용 계층에서 사용되는 형식으로 인코딩/디코딩/압축변환 등을 처리합니다.</li>
<li>응용 - 사용자와 네트워크 간의 인터페이스를 제공합니다. 네트워크와 응용 프로그램 간에 HTTP, FTP, SMTP 등의 프로토콜을 사용하여 데이터를 전달합니다.</li>
</ol>
<h3 id="17-세션-기반-인증과-토큰-기반-인증의-차이에-대해-설명해주세요">17. 세션 기반 인증과 토큰 기반 인증의 차이에 대해 설명해주세요.</h3>
<p>공통점: 웹 애플리케이션에서 사용자 인증을 처리함</p>
<p>세션 기반 인증: 사용자에 대한 연결 상태를 유지할 수 있는 &#39;세션&#39;을 서버에서 관리하는 방법입니다.
데이터를 서버에 저장하고 사용자는 자신의 세션 ID만 전달하면 인증할 수 있어 적은 통신 비용으로 인증할 수 있습니다. 민감한 데이터를 서버에 저장하므로 토큰으로 통신하는 것보다 안전합니다.
하지만 그만큼 서버의 저장 공간 확보와 여러 서버 환경(로드 밸런싱)에서 각 서버가 세션을 공유해야 하는 과정이 필요합니다.</p>
<p>토큰 기반 인증: 사용자가 자신을 인증할 수 있는 &#39;토큰&#39;을 가지고 서버와 통신하는 방법입니다.
서버가 사용자의 상태를 저장할 필요가 없어 &#39;무상태&#39;의 특성을 갖고 있습니다. 또한 서버의 확장에 영향이 없어 서버 확장성에 좋은 방법입니다.
단점은 클라이언트가 토큰을 갖고 있어 비교적 안전하지 않고, 토큰의 유효기간이 만료되기 전에 서버에서 토큰을 무효화하기 어려울 수 있습니다. 또 토큰은 세션 ID를 전달하는 방법보다 헤더 크기가 커져서 통신 비용이 좀 더 큽니다.</p>
<h3 id="18-jwt-refresh-access-token에-대해서-설명해주세요">18. JWT, Refresh, Access Token에 대해서 설명해주세요.</h3>
<p>JWT: JSON 객체를 사용하여 정보를 안전하게 전달하기 위해 압축된 웹 토큰입니다.
헤더, 페이로드, 시그니처로 구성되어있고, 헤더에 토큰 유형과 알고리즘을 포함하며
페이로드에는 클레임이 포함되고 시그니처에 비밀키를 사용하여 암호화한 정보가 들어있습니다. 시그니처로 토큰의 무결성을 확인합니다.</p>
<p>액세스 토큰: 클라이언트가 안전하게 접근할 수 있도록 인증을 증명할 수 있는 토큰입니다.
짧은 유효기간을 가지고 주로 HTTP 헤더에 포함하여 전송합니다. 유효기간이 짧아서 토큰이 탈취되어도 피해 가능성을 줄일 수 있습니다.</p>
<p>리프레시 토큰: 액세스 토큰의 유효기간이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰입니다. 따라서 긴 유효기간을 가지는 것이 보통입니다. 리프레시 토큰이 필요할 때만 건네줄 수 있으므로 액세스 토큰보다 노출이 낮아 보안을 높일 수 있습니다.</p>
<h3 id="19-oauth에-대해서-설명해주세요">19. OAuth에 대해서 설명해주세요.</h3>
<p>OAuth(Open Authorization)는 애플리케이션이 사용자 인증을 직접 처리하지 않고 따로 처리해주는 서버에 요청하는 인증 프로토콜입니다. 사용자의 비밀번호를 애플리케이션에 저장할 필요가 없어 보안성이 높아지고 사용자는 하나의 인증 정보로 여러 어플리케이션에서 인증가능하여 편리합니다.
OAuth는 현재 2.0 버전이 널리 사용되고있으며 이전 버전에 비해 더 간단하고 확장 가능한 구조를 가집니다. 우리나라는 주로 카카오, 구글, 네이버, 페이스북 등이 여러 애플리케이션에서 API 접근과 소셜 로그인에서 사용되고 있습니다.</p>
<h3 id="20-클래스형과-함수형의-차이를-설명해주세요-어떤-방식을-주로-사용하였고-그-이유가-뭔지-답변해주세요">20. 클래스형과 함수형의 차이를 설명해주세요. 어떤 방식을 주로 사용하였고 그 이유가 뭔지 답변해주세요.</h3>
<p>클래스형은 객체지향이며 클래스와 객체를 사용하여 구현합니다. 상속과 다형성, 캡슐화, 추상화를 사용할 수 있습니다.</p>
<p>함수형은 데이터의 불변성을 중시하여 함수를 사용하고 복잡한 함수는 합성하여 구현할 수 있습니다. 함수는 입력이 같으면 항상 같은 출력을 반환하는 것이고 상태를 변경하지 않는다는 특징을 가집니다.</p>
<p>저는 주로 객체지향인 클래스형을 사용합니다.</p>
<p>클래스형을 사용하면 클래스를 통해서 현실세계를 모델링하고 상속과 다형성으로 코드를 재사용할 수 있으며 유지보수와 확장성이 좋아 대규모 프로젝트에서 유리합니다.</p>
<h3 id="21-cicd에-대해서-설명해주세요">21. CI/CD에 대해서 설명해주세요.</h3>
<h3 id="22-tdd에-대해서-설명해주세요">22. TDD에 대해서 설명해주세요.</h3>
<h3 id="23-프로세스와-쓰레드에-대해서-설명하고-그-차이에-대해서-설명해주세요">23. 프로세스와 쓰레드에 대해서 설명하고 그 차이에 대해서 설명해주세요.</h3>
<h3 id="24-멀티프로세스와-멀티쓰레드의-특징에-대해-설명해주세요">24. 멀티프로세스와 멀티쓰레드의 특징에 대해 설명해주세요.</h3>
<h3 id="25-쿼리-최적화에-대해-설명해주시고-방법에-대해-설명해주세요">25. 쿼리 최적화에 대해 설명해주시고 방법에 대해 설명해주세요.</h3>
<h3 id="26-db-로직-최소화를-하려면-어떻게-해야-할까요">26. DB 로직 최소화를 하려면 어떻게 해야 할까요?</h3>
<h3 id="27-테스트코드에-대해서-아는대로-설명해주시고-활용-경험에-대해서-답변해주세요">27. 테스트코드에 대해서 아는대로 설명해주시고 활용 경험에 대해서 답변해주세요.</h3>
<h3 id="28-array-linkedlist에-대해-설명해주시고-각각-어떻게-사용하는지-말씀해주세요">28. Array, LinkedList에 대해 설명해주시고 각각 어떻게 사용하는지 말씀해주세요.</h3>
<p>배열은 인덱스를 가짐. 원하는 데이터를 한번에 접근하기 때문에 접근 속도 빠름.</p>
<p>크기 변경이 불가능하며, 데이터 삽입 및 삭제 시 그 위치의 다음 위치부터 모든 데이터 위치를 변경해야 되는 단점 존재</p>
<p>연결리스트는 인덱스 대신에 현재 위치의 이전/다음 위치를 기억함.</p>
<p>크기는 가변적. 인덱스 접근이 아니기 때문에 연결되어 있는 링크를 쭉 따라가야 접근이 가능함. (따라서 배열보다 속도 느림)</p>
<p>데이터 삽입 및 삭제는 논리적 주소만 바꿔주면 되기 때문에 매우 용이함</p>
<p>데이터의 양이 많고 삽입/삭제가 없음. 데이터 검색을 많이 해야할 때 → Array
데이터의 양이 적고 삽입/삭제 빈번함 → LinkedList</p>
<h3 id="29-aws-s3-ec2를-사용하는-이유와-사용-경험에-대해서-답변해주세요">29. AWS S3, EC2를 사용하는 이유와 사용 경험에 대해서 답변해주세요.</h3>
<h3 id="30-정렬-알고리즘에-대해서-아는대로-설명해주세요">30. 정렬 알고리즘에 대해서 아는대로 설명해주세요.</h3>
<h3 id="31-스프링이-뭔지-간단히-설명해주세요">31. 스프링이 뭔지 간단히 설명해주세요.</h3>
<p>자바 스프링(Spring Framework)은 자바 플랫폼을 위한 오픈소스 애플리케이션 프레임워크입니다.
객체 지향적인 설계 원칙으로 자바 객체 POJO를 다양한 엔터프라이즈 서비스에 의존적이지 않게 연결해줍니다. 결합도를 느슨하게 해서 개발자의 생산성을 높이고 애플리케이션의 유지보수를 용이하게 합니다.</p>
<p>다양한 엔터프라이즈 서비스 예시:
대규모 비즈니스 애플리케이션에 사용될 수 있는 기능/기술들. 자바 EE는 이런 서비스를 지원하기 위해 여러 API와 프레임워크를 포함하고 있다.</p>
<ul>
<li>JPA</li>
<li>JSP</li>
<li>CDI : 의존성 주입과 빈 컨텍스트 관리</li>
<li>Security</li>
<li>WebSocket: C-S 간의 실시간 통신을 위한 프로토콜 서비스, 이벤트 기반 처리</li>
<li>JSON-Processing / JSON-Binding: JSON 데이터 &lt;-&gt; 자바 객체 매핑</li>
</ul>
<p>SE/EE 차이:</p>
<ul>
<li>SE: 데스크톱 앱, 서버 앱, 임베디드 애플리케이션 개발에 목적을 둔 표준 자바 플랫폼</li>
<li>SE의 핵심 서비스: 기본적인 자바 클래스 라이브러리 java.util 등,
JVM 자바 앱 실행위한 가상 머신, JDBC DB연동 API 등.</li>
<li>EE: 웹 애플리케이션 및 엔터프라이즈 서비스 개발에 목적을 둔 추가 기능 플랫폼</li>
<li>SE의 확장된 라이브러리를 지원, 웹 서비스 필요한 라이브러리가 많은 것이 특징</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL: 2024/06/27 - Http 요청에 쿠키가 포함되는 조건]]></title>
            <link>https://velog.io/@a-white-bit/TIL-20240627-Http-%EC%9A%94%EC%B2%AD%EC%97%90-%EC%BF%A0%ED%82%A4%EA%B0%80-%ED%8F%AC%ED%95%A8%EB%90%98%EB%8A%94-%EC%A1%B0%EA%B1%B4</link>
            <guid>https://velog.io/@a-white-bit/TIL-20240627-Http-%EC%9A%94%EC%B2%AD%EC%97%90-%EC%BF%A0%ED%82%A4%EA%B0%80-%ED%8F%AC%ED%95%A8%EB%90%98%EB%8A%94-%EC%A1%B0%EA%B1%B4</guid>
            <pubDate>Thu, 27 Jun 2024 02:32:41 GMT</pubDate>
            <description><![CDATA[<h1 id="궁금증">궁금증</h1>
<blockquote>
<p>HTTP 통신에서 클라이언트의 브라우저에 쿠키(Cookie) 저장소가 있으며 이 곳에 여러 쿠키가 저장되어 있는 상태일 때를 가정하자.
HTTP 요청을 할 때 보내는 데이터는 크게 <code>Header</code>와 <code>Body</code>로 나눌 수 있다.
쿠키는 <code>Header</code>에 포함되어 전송된다.</p>
<p>그렇다면 HTTP 요청을 보낼 때마다 필요 여부 상관없이 쿠키 저장소에 있던 모든 쿠키들이 항상 헤더에 담겨 전송되는가?</p>
</blockquote>
<h1 id="쿠키가-http-요청에-포함되는-조건">쿠키가 HTTP 요청에 포함되는 조건</h1>
<p>다음 조건을 모두 만족하는 쿠키들은 매번 통신 헤더에 포함된다.</p>
<h3 id="1-도메인-매칭">1. 도메인 매칭</h3>
<p>쿠키의 <code>Domain</code> 속성 값이 현재 요청 도메인과 일치하거나 요청이 하위 도메인에 포함되는 경우</p>
<h3 id="2-경로-매칭">2. 경로 매칭</h3>
<p>쿠키의 <code>Path</code> 속성 값이 현재 요청 경로와 일치하거나 요청 경로가 하위에 포함될 경우</p>
<h3 id="3-보안-플래그-secure">3. 보안 플래그 (Secure)</h3>
<p>쿠키의 <code>Secure</code> 속성이 true라면 HTTPS 요청일 경우만 쿠키 전송</p>
<h3 id="4-samesite-정책">4. SameSite 정책</h3>
<p><code>SameSite</code> 속성으로 쿠키 전송 컨텍스트 제어 가능</p>
<ul>
<li><code>SameSite=Strict</code>: 동일 사이트 요청만 가능</li>
<li><code>SameSite=Lax</code>: 크로스사이트 서브 리소스 요청 에서는 쿠키 전송 x</li>
<li><code>SameSite=None</code> + <code>Secure=true</code>: 크로스사이트 요청시 쿠키 전송 o</li>
</ul>
<h3 id="5-기간-유효">5. 기간 유효</h3>
<p>쿠키의 <code>Expires</code> 또는 <code>Max-Age</code> 속성 값이 만료되지 않은 경우</p>
<h1 id="결론">결론</h1>
<blockquote>
<p>클라이언트의 브라우저에 여러 쿠키가 저장되어 있는 경우,
요청을 보낼 때 브라우저는 쿠키의 도메인, 경로, 보안 플래그, SameSite 정책, 유효 기간 등의 조건을 검사하여 조건을 만족하는 쿠키들만 HTTP 요청 헤더의 Cookie 헤더에 포함하여 전송한다.</p>
<p>따라서, 모든 쿠키가 항상 전송되는 것은 아니지만 조건이 맞는다면 해당하는 모든 쿠키를 전송한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Jackson 라이브러리, ObjectMapper]]></title>
            <link>https://velog.io/@a-white-bit/Spring-Jackson-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-ObjectMapper</link>
            <guid>https://velog.io/@a-white-bit/Spring-Jackson-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-ObjectMapper</guid>
            <pubDate>Fri, 21 Jun 2024 21:08:18 GMT</pubDate>
            <description><![CDATA[<h1 id="jackson">Jackson</h1>
<h2 id="개요">개요</h2>
<p>Spring 프레임워크를 사용하면서 항상 사용하게 되는 라이브러리다.
웹 개발이 거의 비슷한 형태로 진행되어서 자세한 것은 모르는 채로 한정된 애너테이션만 사용했었는데, 다양한 설정 애너테이션이 있는 것 같아 좀 더 알아보았다.</p>
<p>Java의 객체가 담는 데이터를 외부에서 표현해야 할 때 JSON 형태로 저장하는 경우가 많다.
Spring에서는 이 형태를 전환하는 것을 <strong>직렬화(serialization), 역직렬화(deserialization)</strong>라고 표현하더라.</p>
<p>Jackson 라이브러리가 위 방법을 편리하게 제공해준다.</p>
<h3 id="직렬화">직렬화</h3>
<ul>
<li><p>Java 클래스 표현</p>
<pre><code class="language-java">public class person() {
  private int id;
  private String name;
}</code></pre>
</li>
<li><p>JSON 형식
<code>json { &quot;id&quot; : 1, &quot;name&quot; : &quot;yang&quot; }</code></p>
</li>
</ul>
<p>이와 같이 객체로 표현되던 멤버 변수를 Json 포맷에서는 연속적인 데이터로 표현해서 Stream을 통해 데이터를 읽을 수 있다. Java 8의 추가된 기술 스트림이 아니다. I/O 시에 발생하는 Stream of Bytes 이다.</p>
<blockquote>
<p><em>정확히는 멤버 변수를 직렬화하는 것이 아니라 프로퍼티를 직렬화한다고 한다!
무슨 말이냐면, person() 클래스의 Getter 로 접근할 수 있는 멤버 변수에 한해서만 직렬화 된다.
잘 생각해보면, 당연하다. private으로 선언된 멤버 변수를 서드파티 라이브러리 클래스가 접근할 권한은 없긴 하다.</em></p>
</blockquote>
<h3 id="역직렬화">역직렬화</h3>
<p>그렇다면 역직렬화는 JSON -&gt; Java 객체를 표현하는 방식이 되겠다.</p>
<blockquote>
<p><em>역직렬화는 반대로 Setter 가 필요한데 주의점이 있다.
객체 생성을 위한 기본생성자가 필요하고 (설정으로 생성자 형태를 변경할 수도 있다고 한다.)
직렬화와 다르게 Getter도 필요하다고 한다.</em></p>
</blockquote>
<h2 id="사용법">사용법</h2>
<h3 id="objectmapper-객체-사용-애너테이션-x">ObjectMapper 객체 사용 (애너테이션 x)</h3>
<p>Controller로 들어가기 전에는 <code>@RequestBody</code> <code>@ResponseBody</code> 를 활용할 수 없기 때문에 알아두어야 한다.</p>
<p>본인은 현재 Spring boot 3.3 버전 환경이기 때문에 따로 Jackson 라이브러리에 대한 설정은 하지 않았다.</p>
<ul>
<li>ObjectMapper 객체 생성
<code>ObjectMapper objectMapper = new ObjectMapper();</code></li>
<li>직렬화
파일 출력 예시
<code>objectMapper.writeValue([fileObject], [javaObject]);</code>
스트링으로 표현 (HttpServlet에게 전달할 String)
<code>objectMapper.writeValueAsString([javaObject]);</code></li>
<li>역직렬화
<code>objectMapper.readValue([jsonString], [javaClass].class);</code>
HttpServlet 을 통해 클라이언트로부터 받은 json을 처리할 때 예시
<code>HttpServletRequest req</code>
<code>objectMapper.readValue(req.getInputStream(), [Class].class);</code></li>
</ul>
<p>이 외에 단순한 Object가 아닌 List나 Tree 혹은 Map 같은 컬렉션 형태의 Json 변환은 매개변수와 리턴형태가 달라질 수 있다.</p>
<h3 id="애너테이션-활용">애너테이션 활용</h3>
<p><code>@RequestBody</code> <code>@ResponseBody</code> 를 사용하면 ObjectMapper를 작성하지 않아도 자동으로 직렬화/역직렬화 해준다. Controller 레이어에서 <code>@ResponseBody</code> 메서드는 객체를 리턴하기만 하면 된다.</p>
<p>항상 사용해왔던 기본 애너테이션 말고, 몇 가지 편의성 애너테이션을 찾아보았다.</p>
<h4 id="프로퍼티-관련">프로퍼티 관련</h4>
<ul>
<li><p><code>@JsonValue</code> : 멤버 변수, 메서드에 붙일 수 있는데 1개만 사용할 수 있다. 외부에서 해당 객체를 직렬화할 때 <code>@JsonValue</code>에 해당하는 프로퍼티가 담긴다. 보통 enum의 name을 다른 프로퍼티로 표현하고 싶을 때 사용하는 것 같다.</p>
</li>
<li><p><code>@JsonProperty(&quot;name&quot;)</code> : 멤버변수에 붙이면, 해당 &quot;name&quot;으로 프로퍼티를 생성해준다. (Getter/Setter 없이)</p>
</li>
<li><p><code>@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)</code> : 클래스에 붙이면, 멤버변수와 Getter 모두 Json 매핑된다.
<code>@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)</code> : Getter는 매핑되지 않고, 필드만 매핑된다.</p>
</li>
<li><p><code>@JsonIgnore</code> : 위 <code>@JsonAutoDetect</code>로부터 제외될 멤버에 붙인다.</p>
</li>
<li><p><code>@JsonInclude(JsonInclude.Include.NON_NULL)</code> : 특정 프로퍼티만 포함시키기. 예시는 NOT NULL 필드만 포함. 전체 적용은 클래스에 붙이기, 멤버에만 적용은 멤버에 붙이기.</p>
</li>
<li><p><code>@JsonPropertyOrder({&quot;name&quot;, &quot;id&quot;})</code> : 직렬화 순서 제어</p>
</li>
<li><p><code>@JsonRawValue</code> : json 포맷의 String이 들어있는 프로퍼티에 붙이면, <code>&quot;{\n  \&quot;attr\&quot;:false\n}&quot;</code> -&gt; <code>{&quot;attr&quot;: false}</code> 로 직렬화됨</p>
</li>
</ul>
<h1 id="참고">참고</h1>
<blockquote>
<p><a href="https://velog.io/@chosj1526/Spring-Jackson-%EC%9D%B4%EB%9E%80">[Spring] Jackson 이란 (+Json)</a>
<a href="https://velog.io/@zooneon/Java-ObjectMapper%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-JSON-%ED%8C%8C%EC%8B%B1%ED%95%98%EA%B8%B0">ObjectMapper를 이용하여 JSON 파싱하기</a>
<a href="https://interconnection.tistory.com/137">Jackson ObjectMapper 정리</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>