メッセージベースのMicroServiceをNode.js上で簡単につくれるSenecaを試してみた

# 背景

関わっているプロジェクトで触る機会があったので備忘録的にメモ

# Senecaとは

Node.js環境でメッセージベースのMicrorServiceを簡単に構築出来るパッケージ。メッセージはJSON形式です。

# Senecaの3つの重要な機能

- **Pattern matching**: Instead of fragile service discovery, you just let the world know what sort of messages you care about.
- **Transport independence**: You can send messages between services in many ways, all hidden from your business logic.
- **Componentisation**: Functionality is expressed as a set of plugins which can be composed together as microservices.

パターンマッチング、独立した転送、コンポーネント化ということで、ソースコードに触れながらこれらの恩恵を感じていきます(笑)

# Senecaの基本的な使い方

```js

var seneca = require('seneca')()

seneca.add('role:math,cmd:sum', (msg, reply) => {
  reply(null, {answer: (msg.left + msg.right)})
})

seneca.act({role: 'math', cmd: 'sum', left: 1, right: 2}, function (err, result) {
  if (err) return console.error(err)
  console.log(result)
})
```
参考：http://senecajs.org/getting-started/

サンプルが凄くシンプルで解りやすかった。
seneca.addがアクションの登録、seneca.actがメッセージの送信。

### seneca.add

```js

seneca.add('role:math,cmd:sum', (msg, reply) => {
  reply(null, {answer: (msg.left + msg.right)})
})
```

1つ目のパラメータが処理対処とするメッセージ(JSON形式)のパターン
2つ目のパラメータが実際に処理対象のメッセージが来た時に実行するFunction(アクション)

アクションはmsgとreplyという2つのパラメータを持っていてmsgはメッセージのPlain Object、replyはコールバックでerrorとresponsdのシグネチャを持っています。

### seneca.act

```js
seneca.act({role: 'math', cmd: 'sum', left: 1, right: 2}, function (err, result) {
  if (err) return console.error(err)
  console.log(result)
})
```
1つ目のパラメータがメッセージ
2つ目のパラメータがコールバック

この例だとseneca.addの`reply(null, {answer: (msg.left + msg.right)})`で指定された情報が`functin(errr, rersult)`に入ってくる。

# その他

### seneca.prior

```js
var seneca = require('seneca')()

seneca
  .add('role:math,cmd:sum',function (msg, respond) {
      var sum = msg.left + msg.right
      respond(null, { answer: sum })
    })
  .add('role:math,cmd:sum',function (msg, respond) {
      // make an error if msg.left or msg.right is infinite value
      if (!Number.isFinite(msg.left) ||
          !Number.isFinite(msg.right))
      {
        return respond(new Error("Expected left and right to be numbers."))
      }

      this.prior({
        role:  'math',
        cmd:   'sum',
        left:  msg.left,
        right: msg.right,

      }, function (err, result) {
        if (err) return respond(err)

        result.info = msg.left+'+'+msg.right
        respond(null, result)
      })
    })

  .act('role:math,cmd:sum,left:1.5,right:2.5',
        console.log // prints { answer: 4, info: '1.5+2.5' }
     )
```

**prior**を利用することで、メッセージに対するアクションの前に特定の処理を実行することができる。

1つ目のパラメータは事前処理を追加したいメッセージ
2つ目のパラメータは事前処理の内容

また、サンプルコードの中では1つ目のaddで追加したアクションに対して2つ目のaddでアクションのオーバーライドを行なっている。

### seneca.use
```js

require('seneca')()
  .use(plugin, options)
```
**use**を利用することで、パッケージ化したロジックを利用することが出来る。

1つ目のパラメータは定義した関数名かプラグイン名
2つ目のパラメータは関数やプラグインに渡すオブジェクト

```js:index.js
function math(options) {

  this.add('role:math,cmd:sum', function (msg, respond) {
    respond(null, { answer: msg.left + msg.right })
  })

  this.add('role:math,cmd:product', function (msg, respond) {
    respond(null, { answer: msg.left * msg.right })
  })

}

require('seneca')()
  .use(math)
  .act('role:math,cmd:sum,left:1,right:2', console.log)
```
こちらが、関数名を指定したケース。
useで指定されるパッケージの場合は**this**で**seneca**のインスタンスにアクセス出来る。

```js:math.js
module.exports = function math(options) {

  this.add('role:math,cmd:sum', function sum(msg, respond) {
    respond(null, { answer: msg.left + msg.right })
  })

  this.add('role:math,cmd:product', function product(msg, respond) {
    respond(null, { answer: msg.left * msg.right })
  })

}
```
```js:index.js
// ①ファイルパスを指定するケース
require('seneca')()
  .use(require('./math.js'))
  .act('role:math,cmd:sum,left:1,right:2', console.log)

// ②パッケージ名を指定するケース
require('seneca')()
  .use('math') // finds ./math.js in local folder
  .act('role:math,cmd:sum,left:1,right:2', console.log)
```
こちらが、パッケージ名を指定したケース。

### seneca.wrap

```js
module.exports = function math(options) {

  this.add('role:math,cmd:sum', function sum(msg, respond) {
    respond(null, { answer: msg.left + msg.right })
  })

  this.add('role:math,cmd:product', function product(msg, respond) {
    respond(null, { answer: msg.left * msg.right })
  })

  this.wrap('role:math', function (msg, respond) {
    msg.left  = Number(msg.left).valueOf()
    msg.right = Number(msg.right).valueOf()
    this.prior(msg, respond)
  })

}
```
**wrap** を利用すると、特定のパターンにマッチしたメッセージのアクションをオーバーライドすることができる。上記ケースの場合は**add**された2つのアクションの事前処理としてmsg.left、msg.rghtを数値に変換している。

1つ目のパラメータは対象とするメッセージのパターン
2つ目のパラメータはオーバーライドする処理内容

# For MicroService


```js:math.js
module.exports = function math(options) {

  this.add('role:math,cmd:sum', function sum(msg, respond) {
    respond(null, { answer: msg.left + msg.right })
  })

  this.add('role:math,cmd:product', function product(msg, respond) {
    respond(null, { answer: msg.left * msg.right })
  })

  this.wrap('role:math', function (msg, respond) {
    msg.left  = Number(msg.left).valueOf()
    msg.right = Number(msg.right).valueOf()
    this.prior(msg, respond)
  })

}
```

```js:service.js
require('seneca')()
  .use('math')
  .listen({ type: 'tcp', pin: 'role:math' })
```

```js:client.js
require('seneca')()
  .client({ type: 'tcp', pin: 'role:math' })
  .act('role:math,cmd:sum,left:1,right:2',console.log)
```

**listen** を利用することで、特定のパターンのメッセージをリッスンすることが出来る。便利！typeにはtcpやamqpなどパッケージをインストールすることで様々なタイプのメッセージを指定出来る。

**client** を利用することで、特定のパターンのメッセージを指定したタイプにメッセージを発信出来る。

ここからは環境に依存するものが多いので、パラメーターの紹介は割愛。
参考：http://senecajs.org/getting-started/