
Strike Log는 단순히 볼링 점수를 기록하는 앱으로 시작했지만, 만들다 보니 생각보다 많은 기능이 필요했습니다. 점수를 입력하고 저장하는 기능만 있으면 끝날 줄 알았지만, 실제 사용 흐름을 생각하면 평균 점수, 최고 점수, 경기 기록, 클럽 운영, 실시간 경기방, 배지, 출석 streak, 푸시 알림까지 자연스럽게 이어졌습니다.
이번 글은 Strike Log 제작기 마지막 편입니다. Flutter와 NestJS로 앱을 만들며 구현한 내용을 돌아보고, 실제 앱에서 기능 구현만큼 중요했던 운영 설계를 정리해보겠습니다.
기능은 연결될 때 진짜 서비스가 된다
앱을 만들 때 각각의 기능은 따로 보면 단순해 보입니다. 점수 입력 화면은 볼링 규칙을 계산하면 되고, 로그인은 JWT로 처리하면 됩니다. 게임 기록은 API로 저장하면 되고, 알림은 FCM을 붙이면 됩니다.
하지만 실제 서비스에서는 기능 하나가 다른 기능과 계속 연결됩니다. 사용자가 게임을 저장하면 단순히 점수만 저장되는 것이 아닙니다. 평균 점수가 바뀌고, 최고 기록이 갱신될 수 있고, 배지가 새로 지급될 수 있습니다. 클럽에 속한 사용자라면 클럽 통계에도 영향을 줄 수 있습니다. 특정 조건을 만족하면 푸시 알림이나 인앱 알림도 발생합니다.
이 과정을 만들면서 느낀 점은, 앱 개발에서 중요한 것은 기능을 몇 개 만들었는가보다 기능들이 서로 자연스럽게 이어지는가였습니다.
Flutter 앱 구조는 기능 단위로 나눴다
Strike Log의 Flutter 앱은 lib/core와 lib/features 중심으로 구성했습니다. core에는 API 클라이언트, 공통 설정, 인증 처리처럼 여러 기능에서 함께 쓰는 코드를 두고, features에는 auth, game, home, group, badges, notifications 같은 기능별 코드를 나눴습니다.
이 구조의 장점은 기능을 추가하거나 수정할 때 영향 범위를 비교적 좁게 가져갈 수 있다는 점입니다. 예를 들어 배지 기능을 수정할 때 게임 입력 화면이나 알림 화면 전체를 뒤질 필요가 줄어듭니다.
처음부터 완벽한 구조를 잡기는 어렵습니다. 하지만 기능이 늘어날수록 어디에 어떤 코드가 있어야 하는지가 중요해졌고, 기능 단위 구조는 유지보수에 꽤 도움이 됐습니다.
NestJS 백엔드는 도메인 흐름을 중심으로 설계했다
백엔드는 단순 CRUD 서버가 아니라 앱의 규칙을 관리하는 역할을 맡았습니다. 게임 기록, 게임 시리즈, 실시간 경기방, 클럽, 배지, 알림은 각각 독립적인 기능처럼 보이지만 실제로는 서로 연결되어 있습니다.
그래서 백엔드에서는 games, game-series, game-rooms, groups, badges, notifications 같은 도메인을 나누고, 각 도메인에서 책임져야 할 일을 분리했습니다.

특히 전역 JWT 인증을 적용하고, 공개가 필요한 API만 @Public()으로 열어두는 방식은 운영 관점에서 안정감을 줬습니다. 작은 앱이라도 인증 정책이 흐트러지면 나중에 수정하기가 훨씬 어려워지기 때문입니다.
운영을 생각하면 예외 처리가 먼저 보인다
개발 초반에는 정상 흐름을 만드는 데 집중하게 됩니다. 사용자가 점수를 입력하고, 저장 버튼을 누르고, 서버에 저장되고, 화면에 결과가 보이는 흐름입니다.
하지만 실제 앱에서는 네트워크가 끊길 수 있고, 서버 응답이 늦을 수 있고, 인증이 만료될 수 있습니다. 사용자가 같은 버튼을 여러 번 누를 수도 있고, 푸시 토큰이 더 이상 유효하지 않을 수도 있습니다.
Strike Log에서는 게임 저장 요청에 재시도 로직을 넣고, 4xx 오류는 즉시 중단하도록 분리했습니다. 실시간 경기방은 연결이 끊겼다고 바로 방을 삭제하지 않고, 일정 시간 grace period를 둔 뒤 정리하도록 했습니다. 알림 토큰도 무효 토큰을 정리하는 흐름을 고려했습니다.
이런 예외 처리는 화면에서 잘 드러나지 않지만, 앱을 실제로 운영하려면 꼭 필요한 부분이었습니다.
아직 남은 개선점
Strike Log는 기본적인 앱 구조와 주요 기능을 갖췄지만, 아직 개선할 부분도 많습니다.
첫 번째는 사용자 경험입니다. 점수 입력 화면은 볼링 규칙을 정확히 반영해야 하지만, 동시에 처음 쓰는 사람도 헷갈리지 않아야 합니다. 10프레임 보너스 투구처럼 예외가 많은 부분은 더 직관적인 안내가 필요할 수 있습니다.
두 번째는 통계 고도화입니다. 현재는 평균, 최고점, 추이 차트 중심이지만, 사용자가 자신의 약점을 파악할 수 있는 분석 기능을 더 붙일 수 있습니다. 예를 들어 오픈 프레임 비율, 스트라이크 이후 득점 흐름, 월별 성장 추이 같은 데이터가 가능합니다.
세 번째는 클럽 운영 기능입니다. 가입 승인제와 1인 1클럽 정책은 기본 틀을 만들었지만, 실제 클럽 운영에서는 공지, 정기전, 멤버 권한 같은 요구가 더 생길 수 있습니다.
앱 개발은 출시 이후가 더 중요하다

이번 제작기를 쓰면서 가장 크게 느낀 점은, 앱은 만들어서 끝나는 것이 아니라 운영하면서 계속 다듬는 제품이라는 점입니다.
Flutter로 화면을 만들고, NestJS로 API를 만들고, Railway와 Firebase로 운영 환경을 구성하는 것까지는 시작에 가깝습니다. 진짜 중요한 일은 사용자가 어디에서 불편을 느끼는지 확인하고, 기록이 제대로 쌓이는지 점검하고, 장애가 났을 때 빠르게 원인을 찾을 수 있도록 준비하는 것입니다.
그래서 Sentry 같은 오류 추적 도구, 서버 로그, 알림 실패 처리, 데이터 마이그레이션 전략 같은 요소도 기능 개발만큼 중요합니다.
마무리
Strike Log 제작기는 볼링 점수 기록 앱을 만드는 과정이었지만, 결국은 실제 앱 서비스를 구성하는 여러 요소를 하나씩 붙여보는 과정이었습니다.
점수 계산 로직에서 시작해 게임 저장, 실시간 경기방, 클럽 운영, 배지와 출석 streak, FCM 알림, Railway와 Firebase 운영까지 이어졌습니다.
작은 앱이라도 제대로 만들려면 프론트엔드, 백엔드, 데이터베이스, 인증, 알림, 운영 환경이 함께 맞물려야 합니다. 이번 연재가 Flutter와 NestJS로 실제 앱을 만들고 싶은 분들에게 작은 참고가 되었으면 합니다.
'APP' 카테고리의 다른 글
| Railway와 Firebase로 Flutter 앱 백엔드 운영하기 (0) | 2026.07.08 |
|---|---|
| FCM 푸시와 인앱 알림을 함께 연결하기 (0) | 2026.07.07 |
| 배지와 출석 streak로 다시 켜고 싶은 앱 만들기 (0) | 2026.07.06 |
| 클럽 운영 기능 만들기: 가입 승인제와 1인 1클럽 정책 (0) | 2026.07.03 |
| Socket.IO로 클럽 실시간 볼링 경기방 만들기 (0) | 2026.07.01 |
