๐Ÿ”ฅ TIL #1. Node์— ๋Œ€ํ•œ ์ดํ•ด์™€ ๋ง›๋ณด๊ธฐ

๋ฐฑ์Šน์ง„ยท2020๋…„ 12์›” 18์ผ
1

NodeJS

๋ชฉ๋ก ๋ณด๊ธฐ
1/7

๐ŸŒŸ Today I learned

  1. node ๊ณต๋ถ€๋ฅผ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ์ง€์‹
  2. node ๋กœ server ์‹คํ–‰ ํ•ด๋ณด๊ธฐ
  3. express๋กœ routing ๊ฐ„์†Œํ™” ํ•˜๊ธฐ
  4. ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ๊ธฐ์ˆ (์„œ๋ฒ„ ์ž๋™ ์žฌ์‹คํ–‰, npm)

1. node ๊ณต๋ถ€๋ฅผ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ์ง€์‹

node๋ž€ ๋น„๋™๊ธฐ(Asynchronous) ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜(event-driven) javascript runtime ํ™˜๊ฒฝ์ด๋‹ค.

1. ๋น„๋™๊ธฐ(Asynchronous)

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

2. ๋น„๋™๊ธฐ(Asynchronous) ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜(event-driven)

node ํ™˜๊ฒฝ์—์„œ event๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ(http request)๊ณผ ๊ฐ™๋‹ค.
'๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜'์€ '๋น„๋™๊ธฐ๋กœ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌ'๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ client์˜ ์š”์ฒญ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๋๋‚˜๊ธฐ ์ „์— ๋‹ค์Œ client์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค. ์ฆ‰, ํ•œ ์‹œ์ ์—์„œ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ 'single thread' ๊ฐ€ ๋“ฑ์žฅํ•œ๋‹ค.

ํ•˜๋‚˜์˜ request๊ฐ€ ์™”์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ ๋ฃจํ‹ด์„ ์ƒ๊ฐํ•ด ๋ณด์ž.

GET method๋กœ ์ •๋ณด ๋ชฉ๋ก์„ ๋‹ฌ๋ผ๋Š” ์š”์ฒญ์ผ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด Database์— ์ ‘๊ทผ, ํ•„์š”ํ•œ data๋ฅผ ์ˆ˜์ง‘ํ•˜์—ฌ ์‘๋‹ตํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Š” Heavy loadํ•œ ์ž‘์—…์œผ๋กœ single thread์—์„œ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  INTENSIVE OPERATION(C++ thread pool) ์— ๋Œ€์‹  ์ฒ˜๋ฆฌํ† ๋ก ์œ„์ž„ํ•œ๋‹ค.
์ฆ‰ single thread๋Š” ์ด๋ฒคํŠธ(์š”์ฒญ)์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ(์‘๋‹ต)๋งŒ ๋‹ด๋‹นํ•œ๋‹ค.

๋‹จ์ง€ Javascript๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋ฟ์ธ๋ฐ ์–ด๋–ป๊ฒŒ ์ด๋Ÿฐ๊ฒŒ ๊ฐ€๋Šฅํ• ๊นŒ?

3. Javascript runtime ํ™˜๊ฒฝ(chrome V8 Engine)

Javascript runtime ํ™˜๊ฒฝ์ด๋ž€?

JavaScript ๋กœ ์งœ์—ฌ์ง„ ์†Œ์Šค์ฝ”๋“œ๋ฅผ CPU๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๊ณ„์–ด(ex. 0๊ณผ 1๋กœ ์ด๋ฃจ์–ด์ง„ bytecode)๋กœ ๋ณ€ํ™˜์‹œํ‚ค๊ณ  ๋˜ํ•œ ํ”„๋กœ๊ทธ๋žจ์˜ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ

์ด๋Ÿฐ ํ™˜๊ฒฝ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ๋” ํ•˜๋Š”๊ฒƒ์ด chrome V8 Engine์ด๋‹ค. google web browser์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์—”์ง„์œผ๋กœ ์ด๋ฅผ browser ์—†์ด ์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ ๊ฒƒ์ด ๋ฐ”๋กœ node์ด๋‹ค.

2. node๋กœ server ์‹คํ–‰ ํ•ด๋ณด๊ธฐ

์•„๋ž˜ ์ฝ”๋“œ๋Š” http ๋ชจ๋“ˆ์„ ์ด์šฉํ•ด server ์ƒ์„ฑ ๋ฐ listening ํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ์ด๋‹ค.
createServer() ํ˜ธ์ถœ์„ ํ†ตํ•ด ์›น์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ํ˜ธ์ถœ์‹œ ์ฃผ์ž…ํ•˜๋Š” callback ํ•จ์ˆ˜๋Š” http request๊ฐ€ ์˜ฌ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š”๋ฐ ์ด ๋•Œ node ๊ฐ€ ํŠธ๋žœ์žญ์…˜์„ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด request, response ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
req ๊ฐ์ฒด์˜ 'url' ๋ฐ 'method' ์†์„ฑ์„ ์ด์šฉ, routing ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์—ฌ๊ธฐ์„  sendPosts๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ์‘๋‹ต์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.
์‘๋‹ต(res)์˜ header์— content-type๋ฅผ json์œผ๋กœ ์„ค์ •, ์‘๋‹ต์„ jsonํ˜•์‹์œผ๋กœ ํ•  ๊ฒƒ์ž„์„ ์ง€์ •ํ–ˆ๋‹ค.
require๋Š” ์ „์—ญ ๋˜๋Š” ์‚ฌ์šฉ์ž module์„ importํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

// chapter1.js
const http = require('http')
const {sendPosts} = require('./sendPosts')

const server = http.createServer((req, res) => {
    const {url, method} = req
    console.log(url, ' ', method)

    console.log('run server')
    res.setHeader('Content-Type', 'application/json')
  
    if (url === '/products' && method === 'GET') {
        sendPosts(res)
        return
    } 

    res.end(JSON.stringify({message:'answer.'}))
})

const PORT = 8000
server.listen(PORT, () => {
    console.log(`Server is listening on PORT ${PORT}`)
})

์•„๋ž˜ ์ฝ”๋“œ๋Š” chapter1.js์—์„œ /products routing์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ณ„๋„๋กœ ์ •์˜ํ•œ js ํŒŒ์ผ์ด๋‹ค.
ํ˜ธ์ถœ์‹œ res๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ res์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ํ˜•์‹์œผ๋กœ ๊ฐ์ฒด๋ฅผ Json.stringify()๋ฅผ ์ด์šฉํ•˜์—ฌ json ๋ฌธ์ž์—ด๋กœ converting ํ•œ๋‹ค.
๋งˆ์ง€๋ง‰ ์ค„์— module.exports ์— ํ•จ์ˆ˜๋ฅผ ๊ฐ์ฒด๋กœ ์ง‘์–ด๋„ฃ๊ณ  ์žˆ๋‹ค.
์™ธ๋ถ€๋กœ exportํ•  ํ•จ์ˆ˜๋Š” module.exports์— ๋„ฃ์–ด์•ผ ํ•˜๋Š”๋ฐ 'property:value' ํ˜•์‹์ด ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐฉ์‹์ด๋‚˜ property์™€ value๊ฐ€ ๊ฐ™์œผ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ํ•˜๋‚˜๋กœ ์ž…๋ ฅํ•ด๋„ ๋œ๋‹ค.

// sendPosts.js
const sendPosts = (res) => {
    res.end(
      JSON.stringify({ 
          products: [{
            id: 1,
            title: "node",
            description: "node.js is awesome"
          }, {
            id: 2,
            title: "express",
            description: "express is a server-side framework for node.js"
          }
        ]
      }))
  }

  module.exports = { sendPosts } // withoutExpress.js ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ชจ๋“ˆ๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค.

์ด ์ƒํƒœ๋กœ node๋กœ chapter1.js๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜์˜จ๋‹ค.

๊ทธ๋ฆฌ๊ณ  'http'๋ฅผ ์ด์šฉํ•ด ์š”์ฒญ์„ ํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜จ๋‹ค.

3. express๋กœ routing ๊ฐ„์†Œํ™” ํ•˜๊ธฐ

express ๋ž€?
express๋Š” node.js๋ฅผ ์œ„ํ•œ ๋น ๋ฅด๊ณ  ๊ฐœ๋ฐฉ์ ์ธ ๊ฐ„๊ฒฐํ•œ web framework
express reference

์œ„์—์„œ Server ์ƒ์„ฑ ๋ฐ routing ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž ์‹œ ์†Œ๊ฐœํ–ˆ์—ˆ๋‹ค. request์˜ url ๊ณผ method๋ฅผ ์ฒดํฌ, ํ•ด๋‹น routing์— ๋Œ€์‘ํ•  ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋Š”๋ฐ ๋ญ”๊ฐ€ ๋ณต์žกํ•ด ๋ณด์ธ๋‹ค. routing๋Œ€์ƒ์ด ๋งŽ์•„์ง€๋ฉด ์ € if๋ฌธ์ด ์ ์  ๋งŽ์•„์งˆ ๊ฒƒ์ด๋ฏ€๋กœ condition ์ฒดํฌ ๋ฐฉ์‹๋ณด๋‹ค ๊น”๋”ํ•œ ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
express๋Š” ์ด๋Ÿฐ ์ž‘์—…์š”์†Œ๋ฅผ ๊ฐ„์†Œํ™”ํ•˜์—ฌ ๊ฐœ๋ฐœ ์„ฑ๋Šฅ ํ–ฅ์ƒ์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
์šฐ์„  express๋ฅผ ์„ค์น˜ํ•ด ๋ณด์ž.

npm install express

npm(Node Package Manager) ์€ node์—์„œ ์‚ฌ์šฉํ•˜๋Š” package๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด๋‹ค. node๋ฅผ ์„ค์น˜ํ•˜๋ฉด ๊ฐ™์ด ์„ค์น˜๋˜๋ฉฐ python์˜ pip์™€ ๋น„์Šทํ•˜๋‹ค.
express ์„ค์น˜๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

// express.js
const http = require('http')
const express = require('express')
//const {sendPosts} = require('./sendPosts')
const {hello, sendPosts} = require('./functions')

// app => application
// Backend ๋‹จ์—์„œ ๋Œ์•„๊ฐˆ application
const app = express()

app.get('/', hello)
app.get('/products', sendPosts)

const server = http.createServer(app)
const PORT = 8000
server.listen(PORT, () => {
    console.log(`running server ${PORT}`)
})

์œ„ ์ฝ”๋“œ๋Š” chapter1.js์™€ ๋™์ผํ•œ ์ž‘์—…(web server ์‹คํ–‰ ๋ฐ 'products' routing)์„ ํ•œ๋‹ค.
express ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ, http request์— ๋Œ€ํ•ด url, method ์ฒดํฌํ•˜๋Š” ๊ณผ์ • ๋Œ€์‹  express ๊ฐ์ฒด์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ routing ์„ ์ •์˜ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  express ๊ฐ์ฒด๋ฅผ createServer() ํ˜ธ์ถœ์‹œ ์ฃผ์ž…ํ•˜๊ณ  listen()ํ•˜๋ฉด ๋์ด๋‹ค.
์ด์ฒ˜๋Ÿผ express๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋Š” ์ž์ž˜ํ•œ ๋ถ€๋ถ„์„ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค. routing์€ ๋ฌผ๋ก  middleware๋ผ๋Š” ๊ธฐ๋Šฅ(like decorator) ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

4. ๊ฐœ๋ฐœ์„ ์œ„ํ•ด ์•Œ์•„์•ผ ํ•  ๊ธฐ์ˆ (์„œ๋ฒ„ ์ž๋™ ์žฌ์‹คํ–‰, npm)

๋‚ด๊ฐ€ ๋งŒ๋“  node๋ฅผ ๋‹ค๋ฅธ ์žฅ๋น„์—์„œ ์‹คํ–‰ํ•˜๋ ค๋ฉด ๋ญ๊ฐ€ ํ•„์š”ํ• ๊นŒ? ๋จผ์ € node๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ npm์œผ๋กœ ์„ค์น˜ํ–ˆ๋˜ package๋“ค์ด ๊ทธ ์žฅ๋น„์—์„œ ์„ค์น˜๊ฐ€ ๋˜์–ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค. ์ด์— ๋Œ€ํ•œ script๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

npm init -y  (์„ค์น˜๋œ package ์ •๋ณด)

์œ„๋Š” ๊ฐœ๋ฐœ์ด ์™„๋ฃŒ๋œ node์—์„œ npm ๋ช…๋ น์œผ๋กœ ํ˜„์žฌ node๊ฐ€ dependency ์ค‘์ธ package ์ •๋ณด๋ฅผ package.json ํŒŒ์ผ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
์œ„์—์„œ ์ž‘์—…ํ•œ ์ฝ”๋“œ ๊ธฐ์ค€์œผ๋กœ package.json์ด ์ƒ์„ฑ๋œ ๋‚ด์šฉ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค

{
  "name": "JunSession",
  "version": "1.0.0",
  "description": "",
  "main": "chapter1.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon express.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.6"
  }
}

'name'์—๋Š” 'main'์— ํ•ด๋‹นํ•˜๋Š” jsํŒŒ์ผ์ด ์œ„์น˜ํ•œ ํด๋”๋ช…์ด ๋“ค์–ด๊ฐ„๋‹ค.
๋ฐ‘์— 'dependencies'๋ฅผ ๋ณด๋ฉด 'express'๊ฐ€ version ์ •๋ณด์™€ ํ•จ๊ป˜ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค. npm์œผ๋กœ package๋ฅผ ์„ค์น˜ํ•˜๋ฉด 'dependencies' ์— ํ•ญ๋ชฉ์— ๊ณ„์† ์ถ”๊ฐ€๋œ๋‹ค.
๋ฐ‘์˜ 'devDependencies' ๋Š” ๊ฐœ๋ฐœ์šฉ์œผ๋กœ ์„ค์น˜ํ•œ package๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๊ฐœ๋ฐœ์‹œ์—๋งŒ ์‚ฌ์šฉ๋˜๋ฉฐ ์‹ค์ œ ๋ฐฐํฌ๋˜์–ด ์„ค์น˜๋ ๋• ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.

package๋ฅผ ๊ฐœ๋ฐœ์šฉ์œผ๋กœ ์„ค์น˜ํ•˜๋Š” ๋ช…๋ น์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

npm install -D {์„ค์น˜ํ•  package ์ด๋ฆ„} (์—ฌ๊ธฐ์„œ๋Š” nodemon) 

# -D ์˜ต์…˜์„ ๋„ฃ์œผ๋ฉด ๊ฐœ๋ฐœ์šฉ์ž„์„ ์˜๋ฏธ

์œ„์—์„œ nodemon ์ด๋ž€ package๋ฅผ ๊ฐœ๋ฐœ์šฉ์œผ๋กœ ์„ค์น˜ํ•œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
nodemon์€ server ๊ธฐ๋™ ์ค‘์— node source๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ server๋ฅผ ์žฌ์‹œ์ž‘ํ•˜๋„๋ก ํ•ด์ฃผ๋Š” package์ด๋‹ค. nodemon ์„ ๊ตฌ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. package.json ์—์„œ "scripts"์— "dev":"nodemon express.js" ์ถ”๊ฐ€.
    (key๋Š” ๋ช…๋ น์–ด์ด๊ณ  value๋Š” ์‹ค์ œ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ช…๋ น)
  2. ์•„๋ž˜ npm ๋ช…๋ น์„ ํ†ตํ•ด ์‹คํ–‰.
    npm run dev

์ •์ƒ ์‹คํ–‰์‹œ ์•„๋ž˜์™€ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜์˜จ๋‹ค.

์ด ์ƒํƒœ์—์„œ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ์„œ๋ฒ„๋Š” ์žฌ์‹คํ–‰ ๋œ๋‹ค.

profile
12๋…„ .NET ๊ฐœ๋ฐœ ๊ฒฝ๋ ฅ์„ ๊ฐ€์ง„ ์›น ์ดˆ์งœ ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค :)

0๊ฐœ์˜ ๋Œ“๊ธ€