힐링민턴파크
배드민턴 체육관의 코트 배정·경기 진행·대기·출석을 서버 권위(server-authoritative) 상태로 관리하고, 관리자·현장 모니터·모바일까지 Socket.IO 실시간 브로드캐스트로 동기화하는 운영 관리 시스템. Flutter 모바일 앱은 QR·NFC 현장 출석과 실시간 조회를 담당하며 출시 준비 중이다.
해결한 문제
운영 상태가 재시작과 함께 사라진다
진행 중인 경기 이력이 메모리와 JSON 파일에만 존재해, 서버를 재시작하면 코트 현황이 모두 초기화됐다.
화면마다 다른 상태를 보여준다
운영자 화면·TV 모니터·모바일이 각각 다른 상태를 표시해, 코트 배정 결과가 즉시 반영되지 않았다.
출석 중복 처리와 수기 기록
네트워크 재시도나 중복 클릭으로 출석이 이중 저장되고, 운영자가 수기로 출석을 처리해 누락이 잦았다.
데이터 흐름
모든 상태 변경은 단방향 선형 파이프라인을 따른다. 클라이언트는 상태를 계산하지 않고 수신만 한다.
REST API
Idempotency-Key
PostgreSQL
partial unique index
EventBus
in-process
Socket.IO
12토픽 브로드캐스트
쓰기 요청은 Idempotency-Key 래퍼를 통과한 뒤
FOR UPDATE row-locking과 partial unique index가 걸린 트랜잭션으로 커밋된다.
커밋 성공 후 EventBus가 이벤트를 emit하고, Socket.IO가 연결된 전 클라이언트에 동일한 상태를 브로드캐스트한다.
이벤트 토픽 12종은 모두 JSON Schema(ajv)로 계약 테스트된다.
관리자 토큰
JWT_SECRET
웹 운영 콘솔 전용 · 앱에 사용 불가
앱 사용자 토큰
APP_JWT_SECRET
소셜 로그인 · 관리자 API 접근 불가
현장 출석 토큰
VENUE_TOKEN_SECRET
QR/NFC 고정 인쇄용 · 별도 시크릿
기술적으로 내세울 점
개발자 관점에서 이 시스템이 어떻게 동작하는지를 코드 수준에서 설명한다.
동시성 제어는 DB가 강제한다
코트 배정 시 FOR UPDATE row-locking으로 동시 쓰기를 직렬화하고,
코트당 동시 1경기를 partial unique index (WHERE status='active')로 DB 레벨에서 강제한다.
애플리케이션 코드에 버그가 있더라도 DB가 무결성을 보장한다.
소켓 12토픽 전부 계약 테스트
live:state,
court:updated,
match:started/finished 등 12개 소켓 토픽의 페이로드를 JSON Schema(ajv)로 정의하고 전수 검증한다.
백엔드와 앱·웹 사이의 인터페이스 회귀를 CI에서 자동으로 잡는다.
Idempotency-Key로 중복 저장 차단
네트워크 재시도나 중복 클릭이 발생해도 idempotency_keys 테이블이 동일 요청을 감지해 경기 기록의 이중 저장을 막는다.
출석 요청도 동일한 원칙이 적용되어 동일 토큰의 중복 출석은 에러가 아닌 멱등 응답(200)을 반환한다.
앱 테스트 119건 — Flutter analyze 0 issues
Flutter 앱은 flutter_test로 119건의 테스트가 green이며,
flutter analyze도 0 issues 상태다.
Riverpod 코드 생성(@riverpod)과 JSON 직렬화 코드 생성을 통해 보일러플레이트를 선언적으로 관리한다.
서버 시간 오프셋 보정 — 기기 시계를 믿지 않는다
앱은 매 응답의 serverTime으로 ServerClock의 오프셋을 갱신한다.
경기 진행 타이머는 이 보정 시각으로 계산하므로, 기기 시계가 틀려도 모든 화면에서 동일한 진행 시간을 표시한다.
WebSocket 전용(transports=['websocket'] 고정)으로 폴링 폴백을 제거해 배터리·트래픽 비용도 줄였다.