KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

一休の伊藤直也氏に聞く、フルベットしない技術ポートフォリオ戦略 〜実践から学ぶ、医療変革プラットフォーマーの次なる一手〜

カケハシでの社内講演に、株式会社一休 執行役員CTOの伊藤直也氏をお招きしました。同社がどのようにレガシーシステムから脱却し、事業リスクを抑えながらRust/Go/TypeScriptを使い分けてきたのかお話を伺いました。社内向けの場ではありましたが、非常に有意義だったためご本人の許可を得て外部向けにまとめました。

当日は、医療変革プラットフォーマーを目指すカケハシのチーフアーキテクトである木村彰宏との対談形式でお話を伺い、ファシリテーターはカケハシのテックリードである松山が務めました。

 

松山: 本日は宜しくお願いします。まず、一休での関数型プログラミングの導入の背景についてお聞かせください。

 

 

伊藤: 実は、最初から TypeScript による関数型プログラミングを目指していたわけではなく、結果的にそうなったという方が近いです。参照透過性などへの強いこだわりよりも、「型がちゃんと効いてるほうが嬉しい」くらいの比較的軽い動機でした。弊社ではそれまで C# や Python でオブジェクト指向による開発を行ってきました。

私自身、この業界で20年近く、 Java や Perl、Ruby といった言語も使ってきましたが、どのシステムも何度作っても結局は同じことの繰り返しで、時間が経つとアーキテクチャの解釈が人によって異なったりして、技術的負債が蓄積して作り直し、というサイクルを繰り返していました。新システムを作るのにこれまで通りの方法で作っても何も変わらないだろうなと感じ、新しいプロジェクトでは従来型のクラスベースのオブジェクト指向から一度離れてみよう、というのが出発点でした。 

Webアプリケーション開発という前提と、社内の技術的文脈から、静的型付け言語としてはTypeScriptが現実的な選択肢でした。TypeScriptらしい、よりスケーラブルな書き方を模索する中で、クラスを使ってDTOに値を詰めてgetter/setterを書くような、旧来の手法ではないスタイルにたどり着きました。結果として、以前よりコードの書き方に統一感も出て、壊れにくくなったと実感しています。 クラスベース設計の限界としては、getter/setterのボイラープレート問題に加え、人数の増加に伴い実装方針にばらつきが出やすい点がありました。

オブジェクト側に実装を寄せる人もいれば、オブジェクトへの委譲をそこまでやらない人もいる。ドメインロジックを内部でしっかり処理する人もいれば、DTOとして扱ってロジックを外部に書いてしまう人もいて、ある程度コードレビューなどで方針を統一しても、新しい人が入ると同じ問題が再発するという状況でした。

 

松山: 「どこに処理を書くか」という問題は、関数型でも起こり得るように思いますが、何か違いがあるのでしょうか? また、以前Xでも言及されていた「Domain Modeling Made Functional」について、クラスベースが一般的なドメイン駆動設計を関数型で実践する上で、大変な点はありましたか?

 

伊藤: 正直なところ、なぜ現在うまくいっているのかあまり深くは考えていませんでした。ただ、おそらく関数型であること以上に、型による設計上の制約が強く働いているからかもしれません。例えば、「ワークフローはドメインロジックを起動するための部品であり、ロジックは必ずデータの近くに関数として書く」という構成にしていますが、型の制約もあるためか、その方針に自然と収束していく感じがします。

また、関数型ではIOを途中でいきなり発生させにくいという制約が、逆にロジックを自由に書きすぎることを防ぎ、良い方向に作用しているように思います。 

ドメイン駆動設計を関数型で行うことは、それほど大変ではありません。オブジェクトの内部状態を更新する際に、オブジェクト自体を書き換えるのではなく、引数をコピーして変更した値を返すという基本を徹底するだけで、全体のアーキテクチャ自体は従来のものと大きく変わらず、イディオムが変わる程度です。

 

松山: 関数型を進めていく上での理想像はありますか? 例えば、Haskellのように堅牢だが学習コストが高いというイメージではなく、皆が書きやすいような理想があれば教えてください。

 

伊藤: TypeScriptで関数型プログラミングを行う上で悩ましいのは、エラーや例外をどう扱うかですね。我々はResult型を導入して大域的な例外のスローはしないようにしていますが、Result型をモナドなどの支援機構なしで書くのは面倒ですね。

とはいえTypeScriptで他に良い方法も思いつかないので、多少の記述の面倒さを受け入れつつもそれでやっています。あとは代数的データ型が、模倣ではなくもっと言語の基本操作として書けると良いのですが。エコシステムのことを無視して文法的な理想だけを追うなら、結局Haskellが一番良いと考えています。学習コストは確かに高いんでしょうけど、かといって学習コストの低い言語はすなわち、必要な概念が実装されていないということでもあり。

Haskellのdo記法のようなモナドの仕組みが言語に組み込まれていると、Result型やOption型、Maybe型のようなモナディックな構造を扱う際にコードをフラットに書けます。TypeScriptでもモナドなどを使いたい気持ちはありますが、サードパーティのライブラリなどで無理矢理導入するとTypeScriptらしからぬ複雑なコードになりがちなためResult型のみを導入するに留めています。それでもなお無理した記述をせざるを得ない場面もあって「言語に組み込まれた形でのモナドがあればなあ、Haskell はいいよな」という気持ちに着地してしまいます(笑)

 

松山: ありがとうございます。木村さんもバックエンド開発の経験があり、TypeScriptも書かれていたと思います。伊藤さんのお話を聞いてどう思われましたか?

 

木村: 伊藤さんのお話には非常に共感します。私は2019年頃(前職)からバックエンドでTypeScriptを選択しています。Webサービス向けのSDKをTypeScriptで提供していました。SDK側にビジネスロジックが多く含まれていたのをバックエンドにリビルドしていく過程でロジックの可搬性が重要だったので、バックエンドをTypeScriptで構築していました。

私はキャリアの中で長らくScalaを使った開発をしていて、イミュータブルな設計思想や副作用の局所化、型による制約を他の言語でも実現したいと考えていました。このようなスタイルはどんな言語でも実現しようと思えばできますが、TypeScriptが程よい選択肢だと思っています。 私もワークフローのような組み方をすることが多く、制約を設けるやり方には共感します。制約は一時的にアジリティを下げるかもしれませんが、ユースケースの書き方や責務の分け方を都度議論して統一性のあるコードを作るコストの方がはるかに高いと感じています。easyでないコードでもあらかじめベストプラクティスを用意しておけば、メンバーがそれに沿って開発を進めてくれるという実感がありました。


 私が以前fp-tsを導入したのは、ただ単にエラーを型(Either)で表現し、その型に応じたエラーハンドリングを実施したかったからです。関数シグネチャを見るだけでエラーが把握できるようにしたかったのです。それ以外のAPIはあまり使わないようにしていました。業務ロジックでEitherを導入すると、複数のEitherを1つのEitherに結合するような機会がよくあるので、Traverse処理が簡潔にできるライブラリを探していました。

今は、neverthrowを使うようにしていますが、当初はTraverse処理にそこまで開かれていなかったので、fp-tsを導入しました。


Either型(あるいは、Result型)は、TypeScriptではタグ付きユニオン型で定義され、その型の関数が定義されているだけなので、手続き的にも書ける柔軟性があります。現場では、関数シグネチャをしっかり定義し単体テストが書けていれば、手続き的な書き方でもひとまず受け入れていました。プルリクエストでのやり取りの中で、宣言的な書き方を紹介していくうちにチームがそのスタイルに慣れていった感覚があります。ただ、一部処理の機能要件を達成するために導入したライブラリが、気が付くとあらゆるレイヤーにおいて利用され、プロジェクト全体が「関数型的」なコードになってしまい、少しやりすぎではないかと感じることもありました。ある意味それだけ、関数型プログラミングのスタイルが浸透したともとれるのですが。

 

松山: おふたりとも、開発において型によるより強い制約を設けるための手段として関数型プログラミングに着目されているように伺えました。

 

伊藤: そうですね。もちろん、他にも利点は多くあると思いますが、特にWebアプリケーション開発においてはそれが重要だと思います。

 

複数言語戦略とRustの採用

松山: Rustの採用は、どういった経緯があったんでしょうか?

 

伊藤: 一休で提供しているレストラン事業向けのシステムは元々Pythonで開発していましたが、コロナ禍で約2年間開発を停止していました。開発再開時にPythonモジュールのアップグレードが難航したのと、もともとPython製アプリケーションはコンテナでの実行効率が悪く Goで書かれたホテル事業のシステムに対し、Pythonでは10倍ほどのクラウドのリソースを要していて、これはさすがにちょっとなと思っていました。

他にも色々ありまして、しばらく開発を停めていて負債化してしまったシステムを再度まともな状態にもっていくのに労力を使うぐらいなら、このタイミングで作り直すのがいいだろうと判断しました。


より高速な言語を検討する中で、宿泊事業で使っていたGoが候補に挙がったのですが、私の経験上、事業が異なるなら別のプログラミング言語を使い、社内に2〜3種類の異なるシステムがあっても良いだろうという直感的な経験則があり、Go以外の言語でのリプレースを考え始めました。

社内にRustに非常に明るいエンジニアがいたので「WebアプリケーションをRustで作るの、今ならどう?」と質問してみたところ「いけるんじゃないですか?」ということだったので、Rustの盛り上がりやエコシステムに将来性を感じていたこともあり、まず1画面だけ書き換えてみよう、という形で始まりました。実際にやってみると心配していたメモリ管理周りでのつまずきの声はほとんど聞こえてきませんでした。


それよりも非同期ランタイムの使い方やCPUバウンドな処理のオフロード方法とか、そういう他の言語ではあまり意識しない部分が、チーム内にRustに詳しい人がいないと難かっただろうな、という印象でした。それ以外はあまり、Rustに固有の苦労話というのは聞いていません。プロダクションに投入してみたところ、やはり速度に関しては圧倒的で、Pythonでバックエンドを捌いていたときに比べ、ずっと少ないリソースで安定的に稼働させることができました。



松山: 複数言語を使うメリットは何でしょうか? 人材流動性が一時的に下がる可能性もあると思いますが。

 

伊藤: 「最大のメリット」というわけではありませんが、採用にプラスになる点はわかりやすいと思います。Rust、Go、TypeScriptなど、多様な技術を扱える体制は外からみたときに魅力的なんでしょうね。しかしこれは副次的メリットで、本質的なメリットは、特定の技術やプラットフォームへの過度な依存を避け、会社全体の技術スタックが急に時代遅れになるリスクを軽減できることでしょうか。

私が以前在籍していた会社では使用言語を特定の言語一つに統一していましたが、当時 Ruby on Railsの台頭があったためか、自分たちが依存していた言語のエコシステムが急速に縮小してしまって、当初思い描いていたような形での運用が難しくなったことがありました。


そういう経験もあったので、可能であれば複数の言語を併用してポートフォリオを組む方が柔軟に対応できるかもしれないなと考えるようになりました。 「この技術が最先端だ」と思われているうちは問題ありませんが、どんな技術にも流行のピークがあり、いずれ安定期や衰退期に入ります。新しい魅力的な技術が登場した際に、会社全体が一つの技術に固執していると身動きが取りづらくなってしまいます



木村: 以前勤めていた会社では、使用するプログラミング言語を主要なものに絞ることで、部署を横断して知見を共有しやすくなるとともに、メンバーが異なるプロジェクトへスムーズに移動できるといったメリットを享受していました。ただ、おっしゃるように、プログラミング言語をはじめとする技術への分散投資ができていないと技術の衰退や市況環境の変化に直接的な影響が出てしまいますよね。個人のキャリアを振り返ってもそういった変化に追随できるよう選択をしてきましたし、それは組織運営においても重要ですよね。


とはいえ、短期的には一つの言語に絞った方が良い場合もあると思います。そういった技術選定を、技術に詳しくない経営層にどう説明されているのでしょうか?

 

伊藤: 正直なところ、「いい感じにやっとくんで」くらいしか言っていません(笑)。事業ごとに別言語を使っていることを経営層に詳細に説明することはあまりないですね。これまで信頼貯金を貯めてきたことで「その辺はこの人に任せておけばよいだろう」と思われていると解釈してます。


とはいえ任されているからといって好き勝手やっているわけではないです。私はこの会社に後からCTOとして参画しましたし、会社は私が辞めても存続するものなので、組織が長く続くことを前提に考えています。ポートフォリオを組んでおけば、先に話したようなリスクに対して強くなるだろうし、私が退任した以降もその効果は続くだろうとか、そういうことは意識しますね。


元々社内は .NET 中心でしたが、一気には変えられない。徐々にGoを取り入れて、.NET と並行稼働になり··· と技術的負債を返却する過程で結果的に常に2つ3つの技術を持つ状態になりました。当初から「混在していていいのか?」と社外から質問されることもありました。以前は経験値も少なかったので、ややごまかすような説明しかできなかったものの、今では「技術の多様性を担保するためです」と自信を持って言えるようになりました。



松山: 複数の言語を運用するのは、ベンチャー企業の初期には難しいと感じます。企業の規模やチームがある程度大きくなってから取り入れられるものでしょうか?

 

伊藤:おっしゃる通りです。弊社でも複数言語を使っていますが、事業単位では特定の言語に絞ります。例えば、ホテル事業は基本的にGoのみ、レストラン事業はRust、私が関わっているSaaS系のレストラン向けプロダクトはTypeScript、といった具合です。ベンチャー企業は基本的にワンプロダクトでしょうから、初期に技術ポートフォリオのために複数の言語を使い分けることは考えられないでしょうね。



松山: 新しいプロダクトを立ち上げる際の言語選定の基準は何ですか? メンバーに合わせるのか、それとも他の基準があるのでしょうか?

 

伊藤合理性とメンバーのモチベーションの両方を考慮します。どちらか一方では決めないです。理屈だけ··· たとえばビジネスのことを考えてこうでこう、みたいな理由で選んでもそれを使うメンバーがやる気を持てなければうまくいきませんし、逆にメンバーがやりたい言語だけで決めるとバランスを欠くとか、中長期的な視点を欠くこともあります。「やりたい人がいること」と「その言語やエコシステムに将来性があること」、この両方の条件が揃ったものを採用するようにしています。Rustの場合も、社内に詳しいメンバーがいたからこそ任せられると思い採用しました。

 

トップダウンで進めた技術移行とチームの変化

松山: 技術の移行をトップダウンで進めると、社内から様々な反応があったかと思います。賛成や反対、共感や反感など、どのように対処し、説明し、納得を得るためにどんな取り組みをされたのでしょうか?

 

 

伊藤: .NETからオープンソース系のGoやPythonへ移行した際は、「なぜそんなことを?」という雰囲気も多少感じました。私も入社してそれほど時間が経っておらず、現場からの信頼もそれほど得られていない状況です。しかし、一人ひとりを説得していては進まないし、トップダウンで進めてある程度形になってから「こういう理由でこうしました、よろしくね」と理解を求める形を取りました。最初から全員の合意を得ようとすると、細かい議論ばかりで前に進めません。最終的に決める人間が責任を持ってトップダウンで進めるのが最も現実的でした。反対意見を押し切れるのは、権限と責任を持つ立場だからこそです。


おそらく現場では“不安”が大きかったんだと思います。慣れ親しんだ.NET、Windows、Visual Studioから未知のプラットフォームへ移行するわけですから。弊社は元々.NET畑の人が多くて、全員WindowsでVisual Studioを使って開発していました。そこにMacでPythonやらRubyやらを書いている私が入ってきて「これでやって」と言うのですから、不安になるのも当然だと思います。それでも、時間の経過とともに「新しいことができて楽しい」という空気が広がっていきました。


古いシステムで作っていた人たちにとっては、新しいシステムの方が楽に書けるしテストもきちんと実行できるし、デプロイもしやすい。こっちの方が開発しやすいな、と支持されるようになっていったと思います。そういう新しいプラットフォームへの切り替えを過去何度かしてきたため、最近のRustやTypeScriptで新しい開発を始める際には、かつてのような不安はもうなくなっていたと思います。

 

松山: ”雰囲気が変わる”というのは、具体的に感じられるものですか? チーム単位での違いや、個人の適応力の問題などはありましたか?

 

伊藤: はい、それは感じますね。先ほど申し上げたように、古い環境からPythonやGoに移行した際には、開発のしやすさから支持が広がりました。一度、言語移行の成功体験を組織全体で共有できたことが大きく、その後のRustやTypeScriptへの挑戦も前向きに進められたのだと思います。

 

技術選定と信頼の築き方、マネジメントの役割

松山: 以前、naoyaさんとSHIFTの川口さんの対談で「良い兄貴問題」について触れられていたのが印象的でした。最近のエンジニアマネージャーはメンバー育成に注目が集まりがちですが、本来は目標達成が最も重要だというお話に共感しました。改めてご説明いただけますか?

 

伊藤: はい。「良い兄貴問題」とは、マネジメントを始めた人がやがて現場の仕事をやらなくなって、メンバーが面倒に思っている調整ごとや雑務を巻き取ることで好かれるものの、気がつけば事業や開発を推進する力を失っている、という状況を指します。


マネージャーはコードも書かず企画やプロジェクトを先導することもしなくなり、事業やプロダクトに関する解像度が落ちてかつてのような全能感を失う。それでもチームの役に立とうと1on1や組織運営といった“周辺業務”ばかりに邁進するようになって、現場では“役に立たない人”になってしまう。マネージャーの本来の役割はチームで目標を達成することであり、メンバーのモチベーション向上などはその手段の一つでしかないです。しかし、調整や雑務ばかりでは事業やプロダクトが行き詰まったとき、その局面を打開するのに組織的なアプローチしか取れなくなってしまう。だからこそ、「プロダクトを作ることや、コードを書くことを続けよう」と伝えています。 


少し話はかわりますが、私たちは技術と事業目標を直接結びつけることはしません。事業目標と技術は多くの場合関係がないので、目標設定に技術の話を混ぜ込むとおかしなことになります。例えば関数型でシステムを作りたいとして、それを無理やり事業目標に結びつけようとすると「関数型プログラミングの方が事業に貢献する」なんていう妙な屁理屈を捻りだすことになる。だから、事業目標達成にコミットする、その手段は我々に任せてほしいというスタンスです。その上で自分たちが快適で効率的な開発環境を整えることができれば、仕事も捗り成果にも繋がるでしょう、そういう順番です。目標と技術的手段は分離して考えています。 


ただし、常に事業目標を優先せよ、というようなメッセージを強く打ち出しすぎると今度は「技術は手段である」という側面が強調されすぎて、技術的なチャレンジがしにくくなってしまいます。技術的に正しいことは技術的に正しいこととして、大義名分をもって実行していきたいですよね。そういう後押しもマネジメントがしていきます。 Rustへの書き換えのような大きな判断は、「事業的にRustが正解だから」というようなごまかしたような主張はせず、「技術的にこっちの方がいいからそうしよう」という話をストレートにして、後押ししてきました


この話が「良い兄貴問題」とも関連します。技術的負債の返済を正面からビジネス側に説明するのは非常に困難です。しかし、マネージャーがプロダクト開発に主体的に関与し、状況をコントロールしていれば、「次の新機能開発時にこの部分も書き換える」という判断と実行が可能です。マネージャーが調整役に徹していると、こういうビジネスと技術の双方を加味したミクロな判断ができません。結果、ビジネス要求をチームに流すだけになって、技術的負債の返却は、別途現場でプロジェクト化するしか方法がなくなります。しかしそのプロジェクトの遂行にはステークホルダーの説得が必要だが、前述の通りそれは難しい。ゆえにいつまで経っても、プロジェクトを進める機会がやってこない。


結論、自分たちのプロダクトやサービスに対するコントロールをどれだけ持てるかが重要で、そこを握っていれば技術的な判断も主体的に行えます。ここを手放さないためにもマネジメントがプロダクトやサービス開発に主体的であることが重要だと考えています。

 

松山: それは信頼があってこその話だと思いますが、どのように信頼を築いてこられたのでしょうか?

 

 

伊藤: 目標達成はもちろんですが、ステークホルダーの期待に日々きちんと応える続けることですね。「この人たちには、やり方も含めて任せればいいんだな」と思われたら良いんです。


私も入社当初は社長を含め周囲から全く信頼されておらず、プロジェクトへの人員割り当てを事細かく全て説明することを求められたりすることもありました。しかし、成果を出し続けるうちに、徐々に「この人には細かいことを言わなくても大丈夫だ」と思われるようになっていったと思います。ここでの成果は、開発的な成果というよりはビジネスまで含めた成果です。


以前、レストランの基幹システムをビジネス上の課題解決のために作り直すプロジェクトがあり、それを全面的に任されました。課題把握から解決策の実行まで全て自分でやって、まあまあ期待通りの内容で完遂したあたりから、「この人は任せて大丈夫だ」と認識されたようです。それが大きなきっかけだったと思います。

 

松山: なるほど、ありがとうございます。木村さんにお伺いしますが、今後社内で様々な施策を展開されるにあたり、トップダウンやボトムアップなどをどのような形で進めていくイメージを持っていますか?

 

木村: 私はアーキテクトなので、「どんな問題を解くか」という問題定義の部分は、ある程度トップダウンで示す必要があると考えています。しかし、「どう解くか」まで全てトップダウンで決めるつもりはありません。「こういう課題があるので、こういうアーキテクチャにすべき」「この品質特性を守るにはこういう技術が必要」といったガイドラインレベルの話はするかもしれませんが、基本的にはそこまでです。 


カケハシのエンジニアはサービス指向性が強く、向かうべき方向さえ明確になれば自律的に動ける方が多いので、細かく指示するのはおこがましいとすら思っています。 一方で、抽象的なことばかり言っていると思われないよう、今も現場でコードを書いています。


トヨタの豊田章男さんの「トップダウンとは自分でやって見せることだ」という言葉に共感しており、最終的には「自分が全ての責任を負う」──そういう覚悟で取り組んでいます。 私の考えた課題が全て正しいとは思っていないので、あくまで「叩き台」として問題を提示し、現場の声も聞きながら一緒に進めていくことが多いです。ただ、現在の会社の規模は大きく、ドメインも5〜6個あるため、全てを個別に見るのは困難です。そのため、ドメインチームごとにアーキテクトを配置し、その方々と対話しながら進めていく体制を考えています。

 

AIと共に進化する開発現場・役割の再定義と人間の価値

松山: 最近Devinを使い始めて、人間の仕事が今後どう変わるのか気になるようになりました。エンジニアの仕事もAIで大きく変わっていくと思いますが、どう見ていますか?

 

伊藤: 変化の速さには正直驚いています。2年前にChatGPT-3.5を触った時は「AIもコードを書けるようになってきたな」程度でしたが、1年半でここまで実用的になるとは予想外でした。もっと時間がかかると考えていましたが、未来予測が全然当たらないですね。


こういう状況では未来を正確に予測しようとすることより、足下で起きていることにどう適応していくかを重視したほうがいいかなと思っています。先のことは分からないが、目の前のことを早めにキャッチアップして、適応する。それを繰り返していれば、先のことはわからないものの、それなりの位置には立ち続けていられるだろうという考えです。


昨今社内では、フロントエンドのコードはかなりAIに任せるようになっています。プレゼンテーションはドメイン知識が少なくても、デザインシステムやFigmaの情報があれば生成できるようで、うまく活用されています。ただし、大規模なコードや複雑なドメインロジックはまだAIでは難しいですね。フロントエンド中心の人たちは、AIを積極的に活用し、別のことに時間を使うようにもなっています。組織全体で、現在どこまでAIに任せられるか、逆にどこが難しいかの肌感覚を掴むためにも、まずは皆に積極的にAIを使ってもらう必要があると考えています。ここは敢えて、手段を目的化するフェーズかなと思っています。

 

木村: 技術革新の真っ只中で様々なパラダイム・シフトが発生していますが、あまり流行りに左右されずに裏で動いている構造の理解をし、エンジニアリングに落とし込んでいきたいですね。

確かに、ドメインロジックを自律的に組んでもらうことは難しいですが、ある程度線引きは可能かもしれません。ドメインモデリングには「探索」と「体系化」の2つのフェーズがあり、探索フェーズではAIエージェントにアイデア出しや支援をしてもらうのが有効だと考えています。体系化フェーズではドメイン知識が必要なので開発者が主体となりますが、ある程度進んでくると部分的に自律的AIに任せられるかもしれません。 


個人的には、生成AIのパラダイム以前から、ドメインロジックが組めて検証が済むと「だいたい仕事が終わったな」という感覚を抱くことが多かったです。アダプターの部分は単純化しやすいため、そこがAIで効率化されたのが現在のフェーズだと認識しています。

 

松山: ありがとうございます、本日は以上とさせていただきます。貴重なお話をありがとうございました。


伊藤: こちらこそ、ありがとうございました。