본문으로 건너뛰기

Group 도메인 모델

데이터 저장 규칙

  • Group 도메인 관련 모든 데이터는 group PostgreSQL 스키마에 저장되어야 합니다. (business-rules.md 1.1)
  • 그룹과 연결된 역할(Role) 정보는 IAM 도메인(iam 스키마)을 참조합니다.
  • 그룹과 연결된 사용자(User) 정보는 User 도메인(private, operation 스키마)을 참조합니다.
  • 사용자-그룹 관계는 사용자 유형에 따라 적절한 스키마에 저장됩니다:
    • 고객(환자) 사용자: private.user_groups
    • 내부 운영자 사용자: operation.user_groups
  • 그룹-역할 관계는 group.group_roles 테이블에 저장됩니다.
  • 본 문서의 Prisma 스키마 섹션에는 Group 도메인이 직접 관리하는 엔티티(Group, GroupMetadata, GroupRole, UserGroup 등)만 정의합니다.
  • User 및 Role 엔티티의 전체 Prisma 스키마 정의는 각 도메인의 domain-model.md 문서를 참조하세요.

1. 엔티티 관계도 (ERD)

2. 엔티티

2.1 Group

/**
* 사용자를 공통된 특성, 자격, 소속에 따라 분류하고 식별하는 엔티티.
* 그룹은 여러 역할을 가질 수 있으며, 여러 사용자를 포함할 수 있습니다.
*/
interface Group {
id: GroupId; // PK
name: string; // Unique
description?: string;
groupType: GroupType; // 그룹 유형 (Patient, Operation, Development)
status: GroupStatus; // 그룹 상태 (ACTIVE, INACTIVE, DEPRECATED, ARCHIVED)
isActive: boolean; // 활성/비활성 상태
createdAt: Date;
updatedAt: Date;

// 관계 필드 (런타임 로드)
roles?: RoleReference[]; // IAM 도메인 참조 (다대다)
members?: UserReference[]; // User 도메인 참조
metadata?: GroupMetadata[]; // 그룹 메타데이터
parentGroups?: GroupHierarchy[]; // 상위 그룹
childGroups?: GroupHierarchy[]; // 하위 그룹
}

2.2 UserGroup (Association Entity)

/**
* User와 Group 간의 다대다 관계를 나타내는 연결 엔티티.
* 사용자 유형에 따라 다른 스키마에 저장됩니다.
*/
interface UserGroup {
userId: UserId; // Composite PK, FK
groupId: GroupId; // Composite PK, FK
assignedAt: Date;
assignedBy?: UserId; // 할당 담당자
isActive: boolean; // 멤버십 활성 상태
}

2.3 GroupRole (Association Entity)

/**
* Group과 Role 간의 다대다 관계를 나타내는 연결 엔티티.
* 그룹에 할당된 역할은 해당 그룹의 모든 멤버에게 적용됩니다.
*/
interface GroupRole {
groupId: GroupId; // Composite PK, FK
roleId: RoleId; // Composite PK, FK (iam.roles)
assignedAt: Date;
assignedBy?: UserId; // 할당 담당자
isActive: boolean; // 역할 할당 활성 상태
}

2.4 GroupMetadata

/**
* 그룹의 복잡한 비즈니스 메타데이터를 별도로 관리하는 엔티티.
*
* 🎯 설계 목적:
* - 핵심 Group 엔티티와 복잡한 설정 정보 분리
* - 그룹 타입별 다양한 정책/규칙과 안정적인 식별자 분리
* - 성능 최적화: 기본 그룹 조회 시 무거운 메타데이터 제외
* - 확장성: 새로운 그룹 정책 추가 시 스키마 변경 없이 JSON으로 유연하게 확장
*/
interface GroupMetadata {
id: string; // PK
groupId: GroupId; // FK (unique)
groupType: string; // PATIENT, OPERATION, DEVELOPMENT
accessLevel: string; // GENERAL, LIMITED_ACCESS, INTERNAL
maxMembers?: number; // 최대 멤버 수 (선택적)
autoAssignRules?: AutoAssignConfig; // 자동 할당 규칙
businessRules: GroupBusinessRuleConfig; // 비즈니스 규칙
organizationInfo?: OrganizationConfig; // 조직 정보 (필요시)
createdAt: Date;
updatedAt: Date;
}

/**
* 자동 할당 규칙 설정 예시
*/
interface AutoAssignConfig {
criteria: string; // 할당 조건 (예: "plan.therapeutic", "role.admin")
conditions?: {
userType?: string[];
planCategories?: string[];
emailDomain?: string;
department?: string;
};
maxAutoAssign?: number; // 자동 할당 최대 수
priority: number; // 여러 규칙 중 우선순위
}

/**
* 그룹별 비즈니스 규칙 설정 예시
*/
interface GroupBusinessRuleConfig {
dataAccess: {
restrictionLevel: 'none' | 'partial' | 'strict';
allowedOperations: string[];
};
sessionManagement: {
maxConcurrentSessions?: number;
sessionTimeoutMinutes?: number;
};
auditRequirement: {
level: 'basic' | 'standard' | 'full';
retentionDays: number;
};
notifications: {
channels: string[]; // email, sms, push
frequency: 'immediate' | 'daily' | 'weekly';
};
}

/**
* 조직 정보 설정 (필요시)
*/
interface OrganizationConfig {
department?: string;
location?: string;
costCenter?: string;
manager?: string;
externalSystemIds?: Record<string, string>; // 외부 시스템 연동 ID
}

2.5 GroupTemplate

/**
* 그룹 생성을 위한 템플릿 엔티티.
*
* 🎯 설계 목적:
* - 그룹 생성 시 일관성 보장
* - 그룹 타입별 표준화된 설정으로 운영 실수 방지
* - 새로운 조직 구조나 사용자 분류 추가 시 재사용 가능한 기본 설정 제공
* - 동적 그룹 생성을 위한 템플릿 기반 자동화
*
* 💡 사용 시나리오:
* 1. 새로운 환자 그룹 생성 시 "PATIENT" 템플릿 사용
* 2. 새로운 클리닉이나 부서 추가 시 조직 그룹 템플릿으로 일관된 구조 생성
* 3. 계절별/이벤트별 임시 그룹 생성 시 기본 템플릿 복사 후 특화 설정
* 4. 사용자 증가에 따른 자동 그룹 분할 시 템플릿 기반 새 그룹 생성
*/
interface GroupTemplate {
id: GroupTemplateId; // PK
name: string; // Unique
description?: string;
groupCategory: GroupCategory; // PATIENT, OPERATION, DEVELOPMENT
defaultConfig: DefaultGroupConfig; // 기본 설정 구조
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}

/**
* 템플릿 기본 설정 구조
*/
interface DefaultGroupConfig {
groupType: string;
accessLevel: string;
maxMembers?: number;
defaultRoleIds?: string[]; // 기본 할당 역할 (IAM 도메인)
defaultBusinessRules: GroupBusinessRuleConfig;
autoAssignRules?: AutoAssignConfig[];
organizationStructure?: {
hierarchyLevel: number; // 조직 계층 레벨
allowSubGroups: boolean;
inheritanceRules: string[]; // 상위 그룹으로부터 상속받을 속성들
};
}

2.6 GroupHierarchy

/**
* 그룹 간 계층 구조를 나타내는 엔티티.
* 조직도나 팀 구조를 표현합니다.
*/
interface GroupHierarchy {
parentGroupId: GroupId; // Composite PK, FK
childGroupId: GroupId; // Composite PK, FK
createdAt: Date;
createdBy?: UserId; // 계층 구조 생성자
}

2.7 Role (Group Domain View)

/**
* 역할 엔티티 (Group 도메인 관점).
* 실제 데이터는 `iam.roles` 테이블에 저장되며, IAM 도메인이 관리합니다.
* Group 도메인은 그룹과 역할의 연결 관계를 관리합니다.
*/
interface RoleReference {
id: RoleId;
name: string;
roleType: RoleType;
isActive: boolean;
// IAM 도메인에서 관리하는 기타 필드 (permissions 등)
}

2.8 User (Group Domain View)

/**
* 사용자 엔티티 (Group 도메인 관점).
* 실제 데이터는 사용자 유형에 따라 다른 스키마에 저장됩니다:
* - 고객(환자) 사용자: `private.users` 테이블
* - 내부 운영자 사용자: `operation.users` 테이블
* Group 도메인은 사용자-그룹 멤버십을 관리합니다.
*/
interface UserReference {
id: UserId;
userType: UserType;
// User 도메인에서 관리하는 기타 필드 (email, name 등)
}

3. 설계 철학: 왜 Metadata와 Template 모델을 분리했는가? 🤔

핵심 설계 원칙: 동적 그룹 관리 (Dynamic Group Management)

Group 도메인에서 핵심 엔티티메타데이터, 템플릿을 분리한 이유는 다음과 같습니다:

🔹 1. 다양한 그룹 타입별 유연한 설정

// ❌ 모든 그룹 설정을 하나의 테이블에 섞은 경우
model Group {
id: string
name: string

// 환자 그룹 전용 설정 (운영자 그룹에서는 불필요)
maxPatients: number?
treatmentPlan: string?

// 운영자 그룹 전용 설정 (환자 그룹에서는 불필요)
department: string?
accessLevel: string?
auditRequired: boolean?

// 개발자 그룹 전용 설정
gitlabProjectId: string?
deploymentAccess: boolean?
}

// ✅ 그룹 타입별로 유연하게 분리한 경우
model Group {
id: string // 핵심 식별자 (모든 그룹 공통)
name: string // 기본 정보 (모든 그룹 공통)
groupType: string // 그룹 타입으로 구분
}

model GroupMetadata {
groupId: string
groupType: string // PATIENT | OPERATION | DEVELOPMENT
// groupType에 따라 다른 설정을 JSON으로 저장
businessRules: Json // 타입별 특화된 규칙들
autoAssignRules: Json // 타입별 자동 할당 규칙
}

🔹 2. 동적 그룹 할당과 자동화

// 💡 실제 비즈니스 시나리오: 사용자가 가입할 때 자동 그룹 할당
async function autoAssignUserToGroups(user: User) {
const applicableTemplates = await groupTemplateRepo.findByUserCriteria(user);

for (const template of applicableTemplates) {
const autoAssignRules = template.defaultConfig.autoAssignRules;

if (evaluateAssignmentCriteria(user, autoAssignRules)) {
// 템플릿 기반으로 새 그룹 생성 또는 기존 그룹에 할당
const targetGroup = await findOrCreateGroupFromTemplate(template, user);
await assignUserToGroup(user.id, targetGroup.id);
}
}
}

// 환자 그룹 자동 할당 예시
{
"autoAssignRules": [
{
"criteria": "user.plan.category === 'THERAPEUTIC'",
"conditions": {
"planCategories": ["THERAPEUTIC"],
"userType": ["PATIENT"]
},
"maxAutoAssign": 1000,
"priority": 1
}
]
}

// 운영자 그룹 자동 할당 예시
{
"autoAssignRules": [
{
"criteria": "user.email.endsWith('@weltcorp.com')",
"conditions": {
"emailDomain": "weltcorp.com",
"userType": ["OPERATION_USER"]
},
"priority": 2
}
]
}

🔹 3. 조직 확장에 따른 확장성

// 💡 실제 확장 시나리오: 새로운 클리닉 추가
async function addNewClinic(clinicInfo: ClinicInfo) {
// 1. 클리닉 운영자 그룹 템플릿 로드
const operatorTemplate = await groupTemplateRepo.findByCategory('CLINIC_OPERATOR');

// 2. 클리닉별 맞춤 설정
const clinicConfig = {
...operatorTemplate.defaultConfig,
organizationInfo: {
department: `Clinic_${clinicInfo.code}`,
location: clinicInfo.address,
manager: clinicInfo.managerId
},
businessRules: {
...operatorTemplate.defaultConfig.businessRules,
dataAccess: {
restrictionLevel: 'partial', // 클리닉별 데이터만 접근
allowedOperations: ['view_patients', 'create_reports']
}
}
};

// 3. 새 그룹 생성
const clinicGroup = await groupService.createFromTemplate(
operatorTemplate,
clinicConfig,
`clinic_operators_${clinicInfo.code}`
);

// 4. 클리닉 직원들 자동 할당
await autoAssignClinicStaff(clinicInfo.staffEmails, clinicGroup.id);
}

🔹 4. 성능 최적화: 계층적 조회

-- ❌ 모든 그룹 정보가 한 테이블에 섞여 있는 경우
SELECT * FROM groups
WHERE groupType = 'PATIENT' AND isActive = true;
-- → 불필요한 organization_info, audit_config 등도 함께 조회

-- ✅ 계층적 조회로 필요한 것만
-- 1단계: 기본 그룹 목록 (빠름)
SELECT id, name, groupType FROM groups
WHERE groupType = 'PATIENT' AND isActive = true;

-- 2단계: 특정 그룹의 상세 설정 (필요시에만)
SELECT g.*, gm.* FROM groups g
LEFT JOIN group_metadata gm ON g.id = gm.group_id
WHERE g.id = 'specific_group_id';

🔹 5. 그룹 계층 구조와 권한 상속

// 💡 조직 계층 구조 시나리오
async function createDepartmentHierarchy() {
// 1. 상위 부서 그룹 생성
const mainDepartment = await createGroupFromTemplate('DEPARTMENT', {
name: 'Clinical Operations',
businessRules: {
dataAccess: { restrictionLevel: 'none' },
auditRequirement: { level: 'full' }
}
});

// 2. 하위 팀 그룹들 생성 (상위 그룹 설정 상속)
const teams = ['Team_A', 'Team_B', 'Team_C'];
for (const teamName of teams) {
const teamGroup = await createGroupFromTemplate('TEAM', {
name: teamName,
// 상위 그룹 설정 상속
businessRules: {
...mainDepartment.metadata.businessRules,
// 팀별 추가 제한
dataAccess: { restrictionLevel: 'partial' }
}
});

// 계층 구조 생성
await groupHierarchyService.addChild(mainDepartment.id, teamGroup.id);
}
}

📊 Group 도메인 특화 이점

측면단일 모델분리 모델
그룹 타입 확장스키마 변경 필요JSON 설정으로 유연한 확장
자동 할당하드코딩된 로직템플릿 기반 설정 주도
조직 확장수동 설정 (실수 위험)템플릿으로 일관된 구조
권한 계산복잡한 쿼리메타데이터 기반 효율적 계산
다국가 운영지역별 스키마 필요메타데이터로 지역 특화 설정

🌟 Group 도메인의 특별한 가치

Group 도메인의 Metadata와 Template 분리는 특히 다음과 같은 동적이고 확장 가능한 사용자 분류 시스템을 가능하게 합니다:

  1. 🔄 자동화된 그룹 관리: 사용자 속성 변화에 따른 자동 그룹 이동
  2. 🏢 조직 구조 변화 대응: 인수합병, 조직개편 등에 유연한 대응
  3. 🌍 다국가/다지역 확장: 지역별 법규나 문화에 맞춘 그룹 정책
  4. 📊 동적 권한 계산: 그룹 메타데이터 기반 실시간 권한 계산
  5. 🎯 맞춤형 서비스: 그룹별 특화된 기능과 콘텐츠 제공

이러한 설계를 통해 확장 가능한 사용자 분류, 자동화된 그룹 관리, 조직 변화에 유연한 대응을 달성할 수 있습니다! 🎯

3. 값 객체

3.1 GroupId

type GroupId = string; // UUID or other unique identifier

3.2 GroupTemplateId

type GroupTemplateId = string; // UUID or other unique identifier

3.3 PlanId

type PlanId = string; // From Plan Domain

3.4 UserId

type UserId = string; // From User Domain

3.5 GroupType

/**
* 그룹 유형을 정의하는 열거형.
*/
enum GroupType {
PATIENT = 'PATIENT', // 환자 그룹
OPERATION = 'OPERATION', // 내부 운영자 그룹
TESTER = 'TESTER', // 테스터 그룹
EXTERNAL = 'EXTERNAL' // 외부 협력사 그룹
}

3.6 UserType

/**
* 사용자 유형 (User 도메인과 일치).
*/
enum UserType {
PATIENT = 'PATIENT', // 환자(고객)
OPERATION_USER = 'OPERATION_USER', // 내부 운영자
SERVICE_ACCOUNT = 'SERVICE_ACCOUNT' // 서비스 계정
}

3.7 GroupTemplateData

/**
* 그룹 템플릿의 데이터 구조.
*/
interface GroupTemplateData {
defaultPlanId: PlanId;
defaultMetadata: Record<string, string>;
groupType: GroupType;
isActive: boolean;
maxMembers?: number;
}

3.8 GroupMembershipInfo

/**
* 그룹 멤버십 정보.
*/
interface GroupMembershipInfo {
userId: UserId;
groupId: GroupId;
assignedAt: Date;
assignedBy?: UserId;
isActive: boolean;
membershipType: 'DIRECT' | 'INHERITED'; // 직접 할당 vs 계층 상속
}

4. 집계 (Aggregates)

4.1 Group Aggregate

  • 루트: Group
  • 엔티티: UserGroup (관계), GroupMetadata, GroupHierarchy
  • 값 객체: GroupId, GroupName (unique), Description, GroupType, IsActive
  • 참조: Plan (Plan 도메인, 단일 필수), User (User 도메인, 다대다)
  • 불변식:
    • Group 이름은 고유해야 함
    • 모든 그룹은 반드시 하나의 플랜과 연결되어야 함
    • 활성/비활성 상태 관리
    • 그룹 계층 구조에서 순환 참조 금지
    • 사용자는 동일한 그룹에 중복 할당 불가

4.2 GroupTemplate Aggregate

  • 루트: GroupTemplate
  • 값 객체: GroupTemplateId, GroupTemplateData
  • 불변식:
    • 템플릿 이름은 고유해야 함
    • 템플릿 데이터는 유효한 구조여야 함

5. 도메인 서비스

5.1 GroupService

interface GroupService {
createGroup(name: string, groupType: GroupType, planId: PlanId, description?: string): Promise<Group>;
getGroup(id: GroupId): Promise<Group | null>;
getGroupByName(name: string): Promise<Group | null>;
getAllGroups(filter?: { groupType?: GroupType; planId?: PlanId; isActive?: boolean }): Promise<Group[]>;
updateGroup(id: GroupId, data: { name?: string; description?: string; isActive?: boolean }): Promise<Group>;
deleteGroup(id: GroupId): Promise<void>; // Soft delete or check dependencies
validateGroupConfiguration(groupId: GroupId): Promise<{ isValid: boolean; errors: string[] }>;
}

5.2 GroupMembershipService

interface GroupMembershipService {
assignUserToGroup(userId: UserId, groupId: GroupId, assignedBy?: UserId): Promise<void>;
removeUserFromGroup(userId: UserId, groupId: GroupId): Promise<void>;
getUserGroups(userId: UserId): Promise<Group[]>;
getGroupMembers(groupId: GroupId): Promise<UserReference[]>;
transferUserToGroup(userId: UserId, fromGroupId: GroupId, toGroupId: GroupId): Promise<void>;
validateUserGroupAssignment(userId: UserId, groupId: GroupId): Promise<boolean>;
getGroupMembershipInfo(userId: UserId, groupId: GroupId): Promise<GroupMembershipInfo | null>;
}

5.3 GroupMetadataService

interface GroupMetadataService {
setGroupMetadata(groupId: GroupId, key: string, value: string): Promise<void>;
getGroupMetadata(groupId: GroupId, key?: string): Promise<GroupMetadata[]>;
deleteGroupMetadata(groupId: GroupId, key: string): Promise<void>;
updateGroupMetadata(groupId: GroupId, key: string, value: string): Promise<void>;
}

5.4 GroupTemplateService

interface GroupTemplateService {
createTemplate(name: string, description: string, templateData: GroupTemplateData): Promise<GroupTemplate>;
getTemplate(id: GroupTemplateId): Promise<GroupTemplate | null>;
getAllTemplates(filter?: { isActive?: boolean }): Promise<GroupTemplate[]>;
updateTemplate(id: GroupTemplateId, data: Partial<GroupTemplate>): Promise<GroupTemplate>;
deleteTemplate(id: GroupTemplateId): Promise<void>;
createGroupFromTemplate(templateId: GroupTemplateId, groupName: string): Promise<Group>;
}

5.5 GroupHierarchyService

interface GroupHierarchyService {
createHierarchy(parentGroupId: GroupId, childGroupId: GroupId, createdBy?: UserId): Promise<void>;
removeHierarchy(parentGroupId: GroupId, childGroupId: GroupId): Promise<void>;
getParentGroups(groupId: GroupId): Promise<Group[]>;
getChildGroups(groupId: GroupId): Promise<Group[]>;
getGroupHierarchy(rootGroupId: GroupId): Promise<GroupHierarchyTree>;
validateHierarchy(parentGroupId: GroupId, childGroupId: GroupId): Promise<boolean>;
detectCircularReference(groupId: GroupId): Promise<boolean>;
}

5.6 GroupPlanAssignmentService

/**
* Group 도메인 관점에서 그룹과 플랜의 연결을 관리하는 서비스.
* Plan 도메인과 협력하여 플랜 할당을 처리합니다.
*/
interface GroupPlanAssignmentService {
assignPlanToGroup(groupId: GroupId, planId: PlanId): Promise<void>; // Plan 도메인과 협력
changePlanForGroup(groupId: GroupId, newPlanId: PlanId): Promise<void>;
getGroupPlan(groupId: GroupId): Promise<PlanReference | null>;
validatePlanAssignment(groupId: GroupId, planId: PlanId): Promise<boolean>;
analyzeUserImpactForPlanChange(groupId: GroupId, newPlanId: PlanId): Promise<PlanChangeImpact>;
}

6. 도메인 이벤트

6.1 Group 생명주기 이벤트

  • GroupCreated: 새로운 그룹 생성
  • GroupUpdated: 그룹 정보 수정
  • GroupDeleted: 그룹 삭제
  • GroupActivated: 그룹 활성화
  • GroupDeactivated: 그룹 비활성화

6.2 그룹 멤버십 이벤트

  • UserAssignedToGroup: 사용자가 그룹에 할당됨
  • UserRemovedFromGroup: 사용자가 그룹에서 제거됨
  • UserTransferredBetweenGroups: 사용자가 그룹 간 이동
  • GroupMembershipValidationFailed: 그룹 멤버십 검증 실패

6.3 그룹-플랜 관계 이벤트

  • PlanAssignedToGroup: 그룹에 플랜 할당
  • PlanChangedForGroup: 그룹의 플랜 변경
  • PlanUnassignedFromGroup: 그룹에서 플랜 할당 해제

6.4 그룹 계층 구조 이벤트

  • GroupHierarchyCreated: 그룹 계층 구조 생성
  • GroupHierarchyRemoved: 그룹 계층 구조 제거
  • CircularReferenceDetected: 순환 참조 감지

6.5 그룹 메타데이터 이벤트

  • GroupMetadataUpdated: 그룹 메타데이터 변경
  • GroupTemplateCreated: 그룹 템플릿 생성
  • GroupFromTemplateCreated: 템플릿으로부터 그룹 생성

6.6 그룹 검증 이벤트

  • GroupValidationFailed: 그룹 구성 검증 실패
  • GroupConfigurationChanged: 그룹 구성 변경

7. 도메인 규칙

상세 내용은 business-rules.md 참조

주요 규칙 카테고리:

  1. 그룹 정의 규칙: 그룹 목적, 구성 요소, 유형별 분류
  2. 그룹 유형 규칙: 환자, 운영자, 테스터 그룹
  3. 그룹-플랜 관계 규칙: 필수 플랜 연결, 플랜 변경
  4. 그룹 멤버십 규칙: 사용자 할당/해제, 멤버십 관리
  5. 그룹 생명주기 규칙: 생성, 수정, 삭제, 상태 관리
  6. 그룹 계층 구조 규칙: 계층 생성, 순환 참조 방지
  7. 그룹 템플릿 규칙: 템플릿 생성, 그룹 생성
  8. 감사 및 보안 규칙: 감사 로그, 접근 제어

8. 데이터베이스 스키마 (Prisma)

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["group", "iam", "private", "operation"]
}

// --- 외부 도메인 모델 (참조용 stub) ---

// Role 모델 (iam 스키마)
// 실제 정의는 iam/domain-model.md 참조
model Role {
id String @id @map("id")
name String @unique
roleType String @map("role_type")
// ... other fields defined in IAM domain
groupRoles GroupRole[] @relation("RoleGroups") // Role이 할당된 GroupRole 목록

@@map("roles")
@@schema("iam")
}

// User 모델 (private 스키마) - 환자용
// 실제 정의는 user/domain-model.md 참조
model User {
id String @id @map("id")
userType String @map("user_type")
// ... other fields defined in User domain
userGroups UserGroup[] @relation("UserMemberships")

@@map("users")
@@schema("private")
}

// User 모델 (operation 스키마) - 내부 운영자용
// 실제 정의는 user/domain-model.md 참조
model OperationUser {
id String @id @map("id")
userType String @map("user_type")
// ... other fields defined in User domain
userGroups OperationUserGroup[] @relation("OperationUserMemberships")

@@map("users")
@@schema("operation")
}

// --- Group 도메인 모델 (group 스키마) ---

// Group 모델
model Group {
id String @id @default(uuid()) @map("id")
name String @unique
description String?
groupType String @map("group_type") // GroupType enum
status String @default("ACTIVE") @map("status") // GroupStatus enum
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
groupRoles GroupRole[] @relation("GroupRoles")
userGroups UserGroup[] @relation("GroupMemberships")
operationUserGroups OperationUserGroup[] @relation("OperationGroupMemberships")
metadata GroupMetadata[]
parentHierarchies GroupHierarchy[] @relation("ChildGroup")
childHierarchies GroupHierarchy[] @relation("ParentGroup")

@@map("groups")
@@schema("group")
}

// User Group 모델 - 환자용
model UserGroup {
userId String @map("user_id") // private.users(id)
groupId String @map("group_id") // group.groups(id)
assignedAt DateTime @default(now()) @map("assigned_at")
assignedBy String? @map("assigned_by")
isActive Boolean @default(true) @map("is_active")

// 관계
user User @relation("UserMemberships", fields: [userId], references: [id], onDelete: Cascade)
group Group @relation("GroupMemberships", fields: [groupId], references: [id], onDelete: Cascade)

@@id([userId, groupId])
@@map("user_groups")
@@schema("group")
}

// Group Role 모델
model GroupRole {
groupId String @map("group_id") // group.groups(id)
roleId String @map("role_id") // iam.roles(id)
assignedAt DateTime @default(now()) @map("assigned_at")
assignedBy String? @map("assigned_by")
isActive Boolean @default(true) @map("is_active")

// 관계
group Group @relation("GroupRoles", fields: [groupId], references: [id], onDelete: Cascade)
role Role @relation("RoleGroups", fields: [roleId], references: [id], onDelete: Cascade)

@@id([groupId, roleId])
@@map("group_roles")
@@schema("group")
}

// User Group 모델 - 내부 운영자용
model OperationUserGroup {
userId String @map("user_id") // operation.users(id)
groupId String @map("group_id") // group.groups(id)
assignedAt DateTime @default(now()) @map("assigned_at")
assignedBy String? @map("assigned_by")
isActive Boolean @default(true) @map("is_active")

// 관계
user OperationUser @relation("OperationUserMemberships", fields: [userId], references: [id], onDelete: Cascade)
group Group @relation("OperationGroupMemberships", fields: [groupId], references: [id], onDelete: Cascade)

@@id([userId, groupId])
@@map("user_groups")
@@schema("operation")
}

// Group 메타데이터 모델
model GroupMetadata {
groupId String @map("group_id")
metadataKey String @map("metadata_key")
metadataValue String @map("metadata_value")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)

@@id([groupId, metadataKey])
@@map("group_metadata")
@@schema("group")
}

// Group 템플릿 모델
model GroupTemplate {
id String @id @default(uuid()) @map("id")
name String @unique
description String?
templateData Json @map("template_data") // GroupTemplateData
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

@@map("group_templates")
@@schema("group")
}

// Group 계층 구조 모델
model GroupHierarchy {
parentGroupId String @map("parent_group_id")
childGroupId String @map("child_group_id")
createdAt DateTime @default(now()) @map("created_at")
createdBy String? @map("created_by")

// 관계
parentGroup Group @relation("ParentGroup", fields: [parentGroupId], references: [id], onDelete: Cascade)
childGroup Group @relation("ChildGroup", fields: [childGroupId], references: [id], onDelete: Cascade)

@@id([parentGroupId, childGroupId])
@@map("group_hierarchies")
@@schema("group")
}

9. 도메인 간 협력

9.1 Plan 도메인과의 협력

  • 플랜 정보 조회: Group 도메인은 Plan 도메인으로부터 플랜 정보를 조회
  • 플랜 할당 관리: Plan 도메인과 협력하여 그룹-플랜 연결 관리

9.2 User 도메인과의 협력

  • 사용자 정보 조회: Group 도메인은 User 도메인으로부터 사용자 정보를 조회
  • 멤버십 관리: User 도메인과 협력하여 사용자-그룹 멤버십 관리

9.3 IAM 도메인과의 협력

  • 권한 계산 지원: 그룹 정보를 IAM 도메인에 제공하여 사용자 권한 계산 지원
  • 그룹 기반 접근 제어: IAM 도메인의 권한 체계와 연동

9.4 Organization 도메인과의 협력 (향후)

  • 조직 구조 연동: 외부 조직 구조와 그룹 계층 구조 동기화
  • 조직 기반 그룹 생성: 조직 변경에 따른 자동 그룹 생성/수정

9.5 MAO 티어/동의 연계 모델 확장

  • Group/GroupMembership 엔티티에 tier(anonymous/guest/member), consent_version, data_persistence 메타데이터를 추가해 기본 그룹 매핑과 보존 정책을 표현한다.
  • Guest→Member 이관 시 이전 멤버십을 비활성화하고 신규 member 멤버십을 생성하며, 이벤트 페이로드에 tier/consent_version을 포함한다.
  • Anonymous/guest 그룹은 컨텍스트 저장을 제한하기 위해 보존 TTL 필드(retention_ttl_minutes)를 사용한다.

10. 변경 이력

버전날짜작성자변경 내용
0.1.02025-07-15bok@weltcorp.comGroup 도메인 모델 초기 생성 (IAM 도메인에서 분리)
0.2.02025-07-16bok@weltcorp.comGroup-Plan 독립성 확보: planId 제거, GroupRole 추가, Plan → Role 참조 변경
0.3.02025-11-26bok@weltcorp.comMAO 라우팅을 위한 tier/consent_version/data_persistence 필드 추가 및 guest→member 이관 시 멤버십 메타데이터/이벤트 확장