-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
📋 기본 정보
- 상위 이슈: [FEATURE] 멀티 테넌트 작업자 및 역할 관리 ERD 설계 #163 (설계 B 최종안 기준), [REFACTOR] Organization 내부 로직 수정 #165
- 관련 문서: 이슈 [FEATURE] 멀티 테넌트 작업자 및 역할 관리 ERD 설계 #163 설계 B 최종안 ERD
- 목표: 이슈 #163의 설계 B 최종안 ERD에 맞춰 Organization 도메인을 리팩토링하고 Worker 기반 멀티 테넌트 구조 구현
🎯 설계 B 핵심 요구사항
- User는 전역 계정이며 테넌트 맥락이 없다.
- Worker는 User의 테넌트 내 ID이며, 테넌트별로 여러 Worker가 존재할 수 있음 (User 1:N Worker).
- Worker는 오직 User 기반으로만 생성되며, Organization은 Worker로 승격되지 않는다.
- TenantWorkerMap을 통해 Worker가 어떤 테넌트에 소속되는지 정의.
- WorkerRole을 통해 Worker가 테넌트 내에서 수행할 역할을 정의.
- Shared Tenant 접근은 반드시 Worker 멤버십 + 역할 검증이 필요.
- Organization은 User와의 관계를 정의하는 독립 도메인으로만 존재하며, Worker로 승격되지 않는다.
- 모든 리소스 접근은 tenant_id + worker_id 조합으로 스코핑된다.
📐 구현 계획
Phase 1: 새로운 엔티티 구현
Step 1.1: Worker 엔티티 구현 (설계 B 기준)
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/entity/Worker.javasrc/main/java/com/agenticcp/core/domain/organization/repository/WorkerRepository.java
구현 내용 (설계 B):
@Entity
@Table(name = "workers", indexes = {
@Index(name = "idx_worker_user", columnList = "user_id"),
@Index(name = "idx_worker_tenant", columnList = "tenant_id")
})
public class Worker extends BaseEntity {
/** 전역 User (User 1:N Worker) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
/** 소속 테넌트 (Tenant 1:N Worker) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tenant_id", nullable = false)
private Tenant tenant;
@Table(uniqueConstraints = @UniqueConstraint(
columnNames = {"user_id", "tenant_id"}
))
}주의사항:
worker_type필드 없음 (설계 B에는 없음)user_id+tenant_id둘 다 필수- 같은 User가 여러 Tenant에서 Worker가 될 수 있음 (User 1:N Worker)
- 복합 Unique 제약:
(user_id, tenant_id)
Repository 메서드:
findByUserId(Long userId)findByTenantId(Long tenantId)findByUserIdAndTenantId(Long userId, Long tenantId)
DDL:
CREATE TABLE workers (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100),
is_deleted BOOLEAN DEFAULT FALSE,
CONSTRAINT fk_worker_user FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT fk_worker_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id),
CONSTRAINT uk_worker_user_tenant UNIQUE (user_id, tenant_id),
INDEX idx_worker_user (user_id),
INDEX idx_worker_tenant (tenant_id)
);Step 1.2: OrganizationMember 엔티티 구현
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/entity/OrganizationMember.javasrc/main/java/com/agenticcp/core/domain/organization/repository/OrganizationMemberRepository.java
구현 내용 (설계 B):
@Entity
@Table(name = "organization_member", uniqueConstraints = {
@UniqueConstraint(name = "uk_org_member", columnNames = {"organization_id", "user_id"})
}, indexes = {
@Index(name = "idx_org_member_org", columnList = "organization_id"),
@Index(name = "idx_org_member_user", columnList = "user_id")
})
public class OrganizationMember extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "organization_id", nullable = false)
private Organization organization;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "role", length = 50)
private String role; // 조직 내 역할 (선택적)
}주의사항:
- 복합 PK 사용:
(organization_id, user_id)가 복합 PK idPK 없음 (설계 B ERD 기준)- 필드명:
role(ERD DDL 기준)
Repository 메서드:
findByOrganizationId(Long organizationId)findByUserId(Long userId)findByOrganizationIdAndUserId(Long organizationId, Long userId)deleteByOrganizationIdAndUserId(Long organizationId, Long userId)
DDL:
CREATE TABLE organization_member (
organization_id BIGINT,
user_id BIGINT,
role VARCHAR(50),
PRIMARY KEY (organization_id, user_id),
FOREIGN KEY (organization_id) REFERENCES organization(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);Step 1.3: TenantWorkerMap 엔티티 구현 (설계 B 기준)
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/entity/TenantWorkerMap.javasrc/main/java/com/agenticcp/core/domain/organization/repository/TenantWorkerMapRepository.java
구현 내용 (설계 B):
@Entity
@Table(name = "tenant_worker_map", uniqueConstraints = {
@UniqueConstraint(name = "uk_tenant_worker", columnNames = {"tenant_id", "worker_id"})
}, indexes = {
@Index(name = "idx_tenant_worker_tenant", columnList = "tenant_id"),
@Index(name = "idx_tenant_worker_worker", columnList = "worker_id")
})
@IdClass(TenantWorkerMapId.class) // 복합 PK
public class TenantWorkerMap extends BaseEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tenant_id", nullable = false)
private Tenant tenant;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "worker_id", nullable = false)
private Worker worker;
@Column(name = "access_scope", length = 30)
private String accessScope; // 공유 테넌트에서의 참여 범위
@Column(name = "joined_at")
private LocalDateTime joinedAt;
}주의사항:
- 복합 PK 사용:
(tenant_id, worker_id)가 복합 PK idPK 없음 (설계 B ERD 기준)status필드 없음 (ERD에 없음)
복합 PK 클래스:
@Embeddable
public class TenantWorkerMapId implements Serializable {
private Long tenantId;
private Long workerId;
}Repository 메서드:
findByTenantId(Long tenantId)findByWorkerId(Long workerId)findByTenantIdAndWorkerId(Long tenantId, Long workerId)
DDL:
CREATE TABLE tenant_worker_map (
tenant_id BIGINT,
worker_id BIGINT,
access_scope VARCHAR(30),
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, worker_id),
FOREIGN KEY (tenant_id) REFERENCES tenant(id),
FOREIGN KEY (worker_id) REFERENCES worker(id)
);Step 1.4: WorkerRole 엔티티 구현 (설계 B 기준)
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/entity/WorkerRole.javasrc/main/java/com/agenticcp/core/domain/organization/repository/WorkerRoleRepository.java
구현 내용 (설계 B):
@Entity
@Table(name = "worker_role", uniqueConstraints = {
@UniqueConstraint(name = "uk_worker_role", columnNames = {"worker_id", "role_id", "tenant_id"})
}, indexes = {
@Index(name = "idx_worker_role_worker", columnList = "worker_id"),
@Index(name = "idx_worker_role_tenant", columnList = "tenant_id"),
@Index(name = "idx_worker_role_role", columnList = "role_id")
})
@IdClass(WorkerRoleId.class) // 복합 PK
public class WorkerRole extends BaseEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "worker_id", nullable = false)
private Worker worker;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id", nullable = false)
private Role role;
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tenant_id", nullable = false)
private Tenant tenant;
}주의사항:
- 복합 PK 사용:
(worker_id, role_id, tenant_id)가 복합 PK idPK 없음 (설계 B ERD 기준)status필드 없음 (ERD에 없음)
복합 PK 클래스:
@Embeddable
public class WorkerRoleId implements Serializable {
private Long workerId;
private Long roleId;
private Long tenantId;
}Repository 메서드:
findByWorkerId(Long workerId)findByWorkerIdAndTenantId(Long workerId, Long tenantId)findByTenantId(Long tenantId)findByWorkerIdAndTenantIdAndRoleId(Long workerId, Long tenantId, Long roleId)
DDL:
CREATE TABLE worker_role (
worker_id BIGINT,
role_id BIGINT,
tenant_id BIGINT,
PRIMARY KEY (worker_id, role_id, tenant_id),
FOREIGN KEY (worker_id) REFERENCES worker(id),
FOREIGN KEY (role_id) REFERENCES role(id),
FOREIGN KEY (tenant_id) REFERENCES tenant(id)
);Phase 2: Tenant 엔티티 수정 (설계 B 기준)
Step 2.1: Tenant 엔티티에 tenant_type, owner_org_id 추가
파일 위치:
src/main/java/com/agenticcp/core/domain/tenant/entity/Tenant.java
수정 내용 (설계 B):
@Entity
@Table(name = "tenants")
public class Tenant extends BaseEntity {
@Column(name = "name", length = 100)
private String name;
/** 테넌트 타입 (DEDICATED or SHARED) */
@Column(name = "tenant_type", length = 20)
@Enumerated(EnumType.STRING)
private TenantType tenantType; // DEDICATED, SHARED
/** Dedicated일 때 주인 조직 (nullable) */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_org_id")
private Organization ownerOrganization;
// 기존 필드들 유지...
}Enum 추가:
public enum TenantType {
DEDICATED, // 전용 테넌트
SHARED // 공유 테넌트
}DDL:
ALTER TABLE tenants
ADD COLUMN tenant_type VARCHAR(20) CHECK (tenant_type IN ('DEDICATED','SHARED')),
ADD COLUMN owner_org_id BIGINT,
ADD CONSTRAINT fk_tenant_owner_org FOREIGN KEY (owner_org_id) REFERENCES organization(id);Phase 3: Organization 엔티티 리팩토링
Step 3.1: Organization 엔티티 정리
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/entity/Organization.java
제거할 필드 (ERD에 없음):
- 모든 Deprecated 필드 제거 (
org_key,orgName,description,parentOrganization,status,orgType등)
최종 구조 (설계 B):
@Entity
@Table(name = "organization", indexes = {
@Index(name = "idx_organizations_name", columnList = "name")
})
public class Organization extends BaseEntity {
/** 조직명 (ERD: name) */
@NotBlank(message = "조직명은 필수입니다")
@Size(max = 100, message = "조직명은 100자를 초과할 수 없습니다")
@Column(name = "name", length = 100)
private String name;
}주의사항:
- 테넌트와의 1:1 관계는 Tenant 측에서 관리 (Tenant.organization_id)
Step 3.2: User 엔티티 수정
파일 위치:
src/main/java/com/agenticcp/core/domain/user/entity/User.java
수정 내용:
organization필드 제거 (OrganizationMember로 관리)tenant필드 제거 (Worker로 관리)
Phase 4: 서비스 구현
Step 4.1: WorkerService 구현 (설계 B 기준)
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/service/WorkerService.java
주요 메서드:
@Service
@Transactional(readOnly = true)
public class WorkerService {
/**
* User를 특정 Tenant 컨텍스트의 Worker로 생성
* 설계 B: Worker는 user_id + tenant_id 필요
*/
@Transactional
public Worker createWorker(Long userId, Long tenantId) {
// User와 Tenant 존재 확인
// 이미 존재하는지 확인 (user_id + tenant_id)
// 없으면 생성
}
/**
* Worker 조회 (user_id + tenant_id)
*/
public Optional<Worker> findByUserIdAndTenantId(Long userId, Long tenantId);
/**
* User의 모든 Worker 조회
*/
public List<Worker> findByUserId(Long userId);
/**
* Tenant의 모든 Worker 조회
*/
public List<Worker> findByTenantId(Long tenantId);
}주의사항:
- Organization을 Worker로 승격하는 메서드 없음 (설계 B)
- 항상 User + Tenant 조합으로만 생성
Step 4.2: OrganizationMemberService 구현
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/service/OrganizationMemberService.java
주요 메서드:
@Service
@Transactional(readOnly = true)
public class OrganizationMemberService {
/**
* 조직에 멤버 추가
*/
@Transactional
public OrganizationMember addMember(Long organizationId, Long userId, String role);
/**
* 조직에서 멤버 제거
*/
@Transactional
public void removeMember(Long organizationId, Long userId);
/**
* 조직의 멤버 목록 조회
*/
public List<OrganizationMember> getMembers(Long organizationId);
/**
* 사용자가 속한 조직 목록 조회
*/
public List<OrganizationMember> getOrganizationsByUserId(Long userId);
}Step 4.3: TenantWorkerService 구현
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/service/TenantWorkerService.java
주요 메서드:
@Service
@Transactional(readOnly = true)
public class TenantWorkerService {
/**
* 테넌트에 Worker 할당 (Shared Tenant용)
*/
@Transactional
public TenantWorkerMap assignWorkerToTenant(Long tenantId, Long workerId, String accessScope);
/**
* 테넌트에서 Worker 제거
*/
@Transactional
public void removeWorkerFromTenant(Long tenantId, Long workerId);
/**
* 테넌트의 Worker 목록 조회
*/
public List<TenantWorkerMap> getWorkersByTenant(Long tenantId);
/**
* Worker가 속한 테넌트 목록 조회
*/
public List<TenantWorkerMap> getTenantsByWorker(Long workerId);
}Step 4.4: WorkerRoleService 구현
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/service/WorkerRoleService.java
주요 메서드:
@Service
@Transactional(readOnly = true)
public class WorkerRoleService {
/**
* Worker에게 테넌트 내 역할 부여
*/
@Transactional
public WorkerRole assignRole(Long workerId, Long tenantId, Long roleId);
/**
* Worker의 역할 제거
*/
@Transactional
public void removeRole(Long workerId, Long tenantId, Long roleId);
/**
* Worker의 역할 목록 조회
*/
public List<WorkerRole> getRoles(Long workerId, Long tenantId);
}Step 4.5: OrganizationService 리팩토링
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/service/OrganizationService.java
수정 내용:
- 계층 구조 관련 메서드 제거
- name 필드만 사용
- Organization을 Worker로 승격하는 로직 제거 (설계 B)
주요 메서드:
@Service
@Transactional(readOnly = true)
public class OrganizationService {
/**
* 조직 생성 - name만 사용
*/
@Transactional
public OrganizationResponse createOrganization(CreateOrganizationRequest request) {
// name 중복 검사
// Organization 생성
// Organization을 Worker로 승격하지 않음 (설계 B)
}
/**
* 조직 수정 - name만 수정
*/
@Transactional
public OrganizationResponse updateOrganization(Long id, UpdateOrganizationRequest request);
// 기타 기본 CRUD 메서드...
}Phase 5: DTO 및 Response 수정
Step 5.1: Organization DTO 수정
CreateOrganizationRequest: name만UpdateOrganizationRequest: name만OrganizationResponse: name만
Step 5.2: OrganizationMember DTO 생성
AddMemberRequest: userId, roleOrganizationMemberResponse: organization, user, role 정보
Phase 6: Controller 리팩토링
Step 6.1: OrganizationController 수정
제거할 API:
- 계층 구조 관련 API 모두 제거
- 사용자 관리 API는 OrganizationMemberController로 이동
유지할 API:
- 기본 CRUD (name만 사용)
Step 6.2: OrganizationMemberController 생성
POST /api/organizations/{orgId}/members- 멤버 추가DELETE /api/organizations/{orgId}/members/{userId}- 멤버 제거GET /api/organizations/{orgId}/members- 멤버 목록 조회
Step 6.3: WorkerController 생성 (설계 B 기준)
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/controller/WorkerController.java
구현 API (설계 B):
@RestController
@RequestMapping("/api/users/{userId}/workers")
public class WorkerController {
/**
* User를 특정 Tenant 컨텍스트의 Worker로 생성
* 설계 B: POST /api/users/{userId}/workers
*/
@PostMapping
public ResponseEntity<ApiResponse<WorkerResponse>> createWorker(
@PathVariable Long userId,
@RequestBody @Valid CreateWorkerRequest request // tenantId 포함
);
/**
* User의 Worker 목록 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<WorkerResponse>>> getWorkers(
@PathVariable Long userId
);
}Step 6.4: TenantWorkerController 생성
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/controller/TenantWorkerController.java
구현 API:
@RestController
@RequestMapping("/api/tenants/{tenantId}/workers")
public class TenantWorkerController {
/**
* 테넌트의 Worker 목록 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<TenantWorkerMapResponse>>> getWorkers(
@PathVariable Long tenantId
);
/**
* 테넌트에 Worker 할당 (Shared Tenant용)
*/
@PostMapping
public ResponseEntity<ApiResponse<TenantWorkerMapResponse>> assignWorker(
@PathVariable Long tenantId,
@RequestBody @Valid AssignWorkerRequest request
);
/**
* 테넌트에서 Worker 제거
*/
@DeleteMapping("/{workerId}")
public ResponseEntity<Void> removeWorker(
@PathVariable Long tenantId,
@PathVariable Long workerId
);
}Step 6.5: WorkerRoleController 생성
파일 위치:
src/main/java/com/agenticcp/core/domain/organization/controller/WorkerRoleController.java
구현 API:
@RestController
@RequestMapping("/api/workers/{workerId}/roles")
public class WorkerRoleController {
/**
* Worker의 역할 목록 조회
*/
@GetMapping
public ResponseEntity<ApiResponse<List<WorkerRoleResponse>>> getRoles(
@PathVariable Long workerId,
@RequestParam(required = false) Long tenantId
);
/**
* Worker에게 역할 부여
*/
@PutMapping
public ResponseEntity<ApiResponse<WorkerRoleResponse>> assignRole(
@PathVariable Long workerId,
@RequestBody @Valid AssignRoleRequest request // tenantId, roleId 포함
);
}Phase 7: 데이터베이스 마이그레이션
Step 7.1: 새로운 테이블 생성 (설계 B 기준)
-- Worker 테이블 (설계 B)
CREATE TABLE workers (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100),
is_deleted BOOLEAN DEFAULT FALSE,
CONSTRAINT fk_worker_user FOREIGN KEY (user_id) REFERENCES users(id),
CONSTRAINT fk_worker_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id),
CONSTRAINT uk_worker_user_tenant UNIQUE (user_id, tenant_id),
INDEX idx_worker_user (user_id),
INDEX idx_worker_tenant (tenant_id)
);
-- OrganizationMember 테이블 (복합 PK)
CREATE TABLE organization_member (
organization_id BIGINT,
user_id BIGINT,
role VARCHAR(50),
PRIMARY KEY (organization_id, user_id),
FOREIGN KEY (organization_id) REFERENCES organization(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- TenantWorkerMap 테이블 (복합 PK)
CREATE TABLE tenant_worker_map (
tenant_id BIGINT,
worker_id BIGINT,
access_scope VARCHAR(30),
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, worker_id),
FOREIGN KEY (tenant_id) REFERENCES tenant(id),
FOREIGN KEY (worker_id) REFERENCES worker(id)
);
-- WorkerRole 테이블 (복합 PK)
CREATE TABLE worker_role (
worker_id BIGINT,
role_id BIGINT,
tenant_id BIGINT,
PRIMARY KEY (worker_id, role_id, tenant_id),
FOREIGN KEY (worker_id) REFERENCES worker(id),
FOREIGN KEY (role_id) REFERENCES role(id),
FOREIGN KEY (tenant_id) REFERENCES tenant(id)
);
-- Tenant 테이블 수정
ALTER TABLE tenants
ADD COLUMN tenant_type VARCHAR(20) CHECK (tenant_type IN ('DEDICATED','SHARED')),
ADD COLUMN owner_org_id BIGINT,
ADD CONSTRAINT fk_tenant_owner_org FOREIGN KEY (owner_org_id) REFERENCES organization(id);Step 7.2: 기존 데이터 마이그레이션
-- 1. User.tenant_id를 기반으로 Worker 생성 (User 1:N Worker)
INSERT INTO workers (user_id, tenant_id, created_at, updated_at)
SELECT id, tenant_id, created_at, updated_at
FROM users
WHERE tenant_id IS NOT NULL
ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP;
-- 2. User.organization_id를 organization_member로 이관
INSERT INTO organization_member (organization_id, user_id, role)
SELECT organization_id, id, NULL
FROM users
WHERE organization_id IS NOT NULL
ON DUPLICATE KEY UPDATE organization_id = organization_id;
-- 3. Worker 생성 후 TenantWorkerMap 생성 (Shared Tenant용)
-- (Dedicated Tenant는 owner_org_id로 관리)📝 체크리스트
Phase 1: 새로운 엔티티 구현
- Worker 엔티티 구현 (user_id + tenant_id, 복합 Unique)
- OrganizationMember 엔티티 구현 (복합 PK)
- TenantWorkerMap 엔티티 구현 (복합 PK)
- WorkerRole 엔티티 구현 (복합 PK)
Phase 2: Tenant 엔티티 수정
- tenant_type enum 추가 (DEDICATED/SHARED)
- owner_org_id 필드 추가
Phase 3: Organization 엔티티 리팩토링
- Organization 엔티티에서 Deprecated 필드 제거
- name 필드만 유지
- User 엔티티에서 organization, tenant 필드 제거
Phase 4: 서비스 구현
- WorkerService 구현 (User + Tenant 조합으로만 생성)
- OrganizationMemberService 구현
- TenantWorkerService 구현
- WorkerRoleService 구현
- OrganizationService 리팩토링 (Worker 승격 로직 제거)
Phase 5: DTO 및 Response 수정
- Organization DTO 수정 (name만)
- OrganizationMember DTO 생성
- Worker DTO 생성
Phase 6: Controller 리팩토링
- OrganizationController에서 Deprecated API 제거
- OrganizationMemberController 생성
- WorkerController 생성 (POST /api/users/{userId}/workers)
- TenantWorkerController 생성
- WorkerRoleController 생성
Phase 7: 데이터베이스 마이그레이션
- 새로운 테이블 생성 스크립트 작성 (설계 B 기준)
- 기존 데이터 마이그레이션 스크립트 작성
- Tenant 테이블 수정 (tenant_type, owner_org_id)
⚠️ 주의사항
-
설계 B 핵심 원칙
- Worker는 오직 User 기반으로만 생성 (Organization 승격 안됨)
- Worker는 user_id + tenant_id 둘 다 필요
- 복합 PK 사용 (TenantWorkerMap, WorkerRole, OrganizationMember)
-
데이터 마이그레이션
- 마이그레이션 전 반드시 데이터 백업
- 단계별 검증 후 다음 단계 진행
- 롤백 스크립트 준비
-
JPA 복합키 처리
@IdClass또는@EmbeddedId사용- Repository 메서드 시그니처 주의
-
트랜잭션 관리
- Worker 생성 시 관련 테이블 동시 업데이트
- 트랜잭션 단위 명확히 설정
🔗 관련 이슈
- [FEATURE] 멀티 테넌트 작업자 및 역할 관리 ERD 설계 #163: 멀티 테넌트 작업자 및 역할 관리 ERD 설계 (설계 B 최종안)
- [REFACTOR] Organization 내부 로직 수정 #165: Organization 내부 로직 수정
Metadata
Metadata
Assignees
Labels
No labels