-
๊ตฌ์กฐ ์์ฝ
- 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-server4๊ฐ ์๋น์ค ๊ตฌ์ฑ.
- HTTP API:
-
ํจํด ๊ด์
- ์ ํ์ ์ธ ๋ ์ด์ด๋ ์ํคํ
์ฒ + ๋๋ฉ์ธ ๋ชจ๋ํ:
- ํ๋ ์ ํ ์ด์ (๋ผ์ฐํฐ) / ๋น์ฆ๋์ค(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), ์์ผ ์ปจํ ์คํธ์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด ๋๋ฉ์ธ ํธ๋ค๋ฌ์์ ์ฌ์ฉ โ ๊ตฌ์กฐ์ ์ผ๋ก ๊น๋ํ๋ค.
- Socket.IO + Redis Adapter (
-
์์กด์ฑ ์ญ์ ๋ฏธํก
- ์๋น์ค ๋ ์ด์ด๊ฐ
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 ํ๊ฒฝ ์กฐ์ .
- backend Dockerfile์ CMD๊ฐ
-
๋๋ฉ์ธ ๊ฐ ๊ฒฐํฉ
- ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๊ฐ DB ์คํค๋ง/์ฟผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ง์ ์๋ค(
checkCondition,checkData๋ฑ์์ raw SQL). - ๊ท๋ชจ๊ฐ ๋ ์ปค์ง๋ฉด ๋๋ฉ์ธ ์๋น์ค/๋ฆฌํฌ์งํ ๋ฆฌ ๋ ์ด์ด๊ฐ ๋ฏธ๋ค์จ์ด/๋ผ์ฐํฐ ์์ชฝ์ ๊ณต์ ๋๋๋ก ๋ฆฌํฉํฐ๋ง ํ์.
- ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๊ฐ DB ์คํค๋ง/์ฟผ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ง์ ์๋ค(
-
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). - ๋น์ฆ๋์ค ๋ก์ง(์: ๋งค์น ์์ฑ, ๊ฒ์๊ธ ์์ฑ)์ ์ด ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ ๋ก ํ๋ฏ๋ก, ๊ฐ ๋๋ฉ์ธ ์ฝ๋์์ ๋ก๊ทธ์ธ/์ญํ ๋ก์ง์ ์ค๋ณต ๊ตฌํํ์ง ์์.
- ์ญํ : ํ ํฐ ํ์ฑ โ DB์์ ํ์ฌ ํ/์ปค๋ฎค๋ํฐ ์ญํ ์กฐํ โ
s3Uploader๊ณ์ด:- ์
๋ก๋ํ ํ์ผ์ ํ์/ํฌ๊ธฐ ๊ฒ์ฆ, S3 ์
๋ก๋, URL ์์ฑ๊น์ง ํ ์ฑ
์์ ์ง์ค (
src/middleware/s3UpLoader.js:1). - ์๋น์ค ํจ์๋ ์ ๋ก๋ ๊ฒฐ๊ณผ URL๋ง ์ฌ์ฉํ๋ฉด ๋๋ฏ๋ก, ์ ์ฅ์(S3) ๊ตฌํ ๋ณ๊ฒฝ์ ๋ ์ํฅ์ ๋ฐ๋๋ค.
- ์
๋ก๋ํ ํ์ผ์ ํ์/ํฌ๊ธฐ ๊ฒ์ฆ, S3 ์
๋ก๋, URL ์์ฑ๊น์ง ํ ์ฑ
์์ ์ง์ค (
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.sqlvssrc/middleware/checkData.js:31) โ ์ค์ ์คํ ์ 404 ์๋ฌ๋ฅผ ํญ์ ๋์ง ๊ฐ๋ฅ์ฑ.- ํฌํธํด๋ฆฌ์ค์์ ์ด๋ฐ ๋ถ๋ถ์ ๋ฏธ๋ฆฌ ์ธ์งํ๊ณ ์๋ค๋ ์ ์ ์ธ๊ธํ๋ฉด, ์ฝ๋ ๋ฆฌ๋ทฐ/ํ ์คํธ ๊ฐ์ ๋ฅ๋ ฅ์ ๋ณด์ฌ์ค ์ ์์.
-
๊ธ๋ก๋ฒ ์์ธ ์ฒ๋ฆฌ
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)ํธ์ถ โ ์์ธ ํ๋ฆ์ด ์ผ์ ํ๋ค.
- ๋ชจ๋ service ํจ์ export ์
-
์ฌ์ฉ์ ์นํ์ ์ธ ๋ฉ์์ง
- ๋๋ถ๋ถ์ ์๋ฌ ๋ฉ์์ง๊ฐ ์ฌ์ฉ์/๋น์ฆ๋์ค ๊ด์ ์ ๋ฌธ์ฅ์ผ๋ก ์์ฑ๋์ด ์์(โ๋งค์น๊ฐ ์์ง ์ข ๋ฃ๋์ง ์์์ต๋๋คโ, โ์ด๋ฏธ ํด๋น ํฌ์ง์ ์ ๋ค๋ฅธ ์ฐธ๊ฐ์๊ฐ ์์ต๋๋คโ ๋ฑ).
-
๋ก๊น ์ ๋ต ๋ถ์ฌ
console.log,console.error์์ค์ ๋จ์ ๋ก๊น ๋ง ์ฌ์ฉ.- ๋ก๊ทธ ๋ ๋ฒจ(INFO/WARN/ERROR), ๊ตฌ์กฐํ(JSON), ์ฝ๋ฆด๋ ์ด์ ID, ์์ฒญ ๋จ์ ํธ๋ ์ด์ฑ ๋ฑ์ด ์์ด ์ด์ ํ๊ฒฝ์์ ๋ฌธ์ ๋ถ์์ด ์ด๋ ค์ธ ์ ์์.
- ๊ฐ์ ์ ์:
winston๋ฑ์ผ๋ก ์ต์ํ level ๊ธฐ๋ฐ ๋ก๊ทธ + JSON ๋ก๊ทธ ๊ตฌ์กฐํ.- ์๋ฌ ํธ๋ค๋ฌ์์
status>=500์๋ฌ๋ง ๋ณ๋ ์ฑ๋๋ก ๊ธฐ๋ก(์: error.log).
-
์๋ฌ ํ์ ๋ถ๋ฅ ๋ถ์กฑ
customError๋ status, message๋ง ๋ด๋ ๋จ์ Error.- ์ธ์ฆ/์ธ๊ฐ/๊ฒ์ฆ/๋น์ฆ๋์ค ์๋ฌ ๋ฑ ํ์ ๋ณ ๊ตฌ๋ถ์ด ์์ด์, ํด๋ผ์ด์ธํธ๊ฐ ์๋ฌ ์ข ๋ฅ๋ณ๋ก ๋ทฐ/UX๋ฅผ ์ธ๋ถํํ๊ธฐ ์ด๋ ต๋ค.
-
ํํฉ
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% ์ ๋๋ฅผ ๋ชฉํ๋ก ์ ์ง์ ํ๋.
- ๋จ๊ธฐ:
-
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๊ฐ ์ฒซ ๋ณ๋ชฉ ์ง์ .
- ๊ณต๊ฐ ๋งค์น ๋ชฉ๋ก, ์ปค๋ฎค๋ํฐ/ํ ๊ฒ์ํ, ํ ์ฑํ
์กฐํ ๋ฑ read-heavy API:
-
์ ๊ทํ ๋ฐ ์ ์ฝ ์กฐ๊ฑด ํ์ฉ
- ์ธ๋ํค, UNIQUE, CHECK, EXCLUDE ๋ฑ ์ ์ฝ์ ์ ๊ทน ์ฌ์ฉํด ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ํ๋ณด (
DDL.sql). match.participant์EXCLUDE USING GIST+TSTZRANGE๋ ์๊ฐ ๊ฒน์นจ์ DB ๋ ๋ฒจ์์ ๋ง๋ ๊ณ ๊ธ ํจํด์ผ๋ก, ์ดํ๋ฆฌ์ผ์ด์ ๋ก์ง์ ๋จ์ํํ๊ณ ๋์์ฑ ์ด์๋ฅผ ์๋ฐฉํ๋ค.
- ์ธ๋ํค, UNIQUE, CHECK, EXCLUDE ๋ฑ ์ ์ฝ์ ์ ๊ทน ์ฌ์ฉํด ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ํ๋ณด (
-
ํ์ด์ง ์ฒ๋ฆฌ
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์๋ ํ์ ์ ํํฐ์ ๋/์์นด์ด๋น ๊ณ ๋ ค.
- ์ค์ ์ฟผ๋ฆฌ ํจํด์ ๋ง์ถฐ
- PK/UNIQUE ์ธ์ ์์ฃผ ํํฐ๋ง/์กฐ์ธ์ ์ฌ์ฉ๋๋ ์ปฌ๋ผ(์:
-
N+1 ์์ค๊น์ง๋ ์๋์ง๋ง, ๊ณผ๋ํ ๋ค์ค ์ฟผ๋ฆฌ
checkData.checkExistsInDB๋ฐ ์ฌ๋ฌcheckCondition์ด ์๋ก ๋ค๋ฅธ SQL๋ก ์กด์ฌ โ ํ๋์ ๋๋ฉ์ธ ์ก์ ์ ๋ํด ๊ด๋ จ๋ ์ ๋ณด๋ฅผ ํ ๋ฒ์ JOIN ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ค๋ ํํ๋ก ์ต์ ํ ๊ฐ๋ฅ.- ์: ๋งค์น ์์ธ ์กฐํ ์, match ์ ๋ณด + ํ ์ ๋ณด + ์ฐธ๊ฐ์ ๋ชฉ๋ก์ ๋จ๊ณ์ ์ผ๋ก ์ฌ๋ฌ API๋ก ๋๋์ง ๋ง๊ณ , API ์ค๊ณ/์ฟผ๋ฆฌ ๋ ๋ฒจ์์ ํ ๋ฒ์ ๊ฐ์ ธ์ค๋ ๊ฒ๋ ๊ณ ๋ ค.
-
SELECT * ์ฌ์ฉ
- ์กด์ฌ ์ฌ๋ถ๋ง ํ์ธํ๋ ์ฉ๋์์๋
SELECT *์ฌ์ฉ (checkExistsInDB๋ฑ). - ์ฑ๋ฅ ์ํฅ์ ํฌ์ง ์์ง๋ง, ๋ช
์์ ์ผ๋ก
SELECT 1๋๋ ํ์ํ ์ปฌ๋ผ๋ง ์กฐํํ๋ ์ต๊ด์ด ์ข๋ค.
- ์กด์ฌ ์ฌ๋ถ๋ง ํ์ธํ๋ ์ฉ๋์์๋
- Redis ์ฌ์ฉ
- SMS ์ธ์ฆ ์ฝ๋ ๋ฐ ์๋ ํ์ ์ ํ:
searchPwSend,searchPwVerify๋ฑ์์redisClientํ์ฉ (src/router/account/service.jsํ๋ฐ๋ถ).- TTL (
CODE_EXPIRY), ์ ์ก ํ์ ์ ํ (MAX_SEND_COUNT,SEND_COUNT_EXPIRY) ๋ฑ์ผ๋ก ๋ณด์ + ์ฑ๋ฅ์ ๋์์ ๊ณ ๋ ค.
- TTL (
- Chat ์๋ฒ Redis Pub/Sub: Socket.IO Redis adapter (
chat/index.js:17)๋ก ์ฌ๋ฌ ์ฑํ ์ธ์คํด์ค ๊ฐ ๋ฉ์์ง ๋๊ธฐํ.
- SMS ์ธ์ฆ ์ฝ๋ ๋ฐ ์๋ ํ์ ์ ํ:
-
์ฝ๊ธฐ ์บ์ ๋ถ์ฌ
- ๋งค์น ๋ชฉ๋ก, ์ธ๊ธฐ ๊ฒ์ํ, ํ/์ปค๋ฎค๋ํฐ ์ ๋ณด ๋ฑ read-heavy ์๋ํฌ์ธํธ์ HTTP ์บ์/Redis ์บ์๊ฐ ์๋ค.
- ํนํ ๋ญํน/MMR, ์ปค๋ฎค๋ํฐ ์ ๋ณด, ์์ฃผ ์กฐํ๋๋ ๊ณต์ง ๋ฑ์ TTL ๊ธฐ๋ฐ์ cache-aside ํจํด์ ์ฌ์ฉํ๋ฉด DB ๋ถํ๋ฅผ ์ค์ผ ์ ์์.
-
์บ์ ๋ฌดํจํ ์ ๋ต
- SMS/Chat ์ธ์ ์บ์๋ฅผ ์ฐ์ง ์์์ invalidation ์ค๊ณ ์์ฒด๊ฐ ์๋ ์ํ.
- ํฅํ ์บ์๋ฅผ ๋์ ํ ๊ฒฝ์ฐ, CRUD ์ด๋ฒคํธ๋ณ๋ก ์บ์ ํค๋ฅผ ์ด๋ป๊ฒ ๊ฐฑ์ /์ญ์ ํ ์ง ์ค๊ณ ํ์.
-
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 ์ญ์ ์ฌ๋ถ ๋ฑ์ด ๋ช ํํ ์ ๋ฆฌ๋์ด ์์ผ๋ฉด ์ข์.
- AccessToken ๋ง๋ฃ ํ RefreshToken ํ๋ฆ์ ๊ตฌํ๋์ด ์์ผ๋ (
-
์ธ์ฆ (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) ๋ฐ๊ธ.
- AccessToken:
-
์ธ๊ฐ (AuthZ / RBAC)
- ์ญํ ์์:
TEAM_ROLE,COMMUNITY_ROLE,BOARD_CATEGORY๋ฑ (src/constant/constantIndex.js). checkRole๊ณ์ด ๋ฏธ๋ค์จ์ด๋ก ์ญํ ์ ๋ฐ๋ฅธ ์ ๊ทผ ์ ์ด:checkIsTeamLeader,checkIsTeamSubLeader,checkIsCommunityAdminRole,checkIsCommunityStaffRole๋ฑ.checkHasTeamOrCommunity๋ก ๊ฒ์ํ ์นดํ ๊ณ ๋ฆฌ๋ณ ์์ ์ฌ๋ถ ๊ฒ์ฆ.
checkCondition์ ๋ค์ํ ์ฒดํฌ๋ก ์ ๋ฌด ๊ท์น ๊ธฐ๋ฐ ์ธ๊ฐ:- ๋งค์น ์ค๋๋ง ์์ /์ญ์ ๊ฐ๋ฅ(
checkIsMatchOwner), ์ข ๋ฃ๋์ง ์์ ๋งค์น์์๋ง ์ฐธ์ฌ ๊ฐ๋ฅ(checkMatchNotEnded), ์ฑํผ์ธ์ญ ๋งค์น์์๋ง ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ ์ ํ(checkIfChampionshipMatchOnly).
- ๋งค์น ์ค๋๋ง ์์ /์ญ์ ๊ฐ๋ฅ(
- ์ญํ ์์:
- RBAC๊ณผ ๋๋ฉ์ธ ๊ท์น์ด ๋ฏธ๋ค์จ์ด ์ฒด์ธ์ผ๋ก ์ฒด๊ณ์ ์ผ๋ก ๊ตฌํ๋์ด ์์ด, ํฌํธํด๋ฆฌ์ค์์ ๊ฐ์กฐํ๊ธฐ ์ข์ ๋ถ๋ถ์ด๋ค.
- ๋ค๋ง, ๊ถํ/์ํ ์ฒดํฌ๊ฐ ์ฝ๋ ์ฌ๋ฌ ๊ตฐ๋ฐ์ ์ฐ์ฌํด ์์ด, ๋๋ฉ์ธ ์๋น์ค๋ก ํ ๋ฒ ๋ ์ถ์ํํ๋ฉด ์ ์ง๋ณด์์ฑ์ด ํฌ๊ฒ ์ฌ๋ผ๊ฐ ๊ฒ.
- 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 ํํ๋ก ๋จ๊ฒจ ์ค์ ์์งํ๋ ํจํด์ ๋์ ํ๋ฉด ์ด์ ์ฑ์๋๊ฐ ์ฌ๋ผ๊ฐ๋ค.
- ๋๋ฉ์ธ ์ค์ฌ ๋ชจ๋ํ + ๋ฏธ๋ค์จ์ด ์ฒด์ธ ์ค๊ณ
- ๋๋ฉ์ธ๋ณ(router/service/sql) ๊ตฌ์กฐ์ ๊ณตํต ๋ฏธ๋ค์จ์ด(
checkInput,checkLogin,checkRole,checkCondition,s3Uploader๋ฑ)๋ฅผ ํตํด ๊ถํ/๊ฒ์ฆ/์กฐ๊ฑด ๋ก์ง์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ํ ๋ ์ด์ด๋ก ์ค๊ณํ ์ .
- ๋๋ฉ์ธ๋ณ(router/service/sql) ๊ตฌ์กฐ์ ๊ณตํต ๋ฏธ๋ค์จ์ด(
- ํํํ ๋ฐ์ดํฐ ๋ชจ๋ธ๋ง๊ณผ ๊ณ ๊ธ Postgres ๊ธฐ๋ฅ ํ์ฉ
- ๋ฉํฐ ์คํค๋ง ๋ถ๋ฆฌ, ENUM/์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ,
TSTZRANGE+EXCLUDE USING GIST๋ก ์ฐธ๊ฐ ์๊ฐ ์ค๋ณต์ DB ๋ ๋ฒจ์์ ์ฐจ๋จํ๋ ๋ฑ, ์ถ๊ตฌ ๋๋ฉ์ธ์ ๋ง๋ ๊ด๊ณํ ๋ชจ๋ธ๋ง์ ๊น์ด ์๊ฒ ์ํํ ์ .
- ๋ฉํฐ ์คํค๋ง ๋ถ๋ฆฌ, ENUM/์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ,
- ์ค์๋น์ค ์์ค์ ์ธ์ฆ/์ธ๊ฐ ํ๋ฆ ๊ตฌํ
- JWT Access/Refresh Token, ๋๋ฐ์ด์ค๋ณ RefreshToken ๊ด๋ฆฌ, Discord OAuth2 ๋ก๊ทธ์ธ, SMS ๊ธฐ๋ฐ 2์ฐจ ์ธ์ฆ ๋ฐ Redis๋ฅผ ํ์ฉํ rate limiting๊น์ง ์ฌ๋ฌ ์ธ์ฆ ์๋จ์ ์กฐํฉํ ํ์ค์ ์ธ Auth ํ๋ก์ฐ๋ฅผ ๊ตฌ์ถํ ์ .
- ์ค์๊ฐ ์ฑํ
์๋น์ค์ ๋ถ๋ฆฌ์ ์ํ ํ์ฅ ๊ณ ๋ ค
- Socket.IO + Redis Adapter๋ก ๋ณ๋ ์ฑํ ์๋ฒ๋ฅผ ๊ตฌ์ฑํ๊ณ , ํ ๋จ์ ๋ฃธ ๋ชจ๋ธ, ๋ฉ์์ง ์์ํ(Postgres)๋ฅผ ํตํด ์ค์๊ฐ ๊ธฐ๋ฅ์ ๋ฉ์ธ API์ ๋ถ๋ฆฌํ์ฌ ์ค๊ณํ ์ .
- ํ์ผ ์
๋ก๋ ๋ฐ ์ธ๋ถ ์คํ ๋ฆฌ์ง ์ฐ๋ ์ฑ
์ ๋ถ๋ฆฌ
multer+ AWS S3 + CDN ๋๋ฉ์ธ์ ์ด์ฉํ ์ ๋ก๋ ํ์ดํ๋ผ์ธ์s3Uploader*๋ฏธ๋ค์จ์ด๋ก ์บก์ํํ์ฌ, ๋๋ฉ์ธ ๋ก์ง๊ณผ ์ธํ๋ผ ๋ํ ์ผ์ ๋ถ๋ฆฌํ ์ .
-
์๋ํ๋ ํ ์คํธ ๋ฐ ํ์ง ๊ฒ์ดํธ ๋ถ์ฌ
- ํ์ฌ ํ๋ก์ ํธ์๋ ์ ๋/ํตํฉ/E2E ํ ์คํธ๊ฐ ์๊ณ , CI์์ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ํ์ธํ๋ ์ฒด๊ณ๋ ์๋ค.
- โ์ค์ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๊ธฐ๋ฅ ๊ตฌํ๊ณผ ์คํค๋ง ์ค๊ณ์ ์ง์คํ๊ณ , ๋ค์ ๋จ๊ณ๋ก๋ Jest๋ฅผ ํตํ ๋๋ฉ์ธ ๋จ์ ํ ์คํธ์ CI ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํด ํ์ง์ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ถ๋คโ๊ณ ์ค๋ช ํ๋ ๊ฒ์ด ์ข๋ค.
-
์ด์/๊ด์ธก(Observability) ๋ฐ ์ผ๋ถ ์ธํ๋ผ ์ค์ ์ ๋ฏธ์ฑ์
- ๋ก๊น
์ด
console.log์์ค์ด๊ณ , Dockerfile์ CMD๊ฐ dev ์ค์ฌ(tail -f /dev/null)์ธ ์ , CORS ์ค์ ์ด ํ๋์ฝ๋ฉ/์ฃผ์ ์ํ์ธ ์ ๋ฑ. - โํ์ฌ๋ ์๊ท๋ชจ ๊ฐ์ธ/ํ ํ๋ก์ ํธ ์์ค์ ์ด์์ด์๊ณ , ๋ค์์๋ ๊ตฌ์กฐํ ๋ก๊ทธ, ๋ชจ๋ํฐ๋ง, ํฌ์ค์ฒดํฌ, CI/CD๋ฅผ ํฌํจํ ํ๋ก๋์ ์ด์ ๊ด์ ์์ ์ค๊ณ๋ฅผ ๋ ๋ณด์ํ๊ฒ ๋คโ๊ณ ์ ๋ฆฌํ๋ฉด ์ข๋ค.
- ๋ก๊น
์ด
์ถ์ฒ ์ค๋ํซ: 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 ํ์ฉ ๋ฅ๋ ฅ์ ๊ฐํ๊ฒ ์ดํํ ์ ์๋ค.