Skip to content

[REFACTOR] 전체 도메인 점검 #141

@ryuwldnjs

Description

@ryuwldnjs

점검 당시 문제 요약

도메인 상태 발견된 문제
problem ProblemService middleman, 역할 불명확
recommendation God Class, 코드 중복, Repository 직접 접근, 타임아웃 등
solvedac ⚠️ Client/Service 역할 혼재, 타임아웃 미설정
infrastructure ⚠️ mail DIP 위반
team ⚠️ 양방향 의존, TeamActivityService 책임 과잉, 코드 중복
member MemberService God Class, 풀이 관련 책임 혼재
solve (신설 필요) 🆕 member에서 풀이 인증/기록/랭킹 분리 필요
notification ⚠️ Repository 직접 접근
auth 문제 없음
common ⚠️ MissionCyclePolicy 미통합 (3곳에서 중복 구현)
config 문제 없음

도메인별 상세

1. problem

문제점:

  • ProblemService가 단순 위임만 수행 (Middleman) — 4개 메서드 모두 SolvedAcClient.recommendUnsolvedProblems()로 단순 위임, 자체 비즈니스 로직 없음
  • 도메인 역할 불명확 — ProblemController/api/problem/recommend가 solved.ac API를 프록시할 뿐, DB 저장 없음
  • 데드 코드: TeamProblem.java (레거시 엔티티, 어디서도 미사용), TeamProblemRecommendResponse.java (레거시 DTO, 어디서도 미사용)

호출부 분석 (ProblemService 사용처 2곳):

  • RecommendationCreator:89 — 팀 추천 생성 시 problemService.recommend() 호출
  • ProblemController:47,70 — 사용자 API 엔드포인트에서 호출

유지 대상:

  • ProblemSyncService — solved.ac → 로컬 DB 메타데이터 동기화 (역할 명확)
  • Problem, Tag, ProblemTag — 문제 메타데이터 저장소 엔티티 (역할 명확)

해결 방안:

  • ProblemService 제거 → 호출부에서 SolvedAcClient 직접 사용
  • 데드 코드 삭제: TeamProblem.java, TeamProblemRecommendResponse.java
  • problem 도메인을 **"문제 메타데이터 저장소"**로 역할 재정의

2. recommendation ❌ (가장 심각)

문제점:

  • (Critical) 외부 API 타임아웃 미설정 (SolvedAcClient 주석 처리됨)
  • (Critical) 트랜잭션 경계 불명확 (팀별 분리 안 됨)
  • (High) Rate Limiting 미고려 (solved.ac 15분당 256회 제한)
  • (High) 서킷브레이커/Retry 부재
  • (Medium) N+1 쿼리 (getActiveTeams()에서 팀원 조회) — findPendingRecommendations는 JOIN FETCH 적용 완료
  • (Medium) Batch 미사용 (반복문 내 개별 save() 호출)
  • RecommendationService가 God Class (6가지 책임)
  • 다른 도메인 Repository 직접 접근 (TeamRepository, TeamMemberRepository, TeamIncludeTagRepository, ProblemTagRepository)
  • 코드 중복 (createManualRecommendation, prepareDailyRecommendation 80% 동일)

해결 방안:

  • 타임아웃: SolvedAcClient에 connectTimeout(5s), readTimeout(10s) 설정
  • 트랜잭션: 팀별 @Transactional(propagation = REQUIRES_NEW)
  • Rate Limiting: 기존 Redis Rate Limiter 활용 + 배치 분할
  • 서킷브레이커: Resilience4j @CircuitBreaker + @Retry
  • N+1: JOIN FETCH 쿼리 사용
  • Batch: saveAll() 사용
  • Repository 직접 접근 → 각 도메인 Service 통해 접근
  • God Class 분리 → RecommendationCreator, RecommendationEmailService 등으로 분리 완료
  • 코드 중복 제거

참고 문서: docs/plans/scheduler-refactoring/


3. solvedac

문제점:

  • 타임아웃 주석 처리됨 - Critical (Resilience4j 적용 시 함께 해결 예정)
  • SolvedAcClient에 비즈니스 로직 혼재
  • 테스트 메서드 프로덕션 혼재 (recommendTest())
  • 예외 처리 불일치
  • 패키지 위치 부적절
  • 네이밍 부적절

해결 방안:

  • 타임아웃 설정 (Resilience4j 적용 시 함께)
  • Client/Service 역할 분리 (SolvedAcRestClient + SolvedAcClient)
  • 테스트 메서드 삭제, 예외 처리 통일
  • 패키지 이동 + 네이밍 개선
  • BojVerificationFacadeSolvedAcClient 경유로 변경

참고 문서: docs/plans/domain-refactoring/solvedac-역할-분리.md


4. infrastructure

문제점:

  • MailSendService DIP 위반

해결 방안:

  • MailSendService 제거, MailSender 인터페이스 + SesMailSender 구현체로 분리
  • MailBuilder들을 각 도메인 패키지 내로 이동 (recommendation/mail/, team/mail/, member/mail/)

잘 된 점:

  • MailSender/MailMessage: 도메인 무관한 순수 인프라 인터페이스 + DTO
  • SesMailSender: 도메인 엔티티 import 없음 (깔끔한 구현체)
  • CssInliner: 순수 인프라 유틸 (도메인 무관)
  • ratelimit/: Redis 기반 Rate Limiter, Lua 스크립트 원자적 연산, AOP 분리
  • scheduler/: 책임 분리 (스케줄러는 트리거만, 로직은 Service)

참고 문서: docs/plans/domain-refactoring/mail-DIP-위반-해소.md


5. team

문제점:

  • 양방향 의존: team ↔ recommendation (TeamServiceRecommendationService, RecommendationServiceTeamRepository)
  • TeamActivityService 책임 과잉 — team + recommendation + solve 3개 도메인의 조정자인데, 각 도메인 서비스를 거치지 않고 Repository를 직접 뒤지고 있음
  • 다른 도메인 Repository 직접 접근 (TagRepository, RecommendationRepository, MemberSolvedProblemRepository)
  • 코드 중복 (validatePrivateTeamAccess(), assignRanks())

잘 된 점:

  • ✅ 서비스 분리: TeamService, TeamJoinService, TeamActivityService
  • ✅ 권한 검증 로직: validateTeamLeaderAccess()

해결 방안:

  • 양방향 의존 해소: TeamService에서 recommendation 조회 제거 (프론트에서 별도 API 호출)
  • TeamActivityService → solve 서비스 호출로 전환 (SolveService, RankingService)
  • validatePrivateTeamAccess() 중복 제거 → Team 엔티티 메서드 또는 별도 validator
  • Repository 직접 접근 → Service 통해 접근

6. member ✅

문제점:

  • MemberService가 God Class — 회원 관리 + 풀이 인증 + 풀이 통계 + 이메일 변경 + 핸들 등록, 의존성 8개 주입
    • verifyProblemSolved(): 문제해결 인증 → solve로 이동
    • getDailySolved(): 일별 풀이 현황 → solve로 이동
  • RankingService가 member에 위치 — 전체 랭킹은 회원 관리가 아니라 풀이 데이터 집계
  • MemberSolvedProblem 엔티티가 member에 위치 — "풀이"는 회원의 속성이 아니라 독립적 행위
  • 다른 도메인 Repository 직접 접근 (ProblemRepository, TeamMemberRepository)
  • 이메일 인증에서 인프라 관심사 혼재jwtUtil, frontendUrl 직접 참조, URL 조합 로직이 서비스에 위치

해결 방안:


7. solve ✅ (신설)

배경:
"누가 어떤 문제를 풀었는가"에 대한 책임이 member, team 2개 도메인에 흩어져 있었음.

이전 위치 기능 핵심 데이터
member/MemberService 문제해결 인증 (verifyProblemSolved) MemberSolvedProblem
member/MemberService 일별 풀이 현황 (getDailySolved) MemberSolvedProblem
member/RankingService 전체 랭킹 (getGlobalRanking) MemberSolvedProblem
member/domain MemberSolvedProblem 엔티티 MemberSolvedProblem
member/repository MemberSolvedProblemRepository MemberSolvedProblem

solve 도메인 구성:

solve/
├── domain/
│   └── MemberSolvedProblem.java        ← member에서 이동
├── repository/
│   └── MemberSolvedProblemRepository.java  ← member에서 이동
├── service/
│   ├── SolveService.java               ← 문제해결 인증 + 일별 풀이 현황 + 풀이 상태 조회
│   └── RankingService.java             ← 전체 랭킹 (member에서 이동) + assignRanks 통합
├── dto/
│   ├── request/
│   └── response/
│       ├── DailySolvedResponse.java    ← member에서 이동
│       └── GlobalRankingResponse.java  ← member에서 이동
└── SolveController.java

효과:

  • MemberService God Class 해소 (의존성 8개 → 4~5개)
  • assignRanks() 코드 중복 제거 (RankingService + TeamActivityService → RankingService 하나로 통합)
  • TeamActivityService가 Repository 직접 접근 대신 서비스 호출로 전환
  • "풀이"라는 관심사가 한 곳에 응집

#160에서 모두 해결 완료


8. notification

문제점:

  • MemberRepository 직접 접근

해결 방안:

  • memberRepository.findById()memberService.getById()

9. auth ✅

상태: 문제 없음

  • MemberService를 통해 member 도메인 접근 (올바름)

10. common ⚠️

문제점:

  • 미션 사이클(오전 6시 기준) 날짜 계산 로직이 3곳에서 중복 구현됨
    • MemberService.getAdjustedDate() — member 도메인
    • TeamActivityService.getMissionDate() — team 도메인
    • MissionCyclePolicy.getMissionCycleStart() — recommendation 도메인 (유일하게 클래스로 분리됨)

해결 방안:

  • MissionCyclePolicy를 common으로 이동
  • member, team에서 자체 구현 제거 → MissionCyclePolicy 사용으로 통일

11. config ✅

상태: 문제 없음

  • Security, JWT, Redis, Swagger 설정 클래스

공통 이슈 정리

이슈 1: 다른 도메인 Repository 직접 접근

위치 접근 대상
RecommendationService TeamRepository, TeamMemberRepository, TeamIncludeTagRepository, ProblemTagRepository
TeamService TagRepository
TeamActivityService RecommendationRepository, MemberSolvedProblemRepository
MemberService TeamMemberRepository
NotificationService MemberRepository

원칙: 다른 도메인 데이터 필요 시 → 해당 도메인 Service를 통해 접근


이슈 2: 양방향 의존

team ↔ recommendation

해결: 한쪽 의존 제거 (team → recommendation 제거)


이슈 3: God Class

서비스 상태 비고
RecommendationService ✅ 해결됨 #152 — RecommendationCreator, RecommendationEmailService 등으로 분리
MemberService ✅ 해결됨 #160 — solve 분리로 해소

이슈 4: 코드 중복

중복 로직 위치 해결 방안
assignRanks() 순위 부여 RankingService ↔ TeamActivityService ✅ #160에서 solve의 RankingService로 통합
validatePrivateTeamAccess() TeamServiceTeamActivityService Team 엔티티 메서드 또는 별도 validator
미션 사이클 날짜 보정 (오전 6시) MemberServiceTeamActivityServiceMissionCyclePolicy MissionCyclePolicy를 common으로 이동, 통일

우선순위

순위 이슈 도메인 상태
1 타임아웃 미설정 (Critical) solvedac 미해결
2 트랜잭션 경계 불명확 (Critical) recommendation 미해결
3 Rate Limiting 미고려 (High) recommendation 미해결
4 서킷브레이커/Retry 부재 (High) recommendation 미해결
5 solve 도메인 신설 member → solve #160 해결
6 이메일 인증 책임 재배치 member #162 해결
7 Repository 직접 접근 전체 미해결
8 God Class 분리 (MemberService) member #160 해결
9 코드 중복 해소 (assignRanks 등) team, member #160 해결
10 MissionCyclePolicy common 이동 common 미해결
11 N+1 쿼리 / Batch 미사용 recommendation 미해결
12 양방향 의존 해소 team ↔ recommendation 미해결
13 Client/Service 역할 분리 solvedac #142 해결
14 DIP 위반 해소 infrastructure/mail #144 해결
15 Middleman 제거 problem #156 해결

하위 이슈 문서

순위 이슈 참조
1 타임아웃 미설정 solvedac-타임아웃-설정.md
2 트랜잭션 경계 불명확 recommendation-트랜잭션-분리.md
3 Rate Limiting 미고려 recommendation-Rate-Limiting.md
4 서킷브레이커/Retry 부재 recommendation-서킷브레이커.md
5 solve 도메인 신설 #160 해결
6 이메일 인증 책임 재배치 #162 해결
7 Repository 직접 접근 공통-Repository-직접-접근.md
8 N+1 쿼리 / Batch 미사용 recommendation-N+1-쿼리-Batch.md
9 양방향 의존 해소 team-recommendation-양방향-의존-해소.md
10 RecommendationService God Class 분리 #152 해결
11 Client/Service 역할 분리 #142 해결
12 DIP 위반 해소 #144 해결
13 Middleman 제거 #156 해결

Sub-issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions