Group 도메인 모델
데이터 저장 규칙
- Group 도메인 관련 모든 데이터는
groupPostgreSQL 스키마에 저장되어야 합니다. (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 분리는 특히 다음과 같은 동적이고 확장 가능한 사용자 분류 시스템을 가능하게 합니다:
- 🔄 자동화된 그룹 관리: 사용자 속성 변화에 따른 자동 그룹 이동
- 🏢 조직 구조 변화 대응: 인수합병, 조직개편 등에 유연한 대응
- 🌍 다국가/다지역 확장: 지역별 법규나 문화에 맞춘 그룹 정책
- 📊 동적 권한 계산: 그룹 메타데이터 기반 실시간 권한 계산
- 🎯 맞춤형 서비스: 그룹별 특화된 기능과 콘텐츠 제공
이러한 설계를 통해 확장 가능한 사용자 분류, 자동화된 그룹 관리, 조직 변화에 유연한 대응을 달성할 수 있습니다! 🎯
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 참조
주요 규칙 카테고리:
- 그룹 정의 규칙: 그룹 목적, 구성 요소, 유형별 분류
- 그룹 유형 규칙: 환자, 운영자, 테스터 그룹
- 그룹-플랜 관계 규칙: 필수 플랜 연결, 플랜 변경
- 그룹 멤버십 규칙: 사용자 할당/해제, 멤버십 관리
- 그룹 생명주기 규칙: 생성, 수정, 삭제, 상태 관리
- 그룹 계층 구조 규칙: 계층 생성, 순환 참조 방지
- 그룹 템플릿 규칙: 템플릿 생성, 그룹 생성
- 감사 및 보안 규칙: 감사 로그, 접근 제어
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.0 | 2025-07-15 | bok@weltcorp.com | Group 도메인 모델 초기 생성 (IAM 도메인에서 분리) |
| 0.2.0 | 2025-07-16 | bok@weltcorp.com | Group-Plan 독립성 확보: planId 제거, GroupRole 추가, Plan → Role 참조 변경 |
| 0.3.0 | 2025-11-26 | bok@weltcorp.com | MAO 라우팅을 위한 tier/consent_version/data_persistence 필드 추가 및 guest→member 이관 시 멤버십 메타데이터/이벤트 확장 |