Spark ์‹œ์ž‘ํ•˜๊ธฐ


์›น ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ž…๋ฌธ์„ ์œ„ํ•ด Java ์˜ ๊ฐ„๋‹จํ•œ ์›น ํ”„๋ ˆ์ž„์›Œํฌ์ธ Spark ๋ฅผ ์•Œ์•„๋ณด์ž.

์šฐ์„  ์ธํ…”๋ฆฌ์ œ์ด์—์„œ gradle ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

๋นŒ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด build.gradle ์ด๋ผ๋Š” ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.

Spark ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด build.gradle ์— dependency ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€ํ•œ๋‹ค.

dependencies {
    implementation "com.sparkjava:spark-core:2.9.3" // add to build.gradle (for Java users)}
}

๊ณต์‹ Spark ๋ฌธ์„œ์—๋Š” compile ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ๋‹ค.

Gradle 7.0 ๋ฒ„์ „ ๋ถ€ํ„ฐ implementation ํ‚ค์›Œ๋“œ๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•˜๊ฒŒ ๋” ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

์ด์ œ Spark ๋ฅผ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ์™„๋ฃŒ ๋˜์—ˆ๋‹ค.

import static spark.Spark.*;
 
public class HelloWorld {
    public static void main(String[] args) {
        get("/users", (req, res) -> "Hello World");
    }
}

main ์— ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‹คํ–‰ํ•ด๋ณด์ž.

์‹คํ–‰์ด ์„ฑ๊ณต๋œ ์ดํ›„ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•ด localhost:4567/users ์— ์ ‘์†ํ•ด๋ณด์ž.

์„ฑ๊ณต์ ์œผ๋กœ Hello World ๊ฐ€ ์ถœ๋ ฅ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

GET ์‚ฌ์šฉ๋ฒ•


public class HelloWorld {
    public static void main(String[] args) {
        get("/users", (req, res) -> "Hello World");
    }
}

์œ„ ์ฝ”๋“œ๋Š” HTTP ๋ฉ”์„œ๋“œ์ธ GET ์„ Spark ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•์ด๋‹ค.

์•ž์„œ ์–ธ๊ธ‰ํ•œ localhost:4567/hello ์˜ ์˜๋ฏธ๋ถ€ํ„ฐ ์‚ดํŽด๋ณด์ž.

localhost ์—์„œ 4567๋ฒˆ ํฌํŠธ์˜ /hello ์— ์ ‘์†ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.

Spark ์˜ ๊ธฐ๋ณธ ํฌํŠธ ๋ฒˆํ˜ธ๋Š” 4567 ์ด๊ณ  port() ๋ฅผ ํ†ตํ•ด ํฌํŠธ ๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

public static void get(final String path, final Route route) {
    getInstance().get(path, route);
}

์ด๋ฒˆ์—” get() ๋ฉ”์„œ๋“œ์˜ ๋‚ด๋ถ€๋ฅผ ์‚ดํŽด๋ณด์ž.

parameter ๋กœ path ์™€ route ๋ฅผ ๋ฐ›๊ณ  ์žˆ๋‹ค.

ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๋ฉด path ๋Š” โ€œ/users" ์ž„์„ ์•Œ ์ˆ˜ ์žˆ๊ณ , route ๋Š” ๋žŒ๋‹ค์‹์ด argument ๋กœ ์ฃผ์–ด์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด get() ๋ฉ”์„œ๋“œ๊ฐ€ URL path ๋ฅผ ์ฒซ๋ฒˆ์งธ argument ๋กœ ๋ฐ›๊ณ  ํ•ด๋‹น ์ฃผ์†Œ์— ์ˆ˜ํ–‰๋˜๋Š” ํ–‰์œ„๋ฅผ ๋žŒ๋‹ค์‹์œผ๋กœ ํ‘œํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ๋‹ค.

public class HelloWorld {
    public static void main(String[] args) {
        get("/users/:name", (req, res) -> "Hello " + req.params(":name"));
    }
}

๋‹ค๋ฅธ get() ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

์ด๋ฒˆ์—” path ๊ฐ’์— "/users/:name" ์„ ์ „๋‹ฌํ–ˆ๋‹ค.

๋ธŒ๋ผ์šฐ์ €์—์„œ localhost:4567/users/meatsby ๋ฅผ ํ˜ธ์ถœํ–ˆ์„ ๋•Œ, :name ๊ฐ’์— meatsby ๊ฐ€ ํ• ๋‹น๋œ๋‹ค.

req.params() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ ์ƒˆ๋กœ์šด ๋ฌธ์ž์—ด์— ์ถ”๊ฐ€์‹œ์ผœ localhost:4567/users/meatsby ์ฃผ์†Œ์—์„œ โ€œHello meatsbyโ€ ๋ฅผ ์ถœ๋ ฅํ•˜๊ฒŒ ๋œ๋‹ค.

public class HelloWorld {
    public static void main(String[] args) {
        get("/users", (req, res) -> "Hello " + req.queryParams("name") + " ๋‚˜์ด๋Š” " + req.queryParams("age"));
    }
}

๋˜ ๋‹ค๋ฅธ get() ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

๋ธŒ๋ผ์šฐ์ €์—์„œ localhost:4567/users?name=meatsby&age=26 ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

?name=meatsby&age=26 ์ด ๊ตฌ๋ฌธ์€ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š”๋ฐ, name ๊ฐ’์— meatsby ๋ฅผ age ๊ฐ’์— 26 ์„ ์ „๋‹ฌํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ–๊ณ ์žˆ๋‹ค.

์ฟผ๋ฆฌ์ŠคํŠธ๋ง์œผ๋กœ ์ „๋‹ฌ๋œ ๊ฐ’๋“ค์€ req.queryParams() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด name ๊ฐ’๊ณผ age ๊ฐ’์„ ๋ฐ›์•„ ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ ์ƒˆ๋กœ์šด ๋ฌธ์ž์—ด์— ์ถ”๊ฐ€์‹œ์ผœ localhost:4567/users?name=meatsby&age=26 ์ฃผ์†Œ์—์„œ โ€œHello meatsby ๋‚˜์ด๋Š” 26โ€ ์„ ์ถœ๋ ฅํ•˜๊ฒŒ ๋œ๋‹ค.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sign Up</title>
</head>
<body>
<h1>Sign Up</h1>
<form action="/users">
    Name : <input type="text" name="name">
    <br>
    <br>
    Age : <input type="text" name="age">
    <br>
    <br>
    <input type="submit" value="Sign Up">
</form>
</body>
</html>

์ด๋ฒˆ์—” ์ •์  html ์„ ์ถ”๊ฐ€ํ•ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ UI ๋กœ ๋ฐ›์•„๋ณด์ž.

form.html ์„ resources.static ์— ์ถ”๊ฐ€ํ•˜๋ฉด localhost:4567/form.html ์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด UI ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

form.html ๋‚ด๋ถ€์˜ input ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ๋œ๋‹ค.

๊ฐ ๋ธ”๋ก์— ๋ฐ์ดํ„ฐ๋ฅผ ์ง‘์–ด๋„ฃ๊ณ  ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด URL ์ด redirect ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ด๋•Œ form ํƒœ๊ทธ์˜ action ์„ ํ†ตํ•ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ์ฃผ์†Œ๋ฅผ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์ œ ๋ฐ์ดํ„ฐ๋Š” action ์ด ๊ฐ€๋ฆฌํ‚ค๋Š” /users ์ฃผ์†Œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š” main ์˜ get() ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌ๋œ๋‹ค.

์ „๋‹ฌ๋œ name ๊ณผ age ๋ฅผ route ๋žŒ๋‹ค์‹์— ์ ์šฉ๋˜์–ด ๊ธฐ์กด์— localhost:4567/users?name=meatsby&age=26 ๋ฅผ ํ˜ธ์ถœํ–ˆ๋˜ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ ํ•ด๋‹น ์›น์‚ฌ์ดํŠธ๊ฐ€ ํšŒ์›๊ฐ€์ž…์— ๊ด€ํ•œ ์›น์‚ฌ์ดํŠธ์ด๊ณ  ์ž…๋ ฅ๋ฐ›๋Š” ๋ฐ•์Šค์— ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, url ์— ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ƒ ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

์ด ๋•Œ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด POST ๋ฉ”์„œ๋“œ์ด๋‹ค.

POST ์‚ฌ์šฉ๋ฒ•


public class HelloWorld {
    public static void main(String[] args) {
        post("/users", (req, res) -> "Hello " + req.queryParams("name") + " ๋‚˜์ด๋Š” " + req.queryParams("age"));
    }
}

post() ์‚ฌ์šฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. ์œ„์— ๋‚˜์˜จ ๊ฒƒ ์ฒ˜๋Ÿผ get() ๋ฉ”์„œ๋“œ๋ฅผ post() ๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

์ด๋Ÿด ๊ฒฝ์šฐ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ด๋„ url ์— parameter ๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค.

<form action="/users" method="post">

๋‹ค๋งŒ ์ฃผ์˜ํ•  ์ ์€ form ํƒœ๊ทธ์˜ ๊ธฐ๋ณธ ๋ฉ”์„œ๋“œ๋Š” get ์ด๊ธฐ ๋•Œ๋ฌธ์— post ๋กœ ๋ณ€๊ฒฝํ•ด์ค˜์•ผํ•œ๋‹ค.

template engine ์‚ฌ์šฉ๋ฒ•


template engine ์ด๋ž€?

template engine ์€ html ํŒŒ์ผ์„ ๋™์ ์œผ๋กœ ๋งŒ๋“ค ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์ด๋‹ค.

  • localhost:4567/form.html - ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›๋Š” ์ •์  html
  • localhost:4567/users - ์ž…๋ ฅ๋ฐ›์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋™์  html

template engine ์„ ์“ฐ๋Š” ์ด์œ 

public class HelloWorld {
    public static void main(String[] args) {
        post("/users", (req, res) -> {
            return "<html>" +
                   "<body>" +
            // ...
                   "</body>" +
                   "</html>";
        }
    }
}

์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋Š” localhost:4567/users ๋ฅผ html ๋กœ ํ‘œํ˜„ํ•˜๊ณ ์ž ํ•œ๋‹ค.

์ด๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„  ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด route ์˜ ๋žŒ๋‹ค์‹ ๋ฐ”๋””์— html ํƒœ๊ทธ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋„ฃ์–ด์ฃผ๋ฉด๋œ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Š” ๊ต‰์žฅํžˆ ๋ถˆํŽธํ•˜๊ณ  ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์ด๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•˜๋Š” ๊ฒƒ์ด template engine ์ธ๋ฐ, html ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ argument ๋กœ ๋ฐ›์•„ ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ๋‚ด์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด ์ค€๋‹ค.

dependency ์ ์šฉ

dependencies {
    implementation "com.sparkjava:spark-template-handlebars:2.7.1"
}

Spark ์—์„œ ์ง€์›ํ•˜๋Š” ๋‹ค์–‘ํ•œ template engine ์ค‘์— handlebars ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

template engine ์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด build.gradle ์— ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

render ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€

public static String render(Map<String, Object> model, String templatePath) {
    return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath));
}

main ์— ์œ„์™€ ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€ ๋’ค template engine ์„ ํ™œ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

public static void main(String[] args) {
    staticFiles.location("/static");
 
    post("/users", (req, res) -> {
        Map<String, Object> model = new HashMap<>();
        model.put("name", req.queryParams("name"));
        model.put("age", req.queryParams("age"));
 
        return render(model, "result.html");
    });
}

render() ๋ฉ”์„œ๋“œ๋Š” Map<String, Object> ํ˜•์‹์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š” model ์„ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

  • model ์˜ key = template ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ณ€์ˆ˜ {{name}}
  • model ์˜ value = post ์š”์ฒญ์œผ๋กœ ๋ฐ›์€ ๊ฐ’ โ€œmeatsbyโ€
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sign Up</title>
</head>
<body>
<h1>Sign Up Result</h1>
Name : {{name}}, Age : {{age}}
<br>
</body>
</html>

render() ๋ฉ”์„œ๋“œ์— ์‚ฌ์šฉํ•  result.html ์„ ์œ„์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด ์ค€๋‹ค.

Spark ์—์„œ ๋™์  html ํŒŒ์ผ์€ ํ•ญ์ƒ resources.templates ๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ๋” ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— result.html ์„ ๊ผญ ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ์— ์œ„์น˜์‹œ์ผœ์•ผ ํ•œ๋‹ค.

handlebars ๋Š” Mustache ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, {{name}} ์€ model ์—์„œ ํ‚ค๊ฐ’์ด name ์ธ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.

java bean ๊ณผ handlebars ๋ฐ˜๋ณต๋ฌธ


java bean ์ด๋ž€?

java bean ์ด๋ž€, ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์žˆ๊ณ , iv ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, iv ์— ๋Œ€ํ•œ getter ์™€ setter ๊ฐ€ ์กด์žฌํ•˜๋Š” ํด๋ž˜์Šค๋‹ค.

handlebars ๋ฐ˜๋ณต๋ฌธ

public static void main(String[] args) {
    staticFiles.location("/static");
 
    List<User> users = new ArrayList<>();
 
    post("/users", (req, res) -> {
        User user = new User(req.queryParams("name"), req.queryParams("age"));
        users.add(user);
 
        Map<String, Object> model = new HashMap<>();
        model.put("users", users);
 
        return render(model, "result.html");
    });
}

main ์ด User.java ๋ผ๋Š” java bean ํด๋ž˜์Šค๊ฐ€ ์ƒ๊ธฐ๊ณ  post ์š”์ฒญ์œผ๋กœ ์ „๋‹ฌ๋œ ์ธ์ž๋“ค์„ ํ†ตํ•ด user ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ์— ๋‹ด์•„ template ์— ๋„˜๊ฒจ์ฃผ๋Š” ๋ฉ”์„œ๋“œ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

์ด๋Ÿด ๊ฒฝ์šฐ result.html ์€ ์–ด๋–ป๊ฒŒ ๋ณ€ํ•ด์•ผ ํ• ๊นŒ?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Sign Up</title>
</head>
<body>
<h1>Sign Up Result</h1>
{{#users}}
Name : {{name}}, Age : {{age}}
<br>
{{/users}}
</body>
</html>

๊ฐ„๋‹จํ•˜๋‹ค. {{#users}} ์™€ {{/users}} ์ฝ”๋“œ ์‚ฌ์ด์˜ ์žˆ๋Š” ๊ตฌ๋ฌธ์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฐ˜๋ณต๋ฌธ์„ ๋Œ์•„ user ๋ฆฌ์ŠคํŠธ์—์„œ ํ•˜๋‚˜ ์”ฉ ์ถœ๋ ฅํ•œ๋‹ค.

๋‚ด๋ถ€์˜ {{name}} ๊ณผ {{age}} ๋Š” ์‚ฌ์‹ค {{user.name}} ๊ณผ {{user.age}} ์ด์ง€๋งŒ, {{#users}} ์™€ {{/users}} ์—๊ฒŒ ๊ฐ์‹ธ์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์—ฌ๊ธฐ์„œ {{user.name}} ์€ user.getName() ๊ณผ ๊ฐ™๋‹ค.

References