TIL -20260630

juni·2026년 6월 30일

TIL

목록 보기
389/468

0630 백엔드 실무 심화 (7/N): 상태값 설계와 변경 이력 관리


✅ 1. 상태값이란 무엇인가?

  • 상태값(Status)은 데이터가 현재 어떤 단계에 있는지 표현하는 값입니다.
  • 백엔드 실무에서는 주문, 상담 신청, 사전예약, 결제, 배송, 알림 발송, 엑셀 생성 작업처럼 여러 단계로 진행되는 데이터에 상태값을 사용합니다.
상담 신청:
PENDING → CALLING → DONE → CANCELLED

주문:
PENDING → PAID → PROCESSING → COMPLETED → CANCELLED

엑셀 Export:
PENDING → PROCESSING → COMPLETED → FAILED
  • 상태값은 단순한 문자열이 아니라 업무 흐름을 표현하는 핵심 데이터입니다.
  • 상태값이 엉성하면 관리자 페이지, 통계, 알림, 엑셀 다운로드, 정산, 고객 응대가 모두 꼬일 수 있습니다.

✅ 2. 상태값 설계가 중요한 이유

➕ 2-1. 운영 흐름을 명확하게 만든다

  • 상담 신청이 “접수됨”인지, “상담 중”인지, “완료”인지, “취소”인지 알아야 운영자가 다음 행동을 결정할 수 있습니다.
PENDING:
새로 접수된 상담

CALLING:
상담원이 연락 중

DONE:
상담 완료

CANCELLED:
취소 또는 무효 처리

➕ 2-2. 관리자 페이지 필터 기준이 된다

  • 상태값은 관리자 목록에서 필터로 자주 사용됩니다.
상담 신청 목록:
- 전체
- 신규 접수
- 상담 중
- 완료
- 취소

주문 목록:
- 결제 대기
- 결제 완료
- 처리 중
- 개통 완료
- 취소
  • 상태값이 잘 설계되어 있으면 운영자가 데이터를 빠르게 찾을 수 있습니다.

➕ 2-3. 통계와 리포트 기준이 된다

  • 상태값은 통계의 기준이 됩니다.
오늘 신규 신청 수
상담 완료 수
취소 수
전환율
유입경로별 완료율
상품별 상담 완료율
  • 상태값이 불명확하면 통계도 믿기 어려워집니다.

➕ 2-4. 자동화 조건이 된다

  • 특정 상태가 되었을 때 알림을 보내거나, 일정 시간이 지나면 자동 변경하는 기능을 만들 수 있습니다.
PENDING 상태로 24시간 이상 유지
  ↓
관리자 알림

사전예약 상태가 OPEN
  ↓
신청 버튼 노출

ExportJob 상태가 COMPLETED
  ↓
다운로드 버튼 활성화

✅ 3. 좋은 상태값의 기준

➕ 3-1. 의미가 명확해야 한다

나쁜 예시:
WAIT
OK
NO
CHECK
TEMP
END
  • 이런 상태값은 나중에 봤을 때 정확한 의미를 알기 어렵습니다.
좋은 예시:
PENDING
IN_PROGRESS
COMPLETED
CANCELLED
FAILED
EXPIRED
  • 상태명만 봐도 현재 단계를 이해할 수 있어야 합니다.

➕ 3-2. 상태값 개수가 너무 많지 않아야 한다

  • 상태가 너무 많으면 운영자가 헷갈립니다.
  • 백엔드 로직도 복잡해지고, 통계도 어려워집니다.
좋지 않은 예시:
NEW
NEW_CHECKED
CALL_WAIT
CALL_FIRST
CALL_SECOND
CALL_THIRD
CALL_FAIL_TEMP
CALL_FAIL_FINAL
DONE_OK
DONE_BAD
CANCEL_USER
CANCEL_ADMIN
  • 처음에는 큰 흐름 중심으로 단순하게 만들고, 정말 필요한 경우에만 세분화하는 것이 좋습니다.

➕ 3-3. 상태 변경 방향이 정해져 있어야 한다

  • 아무 상태에서나 아무 상태로 바뀌면 데이터가 꼬입니다.
  • 상태 변경에는 가능한 흐름이 있어야 합니다.
상담 신청:
PENDING → CALLING → DONE
PENDING → CANCELLED
CALLING → CANCELLED

불가능한 흐름:
DONE → PENDING
CANCELLED → CALLING
  • 예외적으로 되돌리기가 필요하다면 권한과 이력을 함께 남겨야 합니다.

✅ 4. 상태값을 문자열로 관리할 때의 문제

  • 상태값을 아무 문자열로 저장하면 오타와 불일치가 생길 수 있습니다.
DB에 저장된 상태:
PENDING
pending
Pendding
WAIT
WAITING
DONE
COMPLETE
COMPLETED
  • 같은 의미인데 여러 값이 섞이면 필터, 통계, 엑셀 다운로드에서 문제가 됩니다.

➕ 4-1. 문제 예시

SELECT COUNT(*) FROM consults
WHERE status = 'COMPLETED';
  • 만약 어떤 데이터는 DONE, 어떤 데이터는 COMPLETE로 저장되어 있다면 완료 건수가 정확히 나오지 않습니다.

✅ 5. Enum으로 상태값 관리하기

  • 상태값은 가능하면 Enum으로 관리하는 것이 좋습니다.
  • Prisma, TypeScript, DB에서 상태값 범위를 제한하면 오타를 줄일 수 있습니다.

➕ 5-1. Prisma Enum 예시

enum ConsultStatus {
  PENDING
  CALLING
  DONE
  CANCELLED
}

model Consult {
  id        Int           @id @default(autoincrement())
  name      String
  phone     String
  status    ConsultStatus @default(PENDING)
  createdAt DateTime      @default(now())
  updatedAt DateTime      @updatedAt
}
  • ConsultStatus에 정의된 값만 저장할 수 있습니다.
  • 상태값 오타를 줄일 수 있습니다.

➕ 5-2. TypeScript Enum 예시

export enum ConsultStatus {
  PENDING = 'PENDING',
  CALLING = 'CALLING',
  DONE = 'DONE',
  CANCELLED = 'CANCELLED',
}
  • 프론트엔드와 백엔드에서 같은 상태값을 공유하면 더 안전합니다.
  • 가능하면 상수 파일이나 타입 패키지로 관리하는 것도 좋습니다.

✅ 6. 상태값 이름과 화면 표시명 분리

  • DB에는 영어 코드값을 저장하고, 화면에는 한글 표시명을 보여주는 방식이 좋습니다.

➕ 6-1. 코드값과 표시명 예시

DB 상태값화면 표시명
PENDING신규 접수
CALLING상담 중
DONE상담 완료
CANCELLED취소
export const CONSULT_STATUS_LABEL: Record<ConsultStatus, string> = {
  [ConsultStatus.PENDING]: '신규 접수',
  [ConsultStatus.CALLING]: '상담 중',
  [ConsultStatus.DONE]: '상담 완료',
  [ConsultStatus.CANCELLED]: '취소',
};
  • DB에 한글을 직접 저장하면 나중에 화면 문구 변경, 다국어 처리, API 연동에서 불편해질 수 있습니다.
  • DB에는 안정적인 코드값을 저장하고, UI에서 표시명을 매핑하는 것이 좋습니다.

✅ 7. 상태 변경 API 설계

  • 상태 변경은 단순 수정 API와 분리하는 것이 좋습니다.
  • 상태 변경은 업무 흐름과 이력이 중요하기 때문입니다.

➕ 7-1. API 예시

PATCH /api/admin/consults/:id/status
PATCH /api/admin/orders/:id/status
PATCH /api/admin/reservations/:id/status

➕ 7-2. 요청 Body 예시

{
  "status": "DONE",
  "memo": "고객 상담 완료"
}
  • 상태 변경 사유나 메모를 함께 받을 수 있습니다.
  • 나중에 변경 이력에 남기기 좋습니다.

✅ 8. 상태 변경 검증

  • 상태 변경 API에서는 다음을 검증해야 합니다.

➕ 8-1. 검증 항목

  1. 로그인한 관리자인가?
  2. 해당 데이터를 수정할 권한이 있는가?
  3. 요청한 상태값이 허용된 상태인가?
  4. 현재 상태에서 목표 상태로 변경 가능한가?
  5. 상태 변경 사유가 필요한 경우 입력했는가?
  6. 변경 이력을 저장할 수 있는가?
예시:
DONE 상태의 상담을 다시 PENDING으로 변경
  ↓
일반 관리자에게는 금지

SUPER_ADMIN만 되돌리기 가능
  ↓
사유 필수
  ↓
변경 이력 저장

✅ 9. 상태 전이 규칙

  • 상태 전이(State Transition)는 어떤 상태에서 어떤 상태로 변경할 수 있는지 정한 규칙입니다.

➕ 9-1. 상담 신청 상태 전이 예시

const CONSULT_STATUS_TRANSITIONS: Record<ConsultStatus, ConsultStatus[]> = {
  [ConsultStatus.PENDING]: [
    ConsultStatus.CALLING,
    ConsultStatus.DONE,
    ConsultStatus.CANCELLED,
  ],
  [ConsultStatus.CALLING]: [
    ConsultStatus.DONE,
    ConsultStatus.CANCELLED,
  ],
  [ConsultStatus.DONE]: [],
  [ConsultStatus.CANCELLED]: [],
};
  • DONE과 CANCELLED는 종료 상태로 보고 일반 변경을 막았습니다.
  • 실제 운영에서 되돌리기가 필요하다면 별도 권한과 사유를 요구할 수 있습니다.

➕ 9-2. 상태 전이 검증 함수

function canChangeStatus(
  currentStatus: ConsultStatus,
  nextStatus: ConsultStatus,
) {
  return CONSULT_STATUS_TRANSITIONS[currentStatus].includes(nextStatus);
}
  • 상태 변경 API에서 이 함수를 사용하면 잘못된 흐름을 막을 수 있습니다.

✅ 10. 상태 변경 이력이란 무엇인가?

  • 상태 변경 이력(Status History)은 데이터의 상태가 언제, 누가, 어떤 값에서 어떤 값으로 바뀌었는지 기록하는 테이블입니다.
  • 상태값만 저장하면 현재 상태는 알 수 있지만, 과거 흐름은 알 수 없습니다.
현재 상태:
DONE

알 수 없는 것:
누가 변경했는지
언제 변경했는지
이전 상태가 무엇이었는지
왜 변경했는지
  • 실무에서는 현재 상태와 변경 이력을 함께 관리해야 합니다.

✅ 11. 변경 이력이 필요한 이유

➕ 11-1. 운영 추적

  • 누가 언제 상담 상태를 바꿨는지 알 수 있습니다.
  • 고객 민원이나 내부 확인이 필요할 때 근거가 됩니다.

➕ 11-2. 관리자 책임 분리

  • 여러 관리자가 같은 데이터를 처리할 때 이력이 없으면 책임 추적이 어렵습니다.

➕ 11-3. 장애와 실수 복구

  • 잘못된 상태 변경이 발생했을 때 이전 상태를 확인할 수 있습니다.

➕ 11-4. 통계 분석

  • 상태 변경 시간을 기준으로 처리 시간을 계산할 수 있습니다.
신규 접수 시간:
2026-06-30 10:00

상담 완료 시간:
2026-06-30 14:30

처리 소요 시간:
4시간 30분

✅ 12. 상태 변경 이력 테이블 설계

➕ 12-1. ConsultStatusHistory 모델 예시

model ConsultStatusHistory {
  id           Int           @id @default(autoincrement())
  consultId    Int
  adminId      Int?
  fromStatus   ConsultStatus?
  toStatus     ConsultStatus
  memo         String?
  reason       String?
  ipAddress    String?
  userAgent    String?
  createdAt    DateTime      @default(now())

  consult      Consult       @relation(fields: [consultId], references: [id])
}

➕ 12-2. 주요 필드 설명

필드설명
consultId어떤 상담 신청의 이력인지
adminId변경한 관리자
fromStatus이전 상태
toStatus변경 후 상태
memo관리자 메모
reason변경 사유
ipAddress요청 IP
userAgent요청 브라우저 정보
createdAt변경 일시
  • 생성 시점에는 fromStatus가 없을 수 있습니다.
  • 자동 변경이라면 adminId가 없고 reason에 시스템 변경 사유를 남길 수 있습니다.

✅ 13. 상태 변경 Service 예시

async updateConsultStatus(
  admin: AdminPayload,
  consultId: number,
  dto: UpdateConsultStatusDto,
  requestMeta: {
    ipAddress?: string;
    userAgent?: string;
  },
) {
  return this.prisma.$transaction(async (tx) => {
    const consult = await tx.consult.findUnique({
      where: {
        id: consultId,
      },
    });

    if (!consult) {
      throw new NotFoundException('상담 신청을 찾을 수 없습니다.');
    }

    if (!canChangeStatus(consult.status, dto.status)) {
      throw new BadRequestException('변경할 수 없는 상태입니다.');
    }

    const updated = await tx.consult.update({
      where: {
        id: consultId,
      },
      data: {
        status: dto.status,
      },
    });

    await tx.consultStatusHistory.create({
      data: {
        consultId,
        adminId: admin.id,
        fromStatus: consult.status,
        toStatus: dto.status,
        memo: dto.memo,
        ipAddress: requestMeta.ipAddress,
        userAgent: requestMeta.userAgent,
      },
    });

    return updated;
  });
}
  • 상태 변경과 이력 저장을 하나의 트랜잭션으로 묶었습니다.
  • 상태만 바뀌고 이력이 빠지는 상황을 막을 수 있습니다.

✅ 14. 상태 변경과 권한

  • 상태 변경은 권한과 함께 설계해야 합니다.
  • 모든 관리자가 모든 상태로 변경할 수 있으면 운영 사고가 발생할 수 있습니다.

➕ 14-1. 권한별 상태 변경 예시

역할가능한 변경
SUPER_ADMIN모든 상태 변경, 되돌리기 가능
ADMIN일반 상태 변경 가능
MANAGER본인 담당 상담의 상담 중/완료 변경 가능
VIEWER변경 불가
MARKETER변경 불가

➕ 14-2. Service 권한 검증 예시

if (admin.role === AdminRole.VIEWER) {
  throw new ForbiddenException('상태 변경 권한이 없습니다.');
}

if (admin.role === AdminRole.MANAGER && consult.managerId !== admin.id) {
  throw new ForbiddenException('본인 담당 상담만 변경할 수 있습니다.');
}
  • Controller의 Guard에서 1차로 막고, Service에서 데이터 범위까지 확인하는 것이 좋습니다.

✅ 15. 상태값과 Soft Delete

  • 삭제가 필요한 데이터도 실제 삭제보다 상태값이나 deletedAt으로 처리하는 경우가 많습니다.

➕ 15-1. 실제 삭제

DELETE FROM consults WHERE id = 123;
  • 데이터가 완전히 사라집니다.
  • 복구와 추적이 어렵습니다.

➕ 15-2. Soft Delete

deletedAt = 2026-06-30 15:20
status = CANCELLED
  • 목록에서는 숨기지만 DB에는 남겨둡니다.
  • 이력 추적과 복구가 가능합니다.

➕ 15-3. 실무 기준

상담 신청:
Soft Delete 또는 CANCELLED 처리 권장

상품:
판매 종료, 숨김 처리 권장

배너:
isActive=false 또는 deletedAt 사용

관리자 계정:
삭제보다 isActive=false 권장
  • 운영 데이터는 실제 삭제보다 상태 변경이나 비활성화로 처리하는 것이 안전합니다.

✅ 16. 상태값과 날짜 필드

  • 상태값만으로는 “언제 완료됐는지”, “언제 취소됐는지”를 빠르게 알기 어렵습니다.
  • 자주 쓰는 상태 변경 시간은 별도 컬럼으로 저장할 수 있습니다.

➕ 16-1. 예시

model Consult {
  id          Int           @id @default(autoincrement())
  status      ConsultStatus @default(PENDING)
  createdAt   DateTime      @default(now())
  updatedAt   DateTime      @updatedAt
  calledAt    DateTime?
  completedAt DateTime?
  cancelledAt DateTime?
}

➕ 16-2. 장점

  • 완료 일시 기준 검색이 빠릅니다.
  • 처리 소요 시간 계산이 쉽습니다.
  • 통계 쿼리가 단순해집니다.
  • 관리자 화면에서 표시하기 좋습니다.
처리 소요 시간:
completedAt - createdAt

취소율 계산:
cancelledAt이 있는 데이터 기준
  • 단, 날짜 필드와 이력 테이블의 값이 서로 맞도록 트랜잭션으로 관리해야 합니다.

✅ 17. 상태값과 알림

  • 상태가 바뀌면 알림이 필요한 경우가 있습니다.
  • 다만 상태 변경 트랜잭션 안에서 외부 알림을 직접 보내는 것은 조심해야 합니다.

➕ 17-1. 상태 변경 후 알림 예시

상담 상태 DONE 변경
  ↓
고객에게 완료 알림톡 발송

주문 상태 COMPLETED 변경
  ↓
고객에게 개통 완료 알림 발송

사전예약 상태 OPEN 변경
  ↓
신청 대기 고객에게 오픈 알림 발송

➕ 17-2. 권장 흐름

1. 상태 변경
2. 상태 변경 이력 저장
3. 트랜잭션 완료
4. Queue에 알림 Job 등록
5. Worker가 알림 발송
6. 알림 성공/실패 이력 저장
  • 상태 변경 자체는 DB 트랜잭션으로 안전하게 처리합니다.
  • 알림은 Queue로 분리해 실패와 재시도를 관리하는 것이 좋습니다.

✅ 18. 상태값과 관리자 UI

  • 상태값은 백엔드만의 문제가 아닙니다.
  • 관리자 UI에서도 상태값이 명확하게 보이고, 가능한 변경만 선택할 수 있어야 합니다.

➕ 18-1. UI에서 필요한 요소

  • 상태 배지
  • 상태 필터
  • 상태 변경 드롭다운
  • 변경 가능 상태만 표시
  • 상태 변경 사유 입력
  • 상태 변경 이력 타임라인
  • 처리 시간 표시
  • 완료/취소 상태에서 수정 제한
상담 상태:
[신규 접수] [상담 중] [상담 완료] [취소]

변경 이력:
2026-06-30 10:00 신규 접수
2026-06-30 11:20 상담 중 - 김관리자
2026-06-30 14:30 상담 완료 - 김관리자
  • UI에서 아무 상태나 선택 가능하게 하면 실수가 늘어납니다.
  • 백엔드 상태 전이 규칙과 UI 선택지를 맞추는 것이 좋습니다.

✅ 19. 상태값 설계에서 자주 하는 실수

➕ 19-1. 상태값을 화면 문구로 저장

DB 상태값:
신규 접수
상담 중
상담 완료
취소
  • 화면 문구가 바뀌면 DB 값까지 바꿔야 합니다.
  • API 연동이나 통계에서도 불편합니다.
DB 상태값:
PENDING
CALLING
DONE
CANCELLED

UI 표시:
신규 접수
상담 중
상담 완료
취소

➕ 19-2. 상태 변경 이력을 남기지 않음

  • 현재 상태만 있으면 누가 언제 바꿨는지 알 수 없습니다.
  • 운영 사고가 났을 때 원인 파악이 어렵습니다.

➕ 19-3. 상태 변경 규칙이 없음

PENDING → DONE
DONE → PENDING
CANCELLED → CALLING
DONE → CANCELLED
  • 아무 상태로 변경 가능하면 데이터가 신뢰성을 잃습니다.
  • 상태 전이 규칙을 코드로 관리해야 합니다.

➕ 19-4. 상태와 날짜 필드가 따로 논다

status = DONE
completedAt = null

status = PENDING
completedAt = 2026-06-30
  • 상태와 날짜 필드가 맞지 않으면 통계가 틀어집니다.
  • 상태 변경 시 관련 날짜도 함께 업데이트해야 합니다.

✅ 20. 실무 체크리스트

➕ 20-1. 상태값 설계 체크리스트

  1. 상태값 이름이 명확한가?
  2. DB에는 코드값을 저장하고 UI 표시명은 분리했는가?
  3. 상태값 개수가 과하게 많지 않은가?
  4. 종료 상태와 진행 상태를 구분했는가?
  5. 상태 전이 규칙이 정해져 있는가?
  6. 되돌리기 가능 여부와 권한이 정해져 있는가?
  7. 상태별 날짜 필드가 필요한지 검토했는가?
  8. 상태값이 통계와 필터 기준으로 사용 가능한가?

➕ 20-2. 상태 변경 API 체크리스트

  1. 상태 변경 API가 별도로 분리되어 있는가?
  2. 요청 상태값이 Enum으로 검증되는가?
  3. 현재 상태에서 변경 가능한 상태인지 확인하는가?
  4. 상태 변경 권한을 확인하는가?
  5. 본인 담당 데이터인지 확인하는가?
  6. 상태 변경과 이력 저장을 트랜잭션으로 묶었는가?
  7. 변경 사유나 메모를 저장하는가?
  8. 상태 변경 후 필요한 알림은 Queue로 분리했는가?

➕ 20-3. 변경 이력 체크리스트

  1. 이전 상태와 변경 후 상태를 모두 저장하는가?
  2. 변경한 관리자 ID를 저장하는가?
  3. 변경 사유와 메모를 저장하는가?
  4. IP와 User-Agent를 저장하는가?
  5. 자동 변경과 수동 변경을 구분할 수 있는가?
  6. 관리자 화면에서 이력을 확인할 수 있는가?
  7. 로그에 개인정보가 과도하게 저장되지 않는가?

✅ 21. AI를 활용해 상태값/이력 구조를 설계할 때 질문법

  • 상태값은 업무 흐름과 직접 연결되어 있기 때문에 AI에게 요청할 때 실제 상태 흐름, 관리자 권한, 이력 필요 여부를 함께 알려줘야 합니다.

➕ 21-1. 좋은 질문 예시

NestJS + Prisma로 상담 신청 상태 관리 기능을 설계하고 싶어.

상황:
1. 상담 신청은 신규 접수, 상담 중, 상담 완료, 취소 상태가 있음
2. 관리자는 상태를 변경할 수 있음
3. MANAGER는 본인 담당 상담만 변경 가능
4. VIEWER는 조회만 가능하고 변경 불가
5. 상태 변경 시 누가 언제 어떤 상태에서 어떤 상태로 바꿨는지 이력을 남기고 싶음
6. 완료 상태가 되면 completedAt을 저장하고 싶음
7. 취소 상태가 되면 cancelledAt을 저장하고 싶음
8. 상태 변경 후 고객에게 알림톡을 보낼 수도 있음
9. 알림톡은 Queue로 분리하고 싶음

요청:
- Prisma Enum과 모델 설계
- 상태 전이 규칙
- NestJS 상태 변경 Service 예시
- 변경 이력 테이블 설계
- 권한 검증 기준
- 상태별 날짜 필드 처리
- 알림 Queue 분리 방식
을 실무 기준으로 설명해줘.

➕ 21-2. AI 답변 검증 기준

  1. 상태값을 문자열 아무 값으로 받지 않고 Enum으로 제한하는가?
  2. DB 코드값과 UI 표시명을 분리하는가?
  3. 상태 전이 규칙을 고려하는가?
  4. 상태 변경과 이력 저장을 트랜잭션으로 묶는가?
  5. 권한과 데이터 범위 검사를 함께 설명하는가?
  6. 상태별 날짜 필드를 함께 업데이트하는가?
  7. 외부 알림은 트랜잭션 밖 Queue로 분리하는가?
  8. 변경 이력에 관리자, 이전 상태, 변경 후 상태, 사유를 남기도록 하는가?

📌 요약

  • 상태값은 데이터가 현재 어떤 단계에 있는지 표현하는 값이며, 주문, 상담 신청, 사전예약, 알림, 엑셀 Export 같은 기능에서 핵심 역할을 합니다.
  • 상태값은 운영 흐름, 관리자 필터, 통계, 자동화 조건, 알림 발송 기준이 됩니다.
  • DB에는 PENDING, DONE, CANCELLED 같은 안정적인 코드값을 저장하고, UI에서는 한글 표시명으로 매핑하는 것이 좋습니다.
  • 상태값은 문자열 자유 입력보다 Enum으로 제한해 오타와 불일치를 줄여야 합니다.
  • 상태 변경은 가능한 흐름, 즉 상태 전이 규칙을 정해두고 검증해야 합니다.
  • 상태 변경과 변경 이력 저장은 트랜잭션으로 묶어야 데이터 정합성을 지킬 수 있습니다.
  • 변경 이력에는 누가, 언제, 어떤 상태에서 어떤 상태로, 왜 변경했는지를 남기는 것이 좋습니다.
  • 상태 변경 후 알림톡, SMS, 이메일 같은 외부 작업은 Queue로 분리해 실패와 재시도를 관리하는 것이 안정적입니다.

0개의 댓글