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
Copy file name to clipboardExpand all lines: docs/tactical-design.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@
16
16
| Post / Content |`Post`|`PostKeyword`, `PostDocument`, `ContentChunk`| URL은 유일해야 한다. 요약/짧은 요약은 `updateSummaries()`로만 교체된다. 키워드는 `clearKeywords() + addKeyword()` 조합으로만 교체된다. 임베딩 완료 시각은 `markAsEmbedded(LocalDateTime)`으로만 기록된다. `incrementViewCount()`는 비원자적 연산이므로 SQL atomic UPDATE 정책 적용 필요. |`Post`가 핵심 애그리거트 루트다. `PostKeyword`는 `Post` 내부 컬렉션으로 보는 것이 자연스럽다. **`incrementViewCount()` 동시성 정책 미결정** (§1.2 참조). |
17
17
| User Account |`User`|`UserInterestCategory`, `UserInterestKeyword`|`socialType + socialId` 조합은 유일해야 한다. 상태 전이는 `PENDING → ACTIVE → WITHDRAWN → PENDING(재활성화)` 경로만 허용된다. 관심 키워드는 반드시 선택된 관심 카테고리에 속해야 한다. 관심사 교체는 `replaceInterests()`로 단일 트랜잭션 내 불변식 검증과 함께 처리된다. |`User`가 루트다. 계정/온보딩/관심사 불변식을 소유한다. **`replaceInterests()` 도메인 메서드 누락** — 불변식 검증이 서비스 레이어에 산재. |
18
18
| Personalization Profile | 명시적 쓰기 애그리거트 없음 |`PersonalizationProfileDocument`, `UserActivityData`| 같은 `userId` 기준 현재 개인화 프로필 projection은 하나만 유지된다. 프로필 텍스트, 벡터, 핵심 키워드는 함께 재생성된다. | Personalization Profile은 aggregate보다 read model / application service 중심 컨텍스트다. 현재 `PersonalizationProfileService`가 생성 책임을 가진다. |
19
-
| Activity |`ReadPost`, `Bookmark`, `SearchHistory`| 없음 |`Bookmark`는 `userId + postId` 조합이 유일해야 한다. `ReadPost`는 같은 사용자+게시글 중복 저장을 허용하되 `isFirstRead`로 최초 읽기를 구분한다. `SearchHistory`는 같은 검색어를 중복 저장한다 (동일 검색어의 반복 횟수 자체가 개인화 관심 신호가 된다). 행동 기록은 삭제되지 않고 보존된다 (북마크 제외). | 각 행동 기록이 독립 record aggregate처럼 동작한다. `Bookmark`는 `domain/activity/bookmark` slice 아래로 분리되었고 `BookmarkCommandService`/`BookmarkQueryService`/`BookmarkConverter`와 `bookmarks`/`bookmarked_at` rename migration까지 반영되어 있다. `ManyToOne -> id reference` 같은 aggregate 경계 재설계는 별도 이슈로 다루는 편이 안전하다. |
19
+
| Activity |`ReadPost`, `Bookmark`, `SearchHistory`| 없음 |`Bookmark`는 `userId + postId` 조합이 유일해야 한다. `ReadPost`는 같은 사용자+게시글 중복 저장을 허용하되 `ReadPostFirstReadPolicy.isFirstRead()`로 최초 읽기를 구분한다. `SearchHistory`는 같은 검색어를 중복 저장한다 (동일 검색어의 반복 횟수 자체가 개인화 관심 신호가 된다). 행동 기록은 삭제되지 않고 보존된다 (북마크 제외). | 각 행동 기록이 독립 record aggregate처럼 동작한다. `Bookmark`는 `domain/activity/bookmark` slice 아래로, `ReadPost`는 `domain/activity/readpost` slice 아래로 분리되었다. `ReadPostCommandService`/`ReadPostQueryService`/`ReadPostConverter`/`ReadPostFirstReadPolicy`가 읽기 저장/조회/첫 읽기 규칙을 분담하고, SearchHistory만 현재 `ActivityCommandService`에 남아 있다. `ManyToOne -> id reference` 같은 aggregate 경계 재설계는 별도 이슈로 다루는 편이 안전하다. |
20
20
| Search | 명시적 쓰기 애그리거트 없음 |`SearchResult` DTO, `PostDocument` read model | 검색어를 기반으로 검색 결과를 계산한다. 검색 결과는 저장되는 도메인 상태가 아니라 조회 결과다. | Search는 애그리거트보다 query service/read model 중심 컨텍스트다. |
21
21
| Recommendation |**표준: `RecommendationSet`** (현재 코드: `RecommendedPost` 단건) |`RecommendedPost`, `RecommendationHistory`| 같은 `userId + rankOrder` 조합은 유일해야 한다. 새 추천 저장 전 기존 추천은 모두 `RecommendationHistory`로 이동해야 한다. `rankOrder`는 1..N 연속이어야 한다. | 현재 `RecommendedPost` 단건이 루트 역할을 하지만 `RecommendationSet` 개념으로 리팩터링 대상이다 (코드 미반영, 유비쿼터스 언어 README의 문서-코드 동기화 상태 참조). |
22
22
| Auth / Security | 독립 애그리거트 없음 | Refresh Token 저장소, `UserPrincipal`| 토큰 발급/검증/갱신을 수행한다. 사용자 자체는 User Account 컨텍스트에 속한다. | Auth / Security는 도메인 애그리거트보다 보안 애플리케이션/인프라 컨텍스트다. |
| P0 | 사용자 관심사가 변경됨 |`UserInterestsChanged`|`InterestCommandService.updateUserInterests`| 개인화 프로필 재생성, 추천 재생성 | 현재도 관심사 변경 후 개인화 프로필 생성이 호출된다. 이벤트로 분리하기 좋은 지점이다. |
158
158
| P0 | 개인화 프로필이 생성됨 |`PersonalizedProfileGenerated`|`PersonalizationProfileService.generatePersonalizationProfileSync`| 추천 생성, 개인화 검색 준비 완료 | 현재 `PersonalizationProfileService`가 추천 생성을 직접 호출한다. 이벤트 분리 우선순위가 높다. |
159
159
| P0 | 추천이 생성됨 |`RecommendationsGenerated`|`LlmRecommendationService.generateRecommendationsForUser`| Notification, Analytics | 사용자에게 보여줄 현재 추천 목록이 바뀌는 핵심 이벤트다. |
160
-
| P1 | 기술 게시글을 읽음 |`TechnicalPostRead`|`ActivityCommandService.saveReadPost`| 개인화 프로필 갱신, 추천 정책 | 읽기 행동은 개인화 프로필과 읽은 게시글 제외 정책의 핵심 입력이다. |
161
-
| P1 | 기술 게시글을 처음 읽음 |`TechnicalPostFirstRead`|첫 읽기일 때`Post.incrementViewCount`| 인기순 정렬, 분석 | 조회수 증가와 인기순 정렬에 직접 연결된다. |
162
-
| P1 | 기술 게시글을 북마크함 |`TechnicalPostBookmarked`|`ActivityCommandService.addBookmark`| 개인화 프로필 갱신, 추천 튜닝 | 강한 선호 신호로 개인화 품질에 중요하다. |
163
-
| P1 | 북마크가 해제됨 |`BookmarkRemoved`|`ActivityCommandService.deleteBookmark`| 개인화 프로필 갱신, 추천 튜닝 | 선호 신호 제거로 볼 수 있다. |
160
+
| P1 | 기술 게시글을 읽음 |`TechnicalPostRead`|`ReadPostCommandService.saveReadPost`| 개인화 프로필 갱신, 추천 정책 | 읽기 행동은 개인화 프로필과 읽은 게시글 제외 정책의 핵심 입력이다. |
161
+
| P1 | 기술 게시글을 처음 읽음 |`TechnicalPostFirstRead`|`ReadPostFirstReadPolicy.isFirstRead` +`Post.incrementViewCount`| 인기순 정렬, 분석 | 조회수 증가와 인기순 정렬에 직접 연결된다. |
162
+
| P1 | 기술 게시글을 북마크함 |`TechnicalPostBookmarked`|`BookmarkCommandService.addBookmark`| 개인화 프로필 갱신, 추천 튜닝 | 강한 선호 신호로 개인화 품질에 중요하다. |
163
+
| P1 | 북마크가 해제됨 |`BookmarkRemoved`|`BookmarkCommandService.deleteBookmark`| 개인화 프로필 갱신, 추천 튜닝 | 선호 신호 제거로 볼 수 있다. |
164
164
| P1 | 검색어가 기록됨 |`SearchQueryRecorded`|`saveSearchHistory`| 개인화 프로필 갱신, 검색 분석 | 검색 의도는 개인화 프로필의 주요 입력이다. |
165
165
| P1 | 추천 게시글이 클릭됨 |`RecommendationClicked`|`RecommendationHistory.markAsisClicked` 후보 | 추천 분석 | 추천 품질 평가와 모델 개선에 중요하지만 현재 호출 흐름은 약하다. |
166
166
| P2 | RSS 크롤링이 요청됨 |`RssCrawlingRequested`|`CrawlingService.executeCrawling`| 운영 모니터링 | 운영 추적과 중복 실행 분석에 유용하다. |
|`User`|`UserCommandServiceTest` 중심 | User Account aggregate 관점의 직접 `UserTest` 필요 |
510
515
|`UserInterestCategory/Keyword`| repository/service 중심 | User Account 도메인 규칙 테스트 보강 필요 |
511
516
|`PersonalizationProfileDocument`|`PersonalizationProfileServiceTest` + evaluation setup | projection 자체 직접 테스트/세부 parsing 검증은 더 보강 가능 |
512
-
|`ReadPost`|service/repository 중심 | record aggregate 단위 테스트는 선택|
517
+
|`ReadPost`|`ReadPostTest`, `ReadPostFirstReadPolicyTest`, `ReadPostCommandServiceTest`, `ReadPostQueryServiceTest`, `ReadPostRepositoryTest`, `ReadPostIntegrationTest`| 패키지 slice와 첫 읽기 정책까지 분리되었고, 이후 관심사는 동시성/ID reference 같은 별도 이슈다|
513
518
|`Bookmark`|`BookmarkTest`, `BookmarkRepositoryTest`, `BookmarkCommandServiceTest`, `BookmarkQueryServiceTest`, `ActivityControllerIntegrationTest` 중심 | 패키지 slice 분리 이후에도 ID reference 전환 같은 aggregate 경계 재설계는 별도 이슈로 다루는 편이 안전 |
514
519
|`SearchHistory`| repository/service 중심 | record aggregate 단위 테스트는 선택 |
515
520
|`RecommendedPost`| query/controller 중심 | 생성/순위/unique 정책 보강 필요 |
0 commit comments