You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
가장 구조적인 것: Child 엔티티는 domain/user 에 있는데 ChildService 는 domain/health 에 있다. 아이 하나가 두 도메인에 쪼개져 있어 아이를 쓰는 모든 도메인이 domain.user 를 import 한다(도메인 간 결합 1위: health→user 30건).
남은 241건 중 237건이 "다른 도메인의 리포지토리를 직접 쓰지 않는다" 한 규칙입니다.
이 숫자는 0 으로 만들지 않기로 했습니다. 근거는 docs/quality/architecture-rules.md 에 적었습니다 (#151).
23개 엔티티가 @ManyToOne User 를 들고 있다 — 전부 DB 외래키
JPA 로 외래키를 채우려면 엔티티가 필요하고, 그래서 UserRepository 를 주입합니다. 정리 부채가 아니라 설계 비용입니다. 4a 에서 FacilityWaitlist.child 를 ID 로 바꾼 건 읽는 곳이 없어서 가능했고, 나머지는 응답에서 읽습니다 — 끊으면 목록 화면에 N+1 이 생깁니다.
규칙은 남깁니다. 237 은 "고쳐야 할 목록" 이 아니라 "여기까지는 알고 있다" 는 선이고, 새 도메인이 남의 리포지토리를 주입하면 238 이 되어 빌드가 실패합니다.
이 이슈의 값은 숫자가 아니었습니다
규칙을 맞추려고 코드를 옮기기 전에 그 안을 읽은 것이 값을 했습니다. 각 단계에서 나온 것:
단계
찾은 것
2
"찾을 수 없습니다" 34곳이 500 이었다 (운영 알림까지 울릴 수 있는 자리)
3a
알림 설정 API 가 저장하지 않고 받은 값을 돌려줬다. 통계는 HIGH=5, NORMAL=15 가 코드에 박혀 있었다
4a
소유권 검증 3벌의 답이 404·403·500 으로 갈렸다. 403 은 자녀의 존재를 알려줬다
5b
월령 계산 6벌 중 하나만 단위가 달라 돌 지난 아이 전부에게 신생아 정책이 추천됐다
5c
"일정" API 두 개가 일정이 아니었다 (권장 시기 항상 null, OVERDUE 가 나올 수 없음)
BusinessException 이 51 → 84 로 늘었다. 새로 만진 코드는 전부 그쪽을 쓰기 때문이다. CareServiceException 은 96 → 89 로 거의 그대로다. 즉 새 코드만 정리되고 기존 96곳은
손대지 않았다. 두 체계가 공존하는 상태가 이어지고 있다.
한 번에 바꾸면 상태 코드가 조용히 달라질 수 있다 — 두 예외의 기본 매핑이 다르고, 그걸 기대하는
계약 테스트가 있다. 바꾸려면 매핑 차이를 먼저 테스트로 드러내고 옮겨야 한다.
4번(Child)을 손대지 않은 이유
가장 구조적인 문제로 적어 놨지만 아직 그대로다. Child 엔티티를 옮기면 @ManyToOne 으로
엮인 다른 엔티티의 import 가 전부 따라 움직이고, ArchUnit 베이스라인의 기존 위반이 전부
"새 위반" 으로 보인다 — 파일이 옮겨지면 키가 달라지기 때문이다. 재동결이 필요하고, 그러면
그 시점의 다른 위반까지 같이 묻힌다. 분리해서 한 PR 로 해야 한다.
남은 237개 도메인 경계 위반을 0으로 만들지 않기로 한 이유
23개 엔티티가 @ManyToOne User 를 들고 있다. 이건 설계 취향이 아니라 DB 의 FK 다.
끊으려면 ID 만 들고 조회를 따로 해야 하는데, 목록 화면마다 N+1 이 생긴다. 경계를 지키려고
쿼리를 늘리는 건 거꾸로다. 베이스라인으로 더 늘지 않게 막아 두는 쪽이 맞다.
남은 단계(2·4·5)는 각각 독립적으로 닫을 수 있다. 하위 이슈로 쪼개는 것이 맞아 보인다.
"예외 한 체계로" 를 시작하려고 CareServiceException 89곳을 들여다봤더니, 체계가 지저분한
문제가 아니라 상태 코드가 틀린 문제였다.
CareServiceException 은 상태 코드를 문자열로 들고 다니고, 전역 핸들러의 매핑이 네 가지
(UNAUTHORIZED / FORBIDDEN / USER_NOT_FOUND / KAKAO*)뿐이라 나머지가 전부 500 이다.
throw 47곳 중 39곳이 코드를 주지 않는다.
실제로 나가고 있던 것:
상황
나가던 코드
그 시간에 예약 자리가 없음
500
이미 취소한 예약을 또 취소
500
끝나는 시각이 시작보다 앞
500
남의 알림을 요청
500 (소유권 검사는 403 을 던졌다)
마지막이 가장 나쁘다. catch (Exception e) 가 안쪽의 403 을 삼켜 권한 탐색 시도가 로그에
서버 장애로 쌓인다. 요청 경로에서 그렇게 감싸는 블록이 24곳이었다.
PR #159 에서 고쳤다. 실패 테스트 7개를 먼저 세워 500 쪽으로 실패하는 것을 확인한 뒤 고쳤고,
기존 예약 테스트 3개가 깨졌다 — 그 테스트가 "거부가 500 으로 나간다" 를 고정하고 있었다.
Activity
refactor: 기반 코드가 사용자 도메인을 알던 자리를 끊는다
RosieOh commentedon Oct 23, 2026
여덟 단계로 489 → 241, 그리고 멈추는 선
core분리,careFacility→facilityResourceNotFoundExceptionMap응답 17곳 → DTOChildDirectory), 소유권 답 통일HealthService3분할남은 241건 중 237건이 "다른 도메인의 리포지토리를 직접 쓰지 않는다" 한 규칙입니다.
이 숫자는 0 으로 만들지 않기로 했습니다. 근거는
docs/quality/architecture-rules.md에 적었습니다 (#151).JPA 로 외래키를 채우려면 엔티티가 필요하고, 그래서
UserRepository를 주입합니다. 정리 부채가 아니라 설계 비용입니다. 4a 에서FacilityWaitlist.child를 ID 로 바꾼 건 읽는 곳이 없어서 가능했고, 나머지는 응답에서 읽습니다 — 끊으면 목록 화면에 N+1 이 생깁니다.규칙은 남깁니다. 237 은 "고쳐야 할 목록" 이 아니라 "여기까지는 알고 있다" 는 선이고, 새 도메인이 남의 리포지토리를 주입하면 238 이 되어 빌드가 실패합니다.
이 이슈의 값은 숫자가 아니었습니다
규칙을 맞추려고 코드를 옮기기 전에 그 안을 읽은 것이 값을 했습니다. 각 단계에서 나온 것:
HIGH=5, NORMAL=15가 코드에 박혀 있었다null, OVERDUE 가 나올 수 없음)OpenAPI 237 → 226 경로(중복·거짓 엔드포인트 15개 삭제), 테스트 601 → 650개.
남겨 둔 것
CommunityService가SecurityContextHolder를 직접 쓰는 1건 — baseline 에서 계속 추적catch (Exception e)로 원인을 덮는 자리 (ChatbotService등) —HealthService8곳은 4a 에서 처리RosieOh commentedon Oct 24, 2026
중간 점검 — 세어 본 현재 상태
이슈 본문의 수치를 오늘 다시 세었다. 체크한 것은 측정으로 확인된 것뿐이다.
core의 도메인 컨트롤러CareFacilityApiController373줄core/controller비어 있음)facade/ 코드appapp7개 ·facade0개, 문서 일치Map<String,Object>응답CareServiceException96 ·BusinessException51catch (Exception e)Child위치domain/user, 서비스domain/healthHealthServiceUserServiceCommunityServiceArchUnit 베이스라인은 489 → 241 로 줄었다. 테스트는 601 → 681개.
2번(예외 한 체계로)이 왜 안 움직였나
BusinessException이 51 → 84 로 늘었다. 새로 만진 코드는 전부 그쪽을 쓰기 때문이다.CareServiceException은 96 → 89 로 거의 그대로다. 즉 새 코드만 정리되고 기존 96곳은손대지 않았다. 두 체계가 공존하는 상태가 이어지고 있다.
한 번에 바꾸면 상태 코드가 조용히 달라질 수 있다 — 두 예외의 기본 매핑이 다르고, 그걸 기대하는
계약 테스트가 있다. 바꾸려면 매핑 차이를 먼저 테스트로 드러내고 옮겨야 한다.
4번(Child)을 손대지 않은 이유
가장 구조적인 문제로 적어 놨지만 아직 그대로다.
Child엔티티를 옮기면@ManyToOne으로엮인 다른 엔티티의 import 가 전부 따라 움직이고, ArchUnit 베이스라인의 기존 위반이 전부
"새 위반" 으로 보인다 — 파일이 옮겨지면 키가 달라지기 때문이다. 재동결이 필요하고, 그러면
그 시점의 다른 위반까지 같이 묻힌다. 분리해서 한 PR 로 해야 한다.
남은 237개 도메인 경계 위반을 0으로 만들지 않기로 한 이유
23개 엔티티가
@ManyToOne User를 들고 있다. 이건 설계 취향이 아니라 DB 의 FK 다.끊으려면 ID 만 들고 조회를 따로 해야 하는데, 목록 화면마다 N+1 이 생긴다. 경계를 지키려고
쿼리를 늘리는 건 거꾸로다. 베이스라인으로 더 늘지 않게 막아 두는 쪽이 맞다.
남은 단계(2·4·5)는 각각 독립적으로 닫을 수 있다. 하위 이슈로 쪼개는 것이 맞아 보인다.
RosieOh commentedon Oct 24, 2026
2단계를 파다가 실제 결함이 나왔다 — #158 / PR #159
"예외 한 체계로" 를 시작하려고
CareServiceException89곳을 들여다봤더니, 체계가 지저분한문제가 아니라 상태 코드가 틀린 문제였다.
CareServiceException은 상태 코드를 문자열로 들고 다니고, 전역 핸들러의 매핑이 네 가지(
UNAUTHORIZED/FORBIDDEN/USER_NOT_FOUND/KAKAO*)뿐이라 나머지가 전부 500 이다.throw 47곳 중 39곳이 코드를 주지 않는다.
실제로 나가고 있던 것:
마지막이 가장 나쁘다.
catch (Exception e)가 안쪽의 403 을 삼켜 권한 탐색 시도가 로그에서버 장애로 쌓인다. 요청 경로에서 그렇게 감싸는 블록이 24곳이었다.
PR #159 에서 고쳤다. 실패 테스트 7개를 먼저 세워 500 쪽으로 실패하는 것을 확인한 뒤 고쳤고,
기존 예약 테스트 3개가 깨졌다 — 그 테스트가 "거부가 500 으로 나간다" 를 고정하고 있었다.
2단계에 남은 것
#159 는 상태 코드가 틀린 곳만 고쳤다. 두 예외 체계는 그대로 공존한다.
CareServiceException은 여전히 문자열 코드로 상태를 정하고, 코드를 주지 않으면 500 이다.같은 함정이 남아 있다.
조용히 달라지고, 그걸 기대하는 계약 테스트가 있다.
external/publicdata)의catch (Exception e)는 원인을 덮어쓰는 문제가그대로다. HTTP 와 무관해 fix: 사용자 잘못을 서버 잘못으로 보고하지 않는다 (입력·권한 오류가 500) #159 범위에서 뺐지만, 실패 원인이 로그에만 남고 동기화 상태에는
드러나지 않는다.
근본 해결은
CareServiceException을 없애고ErrorCode를 직접 드는BusinessException하나로 모으는 것이다.
ErrorCode는HttpStatus를 직접 들고 있어 이 함정이 없다.