Skip to content

MSA / DDD / Event Storming example on food delivery service

License

Notifications You must be signed in to change notification settings

dbsh1123/food-delivery

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

35 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

CNA Azure 1์ฐจ์ˆ˜ (5์กฐ_๋“ ๋“ ํ•œ ์•„์นจ์‹์‚ฌ)

๋ณธ ์˜ˆ์ œ๋Š” MSA/DDD/Event Storming/EDA ๋ฅผ ํฌ๊ด„ํ•˜๋Š” ๋ถ„์„/์„ค๊ณ„/๊ตฌํ˜„/์šด์˜ ์ „๋‹จ๊ณ„๋ฅผ ์ปค๋ฒ„ํ•˜๋„๋ก ๊ตฌ์„ฑํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฐœ๋ฐœ์— ์š”๊ตฌ๋˜๋Š” ์ฒดํฌํฌ์ธํŠธ๋“ค์„ ํ†ต๊ณผํ•˜๊ธฐ ์œ„ํ•œ ์˜ˆ์‹œ ๋‹ต์•ˆ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

Table of contents

์„œ๋น„์Šค ์‹œ๋‚˜๋ฆฌ์˜ค

๋“ ๋“ ํ•œ ์•„์นจ์‹์‚ฌ

๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ

  1. ๊ณ ๊ฐ์ด ํšŒ์›๊ฐ€์ž…์„ ํ•˜๊ณ  ์ •๋ณด ์ˆ˜์ • ๋ฐ ํƒˆํ‡ด๋ฅผ ํ•œ๋‹ค
  2. ๋งค๋‹ˆ์ ธ๊ฐ€ ์Œ์‹์„ ์นดํƒˆ๋กœ๊ทธ์— ๋“ฑ๋กํ•˜๊ณ  ์žฌ๊ณ ๋“ฑ ์ •๋ณด ์ˆ˜์ • ๋ฐ ์‚ญ์ œ๋ฅผ ํ•œ๋‹ค.
  3. ์นดํƒˆ๋กœ๊ทธ์— ๋“ฑ๋ก๋œ ์Œ์‹์ •๋ณด๋Š” ๋ณ„๋„์˜ ์นดํƒˆ๋กœ๊ทธ ์กฐํšŒ (ํ”„๋ก ํŠธ์—”๋“œ) ์—์„œ ํ™•์ธํ• ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  4. ๊ณ ๊ฐ์ด ์Œ์‹์„ ์ฃผ๋ฌธํ•˜๊ณ , ์ฃผ๋ฌธ์„ ์ƒ์„ฑํ• ๋•Œ๋Š” ๊ณ ๊ฐ์ •๋ณด์™€ ์Œ์‹ ์นดํƒˆ๋กœ๊ทธ ์ •๋ณด๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
    1. Order -> Customer ๋™๊ธฐํ˜ธ์ถœ
    2. Order -> FoodCatalog ๋™๊ธฐํ˜ธ์ถœ
  5. ๊ณ ๊ฐ์ด ๊ฒฐ์ œ๋ฅผ ํ•œ๋‹ค.
  6. ๊ฒฐ์ œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์ฃผ๋ฌธ ๋‚ด์—ญ์ด ๋ฐฐ์†กํŒ€์—๊ฒŒ ์ „๋‹ฌ๋œ๋‹ค.
  7. ๋ฐฐ์†กํŒ€์ด ์ฃผ๋ฌธ ํ™•์ธ ํ›„ ๋ฐฐ์†ก ์ฒ˜๋ฆฌํ•œ๋‹ค.
  8. ๋ฐฐ์†ก์ด ์™„๋ฃŒ๋˜๋ฉด ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค.
  9. ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์„ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค.
  10. ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์„ ์ทจ์†Œํ•˜๋ฉด ๋ฐฐ์†กํŒ€ ํ™•์ธํ›„ ๋ฐฐ์†ก์ด ์ทจ์†Œ๋œ๋‹ค.
  11. ๋ฐฐ์†ก์ด ์ทจ์†Œ๋˜๋ฉด ๊ฒฐ์ œ๋„ ์ทจ์†Œ๋œ๋‹ค.
  12. ๊ณ ๊ฐ์ด ์ฃผ๋ฌธํ•˜๊ฑฐ๋‚˜ ์ทจ์†Œ๋ฅผ ํ•˜๋ฉด ์Œ์‹ Catalog ์˜ ์žฌ๊ณ ์ˆ˜๋Ÿ‰์— ๋ฐ˜์˜์ด ๋˜์–ด์•ผ ํ•œ๋‹ค.

๋น„๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ

  1. ํŠธ๋žœ์žญ์…˜
    1. ๊ฒฐ์ œ๊ฐ€ ๋˜์ง€ ์•Š์€ ์ฃผ๋ฌธ๊ฑด์€ ์•„์˜ˆ ๊ฑฐ๋ž˜๊ฐ€ ์„ฑ๋ฆฝ๋˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค Sync ํ˜ธ์ถœ
    2. ๋ฐฐ์†ก์ด ์™„๋ฃŒ๋œ ์ฃผ๋ฌธ์€ ์ทจ์†Œํ• ์ˆ˜ ์—†๋‹ค. Sync ํ˜ธ์ถœ
  2. ์žฅ์• ๊ฒฉ๋ฆฌ
    1. ๊ฒฐ์ œ์‹œ์Šคํ…œ์ด ๊ณผ์ค‘๋˜๋ฉด ์‚ฌ์šฉ์ž๋ฅผ ์ž ์‹œ๋™์•ˆ ๋ฐ›์ง€ ์•Š๊ณ  ๊ฒฐ์ œ๋ฅผ ์ž ์‹œํ›„์— ํ•˜๋„๋ก ์œ ๋„ํ•œ๋‹ค Circuit breaker, fallback
  3. ์„ฑ๋Šฅ
    1. ๊ณ ๊ฐ์€ ์ž์‹ ์ด ์ฃผ๋ฌธํ•œ ๋‚ด์šฉ๊ณผ ๋ฐฐ๋‹ฌ์ƒํƒœ๋ฅผ ๋‚˜์˜ ์ฃผ๋ฌธ์ •๋ณด(ํ”„๋ก ํŠธ์—”๋“œ)์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค CQRS

Event Storming ๋ชจ๋ธ

image

์ฒดํฌํฌ์ธํŠธ

  • ๋ถ„์„ ์„ค๊ณ„

    • ์ด๋ฒคํŠธ์Šคํ† ๋ฐ:

      • ์Šคํ‹ฐ์ปค ์ƒ‰์ƒ๋ณ„ ๊ฐ์ฒด์˜ ์˜๋ฏธ๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์—ฌ ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜์™€์˜ ์—ฐ๊ณ„ ์„ค๊ณ„์— ์ ์ ˆํžˆ ๋ฐ˜์˜ํ•˜๊ณ  ์žˆ๋Š”๊ฐ€?
      • ๊ฐ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๊ฐ€ ์˜๋ฏธ์žˆ๋Š” ์ˆ˜์ค€์œผ๋กœ ์ •์˜๋˜์—ˆ๋Š”๊ฐ€?
      • ์–ด๊ทธ๋ฆฌ๊ฒŒ์ž‡: Command์™€ Event ๋“ค์„ ACID ํŠธ๋žœ์žญ์…˜ ๋‹จ์œ„์˜ Aggregate ๋กœ ์ œ๋Œ€๋กœ ๋ฌถ์—ˆ๋Š”๊ฐ€?
      • ๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ๋น„๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ˆ„๋ฝ ์—†์ด ๋ฐ˜์˜ํ•˜์˜€๋Š”๊ฐ€?
    • ์„œ๋ธŒ ๋„๋ฉ”์ธ, ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ ๋ถ„๋ฆฌ

      • ํŒ€๋ณ„ KPI ์™€ ๊ด€์‹ฌ์‚ฌ, ์ƒ์ดํ•œ ๋ฐฐํฌ์ฃผ๊ธฐ ๋“ฑ์— ๋”ฐ๋ฅธ ย Sub-domain ์ด๋‚˜ Bounded Context ๋ฅผ ์ ์ ˆํžˆ ๋ถ„๋ฆฌํ•˜์˜€๊ณ  ๊ทธ ๋ถ„๋ฆฌ ๊ธฐ์ค€์˜ ํ•ฉ๋ฆฌ์„ฑ์ด ์ถฉ๋ถ„ํžˆ ์„ค๋ช…๋˜๋Š”๊ฐ€?
        • ์ ์–ด๋„ 3๊ฐœ ์ด์ƒ ์„œ๋น„์Šค ๋ถ„๋ฆฌ
      • ํด๋ฆฌ๊ธ€๋ž ์„ค๊ณ„: ๊ฐ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋“ค์˜ ๊ตฌํ˜„ ๋ชฉํ‘œ์™€ ๊ธฐ๋Šฅ ํŠน์„ฑ์— ๋”ฐ๋ฅธ ๊ฐ์ž์˜ ๊ธฐ์ˆ  Stack ๊ณผ ์ €์žฅ์†Œ ๊ตฌ์กฐ๋ฅผ ๋‹ค์–‘ํ•˜๊ฒŒ ์ฑ„ํƒํ•˜์—ฌ ์„ค๊ณ„ํ•˜์˜€๋Š”๊ฐ€?
      • ์„œ๋น„์Šค ์‹œ๋‚˜๋ฆฌ์˜ค ์ค‘ ACID ํŠธ๋žœ์žญ์…˜์ด ํฌ๋ฆฌํ‹ฐ์ปฌํ•œ Use ์ผ€์ด์Šค์— ๋Œ€ํ•˜์—ฌ ๋ฌด๋ฆฌํ•˜๊ฒŒ ์„œ๋น„์Šค๊ฐ€ ๊ณผ๋‹คํ•˜๊ฒŒ ์กฐ๋ฐ€ํžˆ ๋ถ„๋ฆฌ๋˜์ง€ ์•Š์•˜๋Š”๊ฐ€?
    • ์ปจํ…์ŠคํŠธ ๋งคํ•‘ / ์ด๋ฒคํŠธ ๋“œ๋ฆฌ๋ธ ์•„ํ‚คํ…์ฒ˜

      • ์—…๋ฌด ์ค‘์š”์„ฑ๊ณผย  ๋„๋ฉ”์ธ๊ฐ„ ์„œ์—ด์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€? (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 ๊ณ„์•ฝ์œ„๋ฐ˜๋ฅผ ๋ฏธ๋ฆฌ ์ฐจ๋‹จ ๊ฐ€๋Šฅํ•œ๊ฐ€?

๋ถ„์„/์„ค๊ณ„

AS-IS ์กฐ์ง (Horizontally-Aligned)

image

TO-BE ์กฐ์ง (Vertically-Aligned)

image

Event Storming ๊ฒฐ๊ณผ

์ด๋ฒคํŠธ ๋„์ถœ

image

๋ถ€์ ๊ฒฉ ์ด๋ฒคํŠธ ํƒˆ๋ฝ

image

- ๊ณผ์ •์ค‘ ๋„์ถœ๋œ ์ž˜๋ชป๋œ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋“ค์„ ๊ฑธ๋Ÿฌ๋‚ด๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•จ
    - ์ฃผ๋ฌธ์‹œ>๋ฉ”๋‰ด์นดํ…Œ๊ณ ๋ฆฌ์„ ํƒ๋จ, ์ฃผ๋ฌธ์‹œ>๋ฉ”๋‰ด๊ฒ€์ƒ‰๋จ :  UI ์˜ ์ด๋ฒคํŠธ์ด์ง€, ์—…๋ฌด์ ์ธ ์˜๋ฏธ์˜ ์ด๋ฒคํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ์„œ ์ œ์™ธ

์•กํ„ฐ, ์ปค๋งจ๋“œ ๋ถ€์ฐฉํ•˜์—ฌ ์ฝ๊ธฐ ์ข‹๊ฒŒ

image

์–ด๊ทธ๋ฆฌ๊ฒŒ์ž‡์œผ๋กœ ๋ฌถ๊ธฐ

image

- app์˜ Order, store ์˜ ์ฃผ๋ฌธ์ฒ˜๋ฆฌ, ๊ฒฐ์ œ์˜ ๊ฒฐ์ œ์ด๋ ฅ์€ ๊ทธ์™€ ์—ฐ๊ฒฐ๋œ command ์™€ event ๋“ค์— ์˜ํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์ด ์œ ์ง€๋˜์–ด์•ผ ํ•˜๋Š” ๋‹จ์œ„๋กœ ๊ทธ๋“ค ๋ผ๋ฆฌ ๋ฌถ์–ด์คŒ

๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ๋กœ ๋ฌถ๊ธฐ

image

- ๋„๋ฉ”์ธ ์„œ์—ด ๋ถ„๋ฆฌ 
    - 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 ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ฒฝ์Ÿ๋ ฅ์ด ๋†’์Œ (ํ•‘ํฌ์ƒ‰์œผ๋กœ ์ดํ›„ ์ „ํ™˜ํ•  ์˜ˆ์ •)

ํด๋ฆฌ์‹œ ๋ถ€์ฐฉ (๊ด„ํ˜ธ๋Š” ์ˆ˜ํ–‰์ฃผ์ฒด, ํด๋ฆฌ์‹œ ๋ถ€์ฐฉ์„ ๋‘˜์งธ๋‹จ๊ณ„์—์„œ ํ•ด๋†”๋„ ์ƒ๊ด€ ์—†์Œ. ์ „์ฒด ์—ฐ๊ณ„๊ฐ€ ์ดˆ๊ธฐ์— ๋“œ๋Ÿฌ๋‚จ)

image

ํด๋ฆฌ์‹œ์˜ ์ด๋™๊ณผ ์ปจํ…์ŠคํŠธ ๋งคํ•‘ (์ ์„ ์€ Pub/Sub, ์‹ค์„ ์€ Req/Resp)

image

์™„์„ฑ๋œ 1์ฐจ ๋ชจํ˜•

image

- View Model ์ถ”๊ฐ€

1์ฐจ ์™„์„ฑ๋ณธ์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ์ /๋น„๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ์„ ์ปค๋ฒ„ํ•˜๋Š”์ง€ ๊ฒ€์ฆ

image

- ๊ณ ๊ฐ์ด ๋ฉ”๋‰ด๋ฅผ ์„ ํƒํ•˜์—ฌ ์ฃผ๋ฌธํ•œ๋‹ค (ok)
- ๊ณ ๊ฐ์ด ๊ฒฐ์ œํ•œ๋‹ค (ok)
- ์ฃผ๋ฌธ์ด ๋˜๋ฉด ์ฃผ๋ฌธ ๋‚ด์—ญ์ด ์ž…์ ์ƒ์ ์ฃผ์ธ์—๊ฒŒ ์ „๋‹ฌ๋œ๋‹ค (ok)
- ์ƒ์ ์ฃผ์ธ์ด ํ™•์ธํ•˜์—ฌ ์š”๋ฆฌํ•ด์„œ ๋ฐฐ๋‹ฌ ์ถœ๋ฐœํ•œ๋‹ค (ok)

image - ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์„ ์ทจ์†Œํ•  ์ˆ˜ ์žˆ๋‹ค (ok) - ์ฃผ๋ฌธ์ด ์ทจ์†Œ๋˜๋ฉด ๋ฐฐ๋‹ฌ์ด ์ทจ์†Œ๋œ๋‹ค (ok) - ๊ณ ๊ฐ์ด ์ฃผ๋ฌธ์ƒํƒœ๋ฅผ ์ค‘๊ฐ„์ค‘๊ฐ„ ์กฐํšŒํ•œ๋‹ค (View-green sticker ์˜ ์ถ”๊ฐ€๋กœ ok) - ์ฃผ๋ฌธ์ƒํƒœ๊ฐ€ ๋ฐ”๋€” ๋•Œ ๋งˆ๋‹ค ์นดํ†ก์œผ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ธ๋‹ค (?)

๋ชจ๋ธ ์ˆ˜์ •

image

- ์ˆ˜์ •๋œ ๋ชจ๋ธ์€ ๋ชจ๋“  ์š”๊ตฌ์‚ฌํ•ญ์„ ์ปค๋ฒ„ํ•จ.

๋น„๊ธฐ๋Šฅ ์š”๊ตฌ์‚ฌํ•ญ์— ๋Œ€ํ•œ ๊ฒ€์ฆ

image

- ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋ฅผ ๋„˜๋‚˜๋“œ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•œ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ
    - ๊ณ ๊ฐ ์ฃผ๋ฌธ์‹œ ๊ฒฐ์ œ์ฒ˜๋ฆฌ:  ๊ฒฐ์ œ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ฃผ๋ฌธ์€ ์ ˆ๋Œ€ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒฝ์˜์ž์˜ ์˜ค๋žœ ์‹ ๋…(?) ์— ๋”ฐ๋ผ, ACID ํŠธ๋žœ์žญ์…˜ ์ ์šฉ. ์ฃผ๋ฌธ์™€๋ฃŒ์‹œ ๊ฒฐ์ œ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ๋Š” Request-Response ๋ฐฉ์‹ ์ฒ˜๋ฆฌ
    - ๊ฒฐ์ œ ์™„๋ฃŒ์‹œ ์ ์ฃผ์—ฐ๊ฒฐ ๋ฐ ๋ฐฐ์†ก์ฒ˜๋ฆฌ:  App(front) ์—์„œ Store ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋กœ ์ฃผ๋ฌธ์š”์ฒญ์ด ์ „๋‹ฌ๋˜๋Š” ๊ณผ์ •์— ์žˆ์–ด์„œ Store ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๊ฐ€ ๋ณ„๋„์˜ ๋ฐฐํฌ์ฃผ๊ธฐ๋ฅผ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์— Eventual Consistency ๋ฐฉ์‹์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌํ•จ.
    - ๋‚˜๋จธ์ง€ ๋ชจ๋“  inter-microservice ํŠธ๋žœ์žญ์…˜: ์ฃผ๋ฌธ์ƒํƒœ, ๋ฐฐ๋‹ฌ์ƒํƒœ ๋“ฑ ๋ชจ๋“  ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์นดํ†ก์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋“ฑ, ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์˜ ์‹œ์ ์ด ํฌ๋ฆฌํ‹ฐ์ปฌํ•˜์ง€ ์•Š์€ ๋ชจ๋“  ๊ฒฝ์šฐ๊ฐ€ ๋Œ€๋ถ€๋ถ„์ด๋ผ ํŒ๋‹จ, Eventual Consistency ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ฑ„ํƒํ•จ.

ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ ๋„์ถœ

image

- 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 

DDD ์˜ ์ ์šฉ

  • ๊ฐ ์„œ๋น„์Šค๋‚ด์— ๋„์ถœ๋œ ํ•ต์‹ฌ 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"]

๋™๊ธฐ์‹ ํ˜ธ์ถœ ๊ณผ Fallback ์ฒ˜๋ฆฌ

๋ถ„์„๋‹จ๊ณ„์—์„œ์˜ ์กฐ๊ฑด ์ค‘ ํ•˜๋‚˜๋กœ ์ฃผ๋ฌธ(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
  • ๋˜ํ•œ ๊ณผ๋„ํ•œ ์š”์ฒญ์‹œ์— ์„œ๋น„์Šค ์žฅ์• ๊ฐ€ ๋„๋ฏธ๋…ธ ์ฒ˜๋Ÿผ ๋ฒŒ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค. (์„œํ‚ท๋ธŒ๋ ˆ์ด์ปค, ํด๋ฐฑ ์ฒ˜๋ฆฌ๋Š” ์šด์˜๋‹จ๊ณ„์—์„œ ์„ค๋ช…ํ•œ๋‹ค.)

๋น„๋™๊ธฐ์‹ ํ˜ธ์ถœ / ์‹œ๊ฐ„์  ๋””์ปคํ”Œ๋ง / ์žฅ์• ๊ฒฉ๋ฆฌ / ์ตœ์ข… (Eventual) ์ผ๊ด€์„ฑ ํ…Œ์ŠคํŠธ

๊ฒฐ์ œ๊ฐ€ ์ด๋ฃจ์–ด์ง„ ํ›„์— ์ƒ์ ์‹œ์Šคํ…œ์œผ๋กœ ์ด๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ํ–‰์œ„๋Š” ๋™๊ธฐ์‹์ด ์•„๋‹ˆ๋ผ ๋น„ ๋™๊ธฐ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ƒ์  ์‹œ์Šคํ…œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•˜์—ฌ ๊ฒฐ์ œ์ฃผ๋ฌธ์ด ๋ธ”๋กœํ‚น ๋˜์ง€ ์•Š์•„๋„๋ก ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • ์ด๋ฅผ ์œ„ํ•˜์—ฌ ๊ฒฐ์ œ์ด๋ ฅ์— ๊ธฐ๋ก์„ ๋‚จ๊ธด ํ›„์— ๊ณง๋ฐ”๋กœ ๊ฒฐ์ œ์Šน์ธ์ด ๋˜์—ˆ๋‹ค๋Š” ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ์นดํ”„์นด๋กœ ์†ก์ถœํ•œ๋‹ค(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     # ๋ชจ๋“  ์ฃผ๋ฌธ์˜ ์ƒํƒœ๊ฐ€ "๋ฐฐ์†ก๋จ"์œผ๋กœ ํ™•์ธ

์šด์˜

CI/CD ์„ค์ •

๊ฐ ๊ตฌํ˜„์ฒด๋“ค์€ ๊ฐ์ž์˜ source repository ์— ๊ตฌ์„ฑ๋˜์—ˆ๊ณ , ์‚ฌ์šฉํ•œ CI/CD ํ”Œ๋žซํผ์€ GCP๋ฅผ ์‚ฌ์šฉํ•˜์˜€์œผ๋ฉฐ, pipeline build script ๋Š” ๊ฐ ํ”„๋กœ์ ํŠธ ํด๋” ์ดํ•˜์— cloudbuild.yml ์— ํฌํ•จ๋˜์—ˆ๋‹ค.

๋™๊ธฐ์‹ ํ˜ธ์ถœ / ์„œํ‚ท ๋ธŒ๋ ˆ์ดํ‚น / ์žฅ์• ๊ฒฉ๋ฆฌ

  • ์„œํ‚ท ๋ธŒ๋ ˆ์ดํ‚น ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์„ ํƒ: Spring FeignClient + Hystrix ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•จ

์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ๋‹จ๋ง์•ฑ(app)-->๊ฒฐ์ œ(pay) ์‹œ์˜ ์—ฐ๊ฒฐ์„ RESTful Request/Response ๋กœ ์—ฐ๋™ํ•˜์—ฌ ๊ตฌํ˜„์ด ๋˜์–ด์žˆ๊ณ , ๊ฒฐ์ œ ์š”์ฒญ์ด ๊ณผ๋„ํ•  ๊ฒฝ์šฐ CB ๋ฅผ ํ†ตํ•˜์—ฌ ์žฅ์• ๊ฒฉ๋ฆฌ.

  • Hystrix ๋ฅผ ์„ค์ •: ์š”์ฒญ์ฒ˜๋ฆฌ ์“ฐ๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ์‹œ๊ฐ„์ด 610 ๋ฐ€๋ฆฌ๊ฐ€ ๋„˜์–ด์„œ๊ธฐ ์‹œ์ž‘ํ•˜์—ฌ ์–ด๋Š์ •๋„ ์œ ์ง€๋˜๋ฉด CB ํšŒ๋กœ๊ฐ€ ๋‹ซํžˆ๋„๋ก (์š”์ฒญ์„ ๋น ๋ฅด๊ฒŒ ์‹คํŒจ์ฒ˜๋ฆฌ, ์ฐจ๋‹จ) ์„ค์ •
# application.yml

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 ๊ฐ€ ๋ณ€ํ™”์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด์ •์ง€ ์žฌ๋ฐฐํฌ๊ฐ€ ์„ฑ๊ณตํ•œ ๊ฒƒ์œผ๋กœ ํ™•์ธ๋จ.

์‹ ๊ทœ ๊ฐœ๋ฐœ ์กฐ์ง์˜ ์ถ”๊ฐ€

image

๋งˆ์ผ€ํŒ…ํŒ€์˜ ์ถ”๊ฐ€

- KPI: ์‹ ๊ทœ ๊ณ ๊ฐ์˜ ์œ ์ž…๋ฅ  ์ฆ๋Œ€์™€ ๊ธฐ์กด ๊ณ ๊ฐ์˜ ์ถฉ์„ฑ๋„ ํ–ฅ์ƒ
- ๊ตฌํ˜„๊ณ„ํš ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค: ๊ธฐ์กด customer ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋ฅผ ์ธ์ˆ˜ํ•˜๋ฉฐ, ๊ณ ๊ฐ์— ์Œ์‹ ๋ฐ ๋ง›์ง‘ ์ถ”์ฒœ ์„œ๋น„์Šค ๋“ฑ์„ ์ œ๊ณตํ•  ์˜ˆ์ •

์ด๋ฒคํŠธ ์Šคํ† ๋ฐ

![image](https://user-images.githubusercontent.com/487999/79685356-2b729180-8273-11ea-9361-a434065f2249.png)

ํ—ฅ์‚ฌ๊ณ ๋‚  ์•„ํ‚คํ…์ฒ˜ ๋ณ€ํ™”

image

๊ตฌํ˜„

๊ธฐ์กด์˜ ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค์— ์ˆ˜์ •์„ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋„๋ก Inbund ์š”์ฒญ์„ REST ๊ฐ€ ์•„๋‹Œ Event ๋ฅผ Subscribe ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„. ๊ธฐ์กด ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค์— ๋Œ€ํ•˜์—ฌ ์•„ํ‚คํ…์ฒ˜๋‚˜ ๊ธฐ์กด ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค๋“ค์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ์™€ ๊ด€๊ณ„์—†์ด ์ถ”๊ฐ€๋จ.

์šด์˜๊ณผ Retirement

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);

        **/
    }

About

MSA / DDD / Event Storming example on food delivery service

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 94.1%
  • Python 3.2%
  • Dockerfile 2.7%