コンパイラかく語りき

import { Fun } from 'programming'

Express, MongoDBアプリケーションにRedisを導入する

やること

ExpressとMongoDBで作成するアプリケーションにRedisを導入します。

作るもの

図書館を模したアプリケーションを作成します。

データ -> 書籍
データへのリクエスト -> 書籍が作成されたり、取り出されたり
データストレージ(MongoDB)-> 書棚
キャッシュ(Redis) -> 共用のデスク

のような模し方をしています。

要は、いちいち書棚まで本を探しに行くのは面倒なので、よく使う本は共用のデスクに置いておきたいよねという話。

この記事はCaching a MongoDB Database with Redisを参考にしています。

前提

Node.js, npm, MongoDB, Redisのセットアップは済んでいる前提です。

Mongo起動

起動しておきます。

$ mongod --dbpath=/data --port 27017

アプリケーション起動スクリプト

Nodeアプリケーションの作成。

$ npm init -y
$ npm i --save express mongodb redis body-parser
$ touch index.js

Mongoに接続して、アプリを立ち上げる処理。

// index.js
const express = require('express')
const MongoClient = require('mongodb').MongoClient
const app = express()
const bodyParser = require('body-parser')
const mongoUrl = 'mongodb://localhost:27017/textmonkey'

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())


MongoClient.connect(mongoUrl, (err, db) => {
  if(err) {
    throw 'Error connecting to database - ' + err
  }
  app.listen(8000, () => {
    console.log('Listening on port 8000')
  })
})

基本的なエンドポイント作成

ExpressとMongoDBを利用した基本的なエンドポイントを作成。

$ touch access.js
// access.js

// 書籍の保存
module.exports.saveBook = (db, title, author, text, callback) => {
  db.collection('text').save({
    title: title,
    author: author,
    text: text
  }, callback)
}

// 書籍の取得
module.exports.findBookByTitle = (db, title, callback) => {
  db.collection('text').findOne({ title }, (err, doc) => {
    callback((err || !doc) ? null : doc.text)
  })
}

index.jsに追記

// index.js

// ... 略

// new
const access = require('./access.js') 

MongoClient.connect(mongoUrl, (err, db) => {
  if(err) {
    throw 'Error connecting to database - ' + err
  }
  // new
  app.post('/book', (req, res) => {
    if (!req.body.title || !req.body.author) {
      res.status(400).send("Please send a title and an author for the book")
    } else if (!req.body.text) {
      res.status(400).send("Please send some text for the book")
    } else {
      access.saveBook(db, req.body.title, req.body.author, req.body.text, (err) => {
        if (err) {
          res.status(500).send("Server error")
        } else {
          res.status(201).send("Saved")
        }
      })
    }
  })
  // new
  app.get('/book/:title', (req, res) => {
    if (!req.params['title']) {
      res.status(400).send("Please send a proper title")
    } else {
      access.findBookByTitle(db, req.params['title'], (text) => {
        if (!text) {
          res.status(500).send("Server error")
        } else {
          res.status(200).send(text)
        }
      })
    }
  })
  // ...略
})

これで基本的な書籍の保存と取得が実装できました。

キャッシュ機能を追加

Redisサーバを立ち上げておきます。

$ redis-server

redisに必要なクライアントを呼び出し。

// index.js

// new
const redisClient = require('redis').createClient
const redis = redisClient(6379, 'localhost')

findBookByTitleCached関数を追加

access.jsにて、関数を追加。

// access.js

module.exports.findBookByTitleCached = (db, redis, title, callback) => {
  redis.get(title, (err, reply) => {
    if (err) {
      callback(null)
    } else if (reply) {
      // キャッシュにて書籍を発見
      callback(JSON.parse(reply))
    } else {
      // キャッシュに書籍が無かったのでDBにクエリ発行
      db.collection('text').findOne({ title }, (err, doc) => {
        if (err || !doc) {
          // DBにも書籍が無かった場合
          callback(null)
        } else {
          // DBにて書籍を発見
          // キャッシュにセーブして、クライアントにリターンする
          redis.set(title, JSON.stringify(doc), () => {
            callback(doc)
          })
        }
      })
    }
  })
}

index.jsにて書籍取得にfindBookByTitleCachedを使うように変更。

// index.js

app.get('/book/:title', (req, res) => {
  if (!req.params['title']) {
    res.status(400).send("Please send a proper title")
  } else {
    access.findBookByTitleCached(db, redis, req.params['title'], (text) => {
      if (!text) {
        res.status(500).send("Server error")
      } else {
        res.status(200).send(text)
      }
    })
  }
})

これで、redisにデータがあればそれを返すことができます。書棚までいちいち見に行かずとも、共用のデスクで見つけることができた的な。

アプリケーションを起動

$ node index.js

Mongo, Redisサーバ, Expressが起動しており、問題がなければlocalhostでアプリケーションが立ち上がるはずです。

f:id:chuck0523:20170509180532p:plain

PostManのようなHTTPクライアントで書籍のPOSTやGETを試してみると、アプリケーションが動いていることが確認できます。

Codes on Github

実際のコードを見たい方はこちらからどうぞ。

https://github.com/chuck0523/chuck-personal-repo/tree/master/langs/redis/mongo-node-redis