Skip to content

Latest commit

ย 

History

History
360 lines (269 loc) ยท 23.8 KB

File metadata and controls

360 lines (269 loc) ยท 23.8 KB

FootballSquare Backend ํฌํŠธํด๋ฆฌ์˜ค ๋ฆฌ๋ทฐ

1. ์•„ํ‚คํ…์ฒ˜ ๋ฐ ์‹œ์Šคํ…œ ๋””์ž์ธ ์ ๊ฒ€

1-1. ์ „๋ฐ˜์  ๊ตฌ์กฐ ๋ฐ ํŒจํ„ด

  • ๊ตฌ์กฐ ์š”์•ฝ

    • HTTP API: express ๊ธฐ๋ฐ˜ ๋ชจ๋†€๋ฆฌ์‹ ์•ฑ (src/index.js:1).
      • ๋ผ์šฐํŒ…: ๋„๋ฉ”์ธ๋ณ„ ํด๋” (src/router/account, board, match, community, team, chat)๋กœ ๋ถ„๋ฆฌ.
      • ๊ฐ ๋„๋ฉ”์ธ: router.js (HTTP ๋ ˆ์ด์–ด) โ€“> service.js (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) โ€“> sql.js (์ฟผ๋ฆฌ ์ •์˜) 3๋‹จ ๋ถ„๋ฆฌ.
      • ๊ณตํ†ต ๋ ˆ์ด์–ด: middleware(๊ฒ€์ฆ/๊ถŒํ•œ/์กฐ๊ฑด), constant(Enum), database(Postgres, Redis, S3), util(์—๋Ÿฌ/try-catch).
    • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…: ๋ณ„๋„ ์„œ๋น„์Šค chat/ (Socket.IO + Redis Pub/Sub + Postgres), ๋…๋ฆฝ Docker ์ด๋ฏธ์ง€๋กœ ์šด์˜ (chat/Dockerfile.prod:1).
    • ์ธํ”„๋ผ: docker-compose.yml๋กœ db(Postgres), redis, app, chat-server 4๊ฐœ ์„œ๋น„์Šค ๊ตฌ์„ฑ.
  • ํŒจํ„ด ๊ด€์ 

    • ์ „ํ˜•์ ์ธ ๋ ˆ์ด์–ด๋“œ ์•„ํ‚คํ…์ฒ˜ + ๋„๋ฉ”์ธ ๋ชจ๋“ˆํ™”:
      • ํ”„๋ ˆ์  ํ…Œ์ด์…˜(๋ผ์šฐํ„ฐ) / ๋น„์ฆˆ๋‹ˆ์Šค(service) / ๋ฐ์ดํ„ฐ(sql)์˜ ๋ถ„๋ฆฌ๊ฐ€ ๋น„๊ต์  ์ž˜ ๋˜์–ด ์žˆ์Œ.
      • ๊ณตํ†ต ๊ด€์‹ฌ์‚ฌ(์ธ์ฆ, ๊ถŒํ•œ, ์ž…๋ ฅ ๊ฒ€์ฆ, ํŒŒ์ผ ์—…๋กœ๋“œ, ์กฐ๊ฑด ์ฒดํฌ)๋ฅผ ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ์œผ๋กœ ์บก์Аํ™”.
    • ์˜์กด์„ฑ ๋ฐฉํ–ฅ:
      • ์ƒ์œ„ ๋ ˆ์ด์–ด(๋ผ์šฐํ„ฐ) โ†’ ๋ฏธ๋“ค์›จ์–ด/์„œ๋น„์Šค โ†’ DB ํด๋ผ์ด์–ธํŠธ/SQL (ํ•˜์œ„ ๋ ˆ์ด์–ด)๋กœ ๋‹จ๋ฐฉํ–ฅ.
      • DB ํด๋ผ์ด์–ธํŠธ(src/database/postgreSQL.js:1)์™€ Redis, S3๋Š” ์ง์ ‘ requireํ•˜๋Š” ํ˜•ํƒœ๋กœ, ์ธํ„ฐํŽ˜์ด์Šค/ํฌํŠธ-์–ด๋Œ‘ํ„ฐ ๋ ˆ๋ฒจ์˜ ์ถ”์ƒํ™”(Dependency Inversion)๋Š” ๋ฏธ๊ตฌํ˜„.

๊ฐ•์ 

  • ๋„๋ฉ”์ธ ๋‹จ์œ„ ๋ชจ๋“ˆํ™”

    • match, community, account ๋“ฑ ๋„๋ฉ”์ธ๋ณ„๋กœ router/service/sql ๋ถ„๋ฆฌ โ†’ ๊ธฐ๋Šฅ ํƒ์ƒ‰๊ณผ ๋ณ€๊ฒฝ ๋ฒ”์œ„ ํŒŒ์•…์ด ์‰ฝ๋‹ค.
    • Chat์€ ๋ณ„๋„ ๋ ˆํฌ/์„œ๋น„์Šค(chat/index.js:1)๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด ์›น์†Œ์ผ“ ํŠธ๋ž˜ํ”ฝ๊ณผ HTTP ํŠธ๋ž˜ํ”ฝ์„ ๋ถ„๋ฆฌ ์Šค์ผ€์ผ๋ง ๊ฐ€๋Šฅ.
  • ๋ฏธ๋“ค์›จ์–ด ๊ธฐ๋ฐ˜์˜ ์ˆ˜ํ‰ ๋‹จ๋ฉด(ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ) ์„ค๊ณ„

    • ์ž…๋ ฅ ๊ฒ€์ฆ(checkInput, checkRegInputs, checkIdx ๋“ฑ, src/middleware/checkInput.js:1).
    • ์ธ์ฆ/์ธ๊ฐ€(checkLogin, optionalLogin, checkRole, checkCondition ๋“ฑ).
    • ๋ฐ์ดํ„ฐ ์กด์žฌ ์—ฌ๋ถ€ ์ฒดํฌ(checkData, src/middleware/checkData.js:1).
    • ํŒŒ์ผ ์—…๋กœ๋“œ & S3 ์—…๋กœ๋“œ(multerMiddleware, s3Uploader*, src/middleware/s3UpLoader.js:1).
    • ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ์ค‘๋ณต์„ ๋งŽ์ด ์ œ๊ฑฐํ•˜๊ณ , ๊ถŒํ•œ/์กฐ๊ฑด ๋กœ์ง์„ ๋ผ์šฐํŒ… ๋ ˆ๋ฒจ์—์„œ ์„ ์–ธ์ ์œผ๋กœ ์กฐํ•ฉํ•˜๋Š” ๊ตฌ์กฐ๊ฐ€ ์ž˜ ์žกํ˜€ ์žˆ๋‹ค.
  • ๋„๋ฉ”์ธ ๋ชจ๋ธ๋ง๊ณผ ์Šคํ‚ค๋งˆ ๊ตฌ์กฐ

    • ๋ฉ€ํ‹ฐ ์Šคํ‚ค๋งˆ ์‚ฌ์šฉ (player, team, match, community, championship, board, chat ๋“ฑ, DDL.sql:1)์œผ๋กœ ๋„๋ฉ”์ธ ๊ฒฝ๊ณ„๋ฅผ SQL ๋ ˆ๋ฒจ๊นŒ์ง€ ๋ถ„ํ• .
    • ์ƒํƒœ/์ฝ”๋“œ ๊ฐ’๋“ค์„ common.status, team.role, community.role, board.category, match.type, match.position ๋“ฑ์œผ๋กœ ์ •๊ทœํ™”ํ•˜๊ณ , ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ๋Š” constantIndex๋กœ enumํ™”ํ•ด์„œ ์‚ฌ์šฉ.
    • match.match, match.participant, match.team_stats, match.player_stats ๋“ฑ ์ถ•๊ตฌ ๋„๋ฉ”์ธ์— ํŠนํ™”๋œ ์—”ํ„ฐํ‹ฐ/๊ด€๊ณ„ ๋ชจ๋ธ๋ง์ด ํƒ„ํƒ„ํ•˜๋‹ค.
  • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์•„ํ‚คํ…์ฒ˜

    • Socket.IO + Redis Adapter (chat/index.js:6, chat/socket.js:1)๋กœ,
      • ์ฑ„ํŒ… ์„œ๋ฒ„๋ฅผ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋กœ ์Šค์ผ€์ผ์•„์›ƒ ํ•  ์ค€๋น„๊ฐ€ ๋˜์–ด ์žˆ๋‹ค.
    • ์—ฐ๊ฒฐ ์ธ์ฆ์„ WebSocket ํ•ธ๋“œ์…ฐ์ดํฌ ์‹œ JWT๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  (chat/index.js:23), ์†Œ์ผ“ ์ปจํ…์ŠคํŠธ์— ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด ๋„๋ฉ”์ธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์‚ฌ์šฉ โ†’ ๊ตฌ์กฐ์ ์œผ๋กœ ๊น”๋”ํ•˜๋‹ค.

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • ์˜์กด์„ฑ ์—ญ์ „ ๋ฏธํก

    • ์„œ๋น„์Šค ๋ ˆ์ด์–ด๊ฐ€ pg.Pool ์ธ์Šคํ„ด์Šค์™€ SQL ๋ฌธ์ž์—ด์— ์ง์ ‘ ์˜์กด (service.js ์ „๋ฐ˜).
    • ํ…Œ์ŠคํŠธ์™€ ํ–ฅํ›„ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ต์ฒด(์˜ˆ: CQRS, ์บ์‹œ ๋ ˆ์ด์–ด ์ถ”๊ฐ€)๊ฐ€ ์–ด๋ ค์šฐ๋ฉฐ, ํฌํŠธ/์–ด๋Œ‘ํ„ฐ ๋˜๋Š” Repository ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ถ€์žฌ.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • dbClient.query(sql, params) ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ถ”์ƒํ™”ํ•˜๋Š” adaptor๋ฅผ ๋‘๊ณ , ์„œ๋น„์Šค์—์„œ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์˜์กดํ•˜๋„๋ก ์„ค๊ณ„.
      • Auth, Match, Board ๋“ฑ ํ•ต์‹ฌ ๋„๋ฉ”์ธ์—๋Š” UseCase/Service ํด๋ž˜์Šค ๋˜๋Š” ํ•จ์ˆ˜ ๋ชจ๋“ˆ์„ ๋„์ž…ํ•ด HTTP/DB๋ฅผ ๋ถ„๋ฆฌ.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘/์šด์˜ ํ”Œ๋กœ์šฐ

    • backend Dockerfile์˜ CMD๊ฐ€ tail -f /dev/null (Dockerfile:16)๋กœ ๋˜์–ด ์žˆ์–ด, docker-compose๋งŒ์œผ๋กœ๋Š” ์„œ๋ฒ„๊ฐ€ ์ž๋™ ๊ธฐ๋™๋˜์ง€ ์•Š๋Š”๋‹ค(๊ฐ€์ด๋“œ์— docker exec ... node /app/src/index.js).
    • ํฌํŠธํด๋ฆฌ์˜ค ๊ด€์ ์—์„œ๋Š” ๊ฐœ๋ฐœ ํŽธ์˜์šฉ ์„ค์ •์ด๋ผ ํ•ด๋„, ๋ฐฐํฌ์šฉ Dockerfile.prod ๋˜๋Š” CMD override๊ฐ€ ๋”ฐ๋กœ ์žˆ์—ˆ๋‹ค๋ฉด ๋” ์ข‹์•˜์„ ๊ฒƒ.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • app ์„œ๋น„์Šค์šฉ Dockerfile์„ ์šด์˜/๊ฐœ๋ฐœ๋กœ ๋ถ„๋ฆฌํ•˜๊ฑฐ๋‚˜, CMD ["node", "src/index.js"] + docker-compose.override.yml ๋กœ dev ํ™˜๊ฒฝ ์กฐ์ •.
  • ๋„๋ฉ”์ธ ๊ฐ„ ๊ฒฐํ•ฉ

    • ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ DB ์Šคํ‚ค๋งˆ/์ฟผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ง์ ‘ ์•ˆ๋‹ค(checkCondition, checkData ๋“ฑ์—์„œ raw SQL).
    • ๊ทœ๋ชจ๊ฐ€ ๋” ์ปค์ง€๋ฉด ๋„๋ฉ”์ธ ์„œ๋น„์Šค/๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ ˆ์ด์–ด๊ฐ€ ๋ฏธ๋“ค์›จ์–ด/๋ผ์šฐํ„ฐ ์–‘์ชฝ์— ๊ณต์œ ๋˜๋„๋ก ๋ฆฌํŒฉํ„ฐ๋ง ํ•„์š”.

2. ์ฝ”๋“œ ํ’ˆ์งˆ ๋ฐ ๊ฐœ๋ฐœ ๊ด€ํ–‰ ์ ๊ฒ€

2-1. DRY/SOLID ๊ด€์ 

๊ฐ•์ 

  • DRY ์ธก๋ฉด โ€“ ๊ณตํ†ต ๋กœ์ง ์ถ”์ถœ์ด ์ž˜ ๋˜์–ด ์žˆ์Œ

    • ์ž…๋ ฅ ๊ฒ€์ฆ: checkRegInput, checkRegInputs, checkIdx, checkPage ๋“ฑ (src/middleware/checkInput.js).
    • ์ธ์ฆ: checkLogin, optionalLogin (src/middleware/checkLogin.js:1).
    • ๊ถŒํ•œ: checkRole ๊ณ„์—ด(checkIsTeamLeader, checkIsCommunityAdminRole, checkHasTeamOrCommunity ๋“ฑ, src/middleware/checkRole.js:1).
    • ์กฐ๊ฑด/์ƒํƒœ ์ฒดํฌ: checkMatchEnded, checkMatchNotEnded, checkMatchOverlap, checkIsTherePositionParticipant ๋“ฑ (src/middleware/checkCondition.js).
    • ์—๋Ÿฌ ์ฒ˜๋ฆฌ: customError, trycatchWrapper (src/util/customError.js, src/util/trycatchWrapper.js).
    • ์ด๋กœ ์ธํ•ด ๋ผ์šฐํ„ฐ๋Š” **โ€œ์š”๊ตฌ ์กฐ๊ฑด์„ ๋‚˜์—ดํ•˜๋Š” ์„ ์–ธ์  ์ฝ”๋“œโ€**๊ฐ€ ๋˜์–ด, ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ค‘๋ณต์ด ์ƒ๋‹น ๋ถ€๋ถ„ ์ œ๊ฑฐ๋จ.
  • SRP(๋‹จ์ผ ์ฑ…์ž„ ์›์น™) ์ค€์ˆ˜ ์˜ˆ์‹œ

    • checkLogin:
      • ์—ญํ• : ํ† ํฐ ํŒŒ์‹ฑ โ†’ DB์—์„œ ํ˜„์žฌ ํŒ€/์ปค๋ฎค๋‹ˆํ‹ฐ ์—ญํ•  ์กฐํšŒ โ†’ req.decoded์— ๊ตฌ์กฐํ™”๋œ ๊ถŒํ•œ ์ •๋ณด ์ €์žฅ (src/middleware/checkLogin.js:1).
      • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(์˜ˆ: ๋งค์น˜ ์ƒ์„ฑ, ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ)์€ ์ด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ „์ œ๋กœ ํ•˜๋ฏ€๋กœ, ๊ฐ ๋„๋ฉ”์ธ ์ฝ”๋“œ์—์„œ ๋กœ๊ทธ์ธ/์—ญํ•  ๋กœ์ง์„ ์ค‘๋ณต ๊ตฌํ˜„ํ•˜์ง€ ์•Š์Œ.
    • s3Uploader ๊ณ„์—ด:
      • ์—…๋กœ๋“œํ•  ํŒŒ์ผ์˜ ํ˜•์‹/ํฌ๊ธฐ ๊ฒ€์ฆ, S3 ์—…๋กœ๋“œ, URL ์ƒ์„ฑ๊นŒ์ง€ ํ•œ ์ฑ…์ž„์— ์ง‘์ค‘ (src/middleware/s3UpLoader.js:1).
      • ์„œ๋น„์Šค ํ•จ์ˆ˜๋Š” ์—…๋กœ๋“œ ๊ฒฐ๊ณผ URL๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๋ฏ€๋กœ, ์ €์žฅ์†Œ(S3) ๊ตฌํ˜„ ๋ณ€๊ฒฝ์— ๋œ ์˜ํ–ฅ์„ ๋ฐ›๋Š”๋‹ค.
    • checkData.checkExistsInDB:
      • ๋‹จ์ˆœ ์กด์žฌ ์—ฌ๋ถ€ ๊ฒ€์ฆ์„ ๊ณตํ†ต ํ™”, ๋‹ค์–‘ํ•œ ์—”ํ‹ฐํ‹ฐ์— ์žฌ์‚ฌ์šฉ.
  • LSP(๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™)

    • ํด๋ž˜์Šค ์ƒ์† ๊ตฌ์กฐ๊ฐ€ ์•„๋‹Œ ํ•จ์ˆ˜/๋ชจ๋“ˆ ์กฐํ•ฉ ์Šคํƒ€์ผ์ด๋ผ LSP ์ด์Šˆ๋Š” ๊ฑฐ์˜ ์—†์œผ๋ฉฐ, ์ปดํฌ์ง€์…˜ ์ค‘์‹ฌ ๊ตฌ์กฐ๋ฅผ ์ทจํ•˜๊ณ  ์žˆ์–ด ํ•ด๋‹น ์›์น™์˜ ์œ„๋ฐฐ๋Š” ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค.

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • ์„œ๋น„์Šค ํ•จ์ˆ˜์˜ ์ฑ…์ž„ ๋ฒ”์œ„๊ฐ€ ๋‹ค์†Œ ๋„“์Œ

    • ์˜ˆ: src/router/match/service.js์˜ ์—ฌ๋Ÿฌ ํ•จ์ˆ˜๋Š”
      • (1) ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ํŒŒ์‹ฑ
      • (2) ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ์ ์šฉ
      • (3) ์—ฌ๋Ÿฌ SQL ์ฟผ๋ฆฌ ํ˜ธ์ถœ ๋ฐ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
      • (4) HTTP ์‘๋‹ต ์ƒ์„ฑ
      • ์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌ โ†’ SRP ๊ด€์ ์—์„œ โ€œHTTP ๋ ˆ์ด์–ดโ€์™€ โ€œ๋„๋ฉ”์ธ ์„œ๋น„์Šคโ€๊ฐ€ ์„ž์—ฌ ์žˆ๋‹ค.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • ๋„๋ฉ”์ธ ๋กœ์ง์„ domain/matchService.js ๊ฐ™์€ ๋ชจ๋“ˆ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , router/service๋Š” ์ด๋ฅผ ๋ž˜ํ•‘ํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง.
  • ์ค‘๋ณต / ์‚ฐ์žฌ๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™

    • ๋งค์น˜/์ปค๋ฎค๋‹ˆํ‹ฐ/ํŒ€ ๊ด€๋ จ ๊ถŒํ•œ/์ƒํƒœ ๋กœ์ง์ด ์—ฌ๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด ๋ฐ ์„œ๋น„์Šค์— ๋‚˜๋‰˜์–ด ์žˆ๋‹ค.
    • ์˜ˆ: ํŒ€/์ปค๋ฎค๋‹ˆํ‹ฐ ์†Œ์† ์—ฌ๋ถ€ ์ฒดํฌ๊ฐ€ checkRole, checkCondition, ๊ฐœ๋ณ„ service ํ•จ์ˆ˜์—์„œ ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋˜๋Š” ๋ถ€๋ถ„์ด ๋ณด์ž„.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • โ€œํŒ€ ๋„๋ฉ”์ธ ์„œ๋น„์Šคโ€, โ€œ๋งค์น˜ ๋„๋ฉ”์ธ ์„œ๋น„์Šคโ€ ๋“ฑ์œผ๋กœ ํ•ต์‹ฌ ๊ทœ์น™์„ ์ง‘์ค‘์‹œํ‚ค๊ณ , ๋ฏธ๋“ค์›จ์–ด๋Š” ํ•ด๋‹น ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” thin layer๋กœ ๋‹จ์ˆœํ™”.
  • ์˜คํƒ€ ๋ฐ ์ผ๋ถ€ ๋ฒ„๊ทธ ๊ฐ€๋Šฅ์„ฑ

    • checkIsFormation์ด formation.list, formation_idx๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋Š”๋ฐ, DDL์€ match.formation, match_formation_idx๋ฅผ ์‚ฌ์šฉ (DDL.sql vs src/middleware/checkData.js:31) โ†’ ์‹ค์ œ ์‹คํ–‰ ์‹œ 404 ์—๋Ÿฌ๋ฅผ ํ•ญ์ƒ ๋˜์งˆ ๊ฐ€๋Šฅ์„ฑ.
    • ํฌํŠธํด๋ฆฌ์˜ค์—์„œ ์ด๋Ÿฐ ๋ถ€๋ถ„์„ ๋ฏธ๋ฆฌ ์ธ์ง€ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์„ ์–ธ๊ธ‰ํ•˜๋ฉด, ์ฝ”๋“œ ๋ฆฌ๋ทฐ/ํ…Œ์ŠคํŠธ ๊ฐ์ˆ˜ ๋Šฅ๋ ฅ์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Œ.

2-2. ์—๋Ÿฌ ๋ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

๊ฐ•์ 

  • ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

    • src/index.js ํ•˜๋‹จ์—์„œ app.use((err, req, res, next) => { ... })๋กœ ์ „์—ญ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„.
    • customError(status, message)๋ฅผ ํ†ตํ•ด status/message๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋ถ€์—ฌํ•˜๊ณ , ์‘๋‹ต JSON { message } ํ˜•ํƒœ๋กœ ํ†ต์ผ.
  • ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ์˜ˆ์™ธ ํฌ์ฐฉ

    • ๋ชจ๋“  service ํ•จ์ˆ˜ export ์‹œ trycatchWrapper๋กœ ๊ฐ์‹ธ์„œ async ์˜ˆ์™ธ๋ฅผ next(e)๋กœ ์œ„์ž„ (src/router/account/service.js ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„ ๋“ฑ).
    • ๋ฏธ๋“ค์›จ์–ด๋“ค๋„ ๋‚ด๋ถ€์—์„œ try/catch ํ›„ next(e) ํ˜ธ์ถœ โ†’ ์˜ˆ์™ธ ํ๋ฆ„์ด ์ผ์ •ํ•˜๋‹ค.
  • ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ๋ฉ”์‹œ์ง€

    • ๋Œ€๋ถ€๋ถ„์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ์‚ฌ์šฉ์ž/๋น„์ฆˆ๋‹ˆ์Šค ๊ด€์ ์˜ ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์Œ(โ€œ๋งค์น˜๊ฐ€ ์•„์ง ์ข…๋ฃŒ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹คโ€, โ€œ์ด๋ฏธ ํ•ด๋‹น ํฌ์ง€์…˜์— ๋‹ค๋ฅธ ์ฐธ๊ฐ€์ž๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹คโ€ ๋“ฑ).

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • ๋กœ๊น… ์ „๋žต ๋ถ€์žฌ

    • console.log, console.error ์ˆ˜์ค€์˜ ๋‹จ์ˆœ ๋กœ๊น…๋งŒ ์‚ฌ์šฉ.
    • ๋กœ๊ทธ ๋ ˆ๋ฒจ(INFO/WARN/ERROR), ๊ตฌ์กฐํ™”(JSON), ์ฝ”๋ฆด๋ ˆ์ด์…˜ ID, ์š”์ฒญ ๋‹จ์œ„ ํŠธ๋ ˆ์ด์‹ฑ ๋“ฑ์ด ์—†์–ด ์šด์˜ ํ™˜๊ฒฝ์—์„œ ๋ฌธ์ œ ๋ถ„์„์ด ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Œ.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • winston ๋“ฑ์œผ๋กœ ์ตœ์†Œํ•œ level ๊ธฐ๋ฐ˜ ๋กœ๊ทธ + JSON ๋กœ๊ทธ ๊ตฌ์กฐํ™”.
      • ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ์—์„œ status>=500 ์—๋Ÿฌ๋งŒ ๋ณ„๋„ ์ฑ„๋„๋กœ ๊ธฐ๋ก(์˜ˆ: error.log).
  • ์—๋Ÿฌ ํƒ€์ž… ๋ถ„๋ฅ˜ ๋ถ€์กฑ

    • customError๋Š” status, message๋งŒ ๋‹ด๋Š” ๋‹จ์ˆœ Error.
    • ์ธ์ฆ/์ธ๊ฐ€/๊ฒ€์ฆ/๋น„์ฆˆ๋‹ˆ์Šค ์—๋Ÿฌ ๋“ฑ ํƒ€์ž…๋ณ„ ๊ตฌ๋ถ„์ด ์—†์–ด์„œ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—๋Ÿฌ ์ข…๋ฅ˜๋ณ„๋กœ ๋ทฐ/UX๋ฅผ ์„ธ๋ถ„ํ™”ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

2-3. ํ…Œ์ŠคํŠธ ์ „๋žต

  • ํ˜„ํ™ฉ

    • package.json ๋ฐ chat/package.json ๋ชจ๋‘ "test": "echo \"Error: no test specified\" && exit 1", ์‹ค์ œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋ฏธ๊ตฌํ˜„.
    • ์œ ๋‹›/ํ†ตํ•ฉ/E2E ํ…Œ์ŠคํŠธ, ์ปค๋ฒ„๋ฆฌ์ง€ ๋„๊ตฌ ์‚ฌ์šฉ ํ”์  ์—†์Œ.
  • ์˜ํ–ฅ

    • ๋„๋ฉ”์ธ ๋กœ์ง์ด ๋ณต์žกํ•œ ๋งค์น˜ ์ผ์ •/์ฐธ๊ฐ€/๋Œ€๊ธฐ์—ด/์ฑ”ํ”ผ์–ธ์‹ญ/๊ถŒํ•œ ๋กœ์ง์— ๋น„ํ•ด ํ…Œ์ŠคํŠธ ๋ถ€์žฌ๋Š” ํฐ ๋ฆฌ์Šคํฌ.
    • ๋ฆฌํŒฉํ„ฐ๋ง/์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ํšŒ๊ท€ ๋ฒ„๊ทธ๋ฅผ ์ž๋™์œผ๋กœ ์žก์„ ์ˆ˜๋‹จ์ด ์—†๋‹ค.
  • ๊ฐœ์„  ์ œ์•ˆ

    • ๋‹จ๊ธฐ:
      • checkInput, checkRole, checkCondition ๊ฐ™์€ ์ˆœ์ˆ˜ ํ•จ์ˆ˜/๋ฏธ๋“ค์›จ์–ด๋ถ€ํ„ฐ Jest ๊ธฐ๋ฐ˜ ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€.
      • ๋งค์น˜ ์ƒ์„ฑ/์ฐธ๊ฐ€/๋งˆ๊ฐ, ํŒ€ ๊ฐ€์ž…/ํƒˆํ‡ด, ์ปค๋ฎค๋‹ˆํ‹ฐ ์šด์˜์ง„ ์Šน์ธ ๋“ฑ ํ•ต์‹ฌ use-case๋ฅผ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋กœ ์ตœ์†Œ 5โ€“10๊ฐœ ์ •๋„ ์ •์˜.
    • ์ค‘์žฅ๊ธฐ:
      • /tests ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์ •๋ฆฌ, CI์—์„œ ํ…Œ์ŠคํŠธ ์ž๋™ ์ˆ˜ํ–‰.
      • ์ปค๋ฒ„๋ฆฌ์ง€ 60โ€“70% ์ •๋„๋ฅผ ๋ชฉํ‘œ๋กœ ์ ์ง„์  ํ™•๋Œ€.

3. ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ๋ณ‘๋ชฉ ํ˜„์ƒ ์ ๊ฒ€

3-1. ์ž ์žฌ์  ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„

  • DB ์ค‘์‹ฌ ์•„ํ‚คํ…์ฒ˜

    • ๋Œ€๋ถ€๋ถ„์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๊ธฐ๋Šฅ์ด Postgres ์ฟผ๋ฆฌ์— ์˜์กด.
    • match/community/board ๊ด€๋ จ API์—์„œ ์š”์ฒญ๋‹น ๋‹ค์ˆ˜์˜ ๊ฒ€์ฆ ๋ฏธ๋“ค์›จ์–ด โ†’ ๊ฐ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๊ฐœ๋ณ„ client.query ํ˜ธ์ถœ.
    • ์˜ˆ: ๋งค์น˜ ๊ด€๋ จ ๋ผ์šฐํŠธ์—์„œ checkIdx + checkIsMatch + getMatchAndTeamInfo + ์—ฌ๋Ÿฌ checkCondition ์กฐํ•ฉ ์‹œ ํ•œ ์š”์ฒญ์— 4โ€“8๊ฐœ์˜ ์ฟผ๋ฆฌ ๋ฐœ์ƒ ๊ฐ€๋Šฅ.
  • ์ง‘์ค‘ ํŠธ๋ž˜ํ”ฝ ๊ฐ€๋Šฅ ์ง€์ 

    • ๊ณต๊ฐœ ๋งค์น˜ ๋ชฉ๋ก, ์ปค๋ฎค๋‹ˆํ‹ฐ/ํŒ€ ๊ฒŒ์‹œํŒ, ํŒ€ ์ฑ„ํŒ… ์กฐํšŒ ๋“ฑ read-heavy API:
      • getOpenMatchList, getTeamMatchList, getBoardList, getTeamChat ๋“ฑ (src/router/match/service.js, src/router/board/service.js, src/router/chat/service.js).
      • ํ˜„์žฌ๋Š” ๋ชจ๋“  ์กฐํšŒ๋ฅผ ์‹ค์‹œ๊ฐ„ DB ์ฟผ๋ฆฌ๋กœ ์ฒ˜๋ฆฌ โ†’ ํŠธ๋ž˜ํ”ฝ ๊ธ‰์ฆ ์‹œ DB๊ฐ€ ์ฒซ ๋ณ‘๋ชฉ ์ง€์ .

3-2. DB ์ฟผ๋ฆฌ ์ตœ์ ํ™” ๊ด€์ 

๊ฐ•์ 

  • ์ •๊ทœํ™” ๋ฐ ์ œ์•ฝ ์กฐ๊ฑด ํ™œ์šฉ

    • ์™ธ๋ž˜ํ‚ค, UNIQUE, CHECK, EXCLUDE ๋“ฑ ์ œ์•ฝ์„ ์ ๊ทน ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ํ™•๋ณด (DDL.sql).
    • match.participant์˜ EXCLUDE USING GIST + TSTZRANGE๋Š” ์‹œ๊ฐ„ ๊ฒน์นจ์„ DB ๋ ˆ๋ฒจ์—์„œ ๋ง‰๋Š” ๊ณ ๊ธ‰ ํŒจํ„ด์œผ๋กœ, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ๋™์‹œ์„ฑ ์ด์Šˆ๋ฅผ ์˜ˆ๋ฐฉํ•œ๋‹ค.
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ

    • LIMIT 30 OFFSET page * 30 ํŒจํ„ด์œผ๋กœ ๋Œ€๋ถ€๋ถ„ ๋ชฉ๋ก API๋ฅผ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ (src/router/chat/sql.js:1 ๋“ฑ).

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • ์ธ๋ฑ์Šค ์ „๋žต

    • PK/UNIQUE ์™ธ์— ์ž์ฃผ ํ•„ํ„ฐ๋ง/์กฐ์ธ์— ์‚ฌ์šฉ๋˜๋Š” ์ปฌ๋Ÿผ(์˜ˆ: team_list_idx, community_list_idx, player_list_idx, match_match_idx)์— ๋Œ€ํ•œ ๋ช…์‹œ์  INDEX ์„ ์–ธ์ด ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค.
    • ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด match/board/chat ํ…Œ์ด๋ธ”์—์„œ ์ฟผ๋ฆฌ ์„ฑ๋Šฅ ์ €ํ•˜ ๊ฐ€๋Šฅ์„ฑ.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • ์‹ค์ œ ์ฟผ๋ฆฌ ํŒจํ„ด์— ๋งž์ถฐ CREATE INDEX idx_match_team ON match.match(team_list_idx); ๋“ฑ ๋ณด์กฐ ์ธ๋ฑ์Šค ์„ค๊ณ„.
      • ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๊ฐ€ ์Œ“์ด๋Š” match.team_stats, match.player_stats, board.list, chat.team_chat_message์—๋„ ํ•„์š” ์‹œ ํŒŒํ‹ฐ์…”๋‹/์•„์นด์ด๋น™ ๊ณ ๋ ค.
  • N+1 ์ˆ˜์ค€๊นŒ์ง€๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๊ณผ๋„ํ•œ ๋‹ค์ค‘ ์ฟผ๋ฆฌ

    • checkData.checkExistsInDB ๋ฐ ์—ฌ๋Ÿฌ checkCondition์ด ์„œ๋กœ ๋‹ค๋ฅธ SQL๋กœ ์กด์žฌ โ†’ ํ•˜๋‚˜์˜ ๋„๋ฉ”์ธ ์•ก์…˜์— ๋Œ€ํ•ด ๊ด€๋ จ๋œ ์ •๋ณด๋ฅผ ํ•œ ๋ฒˆ์˜ JOIN ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ํ˜•ํƒœ๋กœ ์ตœ์ ํ™” ๊ฐ€๋Šฅ.
    • ์˜ˆ: ๋งค์น˜ ์ƒ์„ธ ์กฐํšŒ ์‹œ, match ์ •๋ณด + ํŒ€ ์ •๋ณด + ์ฐธ๊ฐ€์ž ๋ชฉ๋ก์„ ๋‹จ๊ณ„์ ์œผ๋กœ ์—ฌ๋Ÿฌ API๋กœ ๋‚˜๋ˆ„์ง€ ๋ง๊ณ , API ์„ค๊ณ„/์ฟผ๋ฆฌ ๋ ˆ๋ฒจ์—์„œ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋„ ๊ณ ๋ ค.
  • SELECT * ์‚ฌ์šฉ

    • ์กด์žฌ ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๋Š” ์šฉ๋„์—์„œ๋„ SELECT * ์‚ฌ์šฉ (checkExistsInDB ๋“ฑ).
    • ์„ฑ๋Šฅ ์˜ํ–ฅ์€ ํฌ์ง€ ์•Š์ง€๋งŒ, ๋ช…์‹œ์ ์œผ๋กœ SELECT 1 ๋˜๋Š” ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ ์กฐํšŒํ•˜๋Š” ์Šต๊ด€์ด ์ข‹๋‹ค.

3-3. ์บ์‹ฑ ์ „๋žต

๊ตฌํ˜„๋œ ๋ถ€๋ถ„

  • Redis ์‚ฌ์šฉ
    • SMS ์ธ์ฆ ์ฝ”๋“œ ๋ฐ ์‹œ๋„ ํšŸ์ˆ˜ ์ œํ•œ: searchPwSend, searchPwVerify ๋“ฑ์—์„œ redisClient ํ™œ์šฉ (src/router/account/service.js ํ›„๋ฐ˜๋ถ€).
      • TTL (CODE_EXPIRY), ์ „์†ก ํšŸ์ˆ˜ ์ œํ•œ (MAX_SEND_COUNT, SEND_COUNT_EXPIRY) ๋“ฑ์œผ๋กœ ๋ณด์•ˆ + ์„ฑ๋Šฅ์„ ๋™์‹œ์— ๊ณ ๋ ค.
    • Chat ์„œ๋ฒ„ Redis Pub/Sub: Socket.IO Redis adapter (chat/index.js:17)๋กœ ์—ฌ๋Ÿฌ ์ฑ„ํŒ… ์ธ์Šคํ„ด์Šค ๊ฐ„ ๋ฉ”์‹œ์ง€ ๋™๊ธฐํ™”.

๋ฏธํกํ•œ ๋ถ€๋ถ„

  • ์ฝ๊ธฐ ์บ์‹œ ๋ถ€์žฌ

    • ๋งค์น˜ ๋ชฉ๋ก, ์ธ๊ธฐ ๊ฒŒ์‹œํŒ, ํŒ€/์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด ๋“ฑ read-heavy ์—”๋“œํฌ์ธํŠธ์— HTTP ์บ์‹œ/Redis ์บ์‹œ๊ฐ€ ์—†๋‹ค.
    • ํŠนํžˆ ๋žญํ‚น/MMR, ์ปค๋ฎค๋‹ˆํ‹ฐ ์ •๋ณด, ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ๊ณต์ง€ ๋“ฑ์€ TTL ๊ธฐ๋ฐ˜์˜ cache-aside ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด DB ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Œ.
  • ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต

    • SMS/Chat ์™ธ์— ์บ์‹œ๋ฅผ ์“ฐ์ง€ ์•Š์•„์„œ invalidation ์„ค๊ณ„ ์ž์ฒด๊ฐ€ ์—†๋Š” ์ƒํƒœ.
    • ํ–ฅํ›„ ์บ์‹œ๋ฅผ ๋„์ž…ํ•  ๊ฒฝ์šฐ, CRUD ์ด๋ฒคํŠธ๋ณ„๋กœ ์บ์‹œ ํ‚ค๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฐฑ์‹ /์‚ญ์ œํ• ์ง€ ์„ค๊ณ„ ํ•„์š”.

4. ๋ณด์•ˆ ๋ฐ ์šด์˜(DevOps) ์ ๊ฒ€

4-1. ๋ณด์•ˆ ์ทจ์•ฝ์  ๊ด€์ 

๊ฐ•์ 

  • SQL Injection ๋ฐฉ์–ด

    • ๋Œ€๋ถ€๋ถ„ ์ฟผ๋ฆฌ๊ฐ€ $1, $2, ... placeholder + client.query(sql, [params]) ํ˜•ํƒœ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค (src/router/account/sql.js, src/router/match/sql.js ๋“ฑ).
    • checkData.checkExistsInDB์—์„œ๋„ ํ…Œ์ด๋ธ”/์ปฌ๋Ÿผ ์ด๋ฆ„์€ ์ฝ”๋“œ ์ƒ์—์„œ๋งŒ ์ฃผ์ž…๋˜๊ณ , ๊ฐ’์€ ๋ฐ”์ธ๋”ฉ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฒ˜๋ฆฌ โ†’ ๋™์  SQL ๊ตฌ์กฐ์ง€๋งŒ Injection ์œ„ํ—˜์€ ๋‚ฎ๋‹ค.
  • ๋น„๋ฐ€๋ฒˆํ˜ธ/ํ† ํฐ ๊ด€๋ฆฌ

    • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” bcrypt๋กœ ํ•ด์‹œ ํ›„ ์ €์žฅ (src/router/account/service.js์˜ updatePassword ๋“ฑ).
    • JWT ๋น„๋ฐ€ํ‚ค, DB/Redis ์ ‘์† ์ •๋ณด๋Š” .env ๊ธฐ๋ฐ˜์œผ๋กœ ๊ด€๋ฆฌ (dotenv.config, src/database/postgreSQL.js:1, src/database/redisClient.js:1).
    • RefreshToken์€ DB ํ…Œ์ด๋ธ”(player.refreshtoken)์— ์ €์žฅ, device_uuid์™€ ํ•จ๊ป˜ UNIQUE ์ œ์•ฝ (DDL.sql).
  • ์ž…๋ ฅ ๊ฒ€์ฆ

    • ์ •๊ทœ์‹ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ(constant/regx.js)๊ณผ enum ๊ฒ€์ฆ(constant/constantIndex.js)์„ ๋ฏธ๋“ค์›จ์–ด ๋ ˆ๋ฒจ์—์„œ ์ฒ ์ €ํžˆ ์ˆ˜ํ–‰:
      • ID/Password/Nickname/Phone/Board Title/Content/Match ์‹œ๊ฐ„ ํ˜•์‹ ๋“ฑ.

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • XSS ๋Œ€์‘ ๋ฏธํก

    • ๊ฒŒ์‹œ๊ธ€/๋Œ“๊ธ€ ๋‚ด์šฉ(board_list_content, board_comment_content)์€ ๊ธธ์ด ์ œํ•œ๋งŒ ์žˆ๊ณ , HTML/์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ์— ๋Œ€ํ•œ ํ•„ํ„ฐ๋ง/escape๋Š” ์•ˆ ๋ณด์ธ๋‹ค.
    • ์‹ค์ œ ํ”„๋ŸฐํŠธ์—”๋“œ ๋ Œ๋”๋ง ์‹œ, ์„œ๋ฒ„ ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ XSS ๋ฐฉ์ง€(escape or sanitize)๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
    • ๊ฐœ์„  ์ œ์•ˆ:
      • ์„œ๋ฒ„์—์„œ ์ €์žฅ ์‹œ HTML์„ sanitize ํ•˜๊ฑฐ๋‚˜,
      • ์ตœ์†Œํ•œ ์ถœ๋ ฅ ์‹œ์—๋Š” escape(ํ…œํ”Œ๋ฆฟ ์—”์ง„/ํ”„๋ŸฐํŠธ์—์„œ)ํ•œ๋‹ค๋Š” ์ „๋žต์„ ๋ช…ํ™•ํžˆ.
  • CORS ์„ค์ •

    • src/index.js์— origin whitelist CORS ์„ค์ •์ด ์žˆ์œผ๋‚˜ ํ˜„์žฌ๋Š” ์ฃผ์„ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ๋‹ค.
    • ์‹ค์„œ๋น„์Šค๋ผ๋ฉด, origin์„ .env ๋˜๋Š” ์„ค์ • ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , CORS ์ •์ฑ…์„ ์žฌํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „.
  • ํ† ํฐ/์„ธ์…˜ ๊ด€๋ฆฌ ์ƒ์„ธ

    • AccessToken ๋งŒ๋ฃŒ ํ›„ RefreshToken ํ๋ฆ„์€ ๊ตฌํ˜„๋˜์–ด ์žˆ์œผ๋‚˜ (checkRefreshToken), RefreshToken ํšŒ์ˆ˜/๋ธ”๋ž™๋ฆฌ์ŠคํŠธ/๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ ์ •์ฑ…์€ ์ฝ”๋“œ์™€ ์Šคํ‚ค๋งˆ๋งŒ์œผ๋กœ๋Š” ์™„์ „ํžˆ ๋“œ๋Ÿฌ๋‚˜์ง€ ์•Š๋Š”๋‹ค.
    • ๋กœ๊ทธ์•„์›ƒ ์‹œ RefreshToken ์‚ญ์ œ ์—ฌ๋ถ€ ๋“ฑ์ด ๋ช…ํ™•ํžˆ ์ •๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฉด ์ข‹์Œ.

4-2. ์ธ์ฆ/์ธ๊ฐ€(AuthN/AuthZ)

๊ตฌํ˜„ ๋‚ด์šฉ

  • ์ธ์ฆ (AuthN)

    • AccessToken: Authorization ํ—ค๋”์— ํ† ํฐ ์ž์ฒด๋ฅผ ๋„ฃ๋Š” ๋ฐฉ์‹(checkLogin).
    • RefreshToken: player.refreshtoken ํ…Œ์ด๋ธ”์— ์ €์žฅ, device_uuid์™€ ๋ฌถ์–ด์„œ per-device token ๊ด€๋ฆฌ.
    • Discord OAuth2: getDiscordSigninPage, discordOauthSigninLogic (src/router/account/service.js)์—์„œ OAuth ์ฝ”๋“œ ๊ตํ™˜, ์‹ ๊ทœ ๊ณ„์ • ์ƒ์„ฑ/๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ.
    • SMS ๊ธฐ๋ฐ˜ ์ž„์‹œ ์ธ์ฆ: ํšŒ์›๊ฐ€์ž…/๋น„๋ฐ€๋ฒˆํ˜ธ ์ฐพ๊ธฐ์—์„œ ํœด๋Œ€ํฐ ์ธ์ฆ + ์ž„์‹œ ์•ก์„ธ์Šค ํ† ํฐ(access_token_temporary) ๋ฐœ๊ธ‰.
  • ์ธ๊ฐ€ (AuthZ / RBAC)

    • ์—ญํ•  ์ƒ์ˆ˜: TEAM_ROLE, COMMUNITY_ROLE, BOARD_CATEGORY ๋“ฑ (src/constant/constantIndex.js).
    • checkRole ๊ณ„์—ด ๋ฏธ๋“ค์›จ์–ด๋กœ ์—ญํ• ์— ๋”ฐ๋ฅธ ์ ‘๊ทผ ์ œ์–ด:
      • checkIsTeamLeader, checkIsTeamSubLeader, checkIsCommunityAdminRole, checkIsCommunityStaffRole ๋“ฑ.
      • checkHasTeamOrCommunity๋กœ ๊ฒŒ์‹œํŒ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์†Œ์† ์—ฌ๋ถ€ ๊ฒ€์ฆ.
    • checkCondition์˜ ๋‹ค์–‘ํ•œ ์ฒดํฌ๋กœ ์—…๋ฌด ๊ทœ์น™ ๊ธฐ๋ฐ˜ ์ธ๊ฐ€:
      • ๋งค์น˜ ์˜ค๋„ˆ๋งŒ ์ˆ˜์ •/์‚ญ์ œ ๊ฐ€๋Šฅ(checkIsMatchOwner), ์ข…๋ฃŒ๋˜์ง€ ์•Š์€ ๋งค์น˜์—์„œ๋งŒ ์ฐธ์—ฌ ๊ฐ€๋Šฅ(checkMatchNotEnded), ์ฑ”ํ”ผ์–ธ์‹ญ ๋งค์น˜์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ ์ œํ•œ(checkIfChampionshipMatchOnly).

ํ‰๊ฐ€

  • RBAC๊ณผ ๋„๋ฉ”์ธ ๊ทœ์น™์ด ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ์œผ๋กœ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์–ด, ํฌํŠธํด๋ฆฌ์˜ค์—์„œ ๊ฐ•์กฐํ•˜๊ธฐ ์ข‹์€ ๋ถ€๋ถ„์ด๋‹ค.
  • ๋‹ค๋งŒ, ๊ถŒํ•œ/์ƒํƒœ ์ฒดํฌ๊ฐ€ ์ฝ”๋“œ ์—ฌ๋Ÿฌ ๊ตฐ๋ฐ์— ์‚ฐ์žฌํ•ด ์žˆ์–ด, ๋„๋ฉ”์ธ ์„œ๋น„์Šค๋กœ ํ•œ ๋ฒˆ ๋” ์ถ”์ƒํ™”ํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํฌ๊ฒŒ ์˜ฌ๋ผ๊ฐˆ ๊ฒƒ.

4-3. ๋ฐฐํฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

๊ฐ•์ 

  • Docker ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ/์šด์˜ ํ™˜๊ฒฝ
    • docker-compose.yml์—์„œ DB, Redis, app, chat-server๋ฅผ ํ•œ ๋ฒˆ์— ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋„๋ก ์ •์˜.
    • DB ์ดˆ๊ธฐ ์Šคํ‚ค๋งˆ/seed SQL (DDL.sql, insert_defaults.sql)์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด, ๋กœ์ปฌ/ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ์žฌํ˜„์ด ์‰ฝ๋‹ค.
    • Chat ์„œ๋ฒ„๋ฅผ ๋ณ„๋„ ์„œ๋น„์Šค๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ํ™•์žฅ์„ฑ/๊ฒฉ๋ฆฌ๋ฅผ ๊ณ ๋ ค.

์•ฝ์  / ๊ฐœ์„  ํฌ์ธํŠธ

  • CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๋ถ€์žฌ

    • GitHub Actions/Jenkins ๋“ฑ ์ž๋™ ๋นŒ๋“œ/๋ฐฐํฌ ์„ค์ •์ด ๋ ˆํฌ์— ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Œ.
    • ํฌํŠธํด๋ฆฌ์˜ค ์ƒ์œผ๋กœ๋Š”, โ€œํ˜„์žฌ๋Š” ์ˆ˜๋™ ๋ฐฐํฌ์ด์ง€๋งŒ, ํ–ฅํ›„ CI/CD๋ฅผ ์–ด๋–ป๊ฒŒ ์„ค๊ณ„ํ• ์ง€โ€๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ข‹๋‹ค.
  • ๋ชจ๋‹ˆํ„ฐ๋ง/๋กœ๊ทธ ์ˆ˜์ง‘

    • Prometheus/Grafana, ELK/EFK, CloudWatch ๋“ฑ ๋ชจ๋‹ˆํ„ฐ๋ง/๋กœ๊ทธ ์ˆ˜์ง‘ ์—ฐ๋™ ํ”์  ์—†์Œ.
    • ์ตœ์†Œํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ๋ฅผ ํŒŒ์ผ/STDOUT JSON ํ˜•ํƒœ๋กœ ๋‚จ๊ฒจ ์ค‘์•™ ์ˆ˜์ง‘ํ•˜๋Š” ํŒจํ„ด์„ ๋„์ž…ํ•˜๋ฉด ์šด์˜ ์„ฑ์ˆ™๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค.

5. ํฌํŠธํด๋ฆฌ์˜ค ์ตœ์ข… ์ •๋ฆฌ ๋ฐ ์ œ์–ธ

5-1. ๋ฉด์ ‘์—์„œ ๊ฐ•์กฐํ•  ๊ธฐ์ˆ ์  ๊ฐ•์  5๊ฐ€์ง€

  1. ๋„๋ฉ”์ธ ์ค‘์‹ฌ ๋ชจ๋“ˆํ™” + ๋ฏธ๋“ค์›จ์–ด ์ฒด์ธ ์„ค๊ณ„
    • ๋„๋ฉ”์ธ๋ณ„(router/service/sql) ๊ตฌ์กฐ์™€ ๊ณตํ†ต ๋ฏธ๋“ค์›จ์–ด(checkInput, checkLogin, checkRole, checkCondition, s3Uploader ๋“ฑ)๋ฅผ ํ†ตํ•ด ๊ถŒํ•œ/๊ฒ€์ฆ/์กฐ๊ฑด ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ˆ˜ํ‰ ๋ ˆ์ด์–ด๋กœ ์„ค๊ณ„ํ•œ ์ .
  2. ํƒ„ํƒ„ํ•œ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง๊ณผ ๊ณ ๊ธ‰ Postgres ๊ธฐ๋Šฅ ํ™œ์šฉ
    • ๋ฉ€ํ‹ฐ ์Šคํ‚ค๋งˆ ๋ถ„๋ฆฌ, ENUM/์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ, TSTZRANGE + EXCLUDE USING GIST๋กœ ์ฐธ๊ฐ€ ์‹œ๊ฐ„ ์ค‘๋ณต์„ DB ๋ ˆ๋ฒจ์—์„œ ์ฐจ๋‹จํ•˜๋Š” ๋“ฑ, ์ถ•๊ตฌ ๋„๋ฉ”์ธ์— ๋งž๋Š” ๊ด€๊ณ„ํ˜• ๋ชจ๋ธ๋ง์„ ๊นŠ์ด ์žˆ๊ฒŒ ์ˆ˜ํ–‰ํ•œ ์ .
  3. ์‹ค์„œ๋น„์Šค ์ˆ˜์ค€์˜ ์ธ์ฆ/์ธ๊ฐ€ ํ๋ฆ„ ๊ตฌํ˜„
    • JWT Access/Refresh Token, ๋””๋ฐ”์ด์Šค๋ณ„ RefreshToken ๊ด€๋ฆฌ, Discord OAuth2 ๋กœ๊ทธ์ธ, SMS ๊ธฐ๋ฐ˜ 2์ฐจ ์ธ์ฆ ๋ฐ Redis๋ฅผ ํ™œ์šฉํ•œ rate limiting๊นŒ์ง€ ์—ฌ๋Ÿฌ ์ธ์ฆ ์ˆ˜๋‹จ์„ ์กฐํ•ฉํ•œ ํ˜„์‹ค์ ์ธ Auth ํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์ถ•ํ•œ ์ .
  4. ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์„œ๋น„์Šค์˜ ๋ถ„๋ฆฌ์™€ ์ˆ˜ํ‰ ํ™•์žฅ ๊ณ ๋ ค
    • Socket.IO + Redis Adapter๋กœ ๋ณ„๋„ ์ฑ„ํŒ… ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ , ํŒ€ ๋‹จ์œ„ ๋ฃธ ๋ชจ๋ธ, ๋ฉ”์‹œ์ง€ ์˜์†ํ™”(Postgres)๋ฅผ ํ†ตํ•ด ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ์„ ๋ฉ”์ธ API์™€ ๋ถ„๋ฆฌํ•˜์—ฌ ์„ค๊ณ„ํ•œ ์ .
  5. ํŒŒ์ผ ์—…๋กœ๋“œ ๋ฐ ์™ธ๋ถ€ ์Šคํ† ๋ฆฌ์ง€ ์—ฐ๋™ ์ฑ…์ž„ ๋ถ„๋ฆฌ
    • multer + AWS S3 + CDN ๋„๋ฉ”์ธ์„ ์ด์šฉํ•œ ์—…๋กœ๋“œ ํŒŒ์ดํ”„๋ผ์ธ์„ s3Uploader* ๋ฏธ๋“ค์›จ์–ด๋กœ ์บก์Аํ™”ํ•˜์—ฌ, ๋„๋ฉ”์ธ ๋กœ์ง๊ณผ ์ธํ”„๋ผ ๋””ํ…Œ์ผ์„ ๋ถ„๋ฆฌํ•œ ์ .

5-2. ๋ฉด์ ‘์—์„œ ์†”์งํ•˜๊ฒŒ ์ธ์ •ํ•˜๊ณ  ๊ฐœ์„  ์˜์ง€๋ฅผ ๋ณด์ผ ์•ฝ์  2๊ฐ€์ง€

  1. ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ ๋ฐ ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ๋ถ€์žฌ

    • ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—๋Š” ์œ ๋‹›/ํ†ตํ•ฉ/E2E ํ…Œ์ŠคํŠธ๊ฐ€ ์—†๊ณ , CI์—์„œ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ์ฒด๊ณ„๋„ ์—†๋‹ค.
    • โ€œ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„๊ณผ ์Šคํ‚ค๋งˆ ์„ค๊ณ„์— ์ง‘์ค‘ํ–ˆ๊ณ , ๋‹ค์Œ ๋‹จ๊ณ„๋กœ๋Š” Jest๋ฅผ ํ†ตํ•œ ๋„๋ฉ”์ธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์™€ CI ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•ด ํ’ˆ์งˆ์„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์‹ถ๋‹คโ€๊ณ  ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  2. ์šด์˜/๊ด€์ธก(Observability) ๋ฐ ์ผ๋ถ€ ์ธํ”„๋ผ ์„ค์ •์˜ ๋ฏธ์„ฑ์ˆ™

    • ๋กœ๊น…์ด console.log ์ˆ˜์ค€์ด๊ณ , Dockerfile์˜ CMD๊ฐ€ dev ์ค‘์‹ฌ(tail -f /dev/null)์ธ ์ , CORS ์„ค์ •์ด ํ•˜๋“œ์ฝ”๋”ฉ/์ฃผ์„ ์ƒํƒœ์ธ ์  ๋“ฑ.
    • โ€œํ˜„์žฌ๋Š” ์†Œ๊ทœ๋ชจ ๊ฐœ์ธ/ํŒ€ ํ”„๋กœ์ ํŠธ ์ˆ˜์ค€์˜ ์šด์˜์ด์—ˆ๊ณ , ๋‹ค์Œ์—๋Š” ๊ตฌ์กฐํ™” ๋กœ๊ทธ, ๋ชจ๋‹ˆํ„ฐ๋ง, ํ—ฌ์Šค์ฒดํฌ, CI/CD๋ฅผ ํฌํ•จํ•œ ํ”„๋กœ๋•์…˜ ์šด์˜ ๊ด€์ ์—์„œ ์„ค๊ณ„๋ฅผ ๋” ๋ณด์™„ํ•˜๊ฒ ๋‹คโ€๊ณ  ์ •๋ฆฌํ•˜๋ฉด ์ข‹๋‹ค.

5-3. ํฌํŠธํด๋ฆฌ์˜ค์šฉ์œผ๋กœ ๊ฐ€์žฅ ์ธ์ƒ์ ์ธ ์ฝ”๋“œ ์Šค๋‹ˆํŽซ ์ œ์•ˆ

์ถ”์ฒœ ์Šค๋‹ˆํŽซ: Postgres๋ฅผ ํ™œ์šฉํ•œ ๋งค์น˜ ์ฐธ๊ฐ€ ์‹œ๊ฐ„ ์ค‘๋ณต ๋ฐฉ์ง€ ์„ค๊ณ„ (DDL.sql ์ค‘ match.participant ํ…Œ์ด๋ธ”)

CREATE TABLE match.participant (
  match_participant_idx SERIAL PRIMARY KEY,
  match_match_idx INT NOT NULL REFERENCES match.match(match_match_idx) ON DELETE CASCADE,
  player_list_idx INT REFERENCES player.list(player_list_idx) ON DELETE SET NULL,
  match_time_range TSTZRANGE NOT NULL,
  CONSTRAINT unique_participation_time 
    EXCLUDE USING GIST (player_list_idx WITH =, match_time_range WITH &&)
      WHERE (player_list_idx IS NOT NULL)
);

์„ ์ • ์ด์œ 

  • ๋‹จ์ˆœํ•œ CRUD๋ฅผ ๋„˜์–ด์„œ, **๋„๋ฉ”์ธ ๊ทœ์น™(ํ•œ ํ”Œ๋ ˆ์ด์–ด๋Š” ๊ฐ™์€ ์‹œ๊ฐ„๋Œ€์— ๋‘ ๋งค์น˜์— ๋™์‹œ์— ์ฐธ์—ฌํ•  ์ˆ˜ ์—†๋‹ค)**๋ฅผ DB ๋ ˆ๋ฒจ์—์„œ ๊ฐ•์ œํ•˜๊ณ  ์žˆ๋‹ค.
  • Postgres์˜ TSTZRANGE์™€ EXCLUDE USING GIST๋ฅผ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•ด์•ผ ๊ตฌํ˜„ ๊ฐ€๋Šฅํ•œ ํŒจํ„ด์œผ๋กœ,
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ concurrency ์ด์Šˆ๋ฅผ ๋ณต์žกํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ ,
    • DB๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ์ œ์•ฝ ๊ธฐ๋Šฅ์„ ์ ๊ทน ํ™œ์šฉํ•œ ์„ค๊ณ„๋ผ๋Š” ์ ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค/๋„๋ฉ”์ธ ๋ชจ๋ธ๋ง์— ๋Œ€ํ•œ ๊นŠ์€ ์ดํ•ด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
  • ๋ฉด์ ‘์—์„œ ์ด ์Šค๋‹ˆํŽซ์„ ์ค‘์‹ฌ์œผ๋กœ,
    • ์™œ ์ด๋Ÿฐ ์ œ์•ฝ์„ ๋‘์—ˆ๋Š”์ง€(๋„๋ฉ”์ธ ์š”๊ตฌ์‚ฌํ•ญ),
    • ๋‹ค๋ฅธ ๋Œ€์•ˆ(์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ ์ฒดํฌ vs DB ์ œ์•ฝ)๊ณผ ๋น„๊ตํ•œ ์žฅ๋‹จ์ ,
    • ์ธ๋ฑ์Šค/์„ฑ๋Šฅ ์ธก๋ฉด ๊ณ ๋ ค ์‚ฌํ•ญ,
    • ํŠธ๋žœ์žญ์…˜/๋™์‹œ์„ฑ ์ƒํ™ฉ์—์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ฅผ ์„ค๋ช…ํ•˜๋ฉด, ์•„ํ‚คํ…ํŠธ ๊ด€์ ์˜ ์‚ฌ๊ณ ์™€ RDBMS ํ™œ์šฉ ๋Šฅ๋ ฅ์„ ๊ฐ•ํ•˜๊ฒŒ ์–ดํ•„ํ•  ์ˆ˜ ์žˆ๋‹ค.