프로젝트로 돌아가기

Google Play 출시 · 다음 가설 논의 중

Umaso

일본에서 한식을 만드는 사람에게 팀이 관리하는 레시피를 제공하는 콘텐츠 중심 v1입니다.

처음에는 일본에서 한식을 만들 때 필요한 재료와 대체재까지 연결하려 했지만, 기능이 먼저 늘며 누구의 어떤 문제를 풀지 흐려졌습니다. 출시를 계속 미루는 대신 팀이 관리하는 레시피 콘텐츠 중심 v1으로 범위를 줄여 Google Play에 출시했습니다. 지금은 이 버전을 운영하며 다음 사용자와 가치 가설을 다시 정의하고 있습니다.

기간
2026.02 - 현재
담당 범위
Flutter 기능 · Go API · 관리자 웹 · 운영
디자이너 2·Flutter 4·서버 4로 시작
저장소
비공개 리포지토리

레시피 탐색에서 재료 구매까지 잇는 서비스 구상

일본에 사는 팀원의 경험에서 한식 레시피를 찾은 뒤에도 현지 재료와 대체재를 따로 검색한다는 가설로 시작했습니다. 하지만 개발 중 기능이 먼저 늘면서 타깃 사용자와 가장 먼저 바꿀 행동이 흐려졌습니다.

새 기능을 멈추고 팀 관리 레시피 콘텐츠 중심 v1으로 범위를 줄여 Google Play에 출시했습니다. 현재는 이 버전을 운영하며 처음의 재료·대체재 구상을 포함해 다음에 검증할 가치를 다시 정의하고 있습니다.

사용에서 운영까지

레시피를 보는 사람과 콘텐츠를 운영하는 팀을 잇는 흐름

  1. 01

    레시피 앱

    Flutter 탐색·검색

  2. 02

    앱 서버

    Go API 인증·규칙

  3. 03

    레시피 데이터

    PostgreSQL 번역·재료

  4. 04

    관리자 화면

    Next.js Admin + R2 이미지

기능 확장을 멈추고 콘텐츠 중심 v1을 출시하기까지

재료·대체재, 쇼핑, 커뮤니티, 추천 아이디어가 병렬로 늘면서 가장 먼저 바꿀 사용자 행동을 한 문장으로 설명하기 어려워졌습니다.

사이드 프로젝트는 참여 가능 시간이 달라 완전한 재설계를 기다리면 출시와 피드백이 더 늦어집니다. 반대로 사용 데이터가 없는 상태에서 처음 가설을 정답처럼 밀 근거도 없었습니다.

새 기능을 멈추고 팀이 관리하는 레시피 콘텐츠 중심 v1으로 줄여 Google Play에 먼저 출시했습니다. 처음의 재료·대체재 구상은 검증된 성과로 포장하지 않고 다음 가설로 되돌렸습니다.

  • Flutter·Go API·admin web에서 콘텐츠 등록부터 표시까지 하나의 흐름으로 완성했습니다.
  • app/server를 monorepo로 옮겨 기능 단위 변경을 추적했습니다.
  • 다음 단계는 feature owner가 하나의 행동을 끝까지 맡는 방식으로 정리했습니다.

콘텐츠 중심 v1을 Google Play에 출시했고 팀이 레시피·재료·번역을 운영할 관리자 흐름을 남겼습니다. 사용·잔존 지표는 아직 없습니다.

출시는 가설 검증의 끝이 아니라 다음에 무엇을 측정할지 정할 수 있게 하는 첫 체크포인트라는 기준이 생겼습니다.

번역을 조회 경로에서 콘텐츠 운영으로 옮겼습니다

일본어 레시피가 필요하지만, 사용자가 열 때마다 번역을 만들면 요청마다 지연 시간·비용·품질 편차를 함께 감당해야 했습니다.

Phase 1 콘텐츠는 팀이 관리자 웹에서 등록하고 검수할 수 있었지만, 사용자 생성 레시피나 댓글의 자동 번역 비용과 검수 정책은 정해지지 않았습니다.

사용 시점이 아니라 관리자 웹에서 한국어 원문을 등록할 때 번역 초안을 만들고 검수 후 저장하는 방식을 선택했습니다. 저장된 번역이 없을 때만 다른 언어로 fallback합니다.

  • 관리자 웹에서 원문 입력과 번역 초안 확인을 하나의 콘텐츠 운영 흐름으로 연결했습니다.
  • is_translated로 번역 상태를 표시해 운영자가 누락 콘텐츠를 확인했습니다.
  • 앱 API가 저장된 번역과 locale fallback을 사용하도록 구성했습니다.

관리자 웹에서 레시피·재료·번역을 함께 운영할 수 있고, 앱은 요청마다 번역을 생성하지 않고 저장된 콘텐츠를 읽습니다.

번역은 화면에 표시하는 기능이 아니라, 누가 언제 검수하고 어떤 상태를 공개할지까지 포함한 콘텐츠 lifecycle로 설계해야 한다는 점을 배웠습니다.

현재 공개 버전에서 요리 후보를 고르는 룰렛

재료 하나와 소스 조합은 같은 대체재가 아니었습니다

재료 A를 재료 B로 바꾸는 경우와 소스를 여러 재료의 조합으로 바꾸는 경우는 사용자가 필요한 정보가 달랐습니다.

단순한 1:1 관계로 만들면 여러 재료나 다른 소스를 포함한 대체안을 표현할 수 없었습니다. 반대로 유연한 다형성 관계를 쓰면 DB 외래키만으로 모든 참조 무결성을 보장하기 어려웠습니다.

재료 대체는 1:1 관계로, 소스 대체는 여러 구성 요소와 분량을 가진 구조로 나눴습니다. component_type과 component_id를 사용하는 대신, 참조의 정합성은 쿼리와 애플리케이션 규칙에서 확인하도록 했습니다.

  • 재료 대체와 소스 대체를 서로 다른 조회 구조로 분리했습니다.
  • component_type에 따라 ingredient/source를 조건부 JOIN하고 COALESCE로 실제 이름을 조회했습니다.
  • 소스 생성·구성요소·번역·audit log 기록을 하나의 트랜잭션으로 처리했습니다.

API와 관리자 화면에서 단일 재료 대체와 여러 구성 요소로 이루어진 소스 대체를 모두 표현할 수 있게 했습니다.

유연한 데이터 모델은 표현력을 얻는 대신 DB 제약으로 지키지 못하는 정합성을 애플리케이션 검증 책임으로 가져오는 설계라는 점을 이해했습니다.

식재료로 만들 수 있는 요리를 찾는 공개 버전 화면

복잡한 레시피 조회를 SQL에 보이는 형태로 남겼습니다

레시피 상세 응답은 재료·번역·대체재·소스 등 여러 관계를 하나의 화면 형태로 조합해야 했습니다.

locale별 번역 fallback이 필요하고 유스케이스마다 필요한 컬럼과 JOIN이 달라, 테이블 중심 추상화만으로는 실제 조회 의도를 검토하기 어려웠습니다.

ORM 대신 pgx와 sqlc를 사용해 유스케이스별 SQL을 명시하고, 생성된 타입으로 애플리케이션 코드의 타입 안전성을 유지했습니다. ORM이 항상 나쁘다는 뜻이 아니라, 이 서비스에서는 조회 형태를 SQL에서 직접 확인할 수 있다는 점을 중시했습니다.

  • 레시피 상세·번역·대체재 조회를 유스케이스별 SQL로 분리했습니다.
  • sqlc 생성 코드로 쿼리 결과 타입을 고정했습니다.
  • locale fallback과 조건부 관계 조회를 SQL에 명시했습니다.

Go API에서 레시피·재료·번역·대체재 조회를 구현했고 필요한 데이터 관계를 쿼리 단위로 확인할 수 있게 했습니다. ORM과의 속도 비교나 N+1 개선 수치는 측정하지 않았습니다.

기술 선택은 라이브러리 이름을 나열하는 것이 아니라, 데이터 형태를 어디서 검토하고 어떤 불변조건을 지킬지까지 설명해야 한다는 점을 배웠습니다.

보유 식재료를 관리하는 공개 버전 냉장고

반복 로그아웃은 로그인 함수의 문제였는가

Flutter 앱에서 사용자의 로그인이 반복적으로 풀려, 화면에 보이는 로그인 함수보다 인증 흐름 전체를 확인해야 했습니다.

사용자용과 관리자용 인증은 secret과 유효기간이 달랐지만, 의존성 주입 구성에서는 비슷한 설정이 같은 경계에 놓여 있었습니다. 함수를 단독으로 읽어서는 원인을 찾을 수 없었습니다.

로그인 함수에서 멈추지 않고 DI 컨테이너에서 어떤 사용자용 refresh secret과 expiry가 주입되는지 추적한 뒤, 사용자와 관리자 인증 경로에 각자의 설정을 다시 연결했습니다.

  • 사용자 refresh token middleware에 주입되는 secret·expiry 설정을 추적했습니다.
  • 사용자 경로에 연결된 관리자 JWT 설정을 사용자용 refresh 설정으로 교체했습니다.
  • 인증 로직·설정·만료·실패 화면을 하나의 흐름으로 확인했습니다.

사용자용 refresh 설정과 관리자용 설정을 분리하고 반복 로그아웃의 원인이 된 DI 연결을 수정했습니다. 공개 가능한 재발률이나 세션 유지율 지표는 없습니다.

인증은 함수 로직뿐 아니라 secret과 expiry가 middleware까지 전달되는 구성 경계도 테스트해야 한다는 점을 배웠습니다.

콘텐츠 중심 v1을 Google Play에 출시했습니다. Flutter 기능과 함께 레시피·재료·번역을 관리하는 Go API, 팀 운영용 관리자 화면과 내부 운영을 담당했습니다.

이 경험 이후에는 구현 전에 누가 무엇 때문에 불편한지, 각 데이터를 누가 관리하는지, 어떤 화면과 API까지 완성해야 출시할 수 있는지 확인합니다. 예를 들어 레시피 필드를 추가할 때는 Flutter 표시뿐 아니라 Go API, DB migration, 관리자 입력 화면까지 함께 점검합니다.

공개 가능한 사용·잔존 지표는 아직 없습니다. Google Play 출시는 배포 역량의 증거이지, 처음의 재료·대체재 가설이 검증됐다는 뜻은 아닙니다.

  • 출시한 v1의 레시피 탐색·재방문 행동 측정
  • 다음 타깃 사용자와 하나의 핵심 행동 결정
  • 대체재료 모델을 다음 단계에 둘지 측정 결과로 판단