-
Notifications
You must be signed in to change notification settings - Fork 2
Open
6 / 66 of 6 issues completedDescription
점검 당시 문제 요약
| 도메인 | 상태 | 발견된 문제 |
|---|---|---|
| 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,prepareDailyRecommendation80% 동일)
해결 방안:
- 타임아웃:
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) - 테스트 메서드 삭제, 예외 처리 통일
- 패키지 이동 + 네이밍 개선
-
BojVerificationFacade→SolvedAcClient경유로 변경
참고 문서: docs/plans/domain-refactoring/solvedac-역할-분리.md
4. infrastructure
문제점:
MailSendServiceDIP 위반
해결 방안:
-
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(TeamService→RecommendationService,RecommendationService→TeamRepository) 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 조합 로직이 서비스에 위치
해결 방안:
- solve 도메인 신설 후 풀이 관련 책임 이동 → [REFACTOR] solve 도메인 신설 — 풀이 인증/기록/랭킹 분리 #160 해결
- 이메일 인증 로직 책임 재배치 → [REFACTOR] MemberService 이메일 인증 로직 책임 재배치 #162 해결
EmailVerificationTokenProvider신설: 토큰 생성/검증 (JwtUtil 래핑)EmailChangeMailBuilder: 토큰 생성 + URL 조합 + 메일 빌드MemberService에서jwtUtil,frontendUrl의존 제거
-
teamMemberRepository.existsByMemberId()→teamService.hasMembership()
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
효과:
MemberServiceGod 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() |
TeamService ↔ TeamActivityService |
Team 엔티티 메서드 또는 별도 validator |
| 미션 사이클 날짜 보정 (오전 6시) | MemberService ↔ TeamActivityService ↔ MissionCyclePolicy |
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 해결 |
Reactions are currently unavailable
Sub-issues
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
In progress