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


  1. ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ ์ดํ•ด
  2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์ƒ ๋ฐ ์š”๊ฑด ์ •์˜
  3. ์š”๊ฑด์— ๋Œ€ํ•œ Usecase ๋ฐ Wireframe ์ž‘์„ฑ
  4. ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ์•„ํ‚คํ…์ณ ์„ค๊ณ„
  5. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„ ํ™˜๊ฒฝ ์ค€๋น„
  6. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์ค€๋น„
  7. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part1
  8. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part2
  9. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part3
  10. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ DevOps ํ™˜๊ฒฝ ๊ตฌ์„ฑ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part1

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

์„œ๋น„์Šค API ํ˜ธ์ถœ ์ฒ˜๋ฆฌ ์ฝ”๋“œ ๊ตฌํ˜„

์•ž์„œ ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค๋Š” REST API๋กœ ๋…ธ์ถœํ•˜๋Š” ํ˜•ํƒœ๋กœ ์•„ํ‚คํ…์ณ๋ฅผ ๊ตฌ์ƒํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ด์— ๋Œ€ํ•œ ๊ตฌํ˜„ ํ•  ์ฐจ๋ก€์ธ๋ฐ, Node.js ํ™˜๊ฒฝ์—์„œ REST API๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Node.js ์ž์ฒด์—์„œ ์ œ๊ณตํ•˜๋Š” HTTP server๋ฅผ ์ด์šฉํ•˜์—ฌ HTTP Request / Reponse๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๊ณ , ๋ณ„๋„๋กœ ๊ตฌ์„ฑ๋œ Framework์„ ์ด์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ์ผ์ •ํ•œ ํ˜•ํƒœ์˜ ํ‹€์„ ์ž‘์„ฑํ•œ ํ›„ ์ด๋ฅผ ๊ฐ๊ฐ์˜ ์„œ๋น„์Šค์— ๋ณต์ œํ•œ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์ง€๋งŒ, ๋ณธ ๊ธ€์—์„œ๋Š” ๋‹ค์–‘์„ฑ์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์„œ๋กœ ๋‹ค๋ฅธ ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ, ์„œ๋น„์Šค ์ž‘์„ฑ์— ๋Œ€ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ณตํ†ต ๋ถ€๋ถ„์„ ์ ์šฉํ•˜์—ฌ ํ–ฅํ›„ DevOps๋ฅผ ์—ผ๋‘ํ•ด ๋‘๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

  • Open API ํ˜•์‹์„ ์ž‘์„ฑ๋œ API ๋ฌธ์„œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ์ž‘์„ฑํ•œ๋‹ค
  • ์ž‘์„ฑ๋œ API๋Š” ๋ฐ˜๋“œ์‹œ ๋Œ€์‘ํ•˜๋Š” ๋‹จ์œ„ Testcase๋ฅผ ์ž‘์„ฑํ•œ๋‹ค
  • lint๋ฅผ ์ ์šฉํ•˜์—ฌ ์ผ๊ด€๋œ expression์„ ์œ ์ง€ํ•œ๋‹ค

์ด์ œ ํ•˜๋‚˜์”ฉ ์ž‘์„ฑํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ์ธ์ฆ ์„œ๋น„์Šค ํ”„๋กœํ† ํƒ€์ž… ๊ตฌํ˜„

์„œ๋น„์Šค API ์ค‘ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ํ˜•ํƒœ์ธ ์‚ฌ์šฉ์ž ์ธ์ฆ ์„œ๋น„์Šค๋ฅผ ๋จผ์ € ๊ตฌํ˜„ํ•ด ๋ด…๋‹ˆ๋‹ค.

Swagger Codegen์„ ์ด์šฉ

์•ž์„œ API๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ–ˆ๋˜ Swagger API Editor์˜ ๊ธฐ๋Šฅ ์ค‘ ์ž‘์„ฑํ•œ API Spec์— ๋Œ€ํ•œ Server ๋ฐ Client ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑ ํ•ด ์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์„œ๋น„์Šค API๋ฅผ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ZIP์œผ๋กœ ์••์ถ•๋œ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ํ•œ ํ›„ ํ•ด๋‹น ํŒŒ์ผ์˜ ์••์ถ•์„ ํ•ด์ œํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด api์™€ controllers๋กœ ๊ตฌ์„ฑ๋œ ํ˜•ํƒœ์˜ ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํฌํ•จ๋œ package.json์„ ํ™•์ธ ํ•ด๋ณด๋ฉด YAML๋กœ ์ž‘์„ฑํ–ˆ๋˜ spec์˜ ๋‚ด์šฉ์ด ์„œ๋น„์Šค ์ •๋ณด๋กœ ๋ฐ˜์˜๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{
  "name": "smv-user-auth-service-api",
  "version": "0.0.3",
  "description": "Simple Visitor Management User Authentication Service API",
  "main": "index.js",
  "scripts": {
    "prestart": "npm install",
    "start": "node index.js"
  },
  "keywords": [
    "swagger"
  ],
  "license": "Unlicense",
  "private": true,
  "dependencies": {
    "connect": "^3.2.0",
    "js-yaml": "^3.3.0",
    "swagger-tools": "0.10.1"
  }
}

๋˜ํ•œ swagger-tool์ด๋ผ๋Š” ๋ชจ๋“ˆ์ด ํฌํ•จ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์•„๋ž˜ index.js ํŒŒ์ผ์„ ๋ณด๋ฉด ์ด ๋ชจ๋“ˆ์„ ์ด์šฉํ•˜์—ฌ API ํ˜ธ์ถœ ๊ฒฝ๋กœ ๋ฐ ํ•ด๋‹น API์˜ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Controller๋ฅผ ์ดˆ๊ธฐํ™” ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ด€๋ จ ์ •๋ณด๋Š” GitHub์˜ swagger-tools ์ •๋ณด๋ฅผ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

CF ์•ฑ ํ™˜๊ฒฝ์œผ๋กœ ํ†ตํ•ฉ

smv-userauth๋Š” Bluemix์˜ node.js ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์ƒ์„ฑ๋œ ๊ฒƒ์œผ๋กœ CF์•ฑ์ด๋ฏ€๋กœ cfenv ๋ชจ๋“ˆ๊ณผ WebUI๋ฅผ ์œ„ํ•œ express middleware๋ฅผ ์‚ฌ์šฉํ•œ ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, swagger-tools๋Š” connect๋ผ๋Š” middleware๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๊ตฌ์กฐ์ ์ธ ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด Express ๊ด€๋ จ ๋ชจ๋“ˆ์„ ์ œ๊ฑฐํ•˜๊ณ  Swagger Editor์—์„œ ์ƒ์„ฑํ•œ ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

package.json ํŒŒ์ผ์˜ dependencies์—์„œ express๋ฅผ ์ œ๊ฑฐํ›„ connect, js-yaml, swagger-tools๋ฅผ ์ถ”๊ฐ€ ํ•ด ์ค๋‹ˆ๋‹ค.

    "dependencies": {
        "cfenv": "1.0.x",
        "connect": "^3.2.0",
        "js-yaml": "^3.3.0",
        "swagger-tools": "0.10.1"
    },

๊ทธ๋ฆฌ๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ํŒŒ์ผ์ธ app.js๋„ express ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ›„ swagger-tools ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

// cfenv provides access to your Cloud Foundry environment
// for more info, see: https://www.npmjs.com/package/cfenv
var cfenv = require('cfenv');

// get the app environment from Cloud Foundry
var appEnv = cfenv.getAppEnv();

// Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
var app = require('connect')();
var http = require('http');
var swaggerTools = require('swagger-tools');
var jsyaml = require('js-yaml');
var fs = require('fs');
var serverPort = appEnv.port;
...

์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค๋ฉด npm install ๋ช…๋ น์œผ๋กœ ์ถ”๊ฐ€๋œ ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•œ ์ˆ˜ smv-userauth.sh์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ ./smv-userauth.sh 

> NodejsStarterApp@0.0.3 start /Project/DevWorks/smv/projects/smv-userauth
> node app.js

Your server is listening on port 6002 (http://localhost:6002)
Swagger-ui is available on http://localhost:6002/docs

Swagger Router Handler ์ •์˜

Try it out! ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์„œ๋ฒ„ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธ ํ•ด ๋ณด๋ฉด Error: Cannot resolve the configured swagger-router handler: loginPOST๋ผ๋Š” ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์š”์ฒญํ•œ API๋ฅผ ์ฒ˜๋ฆฌํ•  swagger-router Handler๊ฐ€ ์—†๋‹ค๋Š” ๋ฉ”์‹œ์ง€์ธ๋ฐ, api ํด๋” ์•„๋ž˜ ์žˆ๋Š” swagger.yaml์— ์ •์˜๋œ path ์ •๋ณด ์ค‘ x-swagger-router-controller ๊ฐ’์ด ์ง€์ •๋˜์–ด ์žˆ์ง€ ์•Š๊ธฐ์— ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

์ƒ์„ฑ๋œ ์ฝ”๋“œ ์ค‘ controllers๋ผ๋Š” ํด๋” ์•„๋ž˜ ์žˆ๋Š” DefaultController.js, DefaultControllerService.js ํŒŒ์ผ์€ mockup ์ •๋ณด๋ฅผ ์ œ๊ณต์šฉ์œผ๋กœ ์ด ํŒŒ์ผ์„ ์ฐธ๊ณ ํ•ด์„œ ์‹ค์ œ ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. swagger-router ๊ด€๋ จ๋œ ์ •๋ณด๋Š” ์•„๋ž˜ URL์„ ์ฐธ๊ณ  ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ด์ œ Controller ์ฝ”๋“œ๋ฅผ ํ™•์ธ ํ•ด ๋ด…๋‹ˆ๋‹ค.

controllers ํด๋”์˜ DefaultController.js์™€ DefaultControllerService.js ํŒŒ์ผ์„ ์ฐธ๊ณ ํ•˜์—ฌ SMVUserAuthController.js ํŒŒ์ผ์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

'use strict';

var url = require('url');

module.exports.authUserinfoGET = function (req, res, next) {
  /**
   * ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒ
   *
   * returns inline_response_200
   **/
  var args = req.swagger.params;
  var examples = {};
  examples['application/json'] = {
    "role" : "aeiou",
    "serial" : "1234567890",
    "phone" : "+82-2-1234-0000",
    "name" : "John Doe",
    "mobile" : "+82-10-1234-0000",
    "userid" : "CN=John Doe/OU=ACME/O=IBM",
    "email" : "john.doe@acme.ibm.com"
  };
  if (Object.keys(examples).length > 0) {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(examples[Object.keys(examples)[0]] || {}, null, 2));
  } else {
    res.end();
  }
};

module.exports.loginPOST = function (req, res, next) {
  /**
   * ์ฃผ์–ด์ง„ ์ธ์ฆ ์ •๋ณด๋กœ ๋กœ๊ทธ์ธ
   *
   * email String ์‚ฌ์šฉ์ž Email
   * passwd String ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ
   * no response value expected for this operation
   **/
  var args = req.swagger.params;
  res.end();
};

module.exports.logoutGET = function (req, res, next) {
  /**
   * ๋กœ๊ทธ์ธ ์ƒํƒœ์—์„œ ๋กœ๊ทธ์•„์›ƒ
   *
   * no response value expected for this operation
   **/
  var args = req.swagger.params;
  res.end();
};

๊ทธ๋ฆฌ๊ณ , api/swagger.yaml ํŒŒ์ผ์˜ x-swagger-router-controller ์˜ ๊ฐ’์„ SMVUserAuthController๋กœ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋ฉด ์•ž์„œ ๋ฐœ์ƒํ–ˆ๋˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Swagger Router Handler ๊ตฌํ˜„

์ด์ œ Swagger Router Handler๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. SMV ์˜ˆ์ œ์—์„œ๋Š” ๋ณ„๋„์˜ ์‚ฌ์šฉ์ž ์ธ์ฆ ์„œ๋ฒ„์—†์ด ๋‹จ์ˆœํ•œ ์ •๋ณด๋ฅผ ์ง€์ •ํ•˜์—ฌ ์‚ฌ์šฉ ํ•˜๊ธฐ๋กœ ํ–ˆ์—ˆ์œผ๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž…๋ ฅํ•˜๋ฉด ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋„๋ก loginPOST ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

parameter ๊ฐ’
email john.doe@acme.ibm.com
passwd passw0rd

API ํ˜ธ์ถœ ์‹œ ์ „๋‹ฌ๋˜๋Š” parameter๋Š” Form Data์ด๋ฏ€๋กœ request object์˜ body ์ •๋ณด์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ Swagger Tools๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ request object์˜ swagger object๋ผ๋Š” ์ •๋ณด๋กœ๋„ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด loginPost๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฝ์šฐ ์ „๋‹ฌ๋œ req.swagger.params ์ •๋ณด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

{
    "email": {
        "originalValue": "john.doe@acme.ibm.com", 
        "path": [
            "paths", 
            "/login", 
            "post", 
            "parameters", 
            "0"
        ], 
        "schema": {
            "description": "\uc0ac\uc6a9\uc790 Email", 
            "in": "formData", 
            "name": "email", 
            "required": true, 
            "type": "string"
        }, 
        "value": "john.doe@acme.ibm.com"
    }, 
    "passwd": {
        "originalValue": "passw0rd", 
        "path": [
            "paths", 
            "/login", 
            "post", 
            "parameters", 
            "1"
        ], 
        "schema": {
            "description": "\uc0ac\uc6a9\uc790 \ube44\ubc00\ubc88\ud638", 
            "in": "formData", 
            "name": "passwd", 
            "required": true, 
            "type": "string"
        }, 
        "value": "passw0rd"
    }
}

Controller ๊ตฌํ˜„์€ Middleware์ธ connect์˜ ํ˜•์‹์„ ๋”ฐ๋ฅด์ง€๋งŒ Node.js์˜ ๊ธฐ๋ณธ http๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ URL์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Redis๋ฅผ ์ด์šฉํ•œ ์ •๋ณด ๊ณต์œ  ๊ตฌ์กฐ์ž‘์„ฑ

์•ž์„œ ์†Œ๊ฐœํ–ˆ๋˜ Adam Wiggins์˜ ํด๋ผ์šฐ๋“œ ์•ฑ12 ์š”์†Œ์—์„œ๋Š” ํ”„๋กœ์„ธ์Šค๋Š” Stateless ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑํ•˜๋ฉฐ, ํŠนํžˆ Sticky Session์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ•์กฐํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜, ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ๋ณผ ๋•Œ๋Š” ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋ช‡ ๊ฐœ๊ฐ€ ๋˜๋“ ์ง€ ์„œ๋น„์Šค๊ฐ€ stateful์ด๊ฑฐ๋‚˜ stateless์ด๊ฑฐ๋‚˜ ์•Œ ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ž์‹ ์€ ์„œ๋ฒ„์— ๋กœ๊ทธ์ธ์„ ํ–ˆ๊ณ  ๊ทธ ์ดํ›„ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ž˜ ์ด์šฉํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฑฐ์ฃ .

๊ทธ๋ ‡๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํ–ˆ๊ณ  ์–ด๋–ค ์‚ฌ์šฉ์ž์ด๋ฉฐ ์–ด๋–ค ๊ถŒํ•œ์„ ๊ฐ€์กŒ๋Š”์ง€ ๊ฐ๊ฐ ๋‚˜๋ˆ„์–ด์ ธ์žˆ๋Š” ์„œ๋น„์Šค์—์„œ ์•Œ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์–ด์ง„ ํ™˜๊ฒฝ์— ๋”ฐ๋ผ์„œ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ์ธ์ฆ ์„ฑ๊ณต ํ›„ ํ•ด๋‹น ์„œ๋น„์Šค ์ ‘๊ทผ์— ๋Œ€ํ•œ token์„ ๋ฐœ๊ธ‰ํ•˜๋Š”๋ฐ JWT(JSON Web Token: https://jwt.io/)์™€ ๊ฐ™์ด Token์— ์ •๋ณด๋ฅผ ๋‹ด์€ ํ›„ ์ด๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ์‹์„ ์ด์šฉํ•˜๊ฑฐ๋‚˜, OAuth2์™€ ๊ฐ™์ด ๋ณ„๋„์˜ ์ธ์ฆ ์„œ๋ฒ„๋ฅผ ๋‘๊ณ  ๊ทธ ์„œ๋ฒ„๊ฐ€ ๋ฐœ๊ธ‰ํ•œ token์„ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

๋ณธ ๊ธ€์—์„œ๋Š” ๋ฒ”์šฉ์€ ์•„๋‹ˆ๊ณ  ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ ํ‘œ์ค€ ํ˜•ํƒœ๋Š” ์•„๋‹ˆ๋ฏ€๋กœ Bluemix์—์„œ ์ œ๊ณตํ•˜๋Š” Redis ์„œ๋น„์Šค (Compose for Redis๋‚˜ Redis Cloud)๋ฅผ ์ด์šฉํ•˜์—ฌ token ๋ฐœ๊ธ‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž…์žฅ์—์„œ Bluemix์˜ Redis ์„œ๋น„์Šค๋“ค์˜ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋™์ผํ•˜์ง€๋งŒ, ์ œ๊ณตํ•˜๋Š” Plan์— ๋”ฐ๋ผ ์œ ๋ฃŒ ๋˜๋Š” ๋ฌด๋ฃŒ๋กœ ์‚ฌ์šฉ์ด๋‚˜ ์šฉ๋Ÿ‰๊ณผ ๊ฐ™์€ ์ œํ•œ ์‚ฌํ•ญ์ด ์žˆ์œผ๋ฏ€๋กœ ์•„๋ž˜ Bluemix catalog ์ •๋ณด๋ฅผ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์„ค๊ณ„ ๋ฐ Redis Client ์„ ํƒ

๊ธฐ๋ณธ ์„ค๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •ํ•ฉ๋‹ˆ๋‹ค.

  • Login ์„ฑ๊ณต ์‹œ Unique ID์ธ ์ธ์ฆ ํ† ํฐ์„ ์ƒ์„ฑํ•œ๋‹ค
  • ์ƒ์„ฑํ•œ ํ† ํฐ์„ ๊ธฐ์ค€์œผ๋กœ Redis์šฉ Key๋ฅผ ์ƒ์„ฑํ•œ๋‹ค
  • Redis์˜ Expire ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜์—ฌ ์ •ํ•ด์ง„ ์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ์ •๋ณด๋ฅผ ์œ ์ง€ํ•œ๋‹ค
  • Redis์˜ Hash Get/Set ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜์—ฌ ์„œ๋น„์Šค๊ฐ„ ๊ณต์œ ๋  ์ •๋ณด๋ฅผ ์ €์žฅ ๊ด€๋ฆฌํ•œ๋‹ค
  • Logout ์‹œ ํ‚ค ์ •๋ณด๋ฅผ ๋ชจ๋‘ ์‚ญ์ œํ•œ๋‹ค

์ด์ œ Node.js์šฉ Redis Client๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. https://redis.io/clients#nodejs๋ฅผ ๋ณด๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ Node.js์šฉ Redis Client๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ๊ธ€์—์„œ๋Š” node_redis๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์€ npm ๋ช…๋ น์œผ๋กœ redis ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜๊ณ  package.json ํŒŒ์ผ๋„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

$ npm install redis --save

Redis Client ์ดˆ๊ธฐํ™”

Redis Client๋Š” ๋‹ค์–‘ํ•œ option์œผ๋กœ ์ดˆ๊ธฐํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. host, port, password๋ฅผ ์ด์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ  ์ด ์ •๋ณด๊ฐ€ ์ „๋ถ€ ํฌํ•จ๋œ url๋ผ๋Š” ์˜ต์…˜์œผ๋กœ ์ดˆ๊ธฐํ™”๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ์™ธ ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์œผ๋‹ˆ https://github.com/NodeRedis/node_redis๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ํ•„์š”์— ๋”ฐ๋ผ ์˜ต์…˜์„ ๊ฒฐ์ • ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์•ž์„œ, Bluemix์—์„œ๋Š” Redis Cloud์™€ Compose for Redis ๋‘ ๊ฐ€์ง€๋ฅผ ์ œ๊ณตํ•œ๋‹ค๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. Redis ์„œ๋น„์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ์‚ฌ์šฉํ•  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์—ฐ๊ฒฐ(Bind)ํ•˜๋ฉด ํ™˜๊ฒฝ๋ณ€์ˆ˜ VCAP_SERVICES์— ํ•ด๋‹น ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์‹ ์ž„ ์ •๋ณด๊ฐ€ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, cfenv ๋ชจ๋“ˆ์—์„œ ํ•„์š”ํ•œ ์„œ๋น„์Šค๋ฅผ ์„ ํƒํ•˜๊ณ  ํ•ด๋‹น ์„œ๋น„์Šค์˜ ์‹ ์ž„์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜์—ฌ Redis Client๋ฅผ ์ดˆ๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์ฝ”๋“œ์—์„œ๋Š” Compose for Redis-smv๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ƒ์„ฑ๋œ Redis ์„œ๋น„์Šค์˜ ์‹ ์ž„์ •๋ณด(Credentials)๋ฅผ ์ฐพ๊ณ  ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ Redis Client ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋‚ด์šฉ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์—ฐํžˆ Compose for Redis-smv๋Š” ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์—ฐ๊ฒฐ(Bind)๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

const REDIS_SERVICE_NAME = 'Compose for Redis-smv';

const appEnv = require('cfenv').getAppEnv();
const redis = require("redis");

const redisCredentials = appEnv.getServiceCreds(REDIS_SERVICE_NAME);

// Initialize 
var redisClientOptions;

if (redisCredentials.uri) {
  redisClientOptions = {

    'url': redisCredentials.uri
  };
} else {
  redisClientOptions = {
    'host': redisCredentials.hostname,
    'port': redisCredentials.port,
    'password': redisCredentials.password
  };
}

const redisClient = redis.createClient(redisClientOptions);

์ธ์ฆ ํ† ํฐ(Auth token) ์ƒ์„ฑ

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

๋ณธ ๊ธ€์—์„œ๋Š” uuid (https://www.npmjs.com/package/uuid)๋ผ๋Š” ๋ชจ๋“ˆ์„ ์ด์šฉํ•˜์—ฌ ์ธ์ฆ ํ† ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด uuid ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ npm install uuid --save

uuid ๋ชจ๋“ˆ์€ RFC4122์˜ Version4 (random) ๋ฐฉ์‹ ์ƒ์„ฑ์„ ์ง€์›ํ•˜๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ธ์ฆ ํ† ํฐ ์ƒ์„ฑ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

var uuidV4 = require('uuid/v4');
var token = uuidV4();

Redis์šฉ Key ์ƒ์„ฑ

์ธ์ฆ ํ† ํฐ์„ Redis์šฉ Key๋กœ ์ง์ ‘ ์‚ฌ์šฉ ํ•  ์ˆ˜๋„ ์žˆ์œผ๋‚˜ ๊ทธ ๋ณด๋‹ค๋Š” ์•ฝ๊ฐ„์˜ ๋ณ€ํ˜•๋œ ๊ฐ’์„ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ„๋„๋กœ ์ค€๋น„๋œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Key๋ฅผ ์–ป๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์ธ์ฆ ํ† ํฐ์˜ '-'(dash) ๋ฌธ์ž๋ฅผ '_'(underscore) ๋ฌธ์ž๋กœ ๋ฐ”๊ฟ”์„œ Redis ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

function tokenAsKey(token) {
  return token.replace(/-/gi, '_'); // replace all of '-' to '_'
}

๊ฐ ์ฝ”๋“œ์—์„œ๋Š” ์ด ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ ์ „๋‹ฌ ๋ฐ›์€ ์ธ์ฆ ํ† ํฐ์„ Redis Key๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Redis์˜ Expire ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•œ ์ธ์ฆ ํ† ํฐ ์ƒ์„ฑ

Redis์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ EXPIRE ๋ช…๋ น(https://redis.io/commands/expire)์€ ํ‚ค์— ๋Œ€์‘ํ•˜๋Š” ์ •๋ณด๋ฅผ ์„ค์ •๋œ ์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ์œ ์ง€ํ•˜๊ณ  ๊ทธ ์‹œ๊ฐ„ ์ดํ›„์—๋Š” ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•˜๋ฉด ์ œํ•œ๋œ ์‹œ๊ฐ„์—์„œ๋งŒ ์œ ํšจํ•œ ์ธ์ฆ ํ† ํฐ์„ ๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ€์žฅ ๋จผ์ € ์ธ์ฆ ํ† ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , ์ด ํ† ํฐ์„ key๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

var token = uuidV4();
var key = tokenAsKey(token);

๊ทธ ๋‹ค์Œ ์ƒ์„ฑ๋œ Key์™€ HSET ๋ช…๋ น(https://redis.io/commands/hset)์œผ๋กœ ์ •๋ณด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

redisClient.hset(key, '_expires_in', EXPIRES_IN_SECS, function(error, result) {
...
});

์ •๋ณด๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด EXPIRE ๋ช…๋ น์„ ์ด์šฉํ•˜์—ฌ ์ฃผ์–ด์ง„ ์‹œ๊ฐ„๊นŒ์ง€๋งŒ ํ•ด๋‹น Key์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์œ ํšจํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

redisClient.expire(key, EXPIRES_IN_SECS, function(error, result) {
    ...
});

์ด ๋ชจ๋“  ๋™์ž‘์ด ์„ฑ๊ณตํ•˜๋ฉด ์•ž์„œ ์ƒ์„ฑํ•œ ์ธ์ฆ ํ† ํฐ ์ •๋ณด๋ฅผ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์ •๋ณด๋กœ์„œ ์ „๋‹ฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • Redis Key ์ •๋ณด๊ฐ€ ์œ ํšจํ•œ์ง€ ํ™•์ธ

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

redisClient.ttl(key, function(error, result) {
    ...
});

Redis์˜ Hash Get/Set ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜์—ฌ์ •๋ณด๋ฅผ ์ €์žฅ ๊ด€๋ฆฌ

Key๊ฐ€ ์ค€๋น„๋˜์—ˆ๋‹ค๋ฉด Redis์˜ HGET ๋ช…๋ น(https://redis.io/commands/hget)๊ณผ HSET ๋ช…๋ น(https://redis.io/commands/hset)์„ ์ด์šฉํ•˜์—ฌ field-value ํ˜•ํƒœ์˜ ์ •๋ณด๋ฅผ ์ด์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

redisClient.hget(key, field, function(error, result) {
    ...
});
redisClient.hset(key, field, value, function(error, result) {
    ...
});

Redis Key์— ๋Œ€ํ•œ ์ •๋ณด ์‚ญ์ œ

Redis DEL ๋ช…๋ น(https://redis.io/commands/del)์€ ํ•ด๋‹นํ•˜๋Š” Key์— ๋Œ€ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์•„์›ƒ ์‹œ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ •๋ณด๋ฅผ ์‚ญ์ œํ† ๋ก ํ•ฉ๋‹ˆ๋‹ค.

redisClient.del(key, function(error, result) {
    ...
});

Redis์™€ ๊ด€๋ จ๋œ ๋ถ€๋ถ„์€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ๋„ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•˜๋ฏ€๋กœ ์ด๋ฅผ ๋ชจ๋“ˆํ™” ํ•œ ํ˜•ํƒœ์ธ SMVAuthTokenHelper๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” https://github.com/mc500/smv/blob/master/projects/smv-userauth/controllers/SMVAuthTokenHelper.js์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

Swagger Router Handler ์—…๋ฐ์ดํŠธ

์ด์ œ SMVAuthTokenHelper ๋ชจ๋“ˆ์„ ์ด์šฉํ•˜์—ฌ SMVUserAuthController ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

'use strict';

var url = require('url'),
    SMVAuthTokenHelper = require('./SMVAuthTokenHelper');

function extractAuthToken(req) {
  var token = req.headers['x-auth-token'];
  return token;
}

module.exports.userinfoGET = function (req, res, next) {
  /**
   * ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์กฐํšŒ
   *
   * returns inline_response_200
   **/
  var example = {
    "role" : "USER",
    "serial" : "1234567890",
    "phone" : "+82-2-1234-0000",
    "name" : "John Doe",
    "mobile" : "+82-10-1234-0000",
    "userid" : "CN=John Doe/OU=ACME/O=IBM",
    "email" : "john.doe@acme.ibm.com"
  };

  var token = extractAuthToken(req);
  SMVAuthTokenHelper.isValidAuthToken(token, function(valid) {
    if (valid) {
      // 
      SMVAuthTokenHelper.getAuthTokenValue(token, 'userid', function(result){
        if (result && result == example.email) {
          res.setHeader('Content-Type', 'application/json');
          res.end(JSON.stringify(example));
          res.end(); 
        } else {
          // Error 
          res.statusCode = 500;
          res.end('Internel Server Error');
        }
      });
    } else {
      res.statusCode = 401;
      res.end('Unauthorized');
    }
  });
};

module.exports.loginPOST = function (req, res, next) {
  // console.log(req.body.email);
  // console.log(req.body.passwd);
  // console.log(req.swagger.params.email.value);
  // console.log(req.swagger.params.passwd.value);
  /**
   * ์ฃผ์–ด์ง„ ์ธ์ฆ ์ •๋ณด๋กœ ๋กœ๊ทธ์ธ
   *
   * email String ์‚ฌ์šฉ์ž Email
   * passwd String ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ
   * no response value expected for this operation
   **/
  var args = req.swagger.params;
  if (!args.email) {
    res.statusCode = 400;
    res.end('email is missing');
    return;
  }
  if (!args.passwd) {
    res.statusCode = 400;
    res.end('passwd is missing');
    return;
  }

  var email = args.email.value;
  var passwd = args.passwd.value
  if (email === 'john.doe@acme.ibm.com' && passwd === 'passw0rd') {
    SMVAuthTokenHelper.generateAuthToken(function(token) {
      // Set the key of user
      SMVAuthTokenHelper.setAuthTokenValue(token, 'userid', email, function(result){
        if (result) {
          res.setHeader('X-AUTH-TOKEN', token);
          res.end('OK'); 
        } else {
          // Error 
          res.statusCode = 500;
          res.end('Internel Server Error');
        }
      });
    });

    return;
  }
  
  res.statusCode = 401;
  res.end('Unauthorized');
};

module.exports.logoutGET = function (req, res, next) {
  //console.log(req.headers['x-auth-token']);
  /**
   * ๋กœ๊ทธ์ธ ์ƒํƒœ์—์„œ ๋กœ๊ทธ์•„์›ƒ
   *
   * no response value expected for this operation
   **/
  var token = extractAuthToken(req);
  SMVAuthTokenHelper.invalidateAuthToken(token, function(valid) {
      if (valid) {
      res.end('OK'); 
    } else {
      res.statusCode = 401;
      res.end('Unauthorized');      
    }
  });
};

Testcase ์ž‘์„ฑ

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

MochaJS๋ฅผ ์ด์šฉํ•œ JavaScript ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

JavaScript ์ฝ”๋“œ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋„๊ตฌ๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๊ฒ ์œผ๋‚˜, ๋ณธ ๊ธ€์—์„œ๋Š” MochaJS๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. MochaJS์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ https://mochajs.org/๋ฅผ ์ฐธ๊ณ  ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋จผ์ € ๋‹ค์Œ ๋ช…๋ น์œผ๋กœ mocha ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ npm install mocha --save-dev

mocha ๋ชจ๋“ˆ์ด ์„ค์น˜ ์™„๋ฃŒ๋˜๋ฉด ํ”„๋กœ์ ํŠธ ํด๋”์— test์ด๋ผ๋Š” ํด๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ mkdir test

์ƒ์„ฑ๋œ test ํด๋”์— SMVAuthTokenHelper์šฉ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์ธ test-authtokenhelper.js๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ์Šต์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

const TEST_TIMEOUT = 60*1000; // 60 seconds

var assert = require('assert'),
    SMVAuthTokenHelper = require('../controllers/SMVAuthTokenHelper');

describe('[SMVAuthTokenHelper Unit Test]', function() {

    var authtoken;

    this.timeout(TEST_TIMEOUT);

    it('AuthToken generation', function (done) {
        SMVAuthTokenHelper.generateAuthToken(function(token) {
            assert(token);

            // keep this. it will be used later testcases
            authtoken = token;

            done();
        });
    });
    
    ...
});

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ์ „์ฒด ๋‚ด์šฉ์€ https://github.com/mc500/smv/blob/master/projects/smv-userauth/test/test-authtokenhelper.js์„ ์ฐธ๊ณ  ํ•˜์‚ฌ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ , package.json ํŒŒ์ผ์˜ script์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด test ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

...
"scripts": {
    "start": "node app.js",
    "test": "mocha"
  },
...

์ด์ œ ์ƒ์„ฑ๋œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์„ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์œผ๋กœ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$ npm test

๊ทธ๋Ÿฐ๋ฐ, ์œ„ ๋ช…๋ น์œผ๋กœ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜๋ฉด์„œ ์ •์ƒ ์ง„ํ–‰์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

$ npm test

> SMVUserAuthService@0.0.3 test /Project/DevWorks/smv/projects/smv-userauth
> mocha

/Project/DevWorks/smv/projects/smv-userauth/controllers/SMVAuthTokenHelper.js:16
if (redisCredentials.uri) {

                    ^

TypeError: Cannot read property 'uri' of null
    at Object.<anonymous> (/Project/DevWorks/smv/projects/smv-userauth/controllers/SMVAuthTokenHelper.js:16:21)
...

SMVAuthTokenHelper ๋ชจ๋“ˆ์ด ์‚ฌ์šฉํ•˜๋Š” Redis ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ์˜ค๋ฅ˜์ธ๋ฐ CF App์—์„œ๋Š” ์—ฐ๊ฒฐ๋œ Redis ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์‹ ์ž„ ์ •๋ณด๋ฅผ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์–ป๊ฒŒ ๋˜๋ฏ€๋กœ ์ด๋ฅผ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋กœ์ปฌ ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ๊ณผ ์œ ์‚ฌํ•œ ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์ธ test.sh์„ ์ƒ์„ฑํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์„œ๋น„์Šค ์•ฑ์šฉ ์Šคํฌ๋ฆฝํŠธ๋Š” Linux๋‚˜ MacOS์˜ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

export PORT=6001
export VCAP_SERVICES=$(node vcap-local.js)
npm test

Windows batch ํŒŒ์ผ์˜ ๊ฒฝ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

set PORT=6001
FOR /F "delims=" %%i IN ('node vcap-local.js') DO set VCAP_SERVICES=%%i
npm test

์ด์ œ test ํŒŒ์ผ์ด ์ •์ƒ ์‹คํ–‰๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

$ ./test.sh 

> SMVUserAuthService@0.0.3 test /Project/DevWorks/smv/projects/smv-userauth
> mocha



  [SMVAuthTokenHelper Unit Test]
    โœ“ AuthToken generation (778ms)
    โœ“ AuthToken validation (176ms)
    โœ“ AuthToken validation negative test (175ms)
    โœ“ AuthToken set value (175ms)
    โœ“ AuthToken get value (174ms)
    โœ“ AuthToken invalidate (351ms)


  6 passing (2s)

๋งŒ์•ฝ ๊ตฌํ˜„ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์˜ˆ์ƒ์„ ๋ฒ—์–ด๋‚œ ์ž˜๋ชป๋œ ๋™์ž‘์„ ํ•œ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

$ ./test.sh 

> SMVUserAuthService@0.0.3 test /Project/DevWorks/smv/projects/smv-userauth
> mocha



  [SMVAuthTokenHelper Unit Test]
    1) AuthToken generation
    2) AuthToken validation
    โœ“ AuthToken validation negative test (520ms)
    3) AuthToken set value
    4) AuthToken get value
    5) AuthToken invalidate


  1 passing (535ms)
  5 failing

  1) [SMVAuthTokenHelper Unit Test] AuthToken generation:
     AssertionError: undefined == true
      at test/test-authtokenhelper.js:24:13
      at Object.generateAuthToken (controllers/SMVAuthTokenHelper.js:44:3)
      at Context.<anonymous> (test/test-authtokenhelper.js:23:28)

  2) [SMVAuthTokenHelper Unit Test] AuthToken validation:
     AssertionError: undefined == true
      at Context.<anonymous> (test/test-authtokenhelper.js:34:9)

  3) [SMVAuthTokenHelper Unit Test] AuthToken set value:
     AssertionError: undefined == true
      at Context.<anonymous> (test/test-authtokenhelper.js:51:9)

  4) [SMVAuthTokenHelper Unit Test] AuthToken get value:
     AssertionError: undefined == true
      at Context.<anonymous> (test/test-authtokenhelper.js:60:9)

  5) [SMVAuthTokenHelper Unit Test] AuthToken invalidate:
     AssertionError: undefined == true
      at Context.<anonymous> (test/test-authtokenhelper.js:69:9)



npm ERR! Test failed.  See above for more details.

Source Code Lint ์ ์šฉ

Microsoft Windows์šฉ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ํ”„๋กœ๊ทธ๋žจ์ด์—ˆ๋˜ Visual Studio๋ฅผ ์‚ฌ์šฉํ–ˆ๋˜ ๋ถ„๋“ค์€ ๋‹ค๋ฅธ ์†Œ์Šค ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋ณผ ๋•Œ Tab Spaces์™€ Shift Width๊ฐ€ ๋‹ฌ๋ผ ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๋ณด๊ธฐ ๋ถˆํŽธํ–ˆ๋˜ ๊ฒฝํ—˜์„ ํ•˜์…จ์œผ๋ฆฌ๋ผ ์ƒ๊ฐ ํ•ฉ๋‹ˆ๋‹ค. ์ตœ๊ทผ์—๋Š”, ํ˜‘์—…์„ ํ†ตํ•œ ๊ฐœ๋ฐœ์„ ๋งŽ์ด ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ๋น„๋‹จ tab์ด๋‚˜ indentation ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ•จ์ˆ˜์˜ ์„ ์–ธ์ด๋‚˜ ์‹œ์ž‘์— ๋Œ€ํ•œ ์Šคํƒ€์ผ ์ฐจ์ด๋กœ ์ธํ•ด ์„œ๋กœ ๋ถˆํŽธํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

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

JavaScript์šฉ Lint

JavaScript์šฉ Lint๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ œ์ผ ์œ ๋ช…ํ•˜๊ธฐ๋„ ํ•˜์ง€๋งŒ ๊ทธ ์›์กฐ๋Š” Douglas Crockford๊ฐ€ ๋งŒ๋“  JSLint์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฐ€ ์“ด ์ฑ…์ธ Javascript the good parts์—์„œ ์†Œ๊ฐœ๋œ ๊ฒƒ์ธ๋ฐ JavaScript์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ฝ”๋“œ์˜ ์งˆ์„ ๋†’์ด๊ณ  ์ž ์žฌ์ ์ธ ์˜ค๋ฅ˜๋ฅผ ์ค„์—ฌ ๋ณด๋‹ค ํšจ์œจ์ ์ธ ํŒจํ„ด ๋ฐ ์ฝ”๋”ฉ ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ•ฉ๋‹ˆ๋‹ค. Code๊ฐ€ ์ด์™€ ๊ฐ™์€ ํŒจํ„ด์ด๋‚˜ ๊ทœ์น™์„ ๋”ฐ๋ฅด๊ณ  ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๋Š” ๋„๊ตฌ๋กœ์„œ JSLint๊ฐ€ ๊ฐœ๋ฐœ ๋˜์—ˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์˜คํ”ˆ์†Œ์Šค ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ํ†ตํ•ด JSLint๋ฅผ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ข€ ๋” ๊ฐœ์„ ํ•œ JSHint๋„ ์žˆ๊ณ , Nicholas C. Zakas๋ผ๋Š” ๋ถ„์ด ๊ฐœ๋ฐœ ํ›„ ์˜คํ”ˆ์†Œ์Šคํ™” ํ•œ ESLint๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

ESLint์˜ ๊ฒฝ์šฐ ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ธฐ๋Šฅ๊ณผ ๊ฐœ์ธ์ด ์„ค์ •ํ•œ ๊ทœ์น™์„ ์ถ”๊ฐ€ ํ•  ์ˆ˜ ์žˆ์–ดAirbnb JavaScript Style Guide๋„ ESLint๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ œ์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋“œ์‹œ ์ด ๊ทœ์น™์„ ๋”ฐ๋ฅผ ํ•„์š”๋Š” ์—†์ง€๋งŒ ์ข‹์€ ์˜ˆ์ œ๋กœ์„œ ์ฐธ๊ณ  ํ•  ์ˆ˜ ์žˆ์„ ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ, IDE(ํ†ตํ•ฉ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ)์—์„œ lint ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ํ˜•ํƒœ๋กœ ๊ฒ€์‚ฌ ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๊ฒฝ์šฐ ์ฝ”๋“œ ์ˆ˜์ •๊ณผ ๋™์‹œ์— ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์ง€์  ๋ฐ ์›์ธ์— ๋Œ€ํ•ด ์•Œ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค๋งŒ, ๋ณธ ๊ธ€์—์„œ๋Š” Command Line ํ™˜๊ฒฝ์—์„œ ๊ฒ€์‚ฌํ•˜๊ณ  ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ๋ชฉ์ ์ด๋ฏ€๋กœ IDE์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ๋‹ค๋ฃจ์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ณธ ๊ธ€์—์„œ๋Š” ํ™•์žฅ์„ฑ์ด ๋†’์€ ESLint๋ฅผ ํ™œ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

ESLint ์„ค์ •

๋‹ค์Œ๊ณผ ๊ฐ™์ด ESLint ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ npm install eslint --save-dev

ESLint๋Š” lint ์‹คํ–‰ ์ค‘ ์ „์ฒด ์ ์šฉ๋˜๋Š” ์„ค์ • ์ •๋ณด๋ฅผ ๋ณ„๋„์˜ .eslintrc.yml๋ผ๋Š” ํŒŒ์ผ์— ์ €์žฅํ•ด ๋†“์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, eslint --init ๋ช…๋ น์„ ์ด์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์ ์ธ ์„ค์ • ํŒŒ์ผ ์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. .eslintrc.yml ํŒŒ์ผ์€ JavaScript๋‚˜ JSON ๊ทธ๋ฆฌ๊ณ  YAML ์„ธ ๊ฐ€์ง€ ํ˜•์‹์„ ์ง€์›ํ•˜๋ฏ€๋กœ ์ดˆ๊ธฐํ™” ์‹œ ์ ์ ˆํ•˜๊ฒŒ ์„ ํƒํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์œผ๋กœ ์„ค์ • ์ •๋ณด๋ฅผ ์ดˆ๊ธฐํ™” ํ•ด ๋ด…๋‹ˆ๋‹ค.

$ ./node_modules/.bin/eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? No
? Where will your code run? Node
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? YAML
Successfully created .eslintrc.yml file in /Project/DevWorks/smv/projects/smv-userauth

์œ„์™€ ๊ฐ™์€ ๋ฌธ๋‹ต ํ˜•์‹์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ํŒŒ์ผ์€ YAML ํ˜•์‹์ธ .eslintrc.yml ํŒŒ์ผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฉฐ ๋‚ด์šฉ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

env:
  es6: true
  node: true
extends: 'eslint:recommended'
rules:
  indent:
    - error
    - 4
  linebreak-style:
    - error
    - unix
  quotes:
    - error
    - single
  semi:
    - error
    - always

๊ธฐ๋ณธ์ ์œผ๋กœ ESLint์— ๋‚ด์žฅ๋œ eslint:recommended ๊ทœ์น™์„ ํ™•์žฅํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ ์šฉ๋œ ๊ทœ์น™์— ๋Œ€ํ•œ ์ƒ์„ธ ๋‚ด์šฉ์€ https://eslint.org/docs/rules/์—์„œ check๋˜์–ด ์žˆ๋Š” ๊ทœ์น™์„ ์ฐธ๊ณ  ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ด์ œ ์ด ์ƒํƒœ์—์„œ ./node_modules/.bin/eslint app.js ๋ช…๋ น์œผ๋กœ ESLint๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

ESLint๋กœ ์†Œ์Šค ์ฝ”๋“œ ๊ฒ€์‚ฌ

ESLint๋ฅผ ์ด์šฉํ•˜์—ฌ smv-userauth์˜ app.js์˜ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ํ™•์ธ ํ–ˆ๋”๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

$ ./node_modules/.bin/eslint app.js

/Project/DevWorks/smv/projects/smv-userauth/app.js
  24:3  error  Expected indentation of 4 spaces but found 2  indent
  25:3  error  Expected indentation of 4 spaces but found 2  indent
  26:3  error  Expected indentation of 4 spaces but found 2  indent
  59:3  error  Expected indentation of 4 spaces but found 2  indent
  60:5  error  Expected indentation of 6 spaces but found 4  indent
  61:5  error  Expected indentation of 6 spaces but found 4  indent
  62:5  error  Missing semicolon                             semi
  65:3  error  Expected indentation of 4 spaces but found 2  indent
  68:3  error  Expected indentation of 4 spaces but found 2  indent
  71:3  error  Expected indentation of 4 spaces but found 2  indent
  74:3  error  Expected indentation of 4 spaces but found 2  indent
  77:3  error  Expected indentation of 4 spaces but found 2  indent
  78:5  error  Expected indentation of 6 spaces but found 4  indent
  78:5  error  Unexpected console statement                  no-console
  79:5  error  Expected indentation of 6 spaces but found 4  indent
  79:5  error  Unexpected console statement                  no-console

โœ– 16 problems (16 errors, 0 warnings)

app.js๋งŒ ์‹คํ–‰ํ–ˆ์„ ๋ฟ์ธ๋ฐ ๊ฝค ๋งŽ์€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค๋งŒ, ๋Œ€๋ถ€๋ถ„์ด indentation(๋“ค์—ฌ์“ฐ๊ธฐ)์œผ๋กœ app.js๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋“ค์—ฌ์“ฐ๊ธฐ ํฌ๊ธฐ๊ฐ€ 2์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. .eslintrc.yml ์˜ ๋‚ด์šฉ ์ค‘ ๋“ค์—ฌ์“ฐ๊ธฐ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด 4์—์„œ 2๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

...
rules:
  indent:
    - error
    - 2
...
$ ./node_modules/.bin/eslint app.js

/Project/DevWorks/smv/projects/smv-userauth/app.js
  62:5  error  Missing semicolon             semi
  78:5  error  Unexpected console statement  no-console
  79:5  error  Unexpected console statement  no-console

โœ– 3 problems (3 errors, 0 warnings)

์ฒ˜์Œ๋ณด๋‹ค ๋งŽ์ด ์ค„์–ด๋“  ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 62:5 error Missing semicolon๋Š” 62๋ฒˆ์งธ ์ค„์— ์„ธ๋ฏธ์ฝœ๋ก ์ด ๋ˆ„๋ฝ๋œ ๊ฒƒ์„ ๋งํ•˜๋ฏ€๋กœ ํ•ด๋‹น ๋ผ์ธ์— ์„ธ๋ฏธ์ฝœ๋ก ์„ ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ 78, 79๋ฒˆ์งธ ๋ผ์ธ์— ์˜ˆ์ƒ์น˜๋ชปํ•œ console ๊ตฌ๋ฌธ์ด ์žˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ no-console ๊ทœ์น™์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ธ๋ฐ, Bluemix์˜ ๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋Š” ํ•ญ์ƒ console์„ ํ†ตํ•œ ํ‘œ์ค€ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ์„ ํ•˜๋Š”๋ฏ€๋กœ .eslintrc.yml์—์„œ ํ•ด๋‹น ๊ทœ์น™์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

...
rules:
  no-console:
    - off
...

์ด์ œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹ค๋ฅธ ํŒŒ์ผ๋“ค์— ๋Œ€ํ•ด ๊ฒ€์‚ฌํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋Œ€์ƒ์ด ํด๋”์ธ ๊ฒฝ์šฐ ํ•ด๋‹น ํด๋”์™€ ํ•˜์œ„ ํด๋”์— ์žˆ๋Š” ํ™•์žฅ์ž๊ฐ€ js์ธ ํŒŒ์ผ์„ ๋ชจ๋‘ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

$ ./node_modules/.bin/eslint app.js controllers

๊ฝค ๋งŽ์ด ๋‚˜์˜ค๊ธฐ๋„ ํ•˜๊ณ  ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด ๋ช‡ ๊ฐ€์ง€๋งŒ ์ •๋ฆฌํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฉ”์‹œ์ง€ ๊ทœ์น™ ์กฐ์น˜ ์‚ฌํ•ญ
'url' is assigned a value but never used no-unused-vars url ๋ณ€์ˆ˜๋ฅผ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๊ฒฝ์šฐ comment ์ฒ˜๋ฆฌ
Expected indentation of 4 spaces but found 2 indent ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ 4๋กœ ์กฐ์ •
Strings must use singlequote quotes ๋ฌธ์ž์—ด์„ ์ž‘์€ ๋”ฐ์˜ดํ‘œ๋กœ ์ž‘์„ฑ
Missing semicolon semi ์„ธ๋ฏธ์ฝœ๋ก  ์ถ”๊ฐ€

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

/*eslint no-unused-vars: "warn" */

...

no-unused-vars์˜ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜„์žฌ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ๋‚˜์ค‘์—๋ผ๋„ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜๊ฐ€ ์„ ์–ธ๋˜์–ด ์žˆ๋Š” ๋ถ€๋ถ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

module.exports.loginPOST = function (req, res, next) {

...

์ด๋ฅผ ์œ„ํ•ด no-unused-vars ๊ทœ์น™์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ต์…˜์„ ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • vars ์˜ต์…˜์€ all๊ณผ local ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ๊ธฐ๋ณธ ๊ฐ’์œผ๋กœ all์ž…๋‹ˆ๋‹ค.
  • varsIgnorePattern ์˜ต์…˜์€ ์ •๊ทœ์‹ ํŒจํ„ด์„ ์ง€์ •ํ•˜์—ฌ ํ•ด๋‹น ์ด๋ฆ„์ธ ๊ฒฝ์šฐ ๋ฌด์‹œ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • args ์˜ต์…˜์€ after-used, all, none ์„ธ๊ฐ€์ง€ ์˜ต์…˜์„ ์ œ๊ณตํ•˜๋Š”๋ฐ ๊ธฐ๋ณธ ๊ฐ’์€ after-used์ธ๋ฐ ์ œ์ผ ๋งˆ์ง€๋ง‰ argument๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€ ํ™•์ด๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ๋ชจ๋“  argument์— ๋Œ€ํ•œ ๋ถ€๋ถ„์€ ๋ฌด์‹œํ•˜๋„๋ก ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

/*eslint no-unused-vars: ["warn", {"args": "none"}] */

...

์•„๋‹ˆ๋ฉด .eslintrc.yml ํŒŒ์ผ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ทœ์น™์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

...
rules:
  no-unused-vars:
    - warn
    - args: none
...

๋˜ํ•œ, ์•ž์„œ ์ž‘์„ฑํ•ด ๋†“์•˜๋˜ Testcase์— ๋Œ€์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ESLint๋ฅผ ์ ์šฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹คํ–‰ ํ™˜๊ฒฝ์˜ ์ฐจ์ด๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— testcase ํŒŒ์ผ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™˜๊ฒฝ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์–ด์•ผ no-undef ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

/* eslint-env mocha */

๊ทธ๋ฆฌ๊ณ , ESLint์˜ ๋Œ€์ƒ์ด ์–ด๋Š์ •๋„ ์ •ํ•ด์กŒ๋‹ค๋ฉด ESLint๋ฅผ package.json์˜ script ํ•ญ๋ชฉ์— ๋“ฑ๋กํ•˜์—ฌ ์‹คํ–‰ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด pckage.json ํŒŒ์ผ์— ESLint ํ˜ธ์ถœ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

...
  "scripts": {
    "start": "node app.js",
    "test": "mocha",
    "lint": "./node_modules/.bin/eslint app.js controllers test"
  },
...

๊ทธ๋ฆฌ๊ณ  ๋ช…๋ น์ฐฝ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด npm run lint ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋ฉด scripts์— ์ •์˜ํ•œ ๋ช…๋ น์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

$ npm run lint

> SMVUserAuthService@0.0.3 lint /Project/DevWorks/smv/projects/smv-userauth
> eslint app.js controllers test

์ „๋ฐ˜์ ์ธ ESLint์˜ ๊ธฐ๋Šฅ ๋ฐ ๊ทœ์น™์— ๋Œ€ํ•œ ์ƒ์„ธ ๋ถ€๋ถ„์€ http://eslint.org/docs/rules/๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋น„์Šค ์ค‘ ์‚ฌ์šฉ์ž ์ธ์ฆ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์„ ํ”„๋กœํ†  ํƒ€์ž…์œผ๋กœ ๋งŒ๋“ค์–ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๊ธ€์—์„œ๋Š” ๋‚˜๋จธ์ง€ API ์„œ๋น„์Šค๋“ค์— ๋Œ€ํ•œ ํ”„๋กœํ† ํƒ€์ž…์„ ๊ตฌ์„ฑํ•˜๋Š” ๋‚ด์šฉ์œผ๋กœ ์ง„ํ–‰ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


  1. ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ ์ดํ•ด
  2. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์ƒ ๋ฐ ์š”๊ฑด ์ •์˜
  3. ์š”๊ฑด์— ๋Œ€ํ•œ Usecase ๋ฐ Wireframe ์ž‘์„ฑ
  4. ๋งˆ์ดํฌ๋กœ ์„œ๋น„์Šค ์•„ํ‚คํ…์ณ ์„ค๊ณ„
  5. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„ ํ™˜๊ฒฝ ์ค€๋น„
  6. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์ค€๋น„
  7. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part1
  8. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part2
  9. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœํ† ํƒ€์ž… ์ž‘์„ฑ Part3
  10. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ DevOps ํ™˜๊ฒฝ ๊ตฌ์„ฑ

์ฐธ๊ณ 

ํ† ๋ก  ์ฐธ๊ฐ€

์ด๋ฉ”์ผ์€ ๊ณต๊ฐœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•„์ˆ˜ ์ž…๋ ฅ์ฐฝ์€ * ๋กœ ํ‘œ์‹œ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค