Model/View/Controller (MVC)

Carson Gross

htmx์™€ ํ•˜์ดํผ๋ฏธ๋””์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ํ”ํ•œ ๋ฐ˜๋Œ€ ์˜๊ฒฌ ์ค‘ ํ•˜๋‚˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

HTML์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  JSON์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๋ชจ๋ฐ”์ผ ์•ฑ์—๋„ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•˜๋ฏ€๋กœ API๋ฅผ ์ค‘๋ณตํ•˜์ง€ ์•Š์œผ๋ ค ํ•  ๊ฒƒ์ด๋‹ค.

์ด๋ฏธ ๋‹ค๋ฅธ ์—์„ธ์ด์—์„œ ์ €๋Š” JSON API์™€ ํ•˜์ดํผ๋ฏธ๋””์–ด API๋ฅผ ๋ณ„๊ฐœ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์„ค๋ช…ํ•œ ๋ฐ” ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ ์—์„ธ์ด์—์„œ ์ €๋Š” HTML์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ API ์—”๋“œํฌ์ธํŠธ๋ฅผ ์•ˆ์ •์ ์ด๊ณ  ๊ทœ์น™์ ์ด๋ฉฐ ํ‘œํ˜„๋ ฅ์ด ์žˆ๋Š” JSON ๋ฐ์ดํ„ฐ API๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด API๋ฅผ โ€œ์ค‘๋ณตโ€œํ•  ๊ฒƒ์„ (์–ด๋Š ์ •๋„๊นŒ์ง€) ๋ช…์‹œ์ ์œผ๋กœ ๊ถŒ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ์•„์ด๋””์–ด์™€ ๊ด€๋ จ๋œ ๋Œ€ํ™”๋ฅผ ๋˜๋Œ์•„๋ณด๋ฉด์„œ, ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์ €๋งŒํผ ์ต์ˆ™ํ•˜์ง€ ์•Š์€ ํŒจํ„ด์„ ๋‹น์—ฐํžˆ ์•Œ๊ณ  ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ๋ชจ๋ธ/๋ทฐ/์ปจํŠธ๋กค๋Ÿฌ (MVC) ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

#MVC ์†Œ๊ฐœ

์ตœ๊ทผ ํŒŸ์บ์ŠคํŠธ์—์„œ ๋งŽ์€ ์ Š์€ ์›น ๊ฐœ๋ฐœ์ž๋“ค์ด MVC์— ๋Œ€ํ•œ ๊ฒฝํ—˜์ด ๋งŽ์ง€ ์•Š๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ณ  ์กฐ๊ธˆ ์ถฉ๊ฒฉ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์•„๋งˆ๋„ ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(SPA)์ด ํ‘œ์ค€์ด ๋˜๋ฉด์„œ ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ๊ฐ€ ๋ถ„๋ฆฌ๋œ ๊ฒƒ๊ณผ ๊ด€๋ จ์ด ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

MVC๋Š” ์›น ์ด์ „์— ์กด์žฌํ•˜๋˜ ๊ฐ„๋‹จํ•œ ํŒจํ„ด์œผ๋กœ, ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ทธ๋ž˜ํ”ฝ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฑฐ์˜ ๋ชจ๋“  ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€๋žต์ ์ธ ์•„์ด๋””์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

๋งŽ์€ ๋ณ€ํ˜•์ด ์žˆ์ง€๋งŒ, ๊ธฐ๋ณธ ์•„์ด๋””์–ด๋Š” ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

์ดˆ๊ธฐ ์›น ๊ฐœ๋ฐœ์—์„œ๋Š” ๋งŽ์€ ์„œ๋ฒ„ ์ธก ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ MVC ํŒจํ„ด์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ๊ฐ€์žฅ ์ต์ˆ™ํ•œ ๊ตฌํ˜„์€ Ruby On Rails๋กœ, ๊ฐ ์ฃผ์ œ์— ๋Œ€ํ•œ ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง€์†๋˜๋Š” ๋ชจ๋ธ, HTML ๋ทฐ ์ƒ์„ฑ์„ ์œ„ํ•œ ๋ทฐ, ๊ทธ๋ฆฌ๊ณ  ๋‘˜ ์‚ฌ์ด๋ฅผ ์กฐ์ •ํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ.

Rails์—์„œ ๋Œ€๋žต์ ์ธ ์•„์ด๋””์–ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

Rails๋Š” HTML ๋ฐ HTTP ์š”์ฒญ/์‘๋‹ต ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ํ‘œ์ค€์ (๋‹ค์†Œ โ€œ์–•๊ณ โ€ ๋‹จ์ˆœํ™”๋œ) MVC ํŒจํ„ด์˜ ๊ตฌํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

#Fat Model/Skinny Controller

Rails ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์ž์ฃผ ์–ธ๊ธ‰๋˜๋Š” ๊ฐœ๋… ์ค‘ ํ•˜๋‚˜๋Š” โ€œFat Model, Skinny Controllerโ€์ž…๋‹ˆ๋‹ค. ์ด ์•„์ด๋””์–ด๋Š” ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ„๋‹จํ•ด์•ผ ํ•˜๋ฉฐ, ๋ชจ๋ธ์—์„œ ๋ฉ”์„œ๋“œ ํ•˜๋‚˜ ๋˜๋Š” ๋‘ ๊ฐœ๋ฅผ ํ˜ธ์ถœํ•œ ํ›„ ๋ฐ”๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ทฐ์— ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ชจ๋ธ์€ ๋ฐ˜๋ฉด์— ๋„๋ฉ”์ธ ํŠนํ™” ๋กœ์ง์ด ๋งŽ์ด ํฌํ•จ๋œ โ€œ๋‘๊บผ์šดโ€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ด๊ฒƒ์ด God Objects๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฐ˜๋Œ€ ์˜๊ฒฌ๋„ ์žˆ์ง€๋งŒ, ์ด๋Š” ์ง€๊ธˆ ๋…ผ์™ธ๋กœ ๋‘๊ฒ ์Šต๋‹ˆ๋‹ค.)

์ด Fat Model/Skinny Controller ๊ฐœ๋…์„ ์—ผ๋‘์— ๋‘๊ณ , MVC ํŒจํ„ด์˜ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์™€ ์ด ํŒจํ„ด์ด ์™œ ์œ ์šฉํ•œ์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

#MVC ์Šคํƒ€์ผ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

์ด๋ฒˆ ์˜ˆ์ œ๋กœ๋Š” ์ œ๊ฐ€ ์ข‹์•„ํ•˜๋Š” ์˜จ๋ผ์ธ ์—ฐ๋ฝ์ฒ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ฃผ์–ด์ง„ ์—ฐ๋ฝ์ฒ˜ ํŽ˜์ด์ง€๋ฅผ HTML ํŽ˜์ด์ง€๋กœ ์ƒ์„ฑํ•˜์—ฌ ํ‘œ์‹œํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

@app.route("/contacts")
def contacts():
    contacts = Contact.all(page=request.args.get('page', default=0, type=int))
    return render_template("index.html", contacts=contacts)

์—ฌ๊ธฐ์„œ ์ €๋Š” Python๊ณผ Flask๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ œ๊ฐ€ Hypermedia Systems ์ฑ…์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ๋“ค์ž…๋‹ˆ๋‹ค.

์ด ์˜ˆ์ œ์—์„œ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋งค์šฐ โ€œ์–‡์Šต๋‹ˆ๋‹คโ€: ์ปจํŠธ๋กค๋Ÿฌ๋Š” Contact ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์—ฐ๋ฝ์ฒ˜๋ฅผ ์กฐํšŒํ•˜๊ณ , ์š”์ฒญ์—์„œ ์ „๋‹ฌ๋œ page ์ธ์ž๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” ๋งค์šฐ ์ „ํ˜•์ ์ธ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค: ์ปจํŠธ๋กค๋Ÿฌ์˜ ์—ญํ• ์€ HTTP ์š”์ฒญ์„ ๋„๋ฉ”์ธ ๋กœ์ง์— ๋งคํ•‘ํ•˜๊ณ , HTTP ํŠน์ • ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜์—ฌ ๋ชจ๋ธ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ(์˜ˆ: ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ)๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ดํ›„ index.html ํ…œํ”Œ๋ฆฟ์— ํŽ˜์ด์ง€๋œ ์—ฐ๋ฝ์ฒ˜ ์ปฌ๋ ‰์…˜์„ ์ „๋‹ฌํ•˜์—ฌ, ์ด๋ฅผ HTML ํŽ˜์ด์ง€๋กœ ๋ Œ๋”๋งํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

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

#JSON ๋ฐ์ดํ„ฐ API ์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ

์ด๋ ‡๊ฒŒ ๋น„๊ต์  ์ž˜ ๊ฐœ๋ฐœ๋œ Contact ๋ชจ๋ธ์ด ๋„๋ฉ”์ธ์„ ์บก์Šํ™”ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋น„์Šทํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ HTML ๋ฌธ์„œ๊ฐ€ ์•„๋‹Œ JSON ๋ฌธ์„œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋‹ค๋ฅธ API ์—”๋“œํฌ์ธํŠธ/์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‰ฝ๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

@app.route("/api/v1/contacts")
def contacts():
    contacts = Contact.all(page=request.args.get('page', default=0, type=int))
    return jsonify(contacts=contacts)

#ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์ค‘๋ณตํ•˜๊ณ  ์žˆ์ž–์•„์š”!

์ด ๋‘ ์ปจํŠธ๋กค๋Ÿฌ ํ•จ์ˆ˜๋“ค์„ ๋ณด๋ฉด โ€œ์ด๊ฑด ์–ด๋ฆฌ์„์–ด, ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฑฐ์˜ ๋™์ผํ•˜์ž–์•„โ€œ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹น์‹ ์ด ๋งž์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ๋กœ์„œ๋Š” ๊ฑฐ์˜ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹œ์Šคํ…œ์— ๋‘ ๊ฐ€์ง€ ์ž ์žฌ์  ์ถ”๊ฐ€ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

#JSON API์— ๋Œ€ํ•œ ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ… ์ถ”๊ฐ€

๋จผ์ €, DDOS ๊ณต๊ฒฉ์ด๋‚˜ ์ž˜๋ชป ์ž‘์„ฑ๋œ ์ž๋™ํ™” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‹œ์Šคํ…œ์„ ๋ฒ”๋žŒํ•˜์ง€ ์•Š๋„๋ก JSON API์— ๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํŒ…์„ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด Flask-Limiter ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

@app.route("/api/v1/contacts")
@limiter.limit("1 per second")
def contacts():
    contacts = Contact.all(page=request.args.get('page', default=0, type=int))
    return jsonify(contacts=contacts)

๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ฃผ์˜ํ•  ์ ์€: ์šฐ๋ฆฌ๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ด ์ œํ•œ์„ ์ ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๊ณ , JSON ๋ฐ์ดํ„ฐ API์—๋งŒ ์ ์šฉํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‘ ๊ฐ€์ง€๋ฅผ ๋ถ„๋ฆฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์‰ฝ๊ฒŒ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

#์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ทธ๋ž˜ํ”„ ์ถ”๊ฐ€ํ•˜๊ธฐ

์ด์ œ ๋‹ค๋ฅธ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•ด ๋ด…์‹œ๋‹ค. HTML ๊ธฐ๋ฐ˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ index.html ํ…œํ”Œ๋ฆฟ์— ํ•˜๋ฃจ์— ์ถ”๊ฐ€๋œ ์—ฐ๋ฝ์ฒ˜ ์ˆ˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ทธ๋ž˜ํ”„๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ๊ทธ๋ž˜ํ”„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐ ๋น„์šฉ์ด ๋งŽ์ด ๋“ญ๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” index.html ํ…œํ”Œ๋ฆฟ์˜ ๋ Œ๋”๋ง์„ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ์— ์˜ํ•ด ์ฐจ๋‹จ๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฅผ ์œ„ํ•ด Lazy Loading ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด HTML๋กœ ํ•ด๋‹น ์ง€์—ฐ ๋กœ๋”ฉ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ƒˆ๋กœ์šด ์—”๋“œํฌ์ธํŠธ /graph๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@app.route("/graph")
def graph():
    graphInfo = Contact.computeGraphInfo(page=request.args.get('page', default=0, type=int))
    return render_template("graph.html", info=graphInfo)

์—ฌ๊ธฐ์„œ๋„ ์—ญ์‹œ, ์šฐ๋ฆฌ์˜ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์—ฌ์ „ํžˆ โ€œ์–‡์Šต๋‹ˆ๋‹คโ€: ๋‹จ์ˆœํžˆ ๋ชจ๋ธ์— ์ž‘์—…์„ ์œ„์ž„ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ทฐ๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ์€, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ HTML API์— ์ƒˆ๋กœ์šด ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์ง€๋งŒ, _JSON ๋ฐ์ดํ„ฐ API์—๋Š” ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ_์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์ด (UI ์š”๊ตฌ์— ์˜ํ•ด ์™„์ „ํžˆ ์ฃผ๋„๋˜๋Š”) ํŠน์ˆ˜ํ•œ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์œ ์ง€๋  ๊ฒƒ์ด๋ผ๊ณ  ๋‹ค๋ฅธ ๋น„์›น ํด๋ผ์ด์–ธํŠธ๋“ค์—๊ฒŒ ์•ฝ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

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

๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” ํ•˜์ดํผ๋ฏธ๋””์–ด API์— ํ•„์š”ํ•œ ์œ ์—ฐ์„ฑ๊ณผ JSON ๋ฐ์ดํ„ฐ API์— ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

MVC ๊ด€์ ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€, ๋„๋ฉ”์ธ ๋กœ์ง์ด ๋ชจ๋ธ์— ์ˆ˜์ง‘๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋‘ ๊ฐœ์˜ API๋ฅผ ์œ ์—ฐํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ๋„ ์ƒ๋‹นํ•œ ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. JSON๊ณผ HTML ์ปจํŠธ๋กค๋Ÿฌ ๊ฐ„์— ์ดˆ๊ธฐ์—๋Š” ๋งŽ์€ ์œ ์‚ฌ์„ฑ์ด ์žˆ์—ˆ์ง€๋งŒ, ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด์„œ ์ฐจ๋ณ„ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์—, ๋ชจ๋ธ ๋กœ์ง์„ ์ค‘๋ณตํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค: ๋‘ ์ปจํŠธ๋กค๋Ÿฌ ๋ชจ๋‘ ์ƒ๋Œ€์ ์œผ๋กœ โ€œ์–‡๊ฒŒโ€ ์œ ์ง€๋˜์—ˆ์œผ๋ฉฐ, ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋ธ ๊ฐ์ฒด์— ์œ„์ž„ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๋‘ API๋Š” ์„œ๋กœ ๋ถ„๋ฆฌ๋˜์—ˆ์œผ๋ฉฐ, ๋„๋ฉ”์ธ ๋กœ์ง์€ ์ค‘์•™์— ์ง‘์ค‘๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

(์ด ์ ์€ ์ œ๊ฐ€ ์™œ ์ฝ˜ํ…์ธ  ํ˜‘์ƒ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”์ง€์™€ ๊ฐ™์€ ์—”๋“œํฌ์ธํŠธ์—์„œ HTML๊ณผ JSON์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š” ์ด์œ ์™€๋„ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.)

#MVC ํ”„๋ ˆ์ž„์›Œํฌ

Spring, ASP.NET, Rails์™€ ๊ฐ™์€ ๋งŽ์€ ์˜ค๋ž˜๋œ ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๋งค์šฐ ๊ฐ•๋ ฅํ•œ MVC ๊ฐœ๋…์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋กœ์ง์„ ๋งค์šฐ ํšจ๊ณผ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Django๋Š” MVT๋ผ๋Š” ๊ฐœ๋…์„ ์‚ฌ์šฉํ•˜์—ฌ MVC์™€ ์œ ์‚ฌํ•œ ๋ณ€ํ˜•์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ MVC์— ๋Œ€ํ•œ ๊ฐ•๋ ฅํ•œ ์ง€์›์€ ์ด๋Ÿฌํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ htmx์™€ ์ž˜ ์–ด์šธ๋ฆฌ๋Š” ์ด์œ  ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ์ด๋Ÿฌํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ htmx์— ๋Œ€ํ•ด ํฅ๋ฏธ๋ฅผ ๋Š๋ผ๋Š” ์ด์œ ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

์œ„์˜ ์˜ˆ์‹œ๋“ค์€ ๋ถ„๋ช… ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์— ํŽธํ–ฅ๋˜์–ด ์žˆ์ง€๋งŒ, ๋™์ผํ•œ ์•„์ด๋””์–ด๋Š” ํ•จ์ˆ˜ํ˜• ์ปจํ…์ŠคํŠธ์—์„œ๋„ ์ ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

#๊ฒฐ๋ก 

MVC ๊ฐœ๋…์ด ์ƒˆ๋กœ์šด ๋ถ„๋“ค์—๊ฒŒ๋Š”, ์ด ๊ธ€์ด MVC ๊ฐœ๋…์— ๋Œ€ํ•ด ์ข‹์€ ๊ฐ์„ ์ฃผ๊ณ , ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ด ์กฐ์ง ์›์น™์„ ์ฑ„ํƒํ•จ์œผ๋กœ์จ API๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด์„œ๋„ ์ฝ”๋“œ์˜ ์ค‘๋ณต์„ ํ”ผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

</>