본문으로 건너뛰기

User Management 도메인 비즈니스 규칙

Event Storming 기반으로 도출된 User Management Domain의 비즈니스 규칙을 정의합니다.


1. 불변식 (Invariants)

1.1 App-User

사이트 ID 필수성

  • 규칙: siteId는 필수 입력 값이며 빈 문자열일 수 없음
  • 위반 시: InvalidSiteIdProvided 이벤트 발행, BAD_REQUEST 에러 반환

운영자 사이트 접근 제약

  • 규칙: operators.site.admin 또는 operators.site.member 그룹의 운영자는 본인 사이트의 app-user만 조회 가능
  • 위반 시: AccessDenied 이벤트 발행, BAD_REQUEST 에러 반환

조회 결과 완전성

  • 규칙: app-user 목록 조회 결과는 반드시 totalCount와 count를 포함해야 함
  • 위반 시: 시스템 오류

1.2 AppUser Report

리포트 생성 데이터 일관성

  • 규칙: 리포트 생성 시 사용자 주기 정보, 수면 일기, 목표 로그, 설문 응답은 동일한 날짜 범위(startDate~endDate)로 조회되어야 함
  • 위반 시: 데이터 불일치

일관성 퍼센티지 상한

  • 규칙: LOT/AET 일관성 퍼센티지는 최대 100을 초과할 수 없음
  • 위반 시: 자동으로 100으로 설정

총 사용 일수 고정값

  • 규칙: totalUsageDays는 항상 app-user의 plan에 정의된 값으로 사용함
  • 위반 시: 없음 (하드코딩된 값)

1.3 ExternalAccess

토큰-사용자 주기 일치

  • 규칙: 외부 토큰의 "sub" 클레임은 요청한 userCycleId와 일치해야 함
  • 위반 시: SubjectMismatch 이벤트 발행, BAD_REQUEST 에러 반환

2. 유효성 규칙 (Validation Rules)

2.1 App-User

페이징 파라미터 검증

  • 규칙: limit이 0이면 기본값 10으로 설정, page가 0이면 기본값 1로 설정
  • 위반 시: 자동 보정

정렬 파라미터 검증

  • 규칙: sortBy가 빈 문자열이면 "createdAt"로, sortOrder는 "desc"로 기본 설정
  • 위반 시: 자동 보정

사이트 ID 검증

  • 규칙: siteId가 빈 문자열인지 검증 필수
  • 위반 시: BAD_REQUEST 에러 반환

2.2 AppUserReport

사용자 주기 존재 검증

  • 규칙: userCycleId로 사용자 주기 정보 조회 가능 여부 검증
  • 위반 시: 조회 실패 에러 반환

날짜 범위 유효성

  • 규칙: startDate와 endDate가 유효한 날짜 범위인지 검증
  • 위반 시: 데이터 조회 실패

2.3 ExternalAccess

토큰 존재 검증

  • 규칙: 토큰이 빈 문자열인지 검증 필수
  • 위반 시: TokenValidationFailed 이벤트 발행, BAD_REQUEST 에러 반환

토큰 타입 검증

  • 규칙: 토큰 타입이 TokenTypeMdAccess인지 검증 필수
  • 위반 시: TokenValidationFailed 이벤트 발행, 토큰 검증 실패

Subject 클레임 타입 검증

  • 규칙: "sub" 클레임이 float64 타입으로 변환 가능한지 검증
  • 위반 시: SubjectMismatch 이벤트 발행, BAD_REQUEST 에러 반환

Subject 클레임 존재 검증

  • 규칙: 토큰 클레임에 "sub"가 존재하는지 검증 필수
  • 위반 시: SubjectMismatch 이벤트 발행, BAD_REQUEST 에러 반환

3. 상태 전이 규칙 (State Transition Rules)

3.1 AppUser 조회 상태 전이

[조회 요청]
├─→ (operators.medi-dashboard.admin 그룹)
│ [권한 검증 생략]
│ ↓
│ [DB 조회]

└─→ (operators.site.admin/member 그룹)
[운영자 정보 조회]
├─→ (사이트 일치)
│ [권한 검증 성공]
│ ↓
│ [DB 조회]

└─→ (사이트 불일치)
[접근 거부]

전환 조건:

  • 조회 요청 → 권한 검증: AppUsersQueryRequested 이벤트 발행
  • 권한 검증 성공 → DB 조회: AccessVerified 이벤트 발행, AppUsersQueried 이벤트 발행
  • 권한 검증 실패 → 접근 거부: AccessDenied 이벤트 발행

3.2 AppUserReport 생성 상태 전이

[리포트 요청]

[사용자 주기 조회]

[현재 일차 계산]

[데이터 조회] (수면 일기, 목표, 설문)

[통계 계산] (성공 횟수, 분모)

[일관성 계산] (LOT, AET)

[요약 생성]

[리포트 생성 완료]

전환 조건:

  • 리포트 요청 → 사용자 주기 조회: AppUserReportRequested 이벤트 발행
  • 사용자 주기 조회 → 데이터 조회: UserCycleRetrieved 이벤트 발행
  • 통계 계산 → 일관성 계산: SuccessCountCalculated 이벤트 발행
  • 요약 생성 → 완료: ReportSummaryGenerated 이벤트 발행

3.3 ExternalAccess 인증 상태 전이

[외부 토큰 수신]

[토큰 검증]
├─→ (검증 성공)
│ [클레임 추출]
│ ├─→ ("sub" 존재 및 일치)
│ │ [Subject 검증 성공]
│ │ ↓
│ │ [리포트 조회]
│ │
│ └─→ ("sub" 없음 또는 불일치)
│ [Subject 불일치]

└─→ (검증 실패)
[인증 거부]

전환 조건:

  • 토큰 수신 → 토큰 검증: ExternalTokenReceived 이벤트 발행
  • 토큰 검증 성공 → 클레임 추출: TokenValidated 이벤트 발행
  • Subject 검증 성공 → 리포트 조회: SubjectVerified 이벤트 발행
  • Subject 불일치 → 거부: SubjectMismatch 이벤트 발행

4. 권한 규칙 (Authorization Rules)

4.1 app-user 목록 조회 권한

APIoperators.medi-dashboard.adminoperators.site.adminoperators.site.member외부 시스템비고
GetAppUsers✓ (모든 사이트)✓ (본인 사이트만)✓ (본인 사이트만)사이트 기반 접근 제어

4.2 app-user 리포트 조회 권한

APIoperators.medi-dashboard.adminoperators.site.adminoperators.site.member외부 시스템비고
GetAppUserReport인증된 운영자만

4.3 외부 토큰 기반 리포트 조회 권한

APIoperators.medi-dashboard.adminoperators.site.adminoperators.site.member외부 시스템비고
GetAppUserReportWithExternalAccessToken유효한 TokenTypeMdAccess 토큰 필요

5. 계산/파생 규칙 (Derivation Rules)

5.1 app-user 목록 조회 계산

그룹 정보 표시 플래그

  • 파생: 운영자가 Welt 사이트 소속인 경우 그룹 정보 표시 플래그 설정
  • 계산식:
    IF operationUser.SiteId == WeltSiteId THEN
    showGroup := true
    ELSE
    showGroup := false

5.2 리포트 일차 계산

현재 일차 계산

  • 파생: 사용자 주기 시작일부터 현재(또는 종료일)까지의 일차 자동 계산

  • 계산식:

    IF NOW() > userCycle.EndAt THEN
    calculateFrom := userCycle.EndAt
    ELSE
    calculateFrom := NOW()

    day := GetDaysDiff(userCycle.StartAt, calculateFrom) + 1

5.3 리포트 통계 계산

성공 횟수 계산

  • 파생: LOT/AET 목표 성공 기록을 집계하여 각각의 성공 횟수 계산
  • 계산 방식:
    successCount.Lot := COUNT(userGoalSuccesses WHERE LotSuccess = true)
    successCount.Aet := COUNT(userGoalSuccesses WHERE AetSuccess = true)

분모용 수면 일기 개수 계산

  • 파생: 첫 번째 rTIB 알고리즘 목표 이후의 수면 일기 개수 계산
  • 계산 방식:
    sleepDiaryCountForDenominator := calculateSleepDiaryCountForDenominator(
    firstRtibAlgorithmUserSleepGoalLog,
    goals,
    diaries
    )

LOT 일관성 퍼센티지 계산

  • 파생: LOT 성공 횟수를 분모로 나눈 백분율 계산
  • 계산식:
    IF sleepDiaryCountForDenominator > 0 THEN
    IF successCount.Lot <= sleepDiaryCountForDenominator THEN
    lotConsistencyPercentage := ROUND(successCount.Lot / sleepDiaryCountForDenominator * 100)
    ELSE
    lotConsistencyPercentage := 100
    ELSE
    lotConsistencyPercentage := 0

AET 일관성 퍼센티지 계산

  • 파생: AET 성공 횟수를 분모로 나눈 백분율 계산
  • 계산식:
    IF sleepDiaryCountForDenominator > 0 THEN
    IF successCount.Aet <= sleepDiaryCountForDenominator THEN
    aetConsistencyPercentage := ROUND(successCount.Aet / sleepDiaryCountForDenominator * 100)
    ELSE
    aetConsistencyPercentage := 100
    ELSE
    aetConsistencyPercentage := 0

수면 기록 퍼센티지 계산

  • 파생: 수면 일기 작성 비율 계산
  • 계산식:
    sleepRecordPercentage := ROUND(totalCount / day * 100)

6. 제약 사항 (Constraints)

6.1 데이터 조회 제약

페이징 기본값

  • 제약: limit 기본값 10, page 기본값 1
  • 적용: 파라미터가 0인 경우 자동 적용

정렬 기본값

  • 제약: sortBy 기본값 "createdAt", sortOrder 기본값 "desc"
  • 적용: 파라미터가 빈 문자열인 경우 자동 적용

lastLogin 정렬 특별 처리

  • 제약: sortBy가 "lastLogin"인 경우 별도의 정렬 로직(searchUsersByLastLogin) 사용 필수

6.2 리포트 생성 제약

총 사용 일수 고정

  • 제약: totalUsageDays는 항상 app-user의 plan에 정의된 값으로 사용함
  • 이유: 시스템 정책에 따른 고정값

날짜 범위 일관성

  • 제약: 수면 일기, 목표 로그 조회는 동일한 startDate~endDate 범위 사용 필수
  • 이유: 데이터 일관성 보장

6.3 보안 제약

전화번호 암호화 조회

  • 제약: 운영자 정보 조회 시 전화번호는 pgp_sym_decrypt로 복호화하여 조회 필수
  • 적용: GetOperationUserById 실행 시

토큰 타입 제한

  • 제약: 외부 액세스 토큰은 TokenTypeMdAccess 타입만 허용
  • 이유: 보안 정책에 따른 접근 제어

Subject 클레임 타입 제한

  • 제약: "sub" 클레임은 float64 타입으로 파싱 가능해야 함
  • 이유: userCycleId(int32)와 비교를 위한 타입 일관성

7. 정책 상세 (Policy Details from Event Storming)

7.1 app-user 목록 조회 정책

WhenThen
AppUsersQueryRequested (operators.site.admin/member)OperationUserRetrieved 발행, AccessVerified 발행
AppUsersQueryRequested (operators.medi-dashboard.admin)AppUsersQueried 발행 (권한 검증 생략)
AccessDeniedInvalidSiteIdProvided 발행, BAD_REQUEST 에러 반환
AppUsersQueriedLastLoginTimeRetrieved 발행 (각 app-user별)
LastLoginTimeRetrievedAppUsersRetrieved 발행

7.2 app-user 리포트 생성 정책

WhenThen
AppUserReportRequestedUserCycleRetrieved 발행, CurrentDayCalculated 발행
UserCycleRetrievedSleepDiariesRetrieved 발행, RtibAlgorithmGoalRetrieved 발행, SleepGoalsRetrieved 발행, QuestionnairesRetrieved 발행
GoalSuccessRecordsRetrievedSuccessCountCalculated 발행
SuccessCountCalculatedSleepDiaryCountCalculated 발행
SleepDiaryCountCalculatedLotConsistencyCalculated 발행, AetConsistencyCalculated 발행
AetConsistencyCalculatedReportSummaryGenerated 발행
ReportSummaryGeneratedAppUserReportGenerated 발행

7.3 외부 토큰 인증 정책

WhenThen
ExternalTokenReceived (빈 문자열)TokenValidationFailed 발행, BAD_REQUEST 에러 반환
TokenValidationRequestedTokenValidated 발행 또는 TokenValidationFailed 발행
TokenValidatedTokenClaimsExtracted 발행
TokenClaimsExtracted ("sub" 없음)SubjectMismatch 발행, BAD_REQUEST 에러 반환
TokenClaimsExtracted ("sub" 불일치)SubjectMismatch 발행, BAD_REQUEST 에러 반환
SubjectVerifiedAppUserReportRequested 발행

변경 이력

버전날짜작성자변경 내용
0.1.02025-12-31dalia@weltcorp.com문서 최초 작성 - GetAppUsers, GetAppUserReport, GetAppUserReportWithExternalAccessToken 기반