개요
제품 개발자는 어떻게 일할까?
현업에서 2년 넘게 플랫폼 업무를 맡고 있다. 안정적인 서비스 런칭, 메트릭 기반의 운영 대응과 모니터링 고도화가 주된 일이다. 완성된 제품에서 추가 개발을 담당하다 보니, 이미 확정된 기획 위에서 서버를 개발하는 경우가 많았고, 제품 자체에 대한 의견을 내기가 쉽지 않았다.
직접 문제를 정의하고, 가설을 세우고, 성과를 만들어가는 과정을 경험해보고 싶었다. 마침 F-Lab에서 사이드 프로젝트 팀 모집을 도와줬고, 온라인 추첨 서비스를 만들게 되었다. 경험 있는 개발자들과 협업하면서 원했던 과정을 직접 경험할 수 있었고, 우수팀으로 선정되었다.
제품 흐름을 재정의하면서 개발 기간을 3주에서 1주로 줄였다. 최종 데모에서는 8명 중 5명이 추첨에 참여했지만, 모바일 Safari의 쿠키 차단으로 3명이 이탈했다. 제품 가설과 기술 선택이 사용자 경험으로 곧바로 이어진다는 점을 함께 배운 프로젝트였다.
| 수료증 | |
|---|---|
![]() | ![]() |
사이드 프로젝트에서 역할
내가 했던 역할 기준으로 했던 일들을 나열해보고 어떤 경험들이 있었는지 정리하고 회고해보겠다.
- 제품 디자인
- 문제 발굴
- 가설 정의
- 백엔드
- 인증 기능 구현
- 관리자 기능 구현
- 인프라 시스템 구축
제품 디자인하면서 경험했던 일들
제품 디자인은 세 단계를 거쳤다. 처음에는 검증하고 싶은 문제를 정의했고, 문제점을 발견한 뒤 재정의했고, 마지막으로 제품을 실현했다.
1차 : 검증하고 싶은 문제 정의
오프라인 뽑기 시장에서 느낀 문제점이 있었다. 개별 포장 비용이 많이 들고, 매장에 진열해야 팔리다 보니 현금 회전이 느렸다. 또한 랜덤 추첨이 정말 공평한지 의심스러웠다.
이 문제를 해결하기 위해 가설을 세웠다. 온라인에서 뽑기를 진행하면 포장비를 절약할 수 있고, 제품 진열 전에 바로 결제가 완료되면 현금 회전이 빨라진다. 기존 40일에서 7일로 단축할 수 있을 것으로 예상했다. 그리고 시스템으로 공정한 확률을 보장할 수 있다고 생각했다.
첫 번째 시안은 이랬다. 사용자는 티켓을 통해 추첨에 참여하고, 추첨을 통해 얻은 상품을 상품 카드로 발급받는다. 마음에 들지 않는 상품 카드는 티켓으로 환불받고, 원하는 상품 카드는 배송 신청해 실물을 받는 구조였다.
1차 가설 정의를 마치고 회고했을 때 두 가지 문제가 있었다.
- 제품 문제: 상품을 전달하는 과정에서 배송요금이 발생했다. 배송요금을 메꾸려면 추첨 수수료를 높여야 하고, 이는 사용자 부담으로 이어져 경험을 떨어뜨릴 수 있었다.
- 기획 문제: 속도가 나지 않았다. 러프한 기획안으로 작업하니 개발자끼리 정리해야 할 것이 너무 많았고, 구현할 내용도 과도하게 많았다.
2차 : 문제 재정의
1차에서 발견한 문제를 해결하기 위해 방향을 수정했다.
배송요금 문제는 기프티콘 같은 디지털 보상으로 우회했다. 배송 기능을 추가 구현하지 않아도 됐고, 배송 요금도 들지 않았다. 다만 기존 구현했던 플로우와 달라서 추가 개발이 필요했고, 개발 속도를 유지하기 위해 기프티콘 구매 및 등록은 수작업으로 진행해야 했다.

속도 문제는 요구사항을 세세하게 정리하는 것으로 해결했다. 입력값과 출력값을 기준으로 설명했고, 모호함을 줄이기 위해 항상 쓰던 단어만 사용했다. 추가로 기존 API를 활용할 수 있게 가이드라인을 제공했다.
| 요구사항 정리 | 기존 구현된 API 적극 활용 |
|---|---|
![]() | ![]() |
결과적으로 1차 개발 시 3주 소요되던 것을 2차 개발 시 1주로 단축할 수 있었다.
3차 : 제품 실현
구현된 전체 시나리오는 다음과 같다.

최종 데모데이에서는 운영 환경에서 직접 추첨을 진행했다. 오프라인 참여자 8명 중 5명이 참여했고, 1등 상품으로 스타벅스 기프티콘 1개, 2등 상품으로 츄파츕스 기프티콘 3개를 걸었다.
| 관리자 추첨 조회 화면 | 관리자 추첨 결과 조회 화면 |
|---|---|
![]() | ![]() |
참여자들과 함께 실시간으로 추첨 결과를 기다리면서 기대심리를 직접 경험할 수 있었다. 단순히 기능을 시연하는 것을 넘어, 서비스가 주려던 재미를 함께 느낄 수 있어서 뜻깊은 이벤트였다.
| 사용자 추첨 조회 화면 | 사용자 추첨 결과 확인 화면 |
|---|---|
![]() | ![]() |
좋은 평가 1 : 추첨 대기를 통한 기대심리 증폭
추첨까지의 기대심리를 증폭시키기 위해 추첨 대기 화면을 구성했다. 추첨 대기는 폴링 방식으로 구현했다.

- 좋았던 점: 실시간으로 추첨을 진행하면서 사용자들의 기대심리를 높일 수 있었다.
- 아쉬웠던 점: VFX가 더 뛰어났으면 기대심리를 더 증폭시킬 수 있었는데, 대기 화면만 나와서 아쉬웠다는 피드백을 받았다.
- 추가로 개발하면 좋은 점: VFX를 고도화할 때, 개발자 관점에서 어떤 VFX가 좋은지 판별할 수 있는 스코어링이 있으면 좋겠다고 생각했다.
좋은 평가 2 : 딥링크를 활용한 기프리콘 등록
카카오톡에서 제공하는 딥링크를 활용해 기프티콘을 곧바로 등록할 수 있도록 편의를 제공했다. 모바일에서 사용자가 접근할 수 있어야 했기 때문에, 시연할 때 QR 링크를 통해 모바일 접근을 유도했다.
![]()
- 좋았던 점: QR 링크를 활용해 모바일로 접근하고 딥링크로 기프티콘을 등록하는 흐름이 좋은 평가를 받았다. 직접 구현한 기능이 아님에도 서비스 퀄리티를 높일 수 있는 방법이 있음을 깨달았다.
백엔드 개발하면서 경험한 일들
백엔드에서는 인증 기능 구현, 관리자 기능 구현, 인프라 시스템 구축을 담당했다.
기능 구현 : 100% 바이브코딩
목표는 컨텍스트를 풍부하게 제공하되, 필요한 내용 위주로 구성하는 것이었다.
.
├── CLAUDE.md <- 전역 개발 가이드라인 (ex. 인증 방법, 코드 컨벤션)
└── src
└── main
├── generated
├── java
│ └── pockmoneythief
│ └── backend
│ ├── PockMoneyThiefApplication.java
│ ├── admin
│ │ └── ADMIN.md <- 관리자 기능 개발 가이드라인
│ ├── auth
│ │ └── AUTHENTICATION.md <- 인증 기능 개발 가이드라인
│ ├── raffle
│ │ └── RAFFLE.md <- 추첨 기능 개발 가이드라인
│ └── user
│ └── USER.md <- 회원 기능 개발 가이드라인
└── resources
- 좋았던 점: 100% 바이브코딩을 슬로건으로 내세우며 개발 난이도가 크게 완화됐다. 기술적인 문제는 AI가 해결하면서 우리는 제품에 집중할 수 있었다.
- 아쉬웠던 점: AI를 포함해 작업자가 네 명이다 보니, 생각보다 코드 관리가 안 되기 시작했다. 깨진 창문 이론처럼 작은 문제들이 쌓여갔다.
관리자 기능 직접 구현
프론트엔드에서 처리할 작업이 많아 병목 현상이 발생하고 있었다. 일정 안에 제품을 완성하려면 리소스 조정이 필요했다. 관리자 화면은 사용자에게 직접 노출되지 않으니 자유롭게 진행해도 된다고 판단했고, 화면과 기능을 직접 전부 구현했다. 프론트엔드의 부담을 줄이면서 제품 실현 가능성을 높일 수 있었다.

인프라 시스템 구축
시스템 구성
비용 문제를 완화하고 싶어서 프론트는 Vercel에 배포했고, 백엔드는 Self hosting했다. 홈서버에 구축하면서 초기 구축 비용만 부담했다.

- 프론트엔드:
Next.js,Vercel배포 - 백엔드:
Spring Boot, 홈서버 배포 (Docker,Kubernetes,Nginx Proxy Manager) - 파일 저장소:
MinIO
DNS 구성 도메인은 가비아에서 구매했고, 외부 DNS 관리자와 내부 DNS 관리자를 분리하는 형태로 구성했다.
- 가비아 (외부 DNS 관리자)
- 와일드카드 레코드(
*.raffleevent.shop)를 홈서버 IP에 연결 - SSL 인증 TXT 레코드 관리
- 와일드카드 레코드(
- Nginx Proxy Manager (내부 DNS 관리자)
- 서브도메인별 라우팅 처리 (
api,minio,nginx등) - SSL 인증서 관리
- 서브도메인별 라우팅 처리 (

- 좋았던 점: 역할이 명확히 분리되어 있어서 새로운 서비스를 추가할 때 외부 DNS를 건드릴 필요가 없었다.
Nginx Proxy Manager에서만 라우팅을 추가하면 돼서 확장이 간편했다.
이번에 내부 DNS를 설정하면서 현업에서 들었던 이야기가 떠올랐다. 여러 제품이 하나의 대표 도메인을 공유하고 환경별 DNS가 독립적으로 등록되다 보니 경로가 자주 꼬였다. 규모가 커질수록 명확한 네이밍 규칙과 문서화가 필요하다는 이야기였다.
사이드 프로젝트에서는 규모가 작아서 문제가 되지 않았지만, 서비스가 늘어나고 환경이 다양해지면 DNS 관리가 금방 복잡해질 수 있음을 깨달았다. 처음부터 명확한 네이밍 규칙과 문서화가 중요하다는 걸 체감했다.
presigned url 적용
이미지 업로드에는 presigned URL을 적용했다. 업로드 책임을 MinIO에게 할당해서, 백엔드 개발자가 이미지 업로드 구현 과정을 생략할 수 있도록 설계했다.
| 이미지 업로드 흐름 | 이미지 조회 흐름 |
|---|---|
![]() | ![]() |
인증 개발 : 크로스 도메인 쿠키 이슈
데모데이에서 QR 링크로 모바일 접근을 유도했기 때문에 크로스 도메인 쿠키가 차단됐다. 오프라인 참여자 8명 중 3명이 해당 이 이슈로 이탈했다.
프론트엔드는 pock-money-thief.vercel.app, 백엔드는 api.raffleevent.shop으로 도메인이 달랐다. 세션 기반 인증을 사용하고 있는데, 서로 다른 도메인 간에는 쿠키가 자동으로 공유되지 않아서 문제가 생겼다.

쿠키 저장 방식
서버가 Set-Cookie: JSessionId=A432D34... 헤더를 보내면, 브라우저는 쿠키를 발급한 도메인 정보와 함께 저장한다.
| 이름 | 값 | 도메인 | 경로 | 만료 | SameSite |
|---|---|---|---|---|---|
| JSessionId | A432D34… | api.raffleevent.shop | / | 세션 | None |
브라우저는 요청 대상과 쿠키의 도메인이 일치하는지 먼저 확인한다. 이 조건을 충족하더라도 현재 페이지와 API의 사이트가 다르면 서드파티 컨텍스트가 되며, 브라우저의 추적 방지 정책에 따라 쿠키 전송이 차단될 수 있다.
시도했던 해결 방법
쿠키의 SameSite=None 설정과 credentials: include 옵션을 적용했다. PC 브라우저에서는 정상 동작했지만, 모바일 Safari에서는 쿠키가 전달되지 않았다.
원인 분석: Safari ITP의 서드파티 쿠키 차단 Safari의 ITP(Intelligent Tracking Prevention)는 모든 서드파티 쿠키를 기본적으로 차단한다. 이 차단에는 예외가 없다.
ITP by default blocks all third-party cookies. There are no exceptions to this blocking. — WebKit Tracking Prevention
SameSite=None 설정은 브라우저에게 “크로스 사이트 요청에서 이 쿠키를 보내도 된다”고 알려주는 것이지만, Safari ITP는 이 설정과 무관하게 서드파티 쿠키 자체를 차단한다. 즉, SameSite=None은 쿠키를 “보낼 의향이 있다”는 선언일 뿐, Safari가 이를 허용한다는 의미가 아니다.
우리 서비스의 경우:
- 사용자가
pock-money-thief.vercel.app(퍼스트파티)에서 페이지를 보고 있음 - 이 페이지에서
api.raffleevent.shop(서드파티)으로 API 요청을 보냄 - Safari ITP가 서드파티인
api.raffleevent.shop의 쿠키를 차단
해결 방법
| 방법 | 설명 |
|---|---|
| 임시 해결 (데모용) | 브라우저 설정에서 “크로스 사이트 추적 방지” 옵션을 비활성화하도록 안내했다. 임시방편이었지만 남은 참여자들은 정상적으로 진행할 수 있었다. |
| 근본적 해결 | 프론트엔드와 백엔드가 동일한 도메인을 공유하는 방식으로 변경한다. 예를 들어 www.raffleevent.shop과 api.raffleevent.shop처럼 같은 등록 도메인(registrable domain)을 사용하면 퍼스트파티로 인식되어 쿠키가 정상 동작한다. |
| 대안: 토큰 기반 인증 | 쿠키 대신 Authorization 헤더에 JWT 토큰을 담아 전송하는 방식. 쿠키 정책의 영향을 받지 않는다. |
배운 점 기술적인 이슈 하나로 사용자 이탈이 발생한다는 것을 직접 체감했다. 개발 환경(PC Chrome)에서는 문제없이 동작하더라도, 실제 사용자 환경(모바일 Safari)에서는 예상치 못한 문제가 생길 수 있다는 경각심을 갖게 됐다.
ITP에 대해 더 조사하면서 알게 된 내용들:
- 7일 제한: 사용자 상호작용이 없는 웹사이트의 JavaScript로 생성된 쿠키와 LocalStorage 등 스크립트 작성 가능 저장소는 7일 후 삭제된다.
- 30일 데이터 삭제: 크로스사이트 추적 기능을 가진 것으로 분류된 도메인은 30일간 퍼스트파티로 방문하지 않으면 웹사이트 데이터가 삭제된다.
- 링크 꾸미기 감지: URL에 클릭 ID 같은 추적 파라미터가 포함된 경우, 랜딩 페이지에서 JavaScript로 생성한 쿠키의 만료가 24시간으로 제한된다.
현업에서 회원 서비스를 다른 계열사로 이관한 경험과도 연결됐다. 서로 다른 도메인이 인증 정보를 공유할 때 각 서비스가 쿠키를 직접 관리하고 토큰으로 연동하도록 설계되어 있었다. 크로스 도메인 쿠키에 의존했다면 브라우저 정책의 영향을 크게 받았을 것이다. 도메인이 다른 서비스 간 인증은 브라우저 정책까지 포함해 설계해야 한다는 점을 체감했다.
마지막으로
제품 개발자를 경험하면서
사이드프로젝트 발표회에서 시연할 때, 사용자들이 어떤 상품이 걸릴지 기대하는 모습을 보면서 개발하고 싶었던 원동력이 무엇인지 다시금 되돌아보게 됐다.
기존에 하던 일도 재밌었다. 서비스 상태를 분석하고 발생할 문제를 예상하며 해결하는 일. 내가 알던 지식으로 가설을 세우고 문제를 해결하면서 능력을 인정받는 일이었다. 사회 구성원에게 인정받는 것이 자부심으로 이어지면서 일의 원동력이 됐다.
이번 프로젝트는 달랐다. 제품을 제공하면서 새로운 가치를 전달하고, 내 생각이 옳았다는 것을 확인하는 경험이었다. 결국 나의 원동력은 내가 예상한 것이 맞아떨어질 때 재미를 느끼는 것 같다. 문제 해결이든 제품 개발이든, 가설을 세우고 검증하는 과정 자체가 즐거웠다.
프로젝트 리더로서
가장 어려웠던 건 적정선을 찾는 일이다. 새로운 시도를 하고 싶으면서도 현실적인 일정 안에 끝내야 했고, 개발자들과 기획 의도를 맞추면서 타협점을 찾아야 했다. 완벽한 시스템을 만들겠다는 욕심을 내려놓고, 지금 검증해야 할 가설이 무엇인가에 집중하는 게 중요했다.
개발자로서
도전적이었던 건 바이브코딩으로 생산성을 높이는 방법을 고민한 것이다. AI와 함께 작업하면서 맥락과 관계없을지도 모르는 코드 더미에서 필요한 코드를 찾고, 이해하지 않아도 되는 요구사항 속에서 필요한 요구사항만 골라내며 개발하는 방법을 고민했다. 학습할 영역은 최소화하되, 필요한 영역은 깊게 이해할 수 있도록 패키지별로 가이드라인을 작성했다. 이 방식이 효과적이라는 걸 휴리스틱하게 알게 된 경험이었다.
팀원에게 배운 것
프로젝트 회고를 하면서 팀원에게 BMAD 기반 AI 에이전트 개발과 3 FILE 시스템을 활용한 최적화 기법을 배웠다. 직접 부딪혀보기 전에 다른 개발자들이 어떻게 활용하고 있는지 먼저 알아봤다면, 더 높은 퀄리티에서 더 많은 고민을 할 수 있었을 텐데 아쉬움이 남는다. 다음 프로젝트에서는 시작 전에 관련 사례를 충분히 조사해볼 생각이다.









