マイクロサービス内で動くAPIをF#で書いている
by
ayato
×
Copy
Open
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
マイクロサービス内で 動くAPIを F#で書いている 2025/06/15 関数型まつり
Slide 2
Slide 2 text
あやぴー - 株式会社ユーザベース - 9LISPという勉強会で道を外したヒト - 約10年関数型言語を業務で使っている - 最近はよくClojure, F#を書いてます - @ayato-p - #fp_matsuri_c
Slide 3
Slide 3 text
どんな開発組織でF#を採用したのか
Slide 4
Slide 4 text
プロダクトチーム紹介 - XPに基づいたアジャイル開発組織 - 100人超の開発者(SWE, SRE, MLE, TE)が在籍 - ペアプログラミングを中心とした開発 - チームの最大人数は5人 - 規模が大きくなってもチームで独立して動けるようにする - 自律したチームなので明示的なリーダーは不在 - マイクロサービスとしてサービスを開発している - モノリス -> マイクロサービスをやってきている - 新規機能はマイクロサービスで開発している - モノリス部分は随時リプレース - マイクロサービスの各APIは多種多様な言語で書かれている - ↑大事
Slide 5
Slide 5 text
プロダクトチーム紹介 ※ イメージです
Slide 6
Slide 6 text
https://agilejourney.uzabase.com/archive/category/UZABASE 興味があれば…
Slide 7
Slide 7 text
F#導入以前のプロダクトチーム - Scala, Kotlin, ClojureなどJVM系言語を筆頭に様々な言語が採用されていた - Elixir, Go, Rustなどの時勢に乗った言語の採用もされていた - OCaml, Haskellのようなちょっとレアな言語も採用されていた - フロントエンドではDart, TypeScriptなども採用されていた - 何故か.NET系の言語だけは採用されていなかった
Slide 8
Slide 8 text
どのように導入したのか - 当時私がいたチームをそそのかした - 全員ノリがよかった - Core APIにデータを投入するAPI ※ イメージです
Slide 9
Slide 9 text
なぜF#だったのか - 組織内で誰も使ったことがなかった - 静的型付けな関数型言語で現実世界で使いやすそう - シンタックスが簡素に感じた - エコシステムは.NETのものが流用できそう
Slide 10
Slide 10 text
開発環境 - .NET (Core) - クロスプラットフォームで動く、モダン.NET - dotnetコマンド - ビルドなど - Paket - 依存関係の管理 - Ionide - VS Code / Cursorで動作するプラグイン - Fantomas - フォーマッター
Slide 11
Slide 11 text
F#らしさを活かした設計
Slide 12
Slide 12 text
F#らしさ - 第一級関数 - コンピュテーション式 - 判別共用体 - 例外 現実世界の問題を解くのにちょうどいい。
Slide 13
Slide 13 text
ASP.NET Core Minimal APIのスタイルを採用した - 関数プログラミングと親和性が高い - フレームワークよりライブラリの組み合わせが好ましいと考えていた - Library patterns: Why frameworks are evil - Tomas Petricek - プロダクトチームとしてはフレームワークに求めている機能が多くはない - 外界を適切に分離して、ドメインを中心に据えたアーキテクチャを好むため ※ ルーティングに対して関数を登録するだけでよく、従来のフレームワークのようにクラスを作ったりアトリビュートを大量に付与する必要がない
Slide 14
Slide 14 text
アーキテクチャ概観 ※ こういうアーキテクチャで開発することが多い
Slide 15
Slide 15 text
境界にインターフェイスではなく関数型を使う - 関数型を境界に用いると他の関数と組み合わせるのが容易になる - 依存先をテストするさいにモックしやすい
Slide 16
Slide 16 text
ドメインを素朴に保つ - ドメインにある関数やレコードはできるだけモナドや副作用と無縁にする - 特に Option はEffective Javaでも言及があるようにフィールドに保持しない - F#の場合は判別共用体で宣言できる可能性が高い
Slide 17
Slide 17 text
ドメインを素朴に保つ - モデリング次第ではあるが判別共用体を頼る方が意味がハッキリする - Option を使うより良い可能性が高い
Slide 18
Slide 18 text
ユースケースで抽象度をあげてエラーを扱う - 判別共用体を多用すると具体的でハンドリングしにくい - 鉄道指向プログラミングを目指したい ※ このくらいなら許容できるレベルではあるが…
Slide 19
Slide 19 text
ユースケースで抽象度をあげてエラーを扱う
Slide 20
Slide 20 text
Resultに頼りすぎず例外を適切に使う - 関数プログラミングの書籍では例外より Result 型の良さが強調されがち - 現実世界では例外で済む事情をわざわざ Result 型に変換する必要はない - 特に外界の事情や業務上のエラーではない状態を Result 型に押し込めてはいけない ※ タイムアウトをResult型や判別共用体に変換してまでドメインレイヤーまで持ち上げたくないよね
Slide 21
Slide 21 text
非同期の取り扱い
Slide 22
Slide 22 text
ドメインは素朴にしたいが…Asyncだけは特別扱い - 以下のように Async を返す関数を引数に取る関数を定義したいことがある - これを無理に消すと Async.RunSynchronously などを使うことになる - DDDで集約を跨いだ情報でロジックを構築するための「getter高階関数パターン」の紹介 - F#で Async を無理に外そうとすると大変な目に…(後述)
Slide 23
Slide 23 text
Asyncを不用意に止めない - Async.RunSynchronously を使って非同期計算を止めてしまうのは禁忌 - .NETは賢いのでスレッドプールを拡大し続けるが、そのために気付きにくい問題になる - レスポンスの値は基本的に Task やそれに準ずるものにしておく - F#の Async は返り値に指定できないので注意 ※ 上記のようなコードを書いてはいけない
Slide 24
Slide 24 text
Asyncを不用意に止めない - 返り値は必ず Task にする
Slide 25
Slide 25 text
TaskよりAsyncを使う - Task は即時実行される、 Async は遅延実行される - 計算の遅延と仲良く付き合う
Slide 26
Slide 26 text
並行処理で想像以上にリクエストが飛ぶ - Async.Parallel で気軽に並行処理が実装できる - 問答無用で通信先を◯しにいく
Slide 27
Slide 27 text
並行処理で想像以上にリクエストが飛ぶ - その先がHTTP通信ならHTTP Clientの同時接続数設定をいれておく - NET 8 Networking Improvements - Async.Parallel の maxDegreeOfParallelism を設定するのも良い - FSharp.Core - Async
Slide 28
Slide 28 text
その他、実装上の悩みごと
Slide 29
Slide 29 text
Minimal APIはF#からの利用に最適化されていない① - C#ではラムダ式を素直に受け取れるようになっている - F#の匿名関数はラムダ式と直接の互換性がないので Func に変換する - Func<> and Delegate implicit conversion · Issue #1131 · fsharp/fslang-suggestions · GitHub
Slide 30
Slide 30 text
Minimal APIはF#からの利用に最適化されていない② - パラメーターバインドで苦労する - 匿名関数にアトリビュートを使えないなど問題が多々ある - Support attributes on lambda expressions · Issue #984 · fsharp/fslang-suggestions · GitHub
Slide 31
Slide 31 text
Minimal APIはF#からの利用に最適化されていない② - パラメーターバインドで取れないときは BindAsync を自前で実装する
Slide 32
Slide 32 text
コンピュテーション式の取り扱いが増えると煩雑 - FSharpPlusを使えるとコンピュテーション式の利用を減らせる - FSharpPlus自体は便利だが… - ドキュメントはあまり親切ではない - monadコンピュテーション式など使えるのは良い
Slide 33
Slide 33 text
DIにReaderを使うか…?
Slide 34
Slide 34 text
関数の呼び出しを検証するのが面倒 - 自分で関数を用意する必要がある
Slide 35
Slide 35 text
運用は大変か?
Slide 36
Slide 36 text
大変ではない - F#特有の難しさはあまりない - .NETに慣れていないことで発生している難しさはある - 逆に.NETの運用に慣れていれば簡単に運用できるはず - dotnet-countersのようなツールを知っていれば◎ - KubernetesにデプロイしているのでDockerイメージをビルドできれば◎ - 諸々のサービス利用も容易◎ - Datadogでメトリクス収集をしている - yamoryでのスキャンも簡単
Slide 37
Slide 37 text
強いて言うなら… - 日本語の情報は例によって少ない - とはいえ、私達の組織にとっては誤差でしかない - そういう言語をよく使っている - .NETやC#に慣れていないことが原因の大変さはある - ドキュメントの読み替えや理解がどうしても難しい - 拡張関数とかどのパッケージ開いたらいいの?とかありがち - 生成AIも万能ではない
Slide 38
Slide 38 text
まとめ
Slide 39
Slide 39 text
まとめ - F#らしさを活かした設計をする - ドメインは素朴な関数やレコードを活用 - 一部で抽象度をあげてエラーハンドリングをする - 例外もちゃんと使う - 非同期はAsyncを適切に利用する - FSharpPlusはすごいが扱うのが難しい - .NETの運用は実務上困ることはない…はず