ERP-node/PHASE3.13_ENTITY_JOIN_SERVI...

7.0 KiB

📋 Phase 3.13: EntityJoinService Raw Query 전환 계획

📋 개요

EntityJoinService는 5개의 Prisma 호출이 있으며, 엔티티 간 조인 관계 관리를 담당하는 서비스입니다.

📊 기본 정보

항목 내용
파일 위치 backend-node/src/services/entityJoinService.ts
파일 크기 574 라인
Prisma 호출 5개
현재 진행률 0/5 (0%) 🔄 진행 예정
복잡도 중간 (조인 쿼리, 관계 설정)
우선순위 🟡 중간 (Phase 3.13)
상태 대기 중

🎯 전환 목표

  • 5개 모든 Prisma 호출을 db.tsquery(), queryOne() 함수로 교체
  • 엔티티 조인 설정 CRUD 기능 정상 동작
  • 복잡한 조인 쿼리 전환 (LEFT JOIN, INNER JOIN)
  • 조인 유효성 검증
  • TypeScript 컴파일 성공
  • Prisma import 완전 제거

🔍 예상 Prisma 사용 패턴

주요 기능 (5개 예상)

1. 엔티티 조인 목록 조회

  • findMany with filters
  • 동적 WHERE 조건
  • 페이징, 정렬

2. 엔티티 조인 단건 조회

  • findUnique or findFirst
  • join_id 기준

3. 엔티티 조인 생성

  • create
  • 조인 유효성 검증

4. 엔티티 조인 수정

  • update
  • 동적 UPDATE 쿼리

5. 엔티티 조인 삭제

  • delete

💡 전환 전략

1단계: 기본 CRUD 전환 (5개)

  • getEntityJoins() - 목록 조회
  • getEntityJoin() - 단건 조회
  • createEntityJoin() - 생성
  • updateEntityJoin() - 수정
  • deleteEntityJoin() - 삭제

💻 전환 예시

예시 1: 조인 설정 조회 (LEFT JOIN으로 테이블 정보 포함)

변경 전:

const joins = await prisma.entity_joins.findMany({
  where: {
    company_code: companyCode,
    is_active: true,
  },
  include: {
    source_table: true,
    target_table: true,
  },
  orderBy: { created_at: "desc" },
});

변경 후:

const joins = await query<any>(
  `SELECT 
     ej.*,
     st.table_name as source_table_name,
     st.table_label as source_table_label,
     tt.table_name as target_table_name,
     tt.table_label as target_table_label
   FROM entity_joins ej
   LEFT JOIN tables st ON ej.source_table_id = st.table_id
   LEFT JOIN tables tt ON ej.target_table_id = tt.table_id
   WHERE ej.company_code = $1 AND ej.is_active = $2
   ORDER BY ej.created_at DESC`,
  [companyCode, true]
);

예시 2: 조인 생성 (유효성 검증 포함)

변경 전:

// 조인 유효성 검증
const sourceTable = await prisma.tables.findUnique({
  where: { table_id: sourceTableId },
});

const targetTable = await prisma.tables.findUnique({
  where: { table_id: targetTableId },
});

if (!sourceTable || !targetTable) {
  throw new Error("Invalid table references");
}

// 조인 생성
const join = await prisma.entity_joins.create({
  data: {
    source_table_id: sourceTableId,
    target_table_id: targetTableId,
    join_type: joinType,
    join_condition: joinCondition,
    company_code: companyCode,
  },
});

변경 후:

// 조인 유효성 검증 (Promise.all로 병렬 실행)
const [sourceTable, targetTable] = await Promise.all([
  queryOne<any>(
    `SELECT * FROM tables WHERE table_id = $1`,
    [sourceTableId]
  ),
  queryOne<any>(
    `SELECT * FROM tables WHERE table_id = $1`,
    [targetTableId]
  ),
]);

if (!sourceTable || !targetTable) {
  throw new Error("Invalid table references");
}

// 조인 생성
const join = await queryOne<any>(
  `INSERT INTO entity_joins 
   (source_table_id, target_table_id, join_type, join_condition, 
    company_code, created_at, updated_at)
   VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
   RETURNING *`,
  [sourceTableId, targetTableId, joinType, joinCondition, companyCode]
);

예시 3: 조인 수정

변경 전:

const join = await prisma.entity_joins.update({
  where: { join_id: joinId },
  data: {
    join_type: joinType,
    join_condition: joinCondition,
    is_active: isActive,
  },
});

변경 후:

const updateFields: string[] = ["updated_at = NOW()"];
const values: any[] = [];
let paramIndex = 1;

if (joinType !== undefined) {
  updateFields.push(`join_type = $${paramIndex++}`);
  values.push(joinType);
}

if (joinCondition !== undefined) {
  updateFields.push(`join_condition = $${paramIndex++}`);
  values.push(joinCondition);
}

if (isActive !== undefined) {
  updateFields.push(`is_active = $${paramIndex++}`);
  values.push(isActive);
}

const join = await queryOne<any>(
  `UPDATE entity_joins 
   SET ${updateFields.join(", ")}
   WHERE join_id = $${paramIndex}
   RETURNING *`,
  [...values, joinId]
);

🔧 기술적 고려사항

1. 조인 타입 검증

const VALID_JOIN_TYPES = ["INNER", "LEFT", "RIGHT", "FULL"];
if (!VALID_JOIN_TYPES.includes(joinType)) {
  throw new Error("Invalid join type");
}

2. 조인 조건 검증

// 조인 조건은 SQL 조건식 형태 (예: "source.id = target.parent_id")
// SQL 인젝션 방지를 위한 검증 필요
const isValidJoinCondition = /^[\w\s.=<>]+$/.test(joinCondition);
if (!isValidJoinCondition) {
  throw new Error("Invalid join condition");
}

3. 순환 참조 방지

// 조인이 순환 참조를 만들지 않는지 검증
async function checkCircularReference(
  sourceTableId: number,
  targetTableId: number
): Promise<boolean> {
  // 재귀적으로 조인 관계 확인
  // ...
}

4. LEFT JOIN으로 관련 테이블 정보 조회

조인 설정 조회 시 source/target 테이블 정보를 함께 가져오기 위해 LEFT JOIN 사용


📝 전환 체크리스트

1단계: Prisma 호출 전환

  • getEntityJoins() - 목록 조회 (findMany with include)
  • getEntityJoin() - 단건 조회 (findUnique)
  • createEntityJoin() - 생성 (create with validation)
  • updateEntityJoin() - 수정 (update)
  • deleteEntityJoin() - 삭제 (delete)

2단계: 코드 정리

  • import 문 수정 (prismaquery, queryOne)
  • 조인 유효성 검증 로직 유지
  • Prisma import 완전 제거

3단계: 테스트

  • 단위 테스트 작성 (5개)
  • 조인 유효성 검증 테스트
  • 순환 참조 방지 테스트
  • 통합 테스트 작성 (2개)

4단계: 문서화

  • 전환 완료 문서 업데이트

🎯 예상 난이도 및 소요 시간

  • 난이도: (중간)

    • LEFT JOIN 쿼리
    • 조인 유효성 검증
    • 순환 참조 방지
  • 예상 소요 시간: 1시간


상태: 대기 중
특이사항: LEFT JOIN, 조인 유효성 검증, 순환 참조 방지 포함