Hypermedia-Friendly Scripting

Carson Gross

REST์— ๋Œ€ํ•œ ์ œ์•ฝ ์กฐ๊ฑด ์ง‘ํ•ฉ์— ์ตœ์ข…์ ์œผ๋กœ ์ถ”๊ฐ€๋œ ๊ฒƒ์€ 3.5.3์ ˆ์˜ ์ฝ”๋“œ ์˜จ ๋””๋งจ๋“œ ์Šคํƒ€์ผ์—์„œ ๋น„๋กฏ๋ฉ๋‹ˆ๋‹ค(Figure 5-8). REST๋Š” ํด๋ผ์ด์–ธํŠธ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ธฐ ์œ„ํ•ด ์• ํ”Œ๋ฆฟ์ด๋‚˜ ์Šคํฌ๋ฆฝํŠธ ํ˜•ํƒœ๋กœ ์ฝ”๋“œ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‚ฌ์ „์— ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๊ธฐ๋Šฅ์˜ ์ˆ˜๋ฅผ ์ค„์ž„์œผ๋กœ์จ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‹จ์ˆœํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋ฐฐํฌ ํ›„ ๊ธฐ๋Šฅ์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ์œผ๋กœ์จ ์‹œ์Šคํ…œ ํ™•์žฅ์„ฑ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ๊ฐ€์‹œ์„ฑ์„ ๋‚ฎ์ถ”๊ธฐ ๋•Œ๋ฌธ์— REST ๋‚ด์—์„œ ์„ ํƒ์ ์ธ ์ œ์•ฝ ์กฐ๊ฑด์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.

-- Roy Fielding - ๋Œ€ํ‘œ ์ƒํƒœ ์ „์†ก(REST)

#์Šคํฌ๋ฆฝํŒ…๊ณผ ์›น

ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์šฐ๋ฆฌ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋…ผ์˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ํ˜„์žฌ ์ธ๊ธฐ๋ฅผ ๋Œ๊ณ  ์žˆ๋Š” SPA ์ ‘๊ทผ๋ฒ•๊ณผ ๋‹ฌ๋ฆฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜์œผ๋กœ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. SPA ์ ‘๊ทผ๋ฒ•์€ JavaScript ๊ธฐ๋ฐ˜์ด๋ฉฐ, ๋„คํŠธ์›Œํฌ ์ˆ˜์ค€์—์„œ RPC ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค.

HDA(Hypermedia-Driven Application) ๋ฌธ์„œ์—์„œ ์Šคํฌ๋ฆฝํŒ…์— ๋Œ€ํ•ด ๊ฐ„๋‹จํžˆ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค:

HDA์—์„œ ํ•˜์ดํผ๋ฏธ๋””์–ด(HTML)๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•˜๋Š” ์ฃผ์š” ๋งค์ฒด์ด๋ฏ€๋กœ:

์Šคํฌ๋ฆฝํŒ…์€ ๊ธฐ์กด ํ•˜์ดํผ๋ฏธ๋””์–ด(HTML)๋ฅผ ๋ณด์™„ํ•˜์ง€๋งŒ, ์ด๋ฅผ ์ดˆ์›”ํ•˜๊ฑฐ๋‚˜ ๊ธฐ๋ณธ์ ์ธ REST-ful ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ „๋ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ์ด ๋งˆ์ง€๋ง‰ ์–ธ๊ธ‰์„ ํ™•์žฅํ•˜์—ฌ REST-ful, ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ดˆ์›”ํ•˜๊ฑฐ๋‚˜ ์ „๋ณตํ•˜์ง€ ์•Š๋Š” ์Šคํฌ๋ฆฝํŒ…์ด ์–ด๋–ค ๋ชจ์Šต์ธ์ง€ ์„ค๋ช…ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ทœ์น™๋“ค์€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ์ง์ ‘ ์ž‘์„ฑ๋œ ์Šคํฌ๋ฆฝํŒ…๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ผ๋ฐ˜์ ์ธ JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์˜ ๊ธฐ๋ณธ ๊ทœ์น™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

์ด ๊ทœ์น™๋“ค์€ ์•„๋ž˜์—์„œ ๋” ์ž์„ธํžˆ ์„ค๋ช…๋ฉ๋‹ˆ๋‹ค.

#๊ธฐ๋ณธ ์›์น™

HDA์˜ ๊ธฐ๋ณธ ์›์น™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ์˜ ์—”์ง„์œผ๋กœ์„œ์˜ ํ•˜์ดํผ๋ฏธ๋””์–ด(HATEOAS)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ… ์ ‘๊ทผ๋ฒ•์€ ์ด ์›์น™์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ, ์ด๋Š” ์Šคํฌ๋ฆฝํŒ…์ด ์„œ๋ฒ„์™€์˜ ๋น„ํ•˜์ดํผ๋ฏธ๋””์–ด ๋„คํŠธ์›Œํฌ ๊ตํ™˜์„ ํ”ผํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์€ ์„œ๋ฒ„์—์„œ ํ•˜์ดํผ๋ฏธ๋””์–ด(์˜ˆ: HTML)๋ฅผ ์‚ฌ์šฉํ•œ ์‘๋‹ต์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ API ํ˜•์‹(์˜ˆ: ๋‹จ์ˆœ JSON)์„ ์‚ฌ์šฉํ•œ ์‘๋‹ต์„ ๋ฐ›๋Š” fetch() ๋ฐ XMLHttpRequest ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

HATEOAS๋ฅผ ์กด์ค‘ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๋˜ํ•œ JavaScript์— ์ €์žฅ๋œ ๋ณต์žกํ•œ ์ƒํƒœ(์ฆ‰, DOM ์™ธ๋ถ€์— ์ €์žฅ๋œ ์ƒํƒœ)๋ฅผ ํ”ผํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ๋งˆ์ง€๋ง‰ ์ง„์ˆ ์€ ์ˆ˜์ •์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค: JavaScript์— ์ƒํƒœ๋ฅผ ํด๋ผ์ด์–ธํŠธ ์ธก์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ๋” ์ •๊ตํ•œ ํ”„๋ก ํŠธ์—”๋“œ ๊ฒฝํ—˜(์˜ˆ: ์œ„์ ฏ)์„ ์ง์ ‘ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ ํ—ˆ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•„๋”ฉ์ด REST์—์„œ ์Šคํฌ๋ฆฝํŒ…์˜ ๋ชฉ์ ์— ๋Œ€ํ•ด ๋งํ•œ ๊ฒƒ์„ ๋‹ค์‹œ ๊ฐ•์กฐํ•˜์ž๋ฉด:

๋ฐฐํฌ ํ›„ ๊ธฐ๋Šฅ์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ์œผ๋กœ์จ ์‹œ์Šคํ…œ ํ™•์žฅ์„ฑ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์Šคํฌ๋ฆฝํŒ…์€ ํ•˜์ดํผ๋ฏธ๋””์–ด(์˜ˆ: HTML)๋ณด๋‹ค ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ ํ•˜์ดํผ๋ฏธ๋””์–ด๋ฅผ ๋” ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” REST-ful ์‹œ์Šคํ…œ์˜ ์ •๋‹นํ•œ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

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

์•„์ด๋””์–ด๋Š” ์Šคํฌ๋ฆฝํŒ…์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๋˜, ๋งŽ์€ SPA ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ HTML์„ ๋” ํฐ JavaScript ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์˜ ๋‹จ์ˆœํ•œ UI ์„ค๋ช… ์–ธ์–ด๋กœ ์ „๋ฝ์‹œํ‚ค์ง€ ์•Š๊ณ  HTML๊ณผ ์ž˜ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

#state

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

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฒ„ํŠผ์ด๋‚˜ ์•ต์ปค๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋‹ค๋ฅธ ์š”์†Œ์— ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ด๋ฅผ ๋ณด์ด๋„๋ก ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ๊ฐ€์‹œ์„ฑ ํ† ๊ธ€์„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”.

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

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

#events

JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ํ›Œ๋ฅญํ•œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š” ํ’๋ถ€ํ•œ ์ปค์Šคํ…€ ์ด๋ฒคํŠธ ๋ชจ๋ธ์„ ๊ฐ€์ง€๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” JavaScript ๊ธฐ๋ฐ˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š” htmx์™€ ๊ฐ™์€ ํ•˜์ดํผ๋ฏธ๋””์–ด ์ง€ํ–ฅ JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ด๋Ÿฌํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋“ฃ๊ณ  ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ตํ™˜์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ชจ๋“  JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๋™์ž‘์„ ํ†ตํ•ด ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ๋™ํ•  ์ˆ˜ ์žˆ๋Š” _ํ•˜์ดํผ๋ฏธ๋””์–ด ์ปจํŠธ๋กค_์ด ๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ ์˜ˆ๋กœ Sortable.js ์˜ˆ์ œ๋ฅผ ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ htmx๋Š” Sortable.js์—์„œ ํŠธ๋ฆฌ๊ฑฐ๋˜๋Š” end ์ด๋ฒคํŠธ๋ฅผ ๋“ฃ์Šต๋‹ˆ๋‹ค:

<form class="sortable" hx-post="/items" hx-trigger="end">
  <div class="htmx-indicator">Updating...</div>
  <div><input type='hidden' name='item' value='1'/>Item 1</div>
  <div><input type='hidden' name='item' value='2'/>Item 2</div>
  <div><input type='hidden' name='item' value='3'/>Item 3</div>
  <div><input type='hidden' name='item' value='4'/>Item 4</div>
  <div><input type='hidden' name='item' value='5'/>Item 5</div>
</form>

end ์ด๋ฒคํŠธ๋Š” Sortable.js์—์„œ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์ด ์™„๋ฃŒ๋  ๋•Œ ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. htmx๋Š” hx-trigger ์†์„ฑ์„ ํ†ตํ•ด ์ด ์ด๋ฒคํŠธ๋ฅผ ๋“ฃ๊ณ  HTTP ์š”์ฒญ์„ ๋ฐœํ–‰ํ•˜์—ฌ ์„œ๋ฒ„์™€ ํ•˜์ดํผ๋ฏธ๋””์–ด๋ฅผ ๊ตํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Sortable.js๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ๊ธฐ๋Šฅ์ด ๊ฐ•๋ ฅํ•œ ํ•˜์ดํผ๋ฏธ๋””์–ด ์ปจํŠธ๋กค๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

#Islands

์ตœ๊ทผ ์›น ๊ฐœ๋ฐœ์—์„œ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ๋Š” ํŠธ๋ Œ๋“œ ์ค‘ ํ•˜๋‚˜๋Š” โ€œ์•„์ผ๋žœ๋“œโ€๋ผ๋Š” ๊ฐœ๋…์ž…๋‹ˆ๋‹ค:

์•„์ผ๋žœ๋“œ ์•„ํ‚คํ…์ฒ˜๋Š” ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ ์›น ํŽ˜์ด์ง€ ๋‚ด์—์„œ ์ž‘์€, ์ง‘์ค‘๋œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์š”์†Œ๋“ค์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

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

์ด๋ฒคํŠธ๋Š” ๋น„ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜์˜ ์•„์ผ๋žœ๋“œ๋ฅผ ๋” ๋„“์€ ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ํ†ตํ•ฉํ•˜๋Š” ๊น”๋”ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ, ์œ„์—์„œ ์„ค๋ช…ํ•œ Sortable.js ์˜ˆ์ œ์ฒ˜๋Ÿผ โ€œ๋‚ด๋ถ€โ€ ์•„์ผ๋žœ๋“œ๋ฅผ โ€œ์™ธ๋ถ€โ€ ํ•˜์ดํผ๋ฏธ๋””์–ด ์ปจํŠธ๋กค๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Deniz AkลŸimลŸek๋Š” ๋น„ํ•˜์ดํผ๋ฏธ๋””์–ด ์•„์ผ๋žœ๋“œ๋ฅผ ๋” ํฐ ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์— ํฌํ•จํ•˜๋Š” ๊ฒƒ์ด ๋ฐ˜๋Œ€์˜ ๊ฒฝ์šฐ๋ณด๋‹ค ์ผ๋ฐ˜์ ์œผ๋กœ ๋” ์‰ฝ๋‹ค๋Š” ์ ์„ ๊ด€์ฐฐํ–ˆ์Šต๋‹ˆ๋‹ค.

#์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ

ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์„ ์œ„ํ•œ ๋งˆ์ง€๋ง‰ ๊ทœ์น™์€ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŒ…์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํ•˜์ดํผ๋ฏธ๋””์–ด ๋‚ด์— ์ง์ ‘ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด๋ฉฐ, ์™ธ๋ถ€ ํŒŒ์ผ์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ๊ณผ๋Š” ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์ด๋Š” ์—ฌ๊ธฐ์„œ ์–ธ๊ธ‰๋œ ๋‹ค๋ฅธ ๊ฐœ๋…๋“ค์— ๋น„ํ•ด ๋…ผ๋ž€์ด ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์„ ์œ„ํ•œ โ€œ์„ ํƒ์ โ€ ๊ทœ์น™์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค: ๊ณ ๋ คํ•ด๋ณผ ๋งŒํ•˜์ง€๋งŒ ํ•„์ˆ˜๋Š” ์•„๋‹™๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์Šคํฌ๋ฆฝํŒ… ์ ‘๊ทผ๋ฒ•์€ ๋…ํŠนํ•˜์ง€๋งŒ, ์ผ๋ถ€ HTML ์Šคํฌ๋ฆฝํŒ… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ํŠนํžˆ Alpine.js์™€ hyperscript์—์„œ ์ฑ„ํƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” hyperscript์˜ ์˜ˆ์ž…๋‹ˆ๋‹ค:

<button _="on click toggle .visible on the next <section/>">
    ๋‹ค์Œ ์„น์…˜ ํ‘œ์‹œ
</button>
<section>
    ....
</section>

์ด ๋ฒ„ํŠผ์€ ํด๋ฆญํ•  ๋•Œ section ์š”์†Œ์˜ .visible ํด๋ž˜์Šค๋ฅผ ํ† ๊ธ€ํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŒ… ์ ‘๊ทผ์˜ ์ฃผ์š” ์žฅ์ ์€ ๊ฐœ๋…์ ์œผ๋กœ ํ•˜์ดํผ๋ฏธ๋””์–ด ์ž์ฒด๊ฐ€ ๊ฐ•์กฐ๋œ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํ•˜์ดํผ๋ฏธ๋””์–ด ์Šคํฌ๋ฆฝํŒ…๋ณด๋‹ค ๋” ๊ฐ•์กฐ๋ฉ๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋ฅผ JSX Components์™€ ๋น„๊ตํ•ด๋ณด๋ฉด, ์Šคํฌ๋ฆฝํŒ… ์–ธ์–ด(JavaScript)๊ฐ€ ํ•ต์‹ฌ ๊ฐœ๋…์ด๋ฉฐ, ํ•˜์ดํผ๋ฏธ๋””์–ด/HTML์ด ๊ทธ ์•ˆ์— ํฌํ•จ๋˜์–ด ์žˆ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค:

class Button extends React.Component {
    constructor(props) {
        // ...
    }
    toggleVisibilityOnNextSection() {
        // ...
    }
    render() {
        return <button onClick={this.toggleVisibilityOnNextSection}>{this.props.text}</button>;
    }
}

์—ฌ๊ธฐ์„œ JavaScript๊ฐ€ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ์ˆ ์ด๋ฉฐ, ํ•˜์ดํผ๋ฏธ๋””์–ด/HTML์€ UI ์„ค๋ช… ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, HTML์ด ํ•˜์ดํผ๋ฏธ๋””์–ด๋ผ๋Š” ์‚ฌ์‹ค์€ ๊ฑฐ์˜ ์ค‘์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

๋ฌผ๋ก , ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ์˜ ๊ฒฝ์šฐ, ํ•˜์ดํผ๋ฏธ๋””์–ด ๋‚ด์—์„œ ์ง์ ‘ ์ˆ˜ํ–‰๋˜๋Š” ์Šคํฌ๋ฆฝํŒ…์˜ ์–‘์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํ•œ๊ณ„๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŒ…์ด ๋„ˆ๋ฌด ๋งŽ์•„์ ธ ํ•˜์ดํผ๋ฏธ๋””์–ด ๋ฌธ์„œ์˜ โ€œํ˜•ํƒœโ€œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง€์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ hyperscript behaviors๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋“ฑ์˜ ๊ธฐ๋ฒ•์„ ํ†ตํ•ด ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŒ…์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ๊ตฌํ˜„์„ ๋ณ„๋„์˜ ํŒŒ์ผ์ด๋‚˜ ์œ„์น˜๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŒ…์€ ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ์Šคํฌ๋ฆฝํŒ…์— ํ•„์ˆ˜๋Š” ์•„๋‹ˆ์ง€๋งŒ, ์ „ํ†ต์ ์ธ ์Šคํฌ๋ฆฝํŒ…/ํ•˜์ดํผ๋ฏธ๋””์–ด ๋ถ„๋ฆฌ์™€๋Š” ๋Œ€์•ˆ์œผ๋กœ ๊ณ ๋ คํ•ด๋ณผ ๋งŒํ•ฉ๋‹ˆ๋‹ค.

#์‹ค์šฉ์ฃผ์˜

๋ฌผ๋ก , ํ˜„์‹ค ์„ธ๊ณ„์—์„œ๋Š” HATEOAS๋ฅผ ์œ„๋ฐ˜ํ•˜๊ณ  ์ด๋ฒคํŠธ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜์ง€ ์•Š๋Š” ์œ ์šฉํ•œ JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ข…์ข… ํ•˜์ดํผ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์ด๋Ÿฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฐพ๊ธฐ ์–ด๋ ค์šด ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ, ์‹ค์šฉ์ฃผ์˜๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜ ํ•˜์ดํผ๋ฏธ๋””์–ด ์นœํ™”์ ์ธ ๋ฐฉ์‹์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ด ์‰ฝ๋‹ค๋ฉด, ๊ทธ๊ฒƒ์ด ์ข‹์€ ์„ ํƒ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ƒํ•˜์ง€ ๋ชปํ•œ ๊ณณ์—์„œ, ์—…์ŠคํŠธ๋ฆผ ์ž‘์„ฑ์ž๊ฐ€ ํ’€ ๋ฆฌํ€˜์ŠคํŠธ๋ฅผ ๊ณ ๋ คํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ๊ทธ๋ ‡์ง€ ์•Š๊ณ  ์ข‹์€ ๋Œ€์•ˆ์ด ์—†๋‹ค๋ฉด, JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค๊ณ„๋œ ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

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

</>