jsでmiddleware patternを実装する

こんにちは、現象数理学科4年の@RyosukeClaです。
今日も[明治大学 advent calendar 四日目](https://qiita.com/advent-calendar/2018/meiji_univ)やっていきます。

## はじめに
Reduxのmiddleware実装をみて試して、これは一般的なmiddleware patternとして使えることを知りました。それが本記事の内容です。本記事の目的はタイトルにある通り、jsでmiddleware patternを理解し実装することです。Middlewareを活用し、柔軟なシステムを構築できるといいなという思いです。

## Middleware Pattern
Middlewareとは、2つのシステムの中間に入るシステムのことです。たとえば、expressのmiddlewareやreduxのmiddlewareなどにあたります。Expressにおけるmiddlewareとは以下のような図をイメージしてください。図のように、HandlerによってRequestが処理されるまでの間にmiddlewareを通します。
<img width="771" alt="スクリーンショット 2018-12-04 4.33.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/183627/5f196984-f020-c8e7-e614-d8118d626e70.png">

Middlewareを上手く扱えれば、処理の共通化や拡張を容易にできるようになります。たとえば、Expressを使った現実的なウェブサーバーは下図のような構造でしょう。ときには、ウェブサーバーに新しいAPIを生やしたり、Proxyを入れたりすることもあるでしょう。

現実的なウェブ開発では様々な機能が必要になります。時を隔てて、システムは多機能化、肥大化していきます。そうとなると、メンテナビリティやスケーラビリティの高い、複雑化する機能要件に耐えうる柔軟なシステム構築が必要です。Middleware patternはそのようなシステムを構築する力になるのです。

<img width="661" alt="スクリーンショット 2018-12-04 4.56.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/183627/688726ab-6556-76fb-116f-57544477e7e9.png">

## Redux middleware
Middleware Patternを実装するにあたって、僕はRedux middlewareを参考にしました。ReduxとはReactとセットで語られることが多い状態管理ライブラリです。そんなReduxにはmiddlewareという機能があります。Reduxのdispatcherを拡張する機能になります。

基本的には、Reduxは次の図のようにdispatchを呼びます。
<img width="286" alt="スクリーンショット 2018-12-04 7.24.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/183627/5a9ff33b-a920-efde-9076-9950fa14d937.png">
middlewareを用いると次の図のようにdipatchを呼ぶことになります。dispatchにmiddleware patternを適応した形です。これからは、手順を踏んでmiddleware patternを実装していきます。
<img width="350" alt="スクリーンショット 2018-12-04 7.24.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/183627/85bef6a1-c601-a764-9db7-6de60737b388.png">

### example
まずは、Reduxにおけるmiddlewareがどんな感じかを載せます。本記事が目指すmiddleware patternのイメージと思ってください。これは、Reduxにlogger middlewareを追加する例です。

loggerの内部では、actionのログを取り、次の処理を実行するためにnext(action)を呼びます。
Actionをdispatchすると、logger、middlewareA、middlewareBと順に実行されて行きます。

```redux-example.js
const logger = store => next => action => {
  console.log('dispatch', action)
  return next(action)
}

const middlewares = [
  logger,
  middlewareA,
  middlewareB
]

const store = createStore(
  App,
  applyMiddleware(...middlewares)
)
```

## Implementation
Middleware patternに必要な機能は、複数のmiddlewareを１つに押し込めることと、middleware内部で次のmiddlewareの処理に移すことです。まずは、１つに押し込めるための関数合成について説明します。次に、next関数によって直列的にmiddlewareを呼ぶ機能を説明します。その他、middleware内部で扱うAPIのインジェクションの説明もします。

### Function composition
関数型言語には関数合成(function composition)という機能があります。数学における関数合成と同じです。具体的には、
$$(f \circ g)(x) = f(g(x))$$
となります。

jsで関数合成を実装すると次となります。

```compose.js
export default function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
```

関数合成は非常に便利です。複数の関数を一つの関数にできるからです。Middlewareもcomposeを上手く使うことで簡単に実装することができます。

たとえば、3倍して自乗して1を足すコードを書いてみます。単純に実装すれば、

```js
const func = x => ((3 * x) * (3 * x)) + 1
```

ですが、関数合成を用いると次のようになります。

```js
const funcs = [
  (x) => x + 1,
  (x) => x * x,
  (x) => 3 * x,
]

const composed = compose(...funcs)
```

### next(action)
先ほどの例では気になる点が一つあります。それは、関数の実行順番が下からになることです。つまり、定義順番は以下のようになってますが、実行される順番は逆です。

| 順番 | 定義   |  処理  |
|:---:|:-----:|:------:|
| 1   | x + 1 | 3 * x  |
| 2   | x * x | x * x  |
| 3   | 3 * x | x + 1  |

Middlewareを繋げるとなると、直感に反します。また、実装者の意図するポイントで次のmiddlewareを実行したいこともあるでしょう。そのようなニーズを満たすには俗に言う`next`があると便利です。

次の例は、1、2、3を順にログするmiddlewareです。

```oneTwoThree.js

const middlewares = [
  next => action => {
    console.log(1)
    return next(action)
  },
  next => action => {
    console.log(2)
    return next(action)
  },
  next => action => {
    console.log(3)
    return next(action)
  },
]

const composed = compose(...middlewares)
const dispatch = composed(action => action)
dispatch({})
```

### MiddlewareAPI
先ほどの例でmiddlewareの実装は終わりと言ってもいいでしょう。しかし、middleware内で使用可能なAPIを提供したいこともあるでしょう。redux middlwareにもexpress middlewareにもAPIが提供されていますね。次のコードのように、reduxであれば`store`、expressであれば`res`にあたります。

```example.js
// redux middleware
const logger = store => next => action => {
  console.log(action)
  return next(action)
}

// express middleware
const logger = (req, res, next) => {
  console.log('logged')
  next()
}
```

redux middleware内では、applyMiddleware という関数が、APIをmiddlewareに渡しているようです。redux middlewareを習い、middlewareにAPIを渡します。

```dispatchFactory.js
const dispatchFactory = (middlewareApi, middlewares, dispatch) => {
  const chain = middlewares.map((middleware => middleware(middlewareApi)))
  return compose(...chain)(dispatch)
}
```

ものすごく単純ですね。この場合、middlewareは以下のように実装します。先ほどの、1、2、3を順にログするコードです。`console.log`ではなく、`api.log`を用いていることに注目してください。

```oneTwoThree.js
const middlewares = [
  api => next => action => {
    api.log(1)
    return next(action)
  },
  api => next => action => {
    api.log(2)
    return next(action)
  },
  next => action => {
    api.log(3)
    return next(action)
  },
]

const api = {
  log: console.log
}
const dispatch = dispatchFactory(api, middlewares, () => {})
dispatch({})
```

reduxのほうの実装は次のようになっています。もちろん、実際のコードとは違い、本記事に向けて改変しています。

```reduxDispatch.js
const api = {
  getState: store.getState,
  dispatch: () => {}
}
const dispatch = dispatchFactory(api, middlewares, store.dispatch)
api.dispatch = dispatch
dispatch({})
```

### Overall

これまでのをまとめると完成です！次のようなコードになります。これで色々な部分をmiddleware化することができるようになりました。ものすごく単純ですね。

```middleware.ts
function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

export function dispatchFactory(middlewareApi, middlewares, dispatch) {
  const chain = middlewares.map((middleware => middleware(middlewareApi)))
  return compose(...chain)(dispatch)
}
```

使い方の例です。

```oneTwoThree.js
import { dispatchFactory } from './middleware'

const middlewareAPI = {
  logger: {
    info (...messages) {
      console.log('[info]', ...messages)
    }
  }
}

const middlewares = [
  api => next => action => {
    api.logger.info('1', action)
    action.n++
    return next(action)
  },
  api => next => action => {
    api.logger.info('2', action)
    action.n++
    return next(action)
  },
  api => next => action => {
    api.logger.info('3', action)
    action.n++
    return next(action)
  }
]

const dispatch = dispatchFactory(middlewareAPI, middlewares, () => { console.log(‘Hello’) })
dispatch({ n: 0 })

// Out:
// [info] 1 { n: 0 }
// [info] 2 { n: 1 }
// [info] 3 { n: 2 }
// Hello
```

また、middlewareを変形させて、express middleware likeにすることもできます。便利ですね。

```toExpressLike.js
let middlewares = [
  (action, api, next) => {...}
]
middlewares.map(middleware => {
  return api => next => action => {
    return middleware(action, api, next)
  }
})
```

## 最後に
最後まで読んでくれてありがとう。Middleware Patternはどうでしたか。自身の文章力の低さを噛み締めつつ、開発の助けになればなと思います。

おつ！