| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
- 맥
- oh-my-zsh
- 분할정복 방법
- cs
- zsh theme
- VI
- MySQL
- Pager
- 오일러 경로
- 동적 프로그래밍 방법
- 순차탐색
- 인프런
- CS스터디
- spring boot
- mysql 표
- Less
- mysql 표 출력
- zsh
- 오블완
- 데이크스트라
- table status
- 네트워킹데이
- 터미널
- 알고리즘
- mycli
- 이진탐색
- 스터디2기
- 인프런워밍업클럽
- 티스토리챌린지
- 욕심쟁이 방법
- Today
- Total
Develop
[Spring Boot] 게시판 - 13. 코드 리팩토링과 배포 파이프라인 개선 본문
안녕하세요 ~ .ᐟ
오늘은 게시판 프로젝트가 2주차 되는날 입니다.
내일부터 시큐리티 적용예정으로, 그동한 작성한 코드의 품질 개선과 배포 안정화 위주로 작업했습니다.
주요 작업
1. 코드 리팩토링 (상수 관리, DTO 최적화)
2. 예외 처리 개선 (ErrorCode 올바르게 사용)
3. API 응답 통일 (ApiResponse)
4. Blue-Green 배포 개선
5. 테스트 커버리지 측정 (JaCoCo)
1. 코드 리팩토링
- 매직 넘버와 문자열을 찾아내서 상수 클래스로 관리
- 나중에 Page Size 를 바꾸고 싶을때 한곳만 수정하면 되기 때문에 상수 클래스로 분리했습니다.
// Before
// 여기저기 흩어진 10
PageRequest.of(0, 10);
// 또 다른 곳에서 10
PageRequest.of(page, 10);
//After
public class PageConstants {
public static final int DEFAULT_PAGE_SIZE = 10;
private PageConstants() {
throw new AssertionError("Constants class");
}
}
- DTO 최적화
- Before - PostResponse 의 경우 Detail 과 List 로 나누어서 응답을 주고 있었음
- After - 공통되는 필드를 PostBaseResponse 로 묶어 각 응답에서 상속받아서 사용
2. API 응답 통일
- 에러/성공 응답을 한 타입으로 관리해서 유지보수성을 높이기 위해 모든 엔드포인트를 ApiResponse로 통일했습니다.
// Json 구조
{
"success": true,
"data": { /* 실제 데이터 */ },
"error": null
}
3. Blue-Green 배포 개선
- 프로덕션 서버에 무중단 배포를 위해 Blue-Green 배포 전략을 구현했습니다.
Before → After 비교
[Before]
-staging, production 모두 docker-compose 단순 재시작 (다운타임 5-10초)
[After]
- staging : docker-compose (동일)
- production : Blue-Green 무중단 배포 (변경)
주요 변경사항
- 무중단 배포 구현
- 8080(Blue) ↔ 8081(Green) 포트 스위칭
- 서비스 중단 없이 새 버전 배포
- 헬스체크 자동화
- 30초 대기 + 10번 재시도 (총 2분)
- 실패 시 자동 롤백으로 안전성 확보
- Nginx 전환 검증
- 설정 파일 자동 백업
- nginx -t 구문 검증
- 전환 실패 시 즉시 설정 복구
- 전환 후 30초 간 5번 헬스체크
- 완전 자동 롤백 구현
배포 흐름
1. Docker 이미지 빌드 및 푸시
2. 새 컨테이너 - 비활성 포트(Green) 시작
3. 헬스체크 (actuator/health)
4. Nginx upstream 전환
5. 이전 컨테이너(Blue) 정리
4. 테스트 커버리지 측정
- 테스트 커버리지 란?
- 내가 짠 코드중에서 몇 %를 테스트했는지 알려주는 도구
- 저는 테스트 커버리지 측정을 위해 Java Code Coverage 라는 툴을 사용했습니다.
- JaCoCo 가 측정하는 것들 (예시)
- Line Coverage : 실행된 코드 라인 비율, 85% (100줄 중 85줄)
- Branch Coverage : if/else 분기 테스트 비율 , 75% (4개 분기 중 3개)
- Method Coverage : 호출된 메서드 비율, 90% (10개 중 9개)
- Class Coverage : 테스트된 클래스 비율, 100% (모든 클래스)
- 장점
- 측정 결과를 보고, 손쉽게 테스트 안된 코드를 찾을 수 있습니다.
- 팀 규칙으로 설정해서, 테스트 커버리지가 일정 % 이하면 머지가 불가하게 하도록 할 수 있습니다.
- 설정 방법
- build.gradle - JaCoCo 플러그인 추가
// 플러그인 추가
plugins {
id 'jacoco'
}
// Java Code Coverage (테스트 커버리지 측정)
jacoco {
toolVersion = "0.8.11"
}
// 테스트 후 자동으로 리포트 생성
tasks.named('test') {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
// JaCoCo 리포트 설정
jacocoTestReport {
dependsOn test
reports {
xml.required = true // Codecov 용
html.required = true // local 확인용
html.outputLocation = layout.buildDirectory.dir('reports/jacoco/test')
}
}
// 커버리지 검증
jacocoTestCoverageVerification {
violationRules {
rule {
// 제외할 클래스들
excludes = [
'**.dto.**',
'**.entity.**',
'**.config.**',
'**.BoardApplication',
'**.exception.**'
]
limit {
minimum = 0.30 // 추후 0.60 수준으로 변경 예정
}
}
}
}
- 테스트 실행 및 리포트 생성
- 터미널에서 아래 명령어 실행
# 테스트 + 커버리지 리포트 생성
./gradlew test jacocoTestReport
# 또는 커버리지 검증까지
./gradlew test jacocoTestCoverageVerification
# 리포트 파일 열기
open build/reports/jacoco/test/index.html
- 테스트 결과 확인 방법
- 방법1. 개인 /로컬용 (프로젝트에서 확인하기)
- 이 방법은 build/reports/jacoco/test/index.html 을 브라우저 여는방식이랑 동일한 결과를 보여줍니다.
- 브라우저에서 테스트 대상과 커버리지 현황을 볼 수 있습니다.
- 방법1. 개인 /로컬용 (프로젝트에서 확인하기)


- 방법2. 팀 PR 리뷰 피드백용 (GitHub Actions에서 자동 측정)
- 이 방법을 사용하려면 방법1. 의 build.gradle 설정이 선행되어야 합니다.
- 이후 아래 파일을 프로젝트에서 워크플로우로 등록합니다. ( 경로 : .github/workflows/test.yml )
- push 후 Pr시에 봇이 자동으로 코멘트를 달아줍니다.

name: Test & Coverage
on:
pull_request:
branches: [ main, develop ]
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Run tests with coverage
run: ./gradlew test jacocoTestReport
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
build/reports/tests/test/
build/test-results/test/
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: build/reports/jacoco/test/
# Codecov 에 업로드
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./build/reports/jacoco/test/jacocoTestReport.xml
fail_ci_if_error: false # 업로드 실패해도 워크플로우는 성공
verbose: true
# PR에 커버리지 코멘트 (jacoco-badge-generator 사용)
- name: Add coverage to PR
if: github.event_name == 'pull_request'
uses: madrapps/jacoco-report@v1.6.1
with:
paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 60
min-coverage-changed-files: 70
# 커버리지 검증 (60% 이상)
# - name: Verify coverage
# run: ./gradlew jacocoTestCoverageVerification
- 방법 3. 코드베이스 전체 추세, 히스토리, 그래프 등 (Codecov 홈페이지에서 확인하기)
- Codecov 사이트에 테스트를 할 레포를 등록하면, Pr 시 마다 테스트 커버리지의 결과를 보여줍니다.
- 제 프로젝트는 아직 테스트를 많이 작성하진 않아서 커버리지가 낮고, 앞으로 테스트를 작성해서 최소 검증기준 60%로 올릴 생각입니다.

트러블 슈팅
- 문제 : 워크플로우에 JaCoCo PR Comment를 달도록 설정을 했는데, 측정된 내용은 안나오고 기본 자동 코멘트만 달림
- 원인 : 권한이 없어서 PR의 내용을 읽지 못하고, 코멘트를 달지 못하고 있었음
- 해결 : Jobs 부분에 권한부여 -> 이후 측정된 결과도 코멘트로 잘 달리게 됨

느낀 점
- 처음에는 팀 프로젝트에서 발생한 배포 오류를 이해해 보기 위해, 개인 프로젝트에 Blue-Green 배포를 먼저 적용해 보았다.
- 이 과정에서 GitHub Actions를 활용해 CI/CD 파이프라인을 구축해 보고, 무중단 배포 방식의 종류와 각 방식에서 발생할 수 있는 장애 요소, 배포 워크플로우 스크립트를 어떻게 작성하고 어떤 트리거로 동작시키면 좋을지까지 자연스럽게 학습하게 되었다.
- 예전에는 팀 회의 시간에 CI/CD 배포 오류가 발생하면 원인을 따라가는 것이 막막했는데, 개인 레포에 파이프라인을 직접 구축해 보니 이제는 팀 레포의 어떤 부분이 문제인지 추측하고 검증해 보는 과정이 재미있어졌다.
- 이번에 정리한 내용을 바탕으로, 다음 팀 회의 때 실제 팀 레포에도 적용해 볼 계획이다.
- 또한 테스트 커버리지를 측정하면서, 앞으로는 테스트 품질을 감이 아니라 숫자로 관리할 수 있는 기반을 마련하게 되었다.
- 이 부분은 앞으로 계속 테스트 해보면서, 팀레포에도 적용할 수 있게 근거를 마련해둬야겠다는 생각이 들었다.

'백엔드 > 게시판 만들기' 카테고리의 다른 글
| [Spring Boot] 게시판 - 15. JWT 인증 구현하기 (Token 기반으로 전환) (0) | 2025.12.16 |
|---|---|
| [Spring Boot] 게시판 - 14. Spring Security 로그인 구현 (0) | 2025.12.14 |
| [Spring Boot] 게시판 - 12. JPA N+1 문제 해결하기 (EntityGraph vs Fetch Join) (0) | 2025.12.09 |
| [Spring Boot] 게시판 - 11. 동적 정렬과 필터링으로 사용자 경험 개선하기 (0) | 2025.12.09 |
| [Spring Boot] 게시판 - 10. QueryDSL로 동적 검색 기능 구현하기 (0) | 2025.12.05 |