Nari

Mazzucchi 주문관리 시스템 · 블로그

이탈리아 정육점 주문관리 시스템 개발기

블로그 목록
개발 일지2026-03-03
FlutterKotlinKtor풀스택

이탈리아 정육점 주문관리 시스템 개발기

왜 만들었나

이탈리아의 작은 정육점 Mazzucchi는 전화와 수기 메모로 주문을 관리하고 있었습니다. 고객이 원하는 부위와 중량을 전화로 주문하면, 주인이 메모장에 적어두고 준비하는 방식이었습니다.

이 과정에서 반복적으로 발생하는 문제가 있었습니다:

  • 전화 주문 중 부위명이나 중량이 잘못 전달되는 경우
  • 피크 시간대에 전화를 받지 못해 놓치는 주문
  • 매출 현황을 파악하려면 영수증을 하나하나 넘겨봐야 하는 번거로움

이 문제를 해결하기 위해 고객용 모바일 웹관리자 전용 앱을 개발했습니다.

무엇을 만들었나

고객용 모바일 웹

고객은 스마트폰 브라우저에서 상품 목록을 확인하고, 원하는 부위와 중량을 선택한 뒤 픽업 날짜와 시간대를 예약합니다. 별도의 앱 설치 없이 URL 접속만으로 주문이 가능합니다.

관리자 앱 (Android / Web)

정육점 주인은 전용 관리자 앱에서 다음 기능을 사용합니다:

  • 주문 관리: 접수된 주문 확인, 상태 변경 (대기 → 준비 중 → 완료)
  • 중량 확정: 고객이 500g을 주문했지만 실제 계량 결과 520g인 경우, 실측값을 입력하여 최종 금액 확정
  • 상품/카테고리 관리: 부위별 상품 등록, 가격 수정, 소프트 삭제
  • 매출 분석: 일간/월간/연간 매출 추이, 카테고리별 매출 비중, 인기 상품 순위
  • FCM 푸시 알림: 주문 상태가 변경되면 고객에게 자동 알림 발송

기술 스택과 아키텍처

| 영역 | 기술 | |---|---| | Frontend | Flutter (Web + Android), BLoC 패턴 | | Backend | Kotlin, Ktor, Exposed ORM | | Database | PostgreSQL | | Infrastructure | Docker Compose, Nginx, Let's Encrypt | | Monitoring | Grafana + Prometheus | | Push Notification | Firebase Cloud Messaging | | Server | Hetzner CX22 (Nuremberg), Ubuntu 24.04 |

이탈리아어 단일 언어 앱이지만, 번역 테이블을 분리하여 향후 다국어 확장이 가능하도록 설계했습니다. 상품명과 카테고리명은 ProductTranslations, CategoryTranslations 테이블에서 관리됩니다.

기술적 챌린지

1. 소프트 삭제와 FK 무결성

상품을 삭제하더라도 과거 주문 내역에는 해당 상품 정보가 남아야 합니다. 하드 삭제 시 Foreign Key 제약 조건에 위배되므로, is_deleted 플래그를 활용한 소프트 삭제 패턴을 적용했습니다.

  • 삭제된 상품은 목록에서 숨겨지지만, 기존 주문에서는 "(deleted)" 접미사와 함께 표시
  • 삭제된 상품으로 신규 주문 생성 시 서버 단에서 차단
  • 카테고리도 동일한 패턴 적용 (상품이 카테고리를 FK로 참조)

2. 주문 중량 확정 플로우

정육점의 특수한 요구사항으로, 고객의 요청 중량과 실제 계량 중량이 다를 수 있습니다. 이를 위해 finalize API를 설계했습니다:

  • 고객 주문: "소고기 안심 500g, 2팩"
  • 관리자 확정: 실제 측정값 520g 입력 → 최종 금액 재계산
  • 상태가 COMPLETED로 전환되며 고객에게 FCM 알림 발송

3. 날짜 기반 주문 조회 최적화

주문은 예약 날짜(scheduledAt)와 완료 날짜(completedAt)가 다를 수 있습니다. 관리자가 특정 날짜의 주문을 조회할 때, 완료된 주문은 완료일 기준으로, 미완료 주문은 예약일 기준으로 필터링합니다.

4. 캐시 전략

상품과 카테고리 데이터는 주문 조회 시 매번 JOIN하면 비효율적이므로, 서버 시작 시 인메모리 캐시에 로드합니다. 상품 생성/수정/삭제 시 캐시를 갱신하여 일관성을 유지합니다.

배포와 운영

월 5유로의 Hetzner VPS에 Docker Compose로 전체 스택을 배포합니다. Nginx가 리버스 프록시와 SSL 종단을 담당하고, Let's Encrypt로 무료 인증서를 발급받습니다.

배포는 rsync 기반 스크립트로 처리합니다. Docker의 멀티스테이지 빌드에서 의존성 레이어를 분리하여, 소스 코드만 변경되었을 때 빌드 시간을 단축했습니다.

회고

실제 사용자의 요구사항을 기반으로 개발하면서, 처음에는 예상하지 못했던 도메인 특성을 많이 발견했습니다. 중량 확정 플로우, 소프트 삭제의 연쇄적 영향, 날짜 기반 조회의 복잡성 등이 대표적입니다.

"되는 것"을 만드는 것보다 "안 부서지는 것"을 만드는 것이 훨씬 어렵다는 것을 체감한 프로젝트였습니다.