2019年のwebAPIの設計を取り巻く問題と技術シリーズ そのいち 導入

最近、2019年のwebAPIの設計まわりの問題について考えていて、問題とその周辺技術の整理を自分の頭の中でしています。 で、その内容をたぶん何回かに分けて書きますが、初回の今日はちょっと導入として浅めの部分を整理してみようと思います。

昨今、サーバーサイドの仕事はもっぱらJSONなどをしゃべることに終始して、エンドユーザーが触る画面をサーバーがレンダリングする事例は減ってきているのではないでしょうか。これはぼくが考えるに不可逆な事態で、今後もユーザーが使うインターネットにつながるデバイスは多様化していくし、それぞれがそれぞれにネイティブな環境を持ち、それらがHTTPSを喋ってインターネットとつながる、という世界はしばらく変わらないでしょう。言葉を変えれば、サーバーサイドでHTTPをしゃべるお客さんが、エンドユーザーからクライアントアプリケーションというプログラムに移行してきている、ということです。

その結果、サーバーサイドからは「複雑な画面を作る」という責務が奪われます。その結果がサーバーサイドアプリケーションになにをもたらすのかを考えてみたいと思います。

まず、Railsなどでよく使われる文脈でいう「decorator」は、昨今においてはその存在感を少しづつ失っていくでしょう(Pythonとかのデコレータとは文脈が違うので注意)。そもそも、この文脈におけるdecoratorは、「画面が要求する様々な"ちょっとした計算"」というプレゼンテーションの関心がモデル(ここでいう「モデル」というのはRailsの文脈でいうような「モデル」です)に混じってしまうことを避けるためのソリューションでした。しかし、昨今ではサーバーサイドアプリケーションがそのような複雑な画面をレンダリングすることはなく、jsonをペロッと吐けばいいだけです。そこには「画面が要求する様々な"ちょっとした計算"」は必要なく、むしろそれは画面を構築するクライアントサイドのプログラムでやってもらうべき仕事となるでしょう。

この「デコレータいらなくなったよね」という話は、牧歌的な「サーバーで全部やります」な時代が終わり、解くべき問題が変化した結果、必要な要素技術が変化していく1例として考えられるのではないでしょうか。ここで確認したかったのは、「デコレータがいらない」という話ではなく、 むしろ「問題がかわったら必要な技術が変化する」ということです。

さて、サーバーサイドからは「複雑な画面を作る」という責務が奪われたわけですが、昨今新たに出てきた新しい問題として、「じゃあサーバーとクライアントの間でどういう責務分割をするべきなの?」「サーバー側はサーバー側で昔と比べて複雑さ増してきてるよね?それをどうする?」というものがあります。次回以降、そのあたりの問題を整理しながら、どういう問題に対してどういう技術が効いてくるのかを書いていこうと思います。

まだ詳細未定ですが、予告としては、

  • webAPIとクライアントのどちらに「ロジック」を寄せるべきかという問いを整理
    • ファーストパーティに閉じたサービスとサードパーティに開かれたプラットフォームでの違い
  • ファーストパーティに閉じた場合
    • サーバーサイドに寄せられた複雑なロジックやミドルウェアとのやり取りとどう向き合うか、その解決としての設計パターンとmicroservices
    • クライアントサイドに残された「状態管理」と「複雑な画面」と「ユーザーインタラクション」とどう向き合うか、そのための技術、その解決としてのFlux系技術
  • サードパーティに閉じた場合
    • クライアントサイドにさらに積まれる責務とどう向き合うか、そのための技術(「いわゆる」DDD的な手法)
    • 多種多様なクライアントに対してどのようにレスポンスを返すのか、そのための技術(GraphQLあたりの話につながる気がしている)
  • コネクションにぎりっぱなし問題
    • なんか書くことあるかな。2019年においてはもうそろそろ書くことなくなってきてる気はする。

あたりを書きたい。本当に書くのかは不明。@qsonaさんあたりとtwitterで喋ったら満足して書かない可能性もあります。

FizzBuzzを題材にユースケース層についてを考えるのはおそらく無意味な気がする

やんざむ先生のこのツイートを見て考えたことをまとめます。

結論を先に書くと、「これはそもそも問い自体が不適切である」(しかし強いて言えばEntity)という立場をわたしは取ります。以下、まじめにFizzBuzzを設計しながら考えてみます。

ひとまず、出発点は上にあるツイートの疑問から出発するとしましょう。

まず前提として、ユースケースってどういう役割か

まず前提として、ぼくはユースケースを「ドメインモデル(このツイートで言うところのEntity)やインフラストラクチャのオーケストレーションをする部分」だと思っています。

たとえば、「あるボタンをクリックされたら、複数のドメインモデルを利用して生まれた結果を、データベースに保存する」というのは、複数のドメインモデルとインフラストラクチャ層にあるリポジトリ組み合わせて 、システムのユーザーのアクションに1対1で対応する「ユースケース」を実現しています。

つまり、ユースケースというのは、

ものだ、と考えています。

FizzBuzzについて考えてみる

さて、翻って、FizzBuzzについて考えてみましょう。まず、FizzBuzzを無理やりアプリケーション層とドメイン層に分けて考えるとしましょう。その場合、

  • 3で割れる場合はFizz
  • 5で割れる場合はBuzz
  • 3でも5でも割れる場合はFizzBuzz
  • それ以外の場合は数字をそのまま表示する
  • これを1から指定の数字まで繰り返す

というのは、FizzBuzzという問題領域を定式化、つまりまさにモデル化したものだと言えるでしょう。なので、これをドメインレイヤーのものであると考えるのは妥当だとすることにそれほどの無理はないでしょう。

ドメインレイヤーでこの定式化された知識をどのように分割するか、という点では、いくつかの判断がありえます。ぼくがぱっと思いついたのは以下のような感じです。

  • 数をValueObjectとみなして、それを利用してDomainServiceがループで文字列を返す
  • もう全部DomainServiceに書いちゃう

前者の発想だと、以下のような実装になります。

case class FizzBuzzNum(i: Int) {
  def asFizzBuzzFormat = i match {
    case i if i % 15 == 0 => "FizzBuzz"
    case i if i % 5 == 0 => "Buzz"
    case i if i % 3 == 0 => "Fizz"
    case i => i.toString
  }
}

object FizzBuzzService{
  def doFizzBuzz(max: Int): Unit = {
    (1 to max)
      .map(i => FizzBuzzNum(i).asFizzBuzzFormat)
      .foreach(println)
  }
}

一方で後者の発想だと以下のような実装になります。

object FizzBuzzService{
  def doFizzBuzz(max: Int): Unit = {
    (1 to max)
      .map{
        case i if i % 15 == 0 => "FizzBuzz"
        case i if i % 5 == 0 => "Buzz"
        case i if i % 3 == 0 => "Fizz"
        case i => i.toString
      }
      .foreach(println)
  }
}

これは、どちらのほうが「良い設計」でしょうか。設計のレビューをするときに参考にすべきなのが各種設計原則です。そして、設計原則を適用した際の結果というのは、「問題がどのようなものか」に依存するのでした。このあたりの詳しい話は実況中継シリーズ 「開発現場で役立たせるための設計原則とパターン」 #builderscon 2018を参照してください。

さて、FizzBuzzについて、「どの部分に今後の変更がありえて、どの部分に今後の変更がないのか」を考えてみましょう。それを考えると、実は「FizzBuzzは完成された問題であり、今後の変更はない」という答えが見えてきます(!)。そうである以上、上のふたつの設計のどちらが正しいかなんてありません。問題が十分に小さいため、どちらのコードもそれなりに可読性があるし、あとはもう趣味の問題です。好きな方にすればいいと思います。

さて、ドメインレイヤーの設計が終わったところで、ユースケースのことを考えてみましょう。ユースケース

ものなのでした。しかし、今回の例に「複数のドメインモデルやインフラストラクチャ層のオブジェクトをオーケストレーション」する必要はあるでしょうか?「ない」というのがその答えではないでしょうか。そうなると、FizzBuzzという問題領域において、ユースケース層の役割は「無い」ということになります。プレゼンテーション層から直接このドメインサービスを叩くだけで良いでしょう。つまり、そもそもFizzBuzzを真面目に設計すると、多層からなる設計は不要というか、多層からなる設計をしようとしたら「役割のない層」が生まれてしまうという、考えてみれば当たり前のところに落ち着いてしまうわけです。

そして、ユースケース層の役割が「無い」ような問題領域を例にとって「ユースケースとはなにか?」を考えても、おそらく意味のある答えは期待できないでしょう。それを考えると、どうしても「これはそもそも問い自体が不適切である」という結論が出てきてしまうのです。

まとめ

このエントリの内容を要約すると、以下のようになります。

追記:

「当事者」になれないわたし

ぼくは空気を読むのが得意な方ではない。と書き始めて思ったけれど、そもそも、おそらく「自分は空気を読むのが得意で」などと自信を持って言えるひとはそうそういないかもしれない。頭ではそうわかっているのだけれど、感情の部分で「"空気を読むのが得意で"と自信を持って言えるひとはいいなあ」なんて思ってしまっている自分もいるのだ。

だって、「自分は空気を読むのが得意で」と自信を持って言えるようなひとは、逆説的なことだけれど、なんだかむしろ「自然体」で過ごしているような気がする。「空気を読む自分」と「その空気の中にいる自分」の間に1mmのズレもなく、「空気の当事者」であることに対して違和感を感じることなく過ごせる、そんな自然体。そんな明瞭な当事者でいられる強さ。「自分は空気を読むのが得意で」とてらいなく言えてしまうひとには、そんな強さを感じ取ってしまう。

翻って自分を見てみると、なんだか空気を読む自分とその空気の中にいる自分の間に、もやもやとしたズレがあるような気がする。べつに「空気を読んでしまった結果本当の自分を殺してしまっている!」なんて思っているわけではない。むしろ本当の自分なんてものがあるのであれば、きっともっと自然体で空気を読めるのではないか。本当の自分なんてものをしっかり持っていれば、本当の自分と「場に求められる自分」の間の距離をしっかりと測ることができる。だからこそ自然に空気に順応することができるのではないか。ぼくの場合、むしろ本当の自分みたいなものがなんだかふわふわしているから、自分と場の距離感を掴みそこねて、場の空気を読もうと変な力が入りすぎて空回りしたり、逆になんだか全然空気を読めなくて無礼な感じになってしまったりしているのではないか……。

しかも、そんな自分を、まるで他人のように、ズレた位置から眺めている自分。ぜんぜん当事者な感じがしない。当事者になれないままなんだかふわふわとプログラマの定年と言われる35歳を迎えようとしています。そのあとは不惑が待っている。ふわふわとしたまま不惑を待つ。けどまあ、きっとみんな大なり小なりそんなふわふわを抱えて生活してるのではないか。ふわふわしたまま大人になったっていいじゃない。だめ?

Template Method Patternがマッチする場合、マッチしない場合

リーダブルコードとハードバップコード

まずはこのおもしろツイートをご紹介させてください。

このツイートを某所で紹介したところ、「たしかに紛らわしい」「"よりよいChordを書くためのシンプルで実践的なテクニック"でもふつうに意味が通じるのがすごい」という声のほかに、「ワーオ,君の書くコードはまるでハードバップのようだ(喧嘩になります)」「 $_[++$_]->%* これとかハードバップ感ある」という声が上がったため、ハードバップのようなコードを書いて見ました。Perlで実行してみましょう。

s///;sub bop {


'$_[++$_]->%* is_a', +{ 'hard', 'bop' }, 'code'


} sub hard { print


$_[++$_]->%*


}

hard bop

このコードは、実行すると "hardbop" を出力します。わたし程度ではこのくらいのヒップさが限界ですが、もっとヒップにできそうな気がするので、ぜひあなたのバップも聴かせてください!ちなみに、このヒップなコード片( $_[++$_]->%* )を書いたのは id:moznion です。彼こそがヒップ。

アジリティを高めるための設計

この発言でもうすべてを語っているんだけど、たとえばDDDの流行(?)によりレイヤー化アーキテクチャ、そこにDIPによる依存関係の整理を加えたオニオンアーキテクチャやヘキサゴナルアーキテクチャ、それらを統括した概念としてのクリーンアーキテクチャなどへの関心はますます高まっているような気がする(要出典)。それ自体は喜ばしいことだとぼくは考えているのだけれど、一方で、初学者などがいきなり「ふんふん、これが"正解"か」と思って、その「構造だけ」を真似してみるという不幸な事故があとを絶たない、というような状況がいまあるような気がしている(要出典)。

そこに存在するのは、「こういう構造だよ」とか「こうやるんだよ」ということだけだ。重要なのは、そうではなくて「こういう構造にすることで、こういう場合に変更に強くなるでしょ。だから、その変更があることが予想できるときにはこうしたらいいよね」くらいまで理解をすることだと思う。

ぼくたちはプログラミングパズルを解いているわけではなくて、実際には「プログラミングを使って問題を解決しようとしている」はずだ(超絶技巧系の遊びだとまたちょっと話は違うんだけど、それはそれとしておく)。そのときに「良い設計」というのは、「新しい問題が出てきたときにすぐにそれに対応できるアジリティの高い構造になっていること」(この「アジリティ」という考え方については、moznion氏と話をしたことに多くを依っている)だと思う。

抽象的な話になったので、例を出したい。

ツイートにもある通りだけど、たとえば「なぜMVWhateverにするのか」というのは、簡単だ。「ViewとModelが密結合したコードだと、"見た目の変更"のときにデータやロジック、状態管理が邪魔をして簡単に見た目の変更ができない」とか、逆に「ロジックを変えようとしたら見た目が壊れちゃうから簡単にロジックの変更ができない」というような、「新しい問題にぶち当たったときにアジリティを落としてしまうような問題」を解決するために役立つパターンだからだからだ。

そして、この「UIとそれ以外を分けないでいるとアジリティ落ちる」問題は、どんな領域のアプリケーションでも「ありがち」なものだらか、「UIとそれ以外を分けるパターン」は広く使えるパターンとして「とりあえず選択しておいてもペイする」ようなパターンだったりする。けど、そうじゃないものもある。

たとえばDDDの戦術的設計(EntityとかValueオブジェクトとかあれね)とかは、本当にそれを選択しないとあとあとアジリティを落としてしまうような問題を引き起こすのかどうか、一旦考えてみてもいいと思う。単なるCRUDの簡単なTODOアプリケーションに対して、いちいちEntityやValueオブジェクトを考えることが、どれだけ後々のアジリティに影響するだろうか。むしろ、それは過度な分割を生み、後々のアジリティを落としてしまうのでは?

もちろん、「戦術的設計をいざやるぞ!ってときに、いきなりやってもできなそうだから、練習のためにやってみます」ならわかるし、そういうのをやってみることはとても価値のあることだと思う。一方で、それが後々もメンテするようなアプリケーションであるならば、「この構造を選択したのは、後々こういう変更があるときにすぐに変更できるアジリティを確保したいからです」を答えられないようなら、「XXアーキテクチャでいきます!」「XXパターンで行きます!」を選択するのはやめたほうがいい。

さて、「なぜその構造にしたの?」に対して「後々こういう変更があるときにすぐに変更できるアジリティを確保したいからです」を言えたとして、もうひとつ考えるべきことがある。それは「ほんとうにその変更って起こる可能性高いの?」ということだ。「起こらないはずの変更」に備えた部分は、「誤った構造化」となって、起こらなかった問題とは異なる「起こった問題」にぶち当たったときの負債となる(見に覚えがありませんか?ぼくは腐るほどあります)。

と、じつはぼくはサラッといまとても難しい話をしていて、これは要するに「未来を予測せよ」と言っているのと変わらない。未来を予測しなくては、「起こる問題」と「起こらない問題」はわからないからだ。しかし、そう簡単に未来が予測できればソフトウェア開発ってのはこんなに難しくないわけで、このあたりは本質的にソフトウェア開発の難しさであると思う。

少なくとも暫定的な話としては、「ある構造を選ぶときには、"こういう未来の問題に対してアジリティ高く反応するため"を言えるようにしよう」「予測できる未来(見た目は絶対かわるよね)(最初は予備校向けのサービスとして売出しますが、そのあと学校に攻め込んでいく予定となっています!)(このアプリやサービスが解決しようとしてる問題領域はめちゃめちゃでかいからそういうレベルのでかさに対応できないとだめだな)については、それに対応する構造にしておこう」「予測できない未来は予測せず、"それに備えた構造"は負債化する可能性が高いのでやめよう」というあたりに結論をおいておきたい。

で、最初に戻るわけです。「アジリティ高くすることが重要なわけで、UI変更のアジリティ高くするためにPDSを意識したり、モデルに関してもドメイン層とインフラ層を分離したりするわけで、"その分離によってどういう変更に対するアジリティを高めたいのか"を説明できないならやるな。」

暇にかまけて

暇。いい響きだと思う。声に出してみる。ヒマ。とても良い。「ヒ」の音でちょっと勢いづいた感じがするも、マ行の発音をするためには一度唇を閉じなければいけないこの感じが、いかにも暇という言葉の甘美な後ろめたさを表しているように思う。

これがもしも「ヒマ」ではなくて「ヒカ」とかだったら、あまりに印象がシャープにすぎる。暇にかまけてよしなし事をそこはかとなく書きつくるようなことは許されないような、そんな感じがする。逆に「ニマ」くらいまでいってしまうと、今度はほげほげとしすぎていて、なんとなく忙しい世間から取り残されるような後ろめたさが感じとれないではないか。

この、シャープなわけでもなければ、ほげほげとし過ぎているわけではない、そんな捉えどころのない「暇」というものについて、少し考えてみたい。

ぼくが「暇」について考えるときにいつも頭に浮かぶのは、スチャダラパーの「暇の過ごし方」という曲だ。これは WILD FANCY ALLIANCE という名盤に収められた曲で、これを「要約」してしまうのは非常に気がひけるというか、それこそ「要約」のような行為はどちらかというと「暇を許容できない」側の好むようなもののような気がするのだけれど、それはそれとして無理に要約を試みれば、「暇が悪いことみたいに言われるけど、暇ってそんなに悪いことか?」みたいな曲だ。ちなみにぼくは「大仏 ピラミッド 巨乳 万里の長城 この世の多くのデカいものの 発想自体ヒマの賜物」というフレーズが好きだ。巨乳がそこに入る発想どうなってんだよ。その「でん」であれすると、まあこの文章自体も「ヒマのなせるわざ」という感じがしないでもない。ともあれ、この暇ってやつの「一筋縄ではいかない感じ」の一端がこの曲にはあらわれているような気がする。

たとえば数学。ある種の「現実的な」ひとたちからすると、もうなんていうかキング・オブ・暇のなせるわざだと思うんですよ。「それなんの役にたつの?」出たよ「現実的」なひとたちの伝家の宝刀。とはいえ数学は多分まだマシなほうで、たとえば「いやいや、一見単なる数字遊びに見える素数の研究が、じつはコンピュータの暗号のためにすごく役にたってるんですよ」というような話があったりするわけだから。ただ、これもおかしな話で、おそらく多くの数学ファン(数学が得意なわけではないけどそのファン、というのはけっこういると思うのですよ)は、数学が役にたつかどうかなんてほんとはどうでもよくて、単純にそれがそれとして面白いんですよね。役にたつからやってるわけじゃないはず。さらに言うと、ある種の「現実的な」ひとたちからしてみたら、数学のこと考えるのに一日のうちの多くの時間を使うみたいのは「暇だね〜」って言いたくなるような感じだと思うんですけど、数学のひとたちの頭の中は暇どころか数学で忙しかったりすんだからな。わかっとけよそれ。

なんか話があっちこっちに飛んでるけど、そんな感じで「暇」ってやつはなかなか一筋縄でいかないところがあるとおもうんですね。自分自身の話に引きつけて言うと、ぼくはもう子供も生まれていい年したオッサンなのに、音楽がやめられないんです。「そんな暇あったら」って言われたことも一度や二度じゃないですよ実際。「音楽なんかやってる暇あったらXXできるでしょ」「いつまで音楽続けてんの?暇だねー」みたいな。

しかし一方で、いるんですね実際。「そんな暇あったら」って感じで、いわゆる「生産性のあること」を四六時中バリバリやってる大人たちが。そういうひとたちの生き方はたしかに暇ではない。暇の入り込む隙間を無くして無くしてビッシリと生産する人たち。どっちかっていったら「ヒカ」って感じの過ごし方してるひとたち。

そういうひとたちに対して後ろめたさみたいなのを感じてる一方で、「そんな暇あったら」って言われながらも音楽辞められないみたいなぼくはたしかに「ヒカ」でも「ニマ」でもない「暇なひと」なのかもしれないと思う。でもね、いいじゃない、それで、とも思うんですよ。「そんな暇あったら」でバリバリ生きていくの、かっこいいし尊いと思います。けど、「そんな暇」とともに過ごすのもまた、人間に備わった高度な機能だと思うんですよ。だめ?許してくれない?

あなたは「どんな暇」を過ごして過ごして生きていますか?「こんな文章読む暇あったら別のことすればよかった」と思われてなければいいなと思います。