1
0
forked from baron/baron-sso
Files
baron-sso/docs/organization-chart-policy.md
2026-02-20 08:16:56 +09:00

7.5 KiB

Organization Chart Architecture & Implementation Policy (ADR)

1. Overview (개요)

본 문서는 Baron SSO 내 adminfront에서 사용될 조직도(Organization Chart) 및 다중 테넌시(Multi-Tenancy) 대응 기능에 대한 아키텍처 결정 사항(Architecture Decision Record)과 세부 구현 방향을 정의합니다.

이 정책은 기존 B2B 테넌트 모델(Tenant)과 사내 사용자 그룹 모델(UserGroup), 그리고 Ory Keto 기반의 권한 제어(ReBAC) 시스템 간의 일관성을 유지하면서, 복잡하고 다양한 형태의 고객사별 조직 구조(N-Depth)를 지원하기 위해 작성되었습니다.


2. Core Architectural Decisions (핵심 아키텍처 결정)

2.1 B2B Tenant vs. Internal UserGroup Hierarchy (테넌트 vs. 유저그룹 계층화)

조직의 계층(Hierarchy)을 표현하기 위해 Tenant 자체를 중첩(Nested)시킬 것인지, 아니면 단일 Tenant 내의 UserGroup을 중첩시킬 것인지에 대한 결정입니다.

  • Decision (결정): 조직도는 UserGroup 내부의 자기 참조(parent_id)를 통해 계층화합니다.
  • Rationale (이유):
    • 관심사 분리 (Separation of Concerns): Tenant 모델은 결제, 도메인 매핑, B2B 고객사(Company) 격리 등 무거운 비즈니스 로직을 담고 있습니다. "개발팀", "인사부"와 같은 단순한 사내 조직 단위까지 Tenant 테이블에 저장하면 시스템 복잡도가 기하급수적으로 증가합니다.
    • 조회 성능 (Performance): 특정 고객사(Company)의 전체 조직도를 그릴 때 SELECT * FROM user_groups WHERE tenant_id = ? 단일 쿼리로 모든 노드를 가져와 애플리케이션 메모리에서 트리를 구성할 수 있어 성능상 매우 유리합니다.
    • 단일 진실 공급원 (SoT): 회사(Company) 단위의 물리적 격리는 Tenant가, 논리적인 사내 부서/팀 구조는 UserGroup이 담당하도록 역할을 명확히 분리합니다.

2.2 Flexible N-Depth Organizational Structure (유연한 N-Depth 조직 구조)

고객사마다 조직 단계(부, 국, 실, 본부, 파트, 반, 셀 등)의 명칭과 깊이(Depth)가 다릅니다. 이를 하드코딩된 Enum으로 제한해서는 안 됩니다.

  • Decision (결정): 조직의 단계나 명칭을 시스템(DB 스키마)에서 강제하지 않으며, N-Depth 인접 목록(Adjacency List) 모델을 사용합니다.
  • Implementation (구현):
    • UserGroup 모델에 parent_id (UUID, Nullable) 컬럼을 추가하여 부모-자식 관계를 형성합니다.
    • 조직 타입(unit_type) 필드는 고정된 Enum(예: TEAM, GROUP) 대신, 고객사가 자유롭게 입력할 수 있는 **동적 문자열(String)**로 관리하거나, 계층의 상대적 깊이(Depth)만을 의미 단위로 사용합니다.
    • 프론트엔드의 Checkbox Tree 컴포넌트는 재귀적(Recursive)으로 설계되어 데이터의 깊이에 상관없이 무한한 N-Depth를 렌더링할 수 있어야 합니다.

3. Data Structure & Schema Updates (데이터 구조 및 스키마 업데이트)

새로운 테이블을 추가하는 대신, 기존 모델을 확장하여 중복을 방지합니다.

3.1 user_groups 테이블 확장

조직 계층 및 부서 단위 표현을 위해 필드를 추가합니다.

  • id, tenant_id, name, description (기존 유지)
  • parent_id (UUID, Nullable FK): 상위 UserGroup 참조 (조직 트리 구성).
  • unit_type (String, Optional): 조직 단위 명칭 (예: "본부", "실", "팀"). 시스템이 강제하지 않으며 프론트엔드 라벨링 용도로 사용됩니다.

3.2 users 테이블 확장 (직급 및 직무)

CSV에서 업로드되는 사용자의 인사 정보(직급, 직무 등)는 User 모델에 직접 저장합니다.

  • position (String): 직급 (예: "수석", "책임", "사원").
  • job_title (String): 직무 (예: "프론트엔드 개발", "기획").
  • (또는 기존에 존재하는 Metadata (JSONB) 필드를 활용하여 스키마 변경 없이 동적 속성으로 관리할 수도 있습니다.)

4. ReBAC Integration Policy (Ory Keto 연동 정책)

DB의 user_groups 계층 트리는 Ory Keto의 관계 튜플(Tuple)과 동기화되어 권한 제어에 사용됩니다. (기존 통합 권한 정책 tenant-usergroup-policy.md 준수)

  1. 조직 계층 동기화 (Hierarchy):
    • DB에서 A팀(UserGroup)이 B본부(UserGroup)의 하위로 설정되면, Keto에는 UserGroup:<A팀_ID>#parent@UserGroup:<B본부_ID> 튜플이 생성됩니다.
  2. 소속원 매핑 (Membership):
    • 유저가 A팀에 속하면 UserGroup:<A팀_ID>#members@User:<유저_ID> 튜플이 생성됩니다.
  3. 조직장 및 어드민 승격 (Leadership):
    • CSV 데이터 분석 또는 수동 지정을 통해 특정 유저가 A팀의 '조직장'으로 식별되면, UserGroup:<A팀_ID>#owners@User:<유저_ID> 튜플이 생성됩니다.
    • 정책에 따라 owners 관계를 가진 유저는 해당 조직(UserGroup)과 그 하위 조직에 대한 admins 권한을 자동으로 상속받습니다.

5. Data Loading & CSV Upload Strategy (데이터 로딩 및 CSV 업로드 전략)

고정된 컬럼 구조는 다양한 회사의 조직도를 수용할 수 없으므로 유연한 파싱 로직이 필요합니다.

5.1 Flexible CSV Format (유연한 CSV 포맷)

  • 경로 기반 방식 (Path-based): 조직 계층을 슬래시(/) 등으로 구분하여 하나의 문자열로 전달받습니다.
    • 예시 컬럼: [조직_경로, 직급, 이름, 직무, 이메일]
    • 데이터 예시: "개발본부/클라우드실/플랫폼팀", "수석", "홍길동", "백엔드 개발", "hong@example.com"
  • 동적 뎁스 방식 (Dynamic Depth): 뒤에서부터 고정된 사용자 속성 열(직급, 직무, 이름, 이메일 등)을 식별하고, 그 앞의 모든 열을 동적인 계층 구조로 해석합니다.

5.2 Processing Flow (처리 흐름)

  1. Parsing & Validation: 프론트엔드/백엔드에서 유연한 CSV 포맷을 파싱하고, UserGroup 계층 경로를 분석합니다.
  2. Tree Resolution: 백엔드는 "개발본부 > 클라우드실 > 플랫폼팀" 경로를 DB에서 조회하거나 없으면 순차적으로 생성(parent_id 매핑)하여 UserGroup ID 트리를 완성합니다.
  3. User Upsert: User 정보를 생성하거나 업데이트(position, job_title 갱신)합니다.
  4. Keto Synchronization: DB 트랜잭션 완료 후, Background Worker가 변경된 조직 계층과 멤버십 정보를 기반으로 Ory Keto 튜플을 생성/삭제(Reconciliation)합니다.

6. Frontend Multi-Tenancy UI (프론트엔드 다중 테넌트 UI)

관리자가 여러 테넌트(Company)에 접근 권한이 있을 경우, 조직도를 명확히 구분하여 보여주어야 합니다.

  • Tabs Interface: 화면 상단 또는 측면에 사용자가 접근 가능한 최상위 Tenant 목록을 탭(Tabs) 형태로 제공합니다.
  • Scoped Fetching: 특정 탭(Tenant)을 선택할 때마다 해당 tenant_id를 파라미터로 백엔드 API를 호출하여, 격리된 해당 회사만의 UserGroup 트리를 렌더링합니다.
  • Checkbox Tree Component: Radix UI와 TailwindCSS를 기반으로 개발되며, N-Depth 중첩을 지원하고 부모-자식 간의 반선택(Indeterminate) 상태를 재귀적으로 계산하는 독립적인(Reusable) 컴포넌트로 구현됩니다.