Skip to content

FootballManagementMSA/Football_Club_Manager

Repository files navigation

โœจ FootBall-Friends ํ’‹๋ณผํ”„๋ Œ์ฆˆ

image image image image

๐Ÿ‘‰๐Ÿป ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

๊ธฐ์กด Monolithicํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ, ์ข…์†์ ์ธ ์„œ๋น„์Šค ๊ณ„์ธต๊ณผ ํ˜‘์—… ๊ณผ์ •์—์„œ Branch๊ฐ€ ์—‰ํ‚ค๋Š” ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋…๋ฆฝ์ ์ธ ์•„ํ‚คํ…์ฒ˜ ํ™˜๊ฒฝ์— ๋Œ€ํ•œ ๊ด€์‹ฌ์œผ๋กœ Microservice Architecture ํ™˜๊ฒฝ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • OO๋Œ€ํ•™๊ต ์žฌํ•™์ƒ ๋Œ€์ƒ ์ถ•๊ตฌ ๋™์•„๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ ๐Ÿšฒ
  • ๋™์•„๋ฆฌ๊ฐ„ ๊ฒฝ๊ธฐ ์ผ์ •์„ ์žก๊ณ  FIFA์ฒ˜๋Ÿผ ์Šค์ฟผ๋“œ๋ฅผ ์งœ๊ณ  ์‹ถ์„ ๋•Œ ๐Ÿ”Œ
  • ๋™์•„๋ฆฌ ์ผ์ • ๊ด€๋ฆฌ๋ฅผ ์ˆ˜์›”ํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ๐Ÿ‘พ

โœจ ์ถ•๊ตฌ ๋™์•„๋ฆฌ ๊ด€๋ฆฌ ์„œ๋น„์Šค, ํ’‹๋ณผํ”„๋ Œ์ฆˆ ์ž…๋‹ˆ๋‹ค! ๐Ÿฅณ

๐Ÿ›  ํ”„๋กœ์ ํŠธ ์•„ํ‚คํ…์ณ

image


โš™ ๊ธฐ์ˆ  ์Šคํƒ

โœ” Frond-end

โœ” Back-end

โœ” Infra

โœ” DevOps

โœ” Monitoring



๐Ÿ’กย ์ฃผ์š” ๊ธฐ๋Šฅ

  1. ๋ฉ”์ธ ํ™ˆ(๋ณธ์ธ ์ผ์ • ๋ฐ ์ •๋ณด ํ™•์ธ) โ™พ
  2. ๊ตฌ๋‹จ ๊ฐ€์ž… ๋ฐ ๊ฒ€์ƒ‰ ๐Ÿ†™
  3. ๊ตฌ๋‹จ์žฅ์˜ ๊ฐ€์ž… ์‹ ์ฒญ(Role ๋ถ€์—ฌ) ๐Ÿ’ฌ
  4. ์ผ์ • ์ƒ์„ฑ ๋ฐ ์กฐํšŒ ๐Ÿ”
  5. ์Šค์ฟผ๋“œ ์ƒ์„ฑ ๋ฐ ์กฐํšŒ ๐Ÿ—“
  6. ํšŒ์› ์ •๋ณด ์กฐํšŒ ๐Ÿšฆ

๋ฉ”์ธ ํ™ˆ(๋ณธ์ธ ์ผ์ • ๋ฐ ์ •๋ณด ํ™•์ธ) ๊ตฌ๋‹จ ๊ฐ€์ž… ๋ฐ ๊ฒ€์ƒ‰ ๊ตฌ๋‹จ์žฅ์˜ ๊ฐ€์ž… ์‹ ์ฒญ(Role ๋ถ€์—ฌ)
์ผ์ • ์ƒ์„ฑ ๋ฐ ์กฐํšŒ ์Šค์ฟผ๋“œ ์ƒ์„ฑ ๋ฐ ์กฐํšŒ ํšŒ์› ์ •๋ณด ์กฐํšŒ

๐Ÿ”† ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

Jwt Token Storage (Cookie? Session? Redis? Memcached?)

Refresh Token์ด๋ž€?

Access Token์˜ ์œ ํšจ๊ธฐ๊ฐ„์„ ์งง๊ฒŒํ•˜์—ฌ ๋ณด์•ˆ๋„ ๋†’์ด๊ณ , ํŽธ์˜์„ฑ๋„ ์ฑ™๊ธฐ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ๋กœ๊ทธ์ธ์„ ์™„๋ฃŒํ•˜๋ฉด, ์œ ํšจ๊ธฐ๊ฐ„์ด ์งง์€ Access Token๊ณผ ์œ ํšจ๊ธฐ๊ฐ„์ด ๊ธด Refresh Token์„ ๋ฐœ๊ธ‰ํ•ด์ค€๋‹ค.

Access Token์€ ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ JWT ํ† ํฐ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋˜๊ณ , Refresh Token์€ Access Token์ด ๋งŒ๋ฃŒ๋˜์—ˆ์„ ๋•Œ, ์ƒˆ๋กœ ๋ฐœ๊ธ‰ํ•ด์ฃผ๋Š” ํ† ํฐ์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

Refresh Token์˜ ํ•„์š”์„ฑ

Access Token ๋งŒ๋ฃŒ์‹œ๊ฐ„์„ ์งง๊ฒŒ ํ•˜๋ฉด ๋ณด์•ˆ์„ฑ์€ ์ข‹์•„์ง‘๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, Access Token์˜ ๋งŒ๋ฃŒ์‹œ๊ฐ„์„ ์งง๊ฒŒ ๊ฐ€์ ธ๊ฐ€๋ฉด ์‚ฌ์ดํŠธ๋ฅผ ์ด์šฉํ•˜๋Š” ํšŒ์›์€ ์ž์ฃผ ๋กœ๊ทธ์ธ ํ•ด์•ผ๋˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, Refresh Token์„ ์ด์šฉํ•˜์—ฌ Access Token์„ ์žฌ๋ฐœ๊ธ‰ํ•  ์ˆ˜ ์žˆ๊ณ  Access Token์˜ ์œ ํšจ ๊ธฐ๊ฐ„์„ ์งง๊ณ  ์ž์ฃผ ์žฌ๋ฐœ๊ธ‰ ํ•˜๋„๋ก ๋งŒ๋“ค์–ด ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๋ฉด์„œ ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์•„์›ƒ ๋˜์–ด ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ๋˜๋Š” ์ƒํ™ฉ์„ ์ฃผ์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

Refresh Token์„ ์–ด๋””์— ์ €์žฅํ•ด์•ผ ํ• ๊นŒ?

Refresh Token์€ Access Token์„ ์žฌ๋ฐœ๊ธ‰ํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„์ž…๋‹ˆ๋‹ค.

Refresh Token์„ ์ฟ ํ‚ค์— ์ €์žฅํ•˜๋ฉด ์˜คํžˆ๋ ค ๋ณด์•ˆ์„ฑ๋งŒ ๋–จ์–ด๋œจ๋ฆฌ๋Š” ํ–‰์œ„๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ฟ ํ‚ค๋Š” CSRF ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜๋‹ค๋Š” ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ข‹์ง€ ์•Š์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ์Šต๋‹ˆ๋‹ค.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Refresh Token์„ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•˜๋Š” ๊ฒƒ๋„ XSS ๊ณต๊ฒฉ์˜ ์ทจ์•ฝ์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ Refresh Token์„ Redis์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š”

  1. Key - Value ๋ฐฉ์‹, ์ธ๋ฉ”๋ชจ๋ฆฌ DB ๋ฐฉ์‹์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ๋ธŒ๋ผ์šฐ์ €์— ๋น„ํ•ด ํƒˆ์ทจ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” redis ์„œ๋ฒ„์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
  3. Refresh Token์€ ์˜๊ตฌ์ ์œผ๋กœ ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.

Redis(In-Memory DB) VS Memcached

๋ ˆ๋””์Šค๋Š” key-value ์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€์ž…๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์—(๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ์ธ RAM) ์ €์žฅํ•˜๊ณ  ์กฐํšŒํ•˜๋Š” in-memory ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ž…๋‹ˆ๋‹ค.

Memcached ๋ผ๋Š” ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ์Šคํ† ๋ฆฌ์ง€๋„ ์žˆ์ง€๋งŒ, ์„ฑ๋Šฅ์ฐจ์ด๊ฐ€ ํฌ๊ฒŒ ์—†๊ณ , Memcached๋Š” ๋ฌธ์ž์—ด๋งŒ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Redis๋ฅผ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

2. Gateway-Server JwtToken (Pre)Filter ์ ์šฉ

MSA ํ™˜๊ฒฝ์—์„œ JwtTokenFilter๋ฅผ ์ ์šฉํ•˜๋Š” ๊ณผ์ •์€ Gateway-Server์—์„œ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—์„œ๋Š” **Filter๋ฅผ ์ ์šฉํ•˜์—ฌ ๋ชจ๋“  ์š”์ฒญ์ด ์œ ํšจํ•œ JWT ํ† ํฐ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ๊ฒ€์ฆ**ํ•ฉ๋‹ˆ๋‹ค. ๊ฒ€์ฆ์„ ํ†ต๊ณผํ•œ ์š”์ฒญ๋งŒ์ด ๋‚ด๋ถ€ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌ๋˜๋ฉฐ, ์ด๋Š” ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๊ณ  ์„œ๋น„์Šค ๊ฐ„์˜ ์•ˆ์ „ํ•œ ํ†ต์‹ ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, Gateway-Se! rver๋Š” ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ๋„ ๋‹ด๋‹นํ•˜์—ฌ, ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค์— ๊ท ๋“ฑํ•˜๊ฒŒ ๋ถ„๋ฐฐํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ณผ์ •์„ ํ†ตํ•ด ์‹œ์Šคํ…œ์˜ ์•ˆ์ •์„ฑ๊ณผ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์„ ๋†’์ด๋ฉฐ, MSA ํ™˜๊ฒฝ์—์„œ์˜ ์„œ๋น„์Šค ์šด์˜์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค.



์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด, ๋งคํ•‘์„ ํ†ตํ•ด ํ”„๋ ˆ๋””์ผ€์ดํŠธ์—์„œ ํ•ด๋‹น ์š”์ฒญ์ด ์ฒ˜๋ฆฌ๋  ์กฐ๊ฑด์„ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„, ์ž‘์—… ์‹คํ–‰ ์ „์— **์‚ฌ์ „ ํ•„ํ„ฐ(Pre Filter)๋ฅผ ํ†ต๊ณผ**ํ•ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” ์š”์ฒญ์— ๋Œ€ํ•œ ์ดˆ๊ธฐ ์ฒ˜๋ฆฌ๋‚˜ ๊ฒ€์ฆ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์กฐ๊ฑด์— ๋ถ€ํ•ฉํ•˜๋Š” ์„œ๋น„์Šค๊ฐ€ ์‹คํ–‰๋˜์–ด, ์š”์ฒญ์— ๋Œ€ํ•œ ์‹ค์ œ ๋กœ์ง์ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ์ž‘์—…์ด ์ข…๋ฃŒ๋œ ํ›„์—๋Š” ํ›„์† ํ•„ํ„ฐ(Post Filter)๋ฅผ ํ†ต๊ณผํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋Š” ์‘๋‹ต์„ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ณด๋‚ด๊ธฐ ์ „์— ํ•„์š”ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•„ํ„ฐ๋Š” ํ”„๋กœํผํ‹ฐ ํŒŒ์ผ์ด๋‚˜ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ •์˜ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์š”์ฒญ๊ณผ ์‘๋‹ต์˜ ํ๋ฆ„์„ ์œ ์—ฐํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ, ์ฒ˜๋ฆฌ๋œ ์‘๋‹ต์€ ๋งคํ•‘์„ ๊ฑฐ์ณ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์„ ํ†ตํ•ด, Spring Cloud Gateway๋Š” ๋‹ค์–‘ํ•œ ์š”์ฒญ์— ๋Œ€ํ•ด ์กฐ๊ฑด๋ถ€ ๋กœ์ง ์‹คํ–‰, ์‚ฌ์ „ ๋ฐ ์‚ฌํ›„ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•œ ์„ธ๋ฐ€ํ•œ ์š”์ฒญ/์‘๋‹ต ๊ด€๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
3. Update ๋กœ์ง ์ˆ˜์ •(JPQL to JPA Dirty Checking)

๋ฌธ์ œ ์ƒํ™ฉ: ํšŒ์› ์ •๋ณด ์ˆ˜์ • ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ๋•Œ @Modifying ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•˜์—ฌ Update ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์ˆ˜์ •ํ•˜๋„๋ก Repository์—์„œ ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„

JPA๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋”ํ‹ฐ ์ฒดํ‚น(Dirty Checking)์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ JPA์Šค๋Ÿฌ์šด ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋”ํ‹ฐ ์ฒดํ‚น์€ ์—”ํ„ฐํ‹ฐ์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ด๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜ํ•˜๋Š” JPA์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์€ ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๋Š” ์‹œ์ ์— ์‹คํ–‰๋˜๋ฉฐ, ๋ณ€๊ฒฝ๋œ ์—”ํ„ฐํ‹ฐ์˜ ์Šค๋ƒ…์ƒท๊ณผ ์›๋ณธ ์—”ํ„ฐํ‹ฐ๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ž๋™์œผ๋กœ UPDATE ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋”ํ‹ฐ ์ฒดํ‚น์„ ์ด์šฉํ•˜๋ฉด, ๊ฐœ๋ฐœ์ž๋Š” ์—”ํ„ฐํ‹ฐ์˜ ์ƒํƒœ๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜๊ณ  ์ ์ ˆํ•œ ์‹œ์ ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜ํ•  ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ์ค„์ด๊ณ , ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ๋‚ฎ์ถ”๋ฉฐ, ๊ฐœ๋ฐœ์ž๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋” ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ๋˜ํ•œ, ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ์— ์Œ“์ธ ์ฟผ๋ฆฌ๋“ค์ด ์ผ๊ด„์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ „์†ก๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ ์ธก๋ฉด์—์„œ๋„ ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Modifying ๋ฐฉ์‹

Spring Data JPA์—์„œ๋Š” @Query ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ์ •์˜ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์„ ์ˆ˜๋ฐ˜ํ•˜๋Š” INSERT, DELETE, UPDATE ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋Š” @Modifying ์• ๋…ธํ…Œ์ด์…˜์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด JPA์˜ ๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ธฐ๋Šฅ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ , ์ฟผ๋ฆฌ ์‹คํ–‰์„ ๋” ํšจ์œจ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@Transactional
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);

์—ฌ๊ธฐ์„œ @Transactional์€ ํ•ด๋‹น ๋ฉ”์„œ๋“œ์˜ ์‹คํ–‰์„ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, @Modifying์€ ๋ณ€๊ฒฝ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๊ฒƒ์ž„์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. @Query๋Š” ์‹คํ–‰ํ•  JPQL ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ , @Param์€ ์ฟผ๋ฆฌ์— ์ „๋‹ฌ๋  ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ, JPA์—์„œ๋Š” ๋ฒŒํฌ ์—ฐ์‚ฐ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋ฒŒํฌ ์—ฐ์‚ฐ์ด๋ž€, ๋‹จ์ผ ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ UPDATE, DELETE ์ž‘์—…์„ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์„ฑ๋Šฅ ๊ฐœ์„ ์—๋„ ํฌ๊ฒŒ ๊ธฐ์—ฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“  ์‚ฌ์šฉ์ž์˜ ๋‚˜์ด๋ฅผ ํ•œ ์‚ด์”ฉ ์ฆ๊ฐ€์‹œํ‚ค๊ณ ์ž ํ•  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@Transactional
@Modifying
@Query("UPDATE User u SET u.age = u.age + 1")
int incrementAllUserAges();

์ด ์ฝ”๋“œ๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž์˜ ๋‚˜์ด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ•œ ๋ฒˆ์— ์—…๋ฐ์ดํŠธํ•˜๊ณ , ๋ณ€๊ฒฝ๋œ ํ–‰์˜ ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. @Transactional๊ณผ @Modifying์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, JPA๋ฅผ ํ†ตํ•ด ํšจ์œจ์ ์œผ๋กœ ๋ฒŒํฌ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ **๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ž‘์—…์„ ๋Œ€๊ทœ๋ชจ๋กœ ์ง„ํ–‰ํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉ**ํ•˜๋ฉฐ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”์—๋„ ํฌ๊ฒŒ ๊ธฐ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
4. SpringBoot + Kafka ์—ฐ๋™

Kafka๋ž€

์นดํ”„์นด(Kafka)๋Š” ์›น์‚ฌ์ดํŠธ, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜, ์„ผ์„œ ๋“ฑ์—์„œ ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ๋ถ„์‚ฐ ์ŠคํŠธ๋ฆฌ๋ฐ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ์ด ํ”Œ๋žซํผ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์ด์—์„œ ์ค‘์žฌ์ž ์—ญํ• ์„ ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ์˜ ์ „์†ก, ์ฒ˜๋ฆฌ, ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ์นดํ”„์นด ์‹œ์Šคํ…œ์€ ์—ฌ๋Ÿฌ ์š”์†Œ(๋…ธ๋“œ)๋กœ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ '์นดํ”„์นด ํด๋Ÿฌ์Šคํ„ฐ'๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

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

Kafka์˜ ๊ธฐ๋ณธ ๊ตฌ์„ฑ ์š”์†Œ

โ€‹image โ–ถ Cluster : ์—ฌ๋Ÿฌ ๋Œ€์˜ ์ปดํ“จํ„ฐ๋“ค์ด ์—ฐ๊ฒฐ๋˜์–ด ํ•˜๋‚˜์˜ ์‹œ์Šคํ…œ์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ์ปดํ“จํ„ฐ๋“ค์˜ ์ง‘ํ•ฉ
โ–ถ Producer : ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด์–ด ์ „๋‹ฌํ•˜๋Š” ์ „๋‹ฌ์ž์˜ ์—ญํ• 
โ–ถ Consumer : ํ”„๋กœ๋“€์„œ์—์„œ ์ „๋‹ฌํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ธŒ๋กœ์ปค์— ์š”์ฒญํ•˜์—ฌ ๋ฉ”์‹œ์ง€(๋ฐ์ดํ„ฐ)๋ฅผ ์†Œ๋น„ํ•˜๋Š” ์—ญํ• 
โ–ถ Broker : ์ƒ์‚ฐ์ž์™€ ์†Œ๋น„์ž์™€์˜ ์ค‘์žฌ์ž ์—ญํ• ์„ ํ•˜๋Š” ์—ญํ• 
โ–ถ Topic : ๋ณด๋‚ด๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•œ ์นดํ…Œ๊ณ ๋ฆฌํ™”

์นดํ”„์นด(Kafka)๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ listener๋ฅผ ํ†ตํ•ด producer๋กœ๋ถ€ํ„ฐ์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด ์‹œ์Šคํ…œ์—์„œ 'KafkaServer'๋Š” broker ์—ญํ• ์„ ํ•˜๋ฉฐ, producer์™€ consumer๋Š” ์นดํ”„์นด๊ฐ€ ์ œ๊ณตํ•˜๋Š” API๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„๋œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์นดํ”„์นด ํด๋Ÿฌ์Šคํ„ฐ๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ broker๋กœ ๊ตฌ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด์˜ ๊ฐ KafkaServer(broker)๋Š” ๊ณ ์œ ํ•œ ์‹๋ณ„์ž์ธ 'broker.id'๋ฅผ ๋ถ€์—ฌ๋ฐ›์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ด๋Ÿฌํ•œ broker๋Š” producer๋กœ๋ถ€ํ„ฐ ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅํ•  ์œ„์น˜ ์ •๋ณด์™€ ํด๋Ÿฌ์Šคํ„ฐ์˜ ๋ฉ”ํƒ€์ •๋ณด๋ฅผ ์ €์žฅ ๋ฐ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Zookeeper์™€ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.

Kafka Cluster๋Š” ์—ฌ๋Ÿฌ ๋ธŒ๋กœ์ปค๋“ค์˜ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ํšจ๊ณผ์ ์ธ ๋ฆฌ๋” ์„ ์ถœ(leader election)์„ ์œ„ํ•ด Zookeeper๋ฅผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • broker์— ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ, ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋ณ€๊ฒฝ๋œ ๋ฆฌ๋” ํŒŒํ‹ฐ์…˜์˜ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์ „์— ๊ทธ ์ •๋ณด๋ฅผ Zookeeper์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์šด์˜๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ์นดํ”„์นด ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ๋†’์€ ๊ฐ€์šฉ์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

Topic

์นดํ”„์นด(Kafka)์—์„œ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์€ 'ํ† ํ”ฝ(Topic)'์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์นดํ”„์นด์˜ ํ† ํ”ฝ์€ ๊ตฌ์ฒดํ™”๋œ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ์˜๋ฏธํ•˜๋ฉฐ, ์—ฐ๊ด€๋œ ์ด๋ฒคํŠธ๋“ค์„ ๋ฌถ์–ด ์˜์†ํ™”ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์ด๋‚˜ ํŒŒ์ผ ์‹œ์Šคํ…œ์˜ ํด๋”์— ๋น„์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ† ํ”ฝ์€ ์นดํ”„์นด์—์„œ ์ƒ์‚ฐ์ž(Producer)์™€ ์†Œ๋น„์ž(Consumer)๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ์ค‘์š”ํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. Producer๋Š” ์นดํ”„์นด์˜ ํ† ํ”ฝ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ €์žฅ(push)ํ•˜๊ณ , Consumer๋Š” ์ €์žฅ๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์–ด(pull)์˜ต๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ† ํ”ฝ์—๋Š” ์—ฌ๋Ÿฌ Producer์™€ Consumer๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฐœ๋…์€ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜๋ฉด, ๊ด€๋ จ๋œ ์ด๋ฒคํŠธ๋“ค์ด ๋ชจ์—ฌ ์ŠคํŠธ๋ฆผ์„ ํ˜•์„ฑํ•˜๊ณ , ์ด ์ŠคํŠธ๋ฆผ์ด ์นดํ”„์นด์— ์ €์žฅ๋  ๋•Œ ํ† ํ”ฝ์˜ ์ด๋ฆ„์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ณผ์ •์„ ํ†ตํ•ด ์นดํ”„์นด๋Š” ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
image

Partition

์œ„์—์„œ ์„ค๋ช…ํ•œ ์นดํ”„์นด์˜ ํ† ํ”ฝ๋“ค์€ ์—ฌ๋Ÿฌ ํŒŒํ‹ฐ์…˜์œผ๋กœ ๋‚˜๋ˆ ์ง‘๋‹ˆ๋‹ค. ํ† ํ”ฝ์ด ์นดํ”„์นด์—์„œ ์ผ์ข…์˜ ๋…ผ๋ฆฌ์ ์ธ ๊ฐœ๋…์ด๋ผ๋ฉด, ํŒŒํ‹ฐ์…˜์€ ํ† ํ”ฝ์— ์†ํ•œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์‹ค์ œ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๋Š” ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ๊ฐ๊ฐ์˜ ํŒŒํ‹ฐ์…˜์€ Append-Only ๋ฐฉ์‹์œผ๋กœ ๊ธฐ๋ก๋˜๋Š” ํ•˜๋‚˜์˜ ๋กœ๊ทธ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

image

ํšŒ์› ํƒˆํ‡ด API ๊ตฌํ˜„

ํšŒ์›ํƒˆํ‡ด API ๊ฐ™์€ ๊ฒฝ์šฐ, User-Server์—์„œ ํšŒ์›์„ ํƒˆํ‡ดํ•˜๋ฉด Team-Server์— ์กด์žฌํ•˜๋Š” UserSquad Table์—์„œ ์‚ญ์ œํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ์ด ์ƒํ™ฉ์—์„œ Kafka๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํšŒ์› ํƒˆํ‡ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ, User-Server๊ฐ€ Kafka์˜ Producer ์—ญํ• ์„ ํ•˜๊ณ , Team-Server๊ฐ€ Consumer ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

USER-SERVER

  1. Kafka Config (USER-SERVER)
@Bean
public Map<String, Object> UserProducerConfig() {
    return CommonJsonSerializer.getStringObjectMap(BOOTSTRAP_SERVERS_CONFIG);
}

@Bean
public ProducerFactory<String, Long> deleteUserProducerFactory() {
    return new DefaultKafkaProducerFactory<>(UserProducerConfig());
}

@Bean
public KafkaTemplate<String, Long> deleteUserKafkaTemplate(){
    return new KafkaTemplate<>(deleteUserProducerFactory());
}
  1. UserKafkaProducer
@Component
@RequiredArgsConstructor
public class UserKafkaProducer {
    private final KafkaTemplate<String, Long> deleteUserKafkaTemplate;

    public void deleteUser(Long userId) {
        deleteUserKafkaTemplate.send("user", userId);
    }

  1. USER-SERVICE
@Transactional
public void deleteUser(String studentId) {
    User user = userRepository.findByStudentId(studentId)
            .orElseThrow(() -> new NotFoundException(NOT_FOUND_STATUS_CODE, NOT_REGISTER_USER_EXCEPTION_MESSAGE));
    userRepository.delete(user);
    userKafkaProducer.deleteUser(user.getId());
}

TEAM-SERVER

  1. Kafka Config
@Bean
public Map<String, Object> ConsumerConfigs() {
    return CommonJsonDeserializer.getStringObjectMap(BOOTSTRAP_SERVERS_CONFIG, GROUP_ID_CONFIG);
}

@Bean
public ConsumerFactory<String, Long> deleteUserSquadConsumerFactory() {
    return new DefaultKafkaConsumerFactory<>(ConsumerConfigs());
}

@Bean
public ConcurrentKafkaListenerContainerFactory<String, Long> deleteUserSquadListener() {
    ConcurrentKafkaListenerContainerFactory<String, Long> factory =
            new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(deleteUserSquadConsumerFactory());
    return factory;
}

  1. TEAM-SERVICE
@Transactional
@KafkaListener(topics = "user", groupId = "group_2")
public void deleteUserSqaud(Long userId) {
    userSquadRepository.deleteAllByUserIds(userId);
}


๐Ÿ‘ป ํ’‹๋ณผํ”„๋ Œ์ฆˆ ํŒ€์›๋“ค!

Role Name Github
BE ๋ฐ•์ข…ํ›ˆ https://github.com/euics
BE ๋ณ€์€์„ฑ https://github.com/bes99
BE ์ด์žฌํ‘œ https://github.com/jaepyo-Lee
Android ์ž„์„ฑ์šฐ https://github.com/imseongwoo
Android ์ด์œคํ˜ธ https://github.com/lyh990517
Android ๊น€์ฐฌํœ˜ https://github.com/1chanhue1
Design ๋ฐ•์žฌ์›

About

Matching schedule management app for college soccer clubs

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages