JavaScriptでcompose可能なCommandとして諦めてPromiseを使うことにしようとした話

手頃なInteractorが見つからずほとほと困り果てた

Clean Architecture を読んで以来、依存の方向はともかく1Interactor が気になって気になって仕方なかったので、ずっと調べてたんだけど、どーにも JavaScript で手頃なものがなくて困っていた。

そのものズバリとしては

vadimdemedes/interactor: Organize logic into interactors

なんだけど、残念ながら Node.js 上では動くけどブラウザに持ってきたら動かなかった。

ここから放浪の旅が始まる。

大きく Interactor や Command Pattern で調べると数年前にリリースしたきりみたいなのが多い。

angeloashmore/cleanroom: Compose your business logic into commands that validate input.

こういうやつ。どれも自分用に作ってみました感が強くて、乗っかるには躊躇する。

business logic方面

business logic で調べるとどうなるかというと、

辺りなんだけど、割と多機能かつちょっと DSL 感が出てくる。もっとシンプルなのでいいんだけど。

あと

trooba/trooba: Fast isomorphic lightweight framework to build pipelines for request/response, stream/response, request/response and stream/stream use-cases

こんなのも見つけて、へー面白いとは思ったものの、なかなかオーバーキルだなって感じ。

functional programming方面

command を compose できるくらいでいいんだよなーとこじらせてたら functional programming でいんじゃね説が出て来て ramdajs とか lodash/fp とか目移りし始めた。特にこれは面白かった。

Future は Promise よりも Lazy に扱えて、Fluture は functional な flavor も乗ったもの。とても面白いんだけど、明らかに語彙が増えすぎて目的から外れるのでやめた。

Promiseでいんじゃね説

まーとにかく右往左往したが、Promise で Promise の結果をまとめるのでいいんじゃねーの? というところに戻って来た。例えば以下のようにすると

function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('reject p1')
    }, 50)
  })
}

function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('reject p2')
    }, 20)
  })
}

p1()
.then(p2)
.catch((err) => console.error(err))

普通に p1 で止まる様子が console で確認できる。極めてフツー。ただし、これだと command 失敗した際に実際どうするかを catch の中だけでうまいことやるのは難しい。

もちろん工夫はあって、

function exec() {
  const eventId = ulid()

  pubsub.publish(`start ${eventId}`)
  p1(eventId)
  .then(() => p2(eventId))
  .catch((err) => pubsub.publish(`fail ${err}`)
}

みたいなことをすると eventId とともに記録を外に出せるので、rollback とかあれこれ対処はしやすくなる。こんな感じの base class があると楽かもしんない。

あとは

辺りを入れるともうちょっと使い勝手もよくなるかも。

参考

  1. たぶん普通は逆で、みんなめっちゃ依存の方向、レイヤーの切り方の記事が多い 

More