4월의 낙원호는 사용자 인증, 재화 시스템, 미니 게임(슬롯머신, 낚시, 선택지 게임), 출석 체크, 우편, 상점, 가방 기능을 제공하는 웹 게임입니다. 또한, 참가자 관리, 보상 지급, 공지 사항 작성, 상점을 관리할 수 있는 운영자 페이지를 제공합니다.
- EdgeDB 도입 배경
- EdgeDB는 유연함과 엄격한 스키마를 동시에 가지며, 복잡한 질의도 직관적으로 작성할 수 있습니다. 또한, 비즈니스 로직을 디비에 담아서 중복과 오류 가능성을 사전에 제거할 수 있을 것으로 기대했습니다.
- API 응답 속도 43배 단축하기
- 서비스 첫날 API 끝점 응답 속도가 평균 2,322ms, 최대 20초까지 느려지고 트랜잭션 실패가 발생했습니다. 클라이언트 반응성을 올리기 위해 클라이언트에서 처리가 완료된 것으로 표시하고, 디비에서 나중에 처리하는 방식을 사용했는데, 트랜잭션 실패로 사용자의 인식 불일치를 가져왔습니다.
- 우선 사용자 인식 불일치를 줄이는 방향으로 핫픽스 후 진짜 원인을 분석했습니다. 사용자 인증에 사용하는
ext::auth::ClientTokenIdentity
가 느린데, 이것을 여러번 질의하는 것을 확인했습니다. - API 끝점 당 한 번씩 질의하고 재사용하도록 변경했습니다.
- 평균 2,322 ms가 걸리던 응답 속도가 54 ms로 줄어들었습니다.
- 권한 및 테이블 상속
- 처음에는 관리자와 사용자가 User를 상속하게 설계했습니다. 하지만, 요구사항이 명확해지고 보니 관리자는 사용자의 슈퍼셋이었습니다.
- 관리자와 사용자를 하나의 테이블로 합치고 권한을
isAdmin
속성으로 옮겼습니다. - 언제든지 사용자와 관리자 전환 가능해지고, 사용자의 모든 기능을 관리자도 사용 가능해졌습니다.
- 동시 편집
- 운영자 페이지를 여러 명이 동시에 수정하면 상태가 꼬일 것을 우려했습니다. 처음에는 CRDT 라이브러리인 Y.js와 Liveblocks 서비스를 조사했습니다. 그 중 Liveblocks는 월 사용자 100명 미만이면 완전 무료여서 바로 도입할 수 있었습니다. 다만, 구현하고 보니 엑셀과 유사한 형태가 되어서, 구글 스프레드시트를 쓰기로 했습니다.
- 타입 세이프한 API 클라이언트
- 타입 세이프한 api 요청을 구현하고 싶었습니다. tRPC, ElysiaJS를 써봤지만 모두 SvelteKit과 매끄럽게 동작하지 않았습니다.
/routes/api/**/+server.ts
를 모두 읽고 클라이언트 코드를 생성하게 만들었습니다.- 예시:
api().mail.post({ id }, body)
->POST /api/mail/[id]
- 코드를 생성하여 생산성을 끌어올릴 수 있었습니다.
- 디자인 시스템 및 UI 컴포넌트
- 기본 요소를 스타일링하는 pico.css에 영향을 받았습니다. portal, modal 같은 간단한 컴포넌트는 직접 구현하고, 복잡한 상태를 가지는 컴포넌트는 Bits UI(Radix UI에 영향을 받은 Svelte 용 헤드리스 컴포넌트)를 사용했습니다.
- Stylebook
- 컴포넌트가 늘어나자, Storybook이 필요해졌으나, Svelte 5를 지원하지 않았습니다. 필요한 기능만 들어있는 유사 Storybook을 구현했습니다.
- 덕분에 다양한 상태를 가지는 컴포넌트를 직관적으로 만들 수 있었습니다.
- 미니 게임 애니메이션
- SVG를 Svelte 컴포넌트에서 다루도록 구현했습니다.
- 인앱 브라우저 대응
- 특정 환경에서 인앱 브라우저가 쿠키를 유지하지 못해서 매번 로그인해야 하는 문제가 발생했습니다. 카카오톡과 안드로이드의 경우 강제로 브라우저가 열리게 만들 수 있었지만, iOS는 불가능했습니다. 대신, 인앱 브라우저인지 감지하여 안내 문구를 띄우는 방식으로 해결을 할까 고민했지만, 링크로 로그인하는 기능을 추가하여 해결했습니다.
- 원인을 알 수 없는 버그
- 특정 주소에서 502 Bad Gateway가 뜨는 문제가 발생했습니다. 개발환경과 빌드 결과물에선 멀쩡한데, 서버에서만 문제를 일으켰습니다.
- 구체적인 원인은 찾지 못했지만, SSR 중에 발생하는 문제였기에 SSR을 포기하고 SPA로 전환했습니다.
- 성과 및 통계
- 사용자 수: 33명
- 제명된 사용자 수: 4명
- 보낸 우편 수: 280건
- 가장 많은 칩을 얻은 사용자의 칩 수: 6,560칩
- 가장 많은 토큰을 얻은 사용자의 토큰 수: 97토큰
- 출석 체크를 매일 하신 사용자: 5명
- 낚시 업적을 모두 달성한 사용자: 2명
- 가장 많이 낚시를 하신 사용자의 낚시 횟수: 290회
- 가장 많은 아이템을 보유하신 사용자의 아이템 수: 295개