๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„์ด ์—†๋Š” ๋ฐฐํฌ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„์ด ์—†๋Š” ๋ฐฐํฌ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

์ด ๋ฌธ์„œ์—์„œ๋Š” ๋ฐฐํฌ ์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์ „ ์ค€๋น„ ์—†์ด ๋ฐฐํฌํ•˜๋ ค๊ณ  ํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„์ด ์—†์–ด์•ผ ํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ ๋‹จ๊ณ„๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค(๋Œ€๋žต. ์ฐจ์„ : ์ถ”๊ฐ€ - ๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„ ์—†์Œ). ์šฐ๋ฆฌ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋Š” ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ธฐ์‚ฌ์˜ ์ฝ”๋“œ ์˜ˆ์ œ๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ๋‹ค์Œ์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GitHub์˜.

์†Œ๊ฐœ

๋‹ค์šดํƒ€์ž„ ์—†๋Š” ๋ฐฐํฌ

์ •๋ง ์‹ ๋น„๋กœ์šด๋ฐ์š”? ๋‹ค์šดํƒ€์ž„ ์—†๋Š” ๋ฐฐํฌ? ์ด๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ์„ ์ธ์‹ํ•˜์ง€ ๋ชปํ•˜๋Š” ๋™์•ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒˆ ๋ฒ„์ „์„ ํ”„๋กœ๋•์…˜์— ์„ฑ๊ณต์ ์œผ๋กœ ๋„์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐฐํฌ๋˜๋Š” ๋•Œ๋ผ๊ณ  ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๋ฐ ํšŒ์‚ฌ ๊ด€์ ์—์„œ ๋ณผ ๋•Œ ์ด๋Š” ์ค‘๋‹จ ์—†์ด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ๋„์ž…ํ•˜๊ณ  ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•œ ์ตœ์ƒ์˜ ๋ฐฐํฌ ์‹œ๋‚˜๋ฆฌ์˜ค์ž…๋‹ˆ๋‹ค.

์ด๊ฒƒ์„ ๋‹ฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ํ•˜๋‚˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ์„œ๋น„์Šค ๋ฒ„์ „ 1์„ ๋ฐฐํฌํ•˜์„ธ์š”.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ˆ˜ํ–‰
  • ๋ฒ„์ „ 2๊ณผ ๋ณ‘๋ ฌ๋กœ ์„œ๋น„์Šค ๋ฒ„์ „ 1๋ฅผ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฒ„์ „ 2๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜์ž๋งˆ์ž ๋ฒ„์ „ 1์„ ์ œ๊ฑฐํ•˜์‹ญ์‹œ์˜ค.
  • ๋๋‚œ!

์‰ฝ์ง€์š”? ๋ถˆํ–‰ํ•˜๊ฒŒ๋„ ์ด๋Š” ๊ทธ๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ๋Š” ๋‚˜์ค‘์— ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด์ œ ๋˜ ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค์ธ ํŒŒ๋ž€์ƒ‰ ๋…น์ƒ‰ ๋ฐฐํฌ๋ฅผ ํ™•์ธํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋“ค์–ด๋ณธ ์ ์ด ์žˆ๋‚˜์š”? ๋ธ”๋ฃจ ๊ทธ๋ฆฐ ๋ฐฐํฌ? Cloud Foundry๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ๋งค์šฐ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ ๋ด ์ด ๋ฌธ์„œ, ์—ฌ๊ธฐ์„œ ์ด์— ๋Œ€ํ•ด ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ„๋žตํ•˜๊ฒŒ ์š”์•ฝํ•˜์ž๋ฉด, ๋ธ”๋ฃจ ๊ทธ๋ฆฐ ๋ฐฐํฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ƒ๊ธฐ์‹œ์ผœ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

  • ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ์‚ฌ๋ณธ XNUMX๊ฐœ("ํŒŒ๋ž€์ƒ‰" ๋ฐ "๋…น์ƒ‰")๊ฐ€ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋ชจ๋“  ํŠธ๋ž˜ํ”ฝ์„ ๋ธ”๋ฃจ ํ™˜๊ฒฝ์œผ๋กœ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ฆ‰, ํ”„๋กœ๋•์…˜ URL์ด ๊ทธ๊ณณ์„ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ์นœํ™˜๊ฒฝ ํ™˜๊ฒฝ์—์„œ ๋ชจ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐฐํฌํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.
  • URL์„ ํŒŒ๋ž€์ƒ‰์—์„œ ๋…น์ƒ‰ ํ™˜๊ฒฝ์œผ๋กœ ์ „ํ™˜

๋ธ”๋ฃจ ๊ทธ๋ฆฐ ๋ฐฐํฌ๋Š” ํ”„๋กœ๋•์…˜ ์ค‘๋‹จ์— ๋Œ€ํ•œ ๊ฑฑ์ • ์—†์ด ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๋„์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚˜๋”๋ผ๋„ '์Šค์œ„์น˜๋ฅผ ์‚ด์ง ๋ˆ„๋ฅด๋Š” ๊ฒƒ'๋งŒ์œผ๋กœ ์‰ฝ๊ฒŒ ์ด์ „ ํ™˜๊ฒฝ์œผ๋กœ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์œ„์˜ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ฝ์€ ํ›„ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์งˆ๋ฌธ์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์šดํƒ€์ž„ ์ œ๋กœ๊ฐ€ ๋ธ”๋ฃจ ๊ทธ๋ฆฐ ๋ฐฐํฌ์™€ ์–ด๋–ค ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๊ธ€์Ž„์š”, ๋™์ผํ•œ ํ™˜๊ฒฝ์˜ ๋‘ ๋ณต์‚ฌ๋ณธ์„ ์œ ์ง€ํ•˜๋ ค๋ฉด ๋‘ ๋ฐฐ์˜ ๋…ธ๋ ฅ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณตํ†ต์ ์ด ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ์ผ๋ถ€ ํŒ€์ด ์ฃผ์žฅํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ๋งˆํ‹ด ํŒŒ์šธ๋Ÿฌ, ์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋ณ€ํ˜•์„ ๋”ฐ๋ฅด์‹ญ์‹œ์˜ค.

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

๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๋Š” ์ด ๊ธ€์˜ ์ฃผ์š” ๋ฌธ์ œ์— ๋„๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค. ์ด ๋ฌธ๊ตฌ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ์Šค์Šค๋กœ์—๊ฒŒ ์งˆ๋ฌธ์„ ๋˜์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์•ฑ์˜ ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „์ด ์ค‘๋‹จ๋˜์ง€ ์•Š๋‚˜์š”? ์‹ค์ œ๋กœ ์ด๋Ÿฐ ์ผ์ด ์‹ค์ œ๋กœ ์ผ์–ด๋‚  ๊ฒƒ์ž…๋‹ˆ๋‹ค ...

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

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

์ด ๋ฌธ์„œ์—์„œ๋Š” ๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„ ์—†๋Š” ๋ฐฐํฌ๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ์ฝ”๋“œ๋กœ ์ž‘์—…ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž์„ธํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฌธ์ œ

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

๋ฒ„์ „ ๊ด€๋ฆฌ ์ฒด๊ณ„

์ด ๊ธฐ์‚ฌ์—์„œ ์šฐ๋ฆฌ๋Š” ํ”Œ๋ผ์ด์›จ์ด ๋ฒ„์ „ ๊ด€๋ฆฌ ๋„๊ตฌ(๋Œ€๋žต. ๋ฒˆ์—ญ: ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.). ๋‹น์—ฐํžˆ ์šฐ๋ฆฌ๋Š” Flyway ์ง€์›์ด ๋‚ด์žฅ๋œ Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ์ž‘์„ฑํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋™์•ˆ ์Šคํ‚ค๋งˆ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Flyway๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ”„๋กœ์ ํŠธ ํด๋”์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ธฐ๋ณธ์ ์œผ๋กœ classpath:db/migration). ์—ฌ๊ธฐ์—์„œ ์ด๋Ÿฌํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์˜ ์˜ˆ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ””โ”€โ”€ db
 โ””โ”€โ”€ migration
     โ”œโ”€โ”€ V1__init.sql
     โ”œโ”€โ”€ V2__Add_surname.sql
     โ”œโ”€โ”€ V3__Final_migration.sql
     โ””โ”€โ”€ V4__Remove_lastname.sql

์ด ์˜ˆ์—์„œ๋Š” ์ด์ „์— ์‹คํ–‰๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹œ์ž‘๋  ๋•Œ ์ฐจ๋ก€๋กœ ์‹คํ–‰๋˜๋Š” 4๊ฐœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŒŒ์ผ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค(V1__init.sql) ์˜ˆ๋กœ์„œ.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

๋ชจ๋“  ๊ฒƒ์ด ์™„๋ฒฝํ•˜๊ฒŒ ์„ค๋ช…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. SQL์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ • ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Spring Boot ๋ฐ Flyway์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”. ์Šคํ”„๋ง ๋ถ€ํŠธ ๋ฌธ์„œ.

Spring Boot์™€ ํ•จ๊ป˜ ์†Œ์Šค ์ œ์–ด ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‘ ๊ฐ€์ง€ ํฐ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ๊ณผ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ถœ์‹œ์™€ ํ•จ๊ป˜ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹จ์ˆœํ™”๋ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฌธ์ œ ํ•ด๊ฒฐ

๊ธฐ์‚ฌ์˜ ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋‘ ๊ฐ€์ง€ ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ดํŽด๋ณด๋Š” ๋ฐ ์ค‘์ ์„ ๋‘˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ๋น„ํ˜ธํ™˜์„ฑ
  • ํ•˜์œ„ ํ˜ธํ™˜์„ฑ

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

์šฐ๋ฆฌ๊ฐ€ ์ž‘์—…ํ•  ํ”„๋กœ์ ํŠธ๋Š” ๊ฐ„๋‹จํ•œ Spring Boot Flyway ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค. Person ั first_name ะธ last_name ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(๋Œ€๋žต. ๋ฒˆ์—ญ: Person ๋Š” ํ…Œ์ด๋ธ”์ด๊ณ  f์ž…๋‹ˆ๋‹คirst_name ะธ last_name - ์ด๊ฒƒ์€ ๊ทธ ์•ˆ์— ์žˆ๋Š” ํ•„๋“œ์ž…๋‹ˆ๋‹ค.). ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค last_name ะฒ surname.

๊ฐ€์ •

์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์‚ดํŽด๋ณด๊ธฐ ์ „์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•ด ๋ช‡ ๊ฐ€์ง€ ๊ฐ€์ •์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๋‹ฌ์„ฑํ•˜๊ณ ์ž ํ•˜๋Š” ์ฃผ์š” ๊ฒฐ๊ณผ๋Š” ์ƒ๋‹นํžˆ ๊ฐ„๋‹จํ•œ ํ”„๋กœ์„ธ์Šค์ž…๋‹ˆ๋‹ค.

๋ฉ”๋ชจ. ๋น„์ฆˆ๋‹ˆ์Šค ํ”„๋กœํŒ. ํ”„๋กœ์„ธ์Šค๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋ฉด ์ง€์› ๋น„์šฉ์„ ๋งŽ์ด ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํšŒ์‚ฌ์—์„œ ์ผํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋งŽ์„์ˆ˜๋ก ๋” ๋งŽ์€ ๋น„์šฉ์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋กค๋ฐฑํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ด๋Š” ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋‹จ์ˆœํ™”ํ•ฉ๋‹ˆ๋‹ค(์‚ญ์ œ ๋กค๋ฐฑ๊ณผ ๊ฐ™์€ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กค๋ฐฑ์€ ๊ฑฐ์˜ ๋ถˆ๊ฐ€๋Šฅํ•จ). ์šฐ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋งŒ ๋กค๋ฐฑํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(์˜ˆ: SQL ๋ฐ NoSQL)๊ฐ€ ์žˆ๋”๋ผ๋„ ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ์ด ๋™์ผํ•˜๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ•œ ๋ฒ„์ „ ์ด์ „์œผ๋กœ ๋กค๋ฐฑํ•˜๋Š” ๊ฒƒ์€ ํ•ญ์ƒ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋” ์ด์ƒ์€ ๊ฐ€๋Šฅํ•˜์ง€ ์•Š์Œ).

๋กค๋ฐฑ์€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฒ„์ „์— ์‰ฝ๊ฒŒ ์ˆ˜์ •๋˜์ง€ ์•Š๋Š” ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ตœ์‹  ์ž‘์—… ๋ฒ„์ „์œผ๋กœ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ์ตœ์‹  ์ž‘์—… ๋ฒ„์ „์ด ์ด์ „ ๋ฒ„์ „์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ์ด์ƒ์˜ ๋กค์•„์›ƒ์— ๋Œ€ํ•ด ์ฝ”๋“œ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์–ด๋ ต๊ณ  ๋น„์šฉ์ด ๋งŽ์ด ๋“ญ๋‹ˆ๋‹ค.

์ฐธ๊ณ  :. ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ์ด ๋ฌธ์„œ์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฃผ์š” ๋ฒ„์ „์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

1๋‹จ๊ณ„: ์ดˆ๊ธฐ ์ƒํƒœ

์•ฑ ๋ฒ„์ „ : 1.0.0
DB ๋ฒ„์ „: v1

๋…ผํ‰

์ด๊ฒƒ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ

DB์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. last_name.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

์ฝ”๋“œ ๋ณ€๊ฒฝ

์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ๊ฐœ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์Œ ์œ„์น˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. last_name:

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastname) {
        this.lastName = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
                + "]";
    }
}

์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ์—ด ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐ

์—ด ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•์˜ ์˜ˆ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ฃผ์˜. ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” ์˜๋„์ ์œผ๋กœ ์ž‘์—…์„ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์ด๊ฒƒ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

์•ฑ ๋ฒ„์ „ : 2.0.0.BAD

DB ๋ฒ„์ „: v2bad

๋…ผํ‰

ํ˜„์žฌ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์œผ๋กœ ์ธํ•ด ๋‘ ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค(์ด์ „ ์ธ์Šคํ„ด์Šค์™€ ์ƒˆ ์ธ์Šคํ„ด์Šค)๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ€๋™ ์ค‘์ง€ ์‹œ๊ฐ„ ์ œ๋กœ ๋ฐฐํฌ๋Š” ๋‹ฌ์„ฑํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค(๊ฐ€์ •์„ ๊ณ ๋ คํ•˜๋ฉด ์‹ค์ œ๋กœ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค).

A/B ํ…Œ์ŠคํŠธ

ํ˜„์žฌ ์ƒํ™ฉ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 1.0.0, ํ”„๋กœ๋•์…˜ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐฐํฌ v1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‘ ๋ฒˆ์งธ ์ธ์Šคํ„ด์Šค ๋ฒ„์ „์„ ๋ฐฐํฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 2.0.0.BAD, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค์Œ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. v2bad.

๋‹จ๊ณ„ :

  1. ๋ฒ„์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒˆ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. 2.0.0.BAD๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. v2bad
  2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ v2bad ์—ด last_name ๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. surname
  3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—…๋ฐ์ดํŠธ๊ฐ€ ์„ฑ๊ณตํ–ˆ์œผ๋ฉฐ ์ผ๋ถ€ ์ธ์Šคํ„ด์Šค๊ฐ€ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. 1.0.0, ๊ธฐํƒ€ - ์— 2.0.0.BAD. ๋ชจ๋“  ๊ฒƒ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค v2bad
  4. ๋ฒ„์ „์˜ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค 1.0.0 ์—ด์— ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. last_name๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ๋žŒ
  5. ๋ฒ„์ „์˜ ๋ชจ๋“  ์ธ์Šคํ„ด์Šค 2.0.0.BAD ๋ฌธ์ œ ์—†์ด ์ž‘๋™ํ•  ๊ฒƒ์ด๋‹ค

๋ณด์‹œ๋‹ค์‹œํ”ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ ์šฉํ•˜๋ฉด A/B ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กค๋ฐฑ

A/B ๋ฐฐํฌ๋ฅผ ์‹œ๋„ํ•œ ํ›„ (๋Œ€๋žต. ๋‹น: ์ €์ž๋Š” ์•„๋งˆ๋„ ์—ฌ๊ธฐ์„œ A/B ํ…Œ์ŠคํŠธ๋ฅผ ์˜๋ฏธํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.) ์šฐ๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋กค๋ฐฑํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. 1.0.0. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋กค๋ฐฑํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‹จ๊ณ„ :

  1. ๋ฒ„์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค. 2.0.0.BAD
  2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์•„์ง v2bad
  3. ๋ฒ„์ „๋ถ€ํ„ฐ 1.0.0 ๊ทธ๊ฒŒ ๋ญ”์ง€ ์ดํ•ดํ•˜์ง€ ๋ชปํ•ด์š” surname, ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค
  4. ์ง€์˜ฅ์ด ํ’€๋ ธ์–ด, ์šฐ๋ฆฐ ๋” ์ด์ƒ ๋Œ์•„๊ฐˆ ์ˆ˜ ์—†์–ด

๋ณด์‹œ๋‹ค์‹œํ”ผ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ ์šฉํ•˜๋ฉด ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋กค๋ฐฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๋กœ๊ทธ

Backward incompatible scenario:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

curl: (22) The requested URL returned error: 500 Internal Server Error

Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ

์ด๋ฆ„์„ ๋ฐ”๊พธ๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ last_name ะฒ surname

์†Œ์Šค ํ”Œ๋ผ์ด์›จ์ด ์Šคํฌ๋ฆฝํŠธ:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

์ด๋ฆ„์„ ๋ฐ”๊พธ๋Š” ์Šคํฌ๋ฆฝํŠธ last_name.

-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

์ฝ”๋“œ ๋ณ€๊ฒฝ

ํ•„๋“œ๋ช…์„ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค lastName ์— surname.

์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์—ด ์ด๋ฆ„ ๋ฐ”๊พธ๊ธฐ

์ด๊ฒƒ์ด ์šฐ๋ฆฌ๊ฐ€ ์ง๋ฉดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋‹ค์šดํƒ€์ž„ ์—†๋Š” ๋ฐฐํฌ๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€ ๋‹จ๊ณ„ ์—†์ด ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ ์šฉํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์ด๋ฏธ ์ž…์ฆํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์‚ฌ์˜ ์ด ์„น์…˜์—์„œ๋Š” ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ํ•จ๊ป˜ 3๊ฐ€์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋ฉ”๋ชจ. ๋ฒ„์ „ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•˜์„ธ์š” v1. ์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. first_name ะธ last_name. ์šฐ๋ฆฌ๋Š” ๋ณ€ํ•ด์•ผ ํ•ด last_name ์— surname. ์•ฑ ๋ฒ„์ „๋„ ์žˆ์Šต๋‹ˆ๋‹ค 1.0.0, ์•„์ง ์‚ฌ์šฉ๋˜์ง€ ์•Š์€ ๊ฒƒ surname.

2๋‹จ๊ณ„: ์„ฑ ์ถ”๊ฐ€

์•ฑ ๋ฒ„์ „ : 2.0.0
DB ๋ฒ„์ „: v2

๋…ผํ‰

์ƒˆ ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํ•ด๋‹น ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•˜์—ฌ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๋™์‹œ์— JAR์„ ๋กค๋ฐฑํ•˜๊ฑฐ๋‚˜ ์ด์ „ JAR์„ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ ์‹คํ–‰ ์ค‘์— ์ค‘๋‹จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์ถœ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค

๋‹จ๊ณ„ :

  1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์ƒˆ ์—ด ์ƒ์„ฑ surname. ์ด์ œ DB ๋ฒ„์ „ v2
  2. ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ last_name ะฒ surname. ์ฃผ์˜์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์œผ๋ฉด ์ผ๊ด„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค!
  3. ์‚ฌ์šฉ๋˜๋Š” ๊ณณ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ๋‘˜ ๋‹ค ะธ ์ƒˆ๋กœ์šด๊ณผ ์˜ค๋ž˜ ๋œ ์—ด. ์ด์ œ ์•ฑ ๋ฒ„์ „ 2.0.0
  4. ์—ด์—์„œ ๊ฐ’์„ ์ฝ์Šต๋‹ˆ๋‹ค. surname, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ null, ์•„๋‹ˆ๋ฉด ๋‚ด๊ฐ€ast_name, if surname ๋ช…์‹œ๋˜์ง€ ์•Š์€. ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค getLastName() ์ฝ”๋“œ์—์„œ ์ถœ๋ ฅ๋˜๋ฏ€๋กœ null ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋กค๋ฐฑํ•  ๋•Œ 3.0.0 ์— 2.0.0.

Spring Boot Flyway๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ฒ„์ „ ์‹œ์ž‘ ์ค‘์— ์ด ๋‘ ๋‹จ๊ณ„๊ฐ€ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. 2.0.0 ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฒ„์ „ ๊ด€๋ฆฌ ๋„๊ตฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ๋‘ ๊ฐ€์ง€ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋จผ์ € db ๋ฒ„์ „์„ ์ˆ˜๋™์œผ๋กœ ์—…๋ฐ์ดํŠธํ•œ ๋‹ค์Œ ์ƒˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌ).

๊ทธ๊ฒƒ์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ์—ด์€ ํ•˜์ง€ ๋ง์•„์•ผํ•œ๋‹ค ์žˆ๋‹ค NULL ์•„๋‹˜. ๋กค๋ฐฑ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ์ด์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ƒˆ ์—ด์— ๋Œ€ํ•ด ์•Œ์ง€ ๋ชปํ•˜๋ฏ€๋กœ ๋กค๋ฐฑ ์ค‘์— ์ด๋ฅผ ์„ค์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Insert. ํ•˜์ง€๋งŒ ์ด ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•˜๋ฉด DB๋Š” v2, ์ƒˆ ์—ด์˜ ๊ฐ’์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ œํ•œ ์œ„๋ฐ˜์œผ๋กœ ์ด์–ด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. getLastName(), ๋ฒ„์ „์—์„œ๋Š” 3.0.0 ์ฝ”๋“œ์—๋Š” ์—ด ๊ฐœ๋…์ด ์—†์Šต๋‹ˆ๋‹ค. last_name. ์ด๋Š” null์ด ๊ฑฐ๊ธฐ์— ์„ค์ •๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์†Œ๋“œ๋ฅผ ์ข…๋ฃŒํ•˜๊ณ  ๋‹ค์Œ์— ๋Œ€ํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. nullํ•˜์ง€๋งŒ ํ›จ์”ฌ ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์€ ๋…ผ๋ฆฌ์—์„œ ๋‹ค์Œ์„ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. getSurname() XNUMX์ด ์•„๋‹Œ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์„ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

A/B ํ…Œ์ŠคํŠธ

ํ˜„์žฌ ์ƒํ™ฉ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. 1.0.0, ํ”„๋กœ๋•์…˜์— ๋ฐฐํฌ๋˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” v1. ๋ฒ„์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‘ ๋ฒˆ์งธ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐฐํฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 2.0.0๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋‹ค์Œ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. v2.

๋‹จ๊ณ„ :

  1. ๋ฒ„์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒˆ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. 2.0.0๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. v2
  2. ๊ทธ ๋™์•ˆ ์ผ๋ถ€ ์š”์ฒญ์ด ๋ฒ„์ „ ์ธ์Šคํ„ด์Šค์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 1.0.0
  3. ์—…๋ฐ์ดํŠธ๊ฐ€ ์„ฑ๊ณตํ–ˆ์œผ๋ฉฐ ๋ฒ„์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‹คํ–‰ ์ค‘์ธ ์ธ์Šคํ„ด์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ์Šต๋‹ˆ๋‹ค. 1.0.0 ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ๋ฒ„์ „ 2.0.0. ๋ชจ๋“  ์‚ฌ๋žŒ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค. v2
  4. ๋ฒ„์ „ 1.0.0 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์„ฑ ์—ด์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ๋ฒ„์ „ 2.0.0 ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š์œผ๋ฉฐ ์˜ค๋ฅ˜๊ฐ€ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  5. ๋ฒ„์ „ 2.0.0 ์ด์ „ ์—ด๊ณผ ์ƒˆ ์—ด ๋ชจ๋‘์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์—ฌ ์ด์ „ ๋ฒ„์ „๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „/์ƒˆ ์—ด์˜ ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ•ญ๋ชฉ ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ด์ œ ์ค‘๋ณต๋œ ๊ฐ’์ด ์žˆ๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(์•„์ง ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ค‘์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Œ). ์˜ˆ๋ฅผ ๋“ค์–ด, ์„ฑ(์—ด ์ด๋ฆ„์— ๊ด€๊ณ„์—†์ด)์ด ๋ฌธ์ž๋กœ ์‹œ์ž‘ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋ ค๋Š” ๊ฒฝ์šฐ A, ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€(old โ†’ new ์—ด) ์ƒˆ ์—ด์„ ์ฟผ๋ฆฌํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ผ๊ด€๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กค๋ฐฑ

์ด์ œ ์•ฑ ๋ฒ„์ „์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค 2.0.0 ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค v2.

๋‹จ๊ณ„ :

  1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฒ„์ „์œผ๋กœ ๋กค๋ฐฑ 1.0.0.
  2. ๋ฒ„์ „ 1.0.0 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์—ด์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. surname์ด๋ฏ€๋กœ ๋กค๋ฐฑ์ด ์„ฑ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

DB ๋ณ€๊ฒฝ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ด์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. last_name.

ํ”Œ๋ผ์ด์›จ์ด ์†Œ์Šค ์Šคํฌ๋ฆฝํŠธ:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€ surname.

์ฃผ์˜. ์ถ”๊ฐ€ํ•˜๋Š” ์—ด์—๋Š” NOT NULL ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์„ธ์š”. JAR์„ ๋กค๋ฐฑํ•˜๋ฉด ์ด์ „ ๋ฒ„์ „์€ ์ถ”๊ฐ€๋œ ์—ด์— ๋Œ€ํ•ด ์ „ํ˜€ ๋ชจ๋ฅด๊ณ  ์ž๋™์œผ๋กœ NULL๋กœ ์„ค์ •๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ์ œํ•œ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ์ด์ „ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹จ์ˆœํžˆ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค.

-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

์ฝ”๋“œ ๋ณ€๊ฒฝ

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

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * Reading from the new column if it's set. If not the from the old one.
     *
     * When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
     * the surname column is not up to date (during the migration process lastName could have been updated).
     * In this case one can run yet another migration script after all applications have been deployed in the
     * new version to ensure that the surname field is updated.
     *
     * However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
     * have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
     * would be reading from lastName, then we would have very old data (since not a single datum was inserted
     * to lastName in version 3.0.0).
     */
    public String getSurname() {
        return this.surname != null ? this.surname : this.lastName;
    }

    /**
     * Storing both FIRST_NAME and SURNAME entries
     */
    public void setSurname(String surname) {
        this.lastName = surname;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
                + "]";
    }
}

3๋‹จ๊ณ„: ์ฝ”๋“œ์—์„œ last_name ์ œ๊ฑฐ

์•ฑ ๋ฒ„์ „ : 3.0.0

DB ๋ฒ„์ „:v3

๋…ผํ‰

๋ฉ”๋ชจ per.: ๋ถ„๋ช…ํžˆ ์›๋ณธ ๊ธฐ์‚ฌ์—์„œ ์ž‘์„ฑ์ž๋Š” ์‹ค์ˆ˜๋กœ 2๋‹จ๊ณ„์—์„œ ์ด ๋ธ”๋ก์˜ ํ…์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋‹จ๊ณ„์—์„œ๋Š” ํ•ด๋‹น ์—ด์„ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. last_name.

์ƒˆ ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํ•ด๋‹น ๋‚ด์šฉ์„ ๋ณต์‚ฌํ•˜์—ฌ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ JAR์„ ๋กค๋ฐฑํ•˜๊ฑฐ๋‚˜ ์ด์ „ JAR์„ ์‹คํ–‰ ์ค‘์ธ ๊ฒฝ์šฐ ์‹คํ–‰ ์ค‘์— ์ค‘๋‹จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กค๋ฐฑ

ํ˜„์žฌ ์•ฑ ๋ฒ„์ „์ด ์žˆ์Šต๋‹ˆ๋‹ค. 3.0.0 ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค v3... ๋ฒ„์ „ 3.0.0 ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค last_name. ์ด๋Š” ๋‹ค์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. surname ๊ฐ€์žฅ ์ตœ์‹  ์ •๋ณด๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

๋‹จ๊ณ„ :

  1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฒ„์ „์œผ๋กœ ๋กค๋ฐฑ 2.0.0.
  2. ๋ฒ„์ „ 2.0.0 ์šฉ๋„์™€ last_name ะธ surname.
  3. ๋ฒ„์ „ 2.0.0 ์ทจํ•  ๊ฒƒ surname, XNUMX์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ -last_name

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” ๊ตฌ์กฐ์  ๋ณ€ํ™”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ด์ „ ๋ฐ์ดํ„ฐ์˜ ์ตœ์ข… ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

์ฝ”๋“œ ๋ณ€๊ฒฝ

๋ฉ”๋ชจ per.: ์ด ๋ธ”๋ก์— ๋Œ€ํ•œ ์„ค๋ช…๋„ ์ž‘์„ฑ์ž๊ฐ€ 2๋‹จ๊ณ„์—์„œ ์‹ค์ˆ˜๋กœ ๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ์‚ฌ์˜ ๋…ผ๋ฆฌ์— ๋”ฐ๋ผ ์ด ๋‹จ๊ณ„์˜ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ์—ด๊ณผ ํ•จ๊ป˜ ์ž‘๋™ํ•˜๋Š” ์š”์†Œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. last_name.

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

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return this.surname;
    }

    public void setSurname(String lastname) {
        this.surname = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", surname=" + this.surname
                + "]";
    }
}

4๋‹จ๊ณ„: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ last_name ์ œ๊ฑฐ

์•ฑ ๋ฒ„์ „ : 4.0.0

DB ๋ฒ„์ „: v4

๋…ผํ‰

๋ฒ„์ „ ์ฝ”๋“œ ๋•Œ๋ฌธ์— 3.0.0 ์ปฌ๋Ÿผ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค last_name, ๋กค๋ฐฑํ•˜๋ฉด ์‹คํ–‰ ์ค‘์— ๋‚˜์œ ์ผ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 3.0.0 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์—ด์„ ์ œ๊ฑฐํ•œ ํ›„.

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๋กœ๊ทธ

We will do it in the following way:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Starting app in version 2.0.0

Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Killing app 1.0.0

Starting app in version 3.0.0

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Killing app 2.0.0

Starting app in version 4.0.0

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

DB ๋ณ€๊ฒฝ

๊ด€๋ จ v3 ์šฐ๋ฆฌ๋Š” ๋‹จ์ง€ ์—ด์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค last_name ๋ˆ„๋ฝ๋œ ์ œํ•œ ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;

-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

์ฝ”๋“œ ๋ณ€๊ฒฝ

์ฝ”๋“œ์—๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์—†์Šต๋‹ˆ๋‹ค.

์ถœ๋ ฅ

์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ฐฐํฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ์—ด ์ด๋ฆ„ ๋ณ€๊ฒฝ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์ˆ˜ํ–‰๋œ ์ž‘์—…์„ ์š”์•ฝํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  1. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „ ๋ฐฐํฌ 1.0.0 ั v1 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ(์—ด ์ด๋ฆ„ = last_name)
  2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „ ๋ฐฐํฌ 2.0.0, ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ณณ last_name ะธ surname. ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์€ ๋‹ค์Œ์—์„œ ์ฝ์Šต๋‹ˆ๋‹ค. last_name. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฒ„์ „์ด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. v2๋‹ค์Œ๊ณผ ๊ฐ™์€ ์—ด์„ ํฌํ•จํ•˜๋Š” last_name๊ณผ surname. surname l์˜ ๋ณต์‚ฌ๋ณธ์ด์—์š”ast_name. (์ฐธ๊ณ : ์ด ์—ด์—๋Š” null์ด ์•„๋‹Œ ์ œ์•ฝ ์กฐ๊ฑด์ด ์žˆ์–ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.)
  3. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „ ๋ฐฐํฌ 3.0.0, ๋ฐ์ดํ„ฐ๋งŒ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. surname ์„ฑ์—์„œ ์ฝ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ฒฝ์šฐ ๋งˆ์ง€๋ง‰ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์ง„ํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค. last_name ะฒ surname. ๋˜ํ•œ ํ•œ๊ณ„ NULL ์•„๋‹˜ ์—์„œ ์ œ๊ฑฐ๋จ last_name. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ํ˜„์žฌ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค v3
  4. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„์ „ ๋ฐฐํฌ 4.0.0 - ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐฐํฌ v4, ์ œ๊ฑฐ last_name. ์—ฌ๊ธฐ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ˆ„๋ฝ๋œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ ๋ฐฉ์‹์„ ๋”ฐ๋ฅด๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค/์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ˜ธํ™˜์„ฑ์„ ์†์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ํ•ญ์ƒ ํ•˜๋‚˜์˜ ๋ฒ„์ „์„ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•”ํ˜ธ

์ด ๊ธฐ์‚ฌ์— ์‚ฌ์šฉ๋œ ๋ชจ๋“  ์ฝ”๋“œ๋Š” ๋‹ค์Œ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊นƒํ—ˆ๋ธŒ. ์•„๋ž˜๋Š” ์ถ”๊ฐ€ ์„ค๋ช…์ž…๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ

์ €์žฅ์†Œ๋ฅผ ๋ณต์ œํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํด๋” ๊ตฌ์กฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โ”œโ”€โ”€ boot-flyway-v1              - 1.0.0 version of the app with v1 of the schema
โ”œโ”€โ”€ boot-flyway-v2              - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
โ”œโ”€โ”€ boot-flyway-v2-bad          - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
โ”œโ”€โ”€ boot-flyway-v3              - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
โ””โ”€โ”€ boot-flyway-v4              - 4.0.0 version of the app with v4 of the schema (app can be rolled back)

์Šคํฌ๋ฆฝํŠธ

์•„๋ž˜ ์Šคํฌ๋ฆฝํŠธ์— ์„ค๋ช…๋œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

ํ™•์ธํ•˜๋ ค๋ฉด ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, ๋‹ฌ๋ฆฌ๋‹ค:

./scripts/scenario_backward_compatible.sh

๊ทธ๋ฆฌ๊ณ  ๋ณด๊ธฐ ์œ„ํ•ด ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, ๋‹ฌ๋ฆฌ๋‹ค:

./scripts/scenario_backward_incompatible.sh

์Šคํ”„๋ง ๋ถ€ํŠธ ์ƒ˜ํ”Œ ์ด๋™ ๊ฒฝ๋กœ

๋ชจ๋“  ์˜ˆ์ œ๋Š” ๋‹ค์Œ์—์„œ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. Spring Boot Sample Flyway.

๋‹น์‹ ์€ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค http://localhost:8080/flyway, ์Šคํฌ๋ฆฝํŠธ ๋ชฉ๋ก์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์˜ˆ์—๋Š” H2 ์ฝ˜์†”๋„ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค( http://localhost:8080/h2-console) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒํƒœ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(๊ธฐ๋ณธ jdbc URL์€ jdbc:h2:mem:testdb).

์ถ”๊ฐ€

๋ธ”๋กœ๊ทธ์—์„œ ๋‹ค๋ฅธ ๊ธฐ์‚ฌ๋„ ์ฝ์–ด๋ณด์„ธ์š”.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€