왜 클래스 기반 ZebraLabelService를 선택했는가
Zebra 라벨 프린터 통합: 왜 클래스 기반 ZebraLabelService를 선택했는가
“React 생태계에서 굳이 클래스를?”
하드웨어(프린터)를 다룰 때는 이야기가 달라집니다. 단일 연결, 복잡한 초기화, 상태/리소스 수명 주기… 이 모든 것을 안전하고 일관되게 관리해야 합니다.
본 글은 실제 프로덕션에서 Zebra 라벨 프린터를 통합하며 내린 결정—클래스 기반 싱글톤 서비스—의 이유와 구현을 원문 코드 중심으로 정리한 내용입니다.
문제 정의와 선택 배경
Zebra 라벨 프린터 통합은 하드웨어라는 단일·희소 자원을 다룹니다. 동시에 웹 앱(특히 React)에서는 화면 전환/재렌더링, 비동기 초기화, 중복 호출이 일어나기 쉽죠.
다음 요구사항이 핵심이었습니다:
- 싱글 연결: 물리 프린터는 사실상 단일 연결/세션 가정이 안전
- 상태 일관성: 앱 전역에서 동일한 프린터/연결 상태 공유
- 초기화 중복 방지: 비동기 초기화 경쟁 조건(Race Condition) 제거
- 명확한 라이프사이클: 초기화 → 사용(출력) → 정리의 수명 주기 캡슐화
따라서 클래스 기반 싱글톤 서비스가 자연스러운 해법.
함수 vs 클래스
함수형 예시
1 | // ❌ 문제가 될 수 있는 함수형 접근 |
단점: 함수형으로는 인스턴스 통제와 프라이빗 상태 보호가 약하고, 전역 모듈 상태는 캡슐화에 불리합니다.
클래스 기반 예시
1 | export class ZebraLabelService { |
장점:
- 상태 캡슐화 & 은닉
- 싱글톤으로 유일 인스턴스 보장
- this 컨텍스트로 상태 일관 접근
- 타입 안전성 확보
왜 싱글톤인가?
1. 하드웨어 리소스 충돌 방지, 2) 전역 상태 일관성, 3) 메모리/초기화 비용 절감, 4) 초기화 재사용.
서비스 레이어 아키텍처와 의존성 관리
프레젠테이션(UI)과 비즈니스 로직을 분리한 Service Layer 패턴을 채택합니다.
1 | // Service Layer (Business Logic) |
의존성 생성 지점을 초기화 시점으로 미루는 형태:
1 | // 외부 의존성을 생성자가 아닌 메서드에서 주입 |
- 테스트 시 Mock 주입 용이
- 교체 가능성 확보
단계별 실용 가이드
1) 중복 초기화 방지: Promise 캐싱
1 | async initialize(options: PrinterInitOptions = {}): Promise<boolean> { |
- 효과: 동시 호출에서도 단 한 번만 초기화 수행
- 장점: 리소스 절약, Race Condition 예방
2) 상태 기반 사전 검증: fail-fast
1 |
|
- 명확한 전제조건 확인
- 친절한 에러 메시지로 문제 지점 즉시 파악
3) 수명 주기(Lifecycle) 명료화
1 | // 초기화 |
- 초기화 → 사용 → 정리의 경로가 코드 레벨에서 분명
- UI/로직 레이어가 이 계약을 신뢰하고 사용 가능
성능/메모리 관점
구현 방식 | 인스턴스 수 | 메모리 사용량 | 초기화 비용 |
---|---|---|---|
함수형 (팩토리) | N개 | N × 기본메모리 | N × 비용 |
클래스형 (싱글톤) | 1개 | 1 × 기본메모리 | 1 × 비용 |
싱글톤은 단 한 번 초기화로 비용/부하를 낮추고, GC 압박도 줄입니다.
React Hook과의 통합 & 에러 처리 일관성
Hook 통합 예시 (UI는 UI만, 서비스는 로직만)
1 | // useAssetLabelOutput.ts에서의 사용 |
- 책임 분리가 뚜렷: Hook은 상태/인터랙션, Service는 비즈니스 로직
에러 처리 표준화
1 | // 모든 메서드에서 일관된 에러 처리 |
- 로그 포맷/전송 일관성
- 상위(UI)로 의미 있는 예외만 전달
의사결정 매트릭스 & 권장 사항
클래스 기반 싱글톤을 권장하는 상황
- 단일 하드웨어 또는 외부 리소스와 연결(프린터, 스캐너, 시리얼 장치 등)
- 초기화 비용이 크고 재사용 가치가 높은 경우
- 앱 전역에서 동일한 상태/연결을 공유해야 할 때
- Race Condition과 중복 초기화를 반드시 피해야 할 때
주의/한계
전역 싱글톤 남용은 테스트 격리/병렬성 저하를 유발 가능
→ 본 설계는 테스트에서 Mock 주입 경로(초기화 시점 의존성 생성)로 이 문제를 최소화
실전 체크리스트
- 초기화는 반드시 한 번만: initPromise로 동시 호출 수렴
- 모든 퍼블릭 메서드 전에 ensurePrinterReady 선행 검증
- Hook에서는 UI 상태(로딩/에러/완료)만 관리
- 에러 포맷/로깅 일관성 유지
- cleanup() 경로 마련: 페이지 이탈/앱 종료 시 안전 해제
- 테스트에서 Mock 가능한 경계(래퍼/드라이버 주입 지점) 확보
결론
클래스 기반 싱글톤 ZebraLabelService는 하드웨어 통합의 현실적 제약(단일 연결, 비싼 초기화, 복잡한 상태/수명 주기)을 안전하게 캡슐화하고
React UI와는 느슨하게 결합해 유지보수성/재사용성/테스트 용이성을 모두 확보합니다.
서비스 레이어(비즈니스 로직) ↔ Hook 레이어(UI 상태) ↔ 컴포넌트 레이어(UI 렌더)의 관심사 분리가 명확해져 실무에서 바로 적용 가능한 구조를 제공합니다.
왜 클래스 기반 ZebraLabelService를 선택했는가
https://devch.co.kr/categories/웹앱/React/react-why_use_class-1-250827/