๐ฆ ๋ชจ์ ๋ฒ๊ธ ํต์ฅ ์๋น์ค ๊ฐ์๋ฃจํ
๋ชฉ์ฐจ ๋ฐ๋ก๊ฐ๊ธฐ
1) ํ๋ก์ ํธ ๊ฐ์
- ๋ชฉํ: ๋ชจ์ ๊ตฌ์ฑ์์ ๋ฏธ์ ์ํ์ ๊ธฐ๋ฐ์ผ๋ก ๋ฒ๊ธยท์๊ธ์ด ์๋ ์ ์ฐ๋๋ ๊ธ์ตํ ํ์ ์๋น์ค ๋ฐฑ์๋ ๊ตฌ์ถ
- ๋ฐฐ๊ฒฝ: ํผ์ ์ต๊ด ํ์ฑ์ ๋์ ํ์ง๋ง ์คํจ ๊ฒฝํ์ด ๋ฐ๋ณต๋จ โ ๋ฒ๊ธยท๋ณด์์ ํ ๋จ์๋ก ๋ฌถ์ด ์ํธ ๊ฒฌ์ ๊ฐ ๋๋ฉด ํจ๊ณผ์ ์ด๋ผ๋ ๊ฒฝํ์์ ์ถ๋ฐํด ์๋น์คํ
- ํต์ฌ ๊ฐ์น: ์ ํํ ์ ์ฐ(ACID) ยท ์ค๋ณต ์๋ ์ด๋ฒคํธ ์ฒ๋ฆฌ(idempotency) ยท ์์ ํ ์ธ์ฆ/์ธ๊ฐ(HTTP-Only Cookie, JWT)
- ํ๋ก ํธ ์๊ตฌ์ฌํญ ์์ฝ(์ฐ๋):
- GPS/์ฌ์ง ๊ธฐ๋ฐ ๋ฏธ์ ํ๋ณ
- ์นด์นด์คํก ์ด๋(๋งํฌ) & ์นด์นด์ค๋งต ์์น ํ์
- ํ๋ณ ๋ฐฐํ โ ๊ฒฐ๊ณผ ๋ฐ์ โ ์๋ ์ ์ฐ/์บ๋ฆฐ๋/๊ฐค๋ฌ๋ฆฌ
2) ์ํคํ ์ฒ ๊ฐ์
-
๊ธฐ์ ์คํ:
Spring Boot ยท Java ยท MySQL ยท Redis(๋ถ์ฐ๋ฝ/์บ์) ยท Kafka(๋น๋๊ธฐ ์ด๋ฒคํธ) ยท Docker ยท Jenkins
(๋ฐฐํฌ: AWS EC2, Nginx ๋ฆฌ๋ฒ์ค ํ๋ก์)
[Client(Web/App)]
โ REST API
โผ
[Spring Boot Backend]
โโ Auth Service (JWT/์ฟ ํค)
โโ Mission Service (GPS/์ฌ์ง ๋ฉํ)
โโ Betting Service (๋ฐฐํ
/์ ์ฐ ๋๊ธฐ)
โโ Settlement Service (์ ์ฐ/์์ฅ)
โโ Notification Service (Kafka ์ด๋ฒคํธ)
โโ Media Service (S3 ์
๋ก๋/์ฒ๋ฆฌ)3) ํต์ฌ ์ํ์ค ์ค๊ณ
A. ์ด๋ ๋ฐ ๊ฐ์
- ๋ฐฑ์๋ โ ์ด๋ ํ ํฐ ์์ฑ
- ์นด์นด์คํก ๋งํฌ๋ก ์ ๋ฌ
- ์ ๊ท ์ฌ์ฉ์๊ฐ ๋งํฌ ํด๋ฆญ โ ํ์๊ฐ์ & ๊ทธ๋ฃน ๋ฑ๋ก
B. ๋ฏธ์ ํ๋ณ
- ๋ฏธ์
์์ฑ (
missions) - ์ฌ์ฉ์๊ฐ GPS/์ฌ์ง ์
๋ก๋ โ ์๋ฒ ์ ์ฅ โ
mission_proofs์์ฑ - ๊ด๋ฆฌ์ ๊ฒ์ฆ ๋๋ ์๋ ๊ฒ์ฆ โ
verified='Y'
C. ๋ฐฐํ
- ์ฌ์ฉ์๋ค์ด ํน์ ํ์์ ๋ฏธ์
์ฑ๊ณต/์คํจ์ ๋ฒ ํ
(
bets) - ๋ง๊ฐ ์์ ๊น์ง ์์ ๊ฐ๋ฅ
D. ์ ์ฐ
- ์ ์ฐ ํธ๋ฆฌ๊ฑฐ (์ค์ผ์ค๋ฌ/๊ด๋ฆฌ์ ์คํ)
- Redis ๋ถ์ฐ๋ฝ์ผ๋ก ์ค๋ณต ์คํ ๋ฐฉ์ง
4) ERD

5) ๋ฌธ์ ๋ฐ ํด๊ฒฐ ๋ฐฉ๋ฒ
๋ฌธ์ 1: ์ด์ฒด ์ ์ค๋ณต ์ฒ๋ฆฌ ๋ฐ ๋ฐ์ดํฐ ์ถฉ๋ ๋ฐ์
์ํฉ
์ด์ฒด์ ํ๋์ ์ฌ์ฉ์๊ฐ ๋น ๋ฅด๊ฒ ๋ฒํผ์ ์ฌ๋ฌ ๋ฒ ๋๋ฅผ ๋, ์ ์ฐํ๊ธฐ ์์ฒญ์ด ์ฌ๋ฌ ๋ฒ ๊ฐ๋ ๊ฒฝ์ฐ๊ฐ ์์ด์ ํ๋์ ์ด์ฒด ์์ฒญ์ ๋ํด ์ค๋ณต ํธ๋์ญ์ ์ด ์์ฑ๋๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. ์ด๋ก ์ธํด ์ฌ์ฉ์๊ฐ ์ํ์ง ์๋ ์ด์ฒด๊ฐ ๋ฐ์ํ๊ฑฐ๋, ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค.
์ ํ์ง
| ์ ํ์ง | ์ฅ์ | ๋จ์ |
|---|---|---|
| 1. ํ๋ก ํธ์๋์์ ์์ฒญ ์ ํ | ๋จ์ํ๊ณ , ์์ฒญ์ด ๋ ๋ฒ ๋ค์ด์ฌ ๊ฒฝ์ฐ๋ฅผ ์์ฒ์ฐจ๋จ | ํ ํด๋ผ์ด์ธํธ ํ์ฅ์ ๊ฐ์ ๋ฌธ์ ๋ฐ์ํ ์ ์์ |
| 2. ๊ฑฐ๋ ๊ณ ์ ID(ref_id) + Unique ์ ์ฝ์กฐ๊ฑด | ๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ | ์์ธ ์ฒ๋ฆฌ ํ์ |
| 3. ๋ ๋์ค ์บ์ ํค๋ก ์์ฒญ ์ถ์ | ๋น ๋ฆ | TTL ์ค์ ๊ด๋ฆฌ ํ์ |
ํ๋จ ๊ทผ๊ฑฐ
์ผ๋จ ์์ฒญ ๊ทธ ์์ฒด๊ฐ ์ฌ๋ฌ ๋ฒ ์ค๋ ๊ฒฝ์ฐ๋ฅผ ์ต์ํ ํ๊ธฐ ์ํด์ 1๋ฒ์ ์ฐ์ ์ ์ผ๋ก ์ ์ฉํ๊ณ ์ด์ฒด ํธ๋์ญ์ ์์ ์ค์ํ ๊ฒ์ ๋ฐ์ดํฐ์ ์ ๋ขฐ์ฑ์ด๋ผ๊ณ ์๊ฐํ์ฌ ๊ฐ์ฅ ์์ ์ ์ธ DB๋จ์์ ์ ์ฝ์ ๊ฑธ์ด์ ์ฒดํฌํ๋ ๋ฐฉ์์ผ๋ก ์ค๋ณต์ ๋ฐฉ์งํ์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
- ํด๋ผ์ด์ธํธ ์์ฒญ๋ง๋ค UUID ๊ธฐ๋ฐ ref_id ์์ฑ
- ๋์ผ ref_id ์ฌ์์ฒญ ์, โ์ด๋ฏธ ์ฒ๋ฆฌ๋ ์์ฒญ์ ๋๋ค.โ๋ฅผ ์ต์ข ์ ์ผ๋ก ๋ฐํ
๊ฒฐ๊ณผ
- ์ค๋ณต ์ด์ฒด 0๊ฑด
- ๋คํธ์ํฌ ์ฌ์ ์ก ์ํฉ์์๋ ์์ ์ ์ธ ์ฒ๋ฆฌ
๋ฌธ์ 2 : ์์ก ์ ๋ฐ์ดํธ ๋์์ฑ ์ถฉ๋
์ํฉ
๋ค๋ฅธ ์ฌ์ฉ์์ ์ด์ฒด์ ๋์์ ๊ฐ์ ๊ณ์ข์ ์ ๊ทผํ๋ฉด ์์ก ๊ณ์ฐ์ด ๊ผฌ์ด๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ์์ต๋๋ค.
| ์ ํ์ง | ์ฅ์ | ๋จ์ |
|---|---|---|
| 1. ๋๊ด์ ๋ฝ | ์ฑ๋ฅ ์ฐ์, ๋ฝ ๊ฒฝํฉ ์์ | ์ถฉ๋ ์ ์ฌ์๋ ํ์ |
| 2. ๋น๊ด์ ๋ฝ | ์๋ฒฝํ ์ผ๊ด์ฑ ๋ณด์ฅ | ๋ฝ ๋๊ธฐ์๊ฐ ์ฆ๊ฐ |
| 3. Redis ๋ถ์ฐ๋ฝ | ํ์ฅ์ฑ ์ข์ | ์ธํ๋ผ ๊ด๋ฆฌ ๋ถ๋ด |
ํ๋จ ๊ทผ๊ฑฐ
์๋น์ค ํธ๋ํฝ์ด ๋ฎ๊ณ ๋จ์ผ ์ธ์คํด์ค ํ๊ฒฝ์ด์๊ธฐ ๋๋ฌธ์
โก ๋น๊ด์ ๋ฝ ์ ์ ์ฉํ์ต๋๋ค.
๋์์ ํ๋์ ๊ณ์ข์ ์ ๊ทผํ๋ ์์ฒญ์ ๋๊ธฐ์์ผ ์ผ๊ด์ฑ ํ๋ณด.
ํด๊ฒฐ ๋ฐฉ๋ฒ
@Lock(LockModeType.PESSIMISTIC_WRITE)
Account findByIdForUpdate(Long id);๊ฒฐ๊ณผ
- ๋์ ์ด์ฒด ์์ฒญ 50๊ฑด ํ ์คํธ์์๋ ์ ํํ ์ต์ข ์์ก ์ ์ง
- ํ๊ท ์ด์ฒด ์ฒ๋ฆฌ์๊ฐ 1.0s โ 1.3s (์ผ๋ถ ์ฆ๊ฐํ์ผ๋ ์์ ์ฑ ํ๋ณด)
๋ฌธ์ 3: ์ด์ฒด ์ค ์ค๋ฅ ๋ฐ์ ์ ์์ก ๋ถ์ผ์น ๋ฌธ์
์ํฉ
๋ฒ๊ธ ๋ฉ๋ถ๋ ์๊ธ ๋ฐฐ๋ถ ๊ณผ์ ์์, ํ ์ฌ์ฉ์์ ์์ก ์ฐจ๊ฐ์ด ์ฑ๊ณตํ๊ณ ๋ค๋ฅธ ์ฌ์ฉ์์ ์ ๊ธ์ด ์คํจํ๋ฉด ์ด ๊ณ์ขํฉ ์ด ๋ง์ง ์๋ ๋ถ์ผ์น ๋ฌธ์ ๊ฐ ์๊ฒผ์ต๋๋ค.
์ ํ์ง
| ์ ํ์ง | ์ฅ์ | ๋จ์ |
|---|---|---|
| โ ๋จ์ ๋ ๋ฒ์ DB UPDATE | ๊ตฌํ ๊ฐ๋จ | ์์์ฑ ๋ณด์ฅ ๋ถ๊ฐ (ํ์ชฝ๋ง ์ฑ๊ณต ๊ฐ๋ฅ) |
| โก Spring @Transactional (๋จ์ผ ํธ๋์ญ์ ) | ์์์ฑ ๋ณด์ฅ | ์๋น์ค ๊ณ์ธต ๋ณต์ก๋ ์ฆ๊ฐ |
| โข SAGA ํจํด (๋ณด์ ํธ๋์ญ์ ) | ๋ถ์ฐ ํธ๋์ญ์ ๋์ฒด ๊ฐ๋ฅ | ๊ตฌํ ๋ณต์ก, ์๋น์ค ๊ท๋ชจ ๋๋น ๊ณผํจ |
ํ๋จ ๊ทผ๊ฑฐ
์๋น์ค๊ฐ ๋จ์ผ DB ๋ด์์ ๋์ํ๊ธฐ ๋๋ฌธ์ โก @Transactional ๊ธฐ๋ฐ์ ๋จ์ผ ํธ๋์ญ์ ์ด ๊ฐ์ฅ ์ ์ ํ์ต๋๋ค. ๋จ, ํธ๋์ญ์ ์์์ ์์ธ ๋ฐ์ ์ ๋ณด์ ๋ก์ง์ ๊ตฌํํ์ฌ, ๋ชจ๋ ์ด์ฒด๊ฐ ์คํจ ์ ์ด์ ์ํ๋ก ์์ ํ ๋ณต๊ตฌ๋๋๋ก ์ค๊ณํ์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId)
.orElseThrow(() -> new AccountNotFoundException());
Account to = accountRepository.findById(toId)
.orElseThrow(() -> new AccountNotFoundException());
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
from.withdraw(amount);
to.deposit(amount);
transactionRepository.save(new Transaction(from, to, amount, LocalDateTime.now()));
}
๊ฒฐ๊ณผ
- ์ด์ฒด ์ค ์์ธ ๋ฐ์ ์ ๋ชจ๋ ๋ณ๊ฒฝ ์๋ ๋กค๋ฐฑ
- ํ ์คํธ ์ผ์ด์ค 100ํ ์ค ๋ฐ์ดํฐ ๋ถ์ผ์น 0๊ฑด
- ์ ์ฒด ์ด์ฒด ํธ๋์ญ์ ํ๊ท ์ฒ๋ฆฌ์๊ฐ 0.9s
5) ์ธ์ฆ ๋ฐ ๋ณด์
- ์ธ์ฆ: JWT + HttpOnly Secure Cookie
- ์ธ๊ฐ: ๊ทธ๋ฃน/๋ฏธ์ ์ ๊ทผ ์ ๊ถํ(ROLE) ๊ธฐ๋ฐ ์ ๊ทผ
- ์ ๋ ฅ ๊ฒ์ฆ: @Value ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ DTO ๋จ์ ์ ๋ ฅ๊ฒ์ฆ
- ๋น๋ฐ๊ด๋ฆฌ: Jenkins Credential, .env ํ์ผ ๋ถ๋ฆฌ
6) ์ฑ๋ฅ ๋ฐ ๋ชจ๋ํฐ๋ง
- ์บ์ฑ: Redis๋ก ๊ทธ๋ฃน/๋ฏธ์ ์บ์ (TTL 5~15๋ถ)
- ๋น๋๊ธฐ ์ฒ๋ฆฌ: ์ด๋ฏธ์ง ๋ฆฌ์ฌ์ด์ง ๋น๋๊ธฐํ โ ์๋ต์๋ 2.0s โ 0.5s
- ํ ์คํธ: JUnit5 + Testcontainers(MySQL/Redis)
- ๋ก๊น : JSON ๊ตฌ์กฐํ ๋ก๊ทธ + Grafana/ํ๋ก๋ฉํ ์ฐ์ค๋์๋ณด๋
7) CI/CD ๋ฐ ๋ฐฐํฌ
- Jenkins ํ์ดํ๋ผ์ธ
- GitHub PR โ ๋น๋/ํ ์คํธ โ Docker ์ด๋ฏธ์ง โ ๋ฐฐํฌ ์๋ํ
- ์ด์ ํ๊ฒฝ
- AWS EC2 + Nginx (HTTPS)
- ๋กค๋ฐฑ
- Docker ์ด๋ฏธ์ง ํ๊ทธ ๋จ์ ์ฌ๋ฐฐํฌ ์คํฌ๋ฆฝํธ ์ค๋น
8) ๊ฒฐ๊ณผ ๋ฐ ์ฑ๊ณผ
- ์ ์ฐ ์ค๋ณต ์คํ 0๊ฑด (๋ฝ/๋ฉฑ๋ฑํค)
- ์๋ฆผ ์ด๋ฒคํธ ๋๋ฝ๋ฅ 90% ์ด์ ๊ฐ์ (Kafka)
- ๋ณด์ ๊ฐํ: JWT + HttpOnly ์ฟ ํค๋ก ํ ํฐ ํ์ทจ ๋ฐฉ์ง
- ์ฃผ์ ๊ธฐ๋ฅ(API/์ ์ฐ/๋ฐฐํ ) 100% ๊ตฌํ ์๋ฃ
9) ํ๋ก์ ํธ ์ด๋ฏธ์ง
-
๋ฏธ์ ์ํ - ์ด๋ํ๊ธฐ(์ฅ์๋ฏธ์ )

-
๋ฏธ์ ์ํ - ๋ฌผ๊ฑด์ดฌ์

- ๋ฒ ํ ํ๊ธฐ - ํ์์ ๋ฏธ์ ์ํ ์ฌ๋ถ๋ฅผ ๋๊ณ ๋ฐฐํ

- ์ ์ฐ ๋ฐ ์บ๋ฆฐ๋์ ๊ฐค๋ฌ๋ฆฌ - ๋ฏธ์ ๊ฒฐ๊ณผ๋ฅผ ์บ๋ฆฐ๋์ ๊ฐค๋ฌ๋ฆฌ์์ ํ์ธ ๋ฐ ์ ์ฐ์์ ๋ธ ๋ฒ๊ธ ํ์ธ๊ฐ๋ฅ
