๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Project ESG+AI/Tech Basics

33์ผ์ฐจ. IT ๊ฐœ๋… ์ •๋ฆฌ

by GreenJin_S2 2025. 11. 24.

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด๋ž€?

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ(stateless programming)์€ ํ”„๋กœ๊ทธ๋žจ์ด ์–ด๋–ค ๊ฐ’์„ ๊ณ„์‚ฐํ•  ๋•Œ ์ด์ „ ์ƒํƒœ(state)์— ์˜์กดํ•˜์ง€ ์•Š๊ณ , ์˜ค์ง ํ˜„์žฌ ์ž…๋ ฅ๊ฐ’๋งŒ์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ์‹์„ ๋งํ•ฉ๋‹ˆ๋‹ค.
์‰ฝ๊ฒŒ ๋งํ•ด, **“์–ด์ œ ๋ญ ํ–ˆ๋Š”์ง€ ๋ชฐ๋ผ๋„, ์˜ค๋Š˜ ๋“ค์–ด์˜จ ์ •๋ณด๋งŒ ๋ณด๊ณ  ํŒ๋‹จํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ”**์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


์™œ ‘๋ฌด์ƒํƒœ’๋ผ๊ณ  ํ• ๊นŒ?

์ƒํƒœ(state)๋ž€ ํ”„๋กœ๊ทธ๋žจ์ด ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋Š” ์ •๋ณด์ž…๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด:

  • ๋กœ๊ทธ์ธ ์—ฌ๋ถ€
  • ์„ธ์…˜ ์ •๋ณด
  • ์ด์ „์— ๊ณ„์‚ฐํ•œ ๊ฐ’
  • ์นด์šดํ„ฐ(1 ์ฆ๊ฐ€, 2 ์ฆ๊ฐ€ ๊ฐ™์€ ๊ฐ’)

์ด๋Ÿฐ ๋ฐ์ดํ„ฐ๋ฅผ ํ”„๋กœ๊ทธ๋žจ ๋‚ด๋ถ€์—์„œ ๊ณ„์† ๋“ค๊ณ  ์žˆ์œผ๋ฉด **์ƒํƒœ๊ฐ€ ์žˆ๋‹ค(stateful)**๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜๋Œ€๋กœ, ํ•จ์ˆ˜๋‚˜ ํ”„๋กœ๊ทธ๋žจ์ด ์ž…๋ ฅ๋งŒ ๋ณด๊ณ  ๋งค๋ฒˆ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋Š” ๊ฒฝ์šฐ๋ฅผ **๋ฌด์ƒํƒœ(stateless)**๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.


ํ•œ๊ตญ์‹ ๋น„์œ ๋กœ ์‰ฝ๊ฒŒ ์„ค๋ช…

๐Ÿœ ๋ผ๋ฉด ๊ฐ€๊ฒŒ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹ ๋น„๊ต

  1. ์ƒํƒœ ์žˆ๋Š” ๊ฐ€๊ฒŒ(Stateful)
    • ๋‹จ๊ณจ ์†๋‹˜์„ ๊ธฐ์–ตํ•จ
    • “์–ด์ œ์ฒ˜๋Ÿผ ๋งค์šด๋ง›์œผ๋กœ ๋“œ๋ฆด๊ฒŒ์š”?”
      → ์ด์ „ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ–‰๋™
  2. ๋ฌด์ƒํƒœ ๊ฐ€๊ฒŒ(Stateless)
    • ๋งค๋ฒˆ ์ฃผ๋ฌธ์„œ๋งŒ ๋ณด๊ณ  ์กฐ๋ฆฌ
    • ์†๋‹˜์ด ๋ˆ„๊ตฌ์ธ์ง€, ๋ญ˜ ๋จน์—ˆ๋Š”์ง€ ๊ธฐ์–ตํ•˜์ง€ ์•Š์Œ
      → ์ž…๋ ฅ(์ฃผ๋ฌธ์„œ)๋งŒ ๋ณด๊ณ  ์š”๋ฆฌํ•จ

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ 2๋ฒˆ์ฒ˜๋Ÿผ ํ•ญ์ƒ ์ž…๋ ฅ๋งŒ ๋ณด๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“œ๋Š” ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค.


์–ด๋””์— ๋งŽ์ด ์“ฐ์ผ๊นŒ?

### 1) ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ(Haskell, Scala ๋“ฑ)

  • ๋ถ€์ž‘์šฉ(side-effect)์„ ์ตœ์†Œํ™”
  • ๊ฐ™์€ ์ž…๋ ฅ์ด๋ฉด ํ•ญ์ƒ ๊ฐ™์€ ์ถœ๋ ฅ → ํ…Œ์ŠคํŠธ·๋””๋ฒ„๊น…์ด ์‰ฌ์›€

2) REST API

  • “์„œ๋ฒ„๋Š” ์š”์ฒญ ๊ฐ„์˜ ์ •๋ณด๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ์•Š๋Š”๋‹ค”๊ฐ€ ํ•ต์‹ฌ ์›์น™
  • ๋”ฐ๋ผ์„œ ๊ฐ ์š”์ฒญ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์‹ค์–ด ๋ณด๋‚ด์•ผ ํ•จ

3) ์„œ๋ฒ„ ํ™•์žฅ์„ฑ(์Šค์ผ€์ผ๋ง)์— ์œ ๋ฆฌ

  • ์ƒํƒœ๋ฅผ ๋“ค๊ณ  ์žˆ์ง€ ์•Š์œผ๋‹ˆ ์–ด๋–ค ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ด๋„ OK
  • ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ ํŠนํžˆ ์ค‘์š”

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์žฅ๋‹จ์ 

์žฅ์ 

  • ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ (๊ฐ™์€ ์ž…๋ ฅ → ๊ฐ™์€ ๊ฒฐ๊ณผ)
  • ํ…Œ์ŠคํŠธ๊ฐ€ ์‰ฌ์›€
  • ์œ ์ง€๋ณด์ˆ˜, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ํฐ ์ด์ 
  • ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ์—์„œ ์ถฉ๋Œ์ด ์ ์Œ

๋‹จ์ 

  • ์ƒํƒœ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ(๋กœ๊ทธ์ธ, ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋“ฑ)์— ๊ตฌํ˜„์ด ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Œ
  • ์™ธ๋ถ€ ์ €์žฅ์†Œ(์ฟ ํ‚ค, DB, ์บ์‹œ ๋“ฑ)์— ์ƒํƒœ๋ฅผ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•จ

ํ•œ ์ค„ ์š”์•ฝ

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ‘๊ธฐ์–ตํ•˜์ง€ ์•Š๋Š”’ ๋ฐฉ์‹์œผ๋กœ, ์ž…๋ ฅ๊ฐ’๋งŒ ๋ณด๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ํŒจ๋Ÿฌ๋‹ค์ž„์ž…๋‹ˆ๋‹ค.

 


๋„ฅ์ŠคํŠธ 15๋ฒ„์ „, ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด, ์Šคํ”„๋ง ๋ถ€ํŠธ msa , ๋ ˆ๋””์Šค, ํฌ์ŠคํŠธ๊ทธ๋ฆฌ์—์„œ ๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์„ ์ดˆ๋ณด์ž๋„ ์•Œ๊ธฐ ์‰ฝ๊ฒŒ ์•Œ๋ ค์ค˜.
 

๋„ฅ์ŠคํŠธ 15, ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด, ์Šคํ”„๋ง ๋ถ€ํŠธ MSA, ๋ ˆ๋””์Šค, ํฌ์ŠคํŠธ๊ทธ๋ฆฌ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ „์ฒด ํ๋ฆ„

์•„๋ž˜ ์„ค๋ช…์€ ์™„์ „ ์ดˆ๋ณด์ž๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก,
ํ•œ๊ตญ์‹ ๋น„์œ  + ๋‹จ๊ณ„๋ณ„ ๊ตฌ์กฐ + ์‹ค์ œ ๊ตฌํ˜„ ํ๋ฆ„์œผ๋กœ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”.


๐Ÿ“Œ ์ „์ฒด ์ฝ˜์…‰ํŠธ: “์„œ๋ฒ„๋Š” ์•„๋ฌด๊ฒƒ๋„ ๊ธฐ์–ตํ•˜์ง€ ์•Š๋Š”๋‹ค”

๋ฌด์ƒํƒœ(stateless) ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋ชจ๋‘ ์š”์ฒญ(Request)์— ๋“ค์–ด์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์„œ๋ฒ„๋Š” “์•„, ์ด ์œ ์ € ์•„๊นŒ ๋กœ๊ทธ์ธํ–ˆ์—ˆ์ง€?” ๊ฐ™์€ ๊ฑธ ๊ธฐ์–ตํ•˜์ง€ ์•Š์•„์š”.

๋Œ€์‹ , ์ธ์ฆ ์ •๋ณด · ์„ธ์…˜ ์ •๋ณด · ์žฅ๋ฐ”๊ตฌ๋‹ˆ · ํ† ํฐ ๊ฒ€์ฆ ๋“ฑ ํ•„์š”ํ•œ ๊ฒƒ์€
๐Ÿ‘‰ ํ† ํฐ(JWT), Redis, DB(PostgreSQL) ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ “์™ธ๋ถ€์— ๋ณด๊ด€”ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงฑ ์ „์ฒด ๊ตฌ์กฐ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ

 
Next.js(ํ”„๋ก ํŠธ) ↓ JWT ์ „์†ก Spring Gateway(API Gateway) ↓ JWT ๊ฒ€์ฆ or Redis ์กฐํšŒ Spring Boot MSA๋“ค(ํšŒ์›/์ฃผ๋ฌธ/์ƒํ’ˆ ๋“ฑ) ↓ ์š”์ฒญ ์ฒ˜๋ฆฌ PostgreSQL(DB ์ €์žฅ) Redis(์„ธ์…˜/์บ์‹ฑ/ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ)

์„œ๋ฒ„๋“ค์€ ์„œ๋กœ “๊ธฐ์–ตํ•˜๋Š” ๊ฒƒ” ์—†์ด ์š”์ฒญ ๋‹จ์œ„๋กœ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


โญ 1๋‹จ๊ณ„. Next.js 15์—์„œ ๋ฌด์ƒํƒœ ์š”์ฒญ ๋งŒ๋“ค๊ธฐ

ํ”„๋ก ํŠธ๋Š” ์„œ๋ฒ„์—๊ฒŒ ์ƒํƒœ๋ฅผ ์“ฐ์ง€ ์•Š๊ณ ๋„ ์š”์ฒญ ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์•„์ด๋””์–ด

  • ๋กœ๊ทธ์ธ ์„ฑ๊ณต → JWT ๋ฐœ๊ธ‰
  • JWT๋Š” HTTP ํ—ค๋”(Authorization: Bearer …) ์— ๋‹ด์•„์„œ ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ „์†ก
  • ์„œ๋ฒ„๊ฐ€ ๊ธฐ์–ตํ•˜์ง€ ์•Š์•„๋„ ๋จ

์˜ˆ์‹œ ํ๋ฆ„ (์‰ฌ์šด ๋น„์œ )

  • ์†๋‹˜์ด ์ฃผ๋ฌธ์„œ(=JWT)๋ฅผ ๋“ค๊ณ  ๋‹ค๋‹ˆ๋ฉฐ ์‹๋‹น(=์„œ๋ฒ„)๋“ค์—๊ฒŒ ์ฃผ๋Š” ๊ตฌ์กฐ
  • ์‹๋‹น์€ ์†๋‹˜์„ ๊ธฐ์–ตํ•  ํ•„์š” ์—†์ด ์ฃผ๋ฌธ์„œ์— ์ •๋ณด๊ฐ€ ๋‹ค ์ ํ˜€์žˆ์Œ

์ฝ”๋“œ ์˜ˆ์‹œ(๊ฐœ๋…)

 
const res = await fetch('/api/user', { headers: { Authorization: `Bearer ${token}`, }, });

โญ 2๋‹จ๊ณ„. ์Šคํ”„๋ง API Gateway์—์„œ ๋ฌด์ƒํƒœ ์ธ์ฆ ์ฒ˜๋ฆฌ

API Gateway๋Š” “์š”์ฒญ์„ ๋ฐ›์•„์„œ ๊ฐ MSA ์„œ๋น„์Šค๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ํ•„ํ„ฐ๋ง” ํ•˜๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค.

ํ•ด์•ผ ํ•  ์ผ

  1. JWT๊ฐ€ ์š”์ฒญ์— ๋‹ด๊ฒผ๋Š”์ง€ ํ™•์ธ
  2. JWT๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆ
  3. ํ•„์š”ํ•˜๋‹ค๋ฉด Redis์—์„œ “๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ํ† ํฐ ์—ฌ๋ถ€” ์กฐํšŒ
  4. ๊ฒ€์ฆ ๋๋‚œ ์‚ฌ์šฉ์ž ์ •๋ณด(Claims)๋ฅผ HTTP ํ—ค๋”์— ๋‹ด์•„ MSA๋กœ ์ „๋‹ฌ

์™œ ์ด๊ฒŒ ๋ฌด์ƒํƒœ์ธ๊ฐ€?

  • Gateway๋Š” ์‚ฌ์šฉ์ž ์„ธ์…˜์„ ๋“ค๊ณ  ์žˆ์ง€ ์•Š์Œ
  • “JWT๊ฐ€ ์ ํ˜€ ์žˆ๋Š”์ง€ → ๊ฒ€์ฆ → ํŒจ์Šค” ์ด ๊ณผ์ •๋งŒ ๋งค๋ฒˆ ๋ฐ˜๋ณต
  • ์•„๋ฌด๊ฒƒ๋„ ๊ธฐ์–ตํ•˜์ง€ ์•Š์Œ

๋น„์œ 

  • ๊ฑด๋ฌผ ์ž…๊ตฌ ๋ณด์•ˆ ๊ฒŒ์ดํŠธ
  • ๋งค๋ฒˆ ์‹ ๋ถ„์ฆ(=JWT)์„ ๋ณด๊ณ  ํ†ต๊ณผ์‹œํ‚ด
  • “์–ด์ œ ํ†ต๊ณผํ–ˆ๋‚˜?” ์ ˆ๋Œ€ ๊ธฐ์–ตํ•˜์ง€ ์•Š์Œ

โญ 3๋‹จ๊ณ„. Spring Boot MSA์—์„œ์˜ ๋ฌด์ƒํƒœ ์ฒ˜๋ฆฌ

๊ฐ MSA(ํšŒ์› ์„œ๋น„์Šค, ์ฃผ๋ฌธ ์„œ๋น„์Šค, ๊ฒฐ์ œ ์„œ๋น„์Šค ๋“ฑ)๋Š” Gateway๊ฐ€ ๋„˜๊ฒจ์ค€ ์ •๋ณด๋งŒ ๋ณด๊ณ  ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ๋ฐฉ์‹

  • MSA๋Š” ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ (@EnableWebSecurity์—์„œ ์„ธ์…˜ ๋น„ํ™œ์„ฑํ™”)
  • ์š”์ฒญ ํ—ค๋”์— ์žˆ๋Š” userId, role ๋“ฑ๋งŒ ์ฝ๊ณ  ์ฒ˜๋ฆฌ
  • ์ƒํƒœ ์ €์žฅ ํ•„์š”ํ•˜๋ฉด PostgreSQL์— ๋ฐ”๋กœ ์ €์žฅ

์˜ˆ์‹œ ์„ค์ •

 
http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

์ค‘์š”ํ•œ ํฌ์ธํŠธ

MSA๋Š” “Gateway๊ฐ€ ๋„˜๊ธด ๊ฐ’ ์—†์œผ๋ฉด ๊ทธ๋ƒฅ 401 ์—๋Ÿฌ”
→ ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์ ˆ๋Œ€ ๋“ค๊ณ  ์žˆ์ง€ ์•Š์Œ


โญ 4๋‹จ๊ณ„. Redis์—์„œ ๊ด€๋ฆฌํ•˜๋Š” “์˜ˆ์™ธ์  ์ƒํƒœ”

๋ฌด์ƒํƒœ ์‹œ์Šคํ…œ์ด๋”๋ผ๋„ ์ผ์‹œ์ ์œผ๋กœ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Œ.
์ด๋•Œ ์„œ๋ฒ„์— ์ €์žฅํ•˜๋ฉด “์ƒํƒœ ์žˆ๋Š” ์„œ๋ฒ„”๊ฐ€ ๋˜๋ฏ€๋กœ,
๋ฌด์กฐ๊ฑด Redis ๊ฐ™์€ ์™ธ๋ถ€ ์ €์žฅ์†Œ์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Redis์— ๋„ฃ๋Š” ๋Œ€ํ‘œ์ ์ธ ์ •๋ณด

  • Refresh Token
  • JWT ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ(๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ)
  • ์š”์ฒญ ๋นˆ๋„(Rate Limiting)
  • ๊ฐ„๋‹จํ•œ ์บ์‹œ

์™œ Redis์ธ๊ฐ€?

  • ๋งค์šฐ ๋น ๋ฅด๊ณ  ์ธ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜
  • ์—ฌ๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผ ๊ฐ€๋Šฅ
    → ์„œ๋ฒ„๊ฐ€ ๊ธฐ์–ตํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ๊ตฌ์กฐ ์™„๋ฒฝ ๊ตฌํ˜„

โญ 5๋‹จ๊ณ„. PostgreSQL์—์„œ ์žฅ๊ธฐ ๋ฐ์ดํ„ฐ ์ €์žฅ

PostgreSQL์€ ์˜๊ตฌ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค.

  • ํšŒ์› ์ •๋ณด
  • ์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ
  • ๊ฒฐ์ œ/์ƒํ’ˆ ์ •๋ณด

์ด๋Ÿฐ ๊ฒƒ๋“ค์€ ์ƒํƒœ(state)๊ฐ€ ์•„๋‹ˆ๋ผ **“๋‹จ์ˆœํ•œ ๋ฐ์ดํ„ฐ”**์ด๋ฏ€๋กœ
๋ฌด์ƒํƒœ ์‹œ์Šคํ…œ์—์„œ๋„ ์ •์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ”— ์ „์ฒด ์š”์ฒญ ํ๋ฆ„ ํ•œ ๋ฒˆ์— ์š”์•ฝ

 
์‚ฌ์šฉ์ž Next.js: JWT ๋ณด๊ด€ ์‚ฌ์šฉ์ž Gateway: JWT ์ „์†ก Gateway JWT ๊ฒ€์ฆ (์ถ”๊ฐ€ ๊ฒ€์ฆ์€ Redis) Gateway userId ๊ฐ™์€ ์ •๋ณด ํ—ค๋”๋กœ ์ „๋‹ฌ MSA ๊ทธ ๊ฐ’๋งŒ ๋ณด๊ณ  ์ฒ˜๋ฆฌ ๋ฐ์ดํ„ฐ๋Š” PostgreSQL์— ์ €์žฅ ์ผ์‹œ์  ์ƒํƒœ๋Š” Redis ์‚ฌ์šฉ

์–ด๋–ค ์„œ๋ฒ„๋„ “๋ฐฉ๋ฌธ์ž๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—”
ํ™•์žฅ์„ฑ · ์†๋„ · ์•ˆ์ •์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.


๐ŸŸฉ ์ดˆ๋ณด์ž๊ฐ€ ๊ฐ€์žฅ ๋งŽ์ด ํ—ท๊ฐˆ๋ฆฌ๋Š” ํฌ์ธํŠธ ์ •๋ฆฌ

โŒ ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์— ์ƒํƒœ ์ €์žฅ

→ ์„ธ์…˜ ์‚ฌ์šฉ ๊ธˆ์ง€
→ ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๋Œ€์ผ ๋•Œ ๋ฌธ์ œ ๋ฐœ์ƒ

โญ• ๋ชจ๋“  ์ƒํƒœ๋Š” “์™ธ๋ถ€ ์‹œ์Šคํ…œ”์— ์ €์žฅ

  • ์ธ์ฆ ์ƒํƒœ → JWT
  • ๋กœ๊ทธ์•„์›ƒ/ํ† ํฐ ๊ด€๋ฆฌ → Redis
  • ์˜๊ตฌ ๋ฐ์ดํ„ฐ → PostgreSQL

โญ• ์„œ๋ฒ„๋Š” “๋“ค์–ด์˜จ ์š”์ฒญ๋งŒ ๋ณด๊ณ  ํŒ๋‹จ”

  • ์š”์ฒญ์— ์ •๋ณด ์—†์œผ๋ฉด ๊ทธ๋ƒฅ ์—๋Ÿฌ
  • ์ด์ „ ์š”์ฒญ์€ 1๋„ ๊ธฐ์–ตํ•˜์ง€ ์•Š์Œ

๐ŸŽฏ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์š”์•ฝ

๋„ฅ์ŠคํŠธ 15 → JWT ๋ฐœ๊ธ‰
๊ฒŒ์ดํŠธ์›จ์ด → JWT ๊ฒ€์ฆ
MSA → ์ž…๋ ฅ๋งŒ ๋ณด๊ณ  ์ฒ˜๋ฆฌ
Redis → ์ž„์‹œ ์ƒํƒœ ์ €์žฅ
PostgreSQL → ์˜๊ตฌ ๋ฐ์ดํ„ฐ ์ €์žฅ
๐Ÿ‘‰ ์ด ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋กœ ๋ฌด์ƒํƒœ ์•„ํ‚คํ…์ฒ˜์ž…๋‹ˆ๋‹ค.

 

 

 


MSA์—์„œ Stream · Flux · Flow์˜ ์œ ์‚ฌ์ ๊ณผ ์ฐจ์ด์  ์ •๋ฆฌ

(์ž๋ฐ” Stream / Reactor Flux / Kotlin Flow ๊ธฐ์ค€)

MSA ํ™˜๊ฒฝ์—์„œ๋Š” ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ, ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ๋ฐฑํ”„๋ ˆ์…”(Backpressure) ๊ฐ™์€ ์š”๊ตฌ๊ฐ€ ๋งŽ์•„์„œ
์ด ์„ธ ๊ธฐ์ˆ ์ด ์ž์ฃผ ๋น„๊ต๋ฉ๋‹ˆ๋‹ค. ์ดˆ๋ณด์ž๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๊ตญ์  ๋น„์œ  ํฌํ•จํ•ด์„œ ์„ค๋ช…๋“œ๋ฆด๊ฒŒ์š”!


## 1. ๊ณตํ†ต์ : “๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋‹ค๋ฃฌ๋‹ค”

Stream · Flux · Flow๋Š” ๋ชจ๋‘ ๋ฐ์ดํ„ฐ๋ฅผ “ํ๋ฆ„์ฒ˜๋Ÿผ” ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ณตํ†ต์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

โœ” ๊ณตํ†ต์  ์š”์•ฝ

  • ๋ฐ˜๋ณต๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ดํ”„๋ผ์ธ(์ค‘๊ฐ„ ์—ฐ์‚ฐ → ์ตœ์ข… ์—ฐ์‚ฐ) ์œผ๋กœ ์ฒ˜๋ฆฌ
  • ์„ ์–ธํ˜• API(“์–ด๋–ป๊ฒŒ”๋ณด๋‹ค “๋ฌด์—‡์„ ํ• ์ง€” ์ค‘์‹ฌ)
  • ๋ถˆ๋ณ€ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์„ ํ˜ธ
  • ํ•จ์ˆ˜ํ˜• ์Šคํƒ€์ผ(map, filter, reduce ๋“ฑ) ์ง€์›

โœ” ํ•œ๊ตญ์‹ ๋น„์œ 

์ปจ๋ฒ ์ด์–ด๋ฒจํŠธ ์œ„์— ๋ฌผ๊ฑด(๋ฐ์ดํ„ฐ)์ด ์ง€๋‚˜๊ฐ€๊ณ ,
๊ทธ๊ฑธ ํ•„์š”ํ•œ ๋Œ€๋กœ ๊ฐ€๊ณตํ•˜๋Š” ๊ตฌ์กฐ

(๋‹ค๋งŒ ์†๋„, ๋ฐฉํ–ฅ, ์ œ์–ด ๋ฐฉ์‹์ด ๋‹ค ๋‹ค๋ฆ„)


## 2. ํ•ต์‹ฌ ์ฐจ์ด์  ์š”์•ฝํ‘œ

๊ตฌ๋ถ„Stream (Java)Flux (Project Reactor)Flow (Kotlin Coroutines)
์„ฑ๊ฒฉ ๋™๊ธฐ(Sync) ์™„์ „ ๋น„๋™๊ธฐ(Async, Reactive) ๋น„๋™๊ธฐ(์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜)
๋ฐ์ดํ„ฐ 0~N๊ฐœ (ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์†Œ๋น„) 0~N๊ฐœ (๋ฌดํ•œ ์ŠคํŠธ๋ฆผ๋„ ๊ฐ€๋Šฅ) 0~N๊ฐœ
๋ฐฑํ”„๋ ˆ์…”(Backpressure) โŒ ์—†์Œ โญ• ์žˆ์Œ(๋ฆฌ์•กํ‹ฐ๋ธŒ ์ŠคํŠธ๋ฆผ ํ‘œ์ค€) โญ• ์ง€์›
์‹คํ–‰ ์‹œ์  ์ฆ‰์‹œ ์‹คํ–‰ ๊ตฌ๋…(subscribe)ํ•ด์•ผ ์‹คํ–‰ collect ํ•ด์•ผ ์‹คํ–‰
์‚ฌ์šฉ ๊ธฐ์ˆ  ๊ณ„์ธต ์ž๋ฐ” ๊ธฐ๋ณธ ๊ธฐ๋Šฅ WebFlux(๋ฆฌ์•กํ‹ฐ๋ธŒ MSA)์—์„œ ํ•ต์‹ฌ ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ API
MSA์— ์ถ”์ฒœ? โŒ ๊ฑฐ์˜ ์‚ฌ์šฉ X (๋™๊ธฐ๋ผ ๋ถ€์ ํ•ฉ) โญ• ๊ณ ์„ฑ๋Šฅ ๋ฆฌ์•กํ‹ฐ๋ธŒ MSA์—์„œ ํ•ต์‹ฌ โญ• ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜ MSA์—์„œ ์ ํ•ฉ

## 3. ๊ฐ๊ฐ์„ ํ•œ๊ตญ์‹ ๋น„์œ ๋กœ ์„ค๋ช…

### โ˜• Java Stream — “์ผ๋ฐ˜ ์ปจ๋ฒ ์ด์–ด๋ฒจํŠธ”

  • ์†๋„๋Š” ์ผ์ •, ๋ฉˆ์ถ”์ง€ ์•Š์Œ
  • ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค ์™€์•ผ ๋‹ค์Œ ์ž‘์—… ๊ฐ€๋Šฅ
  • ๋น„๋™๊ธฐ ๋ถˆ๊ฐ€
    ์ „ํ†ต์ ์ธ ๋ฐฉ์‹

๐Ÿš€ Flux — “์ž๋™ ์†๋„ ์กฐ์ ˆ์ด ๋˜๋Š” ์Šค๋งˆํŠธ ์ปจ๋ฒ ์ด์–ด๋ฒจํŠธ”

  • ๋„ˆ๋ฌด ๋นจ๋ฆฌ ๋“ค์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋Š” ์†๋„๋ฅผ ์ค„์ด๊ฑฐ๋‚˜ ๋ฒ„ํผ๋ง
    ๋ฐฑํ”„๋ ˆ์…”
  • ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ๋„ ๊ฐ€๋Šฅ(์˜ˆ: SSE, ๋ฉ”์‹œ์ง€ ์ŠคํŠธ๋ฆผ)
  • ์™„์ „ ๋น„๋™๊ธฐ
    WebFlux ๊ธฐ๋ฐ˜ MSA์—์„œ ํ•ต์‹ฌ ๊ธฐ์ˆ 

๐ŸŒ€ Kotlin Flow — “์ฝ”๋ฃจํ‹ด์œผ๋กœ ๋งŒ๋“  ์œ ์—ฐํ•œ ์ปจ๋ฒ ์ด์–ด๋ฒจํŠธ”

  • Flux์ฒ˜๋Ÿผ ๋น„๋™๊ธฐ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • ํ•˜์ง€๋งŒ Reactor์ฒ˜๋Ÿผ ์ „์ฒด๋ฅผ ๋ฆฌ์•กํ‹ฐ๋ธŒ๋กœ ๊ฐ•์ œํ•˜์ง€ ์•Š์Œ
    ๊ฐ€๋ณ๊ณ  ๋‹จ์ˆœํ•จ
  • MSA์—์„œ ๋น„์šฉ ์ ์€ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ

## 4. ๊ธฐ์ˆ ๋ณ„๋กœ MSA์—์„œ ์ฃผ๋กœ ์“ฐ๋Š” ์ƒํ™ฉ

### ๐Ÿ”น Stream (MSA์—์„œ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ X)

  • API ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์ž‘์€ ์ปฌ๋ ‰์…˜ ์ฒ˜๋ฆฌ
  • ์™ธ๋ถ€ ๋น„๋™๊ธฐ IO ์—†์„ ๋•Œ ์‚ฌ์šฉ
    → MSA ์ „์ฒด ๊ตฌ์กฐ๋ฅผ ๋™๊ธฐํ™”์‹œํ‚ค๋ฏ€๋กœ ๋ถ€์ ํ•ฉ

๐Ÿ”น Flux (๋ฆฌ์•กํ‹ฐ๋ธŒ MSA ํ•ต์‹ฌ)

MSA์—์„œ ๋‹ค์Œ ์ƒํ™ฉ์—์„œ ์ตœ์ :

  • WebFlux ๊ธฐ๋ฐ˜ API
  • Kafka ๋“ฑ ๋ฉ”์‹œ์ง• ์‹œ์Šคํ…œ๊ณผ ์—ฐ๊ฒฐ
  • ๋†’์€ RPS(์ดˆ๋‹น ์š”์ฒญ ์ˆ˜)
  • ๋ฐฑํ”„๋ ˆ์…” ํ•„์š”ํ•  ๋•Œ
  • ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ(SSE, WebSocket)

MSA ๊ตฌ์กฐ๊ฐ€ ๋ฆฌ์•กํ‹ฐ๋ธŒ ๊ธฐ๋ฐ˜์ด๋ผ๋ฉด Flux๋Š” ์‚ฌ์‹ค์ƒ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.


๐Ÿ”น Flow (์ฝ”ํ‹€๋ฆฐ ๊ธฐ๋ฐ˜ MSA์—์„œ ์ ์  ์ฆ๊ฐ€)

  • Spring Boot 3 + Kotlin์œผ๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ ๊ฐ•๋ ฅ
  • Coroutine + Flow ์กฐํ•ฉ์€ ์ฝ”๋“œ๊ฐ€ ์‰ฌ์›€
  • Reactor ์ „์ฒด ์ƒํƒœ๊ณ„๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์„ ๋•Œ ์„ ํ˜ธ

=> Kotlin MSA๋ผ๋ฉด Flux๋ณด๋‹ค Flow๊ฐ€ ๋‹จ์ˆœํ•˜๊ณ  ์ƒ์‚ฐ์„ฑ ๋†’์Œ.


## 5. ์‹ค์งˆ์ ์ธ ์ฐจ์ด๋ฅผ ‘ํ•œ ์ค„’๋กœ ์ •๋ฆฌํ•˜๋ฉด?

Stream์€ ๋™๊ธฐ ์ปฌ๋ ‰์…˜ ์ฒ˜๋ฆฌ,
Flux๋Š” ๊ณ ์„ฑ๋Šฅ ๋น„๋™๊ธฐ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ(๋ฐฑํ”„๋ ˆ์…” ์ง€์›),
Flow๋Š” ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜์˜ ๊ฐ€๋ณ๊ณ  ์œ ์—ฐํ•œ ๋น„๋™๊ธฐ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ.


## 6. ์ดˆ๋ณด์ž๋ฅผ ์œ„ํ•œ ์š”์•ฝ

โœ” ์ดˆ๋ณด์ž ๊ด€์  ํ•ต์‹ฌ ์š”์•ฝ

  • Stream → ๋™๊ธฐ์ , ์ปฌ๋ ‰์…˜ ์ „์šฉ
  • Flux → ๋ฆฌ์•กํ‹ฐ๋ธŒ MSA ํ•ต์‹ฌ, ๊ณ ์„ฑ๋Šฅ, ๋ฐฑํ”„๋ ˆ์…”
  • Flow → Kotlin ๊ธฐ๋ฐ˜ ๋น„๋™๊ธฐ, ๊ฐ€๋ณ๊ณ  ์ง๊ด€์ 

โœ” ์„ ํƒ ๊ธฐ์ค€

  • ์ž๋ฐ” + ๋ฆฌ์•กํ‹ฐ๋ธŒ MSA(WebFlux) → Flux
  • ์ฝ”ํ‹€๋ฆฐ + ๋น„๋™๊ธฐ MSA → Flow
  • ๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ → Stream

 

 


ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•˜๋Š”๋ฐ. ์ฟ ํ‚ค, ์บ์‹œ๊ฐ€ ์ด๊ฒƒ์„ ๋ฐฉํ•ดํ•˜๊ณ  ์žˆ์–ด ํ˜„์žฌ ๋ ˆ๋””์Šค๊นŒ์ง€๋Š” ์„ค์น˜ํ–ˆ๋Š”๋ฐ ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ •์„ ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ํ• ๊นŒ?

 

์Šคํ”„๋ง API ๊ฒŒ์ดํŠธ์›จ์ด(Spring Cloud Gateway)์—์„œ

๋ฌด์ƒํƒœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๋ฐฉํ•ดํ•˜๋Š” ์ฟ ํ‚ค·์บ์‹œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ์„ค์ • ๋ฐฉ๋ฒ•

์„ค๋ช…์€ ํ˜„์žฌ Redis๊นŒ์ง€ ์„ค์น˜๋œ ์ƒํƒœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ,
“๊ฒŒ์ดํŠธ์›จ์ด์—์„œ ๋ฌด์—‡์„ ๋” ์„ค์ •ํ•˜๋ฉด ๋ฌด์ƒํƒœ๊ฐ€ ๋ณด์žฅ๋˜๋Š”๊ฐ€?”์— ์ง‘์ค‘ํ•ด ์•ˆ๋‚ดํ•ด๋“œ๋ฆด๊ฒŒ์š”.


## ๐Ÿ”ฅ 1. ๋ฌธ์ œ์˜ ๋ณธ์งˆ: “๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์ƒํƒœ๋ฅผ ๋“ค๊ณ  ์žˆ๋‹ค”

๋ฌด์ƒํƒœ ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ์„œ๋ฒ„(๊ฒŒ์ดํŠธ์›จ์ด ํฌํ•จ)๊ฐ€ ์–ด๋–ค ์‚ฌ์šฉ์ž ์ •๋ณด๋„ ๊ธฐ์–ตํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์•„๋ž˜ ๋‘ ์š”์†Œ๊ฐ€ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.

โŒ ์ฟ ํ‚ค(Cookie)

  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ „์†ก
  • JSESSIONID ๊ฐ™์€ ์„ธ์…˜ ์ฟ ํ‚ค๊ฐ€ ๋‚จ์•„ ์žˆ๋Š” ๊ฒฝ์šฐ
  • ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์ฟ ํ‚ค๋ฅผ Downstream ์„œ๋น„์Šค๋กœ ์ „ํŒŒํ•˜๋ฉด
    → “์„œ๋ฒ„ ๊ธฐ๋ฐ˜ ์„ธ์…˜”์ด ์‚ด์•„๋‚จ์Œ → ๋ฌด์ƒํƒœ ์‹คํŒจ

โŒ ๊ฒŒ์ดํŠธ์›จ์ด ์บ์‹œ(HTTP Cache, Filter Cache ๋“ฑ)

  • ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์ด์ „ ์š”์ฒญ·์‘๋‹ต์„ ์บ์‹ฑํ•  ๊ฒฝ์šฐ
    → ์ƒํƒœ ๊ฐ€์ง„ ์„œ๋ฒ„์ฒ˜๋Ÿผ ๋™์ž‘

์ฆ‰, ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ๋งค ์š”์ฒญ์„ “์ƒˆ ์š”์ฒญ”์ฒ˜๋Ÿผ ์ทจ๊ธ‰ํ•ด์•ผ ๋ฌด์ƒํƒœ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.


## โญ 2. ๋ฐ˜๋“œ์‹œ ํ•ด์•ผ ํ•˜๋Š” ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ • ์š”์•ฝ

ํ•ญ๋ชฉ์„ค์ • ๋ชฉ์ 
์„ธ์…˜ ์™„์ „ ๋น„ํ™œ์„ฑํ™” ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ JSESSIONID๋ฅผ ์ ˆ๋Œ€ ์ƒ์„ฑํ•˜์ง€ ์•Š๋„๋ก
์ฟ ํ‚ค ์ œ๊ฑฐ ํ•„ํ„ฐ ์ถ”๊ฐ€ Authorization ์ค‘์‹ฌ ์ธ์ฆ ๊ตฌ์กฐ ์œ ์ง€
์บ์‹ฑ ํ—ค๋” ๊ฐ•์ œ ๋น„ํ™œ์„ฑํ™” ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก
JWT ๊ฒ€์ฆ ํ•„ํ„ฐ ์ถ”๊ฐ€ Redis๋Š” ๋ณด์กฐ(๋ธ”๋ž™๋ฆฌ์ŠคํŠธ/refresh ๊ด€๋ฆฌ)
์š”์ฒญ ๊ฐ„ ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ธˆ์ง€ ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ๋‹จ์ˆœ “๊ฒ€์ฆ → ์ „๋‹ฌ”๋งŒ ์ˆ˜ํ–‰

## ๐Ÿ”ง 3. ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ • ์˜ˆ์‹œ ์ „์ฒด ์ฝ”๋“œ

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฟ ํ‚ค ์ œ๊ฑฐ + ์„ธ์…˜ ๋น„ํ™œ์„ฑํ™” + ์บ์‹œ ์ฐจ๋‹จ + JWT ๋ฌด์ƒํƒœ ์ธ์ฆ ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.


### ๐Ÿ€ application.yml (๊ฒŒ์ดํŠธ์›จ์ด ํ•ต์‹ฌ ์„ค์ •)

โœ” ์„ค๋ช…

  • RemoveRequestHeader=Cookie → ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ์ ˆ๋Œ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ
  • RemoveResponseHeader=Set-Cookie → ๋‹ค์šด์ŠคํŠธ๋ฆผ ์„œ๋น„์Šค๊ฐ€ ๋ณด๋‚ธ ์ฟ ํ‚ค๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด์ง€ ์•Š์Œ
    JSESSIONID๊ฐ€ ์ž๋™ ์ €์žฅ๋  ๊ฐ€๋Šฅ์„ฑ → ์™„์ „ ์ฐจ๋‹จ
  • JwtAuthFilter → ์ง์ ‘ ๋งŒ๋“  JWT ๊ฒ€์ฆ ํ•„ํ„ฐ

## ๐Ÿงฉ 4. ๊ฒŒ์ดํŠธ์›จ์ด์—์„œ ์„ธ์…˜ ์™„์ „ ๋น„ํ™œ์„ฑํ™”

๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์„ธ์…˜์„ ๋งŒ๋“ค๋ฉด ๋ฌด์ƒํƒœ๊ฐ€ ๋ฌด๋„ˆ์ง‘๋‹ˆ๋‹ค.

 

ํ•ต์‹ฌ:

๊ฒŒ์ดํŠธ์›จ์ด๋Š” ์„ธ์…˜์„ ์ ˆ๋Œ€ ๋งŒ๋“ค์ง€ ์•Š๊ณ ,
๋งค ์š”์ฒญ์„ “์ตœ์ดˆ ์š”์ฒญ”์œผ๋กœ ์ฒ˜๋ฆฌ.


## ๐Ÿš€ 5. Redis๋Š” ์–ด๋””์„œ ์‚ฌ์šฉํ•˜๋‚˜์š”?

“๊ฒŒ์ดํŠธ์›จ์ด๋Š” ๋ฌด์ƒํƒœ์ง€๋งŒ, ์ธ์ฆ ๊ด€๋ จ ์ตœ์†Œ ์ƒํƒœ๋Š” Redis๊ฐ€ ๋Œ€์‹  ๊ด€๋ฆฌํ•œ๋‹ค”

MSA ์ „์ฒด ๊ตฌ์กฐ์—์„œ๋Š” JWT ์ž์ฒด๊ฐ€ statelessํ•˜์ง€๋งŒ
Refresh token ๋˜๋Š” Logout ์‹œ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ƒํƒœ๋ฅผ Redis๊ฐ€ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ฆ‰, JWT๊ฐ€ ์œ ํšจํ•œ์ง€ → Redis์—์„œ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์—ฌ๋ถ€ ์กฐํšŒ
์ด๊ฑด ๊ฒŒ์ดํŠธ์›จ์ด๊ฐ€ ์•„๋‹ˆ๋ผ JWT ํ•„ํ„ฐ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.


## ๐Ÿ’ก 6. ๊ฒŒ์ดํŠธ์›จ์ด JWT ํ•„ํ„ฐ(JwtAuthFilter) ์˜ˆ์‹œ

 
\

## ๐ŸŽฏ 7. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฌด์ƒํƒœ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์™„์„ฑ๋˜๋Š” ์ด์œ 

โŒ ์„œ๋ฒ„๋Š” ์ฟ ํ‚ค๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ์•Š๊ณ 

โŒ ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ์„ธ์…˜์„ ์ ˆ๋Œ€ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ 

โŒ ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ์š”์ฒญ์„ ์บ์‹ฑํ•˜์ง€ ์•Š๊ณ 

โญ• ์ธ์ฆ ์ •๋ณด๋Š” JWT๊ฐ€ ์ง์ ‘ ๋“ค๊ณ  ๋‹ค๋‹ˆ๋ฉฐ

โญ• ์˜ˆ์™ธ์  ์ƒํƒœ๋Š” Redis๊ฐ€ ๋ณด๊ด€ํ•˜๊ณ 

โญ• ๊ฒŒ์ดํŠธ์›จ์ด๋Š” ๊ฒ€์ฆ ํ›„ ํ—ค๋”๋งŒ ์ „๋‹ฌ

→ ์„œ๋ฒ„ ์ž์ฒด๋Š” ์•„๋ฌด๊ฒƒ๋„ ๊ธฐ์–ตํ•˜์ง€ ์•Š๋Š” ์ง„์ •ํ•œ Stateless ๊ตฌ์กฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.


## ๐Ÿ”ฅ ์š”์•ฝ

RemoveRequestHeader(Cookie) +
Set-Cookie ์ œ๊ฑฐ +
์„ธ์…˜ STATLEESS +
JWT ํ•„ํ„ฐ + Redis ๊ฒ€์ฆ

์ด 4์ข… ์„ธํŠธ๊ฐ€ ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด์—์„œ ๋ฌด์ƒํƒœ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.

 


๊ฐ•์‚ฌ๋‹˜์ด ์˜ฌ๋ ค์ฃผ์‹  ๊ฒƒ

 

์ข‹์Šต๋‹ˆ๋‹ค, ์ด์ œ **“์ฟ ํ‚ค/์บ์‹œ ๋•Œ๋ฌธ์— ๋ฌด์ƒํƒœ๊ฐ€ ๊นจ์ง€๋Š” ๋А๋‚Œ”**์—์„œ ๋ฒ—์–ด๋‚˜์„œ
 Spring Cloud Gateway + Redis ๊ธฐ์ค€์œผ๋กœ ์ง„์งœ stateless ๊ฒŒ์ดํŠธ์›จ์ด ์„ค์ •์„ ๊ฐ™์ด ์žก์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
0. ๊ฐœ๋… ๋จผ์ € ์ •๋ฆฌ (์ฟ ํ‚ค·์บ์‹œ๊ฐ€ ์ง„์งœ ๋ฐฉํ•ดํ•˜๋Š”๊ฐ€?)

์ฟ ํ‚ค ์ž์ฒด = ๋ฌด์ƒํƒœ๋ฅผ ๋ฐฉํ•ดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
JSESSIONID์ฒ˜๋Ÿผ ์„œ๋ฒ„ ์„ธ์…˜์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฟ ํ‚ค๊ฐ€ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.
๋ฐ˜๋Œ€๋กœ JWT๋ฅผ ๋‹ด์€ httpOnly ์ฟ ํ‚ค๋Š” “ํด๋ผ์ด์–ธํŠธ ์ชฝ ์ƒํƒœ”๋ผ์„œ stateless์— ๋งž์Šต๋‹ˆ๋‹ค.

์บ์‹œ๋„ “์„œ๋ฒ„๊ฐ€ ์œ ์ € ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ์บ์‹œ”๋งŒ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.
์˜ˆ: ์„ธ์…˜ ๊ฐ์ฒด๋ฅผ ๋กœ์ปฌ ์บ์‹œ์— ๋ฐ•์•„๋‘๊ณ  ์“ฐ๋Š” ๊ฒƒ
๋ฐ˜๋Œ€๋กœ ์กฐํšŒ ์‘๋‹ต์„ Redis์— ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์€ stateless์™€ ์ถฉ๋Œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
(์ƒํƒœ = “์œ ์ €์˜ ๋กœ๊ทธ์ธ/์„ธ์…˜ ์ •๋ณด”๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋ผ๊ณ  ๋ณด๋ฉด ํŽธํ•ฉ๋‹ˆ๋‹ค.)

์ฆ‰, ๋ชฉํ‘œ๋Š”:

โœ… Gateway์™€ ๊ฐ MSA๊ฐ€ “์„ธ์…˜์„ ์ ˆ๋Œ€ ์“ฐ์ง€ ์•Š๊ณ ”,
๋งค ์š”์ฒญ ๋•Œ๋งˆ๋‹ค JWT + Redis๋งŒ ๋ณด๊ณ  ๊ฒฐ์ •ํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด๊ฑธ ๊ธฐ์ค€์œผ๋กœ Spring Gateway ์„ค์ •์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
1. ๊ฒŒ์ดํŠธ์›จ์ด ์ชฝ ํ•„์ˆ˜ ์˜์กด์„ฑ ์ •๋ฆฌ

gateway-server์˜ build.gradle (๋˜๋Š” pom.xml)์— ๋Œ€๋žต ์ด๋Ÿฐ ๋А๋‚Œ์ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'http://org.springframework.cloud:spring-cloud-starter-gateway'

    // JWT (๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์“ฐ์‹œ๋Š” ๊ฒƒ์— ๋งž์ถ”๋ฉด ๋ฉ๋‹ˆ๋‹ค)
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

    // Redis (๋ฆฌ์•กํ‹ฐ๋ธŒ)
    implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'

    // Security (WebFlux)
    implementation 'org.springframework.boot:spring-boot-starter-security'
}
ํฌ์ธํŠธ:
webflux + gateway ์กฐํ•ฉ์œผ๋กœ ๊ฐ€์•ผ ์ œ๋Œ€๋กœ ๋…ผ๋ธ”๋กœํ‚น + stateless ์„ค๊ณ„๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค.
spring-boot-starter-web (MVC)๋ž‘ ์„ž์—ฌ ์žˆ์œผ๋ฉด ์„ค์ • ๊ผฌ์ผ ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ™•์ธํ•ด ๋ณด์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2. application.yml – Redis + Gateway ๋ผ์šฐํŒ… + ์„ธ์…˜ ์ตœ์†Œํ™”

spring:
  application:
    name: gateway-server

  data:
    redis:
      host: localhost
      port: 6379
      # password: qwer!234  # ํ•„์š” ์‹œ

  cloud:
    gateway:
      default-filters:
        - RemoveRequestHeader=Cookie   # ํ•„์š”ํ•˜๋ฉด ์ฟ ํ‚ค ์™„์ „ ์ œ๊ฑฐ๋„ ๊ฐ€๋Šฅ
      routes:
        - id: user-service
          uri: http://user-service:8081
          predicates:
            - Path=/api/user/** 
          filters:
            - StripPrefix=1
        - id: board-service
          uri: http://board-service:8082
          predicates:
            - Path=/api/board/**
          filters:
            - StripPrefix=1

server:
  # WebFlux ์„ธ์…˜์„ ์•„์˜ˆ ์˜๋„์ ์œผ๋กœ ์•ˆ ์“ฐ๋Š” ๋ฐฉํ–ฅ
  reactive:
    session:
      timeout: 0s  # ์‚ฌ์‹ค ์„ธ์…˜ ์•ˆ ์“ธ ๊ฑฐ๋ผ ์˜๋ฏธ๋Š” ํฌ์ง€ ์•Š์ง€๋งŒ, "์‚ฌ์šฉ ์•ˆ ํ•จ" ์˜๋„ ํ‘œ์‹œ ์ •๋„
์—ฌ๊ธฐ๊นŒ์ง€๋Š”

Redis ์—ฐ๊ฒฐ

๋ผ์šฐํŒ…๋งŒ ํ•˜๋Š” ๊ฒŒ์ดํŠธ์›จ์ด
์ž…๋‹ˆ๋‹ค.
์ง„์งœ ๋ฌด์ƒํƒœ์˜ ํ•ต์‹ฌ์€ JWT + Redis๋ฅผ ์“ฐ๋Š” ์ธ์ฆ ํ•„ํ„ฐ ์ชฝ์ž…๋‹ˆ๋‹ค.

3. JWT + Redis ๊ธฐ๋ฐ˜ GlobalFilter ์ถ”๊ฐ€ (ํ•ต์‹ฌ)

3-1. JWT ์œ ํ‹ธ (๊ฐœ๋žต)

@Component

public class JwtUtil {

    private final String secret = "your-secret-key"; // ์‹ค์ œ๋กœ๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌ

    public boolean validateToken(String token) {
        // ์—ฌ๊ธฐ์„œ ํŒŒ์‹ฑ + ๋งŒ๋ฃŒ ๊ฒ€์ฆ
        // ์ ๋‹นํžˆ try-catch๋กœ boolean ๋ฆฌํ„ด
    }

    public String getUserId(String token) {
        // claims ์—์„œ userId(or sub) ๊บผ๋‚ด๊ธฐ
    }

    public String getJti(String token) {
        // ํ† ํฐ ์‹๋ณ„์ž(jti) ๊บผ๋‚ด์„œ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ฒดํฌ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    }
}
3-2. Redis๋ฅผ ์ด์šฉํ•œ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ or Refresh ์—ฐ๋™
ํ˜•ํƒœ๋Š” ๋ณดํ†ต ๋‘ ๊ฐ€์ง€ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.
Access ํ† ํฐ๋งŒ ๊ฒ€์ฆ (Redis๋Š” Refresh ์šฉ๋„์—๋งŒ ์‚ฌ์šฉ)

๋กœ๊ทธ์•„์›ƒ๋œ Access ํ† ํฐ์„ Redis ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๋กœ ๊ด€๋ฆฌ
์•„๋ž˜ ์˜ˆ์‹œ๋Š” 2๋ฒˆ ๋А๋‚Œ์œผ๋กœ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
@Component

public class JwtAuthFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;
    private final ReactiveStringRedisTemplate redisTemplate;

    public JwtAuthFilter(JwtUtil jwtUtil, ReactiveStringRedisTemplate redisTemplate) {
        this.jwtUtil = jwtUtil;
        this.redisTemplate = redisTemplate;
    }

    
@Override

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // 1. ํ† ํฐ ์ถ”์ถœ (์ฟ ํ‚ค or Authorization ํ—ค๋”)
        String token = extractToken(request);
        if (token == null) {
            return unauthorized(exchange);
        }

        // 2. ํ† ํฐ ๊ฒ€์ฆ
        if (!jwtUtil.validateToken(token)) {
            return unauthorized(exchange);
        }

        String jti = jwtUtil.getJti(token);

        // 3. Redis ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ฒดํฌ
        return redisTemplate.opsForValue()
            .get("blacklist:" + jti)
            .flatMap(value -> {
                if (value != null) {
                    // ๋กœ๊ทธ์•„์›ƒ๋œ ํ† ํฐ
                    return unauthorized(exchange);
                }
                return continueWithUser(exchange, chain, token);
            })
            .switchIfEmpty(continueWithUser(exchange, chain, token)); // ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ์— ์—†์Œ → ์ •์ƒ ์ง„ํ–‰
    }

    private Mono<Void> continueWithUser(ServerWebExchange exchange, GatewayFilterChain chain, String token) {
        String userId = jwtUtil.getUserId(token);

        // 4. ์š”์ฒญ ํ—ค๋”์— ์œ ์ € ์ •๋ณด ์‹ค์–ด ๋ณด๋‚ด๊ธฐ
        ServerHttpRequest mutated = exchange.getRequest().mutate()
            .header("X-User-Id", userId)
            .build();

        return chain.filter(exchange.mutate().request(mutated).build());
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

    private String extractToken(ServerHttpRequest request) {
        // 1) Authorization: Bearer xxx
        List<String> authHeaders = request.getHeaders().getOrEmpty(HttpHeaders.AUTHORIZATION);
        if (!authHeaders.isEmpty()) {
            String auth = authHeaders.get(0);
            if (auth.startsWith("Bearer ")) {
                return auth.substring(7);
            }
        }

        // 2) httpOnly ์ฟ ํ‚ค ACCESS_TOKEN ์—์„œ ๊บผ๋‚ด๊ธฐ
        List<HttpCookie> cookies = request.getCookies().getOrDefault("ACCESS_TOKEN", List.of());
        if (!cookies.isEmpty()) {
            return cookies.get(0).getValue();
        }

        return null;
    }

    
@Override

    public int getOrder() {
        return -1; // ๊ฐ€์žฅ ์•ž๋‹จ์—์„œ ์ฒ˜๋ฆฌ
    }
}
์ด ํ•„ํ„ฐ์˜ ํŠน์ง•:
์„ธ์…˜์„ ์ „ํ˜€ ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋งค ์š”์ฒญ๋งˆ๋‹ค ํ† ํฐ → Redis ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ํ™•์ธ → X-User-Id ํ—ค๋” ์ถ”๊ฐ€
Gateway ์ธ์Šคํ„ด์Šค๊ฐ€ 10๊ฐœ๋กœ ๋Š˜์–ด๋„,
Redis๋งŒ ๊ณต์œ ํ•˜๋ฉด “๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ”๋Š” ๋ฌธ์ œ์—†์ด ๊ณต์œ ๋ฉ๋‹ˆ๋‹ค.
→ ์ด๊ฒŒ ๋ฐ”๋กœ stateless + externalized state(์™ธ๋ถ€ํ™”ํ•œ ์ƒํƒœ) ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

4. Spring Security(WebFlux) ์„ค์ •์—์„œ ์™„์ „ ๋ฌด์ƒํƒœ๋กœ ๋งŒ๋“ค๊ธฐ
๊ฒŒ์ดํŠธ์›จ์ด์— Spring Security๊ฐ€ ์žˆ๋‹ค๋ฉด, ์•„๋ž˜์ฒ˜๋Ÿผ SecurityContext๋ฅผ ์„ธ์…˜์— ์ €์žฅํ•˜์ง€ ์•Š๊ฒŒ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        return http
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
            .formLogin(ServerHttpSecurity.FormLoginSpec::disable)
            // ๐Ÿ”ฅ ์—ฌ๊ธฐ ์ค‘์š”: SecurityContext๋ฅผ WebSession์— ์ €์žฅํ•˜์ง€ ์•Š๊ฒŒ ์„ค์ •
            .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/auth/**").permitAll()
                .anyExchange().authenticated()
            )
            .build();
    }
}
ํฌ์ธํŠธ:
WebFlux์—์„œ๋Š” sessionCreationPolicy(STATELESS) ๊ฐ™์€ MVC ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ
 NoOpServerSecurityContextRepository ๋ฅผ ์จ์„œ ์•„์˜ˆ ์„ธ์…˜ ์ €์žฅ์„ ๋ง‰์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•ด๋‘๋ฉด Spring Security๋„ ์„ธ์…˜์„ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
5. “์ฟ ํ‚ค/์บ์‹œ๊ฐ€ ๋ฐฉํ•ดํ•œ๋‹ค”๋Š” ๋А๋‚Œ์„ ์—†์• ๋Š” ์ฒดํฌ๋ฆฌ์ŠคํŠธ
๋งˆ์ง€๋ง‰์œผ๋กœ, ์ง€๊ธˆ ํ”„๋กœ์ ํŠธ์—์„œ ํ•œ ๋ฒˆ์”ฉ ์ฒดํฌํ•ด๋ณด์‹œ๋ฉด ์ข‹์„ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.

JSESSIONID๊ฐ€ ๊ตด๋Ÿฌ๋‹ค๋‹ˆ๋Š”์ง€?
๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ → Application → Cookies์—์„œ
 JSESSIONID๊ฐ€ ๋ณด์ด๋ฉด ์–ด๋”˜๊ฐ€์—์„œ ์„ธ์…˜์„ ์“ฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
Gateway/์„œ๋น„์Šค์—์„œ spring-boot-starter-web + 
@Controller
 + thymeleaf ์กฐํ•ฉ์„ ์“ฐ๋ฉด ์ž๋™์œผ๋กœ ์ƒ๊น๋‹ˆ๋‹ค.
Spring Session, HttpSession, @SessionAttributes ์‚ฌ์šฉ ์—ฌ๋ถ€
์ด๋Ÿฐ ๊ฒƒ ์“ฐ๋ฉด ๋ฐ”๋กœ stateful๋กœ ๊ฐ€๋Š” ๊ฒ๋‹ˆ๋‹ค.
๊ฒŒ์ดํŠธ์›จ์ด/๋ฐฑ์—”๋“œ์—์„œ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉํ–ฅ์ด ๋งž์Šต๋‹ˆ๋‹ค.
์บ์‹œ ์šฉ๋„ ํ™•์ธ
Redis๋‚˜ Caffeine์„ “์œ ์ € ์„ธ์…˜์„ ๋ณด๊ด€ํ•˜๋Š” ์šฉ๋„”๋กœ ์“ฐ๊ณ  ์žˆ๋Š”์ง€,
์•„๋‹ˆ๋ฉด “๋ฐ์ดํ„ฐ ์กฐํšŒ ๊ฒฐ๊ณผ ์บ์‹œ”์šฉ์œผ๋กœ๋งŒ ์“ฐ๋Š”์ง€ ๋ถ„๋ฆฌํ•ด์„œ ๋ณด์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ •๋ฆฌ ํ•œ ์ค„

๊ฒŒ์ดํŠธ์›จ์ด์—์„œ JWT + Redis๋กœ๋งŒ ์œ ์ € ์ƒํƒœ๋ฅผ ํŒ๋ณ„ํ•˜๊ณ ,
Spring Security ์„ธ์…˜ ์ €์žฅ์„ ๋ง‰๊ณ ,
๊ฐ MSA๋Š” X-User-Id ํ—ค๋”๋งŒ ๋ณด๊ณ  DB๋ฅผ ์กฐํšŒํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด
์ฟ ํ‚ค/์บ์‹œ๊ฐ€ ์žˆ์–ด๋„ ์™„์ „ํ•œ ๋ฌด์ƒํƒœ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 


๋„ฅ์ŠคํŠธ 15๋ฒ„์ „, ๋ฆฌ์•กํŠธ, ์ฃผ์Šคํƒ ๋“œ, ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ, ์Šคํ”„๋ง ๊ฒŒ์ดํŠธ์›จ์ด, ์Šคํ”„๋ง ๋ถ€ํŠธ msa , ๋ ˆ๋””์Šค, ํฌ์ŠคํŠธ๊ทธ๋ฆฌ์— ๋ฐฑ์—”๋“œ๋ฅผ FLUX๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์˜๊ฒฌ์ด ์žˆ๋Š”๋ฐ ๋„ˆ์˜ ์ƒ๊ฐ์€ ์–ด๋•Œ?

 

๋ฐฑ์—”๋“œ๋ฅผ Flux(WebFlux) ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ์˜๊ฒฌ, ์–ด๋–ป๊ฒŒ ์ƒ๊ฐํ•˜๋ƒ๊ณ ์š”?

ํ˜„์žฌ ๊ธฐ์ˆ  ์Šคํƒ(Next.js 15 · React · Zustand · React Query · Spring Gateway · Spring Boot MSA · Redis · PostgreSQL)์„ ๊ธฐ์ค€์œผ๋กœ **“Flux๋กœ ๋ฐ”๊พธ๋Š” ๊ฒŒ ์‹ค์ œ๋กœ ํ”„๋กœ์ ํŠธ์— ์ด๋“์ด ์žˆ๋Š”๊ฐ€?”**๋ฅผ ํ˜„์‹ค์ ์œผ๋กœ ํ‰๊ฐ€ํ•ด๋“œ๋ฆด๊ฒŒ์š”.


## ๐Ÿ”ฅ ๊ฒฐ๋ก ๋ถ€ํ„ฐ: ์ƒํ™ฉ์— ๋”ฐ๋ผ ๊ฐ•๋ ฅํ•œ ์„ ํƒ์ด ๋  ์ˆ˜๋„ ์žˆ๊ณ , ๊ณผ๋„ํ•œ ์„ ํƒ์ด ๋  ์ˆ˜๋„ ์žˆ๋‹ค

Flux๋Š” “๋ฐ˜๋“œ์‹œ ์จ์•ผ ํ•˜๋Š” ๊ธฐ์ˆ ”์ด ์•„๋‹ˆ๋ผ
ํŠธ๋ž˜ํ”ฝ ์„ฑ๊ฒฉ, ์‹œ์Šคํ…œ ๋ณต์žก๋„, ๊ฐœ๋ฐœํŒ€ ์—ญ๋Ÿ‰, ์žฅ์•  ํŒจํ„ด์— ๋”ฐ๋ผ
์“ฐ๋ฉด ์—„์ฒญ๋‚œ ์ด์ ์ด ์ƒ๊ธฐ๊ณ ,
๋ฐ˜๋Œ€๋กœ ์ž˜๋ชป ์“ฐ๋ฉด ๊ด€๋ฆฌ๊ฐ€ ๋” ์–ด๋ ค์›Œ์ง€๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

์•„๋ž˜ ๋‚ด์šฉ์„ ๊ธฐ์ค€์œผ๋กœ ํŒ๋‹จํ•ด ๋ณด์„ธ์š”.


์Šคํ”„๋ง์—์„œ ๋งํ•˜๋Š” ๋ธ”๋กœํ‚น(Blocking) ์ด๋ž€?

์Šคํ”„๋ง(Spring)์—์„œ ๋ธ”๋กœํ‚น์€

“์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ์Šค๋ ˆ๋“œ๊ฐ€ ๋‹ค๋ฅธ ์ผ์„ ๋ชป ํ•˜๊ณ  ๊ทธ๋Œ€๋กœ ๋ฉˆ์ถ˜ ์ƒํƒœ๋กœ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ”
์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด,
์„œ๋ฒ„๊ฐ€ ํ•œ ๋ช…์˜ ์†๋‹˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ๊ทธ ์Šค๋ ˆ๋“œ๋Š” ๋‹ค๋ฅธ ์†๋‹˜์„ ๋ฐ›์„ ์ˆ˜ ์—†์–ด์„œ ‘๋ถ™์žกํ˜€ ์žˆ๋Š”’ ์ƒํ™ฉ์ด์—์š”.


## ๐Ÿœ ํ•œ๊ตญ์‹ ๋น„์œ ๋กœ ์‰ฝ๊ฒŒ ์„ค๋ช…

โœ” ๋™๋„ค ๊ตญ๋ฐฅ์ง‘

  • ์ฃผ๋ฐฉ์žฅ์ด ๋”ฑ ํ•œ ๋ช…
  • ํ•œ ์†๋‹˜ ๊ตญ๋ฐฅ ๋“์ด๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด
    → ๊ทธ ์†๋‹˜ ์Œ์‹ ์™„์„ฑ๋  ๋•Œ๊นŒ์ง€ ๋‹ค๋ฅธ ์†๋‹˜ ์ฃผ๋ฌธ์„ ์ ˆ๋Œ€ ๋ชป ๋ฐ›์Œ
  • ์ฆ‰, ๋Œ€๊ธฐ ์ค„ ์ฆ๊ฐ€ = ๋ธ”๋กœํ‚น

โœ” ์Šคํ”„๋ง MVC์˜ ๋ธ”๋กœํ‚น ๋ฐฉ์‹์ด ์ด์™€ ๋™์ผ

  • ์ปจํŠธ๋กค๋Ÿฌ ์š”์ฒญ 1๊ฐœ = ์Šค๋ ˆ๋“œ 1๊ฐœ ๊ณ ์ • ๋ฐฐ์ •
  • DB ์กฐํšŒ, ์™ธ๋ถ€ API ํ˜ธ์ถœ ๋“ฑ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ
    → ์Šค๋ ˆ๋“œ๊ฐ€ ์™„์ „ํžˆ “์† ๋†“๊ณ  ๊ธฐ๋‹ค๋ฆผ(Blocking)”

๊ทธ๋ž˜์„œ ๋งŽ์€ ์š”์ฒญ์ด ๋ชฐ๋ฆฌ๋ฉด ์Šค๋ ˆ๋“œ๊ฐ€ ๋ชจ๋‘ ์ฐจ์„œ ์„œ๋ฒ„๊ฐ€ ๋А๋ ค์ง€๊ฑฐ๋‚˜ ์ฃฝ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.


## โœ” ์Šคํ”„๋ง์—์„œ ๋ธ”๋กœํ‚น์ด ๋ฐœ์ƒํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ถ€๋ถ„

1) Spring MVC (Servlet ๊ธฐ๋ฐ˜)

  • ์š”์ฒญ๋‹น ์Šค๋ ˆ๋“œ๋ฅผ ๋ฐฐ์ •ํ•˜๋Š” ๊ตฌ์กฐ
  • ๋‹ด๊ธˆ์งˆํ•˜๋Š” ๋™์•ˆ ์Šค๋ ˆ๋“œ๊ฐ€ ์ผ์„ ๋ชป ํ•จ
    → “๋ธ”๋กœํ‚น ๋ชจ๋ธ”

2) JDBC(์ „ํ†ต์ ์ธ DB ์—ฐ๊ฒฐ)

  • ์ฟผ๋ฆฌ ์‹คํ–‰ ๋™์•ˆ ์Šค๋ ˆ๋“œ ๋Œ€๊ธฐ
    → ๊ฐ€์žฅ ํ”ํ•œ ๋ธ”๋กœํ‚น ์›์ธ

3) RestTemplate

  • ์™ธ๋ถ€ API ํ˜ธ์ถœ ๋™์•ˆ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ธฐ๋‹ค๋ฆผ
    → ๋ธ”๋กœํ‚น I/O

4) ํŒŒ์ผ ์ฝ๊ธฐ/์“ฐ๊ธฐ, ๋„คํŠธ์›Œํฌ I/O ๋Œ€๋ถ€๋ถ„


## โœ” ๊ทธ๋ž˜์„œ ๋ฌธ์ œ๋Š”?

โŒ ํŠธ๋ž˜ํ”ฝ์ด ์กฐ๊ธˆ๋งŒ ๋Š˜์–ด๋„ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ถ€์กฑํ•ด์ง

→ ์„œ๋ฒ„ ์‘๋‹ต ์ง€์—ฐ → ์žฅ์•  ๋ฐœ์ƒ

โŒ ์„œ๋ฒ„ ์‚ฌ์–‘์„ ์˜ฌ๋ ค๋„ “์Šค๋ ˆ๋“œ ๋ณ‘๋ชฉ”์€ ํ•ด๊ฒฐ ์•ˆ ๋จ

โŒ MSA ๊ตฌ์กฐ์—์„œ๋Š” ์„œ๋น„์Šค ๊ฐ„ ํ˜ธ์ถœ์ด ๋งŽ์•„

→ ๋ธ”๋กœํ‚น์ด ๋” ์‹ฌ๊ฐํ•ด์ง


## โœ” ๊ทธ๋ž˜์„œ ๋‚˜์˜จ ๊ฒƒ์ด WebFlux(๋น„๋™๊ธฐ·๋…ผ๋ธ”๋กœํ‚น)

WebFlux + Flux๋Š” ๋‹ค์Œ ํŠน์ง•์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค:

  • ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์Šค๋ ˆ๋“œ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ฒŒ ํ•จ(๋…ผ๋ธ”๋กœํ‚น)
  • I/O๊ฐ€ ๋๋‚˜๋Š” ์ˆœ๊ฐ„ ๋‹ค์‹œ ์Šค๋ ˆ๋“œ๋ฅผ ์ž ๊น๋งŒ ์‚ฌ์šฉ
  • ๊ฐ™์€ ์Šค๋ ˆ๋“œ๋กœ ์ˆ˜์ฒœ ์—ฐ๊ฒฐ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ

ํ•œ๊ตญ์‹ ๋น„์œ ๋กœ ํ•˜๋ฉด:

์ง์›์ด ํ•œ ๋ช…์ด์–ด๋„ ์ฃผ๋ฌธ ๋ฐ›๊ณ  ์š”๋ฆฌํ•˜๊ณ  ๊ณ„์‚ฐ๊นŒ์ง€ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ‘์•Œ๋ฐ” ์ฒœ์žฌ ์‹œ์Šคํ…œ’


## ๐Ÿ”ฅ ํ•œ ์ค„ ์š”์•ฝ

์Šคํ”„๋ง์—์„œ ๋ธ”๋กœํ‚น์€ “์Šค๋ ˆ๋“œ๊ฐ€ I/O๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋А๋ผ ์•„๋ฌด๊ฒƒ๋„ ๋ชป ํ•˜๊ณ  ๋ถ™์žกํ˜€ ์žˆ๋Š” ์ƒํƒœ”์ด๋ฉฐ,
์ด๊ฒƒ์ด ์„œ๋ฒ„์˜ ํ™•์žฅ์„ฑ์„ ํฌ๊ฒŒ ๋–จ์–ด๋œจ๋ฆฐ๋‹ค.

 

 


์ฝ”ํ‹€๋ฆฐ(Kotlin)์ด ๋ญ์•ผ? ์‰ฝ๊ณ  ํ•œ๊ตญ์‹์œผ๋กœ ์„ค๋ช…ํ•ด์ค„๊ฒŒ์š”!

์ฝ”ํ‹€๋ฆฐ์€ JetBrains(์ ฏ๋ธŒ๋ ˆ์ธ) ์ด ๋งŒ๋“  ํ˜„๋Œ€์ ์ธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋กœ,
ํŠนํžˆ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ๊ณผ ๋ฐฑ์—”๋“œ(Spring Boot) ๋ถ„์•ผ์—์„œ ๋งค์šฐ ๋งŽ์ด ์“ฐ์ด๊ณ  ์žˆ์–ด์š”.

“์ž๋ฐ”๋ฅผ ๋” ์‰ฝ๊ณ  ์•ˆ์ „ํ•˜๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ๋งŒ๋“  ๋ฒ„์ „”์ด๋ผ๊ณ  ๋ณด๋ฉด ๊ฑฐ์˜ ๋งž์Šต๋‹ˆ๋‹ค.


## โญ ํ•œ ๋ฌธ์žฅ ์š”์•ฝ

์ฝ”ํ‹€๋ฆฐ = ์ž๋ฐ”๋ณด๋‹ค ์งง๊ณ  ์•ˆ์ „ํ•˜๊ณ  ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ์ด ๋†’์€ ํ˜„๋Œ€์  ์–ธ์–ด


## ์™œ ์ฝ”ํ‹€๋ฆฐ์ด ๋“ฑ์žฅํ–ˆ์„๊นŒ?

์ž๋ฐ”๋Š” ์˜ค๋ž˜๋œ ์–ธ์–ด๋ผ์„œ ์•„๋ž˜ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์–ด์š”.

  • ์ฝ”๋“œ๊ฐ€ ๊ธธ๋‹ค
  • null ๋ฌธ์ œ(NPE)๊ฐ€ ์ž์ฃผ ๋‚œ๋‹ค
  • ์ตœ์‹  ๋ฌธ๋ฒ•์ด ๋ถ€์กฑํ•˜๋‹ค
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ(์ฝ”๋ฃจํ‹ด)๊ฐ€ ์—†๋‹ค

์ด ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด JetBrains๊ฐ€ ์ž๋ฐ”์™€ 100% ํ˜ธํ™˜๋˜๋ฉด์„œ๋„
๋” ์“ฐ๊ธฐ ์ข‹์€ ์–ธ์–ด๋ฅผ ๋งŒ๋“  ๊ฑฐ์˜ˆ์š” → ๊ทธ๊ฒŒ ์ฝ”ํ‹€๋ฆฐ!


## ์ฝ”ํ‹€๋ฆฐ์˜ ์žฅ์  (์ดˆ๋ณด์ž๋„ ์•Œ๊ธฐ ์‰ฝ๊ฒŒ)

โœ” 1) ์ฝ”๋“œ๊ฐ€ ์งง์•„์ง„๋‹ค

์ž๋ฐ” ์ฝ”๋“œ 10์ค„ → ์ฝ”ํ‹€๋ฆฐ์€ 3~5์ค„ ๊ฐ€๋Šฅ

โœ” 2) NPE(null ์—๋Ÿฌ)๋ฅผ ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ๋ง‰์•„์ค€๋‹ค

์ฝ”ํ‹€๋ฆฐ์€ “null์ด ๋  ์ˆ˜ ์žˆ๋Š”์ง€ ์—†๋Š”์ง€”๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

โœ” 3) ์ž๋ฐ”์™€ 100% ํ˜ธํ™˜

์ž๋ฐ” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
→ ์Šคํ”„๋ง์—์„œ๋„ ๋ฌธ์ œ ์—†์Œ

โœ” 4) ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์˜ ์ตœ๊ฐ•์ž = ์ฝ”๋ฃจํ‹ด(Coroutine)

Flux/Reactor๋ณด๋‹ค ๊ฐ€๋ณ๊ณ  ์ง๊ด€์ 
MSA·Web ๊ฐœ๋ฐœ์—์„œ ์ธ๊ธฐ ์ƒ์Šน ์ค‘

โœ” 5) JetBrains IDE(IntelliJ)์™€ ์ฐฐ๋–ก๊ถํ•ฉ

๊ฐœ๋ฐœ ๊ฒฝํ—˜์ด ๋งค์šฐ ์ข‹์Œ


## ํ•œ๊ตญ์‹ ๋น„์œ ๋กœ ์ฝ”ํ‹€๋ฆฐ ์ดํ•ดํ•˜๊ธฐ

๐Ÿœ ์ž๋ฐ” = ์ „ํ†ต์ ์ธ ์นผ๊ตญ์ˆ˜ ์ง‘

  • ๋ง›์€ ์žˆ๊ณ  ๊ฒ€์ฆ๋จ
  • ํ•˜์ง€๋งŒ ์†์งˆ ๊ณผ์ •์ด ๊ธธ๊ณ  ์‹œ๊ฐ„์ด ๋งŽ์ด ํ•„์š”ํ•จ
  • ์˜ค๋ž˜๋œ ๋ฐฉ์‹์ด ๋งŽ์Œ

๐Ÿฑ ์ฝ”ํ‹€๋ฆฐ = ํ˜„๋Œ€์‹ ์ฆ‰์„ ์นผ๊ตญ์ˆ˜ ๊ธฐ๊ณ„

  • ๊ณผ์ •์ด ํ›จ์”ฌ ๋‹จ์ˆœ
  • ์ž๋™ํ™”๋œ ์•ˆ์ „์žฅ์น˜ ์žˆ์Œ
  • ์†๋„ ๋น ๋ฅด๊ณ  ์‹ค์ˆ˜ ์˜ˆ๋ฐฉ์ด ์‰ฌ์›€
  • ์ž๋ฐ”์™€ ์žฌ๋ฃŒ(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

## ์Šคํ”„๋ง์—์„œ ์ฝ”ํ‹€๋ฆฐ์„ ์™œ ๋งŽ์ด ์“ธ๊นŒ?

โœ” ๋ฐฐ๋ฏผ, ์นด์นด์˜ค, ๋„ฅ์Šจ ๋“ฑ ๊ตญ๋‚ด ๋Œ€๊ธฐ์—…์—์„œ ์ ๊ทน ์‚ฌ์šฉ

โœ” MSA + WebFlux + ์ฝ”๋ฃจํ‹ด ์กฐํ•ฉ์ด ๋งค์šฐ ์‰ฝ๊ณ  ๋น ๋ฆ„

โœ” ์ƒ์‚ฐ์„ฑ์ด ํฌ๊ฒŒ ์˜ฌ๋ผ๊ฐ

โœ” ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋›ฐ์–ด๋‚จ

ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค์—์„œ ์ฝ”ํ‹€๋ฆฐ + ์Šคํ”„๋ง ์กฐํ•ฉ์ด ์ธ๊ธฐ์ž…๋‹ˆ๋‹ค.

 

 

๋น„๋™๊ธฐ ๊ตฌ์กฐ๊ฐ€ ๋ญ์•ผ? (์ดˆ๋ณด์ž·ํ•œ๊ตญ์‹ ์„ค๋ช…)

๋น„๋™๊ธฐ(asynchronous) ๊ตฌ์กฐ๋Š” **“๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ผ์„ ๊ณ„์†ํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ”**๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.
์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด…

์ผ์„ ๋งก๊ฒจ๋†“๊ณ , ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋ฉด ์•Œ๋ ค๋‹ฌ๋ผ๊ณ  ํ•œ ๋’ค
๋‚˜๋Š” ๋‚ด ํ•  ์ผ์„ ๊ณ„์†ํ•˜๋Š” ๋ฐฉ์‹