Skip to content

Core Domain Flows

kimhoeyun edited this page Aug 31, 2026 · 6 revisions

Core Domain Flows

포털 연동과 졸업 진단처럼 여러 컴포넌트가 연결되는 핵심 처리 흐름을 설명합니다.

포털 연동

sequenceDiagram
    participant C as Client
    participant B as Backend
    participant D as PostgreSQL
    participant Q as SQS
    participant W as Worker
    participant P as Portal
    participant S as S3

    C->>B: POST /portal/link + Idempotency-Key
    B->>D: scrape_jobs + outbox 저장
    B->>Q: Outbox payload 발행
    B-->>C: job_id + polling_endpoint
    W->>Q: 작업 수신
    W->>P: 포털 로그인 및 데이터 조회
    W->>S: 결과 JSON 저장
    W->>B: POST /internal/scrape-results + HMAC
    B->>S: 결과 JSON 조회 및 checksum 검증
    B->>D: 학사 데이터와 작업 상태 갱신
    C->>B: GET /portal/link/jobs/{jobId}
    B-->>C: 작업 상태
Loading

요청 수락

POST /portal/link는 인증된 사용자와 Idempotency-Key를 요구합니다. 현재 지원하는 portal_typesuwon입니다.

백엔드는 사용자의 기존 연동 여부에 따라 작업을 LINK 또는 REFRESH로 분류합니다. 같은 사용자가 같은 Idempotency-Key와 같은 요청을 다시 보내면 기존 작업을 반환합니다. 같은 키로 다른 요청을 보내면 C07 IDEMPOTENCY_KEY_CONFLICT입니다.

응답에는 다음 값이 들어갑니다.

  • job_id.
  • status=accepted.
  • /portal/link/jobs/{jobId} 형태의 polling_endpoint.

작업과 Outbox 상태

작업 상태는 다음 순서로 진행됩니다.

상태 의미
QUEUED 요청과 Outbox가 저장됨
RUNNING Worker가 작업을 처리 중
POST_PROCESSING 결과를 받아 학사 데이터를 반영 중
SUCCEEDED 데이터 반영 완료
FAILED 발행, Worker, 콜백, 후처리 중 실패

Outbox 상태는 PENDING, SENT, RETRYABLE_FAILED, DEAD입니다. 요청 수락 경로는 생성된 Outbox를 동기적으로 SQS에 발행하고, 발행 결과가 SENT가 아니면 요청을 C10으로 실패시킵니다.

현재 코드에는 dispatchEligibleOutboxes()가 있지만 이를 주기적으로 호출하는 운영 경로가 없습니다. RETRYABLE_FAILED가 자동으로 재처리된다고 가정하면 안 됩니다. 실패 원인을 해결한 뒤 새 요청으로 전체 흐름을 검증합니다.

상태 조회

  • GET /portal/link/jobs/{jobId}는 현재 상태와 오류 정보를 반환합니다.
  • GET /portal/link/jobs/{jobId}/summarySUCCEEDED일 때만 학생 요약을 반환합니다.
  • GET /portal/link/jobs/{jobId}/duration은 시작·종료 시각과 소요 시간을 반환합니다.

다른 사용자의 job_id는 조회할 수 없습니다.

결과 콜백

Worker는 POST /internal/scrape-results로 콜백합니다.

필수 Header입니다.

  • X-Timestamp.
  • X-Signature.

진단에 사용하는 선택 Header입니다.

  • X-Callback-Attempt.
  • X-Request-Id.

서명 canonical string은 X-Timestamp + "." + 원문 Body입니다. 이 문자열을 SCRAPING_CALLBACK_HMAC_SECRET으로 HMAC-SHA256 처리합니다. JSON을 다시 직렬화한 값으로 서명하면 원문이 달라져 검증에 실패할 수 있습니다.

성공 콜백은 result_s3_key를 요구합니다. 백엔드는 Bucket과 Prefix가 설정 범위 안에 있는지, Key가 해당 job_id 범위인지 확인합니다. 그다음 S3 결과를 읽어 선택적으로 SHA-256 checksum을 검증합니다.

동일한 콜백이 다시 와도 처리 결과를 중복 적재하지 않습니다. 중복 콜백은 scrape.job.callback.duplicate metric과 로그로 확인할 수 있습니다.

결과 후처리

S3 결과의 snake_case key는 내부 처리 전에 camelCase로 정규화됩니다. 후처리는 다음 데이터를 갱신합니다.

  • 사용자 포털 연동 상태.
  • 학생 프로필과 학적 정보.
  • 학기별 성적과 수강 과목.
  • 과목, 교수, 학과 기준 정보.
  • 졸업 진행도 계산에 필요한 데이터.

포털 payload에 designatedCourses가 포함되면 지정과목 원본도 같은 후처리 트랜잭션에서 갱신합니다. 이 목록은 지정과목 안내 정보이며 이수 내역이 아니므로 student_courses나 취득학점에 직접 반영하지 않습니다.

  • 필드가 없거나 null이면 구버전 scraper 결과로 보고 기존 목록과 스냅샷 버전을 유지합니다.
  • 빈 배열이면 기존 목록을 모두 삭제하고 스냅샷 버전을 갱신합니다.
  • 값이 있는 배열이면 원본 순서와 중복을 보존해 전체 교체합니다.
  • ScrapeJob.createdAt보다 오래된 결과는 학생 잠금과 스냅샷 버전 비교 후 무시합니다.

결과 스키마가 잘못되면 C17, S3를 읽지 못하면 C16, DB 반영 중 실패하면 C18로 작업이 실패합니다.

졸업 진행도 조회

GET /api/graduation/progress는 포털에서 적재한 학생별 학점과 졸업 요건 정책을 조합합니다.

flowchart LR
    Student["학생 학적\n입학년도·학과·전공"]
    Courses["학생별 이수 과목과 학점"]
    Rules["졸업·영역·복수전공·어학 요건"]
    Service["Graduation Service"]
    Result["충족 여부와 부족 항목"]

    Student --> Service
    Courses --> Service
    Rules --> Service
    Service --> Result
Loading

학생별 이수 학점은 student_courses의 값을 사용합니다. 공통 개설 과목의 학점만으로 개인 이수 학점을 대체하면 재수강이나 학점 보정 결과가 틀릴 수 있습니다.

학생, 학적 데이터, 적용 가능한 졸업 요건이 없으면 정상 계산이 불가능합니다. 이 경우 포털 연동 완료 여부와 학생의 입학년도·학과·전공을 먼저 확인합니다.

강의평가

강의평가는 대상 학기와 과목을 기준으로 필요 여부를 판단합니다. 상태는 학기 학적 데이터에 저장되며 PENDING, SKIPPED, COMPLETED, NOT_RELEASED 등의 흐름을 사용합니다. 대상 학기나 제출 과목이 맞지 않으면 A07, A08 오류를 확인합니다.

Clone this wiki locally