๋ณธ ์์ ๋ MSA/DDD/Event Storming/EDA ๋ฅผ ํฌ๊ดํ๋ ๋ถ์/์ค๊ณ/๊ตฌํ/์ด์ ์ ๋จ๊ณ๋ฅผ ์ปค๋ฒํ๋๋ก ๊ตฌ์ฑํ ์์ ์ ๋๋ค. ์ด๋ ํด๋ผ์ฐ๋ ๋ค์ดํฐ๋ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐ์ ์๊ตฌ๋๋ ์ฒดํฌํฌ์ธํธ๋ค์ ํต๊ณผํ๊ธฐ ์ํ ์์ ๋ต์์ ํฌํจํฉ๋๋ค.
- ์ฒดํฌํฌ์ธํธ : https://workflowy.com/s/assessment-check-po/T5YrzcMewfo4J6LW
- ์์ - ์์๋ฐฐ๋ฌ
๋ฐฐ๋ฌ์ ๋ฏผ์กฑ ์ปค๋ฒํ๊ธฐ - https://1sung.tistory.com/106
๊ธฐ๋ฅ์ ์๊ตฌ์ฌํญ
- ๊ณ ๊ฐ์ด ๋ฉ๋ด๋ฅผ ์ ํํ์ฌ ์ฃผ๋ฌธํ๋ค
- ๊ณ ๊ฐ์ด ๊ฒฐ์ ํ๋ค
- ์ฃผ๋ฌธ์ด ๋๋ฉด ์ฃผ๋ฌธ ๋ด์ญ์ด ์ ์ ์์ ์ฃผ์ธ์๊ฒ ์ ๋ฌ๋๋ค
- ์์ ์ฃผ์ธ์ด ํ์ธํ์ฌ ์๋ฆฌํด์ ๋ฐฐ๋ฌ ์ถ๋ฐํ๋ค
- ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์ ์ทจ์ํ ์ ์๋ค
- ์ฃผ๋ฌธ์ด ์ทจ์๋๋ฉด ๋ฐฐ๋ฌ์ด ์ทจ์๋๋ค
- ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์ํ๋ฅผ ์ค๊ฐ์ค๊ฐ ์กฐํํ๋ค
- ์ฃผ๋ฌธ์ํ๊ฐ ๋ฐ๋ ๋ ๋ง๋ค ์นดํก์ผ๋ก ์๋ฆผ์ ๋ณด๋ธ๋ค
๋น๊ธฐ๋ฅ์ ์๊ตฌ์ฌํญ
- ํธ๋์ญ์
- ๊ฒฐ์ ๊ฐ ๋์ง ์์ ์ฃผ๋ฌธ๊ฑด์ ์์ ๊ฑฐ๋๊ฐ ์ฑ๋ฆฝ๋์ง ์์์ผ ํ๋ค Sync ํธ์ถ
- ์ฅ์ ๊ฒฉ๋ฆฌ
- ์์ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ด ์ํ๋์ง ์๋๋ผ๋ ์ฃผ๋ฌธ์ 365์ผ 24์๊ฐ ๋ฐ์ ์ ์์ด์ผ ํ๋ค Async (event-driven), Eventual Consistency
- ๊ฒฐ์ ์์คํ ์ด ๊ณผ์ค๋๋ฉด ์ฌ์ฉ์๋ฅผ ์ ์๋์ ๋ฐ์ง ์๊ณ ๊ฒฐ์ ๋ฅผ ์ ์ํ์ ํ๋๋ก ์ ๋ํ๋ค Circuit breaker, fallback
- ์ฑ๋ฅ
- ๊ณ ๊ฐ์ด ์์ฃผ ์์ ๊ด๋ฆฌ์์ ํ์ธํ ์ ์๋ ๋ฐฐ๋ฌ์ํ๋ฅผ ์ฃผ๋ฌธ์์คํ (ํ๋ก ํธ์๋)์์ ํ์ธํ ์ ์์ด์ผ ํ๋ค CQRS
- ๋ฐฐ๋ฌ์ํ๊ฐ ๋ฐ๋๋๋ง๋ค ์นดํก ๋ฑ์ผ๋ก ์๋ฆผ์ ์ค ์ ์์ด์ผ ํ๋ค Event driven
-
๋ถ์ ์ค๊ณ
-
์ด๋ฒคํธ์คํ ๋ฐ:
- ์คํฐ์ปค ์์๋ณ ๊ฐ์ฒด์ ์๋ฏธ๋ฅผ ์ ๋๋ก ์ดํดํ์ฌ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ์์ ์ฐ๊ณ ์ค๊ณ์ ์ ์ ํ ๋ฐ์ํ๊ณ ์๋๊ฐ?
- ๊ฐ ๋๋ฉ์ธ ์ด๋ฒคํธ๊ฐ ์๋ฏธ์๋ ์์ค์ผ๋ก ์ ์๋์๋๊ฐ?
- ์ด๊ทธ๋ฆฌ๊ฒ์: Command์ Event ๋ค์ ACID ํธ๋์ญ์ ๋จ์์ Aggregate ๋ก ์ ๋๋ก ๋ฌถ์๋๊ฐ?
- ๊ธฐ๋ฅ์ ์๊ตฌ์ฌํญ๊ณผ ๋น๊ธฐ๋ฅ์ ์๊ตฌ์ฌํญ์ ๋๋ฝ ์์ด ๋ฐ์ํ์๋๊ฐ?
-
์๋ธ ๋๋ฉ์ธ, ๋ฐ์ด๋๋ ์ปจํ ์คํธ ๋ถ๋ฆฌ
- ํ๋ณ KPI ์ ๊ด์ฌ์ฌ, ์์ดํ ๋ฐฐํฌ์ฃผ๊ธฐ ๋ฑ์ ๋ฐ๋ฅธ ย Sub-domain ์ด๋ Bounded Context ๋ฅผ ์ ์ ํ ๋ถ๋ฆฌํ์๊ณ ๊ทธ ๋ถ๋ฆฌ ๊ธฐ์ค์ ํฉ๋ฆฌ์ฑ์ด ์ถฉ๋ถํ ์ค๋ช
๋๋๊ฐ?
- ์ ์ด๋ 3๊ฐ ์ด์ ์๋น์ค ๋ถ๋ฆฌ
- ํด๋ฆฌ๊ธ๋ ์ค๊ณ: ๊ฐ ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ ๊ตฌํ ๋ชฉํ์ ๊ธฐ๋ฅ ํน์ฑ์ ๋ฐ๋ฅธ ๊ฐ์์ ๊ธฐ์ Stack ๊ณผ ์ ์ฅ์ ๊ตฌ์กฐ๋ฅผ ๋ค์ํ๊ฒ ์ฑํํ์ฌ ์ค๊ณํ์๋๊ฐ?
- ์๋น์ค ์๋๋ฆฌ์ค ์ค ACID ํธ๋์ญ์ ์ด ํฌ๋ฆฌํฐ์ปฌํ Use ์ผ์ด์ค์ ๋ํ์ฌ ๋ฌด๋ฆฌํ๊ฒ ์๋น์ค๊ฐ ๊ณผ๋คํ๊ฒ ์กฐ๋ฐํ ๋ถ๋ฆฌ๋์ง ์์๋๊ฐ?
- ํ๋ณ KPI ์ ๊ด์ฌ์ฌ, ์์ดํ ๋ฐฐํฌ์ฃผ๊ธฐ ๋ฑ์ ๋ฐ๋ฅธ ย Sub-domain ์ด๋ Bounded Context ๋ฅผ ์ ์ ํ ๋ถ๋ฆฌํ์๊ณ ๊ทธ ๋ถ๋ฆฌ ๊ธฐ์ค์ ํฉ๋ฆฌ์ฑ์ด ์ถฉ๋ถํ ์ค๋ช
๋๋๊ฐ?
-
์ปจํ ์คํธ ๋งคํ / ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ
- ์ ๋ฌด ์ค์์ฑ๊ณผย ๋๋ฉ์ธ๊ฐ ์์ด์ ๊ตฌ๋ถํ ์ ์๋๊ฐ? (Core, Supporting, General Domain)
- Request-Response ๋ฐฉ์๊ณผ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ๋ฐฉ์์ ๊ตฌ๋ถํ์ฌ ์ค๊ณํ ์ ์๋๊ฐ?
- ์ฅ์ ๊ฒฉ๋ฆฌ: ์ํฌํ ์๋น์ค๋ฅผ ์ ๊ฑฐ ํ์ฌ๋ ๊ธฐ์กด ์๋น์ค์ ์ํฅ์ด ์๋๋ก ์ค๊ณํ์๋๊ฐ?
- ์ ๊ท ์๋น์ค๋ฅผ ์ถ๊ฐ ํ์์๋ ๊ธฐ์กด ์๋น์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํฅ์ด ์๋๋ก ์ค๊ณ(์ด๋ ค์๋ ์ํคํ์ฒ)ํ ์ ์๋๊ฐ?
- ์ด๋ฒคํธ์ ํด๋ฆฌ์๋ฅผ ์ฐ๊ฒฐํ๊ธฐ ์ํ Correlation-key ์ฐ๊ฒฐ์ ์ ๋๋ก ์ค๊ณํ์๋๊ฐ?
-
ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ
- ์ค๊ณ ๊ฒฐ๊ณผ์ ๋ฐ๋ฅธ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ ๋ค์ด์ด๊ทธ๋จ์ ์ ๋๋ก ๊ทธ๋ ธ๋๊ฐ?
-
-
๊ตฌํ
-
[DDD] ๋ถ์๋จ๊ณ์์์ ์คํฐ์ปค๋ณ ์์๊ณผ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ์ ๋ฐ๋ผ ๊ตฌํ์ฒด๊ฐ ๋งคํ๋๊ฒ ๊ฐ๋ฐ๋์๋๊ฐ?
- Entity Pattern ๊ณผ Repository Pattern ์ ์ ์ฉํ์ฌ JPA ๋ฅผ ํตํ์ฌ ๋ฐ์ดํฐ ์ ๊ทผ ์ด๋ํฐ๋ฅผ ๊ฐ๋ฐํ์๋๊ฐ
- [ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ] REST Inbound adaptor ์ด์ธ์ gRPC ๋ฑ์ Inbound Adaptor ๋ฅผ ์ถ๊ฐํจ์ ์์ด์ ๋๋ฉ์ธ ๋ชจ๋ธ์ ์์์ ์ฃผ์ง ์๊ณ ์๋ก์ด ํ๋กํ ์ฝ์ ๊ธฐ์กด ๊ตฌํ์ฒด๋ฅผ ์ ์์ํฌ ์ ์๋๊ฐ?
- ๋ถ์๋จ๊ณ์์์ ์ ๋น์ฟผํฐ์ค ๋ญ๊ท์ง (์ ๋ฌดํ์ฅ์์ ์ฐ๋ ์ฉ์ด) ๋ฅผ ์ฌ์ฉํ์ฌ ์์ค์ฝ๋๊ฐ ์์ ๋์๋๊ฐ?
-
Request-Response ๋ฐฉ์์ ์๋น์ค ์ค์ฌ ์ํคํ ์ฒ ๊ตฌํ
- ๋ง์ดํฌ๋ก ์๋น์ค๊ฐ Request-Response ํธ์ถ์ ์์ด ๋์ ์๋น์ค๋ฅผ ์ด๋ ํ ๋ฐฉ์์ผ๋ก ์ฐพ์์ ํธ์ถ ํ์๋๊ฐ? (Service Discovery, REST, FeignClient)
- ์ํท๋ธ๋ ์ด์ปค๋ฅผ ํตํ์ฌย ์ฅ์ ๋ฅผ ๊ฒฉ๋ฆฌ์ํฌ ์ ์๋๊ฐ?
-
์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์ํคํ ์ฒ์ ๊ตฌํ
- ์นดํ์นด๋ฅผ ์ด์ฉํ์ฌ PubSub ์ผ๋ก ํ๋ ์ด์์ ์๋น์ค๊ฐ ์ฐ๋๋์๋๊ฐ?
- Correlation-key: ๊ฐ ์ด๋ฒคํธ ๊ฑด (๋ฉ์์ง)๊ฐ ์ด๋ ํ ํด๋ฆฌ์๋ฅผ ์ฒ๋ฆฌํ ๋ ์ด๋ค ๊ฑด์ ์ฐ๊ฒฐ๋ ์ฒ๋ฆฌ๊ฑด์ธ์ง๋ฅผ ๊ตฌ๋ณํ๊ธฐ ์ํ Correlation-key ์ฐ๊ฒฐ์ ์ ๋๋ก ๊ตฌํ ํ์๋๊ฐ?
- Message Consumer ๋ง์ดํฌ๋ก์๋น์ค๊ฐ ์ฅ์ ์ํฉ์์ ์์ ๋ฐ์ง ๋ชปํ๋ ๊ธฐ์กด ์ด๋ฒคํธ๋ค์ ๋ค์ ์์ ๋ฐ์ ์ฒ๋ฆฌํ๋๊ฐ?
- Scaling-out: Message Consumer ๋ง์ดํฌ๋ก์๋น์ค์ Replica ๋ฅผ ์ถ๊ฐํ์๋ ์ค๋ณต์์ด ์ด๋ฒคํธ๋ฅผ ์์ ํ ์ ์๋๊ฐ
- CQRS: Materialized View ๋ฅผ ๊ตฌํํ์ฌ, ํ ๋ง์ดํฌ๋ก์๋น์ค์ ๋ฐ์ดํฐ ์๋ณธ์ ์ ๊ทผ์์ด(Composite ์๋น์ค๋ ์กฐ์ธSQL ๋ฑ ์์ด) ๋ ๋ด ์๋น์ค์ ํ๋ฉด ๊ตฌ์ฑ๊ณผ ์ฆ์ ์กฐํ๊ฐ ๊ฐ๋ฅํ๊ฐ?
-
ํด๋ฆฌ๊ธ๋ ํ๋ก๊ทธ๋๋ฐ
- ๊ฐ ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ด ํ๋์ด์์ ๊ฐ์์ ๊ธฐ์ Stack ์ผ๋ก ๊ตฌ์ฑ๋์๋๊ฐ?
- ๊ฐ ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ด ๊ฐ์์ ์ ์ฅ์ ๊ตฌ์กฐ๋ฅผ ์์จ์ ์ผ๋ก ์ฑํํ๊ณ ๊ฐ์์ ์ ์ฅ์ ์ ํ (RDB, NoSQL, File System ๋ฑ)์ ์ ํํ์ฌ ๊ตฌํํ์๋๊ฐ?
-
API ๊ฒ์ดํธ์จ์ด
- API GW๋ฅผ ํตํ์ฌ ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ ์ง์ ์ ์ ํต์ผํ ์ ์๋๊ฐ?
- ๊ฒ์ดํธ์จ์ด์ ์ธ์ฆ์๋ฒ(OAuth), JWT ํ ํฐ ์ธ์ฆ์ ํตํ์ฌ ๋ง์ดํฌ๋ก์๋น์ค๋ค์ ๋ณดํธํ ์ ์๋๊ฐ?
-
-
์ด์
- SLA ์ค์
- ์ ํํ๋ง: Liveness Probe ๋ฅผ ํตํ์ฌ ์ด๋ ํ ์๋น์ค์ health ์ํ๊ฐ ์ง์์ ์ผ๋ก ์ ํ๋จ์ ๋ฐ๋ผ ์ด๋ ํ ์๊ณ์น์์ pod ๊ฐ ์ฌ์๋๋ ๊ฒ์ ์ฆ๋ช ํ ์ ์๋๊ฐ?
- ์ํท๋ธ๋ ์ด์ปค, ๋ ์ดํธ๋ฆฌ๋ฐ ๋ฑ์ ํตํ ์ฅ์ ๊ฒฉ๋ฆฌ์ ์ฑ๋ฅํจ์จ์ ๋ํ ์ ์๋๊ฐ?
- ์คํ ์ค์ผ์ผ๋ฌ (HPA) ๋ฅผ ์ค์ ํ์ฌ ํ์ฅ์ ์ด์์ด ๊ฐ๋ฅํ๊ฐ?
- ๋ชจ๋ํฐ๋ง, ์จ๋ฟํ :
- ๋ฌด์ ์ง ์ด์ CI/CD (10)
- Readiness Probe ์ ์ค์ ๊ณผ Rolling update์ ํตํ์ฌ ์ ๊ท ๋ฒ์ ์ด ์์ ํ ์๋น์ค๋ฅผ ๋ฐ์ ์ ์๋ ์ํ์ผ๋ ์ ๊ท๋ฒ์ ์ ์๋น์ค๋ก ์ ํ๋จ์ siege ๋ฑ์ผ๋ก ์ฆ๋ช
- Contract Test : ์๋ํ๋ ๊ฒฝ๊ณ ํ ์คํธ๋ฅผ ํตํ์ฌ ๊ตฌํ ์ค๋ฅ๋ API ๊ณ์ฝ์๋ฐ๋ฅผ ๋ฏธ๋ฆฌ ์ฐจ๋จ ๊ฐ๋ฅํ๊ฐ?
- SLA ์ค์
- MSAEz ๋ก ๋ชจ๋ธ๋งํ ์ด๋ฒคํธ์คํ ๋ฐ ๊ฒฐ๊ณผ: http://msaez.io/#/storming/nZJ2QhwVc4NlVJPbtTkZ8x9jclF2/every/a77281d704710b0c2e6a823b6e6d973a/-M5AV2z--su_i4BfQfeF
- ๊ณผ์ ์ค ๋์ถ๋ ์๋ชป๋ ๋๋ฉ์ธ ์ด๋ฒคํธ๋ค์ ๊ฑธ๋ฌ๋ด๋ ์์
์ ์ํํจ
- ์ฃผ๋ฌธ์>๋ฉ๋ด์นดํ
๊ณ ๋ฆฌ์ ํ๋จ, ์ฃผ๋ฌธ์>๋ฉ๋ด๊ฒ์๋จ : UI ์ ์ด๋ฒคํธ์ด์ง, ์
๋ฌด์ ์ธ ์๋ฏธ์ ์ด๋ฒคํธ๊ฐ ์๋๋ผ์ ์ ์ธ
- app์ Order, store ์ ์ฃผ๋ฌธ์ฒ๋ฆฌ, ๊ฒฐ์ ์ ๊ฒฐ์ ์ด๋ ฅ์ ๊ทธ์ ์ฐ๊ฒฐ๋ command ์ event ๋ค์ ์ํ์ฌ ํธ๋์ญ์
์ด ์ ์ง๋์ด์ผ ํ๋ ๋จ์๋ก ๊ทธ๋ค ๋ผ๋ฆฌ ๋ฌถ์ด์ค
- ๋๋ฉ์ธ ์์ด ๋ถ๋ฆฌ
- Core Domain: app(front), store : ์์ด์๋ ์๋ ํต์ฌ ์๋น์ค์ด๋ฉฐ, ์ฐ๊ฒฌ Up-time SLA ์์ค์ 99.999% ๋ชฉํ, ๋ฐฐํฌ์ฃผ๊ธฐ๋ app ์ ๊ฒฝ์ฐ 1์ฃผ์ผ 1ํ ๋ฏธ๋ง, store ์ ๊ฒฝ์ฐ 1๊ฐ์ 1ํ ๋ฏธ๋ง
- Supporting Domain: marketing, customer : ๊ฒฝ์๋ ฅ์ ๋ด๊ธฐ์ํ ์๋น์ค์ด๋ฉฐ, SLA ์์ค์ ์ฐ๊ฐ 60% ์ด์ uptime ๋ชฉํ, ๋ฐฐํฌ์ฃผ๊ธฐ๋ ๊ฐ ํ์ ์์จ์ด๋ ํ์ค ์คํ๋ฆฐํธ ์ฃผ๊ธฐ๊ฐ 1์ฃผ์ผ ์ด๋ฏ๋ก 1์ฃผ์ผ 1ํ ์ด์์ ๊ธฐ์ค์ผ๋ก ํจ.
- General Domain: pay : ๊ฒฐ์ ์๋น์ค๋ก 3rd Party ์ธ๋ถ ์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฒฝ์๋ ฅ์ด ๋์ (ํํฌ์์ผ๋ก ์ดํ ์ ํํ ์์ )
ํด๋ฆฌ์ ๋ถ์ฐฉ (๊ดํธ๋ ์ํ์ฃผ์ฒด, ํด๋ฆฌ์ ๋ถ์ฐฉ์ ๋์งธ๋จ๊ณ์์ ํด๋๋ ์๊ด ์์. ์ ์ฒด ์ฐ๊ณ๊ฐ ์ด๊ธฐ์ ๋๋ฌ๋จ)
- View Model ์ถ๊ฐ
- ๊ณ ๊ฐ์ด ๋ฉ๋ด๋ฅผ ์ ํํ์ฌ ์ฃผ๋ฌธํ๋ค (ok)
- ๊ณ ๊ฐ์ด ๊ฒฐ์ ํ๋ค (ok)
- ์ฃผ๋ฌธ์ด ๋๋ฉด ์ฃผ๋ฌธ ๋ด์ญ์ด ์
์ ์์ ์ฃผ์ธ์๊ฒ ์ ๋ฌ๋๋ค (ok)
- ์์ ์ฃผ์ธ์ด ํ์ธํ์ฌ ์๋ฆฌํด์ ๋ฐฐ๋ฌ ์ถ๋ฐํ๋ค (ok)
- ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์ ์ทจ์ํ ์ ์๋ค (ok) - ์ฃผ๋ฌธ์ด ์ทจ์๋๋ฉด ๋ฐฐ๋ฌ์ด ์ทจ์๋๋ค (ok) - ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์ํ๋ฅผ ์ค๊ฐ์ค๊ฐ ์กฐํํ๋ค (View-green sticker ์ ์ถ๊ฐ๋ก ok) - ์ฃผ๋ฌธ์ํ๊ฐ ๋ฐ๋ ๋ ๋ง๋ค ์นดํก์ผ๋ก ์๋ฆผ์ ๋ณด๋ธ๋ค (?)
- ์์ ๋ ๋ชจ๋ธ์ ๋ชจ๋ ์๊ตฌ์ฌํญ์ ์ปค๋ฒํจ.
- ๋ง์ดํฌ๋ก ์๋น์ค๋ฅผ ๋๋๋๋ ์๋๋ฆฌ์ค์ ๋ํ ํธ๋์ญ์
์ฒ๋ฆฌ
- ๊ณ ๊ฐ ์ฃผ๋ฌธ์ ๊ฒฐ์ ์ฒ๋ฆฌ: ๊ฒฐ์ ๊ฐ ์๋ฃ๋์ง ์์ ์ฃผ๋ฌธ์ ์ ๋ ๋ฐ์ง ์๋๋ค๋ ๊ฒฝ์์์ ์ค๋ ์ ๋
(?) ์ ๋ฐ๋ผ, ACID ํธ๋์ญ์
์ ์ฉ. ์ฃผ๋ฌธ์๋ฃ์ ๊ฒฐ์ ์ฒ๋ฆฌ์ ๋ํด์๋ Request-Response ๋ฐฉ์ ์ฒ๋ฆฌ
- ๊ฒฐ์ ์๋ฃ์ ์ ์ฃผ์ฐ๊ฒฐ ๋ฐ ๋ฐฐ์ก์ฒ๋ฆฌ: App(front) ์์ Store ๋ง์ดํฌ๋ก์๋น์ค๋ก ์ฃผ๋ฌธ์์ฒญ์ด ์ ๋ฌ๋๋ ๊ณผ์ ์ ์์ด์ Store ๋ง์ดํฌ๋ก ์๋น์ค๊ฐ ๋ณ๋์ ๋ฐฐํฌ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ Eventual Consistency ๋ฐฉ์์ผ๋ก ํธ๋์ญ์
์ฒ๋ฆฌํจ.
- ๋๋จธ์ง ๋ชจ๋ inter-microservice ํธ๋์ญ์
: ์ฃผ๋ฌธ์ํ, ๋ฐฐ๋ฌ์ํ ๋ฑ ๋ชจ๋ ์ด๋ฒคํธ์ ๋ํด ์นดํก์ ์ฒ๋ฆฌํ๋ ๋ฑ, ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ ์์ ์ด ํฌ๋ฆฌํฐ์ปฌํ์ง ์์ ๋ชจ๋ ๊ฒฝ์ฐ๊ฐ ๋๋ถ๋ถ์ด๋ผ ํ๋จ, Eventual Consistency ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฑํํจ.
- Chris Richardson, MSA Patterns ์ฐธ๊ณ ํ์ฌ Inbound adaptor์ Outbound adaptor๋ฅผ ๊ตฌ๋ถํจ
- ํธ์ถ๊ด๊ณ์์ PubSub ๊ณผ Req/Resp ๋ฅผ ๊ตฌ๋ถํจ
- ์๋ธ ๋๋ฉ์ธ๊ณผ ๋ฐ์ด๋๋ ์ปจํ
์คํธ์ ๋ถ๋ฆฌ: ๊ฐ ํ์ KPI ๋ณ๋ก ์๋์ ๊ฐ์ด ๊ด์ฌ ๊ตฌํ ์คํ ๋ฆฌ๋ฅผ ๋๋ ๊ฐ์ง
๋ถ์/์ค๊ณ ๋จ๊ณ์์ ๋์ถ๋ ํฅ์ฌ๊ณ ๋ ์ํคํ ์ฒ์ ๋ฐ๋ผ, ๊ฐ BC๋ณ๋ก ๋๋ณ๋๋ ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ ์คํ๋ง๋ถํธ์ ํ์ด์ ์ผ๋ก ๊ตฌํํ์๋ค. ๊ตฌํํ ๊ฐ ์๋น์ค๋ฅผ ๋ก์ปฌ์์ ์คํํ๋ ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ๋ค (๊ฐ์์ ํฌํธ๋๋ฒ๋ 8081 ~ 808n ์ด๋ค)
cd app
mvn spring-boot:run
cd pay
mvn spring-boot:run
cd store
mvn spring-boot:run
cd customer
python policy-handler.py
- ๊ฐ ์๋น์ค๋ด์ ๋์ถ๋ ํต์ฌ Aggregate Root ๊ฐ์ฒด๋ฅผ Entity ๋ก ์ ์ธํ์๋ค: (์์๋ pay ๋ง์ดํฌ๋ก ์๋น์ค). ์ด๋ ๊ฐ๋ฅํ ํ์ ์์ ์ฌ์ฉํ๋ ์ธ์ด (์ ๋น์ฟผํฐ์ค ๋ญ๊ท์ง)๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ ค๊ณ ๋ ธ๋ ฅํ๋ค. ํ์ง๋ง, ์ผ๋ถ ๊ตฌํ์ ์์ด์ ์๋ฌธ์ด ์๋ ๊ฒฝ์ฐ๋ ์คํ์ด ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ ๋๋ฌธ์ ๊ณ์ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ ์๋๊ฒ ๊ฐ๋ค. (Maven pom.xml, Kafka์ topic id, FeignClient ์ ์๋น์ค id ๋ฑ์ ํ๊ธ๋ก ์๋ณ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ํ์ธํ์๋ค)
package fooddelivery;
import javax.persistence.*;
import org.springframework.beans.BeanUtils;
import java.util.List;
@Entity
@Table(name="๊ฒฐ์ ์ด๋ ฅ_table")
public class ๊ฒฐ์ ์ด๋ ฅ {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String orderId;
private Double ๊ธ์ก;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public Double get๊ธ์ก() {
return ๊ธ์ก;
}
public void set๊ธ์ก(Double ๊ธ์ก) {
this.๊ธ์ก = ๊ธ์ก;
}
}
- Entity Pattern ๊ณผ Repository Pattern ์ ์ ์ฉํ์ฌ JPA ๋ฅผ ํตํ์ฌ ๋ค์ํ ๋ฐ์ดํฐ์์ค ์ ํ (RDB or NoSQL) ์ ๋ํ ๋ณ๋์ ์ฒ๋ฆฌ๊ฐ ์๋๋ก ๋ฐ์ดํฐ ์ ๊ทผ ์ด๋ํฐ๋ฅผ ์๋ ์์ฑํ๊ธฐ ์ํ์ฌ Spring Data REST ์ RestRepository ๋ฅผ ์ ์ฉํ์๋ค
package fooddelivery;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface ๊ฒฐ์ ์ด๋ ฅRepository extends PagingAndSortingRepository<๊ฒฐ์ ์ด๋ ฅ, Long>{
}
- ์ ์ฉ ํ REST API ์ ํ ์คํธ
# app ์๋น์ค์ ์ฃผ๋ฌธ์ฒ๋ฆฌ
http localhost:8081/orders item="ํต๋ญ"
# store ์๋น์ค์ ๋ฐฐ๋ฌ์ฒ๋ฆฌ
http localhost:8083/์ฃผ๋ฌธ์ฒ๋ฆฌs orderId=1
# ์ฃผ๋ฌธ ์ํ ํ์ธ
http localhost:8081/orders/1
์ฑํ๋ฐํธ (app) ๋ ์๋น์ค ํน์ฑ์ ๋ง์ ์ฌ์ฉ์์ ์ ์ ๊ณผ ์ํ ์ ๋ณด์ ๋ค์ํ ์ฝํ ์ธ ๋ฅผ ์ ์ฅํด์ผ ํ๋ ํน์ง์ผ๋ก ์ธํด RDB ๋ณด๋ค๋ Document DB / NoSQL ๊ณ์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ Mongo DB ๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ํ์๋ค. ์ด๋ฅผ ์ํด order ์ ์ ์ธ์๋ @Entity ๊ฐ ์๋ @Document ๋ก ๋งํน๋์์ผ๋ฉฐ, ๋ณ๋ค๋ฅธ ์์ ์์ด ๊ธฐ์กด์ Entity Pattern ๊ณผ Repository Pattern ์ ์ฉ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ํ์ ์ค์ (application.yml) ๋ง์ผ๋ก MongoDB ์ ๋ถ์ฐฉ์์ผฐ๋ค
# Order.java
package fooddelivery;
@Document
public class Order {
private String id; // mongo db ์ ์ฉ์์ id ๋ ๊ณ ์ ๊ฐ์ผ๋ก key๊ฐ ์๋ ๋ฐ๊ธ๋๋ ํ๋๊ธฐ ๋๋ฌธ์ @Id ๋ @GeneratedValue ๋ฅผ ์ฃผ์ง ์์๋ ๋๋ค.
private String item;
private Integer ์๋;
}
# ์ฃผ๋ฌธRepository.java
package fooddelivery;
public interface ์ฃผ๋ฌธRepository extends JpaRepository<Order, UUID>{
}
# application.yml
data:
mongodb:
host: mongodb.default.svc.cluster.local
database: mongo-example
๊ณ ๊ฐ๊ด๋ฆฌ ์๋น์ค(customer)์ ์๋๋ฆฌ์ค์ธ ์ฃผ๋ฌธ์ํ, ๋ฐฐ๋ฌ์ํ ๋ณ๊ฒฝ์ ๋ฐ๋ผ ๊ณ ๊ฐ์๊ฒ ์นดํก๋ฉ์์ง ๋ณด๋ด๋ ๊ธฐ๋ฅ์ ๊ตฌํ ํํธ๋ ํด๋น ํ์ด python ์ ์ด์ฉํ์ฌ ๊ตฌํํ๊ธฐ๋ก ํ์๋ค. ํด๋น ํ์ด์ฌ ๊ตฌํ์ฒด๋ ๊ฐ ์ด๋ฒคํธ๋ฅผ ์์ ํ์ฌ ์ฒ๋ฆฌํ๋ Kafka consumer ๋ก ๊ตฌํ๋์๊ณ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค:
from flask import Flask
from redis import Redis, RedisError
from kafka import KafkaConsumer
import os
import socket
# To consume latest messages and auto-commit offsets
consumer = KafkaConsumer('fooddelivery',
group_id='',
bootstrap_servers=['localhost:9092'])
for message in consumer:
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))
# ์นดํกํธ์ถ API
ํ์ด์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ปดํ์ผํ๊ณ ์คํํ๊ธฐ ์ํ ๋์ปคํ์ผ์ ์๋์ ๊ฐ๋ค (์ด์๋จ๊ณ์์ ํ ์ผ์ธ๊ฐ? ์๋๋ค ์ฌ๊ธฐ ๊น์ง๊ฐ ๊ฐ๋ฐ์๊ฐ ํ ์ผ์ด๋ค. Immutable Image):
FROM python:2.7-slim
WORKDIR /app
ADD . /app
RUN pip install --trusted-host pypi.python.org -r requirements.txt
ENV NAME World
EXPOSE 8090
CMD ["python", "policy-handler.py"]
๋ถ์๋จ๊ณ์์์ ์กฐ๊ฑด ์ค ํ๋๋ก ์ฃผ๋ฌธ(app)->๊ฒฐ์ (pay) ๊ฐ์ ํธ์ถ์ ๋๊ธฐ์ ์ผ๊ด์ฑ์ ์ ์งํ๋ ํธ๋์ญ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ๋ก ํ์๋ค. ํธ์ถ ํ๋กํ ์ฝ์ ์ด๋ฏธ ์์ Rest Repository ์ ์ํด ๋ ธ์ถ๋์ด์๋ REST ์๋น์ค๋ฅผ FeignClient ๋ฅผ ์ด์ฉํ์ฌ ํธ์ถํ๋๋ก ํ๋ค.
- ๊ฒฐ์ ์๋น์ค๋ฅผ ํธ์ถํ๊ธฐ ์ํ์ฌ Stub๊ณผ (FeignClient) ๋ฅผ ์ด์ฉํ์ฌ Service ๋ํ ์ธํฐํ์ด์ค (Proxy) ๋ฅผ ๊ตฌํ
# (app) ๊ฒฐ์ ์ด๋ ฅService.java
package fooddelivery.external;
@FeignClient(name="pay", url="http://localhost:8082")//, fallback = ๊ฒฐ์ ์ด๋ ฅServiceFallback.class)
public interface ๊ฒฐ์ ์ด๋ ฅService {
@RequestMapping(method= RequestMethod.POST, path="/๊ฒฐ์ ์ด๋ ฅs")
public void ๊ฒฐ์ (@RequestBody ๊ฒฐ์ ์ด๋ ฅ pay);
}
- ์ฃผ๋ฌธ์ ๋ฐ์ ์งํ(@PostPersist) ๊ฒฐ์ ๋ฅผ ์์ฒญํ๋๋ก ์ฒ๋ฆฌ
# Order.java (Entity)
@PostPersist
public void onPostPersist(){
fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ pay = new fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ();
pay.setOrderId(getOrderId());
Application.applicationContext.getBean(fooddelivery.external.๊ฒฐ์ ์ด๋ ฅService.class)
.๊ฒฐ์ (pay);
}
- ๋๊ธฐ์ ํธ์ถ์์๋ ํธ์ถ ์๊ฐ์ ๋ฐ๋ฅธ ํ์ ์ปคํ๋ง์ด ๋ฐ์ํ๋ฉฐ, ๊ฒฐ์ ์์คํ ์ด ์ฅ์ ๊ฐ ๋๋ฉด ์ฃผ๋ฌธ๋ ๋ชป๋ฐ๋๋ค๋ ๊ฒ์ ํ์ธ:
# ๊ฒฐ์ (pay) ์๋น์ค๋ฅผ ์ ์ ๋ด๋ ค๋์ (ctrl+c)
#์ฃผ๋ฌธ์ฒ๋ฆฌ
http localhost:8081/orders item=ํต๋ญ storeId=1 #Fail
http localhost:8081/orders item=ํผ์ storeId=2 #Fail
#๊ฒฐ์ ์๋น์ค ์ฌ๊ธฐ๋
cd ๊ฒฐ์
mvn spring-boot:run
#์ฃผ๋ฌธ์ฒ๋ฆฌ
http localhost:8081/orders item=ํต๋ญ storeId=1 #Success
http localhost:8081/orders item=ํผ์ storeId=2 #Success
- ๋ํ ๊ณผ๋ํ ์์ฒญ์์ ์๋น์ค ์ฅ์ ๊ฐ ๋๋ฏธ๋ ธ ์ฒ๋ผ ๋ฒ์ด์ง ์ ์๋ค. (์ํท๋ธ๋ ์ด์ปค, ํด๋ฐฑ ์ฒ๋ฆฌ๋ ์ด์๋จ๊ณ์์ ์ค๋ช ํ๋ค.)
๊ฒฐ์ ๊ฐ ์ด๋ฃจ์ด์ง ํ์ ์์ ์์คํ ์ผ๋ก ์ด๋ฅผ ์๋ ค์ฃผ๋ ํ์๋ ๋๊ธฐ์์ด ์๋๋ผ ๋น ๋๊ธฐ์์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์์ ์์คํ ์ ์ฒ๋ฆฌ๋ฅผ ์ํ์ฌ ๊ฒฐ์ ์ฃผ๋ฌธ์ด ๋ธ๋กํน ๋์ง ์์๋๋ก ์ฒ๋ฆฌํ๋ค.
- ์ด๋ฅผ ์ํ์ฌ ๊ฒฐ์ ์ด๋ ฅ์ ๊ธฐ๋ก์ ๋จ๊ธด ํ์ ๊ณง๋ฐ๋ก ๊ฒฐ์ ์น์ธ์ด ๋์๋ค๋ ๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ์นดํ์นด๋ก ์ก์ถํ๋ค(Publish)
package fooddelivery;
@Entity
@Table(name="๊ฒฐ์ ์ด๋ ฅ_table")
public class ๊ฒฐ์ ์ด๋ ฅ {
...
@PrePersist
public void onPrePersist(){
๊ฒฐ์ ์น์ธ๋จ ๊ฒฐ์ ์น์ธ๋จ = new ๊ฒฐ์ ์น์ธ๋จ();
BeanUtils.copyProperties(this, ๊ฒฐ์ ์น์ธ๋จ);
๊ฒฐ์ ์น์ธ๋จ.publish();
}
}
- ์์ ์๋น์ค์์๋ ๊ฒฐ์ ์น์ธ ์ด๋ฒคํธ์ ๋ํด์ ์ด๋ฅผ ์์ ํ์ฌ ์์ ์ ์ ์ฑ ์ ์ฒ๋ฆฌํ๋๋ก PolicyHandler ๋ฅผ ๊ตฌํํ๋ค:
package fooddelivery;
...
@Service
public class PolicyHandler{
@StreamListener(KafkaProcessor.INPUT)
public void whenever๊ฒฐ์ ์น์ธ๋จ_์ฃผ๋ฌธ์ ๋ณด๋ฐ์(@Payload ๊ฒฐ์ ์น์ธ๋จ ๊ฒฐ์ ์น์ธ๋จ){
if(๊ฒฐ์ ์น์ธ๋จ.isMe()){
System.out.println("##### listener ์ฃผ๋ฌธ์ ๋ณด๋ฐ์ : " + ๊ฒฐ์ ์น์ธ๋จ.toJson());
// ์ฃผ๋ฌธ ์ ๋ณด๋ฅผ ๋ฐ์์ผ๋, ์๋ฆฌ๋ฅผ ์ฌ์ฌ ์์ํด์ผ์ง..
}
}
}
์ค์ ๊ตฌํ์ ํ์๋ฉด, ์นดํก ๋ฑ์ผ๋ก ์ ์ฃผ๋ ๋ ธํฐ๋ฅผ ๋ฐ๊ณ , ์๋ฆฌ๋ฅผ ๋ง์นํ, ์ฃผ๋ฌธ ์ํ๋ฅผ UI์ ์ ๋ ฅํ ํ ๋, ์ฐ์ ์ฃผ๋ฌธ์ ๋ณด๋ฅผ DB์ ๋ฐ์๋์ ํ, ์ดํ ์ฒ๋ฆฌ๋ ํด๋น Aggregate ๋ด์์ ํ๋ฉด ๋๊ฒ ๋ค.:
@Autowired ์ฃผ๋ฌธ๊ด๋ฆฌRepository ์ฃผ๋ฌธ๊ด๋ฆฌRepository;
@StreamListener(KafkaProcessor.INPUT)
public void whenever๊ฒฐ์ ์น์ธ๋จ_์ฃผ๋ฌธ์ ๋ณด๋ฐ์(@Payload ๊ฒฐ์ ์น์ธ๋จ ๊ฒฐ์ ์น์ธ๋จ){
if(๊ฒฐ์ ์น์ธ๋จ.isMe()){
์นดํก์ ์ก(" ์ฃผ๋ฌธ์ด ์์ด์! : " + ๊ฒฐ์ ์น์ธ๋จ.toString(), ์ฃผ๋ฌธ.getStoreId());
์ฃผ๋ฌธ๊ด๋ฆฌ ์ฃผ๋ฌธ = new ์ฃผ๋ฌธ๊ด๋ฆฌ();
์ฃผ๋ฌธ.setId(๊ฒฐ์ ์น์ธ๋จ.getOrderId());
์ฃผ๋ฌธ๊ด๋ฆฌRepository.save(์ฃผ๋ฌธ);
}
}
์์ ์์คํ ์ ์ฃผ๋ฌธ/๊ฒฐ์ ์ ์์ ํ ๋ถ๋ฆฌ๋์ด์์ผ๋ฉฐ, ์ด๋ฒคํธ ์์ ์ ๋ฐ๋ผ ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์, ์์ ์์คํ ์ด ์ ์ง๋ณด์๋ก ์ธํด ์ ์ ๋ด๋ ค๊ฐ ์ํ๋ผ๋ ์ฃผ๋ฌธ์ ๋ฐ๋๋ฐ ๋ฌธ์ ๊ฐ ์๋ค:
# ์์ ์๋น์ค (store) ๋ฅผ ์ ์ ๋ด๋ ค๋์ (ctrl+c)
#์ฃผ๋ฌธ์ฒ๋ฆฌ
http localhost:8081/orders item=ํต๋ญ storeId=1 #Success
http localhost:8081/orders item=ํผ์ storeId=2 #Success
#์ฃผ๋ฌธ์ํ ํ์ธ
http localhost:8080/orders # ์ฃผ๋ฌธ์ํ ์๋ฐ๋ ํ์ธ
#์์ ์๋น์ค ๊ธฐ๋
cd ์์
mvn spring-boot:run
#์ฃผ๋ฌธ์ํ ํ์ธ
http localhost:8080/orders # ๋ชจ๋ ์ฃผ๋ฌธ์ ์ํ๊ฐ "๋ฐฐ์ก๋จ"์ผ๋ก ํ์ธ
๊ฐ ๊ตฌํ์ฒด๋ค์ ๊ฐ์์ source repository ์ ๊ตฌ์ฑ๋์๊ณ , ์ฌ์ฉํ CI/CD ํ๋ซํผ์ GCP๋ฅผ ์ฌ์ฉํ์์ผ๋ฉฐ, pipeline build script ๋ ๊ฐ ํ๋ก์ ํธ ํด๋ ์ดํ์ cloudbuild.yml ์ ํฌํจ๋์๋ค.
- ์ํท ๋ธ๋ ์ดํน ํ๋ ์์ํฌ์ ์ ํ: Spring FeignClient + Hystrix ์ต์ ์ ์ฌ์ฉํ์ฌ ๊ตฌํํจ
์๋๋ฆฌ์ค๋ ๋จ๋ง์ฑ(app)-->๊ฒฐ์ (pay) ์์ ์ฐ๊ฒฐ์ RESTful Request/Response ๋ก ์ฐ๋ํ์ฌ ๊ตฌํ์ด ๋์ด์๊ณ , ๊ฒฐ์ ์์ฒญ์ด ๊ณผ๋ํ ๊ฒฝ์ฐ CB ๋ฅผ ํตํ์ฌ ์ฅ์ ๊ฒฉ๋ฆฌ.
- Hystrix ๋ฅผ ์ค์ : ์์ฒญ์ฒ๋ฆฌ ์ฐ๋ ๋์์ ์ฒ๋ฆฌ์๊ฐ์ด 610 ๋ฐ๋ฆฌ๊ฐ ๋์ด์๊ธฐ ์์ํ์ฌ ์ด๋์ ๋ ์ ์ง๋๋ฉด CB ํ๋ก๊ฐ ๋ซํ๋๋ก (์์ฒญ์ ๋น ๋ฅด๊ฒ ์คํจ์ฒ๋ฆฌ, ์ฐจ๋จ) ์ค์
# application.yml
feign:
hystrix:
enabled: true
hystrix:
command:
# ์ ์ญ์ค์
default:
execution.isolation.thread.timeoutInMilliseconds: 610
- ํผํธ์ถ ์๋น์ค(๊ฒฐ์ :pay) ์ ์์ ๋ถํ ์ฒ๋ฆฌ - 400 ๋ฐ๋ฆฌ์์ ์ฆ๊ฐ 220 ๋ฐ๋ฆฌ ์ ๋ ์๋ค๊ฐ๋ค ํ๊ฒ
# (pay) ๊ฒฐ์ ์ด๋ ฅ.java (Entity)
@PrePersist
public void onPrePersist(){ //๊ฒฐ์ ์ด๋ ฅ์ ์ ์ฅํ ํ ์ ๋นํ ์๊ฐ ๋๊ธฐ
...
try {
Thread.currentThread().sleep((long) (400 + Math.random() * 220));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- ๋ถํํ ์คํฐ siege ํด์ ํตํ ์ํท ๋ธ๋ ์ด์ปค ๋์ ํ์ธ:
- ๋์์ฌ์ฉ์ 100๋ช
- 60์ด ๋์ ์ค์
$ siege -c100 -t60S -r10 --content-type "application/json" 'http://localhost:8081/orders POST {"item": "chicken"}'
** SIEGE 4.0.5
** Preparing 100 concurrent users for battle.
The server is now under siege...
HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.73 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.75 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.77 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.97 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.81 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.87 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.12 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.16 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.17 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.26 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.25 secs: 207 bytes ==> POST http://localhost:8081/orders
* ์์ฒญ์ด ๊ณผ๋ํ์ฌ CB๋ฅผ ๋์ํจ ์์ฒญ์ ์ฐจ๋จ
HTTP/1.1 500 1.29 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.24 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.23 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.42 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 2.08 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.29 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.24 secs: 248 bytes ==> POST http://localhost:8081/orders
* ์์ฒญ์ ์ด๋์ ๋ ๋๋ ค๋ณด๋ด๊ณ ๋๋, ๊ธฐ์กด์ ๋ฐ๋ฆฐ ์ผ๋ค์ด ์ฒ๋ฆฌ๋์๊ณ , ํ๋ก๋ฅผ ๋ซ์ ์์ฒญ์ ๋ค์ ๋ฐ๊ธฐ ์์
HTTP/1.1 201 1.46 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.33 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.36 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.63 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.65 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.68 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.69 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.71 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.71 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.74 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.76 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 1.79 secs: 207 bytes ==> POST http://localhost:8081/orders
* ๋ค์ ์์ฒญ์ด ์์ด๊ธฐ ์์ํ์ฌ ๊ฑด๋น ์ฒ๋ฆฌ์๊ฐ์ด 610 ๋ฐ๋ฆฌ๋ฅผ ์ด์ง ๋๊ธฐ๊ธฐ ์์ => ํ๋ก ์ด๊ธฐ => ์์ฒญ ์คํจ์ฒ๋ฆฌ
HTTP/1.1 500 1.93 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.92 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 1.93 secs: 248 bytes ==> POST http://localhost:8081/orders
* ์๊ฐ๋ณด๋ค ๋นจ๋ฆฌ ์ํ ํธ์ ๋จ - (๊ฑด๋น (์ฐ๋ ๋๋น) ์ฒ๋ฆฌ์๊ฐ์ด 610 ๋ฐ๋ฆฌ ๋ฏธ๋ง์ผ๋ก ํ๋ณต) => ์์ฒญ ์๋ฝ
HTTP/1.1 201 2.24 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.32 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.16 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.21 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.29 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.30 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.38 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.59 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.61 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.62 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 2.64 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.01 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.27 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.33 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.45 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.52 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.57 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.70 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders
* ์ดํ ์ด๋ฌํ ํจํด์ด ๊ณ์ ๋ฐ๋ณต๋๋ฉด์ ์์คํ
์ ๋๋ฏธ๋
ธ ํ์์ด๋ ์์ ์๋ชจ์ ํญ์ฃผ ์์ด ์ ์ด์๋จ
HTTP/1.1 500 4.76 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.23 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.76 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.74 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.82 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.82 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.84 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.66 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 5.03 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.22 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.19 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.18 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.65 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 5.13 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.84 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.25 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.25 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.80 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.87 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.33 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.86 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.96 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.34 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 500 4.04 secs: 248 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.50 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.95 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.54 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 4.65 secs: 207 bytes ==> POST http://localhost:8081/orders
:
:
Transactions: 1025 hits
Availability: 63.55 %
Elapsed time: 59.78 secs
Data transferred: 0.34 MB
Response time: 5.60 secs
Transaction rate: 17.15 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 96.02
Successful transactions: 1025
Failed transactions: 588
Longest transaction: 9.20
Shortest transaction: 0.00
-
์ด์์์คํ ์ ์ฃฝ์ง ์๊ณ ์ง์์ ์ผ๋ก CB ์ ์ํ์ฌ ์ ์ ํ ํ๋ก๊ฐ ์ด๋ฆผ๊ณผ ๋ซํ์ด ๋ฒ์ด์ง๋ฉด์ ์์์ ๋ณดํธํ๊ณ ์์์ ๋ณด์ฌ์ค. ํ์ง๋ง, 63.55% ๊ฐ ์ฑ๊ณตํ์๊ณ , 46%๊ฐ ์คํจํ๋ค๋ ๊ฒ์ ๊ณ ๊ฐ ์ฌ์ฉ์ฑ์ ์์ด ์ข์ง ์๊ธฐ ๋๋ฌธ์ Retry ์ค์ ๊ณผ ๋์ Scale out (replica์ ์๋์ ์ถ๊ฐ,HPA) ์ ํตํ์ฌ ์์คํ ์ ํ์ฅ ํด์ฃผ๋ ํ์์ฒ๋ฆฌ๊ฐ ํ์.
-
Retry ์ ์ค์ (istio)
-
Availability ๊ฐ ๋์์ง ๊ฒ์ ํ์ธ (siege)
์์ CB ๋ ์์คํ ์ ์์ ๋๊ฒ ์ด์ํ ์ ์๊ฒ ํด์คฌ์ง๋ง ์ฌ์ฉ์์ ์์ฒญ์ 100% ๋ฐ์๋ค์ฌ์ฃผ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ์ด์ ๋ํ ๋ณด์์ฑ ์ผ๋ก ์๋ํ๋ ํ์ฅ ๊ธฐ๋ฅ์ ์ ์ฉํ๊ณ ์ ํ๋ค.
- ๊ฒฐ์ ์๋น์ค์ ๋ํ replica ๋ฅผ ๋์ ์ผ๋ก ๋๋ ค์ฃผ๋๋ก HPA ๋ฅผ ์ค์ ํ๋ค. ์ค์ ์ CPU ์ฌ์ฉ๋์ด 15ํ๋ก๋ฅผ ๋์ด์๋ฉด replica ๋ฅผ 10๊ฐ๊น์ง ๋๋ ค์ค๋ค:
kubectl autoscale deploy pay --min=1 --max=10 --cpu-percent=15
- CB ์์ ํ๋ ๋ฐฉ์๋๋ก ์ํฌ๋ก๋๋ฅผ 2๋ถ ๋์ ๊ฑธ์ด์ค๋ค.
siege -c100 -t120S -r10 --content-type "application/json" 'http://localhost:8081/orders POST {"item": "chicken"}'
- ์คํ ์ค์ผ์ผ์ด ์ด๋ป๊ฒ ๋๊ณ ์๋์ง ๋ชจ๋ํฐ๋ง์ ๊ฑธ์ด๋๋ค:
kubectl get deploy pay -w
- ์ด๋์ ๋ ์๊ฐ์ด ํ๋ฅธ ํ (์ฝ 30์ด) ์ค์ผ์ผ ์์์ด ๋ฒ์ด์ง๋ ๊ฒ์ ํ์ธํ ์ ์๋ค:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
pay 1 1 1 1 17s
pay 1 2 1 1 45s
pay 1 4 1 1 1m
:
- siege ์ ๋ก๊ทธ๋ฅผ ๋ณด์๋ ์ ์ฒด์ ์ธ ์ฑ๊ณต๋ฅ ์ด ๋์์ง ๊ฒ์ ํ์ธ ํ ์ ์๋ค.
Transactions: 5078 hits
Availability: 92.45 %
Elapsed time: 120 secs
Data transferred: 0.34 MB
Response time: 5.60 secs
Transaction rate: 17.15 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 96.02
- ๋จผ์ ๋ฌด์ ์ง ์ฌ๋ฐฐํฌ๊ฐ 100% ๋๋ ๊ฒ์ธ์ง ํ์ธํ๊ธฐ ์ํด์ Autoscaler ์ด๋ CB ์ค์ ์ ์ ๊ฑฐํจ
- seige ๋ก ๋ฐฐํฌ์์ ์ง์ ์ ์ํฌ๋ก๋๋ฅผ ๋ชจ๋ํฐ๋ง ํจ.
siege -c100 -t120S -r10 --content-type "application/json" 'http://localhost:8081/orders POST {"item": "chicken"}'
** SIEGE 4.0.5
** Preparing 100 concurrent users for battle.
The server is now under siege...
HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders
HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders
:
- ์๋ฒ์ ์ผ๋ก์ ๋ฐฐํฌ ์์
kubectl set image ...
- seige ์ ํ๋ฉด์ผ๋ก ๋์ด๊ฐ์ Availability ๊ฐ 100% ๋ฏธ๋ง์ผ๋ก ๋จ์ด์ก๋์ง ํ์ธ
Transactions: 3078 hits
Availability: 70.45 %
Elapsed time: 120 secs
Data transferred: 0.34 MB
Response time: 5.60 secs
Transaction rate: 17.15 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 96.02
๋ฐฐํฌ๊ธฐ๊ฐ์ค Availability ๊ฐ ํ์ 100%์์ 70% ๋๋ก ๋จ์ด์ง๋ ๊ฒ์ ํ์ธ. ์์ธ์ ์ฟ ๋ฒ๋คํฐ์ค๊ฐ ์ฑ๊ธํ๊ฒ ์๋ก ์ฌ๋ ค์ง ์๋น์ค๋ฅผ READY ์ํ๋ก ์ธ์ํ์ฌ ์๋น์ค ์ ์ ์ ์งํํ ๊ฒ์ด๊ธฐ ๋๋ฌธ. ์ด๋ฅผ ๋ง๊ธฐ์ํด Readiness Probe ๋ฅผ ์ค์ ํจ:
# deployment.yaml ์ readiness probe ์ ์ค์ :
kubectl apply -f kubernetes/deployment.yaml
- ๋์ผํ ์๋๋ฆฌ์ค๋ก ์ฌ๋ฐฐํฌ ํ ํ Availability ํ์ธ:
Transactions: 3078 hits
Availability: 100 %
Elapsed time: 120 secs
Data transferred: 0.34 MB
Response time: 5.60 secs
Transaction rate: 17.15 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 96.02
๋ฐฐํฌ๊ธฐ๊ฐ ๋์ Availability ๊ฐ ๋ณํ์๊ธฐ ๋๋ฌธ์ ๋ฌด์ ์ง ์ฌ๋ฐฐํฌ๊ฐ ์ฑ๊ณตํ ๊ฒ์ผ๋ก ํ์ธ๋จ.
- KPI: ์ ๊ท ๊ณ ๊ฐ์ ์ ์
๋ฅ ์ฆ๋์ ๊ธฐ์กด ๊ณ ๊ฐ์ ์ถฉ์ฑ๋ ํฅ์
- ๊ตฌํ๊ณํ ๋ง์ดํฌ๋ก ์๋น์ค: ๊ธฐ์กด customer ๋ง์ดํฌ๋ก ์๋น์ค๋ฅผ ์ธ์ํ๋ฉฐ, ๊ณ ๊ฐ์ ์์ ๋ฐ ๋ง์ง ์ถ์ฒ ์๋น์ค ๋ฑ์ ์ ๊ณตํ ์์
![image](https://user-images.githubusercontent.com/487999/79685356-2b729180-8273-11ea-9361-a434065f2249.png)
๊ธฐ์กด์ ๋ง์ดํฌ๋ก ์๋น์ค์ ์์ ์ ๋ฐ์์ํค์ง ์๋๋ก Inbund ์์ฒญ์ REST ๊ฐ ์๋ Event ๋ฅผ Subscribe ํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํ. ๊ธฐ์กด ๋ง์ดํฌ๋ก ์๋น์ค์ ๋ํ์ฌ ์ํคํ ์ฒ๋ ๊ธฐ์กด ๋ง์ดํฌ๋ก ์๋น์ค๋ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์กฐ์ ๊ด๊ณ์์ด ์ถ๊ฐ๋จ.
Request/Response ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ง ์์๊ธฐ ๋๋ฌธ์ ์๋น์ค๊ฐ ๋์ด์ ๋ถํ์ํด์ ธ๋ Deployment ์์ ์ ๊ฑฐ๋๋ฉด ๊ธฐ์กด ๋ง์ดํฌ๋ก ์๋น์ค์ ์ด๋ค ์ํฅ๋ ์ฃผ์ง ์์.
- [๋น๊ต] ๊ฒฐ์ (pay) ๋ง์ดํฌ๋ก์๋น์ค์ ๊ฒฝ์ฐ API ๋ณํ๋ Retire ์์ app(์ฃผ๋ฌธ) ๋ง์ดํฌ๋ก ์๋น์ค์ ๋ณ๊ฒฝ์ ์ด๋ํจ:
์) API ๋ณํ์
# Order.java (Entity)
@PostPersist
public void onPostPersist(){
fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ pay = new fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ();
pay.setOrderId(getOrderId());
Application.applicationContext.getBean(fooddelivery.external.๊ฒฐ์ ์ด๋ ฅService.class)
.๊ฒฐ์ (pay);
-->
Application.applicationContext.getBean(fooddelivery.external.๊ฒฐ์ ์ด๋ ฅService.class)
.๊ฒฐ์ 2(pay);
}
์) Retire ์
# Order.java (Entity)
@PostPersist
public void onPostPersist(){
/**
fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ pay = new fooddelivery.external.๊ฒฐ์ ์ด๋ ฅ();
pay.setOrderId(getOrderId());
Application.applicationContext.getBean(fooddelivery.external.๊ฒฐ์ ์ด๋ ฅService.class)
.๊ฒฐ์ (pay);
**/
}