TV 벽면 패널에 정확한 Chromecast 연결 끊김 카운트 추가하기
요약
TV 벽보드의 Chromecast 연결 해제 카운트가 하드코딩된 0을 표시하는 문제를 해결하기 위해, API와 UI를 확장했습니다. `DashboardData` 인터페이스에 새 필드를 추가하고, 데이터 계산 로직을 클라이언트 측에서 제거하여 서비스 계층(`dashboard.service.ts`)에서 '단일 진실 공급원'으로 직접 계산하도록 리팩토링한 것이 핵심입니다.
핵심 포인트
- 클라이언트가 아닌 백엔드(서비스 계층)에서 메트릭을 계산해야 일관성이 유지됩니다.
- API 데이터 계약(DashboardData 인터페이스)에 필요한 모든 지표를 명시적으로 추가했습니다.
- 데이터 계산 로직을 리팩토링하여, 연결 해제된 카운트를 신뢰할 수 있는 출처로 제공합니다.
TV 벽보드에 정확한 Chromecast 연결 해제 카운트 추가
요약: 대시보드 API와 UI를 확장하여 실시간 chromecastDisconnected 메트릭을 노출함으로써, 오해의 소지가 있던 "항상 0"인 카운트를 수정했습니다. 이 변경 사항에는 DashboardData에 새 필드를 추가하고, 값을 계산하기 위해 dashboard.service.ts를 리팩토링했으며, TVDashboard.tsx에서 작은 UI 조정이 필요했습니다.
문제점
TV 벽보드(/tv)는 세 가지 Chromecast 메트릭을 표시했습니다:
online: number;
chromecastOnline: number;
chromecastTotal: number;
chromecastDisconnected는 UI에서 하드코딩으로 0 처리되어, 장치가 연결을 잃을 때마다 벽보드가 거짓 정보를 표시하게 만들었습니다. 증상은 프로덕션 환경에서 명확했습니다. 동기화 서비스의 로그에서는 수십 개의 오프라인 Chromecast가 보고되었음에도 불구하고, UI는 "연결 해제됨: 0"을 보여주었습니다. 오류는 발생하지 않았고, 단순히 데이터가 계산되지 않고 있었습니다.
제가 처음 시도한 방법
저의 첫 번째 직감은 클라이언트 측에서 연결 해제된 카운트를 도출하는 것이었습니다:
const disconnected = data.chromecastTotal - data.chromecastOnline;
저는 이 줄을 src/features/tv/TVDashboard.tsx에 추가하고 표시했습니다. 이는 정상적인 경로(happy path)에서는 작동했지만, 백엔드가 chromecastOnline에 대해 null을 반환하기 시작했을 때(동기화 작업이 부분적으로 실패할 때 발생한 최근의 엣지 케이스) 문제가 발생했습니다. 뺄셈 연산은 NaN을 생성했고, UI는 다음과 같은 런타임 경고를 발생시켰습니다:
Warning: Received NaN for prop `chromecastDisconnected` on <StatCard>.
저는 또한 폴백(fallback) 로직을 추가하여 서비스를 패치하려고 시도했습니다:
const online = chromecastOnline ?? 0;
하지만 이는 근본적인 데이터 품질 문제를 가리고, 여전히 UI가 도출된 메트릭에 의존하게 만들었습니다. 벽보드는 현지에서 계산하는 것이 아니라 진실의 출처(source of truth)를 반영해야 했습니다.
구현 과정
1. 데이터 계약 확장
첫 번째 단계는 API가 연결 해제된 카운트를 명시적으로 반환하도록 만드는 것이었습니다. 저는 src/features/tv/TVDashboard.tsx의 DashboardData 인터페이스에 해당 필드를 추가했습니다:
@@ -24,6 +24,7 @@ interface DashboardData {
online: number;
chromecastOnline: number;
...
필드를 추가함으로써 TypeScript는 코드베이스 전체에서 해당 필드의 존재를 강제하게 되어 실수로 누락되는 것을 방지합니다.
2. 서비스 계층에서 지표 계산하기
모든 장치 집계는 src/services/dashboard.service.ts에 있습니다. 저는 UI에 의존하는 대신 원시 장치 목록에서 chromecastDisconnected를 직접 계산하도록 getDeviceCounts를 리팩토링했습니다:
@@ -5,26 +5,37 @@ export async function getLastSync() {
}
...
이 변경의 이유?
- 단일 진실 공급원 (Single source of truth) – 이제 서비스가 계산을 담당하여 모든 소비자(벽걸이 보드, API, 관리자 UI)에서 일관성을 보장합니다.
- 성능 (Performance) – 여러 개의 카운트 쿼리를 실행하는 대신 한 번만 DB에 접근(
getAll)하므로 왕복 횟수가 7회에서 1회로 줄어듭니다. - 탄력성 (Resilience) – 장치에 예상치 못한
status값이 있는 경우, 온라인으로 조용히 처리하기보다 '연결 끊김' 버킷으로 분류되므로 더 안전합니다.
3. 컴포넌트에 새 필드 연결하기
TVDashboard.tsx에서 업데이트된 타입을 가져와 지표를 표시했습니다:
import { DashboardData } from '../../services/dashboard.service';
const TVDashboard: React.FC = () => {
...
더 이상 파생 계산도, NaN 경고도 없습니다. 이제 UI는 백엔드를 신뢰합니다.
4. API 계약 업데이트하기 (해당하는 경우)
벽걸이 보드가 REST 엔드포인트( /api/dashboard)를 사용하는 경우, src/api/dashboard.controller.ts의 응답 스키마에 새 필드를 추가했습니다:
router.get('/dashboard', async (req, res) => {
const counts = await getDeviceCounts();
res.json(counts); // counts는 이제 chromecastDisconnected를 포함합니다
...
또한 문서의 정확성을 유지하기 위해 OpenAPI 스펙(swagger.yaml)도 업데이트했습니다.
5. 테스트
새 필드를 확인하는 유닛 테스트를 tests/services/dashboard.service.test.ts에 추가했습니다:
test('getDeviceCounts returns correct chromecastDisconnected', async () => {
// Mock deviceRepo.getAll() to return a mix of online/offline chromecasts
deviceRepo.getAll = jest.fn().mockResolvedValue([
...
})
모든 CI 파이프라인이 변경 후 통과했습니다.
핵심 요약 (Key Takeaway)
백엔드에서 신뢰성 있게 계산할 수 있는 지표를 UI가 직접 도출하도록 두지 마세요. 서비스 계층(service layer)에 계산을 중앙 집중화하면 단일 진실 공급원(single source of truth)을 제공하고, DB 왕복 횟수(DB round-trips)를 줄이며, 클라이언트 측의 예외 처리(edge-case handling)를 제거할 수 있습니다.
다음 단계 (What's Next)
다음 단계는 "
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/tvview · 2026-10-03
#playadev #buildinpublic
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기