Skip to content

Repository files navigation

OfflinePOS

네트워크가 끊겨도 판매 원장과 outbox를 로컬 SQLite에 함께 기록하고, 연결 복구 후 SQL Server로 중복 없이 동기화하는 Windows POS 포트폴리오입니다. CJ올리브영 Windows Application Engineer (POS 시스템) 공고에 맞춰 C#, .NET Framework 4.8 WPF, Windows Raw Input, REST, TCP 소켓, SQLite와 MSSQL 경계를 한 개의 실행 가능한 수직 슬라이스로 구성했습니다.

이 저장소는 개인 검증 프로젝트입니다. 실제 올리브영 POS, 카드사 승인망, 벤더 장비 SDK를 연동하거나 운영한 결과가 아닙니다.

지원서용 30초 요약

이 프로젝트는 네트워크가 끊겨도 판매를 멈추지 않는 Windows POS를 검증합니다.

질문 이 저장소의 답
무엇을 해결했나 중앙 서버가 끊겨도 Sales + SaleItems + Outbox를 SQLite 한 transaction으로 확정
어떻게 중복을 막나 at-least-once 전송과 MSSQL MessageId + payload hash 멱등 consumer 조합
무엇으로 증명했나 실제 SQLite/LocalDB 통합 테스트 76개, 응답 유실·동시 중복·payload 충돌 fixture
채용 담당자가 먼저 볼 것 Showcase 화면 · 2페이지 기술서 · 상세 Markdown 문서는 로컬 전용

핵심 구현보다 중요한 것은 무엇을 보장하고 무엇을 보장하지 않는지를 분리한 점입니다. 이 프로젝트가 보장하는 것은 중복 매출 등록 방지 설계이며, 실제 카드 승인 중복 방지나 운영 경험을 주장하지 않습니다.

현재 검증 상태

2026-07-17, Windows 11과 SQL Server LocalDB 환경에서 다음을 확인했습니다.

  • Release 전체 빌드: 경고 0개, 오류 0개
  • 자동 테스트: 76개 통과, 실패·건너뜀 0개
  • NuGet 알려진 취약 package: 0개
  • 실제 SQLite 통합 테스트: 판매·판매항목·outbox 원자 커밋과 rollback, lease 만료 복구
  • 실제 SQL Server LocalDB 통합 테스트: 순차/16개 동시 중복, payload 충돌, 정산 프로시저
  • SQLite → HTTP → LocalDB E2E 테스트: 오프라인 판매 5건 복구, 서버 커밋 후 응답 유실 복구
  • 부하 도구 smoke: LocalDB API 20건 모두 Applied; SQLite 100,000행/20,000회 warm-cache 조회 1회 성공
  • 정식 합성 sync run: 5,000건, 50개 batch, 동시성 4, 3,414 sales/s, batch p50 88.706ms / p95 310.092ms / p99 310.150ms, 모두 Applied
  • WPF 운영 흐름: 상품 검색·카테고리·키보드 단축키, 당일 매출 KPI, 거래 내역 조회·선택 영수증 재출력

검증한 범위와 아직 검증하지 않은 범위는 로컬 전용 docs/implementation-status.md에 분리해 두었습니다.

빠른 시작 — Docker 불필요

준비물

  • Windows 11(현재 검증 환경)
  • .NET SDK 10.0.301 또는 같은 10.0 feature band의 최신 patch
  • .NET Framework 4.8 targeting pack과 WPF 빌드 도구
  • SQL Server LocalDB의 (localdb)\MSSQLLocalDB 인스턴스
  • demo helper script를 사용할 경우 sqllocaldbsqlcmd

Docker는 사용하지 않습니다. 서버를 처음 실행하면 OfflinePOS LocalDB 데이터베이스와 테이블·인덱스·프로시저를 내장 SQL 스크립트로 생성합니다.

저장소 루트의 PowerShell에서 빌드와 테스트를 실행합니다.

sqllocaldb start MSSQLLocalDB
dotnet restore OfflinePOS.sln
dotnet build OfflinePOS.sln -c Release --no-restore
dotnet test OfflinePOS.sln -c Release --no-build

세 process를 helper로 시작할 수도 있습니다. WPF 창은 직접 닫고, 종료 script는 server와 terminal helper process를 정리합니다. 로컬 실행 정책이 script를 막으면 아래처럼 process 범위에서만 bypass합니다.

powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\run-demo.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\stop-demo.ps1

이후 PowerShell 창 세 개를 열어 순서대로 실행합니다.

# 터미널 1: REST 동기화 서버, http://localhost:5056
dotnet run --project src\OfflinePOS.Server\OfflinePOS.Server.csproj --launch-profile http
# 터미널 2: TCP 모의 카드단말, 127.0.0.1:5055
dotnet run --project src\OfflinePOS.CardTerminalEmulator\OfflinePOS.CardTerminalEmulator.csproj -- --mode approved --port 5055
# 터미널 3: .NET Framework 4.8 WPF 클라이언트
.\src\OfflinePOS.Client\bin\Release\net48\OfflinePOS.Client.exe

지원서용 화면을 빠르게 재현하려면 Showcase 모드를 사용할 수 있습니다. 상품 3개와 오프라인 상태를 미리 표시하며, 실제 판매를 자동 확정하지는 않습니다.

$env:OFFLINEPOS_SHOWCASE="1"
.\src\OfflinePOS.Client\bin\Release\net48\OfflinePOS.Client.exe

클라이언트는 %LOCALAPPDATA%\OfflinePOSDemo\offline-pos.db를 로컬 원장으로 사용하고, 영수증 텍스트 파일은 %LOCALAPPDATA%\OfflinePOSDemo\receipts에 기록합니다.

1분 동작 확인

  1. 올리브 모이스처 크림을 세 번 담아 2+1 할인(54,000원 → 36,000원)을 확인합니다.
  2. 상단의 연결 차단 전환을 눌러 POS 서버만 차단합니다.
  3. 현금 또는 모의 카드 판매를 확정합니다. 판매는 SQLite에 남고 outbox는 재시도 상태가 됩니다.
  4. 동기화 대시보드에서 상태와 시도 횟수를 확인합니다.
  5. 연결을 복구해 Completed 전환을 확인합니다.

완성된 녹화 흐름은 로컬 전용 docs/demo-script.md를 따릅니다.

아키텍처

flowchart LR
    subgraph Client["OfflinePOS.Client · WPF / .NET Framework 4.8"]
        UI["판매 · 동기화 · 장치 진단"]
        App["Application / Domain"]
        DB[("SQLite\nSales + SaleItems + Outbox")]
        Sync["lease 기반 SyncEngine"]
        UI --> App --> DB
        DB --> Sync
    end

    Sync -->|"REST · at-least-once"| API["ASP.NET Core .NET 10"]
    API -->|"Serializable transaction\n멱등 키 + payload hash"| MSSQL[("SQL Server LocalDB")]

    UI --> Raw["Win32 WM_INPUT"]
    UI --> Printer["영수증 파일 프린터"]
    UI -->|"4-byte length + JSON / TCP"| Terminal["모의 카드단말 .NET 10"]
Loading
  • 클라이언트는 레거시 Windows 실행 환경을 실제로 검증하기 위해 net48을 사용합니다.
  • 도메인·계약·애플리케이션과 장치/저장소 경계는 netstandard2.0으로 공유합니다.
  • 서버와 카드단말 에뮬레이터, 테스트 러너는 net10.0입니다.
  • 판매·판매항목·outbox는 한 SQLite 트랜잭션으로 저장합니다. 인쇄는 커밋 이후 실행하므로 실패해도 판매를 지우지 않습니다.
  • 전송은 at-least-once이며, 서버가 MessageId와 canonical SHA-256 payload hash로 중복 소비를 멱등 처리합니다.

상세 흐름과 데이터 소유권은 로컬 전용 docs/architecture.md, 결정의 배경은 docs/adr/README.md에 있습니다.

핵심 실패 시나리오

상황 기대 결과 자동 증거
outbox insert 실패 sale과 item도 rollback Checkout_WhenOutboxInsertFails_RollsBackSaleAndItems
앱/worker가 lease 중단 만료 후 다른 lease가 재처리 ExpiredLease_IsClaimedAgain_AndCanComplete
서버 커밋 후 응답 유실 재전송 후 중앙 sale은 1건 ServerCommitThenResponseLoss_RetryCompletesOneCentralSale
같은 키·같은 payload 동시 16회 1회 Applied, 15회 AlreadyApplied Concurrent_duplicates_create_one_sale_and_return_stable_results
같은 키·다른 payload HTTP 409, 기존 sale 불변 Same_message_id_with_different_payload_returns_conflict_without_mutating_sale
TCP frame 분할·병합·절단 정확히 재조립하거나 명시적 실패 LengthPrefixedJsonCodecTests
카드단말 결과 저장 후 연결 종료 같은 RequestId 재요청으로 기존 결과 회수 AuthorizeAsync_DropAfterCommitCanBeRecoveredWithSameRequestId

전체 표와 수동 검증 항목은 로컬 전용 docs/failure-matrix.md를 참고하세요.

구현 범위와 표현 경계

상태 범위
구현·자동 검증 N+M 프로모션, SQLite transactional outbox, lease/retry/dead-letter, REST 동기화, MSSQL 멱등 소비, 응답 유실 E2E, TCP framing/timeout/correlation, 프린터 에뮬레이터, 카드 승인 snapshot/payment attempt, poison outbox 격리, 서버 business invariant 검증
구현·로컬 1회 측정 100,000행 SQLite seed와 20,000회 indexed warm-cache 조회(231,514 lookups/s, p50 3.9μs, p95 4.7μs, p99 5.3μs); 5,000건 sync(3,414 sales/s, p50 88.706ms, p95 310.092ms, p99 310.150ms)
구현·수동 검증 대기 WPF 전체 사용자 흐름, 실제 Raw Input 스캐너 장치 구분, 앱 프로세스 재시작 데모, 프린터 실패 후 UI 재출력
미구현/미실행 실제 카드사/벤더 SDK와 COM 연동, 취소·반품 이벤트, 상품 master sync, CPU/memory/UI 측정, UI 자동화, 다중 단말 soak, lease heartbeat/circuit breaker, 현대 WPF 이전 spike
산출물 대기 3분 데모 영상·반복 측정한 공개 성능 보고서

따라서 이 프로젝트가 증명하는 것은 중복 매출 등록을 막는 동기화 설계입니다. 실제 카드 결제의 중복 승인 방지나 운영 트래픽 경험을 주장하지 않습니다.

테스트 구성

프로젝트 주요 범위
OfflinePOS.Domain.Tests 37 도메인 불변식, 1+1/2+1, checkout 계약, sync 상태 분류
OfflinePOS.Sqlite.IntegrationTests 7 실제 SQLite 원자성, rollback, lease, retry/dead-letter, payment attempt, poison payload
OfflinePOS.Devices.Tests 24 TCP 단말, framing, timeout/cancel/correlation, 프린터, 바코드 버퍼
OfflinePOS.Server.IntegrationTests 6 실제 LocalDB API, 순차·동시 멱등성, 409, validator, 정산 프로시저
OfflinePOS.EndToEndTests 2 오프라인 5건 복구, 응답 유실 후 중복 없는 재전송

명령과 결과의 해석은 로컬 전용 docs/verification.md에 정리했습니다.

저장소 구조

src/
  OfflinePOS.Client                 net48 WPF UI, Raw Input
  OfflinePOS.Domain                 판매·상품·프로모션 불변식
  OfflinePOS.Application            checkout와 sync orchestration
  OfflinePOS.Contracts              REST/TCP DTO
  OfflinePOS.Infrastructure.Sqlite  로컬 원장과 outbox
  OfflinePOS.Infrastructure.Http    REST transport와 network gate
  OfflinePOS.Devices                TCP/프린터/바코드 입력 경계
  OfflinePOS.Server                 Minimal API와 MSSQL 멱등 소비자
  OfflinePOS.CardTerminalEmulator   TCP 단말 장애 모드
tests/                               단위·통합·E2E 테스트
sql/mssql/                           중앙 스키마와 정산 프로시저
tools/OfflinePOS.LoadGenerator       합성 sync/SQLite benchmark 도구
scripts/                             검증·DB 초기화·demo·benchmark 실행
docs/                                포트폴리오 근거 문서

상세 포트폴리오 Markdown 원고는 개인정보·지원서 맞춤 내용 보호를 위해 공개 저장소에서 제외하고 로컬에서만 관리합니다. 공개 저장소에는 README, PDF, 이미지 증거만 남깁니다.

포트폴리오 문서

  • 구현 현황과 미구현 경계 · docs/implementation-status.md (로컬 전용)
  • CJ올리브영 공고 요구사항 추적표 · docs/requirements-traceability.md (로컬 전용)
  • 아키텍처 · docs/architecture.md (로컬 전용)
  • 장애·복구 매트릭스 · docs/failure-matrix.md (로컬 전용)
  • TCP 카드단말 프로토콜 · docs/card-terminal-protocol.md (로컬 전용)
  • ADR · docs/adr/README.mddocs/adr/*.md (로컬 전용)
  • 3분 데모 스크립트 · docs/demo-script.md (로컬 전용)
  • 1~2페이지 프로젝트 기술서 원고 · docs/project-sheet.md (로컬 전용)
  • 지원서 첨부용 프로젝트 기술서 PDF
  • WPF 실행 화면 캡처
  • WPF Showcase 화면 캡처
  • 포트폴리오 보기 순서 · docs/portfolio-index.md (로컬 전용)
  • 상용 POS 대비 품질 기준 · docs/competitive-bar.md (로컬 전용)
  • 로컬 검증 기록 · docs/verification.md (로컬 전용)
  • 합성 성능 측정 방법 · docs/performance.md (로컬 전용)
  • 원래 구현 계획 · PLAN.md (로컬 전용)

이력서 한 줄

.NET Framework 4.8 WPF POS 데모에서 판매 원장과 transactional outbox를 SQLite에 원자 저장하고, REST 기반 at-least-once 전송과 MSSQL 멱등 소비자를 결합해 네트워크 단절·응답 유실 상황에서도 중복 매출 등록 없이 복구하도록 구현·통합 테스트했습니다.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages