본문으로 건너뛰기
DEVKBS
작업 원장
020Dpectrum2025.04 – 2025.10운영 중

GOLDIE 금 거래 앱 (사장님)

같은 데이터를 정반대 방향에서 보는 화면

기간
2025.04 – 2025.10
분류
플랫폼
역할
프론트엔드 전담
구성
프론트엔드 1(전담) · 백엔드 1 · Flutter 1
사이트 방문
  • 모바일 화면1080 × 2340
  • 모바일 화면1080 × 2340
  • 모바일 화면1080 × 2340

01개요

GOLDIE는 사용자용과 사업자용 두 개의 웹뷰 앱으로 이루어져 있습니다. 이 기록은 사업자(사장님) 쪽입니다. 소비자 앱이 '얼마에 팔 수 있나'를 다룬다면, 이쪽은 '얼마에 사갈 것인가'를 다룹니다.

같은 데이터를 반대 방향에서 보기 때문에 화면을 그대로 뒤집어 쓸 수 없었습니다. 소비자에게는 예상 수령액이 가장 중요한 숫자지만, 사업자에게는 현재 시세 대비 매입가의 비율과 남은 판단 시간이 더 중요합니다. 무엇을 크게 놓을지가 완전히 달라집니다.

국제 금 시세는 계속 움직입니다. 사업자는 그 흐름을 보면서 금액을 조정하는데, 화면이 잠깐이라도 낡은 숫자를 보여주면 그것은 곧 손해로 이어집니다. 시세·잔여 시간·내 제시 상태 세 가지를 항상 신뢰할 수 있는 상태로 유지하는 것이 이 앱의 핵심이었습니다.

사용자 앱과 코드베이스를 공유하면서도 화면 구성은 갈라야 했기 때문에, 어디까지 공통으로 두고 어디부터 나눌지 정하는 것 자체가 큰 판단이었습니다.

02기술 결정

문제가 무엇이었고, 무엇을 골랐고, 그래서 어떻게 됐는지.

  1. 결정 01

    마감 시각을 클라이언트 시계로 계산하지 않는다

    문제
    남은 시간을 브라우저 로컬 시각으로 계산하면 사용자 기기의 시계가 몇 분씩 틀어져 있을 때 '아직 30초 남았는데 마감됨' 같은 상황이 생깁니다. 돈이 걸린 화면에서는 곧바로 분쟁이 됩니다.
    선택
    서버 응답 시각과 최초 렌더 시점의 로컬 시각 차이(offset)를 한 번 구해두고, 이후 모든 카운트다운을 '로컬 시각 + offset' 기준으로 계산했습니다. 마감 직전 구간에서는 확인 주기를 좁힙니다.
    결과
    기기 시계가 틀어져 있어도 모든 참여자가 동일한 잔여 시간을 봅니다. 최종 판정은 서버에 두어 화면과 결과가 어긋나지 않게 했습니다.
  2. 결정 02

    실시간 시세는 전역 스토어 하나로만 흘린다

    문제
    시세 숫자가 헤더, 입력 폼, 목록 행, 요약 카드 등 여러 곳에 동시에 등장합니다. 각 컴포넌트가 알아서 시세를 가져오면 한 화면 안에서 서로 다른 숫자가 잠깐씩 보입니다.
    선택
    시세 구독은 최상위에서 한 번만 하고 Zustand 스토어에 넣었습니다. 소비하는 쪽은 selector로 필요한 필드만 구독해 관계없는 값이 바뀌어도 리렌더가 번지지 않게 했습니다.
    결과
    화면 전체가 언제나 같은 시세를 가리킵니다. 구독 연결이 하나뿐이라 탭 전환·재접속 시 정리해야 할 지점도 한 곳으로 모였습니다.
  3. 결정 03

    금액 규칙은 조건문이 아니라 스키마로 검증한다

    문제
    최소 단위, 시세 대비 상·하한, 이전 제시 금액과의 관계 같은 규칙이 서로 얽혀 있었습니다. 조건문으로 쌓으면 규칙이 바뀔 때마다 손댈 곳이 늘어납니다.
    선택
    Zod 스키마 하나에 규칙을 모으고 `superRefine`으로 교차 조건을 표현했습니다. 같은 스키마를 제출 전 검증과 입력 중 경고 표시에 함께 썼습니다.
    결과
    규칙이 바뀌면 스키마 한 곳만 고치면 됩니다. 어떤 조건 때문에 막혔는지 사용자에게 정확한 문장으로 알려줄 수 있게 된 것도 부수 효과였습니다.
  4. 결정 04

    두 앱의 공통 부분은 도메인 기준으로 나눈다

    문제
    사용자 앱과 사업자 앱은 같은 도메인을 다루지만 화면은 거의 다릅니다. 컴포넌트를 기준으로 공통화하면 양쪽 사정이 한 컴포넌트 안에 조건문으로 쌓입니다.
    선택
    화면과 컴포넌트는 각자 두고, 시세 계산·상태 정의·API 타입처럼 도메인에 속한 것만 공유했습니다.
    결과
    한쪽 화면을 고쳐도 다른 쪽이 깨지지 않았습니다. '공통화의 단위는 생김새가 아니라 의미'라는 기준을 이때 세웠습니다.

03주요 기능

  • 실시간 시세 기반 매입 화면

    시세 변동에 따라 제시 가능 범위가 함께 움직이고, 벗어나면 이유를 먼저 알려줍니다.

  • 잔여 시간과 상태 전이

    진행 중 → 마감 임박 → 마감 → 확정으로 이어지는 상태를 한 화면에서 끊김 없이 표현.

  • 거래 이력 조회

    과거 제시와 성사 여부를 기간·상태로 좁혀 보는 조회 화면.

  • 사용자와의 1:1 채팅

    Firebase Realtime Database 기반 실시간 상담.

  • 권한 분리

    운영 관리자와 입점 사업자가 서로 다른 메뉴와 데이터 범위를 갖도록 라우트 단위로 분리.

04회고

시간을 다룰 때는 '보여주는 시간'과 '판정하는 시간'을 갈라 놓아야 합니다. 화면은 사용자가 납득할 수준이면 충분하고, 최종 판정은 언제나 서버가 합니다. 이걸 처음에 정해 두니 마감 직전의 애매한 경우를 논의할 때 다툴 일이 없었습니다.

같은 데이터로 만든 두 개의 앱을 동시에 맡으면서, 재사용의 기준을 다시 생각하게 됐습니다. 겉모습이 비슷하다고 합치면 나중에 반드시 갈라야 하고, 의미가 같은 것은 지금 합쳐 두는 편이 낫습니다.

아쉬운 점은 실시간 데이터의 재연결 시나리오를 뒤늦게 다듬었다는 것입니다. 네트워크가 끊겼다 붙는 순간의 처리는 개발 환경에서 잘 드러나지 않아서, 실제 사용 환경에서 보고를 받고서야 손을 댔습니다. 다음부터는 끊김 상황을 개발 초기에 강제로 만들어 두고 시작하려고 합니다.