2024.10.01
自社の社内情報を未来の“ゴミ”にしないための備え 「情報量が多すぎる」時代がもたらす課題とは?
Kotlin製の業務WebアプリケーションをRustでリプレイス(全1記事)
リンクをコピー
記事をブックマーク
Takumi Karibe氏:「Kotlin製の業務WebアプリケーションをRustでリプレイス」というテーマで発表します。先ほど「Rust製の業務WebアプリケーションをRustでリプレイス」という話と、そのどさくさに紛れて「フロントエンドをリプレイス」という話もありましたが、今回もどさくさに紛れてKotlin製のWebアプリケーションをRustでリプレイスした話をします。
自己紹介です。Karibeと申します。2021年の9月に入社して半年と少し経ちました。現在はバックエンドエンジニアとして、受発注管理システムとパートナー連携システムのチームリードを行っています。
社内には「Klein」と呼ばれている受発注管理プロダクトと「SPP」と呼ばれている製造パートナー連携プロダクトがあります。今回は、SPPのリプレイスの話です。
(スライドを示して)SPPは、キャディからサプライパートナーへの発注情報を受注済み案件の一覧として見られたり、見積もりの依頼に回答すると受発注管理プロダクトのKleinでデータが連携することができます。
(スライドを示して)現在の構成はこのようになっています。いろいろ省略していますが、Kotlin製のGraphQLサーバーがあり、これはSPP-APIと呼ばれています。このSPP-APIが受発注管理システムであるRust製のKleinにgRPCで通信をしています。データベースにはPostgreSQLを採用しています。今回は、このSPP-APIのリプレイスの話です。
(スライドを示して)まず「なぜリプレイスするのか」という話です。組織的な問題として、SPP-APIの初期設計者や、Kotlinに慣れた人が退職したり異動したりしてチームからいなくなってしまったことによって、新規の機能開発や運用の効率が非常に悪くなってしまいました。
また、技術的な負債として、受発注管理システムのKleinとSPPでステータスを重複して管理している部分が、分散トランザクション化してバグの温床になっていました。
また、現在は受発注管理システムのKleinをリプレイスしていて、ドメインのモデルがけっこう変わってしまったんです。
そのため、既存のモデルと同居させると確実にバグが多く埋め込まれてしまうことが目に見えてわかっていました。なので、Kleinのリプレイスのために工数を確保して開発を行っているこのタイミングでSPP-APIも変えるしかないとPdMに打診して、なんとかリプレイスにこぎつけました。
どさくさに紛れているので、リプレイスの要件がいくつかあります。Kleinの受発注管理システムのリプレイス計画を破綻させないように、必ず納期を遵守すること、またSPPはUIをリプレイスせずに、バックエンドだけで現在の機能が使えるようにする必要があること。
同時並行で開発するため、仕様が固まっていない部分やモデリング最中のドメインがありますが、仕様を妄想しながら、また、「こういうレスポンスが返ってくるだろう」と意識しながらAPIの開発を行っています。これは仕様やモデルが確定するのを待つよりも、一定の手戻りを許容した開発を行った方が最終的に完成が早いという判断からです。
リプレイス中の構成は、スライドのようになっています。先ほどSPPのKotlin製のAPIがありましたが、新SPP-APIとしてRustでGraphQLサーバーを実装しているところです。こKleinとはgRPCで通信しています。
リプレイス後の構成は、KotlinのGraphQLサーバーと古いリプレイス前のKleinのRust製のサーバーがなくなっていくので、徐々に新SPP-APIに移行していくかたちになります。
ここまでRustに変更する話でしたが、そもそもなぜRustなのかという話をしたいと思います。Rustを採用した理由として、社内に開発実績や慣れている人が多く、KotlinよりもRustで実装したほうが楽だという事情がありました。また、社内にRustで実装しているチームも多く、人員の流動性が確保しやすいので、リソース調整しやすいこともメリットとして考えていました。
また、非常に短い納期で開発しなければならないので、コンパイラに品質の担保を手伝ってほしいこともあり、Rustの採用を後押ししていました。
本来のモチベーションである機能開発や運用効率を高める意味においても、Rustでコンパイラがより堅牢なドメインモデルを表現できるメリットをしっかり活かしていくことで、負債の返済だけでなく、複利の効く資産形成として攻めのリプレイスを考えています。
具体的に利用しているライブラリとして、GraphQLではasync-graphqlを採用しています。Webアプリケーションフレームワークとしては、tokioが開発しているaxumを使っています。ORMにはdieselを使っていて、gRPCにはtonicを採用しています。
設計においては、ベストプラクティスとしてクリーンアーキテクチャを採用しており、それに倣いつつ、若干あえて外している部分もあり、シンプルな設計を目指しています。
クリーンアーキテクチャに特に変わったところはなく、コントローラーからユースケースを呼び出してユースケースがドメインやドメインサービスを呼んだり、インフラ層に接続しにいったりするものになっています。名前はクリーンアーキテクチャだったりDDD(Domain Driven Design)が混ざっていたりしますが、おおよそ「レイヤードアーキテクチャ」です。
一部教科書的ではありませんが、外部接続を抽象化した存在を置かないようにしています。本来ユースケースは、例えばインフラ層のデータベースからエンティティを取得するのか、外部サービスから、(今回でいうと)Kleinからデータを取得するのかを意識せずに、リポジトリからget_〇〇みたいにエンティティを取ってこられることが理想的かもしれません。
しかし、今回はそれも抽象化せずに具体的にデータベースから取ってくる、外部サービスから取ってくることを明示的に書くような設計にしています。
実際にSPPでデータを持っているのか、それとも依存しているサービスのKleinでデータを持っているのか、障害が発生した時にわかりづらくなることがありました。そのため、データの所在をわかりやすく、実装上シンプルにしたかったこと、また納期が短かったのでレビューのコストを下げたかったからです。
スライドの右下に簡単なスニペットを書いています。例えばHogeUseCaseが、DbAdapterProviderとAServiceClientProviderという外部接続クライアントのアダプターにユースケースが依存しているという定義において、3行目にself.provide_db();でデータベースのコネクションを取ってきたり、self.provide_a_service_client();でクライアントのインターフェースを取ってきたりして、ユースケースが具体的にどの外部に接続するのかを明示的に記述しています。
このように書いてあると、「このユースケースでは、ビジネスロジックはデータベースとこのサービスに依存している」ということが、斜め読みでもわかります。これによってGitHub上でプルリクエストを見られる時も、斜め読みで簡単にわかります。
例えばGitHub上で、「このユースケースではデータベースしかアクセスしないはずなのに、なぜこのサービスに接続する必要があるのか」といった議論が簡単にできるようなメリットを今回は採用しました。
コメントアウトされていて見づらいかもしれませんが、self.provide_b_service_client();というものは、今回このユースケースのTraitではデータベースとAサービスクライアントにしか依存していないので、こういった呼び出し方を実装すると、コンパイルエラーになってしまう設計になっています。
斜め読みした時にどこに依存しているかがわかりやすいこともありますが、そもそもこのユースケースでは「この外部とこの外部のモジュールに依存している」ということが型で定義できるので、コンパイル時にも楽で、コードを読む時にも非常に優しい設計にしています。
設計・開発の考慮2として、データの置き場所を整理しています。先ほど「SPPとKleinのどちらにもデータが存在してしまっているため分散トランザクションが発生している」という話をしましたが、今回はそのデータを一部Klein側に寄せることによって、分散トランザクションをうまく行うのではなく、そもそも分散トランザクションを避けるような設計をしています。
また、基本的に可逆的な変更の意思決定は後回しにして、とにかくアプリケーションとしてものを作っていくことを優先しています。これについて不可逆な変更や、例えば他の選択肢を選び直した場合にはコストがかなり高くなってしまう場合を除いて、意思決定の速度を優先する。とにかく実装して、アプリケーションとしてどんどん機能を追加することを優先しています。
具体的には、先ほどのRepositoryの設計として抽象化層を置かないという意思決定がいい例です。設計当初は抽象化層を置くかどうかで議論になりましたが、後になって抽象化層を置くのは実装コストが小さいため、まずは置かずにやってみてから後で考えようという判断をしました。
また、何かあった時にUnknown*Unknownな状況にならないように、意思決定時には他の選択肢のpros/consの整理をしています。AとBという選択肢があり、「今回はAで行きましょう」という場合にも、なぜBではないのか、Bにするには何が必要なのかをあらかじめ整理して考えて開発しています。
設計・開発の考慮3として、依存先の開発状況を意識しない開発を行っています。先ほど話したリプレイスの要件として、Kleinのリプレイスと今回のSPPのリプレイスの納期は同じなので、現在進行形で互いに開発が進んでいる状況です。そのため、インフラ層で新しいKleinのドメインのモデリングや仕様を隠ぺいして、とにかくドメインロジックを先にしてどんどん実装しています。
また、プルリクエストで議論をするのではなく、細かく同期的なレビューで開発着手からマージまでのリードタイムを短縮しています。これについては何千行、何万行と書いてもmainブランチにマージされなければ意味がないので、とにかく開発着手からマージまでのリードタイムを短縮して、どんどんmainブランチにコードをマージすることを意識しています。
また、Rust的にもっといい書き方がある場合でも、何かしらその旨のコメントを書いて、とにかくマージを優先することを意識しています。
まとめです。今回は開発向上のためにKotlinからRustに変更しました。また、同時並行のリプレイスの場合は、今のようにある程度不確実性を受け入れた意思決定や、手戻りとの付き合い方が非常に大事だということが今回の学びです。以上です。
関連タグ:
2024.10.29
5〜10万円の低単価案件の受注をやめたら労働生産性が劇的に向上 相見積もり案件には提案書を出さないことで見えた“意外な効果”
2024.10.24
パワポ資料の「手戻り」が多すぎる問題の解消法 資料作成のプロが語る、修正の無限ループから抜け出す4つのコツ
2024.10.28
スキル重視の採用を続けた結果、早期離職が増え社員が1人に… 下半期の退職者ゼロを達成した「関係の質」向上の取り組み
2024.10.22
気づかぬうちに評価を下げる「ダメな口癖」3選 デキる人はやっている、上司の指摘に対する上手な返し方
2024.10.24
リスクを取らない人が多い日本は、むしろ稼ぐチャンス? 日本のGDP4位転落の今、個人に必要なマインドとは
2024.10.23
「初任給40万円時代」が、比較的早いうちにやってくる? これから淘汰される会社・生き残る会社の分かれ目
2024.10.23
「どうしてもあなたから買いたい」と言われる営業になるには 『無敗営業』著者が教える、納得感を高める商談の進め方
2024.10.28
“力を抜くこと”がリーダーにとって重要な理由 「人間の達人」タモリさんから学んだ自然体の大切さ
2024.10.29
「テスラの何がすごいのか」がわからない学生たち 起業率2年連続日本一の大学で「Appleのフレームワーク」を教えるわけ
2024.10.30
職場にいる「困った部下」への対処法 上司・部下間で生まれる“常識のズレ”を解消するには