-
Notifications
You must be signed in to change notification settings - Fork 1
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: 작업 상태
POST /portal/link는 인증된 사용자와 Idempotency-Key를 요구합니다. 현재 지원하는 portal_type은 suwon입니다.
백엔드는 사용자의 기존 연동 여부에 따라 작업을 LINK 또는 REFRESH로 분류합니다. 같은 사용자가 같은 Idempotency-Key와 같은 요청을 다시 보내면 기존 작업을 반환합니다. 같은 키로 다른 요청을 보내면 C07 IDEMPOTENCY_KEY_CONFLICT입니다.
응답에는 다음 값이 들어갑니다.
-
job_id. -
status=accepted. -
/portal/link/jobs/{jobId}형태의polling_endpoint.
작업 상태는 다음 순서로 진행됩니다.
| 상태 | 의미 |
|---|---|
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}/summary는SUCCEEDED일 때만 학생 요약을 반환합니다. -
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
학생별 이수 학점은 student_courses의 값을 사용합니다. 공통 개설 과목의 학점만으로 개인 이수 학점을 대체하면 재수강이나 학점 보정 결과가 틀릴 수 있습니다.
학생, 학적 데이터, 적용 가능한 졸업 요건이 없으면 정상 계산이 불가능합니다. 이 경우 포털 연동 완료 여부와 학생의 입학년도·학과·전공을 먼저 확인합니다.
강의평가는 대상 학기와 과목을 기준으로 필요 여부를 판단합니다. 상태는 학기 학적 데이터에 저장되며 PENDING, SKIPPED, COMPLETED, NOT_RELEASED 등의 흐름을 사용합니다. 대상 학기나 제출 과목이 맞지 않으면 A07, A08 오류를 확인합니다.