Skip to content

[FEATURE] Organization Business 로직 #172

@YuSung011017

Description

@YuSung011017

📋 기본 정보


🎯 설계 B 핵심 요구사항

  1. User는 전역 계정이며 테넌트 맥락이 없다.
  2. Worker는 User의 테넌트 내 ID이며, 테넌트별로 여러 Worker가 존재할 수 있음 (User 1:N Worker).
  3. Worker는 오직 User 기반으로만 생성되며, Organization은 Worker로 승격되지 않는다.
  4. TenantWorkerMap을 통해 Worker가 어떤 테넌트에 소속되는지 정의.
  5. WorkerRole을 통해 Worker가 테넌트 내에서 수행할 역할을 정의.
  6. Shared Tenant 접근은 반드시 Worker 멤버십 + 역할 검증이 필요.
  7. Organization은 User와의 관계를 정의하는 독립 도메인으로만 존재하며, Worker로 승격되지 않는다.
  8. 모든 리소스 접근은 tenant_id + worker_id 조합으로 스코핑된다.

📐 구현 계획

Phase 1: 새로운 엔티티 구현

Step 1.1: Worker 엔티티 구현 (설계 B 기준)

파일 위치:

  • src/main/java/com/agenticcp/core/domain/organization/entity/Worker.java
  • src/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.java
  • src/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
  • id PK 없음 (설계 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.java
  • src/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
  • id PK 없음 (설계 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.java
  • src/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
  • id PK 없음 (설계 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, role
  • OrganizationMemberResponse: 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)

⚠️ 주의사항

  1. 설계 B 핵심 원칙

    • Worker는 오직 User 기반으로만 생성 (Organization 승격 안됨)
    • Worker는 user_id + tenant_id 둘 다 필요
    • 복합 PK 사용 (TenantWorkerMap, WorkerRole, OrganizationMember)
  2. 데이터 마이그레이션

    • 마이그레이션 전 반드시 데이터 백업
    • 단계별 검증 후 다음 단계 진행
    • 롤백 스크립트 준비
  3. JPA 복합키 처리

    • @IdClass 또는 @EmbeddedId 사용
    • Repository 메서드 시그니처 주의
  4. 트랜잭션 관리

    • Worker 생성 시 관련 테이블 동시 업데이트
    • 트랜잭션 단위 명확히 설정

🔗 관련 이슈

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions