kotalk/docs/archive/planning/windows-desktop-client-architecture.md

489 lines
18 KiB
Markdown
Raw Normal View History

2026-04-16 09:24:26 +09:00
# Windows 데스크톱 클라이언트 기술/아키텍처 결정서
## 문서 목적
이 문서는 Windows PC 기준으로 먼저 개발하는 메신저 데스크톱 앱의 기술 스택과 애플리케이션 구조를 결정하기 위한 기준 문서다. 목표는 "카카오톡 PC에서 기대하는 속도, 상시 실행성, 익숙한 생산성"을 유지하면서도 더 세련된 UI와 안정적인 확장 구조를 확보하는 것이다.
범위는 데스크톱 클라이언트에 한정한다. 서버 프로토콜 상세, 운영 인프라, 모바일 클라이언트는 별도 문서에서 다룬다.
## 최종 권고안
Windows 1차 출시 기준 권고 조합은 아래와 같다.
- UI 프레임워크: `WinUI 3 + Windows App SDK`
- 언어/런타임: `C# + .NET 8`
- 아키텍처 패턴: `MVVM + 기능별 모듈 구조(feature-first)`
- 상태 관리: `CommunityToolkit.Mvvm` 기반의 `ViewModel + Domain Store + Repository` 구조
- 로컬 저장소: `SQLite` 중심의 오프라인 우선 캐시
- 실시간 통신 추상화: `WebSocket` 기반 이벤트 스트림 + `HTTPS` 기반 명령/업로드 API
- 패키징: `최종 배포는 MSIX 패키지드 앱`을 기본으로 하고, 내부 개발 루프에서는 필요 시 unpackaged 디버그 프로필 병행
- 알림: `Windows toast notification + tray resident mode`
- 자동 업데이트: `MSIX App Installer 업데이트 피드`
- 비밀정보 저장: `Windows DPAPI/PasswordVault 계열 저장소`
핵심 판단은 단순하다. 이 프로젝트는 처음부터 Windows에서 가장 자연스럽게 느껴져야 하고, 메신저 특유의 "항상 켜져 있는 앱" 경험이 중요하다. 그 기준에서는 Electron보다 WinUI 3가 유리하고, WPF보다 미래 지향적이며, MAUI/Avalonia보다 Windows 통합 품질이 높다.
## WinUI 3 vs 대안 비교
### 1. WinUI 3
가장 균형이 좋다.
- 장점
- Windows 11 감성에 가장 잘 맞는 기본 컨트롤, 타이포, 재질, 입력 체계를 바로 활용할 수 있다.
- 알림, 앱 아이덴티티, 윈도우 관리, 테마 대응 같은 Windows 통합이 자연스럽다.
- C#/.NET 생태계를 그대로 쓰면서 성능, 메모리, 유지보수 측면에서 Electron보다 유리하다.
- 향후 Windows 전용 고급 기능을 붙일 때 우회가 적다.
- 단점
- WPF보다 생태계와 레퍼런스가 덜 성숙했다.
- 일부 고급 데스크톱 패턴, 특히 tray, title bar 세부 제어, 복잡한 virtualization 튜닝은 Win32 interop 이해가 필요하다.
### 2. WPF
실용성은 높지만 최종 권고안은 아니다.
- 장점
- 데스크톱 안정성과 서드파티 생태계가 매우 강하다.
- tray, 윈도우 제어, 업데이트, 커스텀 chrome 같은 데스크톱 전통 과제는 가장 익숙하게 풀 수 있다.
- 단점
- 기본 인상이 오래되었고, "트렌디한 Windows 앱" 감성을 얻으려면 커스텀 비용이 커진다.
- 지금 새로 시작하는 Windows 전용 메신저라면 장기 방향성에서 WinUI 3보다 매력이 약하다.
### 3. Electron
초기 생산성은 좋지만 이 프로젝트의 최적해는 아니다.
- 장점
- 웹 인력 전환이 쉽고, 풍부한 UI 라이브러리를 바로 쓸 수 있다.
- 크로스플랫폼 확장성이 좋다.
- 단점
- 메신저처럼 항상 켜 두는 앱에서 메모리/배터리/네이티브 감성 측면 손해가 크다.
- Windows 통합이 "가능"한 수준이지 "자연스러운 기본값"은 아니다.
- 보안 경계 관리가 더 까다롭다.
### 4. Avalonia
기술적으로 괜찮지만 이번 목표와는 다르다.
- 장점
- 크로스플랫폼을 염두에 둔 C# 선택지 중 가장 실전적이다.
- 단점
- Windows 퍼스트 제품에서 필요한 OS 통합, 알림, 설치체험, 앱 정체성 측면은 WinUI 3보다 약하다.
### 5. .NET MAUI
권장하지 않는다.
- 장점
- 모바일과 공유할 수 있는 발판은 있다.
- 단점
- Windows 데스크톱 완성도가 핵심인 메신저에는 맞지 않는다.
- 데스크톱 UX, 창 관리, 리스트 성능, 윈도우 느낌 모두 전용 데스크톱 프레임워크보다 불리하다.
## 패키징 선택
최종 결론은 `MSIX 패키지드 앱`이다.
이유는 아래와 같다.
- Windows 알림과 앱 아이덴티티가 안정적이다.
- 설치/제거가 깔끔하고 잔여 파일 문제가 적다.
- 시작 메뉴, 프로토콜 활성화, 알림 활성화, 권한 모델을 제품답게 다루기 좋다.
- 장기적으로 베타/정식 배포 체계를 운영하기 쉽다.
다만 개발 단계에서는 두 가지 현실을 인정해야 한다.
- 내부 개발 속도만 보면 unpackaged 디버그가 더 편할 수 있다.
- 비공개 테스트 배포에서 인증서/설치 체인이 번거로울 수 있다.
실행 전략은 이렇게 잡는다.
- 개발 초기: 개발기 로컬에서는 빠른 디버그 루프를 우선한다.
- 비공개 알파 이후: 배포 검증은 반드시 MSIX 기준으로 한다.
- 외부 배포 시점: MSIX + App Installer를 공식 릴리스 경로로 고정한다.
별도 exe 인스톨러 + 자체 업데이터 조합은 지금 단계에서는 피한다. 메신저 제품은 설치 체인보다 상시 안정성과 Windows 통합이 더 중요하다.
## 앱 셸 아키텍처
메신저는 일반 CRUD 앱이 아니다. 창이 곧 제품이다. 셸 설계가 곧 사용자 경험의 절반이다.
권장 구조는 `단일 메인 셸 + 필요 시 보조 창` 구조다.
- 메인 창
- 좌측 고정 내비게이션
- 채팅 목록
- 선택된 대화 뷰
- 우측 상세 패널은 필요 시 확장
- 보조 창
- 이미지 뷰어
- 파일 전송 상세
- 설정
- 로그인/계정 전환
- 향후 통화 팝업
셸 원칙은 다음과 같다.
- 기본은 단일 창 경험으로 시작한다.
- 대화창을 무제한 분리하는 기능은 초기 버전에 넣지 않는다.
- 창을 여러 개 띄우는 순간 동기화, 포커스, 알림 중복, 메모리 문제가 같이 커진다.
- 대신 "이미지 뷰어", "환경설정", "별도 팝업 업무창" 정도만 보조 창으로 분리한다.
탐색 방식은 페이지 네비게이션보다 `상태 전환형 셸`이 더 적합하다.
- 채팅 목록과 대화 뷰는 페이지 이동보다 같은 셸 내에서 컨텍스트만 바뀌는 구조가 낫다.
- 좌측 메뉴는 `채팅`, `친구`, `오픈채널/서버형 공간(향후)`, `설정` 정도로 제한한다.
- 메신저에서 과도한 딥 네비게이션은 UX를 해친다.
## 상태 관리 전략
권장 모델은 `가벼운 MVVM + 도메인 스토어`다.
전역 단일 상태 저장소 하나로 모든 것을 넣는 구조는 피한다. 메신저는 실시간 이벤트, 임시 입력 상태, 오프라인 큐, 동기화 상태가 얽혀서 거대 스토어가 금방 망가진다.
추천 계층은 아래와 같다.
- View
- ViewModel
- Feature Store
- Repository
- Transport/API Client
- Local DB/Cache
각 계층의 책임은 다음과 같다.
- ViewModel
- 화면 단위 상태만 가진다.
- 선택된 채팅, 입력창 포커스, 필터 텍스트, 패널 열림 여부 같은 UI 상태를 담당한다.
- Feature Store
- 기능 단위의 세션 상태를 가진다.
- 예: `ChatListStore`, `ConversationStore`, `PresenceStore`, `NotificationStore`
- 서버 이벤트와 로컬 변경을 합쳐 화면에 안정적으로 제공한다.
- Repository
- 로컬 DB와 네트워크를 조합한다.
- 예: "메시지 전송", "대화방 페이지 로드", "읽음 상태 반영", "첨부 업로드"를 트랜잭션처럼 묶는다.
전역 공유가 필요한 상태는 아래 정도로 제한한다.
- 현재 로그인 세션
- 연결 상태
- unread 총합
- 활성 워크스페이스/계정
- 공통 설정
## 로컬 캐시와 오프라인 동기화
메신저는 네트워크가 흔들려도 "쓸 수 있어 보이는 느낌"이 중요하다. 그래서 `SQLite를 단순 캐시가 아니라 로컬 소스 오브 트루스에 가깝게` 써야 한다.
권장 원칙은 아래와 같다.
- 앱 진입 시 채팅 목록은 로컬 DB에서 먼저 그린다.
- 선택한 대화의 최근 메시지도 먼저 로컬에서 즉시 보여 준다.
- 서버 응답은 그 뒤에 덮어쓰거나 이어 붙인다.
- 전송 버튼을 누르면 서버 성공 전에도 로컬에 임시 메시지를 만들어 즉시 표시한다.
- 서버 ack를 받으면 임시 ID를 서버 ID로 치환하고 상태를 정정한다.
로컬 DB에 최소한 있어야 하는 도메인은 아래와 같다.
- 계정/세션 메타데이터
- 친구/프로필
- 채팅방 목록
- 채팅방 멤버십
- 메시지 본문
- 메시지 전송 상태
- 읽음 상태
- 첨부파일 메타데이터
- 업로드/다운로드 작업 큐
- 임시 저장 중인 draft
- 사용자 설정
동기화는 `cursor 기반 점진 동기화`를 전제로 설계한다.
- 채팅 목록용 cursor
- 대화방별 메시지 cursor
- 읽음/반응/멤버 변경용 증분 이벤트
오프라인 큐에 들어갈 항목은 제한한다.
- 텍스트 전송
- 읽음 상태 보고
- 반응 추가/취소
- 첨부 업로드 예약
계정 설정 변경이나 대규모 프로필 변경은 온라인 상태에서만 처리하는 편이 안전하다.
## 미디어 처리 전략
초기부터 "무거운 클라이언트"가 되지 않도록 범위를 통제해야 한다.
원칙은 서버가 할 수 있는 무거운 일은 서버에서 처리하고, 클라이언트는 표시와 경량 캐시에 집중하는 것이다.
권장 구성은 아래와 같다.
- 이미지
- 원본과 썸네일을 분리 관리한다.
- 리스트에서는 항상 썸네일/축소본만 사용한다.
- 전체 보기 시에만 고해상도 리소스를 읽는다.
- 동영상
- 채팅 리스트/대화창에는 포스터 프레임 중심으로 표시한다.
- 초기 버전에서는 자동 재생을 금지한다.
- 인라인 편집/트랜스코딩은 넣지 않는다.
- 파일
- 다운로드 전에는 메타데이터만 유지한다.
- 파일 열기는 OS 기본 연결 프로그램에 위임한다.
- 악성 확장자/이중 확장자 표시는 UI에서 명확히 한다.
- 음성/녹음
- 초기 MVP에는 제외하는 편이 낫다.
- 넣더라도 녹음, 재생, 업로드를 별도 기능군으로 격리한다.
미디어 캐시는 계층화한다.
- 메모리 캐시: 현재 화면에 보이는 이미지
- 디스크 캐시: 최근 본 이미지/썸네일
- 영구 보관: 사용자가 저장한 다운로드 파일만
클라이언트에서 FFmpeg 같은 대형 의존성을 너무 일찍 넣지 않는다. 초기에는 서버 썸네일 생성 + 클라이언트 표시만으로 충분하다.
## 알림 전략
권장 조합은 `toast notification + 앱 내부 배너 + tray unread 표시`다.
필수 시나리오는 아래와 같다.
- 앱이 최소화되어 있거나 뒤에 있을 때 새 메시지 toast
- 앱이 포그라운드일 때는 toast 대신 인앱 배너 또는 해당 대화 강조
- 알림 클릭 시 해당 대화로 정확히 이동
- 중복 toast 방지
- mute된 방, 현재 보고 있는 방, 조용한 시간대 예외 처리
메신저는 "창 닫기 = 종료"보다 "창 닫기 = tray 상주"가 더 자연스럽다.
그래서 초기 설계부터 아래를 반영한다.
- 우상단 X 클릭 시 기본 동작을 `tray 최소화`로 둘지 정책 결정
- 완전 종료는 tray 메뉴 또는 설정에서 명시적으로 실행
- 1회성 안내를 통해 사용자가 동작을 오해하지 않게 한다
tray 기능은 보조 요소가 아니라 핵심 상시성 UX다.
## 자동 업데이트
정식 경로는 `MSIX App Installer 업데이트 피드`가 가장 낫다.
추천 이유는 아래와 같다.
- Windows 친화적인 업데이트 흐름을 유지할 수 있다.
- 설치 파일과 업데이트 정책이 한 체계 안에 있다.
- 알파, 베타, 스테이블 채널을 분리하기 좋다.
운영 원칙은 아래처럼 잡는다.
- `dev`, `beta`, `stable` 세 채널 분리
- 앱 시작 시 무조건 업데이트하지 않고, 유휴 상태나 재시작 시점 반영
- 강제 업데이트는 인증/프로토콜 호환성 깨질 때만 사용
업데이터를 별도 커스텀 프로세스로 처음부터 만들지 않는다. 메신저 본체가 안정화되기 전까지는 관리 포인트만 늘어난다.
## 보안 경계
메신저 데스크톱 앱은 생각보다 공격면이 넓다. 첨부파일, 링크 프리뷰, 알림 활성화 인자, 로컬 캐시, 토큰 저장이 다 경계다.
반드시 지킬 기준은 아래와 같다.
- 인증 토큰은 평문 파일로 저장하지 않는다.
- refresh token이나 세션 비밀값은 OS 보호 저장소에 넣는다.
- 로컬 SQLite에는 필요한 최소 데이터만 저장한다.
- 서버에서 온 HTML/리치 콘텐츠를 앱 내부에서 임의 렌더링하지 않는다.
- 첨부파일은 "열기"와 "미리보기"의 경계를 분리한다.
- 로그에는 메시지 본문, 토큰, 첨부 URL 원문을 남기지 않는다.
- 크래시 리포트에도 PII 최소화 규칙을 둔다.
프로세스 경계는 초기에 단순하게 가져간다.
- 기본은 단일 앱 프로세스
- 정말 필요할 때만 보조 프로세스 분리
보조 프로세스를 고려할 만한 경우는 아래다.
- 대용량 미디어 전처리
- 별도 업데이트/복구 헬퍼
- 향후 화면 공유/통화 같은 고권한 기능
초기 MVP에서 브라우저 엔진 기반 렌더링, 스크립트 실행형 플러그인, 임의 확장 기능은 넣지 않는다.
## IPC와 백그라운드 작업
초기 구조에서 필요한 IPC는 제한적이다.
- 단일 인스턴스 유지
- 두 번째 실행 요청이 오면 기존 인스턴스로 포커스 이동
- 프로토콜 링크 또는 알림 활성화 인자를 기존 인스턴스로 전달
이 용도에는 `로컬 named pipe` 수준이면 충분하다.
백그라운드 작업은 "모바일 같은 진짜 background task"보다 `tray 상주 앱` 개념으로 접근하는 것이 맞다.
즉, 메신저 수신과 동기화는 아래를 기본 전제로 삼는다.
- 사용자가 로그아웃하지 않은 이상 앱은 백그라운드에서 살아 있다
- 창만 닫혀도 프로세스는 유지된다
- 연결이 끊기면 지수 백오프로 재연결한다
Windows의 백그라운드 태스크 모델을 과용하지 않는다. 데스크톱 메신저에서는 주 실행 프로세스 상주가 더 단순하고 신뢰성이 높다.
## 권장 폴더 구조
프로젝트는 `레이어만 예쁘게 나눈 구조`보다 `기능 단위로 이해되는 구조`가 유지보수에 낫다.
권장 예시는 아래와 같다.
- `src/App`
- 앱 진입점, 부트스트랩, 셸, 테마, 리소스, 공통 네비게이션
- `src/Features/Auth`
- 로그인, 세션 복구, 계정 전환
- `src/Features/ChatList`
- 채팅 목록, unread, 고정, 검색 진입
- `src/Features/Conversation`
- 메시지 목록, 입력창, 전송, 읽음 상태, 반응
- `src/Features/Contacts`
- 친구 목록, 프로필, 차단/숨김
- `src/Features/Notifications`
- toast, 배너, 알림 정책
- `src/Features/Settings`
- 일반, 알림, 파일, 계정, 실험 기능
- `src/Core`
- 공용 도메인 모델, 인터페이스, 결과 타입, 에러 규약
- `src/Infrastructure/Api`
- HTTP/WebSocket 클라이언트, DTO, 직렬화
- `src/Infrastructure/Persistence`
- SQLite, 마이그레이션, 리포지토리 구현
- `src/Infrastructure/Media`
- 썸네일, 다운로드, 임시 파일, 프리뷰
- `src/Infrastructure/Security`
- 토큰 저장, 암호화, 민감정보 정책
- `src/Infrastructure/Platform`
- tray, 프로토콜 활성화, 파일 연결, OS 통합
- `tests/Unit`
- ViewModel, Store, 도메인 규칙
- `tests/Integration`
- 로컬 DB, 동기화, 업로드/다운로드, reconnect 시나리오
핵심은 `Features``Infrastructure`를 명확히 분리하는 것이다. 채팅 기능 코드가 SQLite 세부 구현이나 WebSocket payload 형태를 직접 알게 만들면 나중에 반드시 꼬인다.
## 단계별 구현 전략
### Phase 0. 제품 골격 검증
목표는 "이 구조로 메신저 느낌이 나는가"만 확인하는 것이다.
- 로그인 화면
- 메인 셸
- 채팅 목록 mock
- 대화창 mock
- 다크/라이트 테마
- 창 최소화, 리사이즈, 기본 반응형
이 단계에서는 서버 연결보다 셸 품질을 먼저 본다.
### Phase 1. 핵심 메시징 MVP
실사용 가능한 최소 메신저를 만든다.
- 계정 로그인/세션 복구
- 채팅 목록 실데이터
- 1:1 채팅
- 텍스트 송수신
- 읽음 상태
- reconnect
- 로컬 캐시 부팅
여기서 가장 중요한 성공 조건은 "앱 재실행 후 바로 이전 대화가 보여야 한다"다.
### Phase 2. 데스크톱다운 완성도 확보
카카오톡 PC 대체감을 만드는 구간이다.
- tray 상주
- toast 알림
- 파일 첨부/다운로드
- 전송 실패 재시도
- draft 저장
- 검색 진입
- 채팅방 정렬/고정
이 단계에서 제품 체감 품질이 크게 올라간다.
### Phase 3. 성능과 운영성 강화
- 대화방 수천 개, 메시지 수만 건에서도 부드러운 스크롤
- 이미지 캐시 최적화
- 크래시 리포트
- 진단 로그
- 업데이트 채널 운영
- 설정 백업/복원 범위 정의
이 단계 없이 사용자 수를 늘리면 운영이 무너질 가능성이 높다.
### Phase 4. 고급 기능 확장
이 단계는 코어가 안정화된 뒤에만 들어간다.
- 멀티 계정
- 그룹 채팅 고급 기능
- 메시지 반응
- 파일 히스토리
- 통화/화면공유
- 관리자/조직 기능
초기부터 이 기능을 욕심내면 아키텍처는 커 보이는데 제품은 느리고 불안정해진다.
## 반드시 미루는 항목
초기 버전에서 제외하는 편이 좋은 항목들이다.
- 플러그인 시스템
- 내장 브라우저 기반 리치 콘텐츠 렌더링
- 과한 애니메이션
- 멀티 윈도우 대화창 자유 분리
- 클라이언트 측 대형 미디어 트랜스코딩
- 암호화 메신저 수준의 종단간암호화 복잡도
이런 요소는 제품 핵심이 아니라 복잡도 폭탄이 되기 쉽다.
## 최종 판단
이 프로젝트의 최적 출발점은 `WinUI 3 + .NET 8 + MVVM + SQLite 기반 오프라인 우선 구조 + MSIX 배포`다.
이 조합은 세련된 Windows UX, 메신저에 필요한 상시성, 실시간성, 설치 품질, 장기 유지보수의 균형이 가장 좋다.
정리하면 아래 네 가지를 흔들지 않는 것이 중요하다.
- Windows 전용 1차 제품답게 네이티브 경험을 우선한다.
- 로컬 캐시는 옵션이 아니라 제품 핵심으로 본다.
- tray, 알림, 재연결은 부가 기능이 아니라 메신저의 본체로 취급한다.
- 초기에 단일 창과 단순 프로세스 구조를 유지해 제품을 먼저 안정화한다.