19. 캡스톤 프로젝트: 타입 안전한 태스크 스케줄러 🟢
이 프로젝트는 본서에서 배운 다양한 패턴들을 하나의 완성된 시스템으로 통합하는 과정입니다. 제네릭, 트레이트, 타입 상태(Type-state), 채널, 에러 처리, 테스트 기법을 모두 동원하여 타입 안전하고 동시성을 지원하는 태스크 스케줄러를 구축해 봅니다.
예상 소요 시간: 4~6시간 | 난이도: ★★★
실무 연습 포인트:
- 제네릭과 트레이트 경계 (1~2장)
- 태스크 생명주기를 위한 타입 상태 패턴 (3장)
- 제로 비용 상태 마커를 위한 PhantomData (4장)
- 워커 통신을 위한 채널 (5장)
- 스코프 스레드를 이용한 동시성 (6장)
thiserror를 활용한 에러 처리 (10장)- 속성 기반 테스트를 포함한 검증 (14장)
TryFrom과 유효성 검사 타입 설계 (15장)
프로젝트 개요: 해결해야 할 문제
다음 요구사항을 충족하는 태스크 스케줄러를 만드세요.
- **태스크(Task)**는 엄격한 생명주기를 가집니다:
대기(Pending) → 실행 중(Running) → 완료(Completed)혹은실패(Failed). - **워커(Worker)**는 채널에서 태스크를 가져와 실행하고 결과를 보고합니다.
- **스케줄러(Scheduler)**는 태스크 제출, 워커 관리, 결과 수집을 총괄합니다.
- 잘못된 상태 전이는 런타임 패닉이 아닌 컴파일 타임 에러로 차단되어야 합니다.
단계별 가이드
1단계: 태스크 타입 정의
PhantomData와 타입 상태 마커를 사용해 Task 구조체를 설계하세요. Pending 상태의 태스크만 start() 메서드를 가질 수 있으며, 호출 시 Running 상태의 태스크를 반환해야 합니다.
2단계: 작업 함구(Work Function) 정의
태스크가 실제로 수행할 로직을 담을 박스형 클로저(Box<dyn FnOnce(...)>)를 포함하는 WorkItem을 만드세요.
3단계: 에러 처리 시스템 구축
thiserror를 사용하여 스케줄러 폐쇄, 태스크 실패, 채널 통신 에러, 워커 패닉 등을 구분하는 에러 열거형을 정의하세요.
4단계: 스케줄러 구현
채널과 스코프 스레드를 사용하여 여러 워커가 병렬로 작업을 처리하고 결과를 수집하는 전체 로직을 완성하세요.
5단계: 통합 테스트 및 검증
- 모든 태스크가 정상 처리되는 케이스(Happy path)
- 특정 태스크가 실패할 때의 에러 핸들링
- 빈 스케줄러가 정상적으로 소멸되는지 확인하는 테스트를 작성하세요.
평가 기준
| 항목 | 목표 |
|---|---|
| 타입 안전성 | 잘못된 상태 전이(예: 완료된 태스크 재실행)가 컴파일되지 않음 |
| 동시성 | 워커들이 병렬로 작동하며 데이터 경합(Data Race)이 없음 |
| 에러 처리 | 모든 실패가 TaskResult에 캡처되며, 예기치 않은 패닉이 발생하지 않음 |
| 코드 구조 | API가 impl Into나 유효성 검사 타입을 사용하여 사용하기 편리함 |
| 문서화 | 주요 타입과 트레이트에 불변성(Invariant)을 설명하는 주석이 포함됨 |
확장 아이디어 (심화 학습)
기본 스케줄러가 완성되었다면 다음 기능들을 추가해 보세요:
- 우선순위 큐: 특정 우선순위가 높은 태스크를 먼저 처리합니다.
- 재시도 전략(Retry Policy): 실패한 태스크를 N번까지 자동으로 재시도합니다.
- 취소(Cancellation): 특정
TaskId를 가진 대기 중인 태스크를 취소합니다. - 비동기 버전:
tokio와 비동기 채널을 사용하도록 포팅해 보세요. - 메트릭 수집: 각 워커별 처리량, 평균 실행 시간, 실패율을 추적합니다.
본 프로젝트를 성공적으로 마치셨다면, Rust의 핵심 디자인 패턴을 실무에 적용할 준비가 되신 것입니다.