詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

Domain Modeling Made Functional を読んだ

最近フロントエンドに限らず TypeScript を書くことが多くなって、これでそれなりの規模のサーバサイドアプリケーションを書くときどうなるんだろう、と気になって読んでみた。いわゆる普通のオブジェクト指向ではなく関数指向な書き方でいきたいとき、どうするのが好ましいのか、というような観点。

名前的にそのものずばり、という本があったので購入した。日本のウェブを検索してみてもいくらか言及があるので価値はありそうだという判断で、大人なので円安でも強行する。

自分は PDF で読みたかったので Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# by Scott Wlaschin で買った。Kindle 版とどっちが安いかはタイミングによりそう。

この本ではドメイン駆動設計(DDD)を F# で実践するとどうなるのか、ということを語っている。F# は読み書きしたことがないけど、文法は見覚えのある感じだし、特別な言語機能は出てこないので、F# で詰まることはなかった。本の構成が「ドメインの分析」→「モデリング」→「実装」となっていて、F# を使った詳細に入っていくのがだいぶ後半だけになっているのもあるかも。

ドメインの分析はイベントストーミングで行うべし、というところから始まっていて、DDD の実践書としてコンパクトながらよくまとまっている。DDD の入門という観点でもよさそう。

結局この本で言っている Functional とは

  • 静的な代数的データ型を駆使してモデリングすることと、
  • 不変なデータと副作用のない関数に基づいてワークフローを実装すること

ということのようである。

代数的データ型によるモデリング

分析を経て得られたドメインモデルを、型を通じてモデリングしていく。ここではとくに直和型が重要な働きを持っていて、これを使うことで違うものを違うものとして表現できる。「ありえない状態はそもそも表現できないようにする(Making Illigal States Unrepresentable In Our Domain)」というのが大方針だ。

Capturing Business Rules in the Type System: 守るべきビジネスルールがあるなら、実行時のチェックではなく型で表現する。たとえば

type CustomerEmail = {
  EmailAddress : EmailAddress
  IsVerified : bool
}

という表現では EmailAddress が変更されたときに IsVerified がどう変化すべきかが(ロジックを読まないと)分からないし、将来の変更でバグを生む余地があるが、

type CustomerEmail =
| Unverified of EmailAddress
| Verified of VerifiedEmailAddress

として、かつ VerifiedEmailAddress の型をもつ値が自由に生成できなくなっていれば(たとえば検証ロジックを通過しないと生成できないなど)よりうまくドメインの事情を反映して、間違いにくくなる。

つまるところ、性質を名前に寄せていくことだと思う。そうすればコードによるチェックから型によるチェックへと場を移すことができる。

本では、いきなり F# を書くのではなくてドメインにおける状態を擬似コード的に表してみるとアラ不思議、F# のコードにそのまま変換できる、というストーリーになっているけれどそこまでうまくいくのかは疑問。まあ、Persistence Ignorance を強調して、終盤になるまで永続化のことを気にせずにモデリングしてるのはいいと思う。完全にデータストレージから切り離された実装をすることは、少なくとも自分が見聞きする規模のアプリケーションでは非現実的だとも思うけど。

ワークフローをパイプラインとして実装する

ワークフローを複数のステップによる値の受け渡しに分解して、それぞれのステップは副作用を持たない状態遷移としてモデリングする。ここでいう状態遷移とは、モデリングを通じて定義したデータ型を入出力として持つような関数だ。状態が変わるというのはデータの持つ値が変わることではなくて、あたらしい型になる、というのを原則として考えておくとよさそう。何らかのビジネスロジックを経て、データは別のものになる。前の例だと、「メールアドレスを検証する」ではなくて「検証されていないメールアドレスを検証されたメールアドレスに変換する」というような形だ。

Pushing Persistence to the Edges: そしてオニオンアーキテクチャ的な流儀で、ワークフロー内のステップ(群)を I/O で挟み込む形に実装する。場合によっては I/O がワークフローの両端だけでなく中に登場することもある。ともあれ純粋なビジネスロジックと I/O が別個の処理として連なっていることが重要。

ドメインロジックを副作用のない関数としてを実装することで、言うまでもなくテストがしやすくなるのはもちろんだけど、ドメインの事情をよりうまく表現できそうにも感じた。ステップの途中で I/O に逃げることができない(= Unit を返せない)ので、モデリングで定義した型のあいだの変換を行わざるを得ない、というイメージ。あとリポジトリは登場しないとか言ってるが I/O 部分がそうでしょと思った(まあいわゆる Java の DDD で言うリポジトリとはちょっと様態が違うんだと思うけど)。

まとめ

ほかにもモナドがどうこうとか Result 型がどうこうみたいな細かい話もあるけど、まあこのへんは近年の静的型をもつ言語に触れたことがあれば飛ばして読めそう。あと DI 周辺は言語によって扱いが違いそうなので真面目に読んでない(そもそもここを深堀りしないようであった)。

読んだらわりと普通だね、となったけど、いかに純粋な世界を関数型・静的型の世界で実現するか、ということに気を払っていないと容易に境界が崩れてしまうことも想像できる。一種の理想的な例として読んでおくと、現実の問題に取り組むときの足がかりにできそうである。

あと TypeScript でこれを直接実践できるかは分かっていない。とはいえ、この原則はどの言語でも役に立ちそうだった。

Scrapboxで行頭にアスタリスクを挿入したら見出しにする

Scrapboxで見出しのようなものがほしいときには [** ほげほげ] と文字装飾を重ねて作るのが常套手段だけど、議事録などすばやくドキュメントを取りたい際にはこの「文字を囲んで装飾する」という手順にモタツキが生じてしまう。文字列を選択して * にしろ、[** と入力して ] にしろ、2工程かかるイメージ。Markdown(#) とか Google Docs(Opt+Cmd+1) だと1工程なので Scrapbox でもそうやりたい。

id:gurrium による 見出しのレベルを変更するショートカット は Docs パターンで、キーボードショートカットで見出し化するというもの。これを重宝していたけれど、どうやら自分のメンタルモデル的には Markdown 風に見出しを作れるほうがあっている模様だった。ということ自分用に、行頭に * を挿入したら見出し化する、という UserScript を書いた。先人の仕事に感謝。

行頭に*で見出しを作る - Scrapboxカスタマイズコレクション

  • 新しい行に * と書くと [* ] に変換される。
  • 既存の行の頭に * と書くと [* および ] でその行が囲まれる。
  • 同様に2レベル以上の見出しも作れる。

という感じ。Scrapbox ではカーソル位置を取るのが難しく、見出しレベルを下げたり見出しを解除するのはちょっと実装できていない。まあとりあえずこれで暮らしてみようと思う。

Stable Diffusion を Colab で Web アプリ化する

Stable Diffusion が来てるねってことで貧者の GPU であるところの Colaboratory でいろいろ試したいのだけどノートブック上で Python のコードをこまごまいじりながら試行錯誤するのは微妙に体験が悪い。

ちょっとしたウェブサービスとして立てて実行できるとよいけれど、なかなかクラウドサービスも帯に短し襷に長しという感じで GPU を気軽に借りられるところはなさそうだ……と思ったら、Colab 上に HTTP サーバを立てられることを知ったので、その方法でやってみることにする。

やってみたソースは以下。

GitHub - motemen/stablediffusion-server-on-colab

README にあるノートブックを開いて Huggingface のトークンを埋め、GPU を選択して実行するとサーバが起動する。サーバが起動する前のセルに表示される https://localhost:5000 のようなリンクをクリックすると、簡単なウェブアプリケーションが起動して、入力した prompt から生成した画像を表示してくれる。画像をクリックすると prompt をファイル名とした画像をダウンロードできるので、ここから気に入ったやつだけ保存しておけばよい。

Colab の HTTP プロキシ機能は当該のリンクをクリックしたノートブックのユーザのみに見られるようにしているようなので、不用意にリロードしたりプロキシしたり他のユーザに見せたりすると 403 になってしまうのに注意。そうなってしまったら、port を変えて再実行するとよさそうである。手元で開発してたときに API だけサーバにプロキシしてたら突然 403 になってしまってハマった。

性質上、ノートブックを開いてる間しか使えません。取り急ぎ作ったものなので、ロード中の進捗表示やステップ数の調整機能くらいはつけたいですね。

はてなで一緒に働きませんか?