
๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ์ด๋?
๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ(stateless programming)์ ํ๋ก๊ทธ๋จ์ด ์ด๋ค ๊ฐ์ ๊ณ์ฐํ ๋ ์ด์ ์ํ(state)์ ์์กดํ์ง ์๊ณ , ์ค์ง ํ์ฌ ์
๋ ฅ๊ฐ๋ง์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ง๋๋ ๋ฐฉ์์ ๋งํฉ๋๋ค.
์ฝ๊ฒ ๋งํด, **“์ด์ ๋ญ ํ๋์ง ๋ชฐ๋ผ๋, ์ค๋ ๋ค์ด์จ ์ ๋ณด๋ง ๋ณด๊ณ ํ๋จํ๋ ํ๋ก๊ทธ๋จ”**์ด๋ผ๊ณ ์๊ฐํ์๋ฉด ๋ฉ๋๋ค.
์ ‘๋ฌด์ํ’๋ผ๊ณ ํ ๊น?
์ํ(state)๋ ํ๋ก๊ทธ๋จ์ด ๊ธฐ์ตํ๊ณ ์๋ ์ ๋ณด์
๋๋ค.
์๋ฅผ ๋ค์ด:
- ๋ก๊ทธ์ธ ์ฌ๋ถ
- ์ธ์ ์ ๋ณด
- ์ด์ ์ ๊ณ์ฐํ ๊ฐ
- ์นด์ดํฐ(1 ์ฆ๊ฐ, 2 ์ฆ๊ฐ ๊ฐ์ ๊ฐ)
์ด๋ฐ ๋ฐ์ดํฐ๋ฅผ ํ๋ก๊ทธ๋จ ๋ด๋ถ์์ ๊ณ์ ๋ค๊ณ ์์ผ๋ฉด **์ํ๊ฐ ์๋ค(stateful)**๊ณ ๋งํฉ๋๋ค.
๋ฐ๋๋ก, ํจ์๋ ํ๋ก๊ทธ๋จ์ด ์ ๋ ฅ๋ง ๋ณด๊ณ ๋งค๋ฒ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฒฐ๊ณผ๋ฅผ ๋ด๋ ๊ฒฝ์ฐ๋ฅผ **๋ฌด์ํ(stateless)**๋ผ๊ณ ํฉ๋๋ค.
ํ๊ตญ์ ๋น์ ๋ก ์ฝ๊ฒ ์ค๋ช
๐ ๋ผ๋ฉด ๊ฐ๊ฒ ๋ ๊ฐ์ง ๋ฐฉ์ ๋น๊ต
- ์ํ ์๋ ๊ฐ๊ฒ(Stateful)
- ๋จ๊ณจ ์๋์ ๊ธฐ์ตํจ
- “์ด์ ์ฒ๋ผ ๋งค์ด๋ง์ผ๋ก ๋๋ฆด๊ฒ์?”
→ ์ด์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋
- ๋ฌด์ํ ๊ฐ๊ฒ(Stateless)
- ๋งค๋ฒ ์ฃผ๋ฌธ์๋ง ๋ณด๊ณ ์กฐ๋ฆฌ
- ์๋์ด ๋๊ตฌ์ธ์ง, ๋ญ ๋จน์๋์ง ๊ธฐ์ตํ์ง ์์
→ ์ ๋ ฅ(์ฃผ๋ฌธ์)๋ง ๋ณด๊ณ ์๋ฆฌํจ
๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ์ 2๋ฒ์ฒ๋ผ ํญ์ ์ ๋ ฅ๋ง ๋ณด๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋๋ ์คํ์ผ์ ๋๋ค.
์ด๋์ ๋ง์ด ์ฐ์ผ๊น?
### 1) ํจ์ํ ํ๋ก๊ทธ๋๋ฐ(Haskell, Scala ๋ฑ)
- ๋ถ์์ฉ(side-effect)์ ์ต์ํ
- ๊ฐ์ ์ ๋ ฅ์ด๋ฉด ํญ์ ๊ฐ์ ์ถ๋ ฅ → ํ ์คํธ·๋๋ฒ๊น ์ด ์ฌ์
2) REST API
- “์๋ฒ๋ ์์ฒญ ๊ฐ์ ์ ๋ณด๋ฅผ ๊ธฐ์ตํ์ง ์๋๋ค”๊ฐ ํต์ฌ ์์น
- ๋ฐ๋ผ์ ๊ฐ ์์ฒญ์ ํ์ํ ๋ชจ๋ ์ ๋ณด๋ฅผ ์ค์ด ๋ณด๋ด์ผ ํจ
3) ์๋ฒ ํ์ฅ์ฑ(์ค์ผ์ผ๋ง)์ ์ ๋ฆฌ
- ์ํ๋ฅผ ๋ค๊ณ ์์ง ์์ผ๋ ์ด๋ค ์๋ฒ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํด๋ OK
- ํด๋ผ์ฐ๋ ํ๊ฒฝ์์ ํนํ ์ค์
๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฅ๋จ์
์ฅ์
- ์์ธก ๊ฐ๋ฅ์ฑ ๋์ (๊ฐ์ ์ ๋ ฅ → ๊ฐ์ ๊ฒฐ๊ณผ)
- ํ ์คํธ๊ฐ ์ฌ์
- ์ ์ง๋ณด์, ๋ถ์ฐ ์์คํ ์์ ํฐ ์ด์
- ๋์์ฑ ์ฒ๋ฆฌ์์ ์ถฉ๋์ด ์ ์
๋จ์
- ์ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ(๋ก๊ทธ์ธ, ์ฅ๋ฐ๊ตฌ๋ ๋ฑ)์ ๊ตฌํ์ด ๋ณต์กํด์ง ์ ์์
- ์ธ๋ถ ์ ์ฅ์(์ฟ ํค, DB, ์บ์ ๋ฑ)์ ์ํ๋ฅผ ๋ณ๋๋ก ๊ด๋ฆฌํด์ผ ํจ
ํ ์ค ์์ฝ
๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ์ ‘๊ธฐ์ตํ์ง ์๋’ ๋ฐฉ์์ผ๋ก, ์ ๋ ฅ๊ฐ๋ง ๋ณด๊ณ ๊ฒฐ๊ณผ๋ฅผ ๊ณ์ฐํ๋ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์ ๋๋ค.
๋ฅ์คํธ 15, ์คํ๋ง ๊ฒ์ดํธ์จ์ด, ์คํ๋ง ๋ถํธ MSA, ๋ ๋์ค, ํฌ์คํธ๊ทธ๋ฆฌ๋ก ๊ตฌํํ๋ ๋ฌด์ํ ํ๋ก๊ทธ๋๋ฐ ์ ์ฒด ํ๋ฆ
์๋ ์ค๋ช
์ ์์ ์ด๋ณด์๋ ์ดํดํ ์ ์๋๋ก,
ํ๊ตญ์ ๋น์ + ๋จ๊ณ๋ณ ๊ตฌ์กฐ + ์ค์ ๊ตฌํ ํ๋ฆ์ผ๋ก ์ฝ๊ฒ ์ค๋ช
ํด๋๋ฆด๊ฒ์.
๐ ์ ์ฒด ์ฝ์ ํธ: “์๋ฒ๋ ์๋ฌด๊ฒ๋ ๊ธฐ์ตํ์ง ์๋๋ค”
๋ฌด์ํ(stateless) ์์คํ
์ ๋ง๋ค๊ธฐ ์ํด์๋ ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ ๋ ํ์ํ ์ ๋ณด๊ฐ ๋ชจ๋ ์์ฒญ(Request)์ ๋ค์ด์์ด์ผ ํฉ๋๋ค.
์๋ฒ๋ “์, ์ด ์ ์ ์๊น ๋ก๊ทธ์ธํ์์ง?” ๊ฐ์ ๊ฑธ ๊ธฐ์ตํ์ง ์์์.
๋์ , ์ธ์ฆ ์ ๋ณด · ์ธ์
์ ๋ณด · ์ฅ๋ฐ๊ตฌ๋ · ํ ํฐ ๊ฒ์ฆ ๋ฑ ํ์ํ ๊ฒ์
๐ ํ ํฐ(JWT), Redis, DB(PostgreSQL) ๋ฑ์ ํ์ฉํ์ฌ “์ธ๋ถ์ ๋ณด๊ด”ํฉ๋๋ค.
๐งฑ ์ ์ฒด ๊ตฌ์กฐ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ
์๋ฒ๋ค์ ์๋ก “๊ธฐ์ตํ๋ ๊ฒ” ์์ด ์์ฒญ ๋จ์๋ก๋ง ์ฒ๋ฆฌํฉ๋๋ค.
โญ 1๋จ๊ณ. Next.js 15์์ ๋ฌด์ํ ์์ฒญ ๋ง๋ค๊ธฐ
ํ๋ก ํธ๋ ์๋ฒ์๊ฒ ์ํ๋ฅผ ์ฐ์ง ์๊ณ ๋ ์์ฒญ ๊ฐ๋ฅํ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค.
ํต์ฌ ์์ด๋์ด
- ๋ก๊ทธ์ธ ์ฑ๊ณต → JWT ๋ฐ๊ธ
- JWT๋ HTTP ํค๋(Authorization: Bearer …) ์ ๋ด์์ ๋งค ์์ฒญ๋ง๋ค ์ ์ก
- ์๋ฒ๊ฐ ๊ธฐ์ตํ์ง ์์๋ ๋จ
์์ ํ๋ฆ (์ฌ์ด ๋น์ )
- ์๋์ด ์ฃผ๋ฌธ์(=JWT)๋ฅผ ๋ค๊ณ ๋ค๋๋ฉฐ ์๋น(=์๋ฒ)๋ค์๊ฒ ์ฃผ๋ ๊ตฌ์กฐ
- ์๋น์ ์๋์ ๊ธฐ์ตํ ํ์ ์์ด ์ฃผ๋ฌธ์์ ์ ๋ณด๊ฐ ๋ค ์ ํ์์
์ฝ๋ ์์(๊ฐ๋ )
โญ 2๋จ๊ณ. ์คํ๋ง API Gateway์์ ๋ฌด์ํ ์ธ์ฆ ์ฒ๋ฆฌ
API Gateway๋ “์์ฒญ์ ๋ฐ์์ ๊ฐ MSA ์๋น์ค๋ก ์ ๋ฌํ๊ธฐ ์ ์ ํํฐ๋ง” ํ๋ ์ญํ ์ ๋๋ค.
ํด์ผ ํ ์ผ
- JWT๊ฐ ์์ฒญ์ ๋ด๊ฒผ๋์ง ํ์ธ
- JWT๊ฐ ์ ํจํ์ง ๊ฒ์ฆ
- ํ์ํ๋ค๋ฉด Redis์์ “๋ธ๋๋ฆฌ์คํธ ํ ํฐ ์ฌ๋ถ” ์กฐํ
- ๊ฒ์ฆ ๋๋ ์ฌ์ฉ์ ์ ๋ณด(Claims)๋ฅผ HTTP ํค๋์ ๋ด์ MSA๋ก ์ ๋ฌ
์ ์ด๊ฒ ๋ฌด์ํ์ธ๊ฐ?
- Gateway๋ ์ฌ์ฉ์ ์ธ์ ์ ๋ค๊ณ ์์ง ์์
- “JWT๊ฐ ์ ํ ์๋์ง → ๊ฒ์ฆ → ํจ์ค” ์ด ๊ณผ์ ๋ง ๋งค๋ฒ ๋ฐ๋ณต
- ์๋ฌด๊ฒ๋ ๊ธฐ์ตํ์ง ์์
๋น์
- ๊ฑด๋ฌผ ์ ๊ตฌ ๋ณด์ ๊ฒ์ดํธ
- ๋งค๋ฒ ์ ๋ถ์ฆ(=JWT)์ ๋ณด๊ณ ํต๊ณผ์ํด
- “์ด์ ํต๊ณผํ๋?” ์ ๋ ๊ธฐ์ตํ์ง ์์
โญ 3๋จ๊ณ. Spring Boot MSA์์์ ๋ฌด์ํ ์ฒ๋ฆฌ
๊ฐ MSA(ํ์ ์๋น์ค, ์ฃผ๋ฌธ ์๋น์ค, ๊ฒฐ์ ์๋น์ค ๋ฑ)๋ Gateway๊ฐ ๋๊ฒจ์ค ์ ๋ณด๋ง ๋ณด๊ณ ํ๋จํฉ๋๋ค.
๊ตฌํ ๋ฐฉ์
- MSA๋ ์ธ์ ์ ์ฌ์ฉํ์ง ์์ (@EnableWebSecurity์์ ์ธ์ ๋นํ์ฑํ)
- ์์ฒญ ํค๋์ ์๋ userId, role ๋ฑ๋ง ์ฝ๊ณ ์ฒ๋ฆฌ
- ์ํ ์ ์ฅ ํ์ํ๋ฉด PostgreSQL์ ๋ฐ๋ก ์ ์ฅ
์์ ์ค์
์ค์ํ ํฌ์ธํธ
MSA๋ “Gateway๊ฐ ๋๊ธด ๊ฐ ์์ผ๋ฉด ๊ทธ๋ฅ 401 ์๋ฌ”
→ ์ฌ์ฉ์ ์ํ๋ฅผ ์ ๋ ๋ค๊ณ ์์ง ์์
โญ 4๋จ๊ณ. Redis์์ ๊ด๋ฆฌํ๋ “์์ธ์ ์ํ”
๋ฌด์ํ ์์คํ
์ด๋๋ผ๋ ์ผ์์ ์ผ๋ก ํ์ํ ๋ฐ์ดํฐ๊ฐ ์์.
์ด๋ ์๋ฒ์ ์ ์ฅํ๋ฉด “์ํ ์๋ ์๋ฒ”๊ฐ ๋๋ฏ๋ก,
๋ฌด์กฐ๊ฑด Redis ๊ฐ์ ์ธ๋ถ ์ ์ฅ์์ ์ ์ฅํด์ผ ํฉ๋๋ค.
Redis์ ๋ฃ๋ ๋ํ์ ์ธ ์ ๋ณด
- Refresh Token
- JWT ๋ธ๋๋ฆฌ์คํธ(๋ก๊ทธ์์ ์ฒ๋ฆฌ)
- ์์ฒญ ๋น๋(Rate Limiting)
- ๊ฐ๋จํ ์บ์
์ Redis์ธ๊ฐ?
- ๋งค์ฐ ๋น ๋ฅด๊ณ ์ธ๋ฉ๋ชจ๋ฆฌ ๊ธฐ๋ฐ
- ์ฌ๋ฌ ์๋ฒ๊ฐ ๋์์ ์ ๊ทผ ๊ฐ๋ฅ
→ ์๋ฒ๊ฐ ๊ธฐ์ตํ์ง ์์๋ ๋๋ ๊ตฌ์กฐ ์๋ฒฝ ๊ตฌํ
โญ 5๋จ๊ณ. PostgreSQL์์ ์ฅ๊ธฐ ๋ฐ์ดํฐ ์ ์ฅ
PostgreSQL์ ์๊ตฌ ๋ฐ์ดํฐ ์ ์ฅ์์ ๋๋ค.
- ํ์ ์ ๋ณด
- ์ฃผ๋ฌธ ๋ฐ์ดํฐ
- ๊ฒฐ์ /์ํ ์ ๋ณด
์ด๋ฐ ๊ฒ๋ค์ ์ํ(state)๊ฐ ์๋๋ผ **“๋จ์ํ ๋ฐ์ดํฐ”**์ด๋ฏ๋ก
๋ฌด์ํ ์์คํ
์์๋ ์ ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
๐ ์ ์ฒด ์์ฒญ ํ๋ฆ ํ ๋ฒ์ ์์ฝ
์ด๋ค ์๋ฒ๋ “๋ฐฉ๋ฌธ์๋ฅผ ๊ธฐ์ตํ์ง ์๊ธฐ ๋๋ฌธ์”
ํ์ฅ์ฑ · ์๋ · ์์ ์ฑ์ด ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
๐ฉ ์ด๋ณด์๊ฐ ๊ฐ์ฅ ๋ง์ด ํท๊ฐ๋ฆฌ๋ ํฌ์ธํธ ์ ๋ฆฌ
โ ์๋ฒ ๋ฉ๋ชจ๋ฆฌ์ ์ํ ์ ์ฅ
→ ์ธ์
์ฌ์ฉ ๊ธ์ง
→ ์๋ฒ๊ฐ ์ฌ๋ฌ ๋์ผ ๋ ๋ฌธ์ ๋ฐ์
โญ ๋ชจ๋ ์ํ๋ “์ธ๋ถ ์์คํ ”์ ์ ์ฅ
- ์ธ์ฆ ์ํ → 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. ํต์ฌ ์ฐจ์ด์ ์์ฝํ
| ์ฑ๊ฒฉ | ๋๊ธฐ(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) ๊ตฌ์กฐ๋ **“๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ค๋ฅธ ์ผ์ ๊ณ์ํ ์ ์๋ ๊ตฌ์กฐ”**๋ฅผ ๋งํฉ๋๋ค.
์ฝ๊ฒ ๋งํ๋ฉด…
์ผ์ ๋งก๊ฒจ๋๊ณ , ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ฉด ์๋ ค๋ฌ๋ผ๊ณ ํ ๋ค
๋๋ ๋ด ํ ์ผ์ ๊ณ์ํ๋ ๋ฐฉ์
'Project ESG+AI > Tech Basics' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| 35์ผ์ฐจ. IT ๊ฐ๋ ์ ๋ฆฌ (0) | 2025.11.26 |
|---|---|
| 34์ผ์ฐจ. IT ๊ฐ๋ ์ ๋ฆฌ (1) | 2025.11.25 |
| 32์ผ์ฐจ. IT ๊ฐ๋ ์ ๋ฆฌ (1) | 2025.11.21 |
| 31์ผ์ฐจ. IT ๊ฐ๋ ์ ๋ฆฌ (1) | 2025.11.20 |
| 30์ผ์ฐจ. IT ๊ฐ๋ ์ ๋ฆฌ (0) | 2025.11.19 |