Skip to content

ktc-boost/Team1_BE

Repository files navigation

Team1_BE

썸네일




💙 왜 BOOST인가? 💙


boo-front

💡 Better 💬 Opinion 📂 Organize Simple 🤝 Together
더 나은 성장과 개선 솔직한 의견 공유 체계적 관리와 정리 누구나 쉽게 사용 가능 함께 만드는 협업 경험

BOOST는 대학생 팀 프로젝트에서 더 나은 성장을 돕고, 솔직한 의견을 자유롭게 나누며,
체계적인 관리와 누구나 쉽게 접근 가능한 환경 속에서 팀원 모두가 함께 협업할 수 있게 만들어주는 협업 툴입니다.





🚀 프로젝트 소개

  • 대학생 팀 프로젝트를 보다 효율적이고 체계적으로 관리할 수 있도록 돕는 협업 툴입니다.
  • 칸반(Kanban) 보드 형태의 직관적인 인터페이스를 제공하여, 팀원들은 할 일을 쉽게 테스크(Task)로 만들고 담당자를 지정할 수 있습니다.
  • 작업물이 업로드되면 팀원들이 확인하고 승인할 수 있으며, 원하는 부분에 마커를 달아 구체적인 피드백을 남길 수 있습니다.
  • 말하기 어려운 피드백을 서비스의 마스코트 Boo가 부드럽게 변환하여 대신 전하게 할 수 있습니다.
  • 복잡한 일정 관리 없이도 팀 프로젝트의 진행 상황을 한눈에 확인하고, 원활한 협업을 경험할 수 있습니다.

🗓️ 개발 기간

2025/09 ~ 2025/11 (약 3개월)


🔗 배포 주소

🌐 BOOST 메인 서비스
📘 BOOST API Swagger 문서



📚 목차



🛠️ 기술 스택 (Spring Backend)

구분 기술
개발 언어 Java
서버 프레임워크 Spring Boot
빌드/의존성 관리 Gradle
데이터베이스 MySQL H2
ORM Spring Data JPA
보안/인증 Spring Security JWT
API 문서화 Swagger
외부 API OpenAI
테스트 JUnit5
컨테이너/배포 Docker
CI/CD GitHub Actions
개발 환경 IntelliJ IDEA Git GitHub
협업 도구 Notion Figma


📐 아키텍처

강원대 BE1팀 아키텍처

🚀 CI/CD 파이프라인

GitHub Actions를 통해 AWS ECS에 자동 배포되는 파이프라인입니다.

배포 전략

  • 배포 트리거: deploy 태그가 GitHub 원격 저장소에 푸시될 때 실행됩니다.
  • 대상 환경: Production
  • 배포 방식: Blue/Green 배포 (AWS ECS)
    • AWS ECS를 사용한 Blue/Green 배포 전략을 사용합니다.
    • 이 방식은 신규 버전('Green')을 먼저 배포하고, Load Balancer의 테스트 리스너를 통해 상태 확인을 완료합니다.
    • 모든 검증이 통과하면, 프로덕션 트래픽(리스너)이 기존 'Blue' 환경에서 'Green' 환경으로 즉시 전환됩니다.
  • 보안:
    • AWS 인증: OIDC 방식으로 안전한 임시 자격 증명 사용
    • 환경 변수: AWS Secrets Manager를 통한 중앙 관리
  • 핵심 장점:
    • 무중단 배포: 트래픽 전환은 순간적으로 일어나므로 사용자는 배포 과정을 인지하지 못합니다.
    • 신속한 롤백: 'Green' 환경 배포 중 문제가 감지되거나 트래픽 전환 후 정상적인 상태가 아님이 확인된 경우, 자동으로 트래픽을 즉시 'Blue' 환경(기존 버전)으로 되돌립니다. 기존 'Blue' 환경은 트래픽 전환이 안정화된 후에 종료되므로 롤백이 매우 안전하고 빠릅니다.

배포 흐름

  1. deploy 태그 푸시
  2. Checkout
  3. JDK 21 설정
  4. Gradle 빌드
  5. AWS OIDC 인증
  6. ECR 로그인
  7. Docker 이미지 빌드
  8. ECR 푸시
  9. ECS 태스크 정의 렌더링
  10. ECS 서비스 배포
  11. 서비스 안정화 대기


✨ 주요 기능

1. 간편한 카카오 로그인

로그인페이지



2. 나의 할 일 관리

  • 개인 칸반보드 형태로 직관적인 작업 생성 및 관리 가능
  • 프로젝트별 개인 테스크를 한눈에 확인하고, 우선순위에 따라 효율적으로 정리
  • 상태(진행 전 / 진행 중 / 검토 중 / 완료) 변경을 통해 작업 현황을 명확하게 관리

나의 할일보드

할 일 생성



3. 프로젝트 할 일 관리

  • 팀 단위 칸반보드를 통해 팀원들과 실시간으로 작업 진행 상황을 공유
  • 각 작업의 담당자, 진행 상태, 마감일 등을 한곳에서 관리하여 협업 효율 극대화
  • 상태 변경 시 팀원들이 즉시 확인할 수 있어 투명한 협업 환경 구축

프로젝트 페이지



4. 손쉬운 프로젝트 생성 / 참여 코드 기반 프로젝트 참여

  • 참여 코드를 통해 안전하게 프로젝트 참여 가능
  • 24시간마다 코드가 리프레시되어 보안 유지
  • 코드 공유로 간편하게 팀원 초대

프로젝트참여생성



5. 팀원 보드 및 공헌도 시스템

  • 각 팀원의 작업 상태 한눈에 확인 가능
  • 점수가 가장 높은 팀원에게 왕관 아이콘 부여
  • 개인의 기여도를 시각적으로 표시하여 협업 동기 부여

팀원보드



6. 팀원 간 작업에 댓글로 피드백

  • 팀원의 작업물에 댓글 가능
  • 피드백 작성 시 원하는 위치에 마커 지정 후 댓글 작성 기능

image



7. 솔직한 의견 공유를 위한 AI 댓글 변환 기능

  • 거친 표현이나 세게 들릴 수 있는 말투를 AI가 자연스럽고 부드럽게 변환
  • 솔직한 피드백은 그대로 유지하면서, 상대방이 기분 좋게 받아들일 수 있도록 조정
  • 팀 내 건강한 커뮤니케이션 문화 형성에 도움

image



8. 작업물 검토 요청 및 승인 시스템

  • 업로드된 작업물 확인 및 승인
  • 리뷰 요청 시 검토 중으로 할 일이 이동하고, 모든 승인을 다 받으면 완료 처리 가능

image

image



9. 웹 푸시 알림 기능

  • 마감일이 얼마 남지 않은 작업 알림
  • 검토가 필요한 작업 발생 시 알림
  • 모든 승인 완료 시 알림
  • 팀원들이 중요한 일정을 놓치지 않도록 실시간으로 안내

image

image



10. 사용자 친화적 UI/UX, 아바타 커스터마이징

  • 직관적인 인터페이스로 누구나 쉽게 사용 가능
  • 귀여운 아바타 지정 가능

아바타 선택 페이지



11. 프로젝트에서 작업한 파일들을 모아보는 기능

  • 프로젝트를 진행하면서 나온 작업 파일들을 한곳에서 모아보기 가능

image



12. 메모 페이지

  • 팀원 간 공유용 자유로운 메모 작성 가능
  • 프로젝트 관련 아이디어, 링크, 참고 자료 정리
  • 간단한 개인 기록용으로도 사용 가능

image



🔐 인증 및 인가 플로우

카카오 로그인부터 토큰 재발급, 로그아웃까지의 전체 인증 흐름을 시각적으로 정리했습니다.

카카오 로그인 (OAuth2 인가 코드 플로우) image
인가 (Authorization) - 일반 API 요청 처리 흐름 image
토큰 재발급 (Refresh Token 쿠키 기반) image
로그아웃 (Refresh Token 삭제) image


📊 ERD

image
ERD 핵심 요소 설명
  • BaseEntity

    필드 타입 설명
    id UUID 기본 키 (자동 생성)
    createdAt LocalDateTime 생성 시각 (@CreatedDate)
    updatedAt LocalDateTime 마지막 수정 시각 (@LastModifiedDate)
    • 모든 엔티티의 기본 클래스
    • JPA Auditing으로 생성/수정 시각 자동 관리
    • 삭제/복구가 필요 없는 엔티티에서 사용

  • SoftDeletableEntity

    필드 타입 설명
    deleted boolean 삭제 여부 (true면 논리 삭제 상태)
    deletedAt LocalDateTime 삭제된 시각
    • BaseEntity 확장 클래스
    • 물리 삭제 대신 논리 삭제(soft delete) 처리
    • reactivate()로 복구 가능
    • 삭제/복구가 필요한 엔티티에서 상속을 받아서 사용


  • ProjectMembership

    필드 타입 설명
    project Project 소속된 프로젝트
    member Member 참여 중인 멤버
    role ProjectRole 프로젝트 내 역할 (예: OWNER, MEMBER 등)
    notificationEnabled boolean 알림 수신 여부
    • SoftDeletableEntity 상속 (프로젝트 탈퇴 시 논리 삭제)
    • ProjectMember 사이의 관계를 나타내는 중간 엔티티
    • 단순 N:M 매핑이 아닌, 역할/알림 설정을 함께 관리

  • ProjectJoinCode

    필드 타입 설명
    joinCode String 프로젝트 참여 초대 코드
    project Project 관련된 프로젝트
    status CodeStatus 코드 상태 (ACTIVE, EXPIRED, REVOKED 등)
    expiresAt LocalDateTime 코드 만료 시각
    • BaseEntity 상속 (삭제 대신 상태 전환으로 관리)
    • 프로젝트 초대 링크/코드 관리용 엔티티
    • 상태 및 만료 시간 기반으로 유효성 검증 (isActive, isExpired)

  • BoostingScore

    필드 타입 설명
    projectMembership ProjectMembership 점수가 귀속되는 프로젝트-멤버 관계
    taskScore Integer 작업(Task) 수행 점수
    commentScore Integer 댓글(Comment) 활동 점수
    approveScore Integer 승인(Approve) 관련 점수
    totalScore Integer 총합 점수
    calculatedAt LocalDateTime 점수 계산 시각
    • 멤버의 프로젝트 내 활동 점수를 관리 (ProjectMembership 단위로 누적)
    • (작업/댓글/승인)에 따라 1시간마다 스케줄링으로 자동 갱신되어 최신 점수를 유지

  • WebPushSubscription

    필드 타입 설명
    member Member 구독한 사용자
    token String 브라우저 푸시 토큰 (고유)
    deviceInfo String 디바이스 정보 (중복 방지용)
    webPushUrl String 푸시 엔드포인트 URL
    publicKey String 클라이언트 공개 키
    authKey String 인증 키
    • SoftDeletableEntity 상속
    • 사용자별 웹 푸시 구독 정보 저장
      • webPushUrl: 브라우저가 푸시 서버(Google FCM 등)에 등록할 때 제공하는 고유 엔드포인트 URL
      • publicKey: 푸시 메시지 암호화를 위한 브라우저 측 공개 키 (서버는 이걸로 암호화함)
      • authKey: 메시지 무결성과 인증을 보장하기 위한 추가 키



⚙️ 기술적 노력 & 설계 고민

저희 프로젝트를 진행하면서 서비스 품질 향상과 성능 개선을 위해 진행한 기술적 시도 및 과정을 정리했습니다.

Refresh Token 보안 강화 및 Stateless 로그아웃 전략

Refresh Token의 안전한 저장 및 XSS 방어

문제 상황

Access Token은 수명이 짧지만, Refresh Token은 수명이 깁니다.
만약 Refresh Token이 JavaScript로 접근 가능한 localStorage나 일반 쿠키에 저장될 경우,
XSS 공격에 탈취당하면 사용자의 계정이 장기간 위험에 노출됩니다.

해결 방안

  • Access Token (수명 30분)
    JavaScript가 API 요청 헤더에 담을 수 있도록 응답 Body에 담아 전달했습니다.
  • Refresh Token (수명 7일): XSS 공격을 원천적으로 차단하기 위해, JavaScript가 접근할 수 없는 HttpOnly 속성의 보안 쿠키로 발급했습니다.
  • 추가로 SameSite=Strict, Secure=true 옵션을 부여하여 CSRF 공격과 HTTP 통신을 방어했습니다.

선택 이유

  • HttpOnly 쿠키는 브라우저 보안 정책에 의해 document.cookie로 접근이 불가능합니다.
  • 이 방식은 XSS 공격자가 스크립트를 삽입하더라도,
    가장 중요한 Refresh Token의 탈취를 막을 수 있는 가장 표준적이고 강력한 웹 보안 전략입니다.

얻은 효과

  • XSS 공격으로부터 Refresh Token을 안전하게 보호하여 인증 시스템의 전반적인 보안 수준을 크게 향상시켰습니다.
  • Access Token은 편리하게 사용하되, 위험은 짧은 만료 기간으로 최소화하는 이중 토큰 전략을 완성했습니다.

Stateless 시스템에서의 로그아웃 처리

문제 상황

JWT는 서버가 상태를 저장하지 않는 Stateless를 전제로 합니다.
이로 인해 사용자가 로그아웃을 해도, 서버는 이미 발급된 Access Token을 강제로 무효화할 방법이 없습니다.
토큰이 유효기간(30분) 동안 탈취되어 사용될 수 있는 보안적 딜레마가 발생했습니다.

해결 방안

  • (고려한 방식) Access Token을 Redis 같은 DB에 '블랙리스트'로 저장하여,
    모든 요청마다 토큰이 블랙리스트에 있는지 검사하는 Stateful 방식.
  • (선택한 방식) JWT의 Stateless 이점을 유지하기로 결정했습니다.
    대신, 로그아웃 시 DB에 저장된 Refresh Token을 즉시 폐기했습니다.

선택 이유

  • '블랙리스트' 방식은 모든 API 요청마다 추가적인 DB 조회가 필요해, JWT의 가장 큰 장점인 성능과 서버 확장성을 포기해야 하는 트레이드오프가 발생했습니다.
  • Refresh Token을 즉시 폐기하는 Stateless 방식은, 비록 Access Token의 짧은 유효기간 동안은 위험이 존재하지만,
  • 그 이후에는 공격자가 새로운 토큰을 발급받을 수 없도록 접근을 원천 차단할 수 있습니다. 성능과 보안 사이의 합리적인 절충안이라고 판단했습니다.

얻은 효과

  • Stateless 아키텍처의 성능과 확장성을 유지하면서, Refresh Token 폐기를 통해 실질적인 로그아웃 기능을 구현했습니다.

Refresh Token 탈취 감지 전략 (RTR)

문제 상황

로그아웃 딜레마를 해결했음에도, 수명이 7일인 Refresh Token 자체가 탈취당하면 여전히 위험했습니다.
공격자는 7일간 지속적으로 새 Access Token을 발급받을 수 있습니다.

해결 방안

  • Refresh Token Rotation (RTR) 전략을 도입했습니다.
  • 토큰 재발급 요청 시, 새로운 Access Token과 함께 '완전히 새로운 Refresh Token'을 발급합니다.
  • 동시에, DB에 저장된 '기존 Refresh Token'은 즉시 폐기(또는 새 토큰으로 교체)합니다.

선택 이유

  • RTR은 Refresh Token을 일회용처럼 사용하게 만듭니다.
  • 만약 토큰이 탈취되더라도, 공격자나 실제 사용자 중 단 한 명만 성공적으로 재발급을 받을 수 있습니다.
  • 이후 무효화된 토큰으로 재발급 요청이 들어오면, 서버는 예외를 발생시켜 이를 탈취 시도로 감지하고 해당 사용자의 모든 세션을 강제 종료시키는 등의 후속 조치를 할 수 있습니다.

얻은 효과

  • 기존의 '토큰 유출 시 7일간 위험' 상태에서, '토큰 유출 시 즉시 감지 및 무력화 가능' 상태로 보안 수준을 대폭 향상시켰습니다.

Task 조회 성능 개선을 위한 데이터베이스 인덱스 도입

1. 시나리오

1.1. 문제 상황

  • 프로젝트 내 태스크가 증가하면서 태스크 목록 조회 시 성능 저하 발생
  • TaskRepository의 주요 쿼리들이 복합 조건(프로젝트+상태+정렬)을 사용하나 인덱스 미적용
  • "내 태스크" 조회 시 task_assignees 조인 테이블 풀스캔으로 인한 지연
  • 마감일 알림 기능에서 전체 테이블 스캔 발생

1.2. 주요 쿼리 패턴 분석

  • 프로젝트별 태스크 조회: 프로젝트 ID + 상태 필터링 + 생성일/마감일 정렬
  • 내 태스크 조회: 멤버 ID로 assignees 조인 + 상태 필터링 + 정렬
  • 멤버별 태스크 집계: 프로젝트 내 모든 멤버의 상태별 태스크 카운팅
  • 마감일 알림: 특정 날짜 + 완료되지 않은 태스크 검색

2. 구현 방법

tasks 테이블 인덱스 (3개)

인덱스명 컬럼 구성 대상 쿼리
idx_tasks_project_status_created project_id, status, created_at, id 프로젝트+상태별 조회 및 생성일 정렬
idx_tasks_project_status_duedate project_id, status, due_date, id 프로젝트+상태별 조회 및 마감일 정렬
idx_tasks_duedate_status due_date, status 마감일 알림 조회

task_assignees 조인 테이블 인덱스 (1개)

인덱스명 컬럼 구성 대상 쿼리
idx_task_assignees_member_task member_id, task_id 멤버별 태스크 조회 (MEMBER OF 쿼리)

3. 장점

3.1. 성능 개선

  • 프로젝트 태스크 목록 조회: 테이블 풀스캔 → 인덱스 스캔으로 변경되어 O(n) → O(log n) 성능 향상
  • 내 태스크 조회: 조인 테이블 풀스캔 제거로 멤버당 할당된 태스크만 효율적으로 접근
  • 정렬 처리: 인메모리 정렬 대신 인덱스 순서 활용으로 추가 정렬 비용 제거
  • 마감일 알림: 특정 날짜 범위 검색 시 인덱스 범위 스캔으로 대폭 개선

3.2. 확장성

  • 태스크 수가 증가해도 조회 성능 선형적으로 유지
  • 프로젝트 멤버가 많아져도 개인 태스크 조회 속도 안정적
  • 동시 사용자 증가 시 DB 부하 감소

3.3. 사용자 경험 개선

  • 태스크 목록 로딩 속도 향상으로 UI 반응성 개선
  • 검색 및 필터링 기능 사용 시 즉각적인 응답
  • 대시보드 및 통계 화면 로딩 시간 단축

Task 엔티티 낙관적 락(Optimistic Lock) 도입

1. 동시성 문제 시나리오

여러 리뷰어가 동시에 같은 Task를 승인할 때 Lost Update 문제 발생

  • 사용자A와 B가 동시에 Task 조회 (approvers: 빈 리스트)
  • 사용자A가 승인 추가 후 저장 (approvers: [A])
  • 사용자B가 승인 추가 후 저장 (approvers: [B])
  • 결과: 사용자A의 승인이 사라지고 데이터 정합성이 깨짐

2. 구현 방법

Task 엔티티에 @Version 필드 추가

  • JPA의 @Version 어노테이션을 사용해 버전 필드 선언
  • JPA가 자동으로 버전을 관리하고 충돌 감지
  • 충돌 시 OptimisticLockException 발생
  • GlobalExceptionHandler에서 409 Conflict 응답 처리

3. 주요 장점

데이터 정합성 보장

  • 동시 수정 시 먼저 커밋된 트랜잭션만 성공
  • Lost Update 문제 방지
  • 승인자 목록, 상태 변경 등 비즈니스 로직의 일관성 유지

높은 성능 & 확장성

  • DB 레벨의 락을 사용하지 않아 읽기 성능 우수
  • 여러 사용자가 동시에 조회해도 성능 저하 없음
  • 데드락 위험 없음

웹 푸시 알림 시스템 설계 및 비동기 이벤트 처리

배경

  • 조별 과제나 협업 과정에서는 팀원들이 자료를 확인하거나 검토해야 하는 상황이 자주 발생합니다.
  • 이를 원활하게 진행하기 위해, 모두가 항상 휴대하고 있는 모바일 기기를 통해 즉각적인 알림을 전달할 수 있는 방법이 필요하다고 판단했습니다.
  • 처음에는 카카오톡 알림톡을 활용하려 했으나, 사업자 등록이 필수라는 제약이 있어 대안을 모색하던 중 웹 푸시(Web Push) 기술을 선택하게 되었습니다.

선택한 이유

  • 별도의 앱 설치 없이 브라우저에서의 알림 허용만으로 알림 전송 가능
  • 사용자가 브라우저를 열지 않아도 실시간 알림 전달 가능
  • Chrome, Edge, Firefox 등 대부분의 브라우저 지원(IOS의 경우 웹앱으로 사용 가능)
  • 협업 시 팀원들에게 자료 확인·검토 알림을 빠르게 전달 가능

웹 푸시 알림 등록 흐름

image
알림 등록 과정 설명
  1. 세션 발급 (create 상태)

    • 클라이언트가 웹 푸시를 요청하면 서버는 TTL 5분짜리 세션을 생성한다.
    • 생성된 세션 정보는 create 상태로 저장된다.
  2. QR 코드 생성 및 표시

    • 클라이언트는 발급된 세션 정보를 기반으로 QR 코드를 생성한다.
    • 이 QR 코드를 사용자에게 표시하여 모바일 기기에서 인식할 수 있도록 한다.
  3. 모바일 연결 (connect 상태 전환)

    • 사용자가 모바일로 QR을 스캔하면, 클라이언트의 요청으로 해당 세션이 connect 상태로 변경된다.
  4. 웹 푸시 등록 및 저장 (register로 상태 전환)

    • 사용자가 모바일 웹에서 알림 설정하기 버튼을 클릭하게 되면
      웹 푸시 알림이 활성화되면서 해당 세션이 register 상태로 변경된다.
    • 즉, 모바일 기기와 브라우저 간의 연결이 성립된다.
  5. 브라우저에서의 페이지 전환

    • 브라우저는 주기적으로 서버에 polling을 수행해 세션 상태를 확인한다.
    • 세션 상태가 register로 변경되면, 클라이언트는 웹 푸시 구독 정보를 등록한다.
    • 서버는 전달받은 구독 정보를 DB에 저장한다.

웹 푸시 알림 전송 흐름

image
알림 전송 흐름 설명
  1. 구독 정보 저장 과정은 위의 웹 푸시 알림 등록 과정을 축약한 것 입니다.

  2. 프론트에서 알림 전송 요청

    • 알림을 보내고 싶은 시점에 브라우저에서 서버에 “알림 보내기” 요청
    • 요청에는 알림 내용(payload)만 포함
  3. 서버에서 푸시 서버로 전송

    • 서버는 DB에서 구독 정보를 조회
    • 푸시 서버로 메시지 전송 요청
    • 푸시 서버는 메시지를 브라우저로 전달
  4. 브라우저 Service Worker가 알림 표시

    • 백그라운드에서 메시지 수신
    • 알림을 구독한 모바일 기기에 푸시 알림 표시
    • 알림 클릭 시 웹페이지 이동 가능

웹 푸시 구현중 고민한 부분

Polling vs WebSocket 선택 이유
  • 고민한 이유
    모바일에서 QR 코드를 스캔해 연결된 후, 프론트에서 자동 페이지 리다이렉트를 구현하고자 했습니다.
    하지만 모바일과 브라우저는 서로 상태를 알 수 없기 때문에 문제가 발생하였고
    Polling과 WebSocket 두 가지 방법을 고민했습니다.

  • Polling 선택 이유

    • 구현 난이도가 낮아 기능 우선 개발에 적합
    • 실시간성이 극도로 중요한 서비스가 아니며, 현재 동시 사용자가 많지 않음
    • 서버 부하가 발생할 수 있지만, 알림 연결 수준에서는 충분히 감당 가능
  • WebSocket 고려

    • 실시간성이 뛰어나고 서버와 브라우저 간 상태 동기화가 즉시 가능
    • 추후 성능 개선이나 동시 접속자 증가 시 업그레이드 가능
  • 결론
    현재는 빠른 구현과 안정성을 위해 Polling을 선택하고,
    나중에 필요에 따라 WebSocket으로 전환하도록 하였습니다.


알림 전송 로직 설계 및 비동기 처리 이유
  • 목적

    알림 전송 과정은 특정 서비스 로직 실행 → 알림 저장 → 푸시 전송 의 세 단계를 각각 독립된 비동기 흐름

    으로 처리하여, 서비스의 안정성과 응답성을 동시에 확보하였습니다.

image

1. 이벤트 발행

  • 알림 발송이 필요한 특정 서비스(TaskService.Java)
if (request.status() == TaskStatus.REVIEW) {
    taskEventPublisher.publishTaskReviewEvent(project.getId(), task.getId());
}
  • 설명

    특정 서비스 로직이 실행된 이후, 조건에 만족되면 알림 발송이 필요하다면 알림 이벤트를 발행합니다.

    예를 들어 Task 상태가 REVIEW로 변경되면, TaskReviewEvent가 발행됩니다.


2. 이벤트 리스너 비동기 처리

  • NotificationEventHandler.Java

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleTaskReviewEvent(TaskReviewEvent event) {
        notificationService.notifyTaskReview(event.projectId(), event.taskId(), NotificationType.REVIEW);
    }
  • 비동기로 실행한 이유

    • Task 상태 변경 트랜잭션과 알림 발송은 서로에게 영향을 주어서는 안된다고 생각하였습니다.
    • 알림 발송 실패가 Task 업데이트에 영향을 주면 비즈니스 일관성이 깨질 것이라고 판단하였습니다.
    • 따라서 @Async + AFTER_COMMIT 조합으로 트랜잭션 커밋 후 별도 스레드에서 실행하도록 설계하였습니다.

3. 알림 저장 및 발송 분리

  • NotificationService.Java

    @Transactional(readOnly = true)
    public void notifyTaskReview(UUID projectId, UUID taskId, NotificationType type) {
        // ...프로젝트 및 태스크 조회
        for (Member member : members) {
            try {
                notificationSenderService.saveAndSendNotification(member, type.title(), type.message(task.getTitle()));
            } catch (Exception e) {
                log.error("Failed to send review notification to member: {}", member.getId(), e);
            }
        }
    }
  • 설명

    • 알림을 받을 대상(Member)을 필터링 후, 개별적으로 saveAndSendNotification 호출
  • 설계 의도

    • 한 명의 알림 발송 실패가 다른 사람에게 영향 주지 않도록 각 멤버별 독립 실행을 하기 위해서 따로 트랜잭션을 독립적인 단위로 실행하고자 하였습니다. 아래에서 더 자세한 설명하도록 하겠습니다.

4. Notification 저장 및 전송 이벤트 발행

  • NotificationSenderService.Java

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveAndSendNotification(Member member, String title, String message) {
        Notification notification = Notification.create(member, title, message);
        notificationRepository.save(notification);
    
        if (member.isNotificationEnabled()) {
            notificationEventPublisher.publishNotificationSavedEvent(member, title, message);
        }
    }
  • 해당 메서드를 부모 트랜잭션의 영향을 받지 않도록 하기 위해 별도의 컴포넌트로 분리하였습니다.

  • 기존 서비스 로직 내에서 멤버별 알림을 순차적으로 처리할 때, 하나의 트랜잭션 안에서 모두 실행되면 특정 멤버의 알림 저장 또는 발송 과정에서 예외가 발생할 경우 전체 트랜잭션이 롤백될 위험이 있었습니다.

  • 따라서 해당 메서드에 @Transactional(propagation = Propagation.REQUIRES_NEW)를 적용하여 각 멤버별 알림 저장을 독립적인 트랜잭션으로 실행하도록 설계하였습니다.

  • REQUIRES_NEW 사용 이유

    • 멤버별 알림 저장은 독립적인 단위로 실행되어야 함
    • 기존 Task 알림 루프나 다른 멤버의 알림과 트랜잭션을 공유할 필요 없음
    • 실패 시 rollback 범위를 최소화하여 부분 성공 허용

5. 푸시 전송 이벤트 처리

  • NotificationEventHandler.Java
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleNotificationSavedEvent(NotificationSavedEvent event) {
    webPushClient.sendNotification(event.member(), event.title(), event.message());
}
  • 비동기로 처리한 이유
    • 서비스 자체에서 알림을 표현해주기 위한 알림 저장(DB)은 비교적 빠르게 처리되지만, 실제 WebPush 전송은 네트워크 I/O로 인해 시간이 더 걸리는 작업입니다.
    • 그래서 DB 트랜잭션과 전송 로직을 분리하여 사용자 응답 속도 향상 및 안정성 확보하였습니다.
  • 결과
    • 알림 저장 → 커밋 후 → 별도 스레드에서 Web Push 전송
    • 네트워크 오류나 푸시 실패 시에도 데이터 무결성은 보장됨

6. 설계 의도 정리

  • 이벤트 기반 구조

    → 비즈니스 로직과 알림 로직을 분리하여 결합도를 최소화하고 유지보수성을 향상시켰습니다.

  • @Async + AFTER_COMMIT 조합

    → 트랜잭션 커밋 이후 비동기 실행으로 처리하여 트랜잭션 안정성과 사용자 응답 속도를 모두 확보했습니다.

  • REQUIRES_NEW 트랜잭션 적용

    → 멤버별 알림을 독립된 트랜잭션으로 처리하여 부분 실패를 허용하고, 다른 알림에 영향을 주지 않도록 했습니다.

  • 2단계 비동기 처리 (Service → Notification)

    → 서비스 로직 이후 알림 처리 단계를 분리하여 에러 상황을 격리하고 장애 전파를 방지했습니다.

  • 에러 로그만 기록, 실패 무시

    → 알림 전송 실패가 핵심 비즈니스 로직에 영향을 주지 않도록 하고, 핵심 도메인 로직의 안정성을 보호했습니다.




🧪 Test Coverage (Jacoco)

Jacoco Coverage image

Jacoco를 활용하여 테스트 코드 커버리지를 측정하고 관리하여 코드의 품질을 향상시켰고 84%의 테스트 커버리지를 달성하였습니다.

  • 단위 테스트 기반 커버리지 측정


📂 폴더 구조

📁 boost/
├── 📁 .github/              # (GitHub Actions - CI/CD 설정)
├── 📁 build/                # (빌드 결과물)
├── 📁 gradle/
├── 📄 build.gradle          # 📜 프로젝트 의존성 및 빌드 설정
├── 📄 Dockerfile            # 🐳 Docker 컨테이너 빌드 스크립트
├── 📄 ecs-taskdef.json      # (AWS ECS 작업 정의)
├── 📄 README.md
└── 📁 src/
    ├── 📁 main/
    │   ├── 📁 java/
    │   │   └── knu/team1/be/boost/
    │   │       ├── BoostApplication.java # 🚀 메인 애플리케이션
    │   │       │
    │   │       ├── 🧩 [feature_domain]/
    │   │       │   ├── controller/     # API 엔드포인트 (API Interface + Controller)
    │   │       │   ├── service/        # 비즈니스 로직
    │   │       │   ├── repository/     # DB 데이터 접근 (JPA Repository)
    │   │       │   ├── dto/            # 데이터 전송 객체 (Request/Response DTOs)
    │   │       │   ├── entity/         # DB 테이블과 매핑 (JPA Entity)
    │   │       │   └── ...             # (필요시 exception, scheduler 등)
    │   │       │
    │   │       ├── 🛡️ security/           # Spring Security (공통 보안 설정)
    │   │       │   ├── SecurityConfig.java
    │   │       │   ├── filter/         # (JwtAuthFilter, JwtExceptionFilter)
    │   │       │   ├── handler/        # (CustomAuthenticationEntryPoint)
    │   │       │   └── util/           # (JwtUtil)
    │   │       │
    │   │       └── 🌍 common/             # 공통 모듈 (여러 도메인에서 사용)
    │   │           ├── config/
    │   │           ├── entity/         # 공통 엔티티 속성
    │   │           ├── exception/      # 공통 예외 처리
    │   │           └── policy/         # 공통 접근 정책
    │   │
    │   └── 📁 resources/
    │       ├── application.yml         # 📋 공통 설정
    │       ├── application-dev.yml     # 💻 개발 환경 설정
    │       ├── application-dev-env.yml # 🔑 개발 환경 변수용 YAML (Git 무시)
    │       └── application-prod.yml    # ☁️ 운영 환경 설정
    │
    └── 📁 test/
        └── 📁 java/
            └── knu/team1/be/boost/
                ├── BoostApplicationTests.java
                │
                └── 🧪 [feature_domain]/   # 도메인별 테스트 코드
                    ├── controller/     # (ExampleControllerTest)
                    └── service/        # (ExampleServiceTest)


🔃 협업 룰, 컨벤션, 브랜치 전략 (Rule, Convention, Git Branch Strategy)

협업 룰

  • 작업 시에는 반드시 이슈 템플릿에 맞게 이슈를 생성하고 작업을 진행한다.
  • PR 제목은 커밋 컨벤션 + 간단 설명 형식
  • 코드 리뷰 시 Approve / Request Changes로 피드백
  • 기능 구현 시 작은 단위로 commit & PR
  • 코드 스타일과 포맷은 Google Java style guide 기준 준수

리뷰 규칙 (Pn 룰)

### 😊 리뷰 규칙을 지킵시다
코드 리뷰는 `Pn`룰에 따라 작성하기.
Reviewer가 피드백을 남길 때 Assignee에게 얼마나 해당 피드백에 대해 강조하고 싶은 지 표현하기 위한 규칙입니다.
- `P1` : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
- `P2` : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
- `P3` : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

커밋 컨벤션

Gitmoji

<Gitmoji>(#이슈번호) <작업 요약>

<상세 설명>
이모지 의미 예시
새로운 기능 추가 ✨ 팀원 API 추가
🐛 버그 수정 🐛 알람 기능 버그 수정
♻️ 리팩토링 ♻️ 메소드 분리
🎨 코드 스타일 변경 🎨 코드 스타일 적용
📝 문서 파일 추가 및 수정 📝 README 업데이트
✏️ 단순 오타 수정 ✏️ typo 수정
테스트 추가 ✅ TaskDetail 테스트 추가
🔥 코드 제거 🔥 사용하지 않는 메소드 제거
🩹 단순한 에러 수정 🩹 API 응답 메시지 오류 수정
🚑️ 핫픽스 🚑️ 특정 에러 핫픽스
🔧 설정 변경 🔧 Config 파일 수정
🚀 배포 관련 수정 🚀 CI/CD workflow 작성

브랜치 전략

브랜치 용도 설명
main 배포용 항상 안정된 버전 유지
develop 개발 통합 기능 완료 후 merge
feat/#이슈-<이름> 기능 개발 새로운 기능 개발 시 사용
refactor/#이슈-<이름> 코드 리팩토링 코드 리팩토링 시 사용
fix/#이슈-<이름> 버그 수정 버그 발생 해결 시 사용
deploy/#이슈-<이름> 배포 관련 배포 관련 작업 시 사용

💡 PR은 반드시 리뷰 후 merge 진행



👥 BOOST 팀원 소개

이진호 김원호 김혜민 서영진 유다연

@treasure-sky

@kmwh

@hyemomo

@seoyoungjin23

@daaoooy
Backend Backend Frontend Backend Frontend



About

KNU1 - BOOST

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages