Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

サーバーサイド Kotlin における技術選択

この記事は、Money Forward Engineering Advent Calendar 2024 12/4 の投稿です。 12/3 は VTRyo さんで SRE Kaigi 2025に 「一人から始めたSREチーム3年間の歩み -求められるスキルの変化とチームのあり方-」で登壇します でした。

adventar.org


こんにちは、マネーフォワードでバックエンドエンジニアをしている @hktechno です。

現在、マネーフォワードでは、バックエンド開発においてこれまでの Ruby on Rails に加えて、サーバーサイド Kotlin の導入を推進しています。

私は現在、これからサーバーサイド Kotlin を導入するプロジェクトや、すでに導入したプロジェクトに対して、社内標準の技術スタックを定義して提案したり、社内向けの共通ライブラリ・ベストプラクティスを提供したりすることで、Kotlin の導入コストを減らし、すぐにビジネスロジックの開発に取り掛かれるような環境整備を進めています。

ここでは、サーバーサイド Kotlin における技術選択について、お話ししたいと思います。

社内のサーバーサイド Kotlin の現状

社内で開発標準や、Kotlin 標準化の流れができる前から、いくつかのプロジェクトがバックエンド開発に Kotlin を導入していました。マネーフォワードには沢山のプロダクト・バックエンドサービスが存在しており、今も毎月のように新しい Kotlin プロジェクトが立ち上がっている状況です。

マネーフォワードでよく使われる言葉に「Let's make it Together!」があります。サーバーサイド Kotlin においても make it together したいのですが、残念ながら各チームの知見が共有できる状況ではないのが正直な所です。

一番の大きな問題は、各チームが導入した技術スタックの乖離があることです。社内には、以下のような技術で開発されているバックエンドプロジェクトが存在しています。

  • DI: Spring Boot, HTTP: WebFlux, ORM: jOOQ
  • DI: Spring Boot, HTTP: WebMVC, ORM: JPA
  • DI: Koin, HTTP: Ktor, ORM: Exposed or jOOQ
  • DI: Micronaut, HTTP: Micronaut, ORM: JPA
  • DI: なし, HTTP: Armeria, ORM: jOOQ

Kotlin という言語は同じでも、使っているライブラリやフレームワークに乖離があると、ベストプラクティスの提供や共通ライブラリにおいてもコストが増えてしまい、各チームの知見が社内で共有しづらい状況になっています。

例えば、社内サービスへの HTTP クライアントの提供ひとつを取っても、Spring WebFlux 前提で提供するのと、ライブラリやフレームワークに依存せず提供するのでは、コストがかなり変わりますし、使い勝手も劣化します。モニタリングにおいても、フレームワークやライブラリが変わると共通のダッシュボードを提供することが困難になります。

Kotlin におけるフレームワークの選択肢

なぜ、同じプログラミング言語を選択しているのに、各チームの技術選択がバラバラになってしまうのでしょうか?

これには、Kotlin を取り巻く環境が大きく関係しています。特に、Kotlin を Java の方言として考えるか、新しい言語として考えるかで、大きく選択肢が変わってきます。

例えば、以下のような観点が技術選択に影響を与えます。

  • Pure Kotlin なライブラリを使いたいか?
  • Kotlin Multiplatform へ移行したいと考えているか?
  • Coroutines を使うつもりがあるか?
  • 十分に成熟した Java のエコシステムの上に乗りたいか?
  • 昔ながらの Java 経験があるエンジニアの知見を最大限活かしたいか?

その結果、大きく3つの選択肢が生まれてきます。

  • Pure Kotlin な選択肢
  • Coroutines 対応した Java で成熟した選択肢
  • Java で十分に成熟した選択肢
    • Servlet + Java Persistence API (JPA)

以下で、それぞれどのようなメリット・デメリットがあるのか、簡単に紹介したいと思います。

Pure Kotlin な選択肢

Java の経験に基づかず、Kotlin を新しいプログラミング言語だと考えて、最新の技術を選択したい場合には Pure Kotlin なフレームワーク・ライブラリを採用することになると思います。

  • DI: Koin (or なし)
  • HTTP: Ktor
  • ORM: ...? (私なら Komapper を使うと思います)

ktor.io

大きなメリットは、Kotlin の言語のポテンシャルを最大限活用できることです。例えば、Coroutines への対応も当然ですし、Kotlin Multiplatform (KMP) への対応が大きなメリットです。

Java への依存を捨てて Pure Kotlin なライブラリが増えている理由は、主に KMP への対応です。Android やモバイル開発では KMP が主流になりつつあり、KMP で開発を行うには Java への依存を捨てなければいけないからです。JetBrains は近年 Multiplatform に特に力を入れていて、Kotlin 公式サイトでも "Concise. Multiplatform. Fun." と3つのメリットの1つとして紹介されている程です。

当然ながら、サーバーサイド Kotlin でも KMP を利用することはできます。しかし、現実的にサーバーサイドで KMP を利用するメリットを見出すのは現状では困難です。価値があるとすれば、サーバーレス環境などでしょう。JVM を避けるメリットは大きくありません。

そして、KMP 対応のライブラリは内部的に Java への依存を捨てているので、場合によっては Java を使ったライブラリとの親和性に問題が出る場合があります。なぜなら、java.* クラスや Java ライブラリが利用できないため、時刻 (java.time) やアノテーション、JSON (Jackson) など全てを KMP に寄せなければいけないからです。 将来的に KMP 移行を考えているならば、Java のライブラリは一切使用できません。

サーバーサイドにおいては、成熟度も微妙なところです。特に Ktor はバージョンアップの際に大きな破壊的変更を導入したりしています。これらのライブラリの主要なユーザーはモバイル開発者だったりするので、バックエンド開発のユーザーが少ないです。Spring Boot のようなフル機能の Web フレームワークも存在しないので、単機能のマイクロサービスならともかく、モノリシックな Web アプリケーションを開発するのは大変です。

ORM も現状では十分に成熟した物が存在しません。JetBrains 製の Exposed がありますが、KMP 対応はおろか R2DBC にも対応しておらず、開発が活発ではないライブラリです。正直、おすすめできません。

KMP 対応ライブラリを選択したいならば、Java のエコシステムとの親和性を捨てても良いかをよく考える必要があります。もし、JVM の上でサーバーサイド Kotlin を導入する場合で、書きやすい Java として Kotlin を導入したいと思っているなら、この選択肢は良いとは言えないかもしれません。

一方で、テストなど、一部のライブラリだけ使うのであれば、KMP 対応ライブラリも十分に選択肢の一つです。

Java で十分に成熟した選択肢

次に、Kotlin は「書きやすい Java」として知られ、若い世代にも親和性が高い言語であるという理由によって、「JVM の上で動かすのだからすでに確立された安定した技術である Java で十分なのでは?」と考える場合にはこの選択肢になるでしょう。

Java のエコシステムは巨大で、世界中で使われており、30年近い歴史があります。過去には、言語仕様の一部として Java EE (現在の Jakarta EE) が定められ、Web アプリケーションの標準が規格として定義され、将来的な安定性が保障されていた時代もありました。現在は他の言語で開発しているエンジニアでも、過去に Java 開発の経験がある場合も多く、知見を活かすことができる場合も多いでしょう。

実際に、社内でも非常に強力な支持層がおり、チームに Java 開発の経験があるエンジニアがいると、Kotlin による新規開発でもこの構成ではじめるプロジェクトが多いようです。

メリットとしては、圧倒的な情報量と巨大なエコシステムです。そして、Java 経験があるエンジニアは、多くの場合このような構成での開発経験があり、知見を活用しやすいので、採用面でもメリットであると言えます。

一方で、この枯れた選択肢を選択する場合、「なぜ Kotlin で書かなくてはいけないのか?」という疑問も生まれてきます。前述した情報量の多さも、多くの場合 Java で書かれた物なので、情報を Kotlin に変換しなければならず、「それならば Java 良いのでは?」となるのも当然でしょう。その説を裏付ける理由の一つに、近年、Java は進化していて、言語機能としても Kotlin に近づいているというのもあります。

ですが、Kotlin のコアな言語機能のひとつであり、採用する大きなメリットである Coroutines の利用が困難になります。なぜなら、このような枯れた Java フレームワーク・ライブラリは、非同期 I/O のことは考えられておらず、Coroutines に対応できないからです。これは大きなデメリットです。

枯れているというと聞こえが悪いかもしれませんが、安定して確立された技術であることはメリットの一つです。 ですが、Coroutines だけではなく、最新の技術への対応も反応が鈍いというデメリットがあります。
一度作って後はメンテナンスするだけのアプリケーションであれば良いですが、継続的に開発をしなければいけない一般的な Web サービス開発では、将来的に負債になる可能性もあります。

Coroutines に対応した Java で成熟した選択肢

Java のエコシステムは活用したい、しかし Kotlin で書くからには Coroutines も使いたいと思っている場合には、以下のような選択肢になると思います。

メリットとしては、フル機能の Web フレームワークである Spring を使えるので、認証やアプリケーションの設定などを全て Spring へ任せることができますし、Java の多くのライブラリは Spring integration を提供しているので、Java のエコシステムとの親和性も高いです。

Spring WebFlux は、Coroutines にも対応していて、問題なく共存することができます。Spring 自体が Kotlin への対応をかなり積極的に進めているので、ほとんど困ることはないでしょう。

docs.spring.io

欠点としては、内部が Java で非同期対応に Reactor が使われているので、場合によっては Reactive Streams の知識が必要になってくる点です。普段の開発では必要ありませんが、デバッグする際にライブラリのコードへ手を伸ばすのであれば、必要になってきます。

Spring Security や、DB のトランザクションなども、Coroutines や Reactor を使う場合には実装が異なってくるので、前述の古い技術スタックの知見が活かせない部分も出てくるかもしれません。

ORM に jOOQ を選んでいる点は完全に社内の事情であり、既存の jOOQ プロジェクトが多いからその知識を活かせるというだけで、実際には Spring Data R2DBC やその他 R2DBC が使える ORM を選べば Coroutines も使えて良いかなと思います。jOOQ も、ORM としては機能が豊富で悪くない選択肢ですし、R2DBC にも対応しています。

www.jooq.org

中途半端な選択肢と思われがちですが、Java のエコシステムを活かしつつ、Kotlin の機能も活かすことができ、将来性を考えてもモダンな開発環境で、実はバランスの取れた選択肢なのではと思っています。そして、社内で大きめのプロダクトを数年運用しているチームも、この構成でうまくいっているという知見もあります。

まとめ

何においても、技術選択は難しいです。正解はありません、全てが正解ですし、時の流れによっても変わります。特に Kotlin においては、取り巻いている環境・技術がとても先進的・流動的で、複数の選択肢が生まれやすい状況です。

「開発標準、社内標準のようなものを定めずに、各チームに選択の自由を与えた方が良いのでは?」という考えもあるでしょう。一方で、何かを決めて提供してあげないと、ただカオスが生まれるだけという側面もあります。弊社では、社内に Kotlin でのバックエンド開発の経験があるエンジニアはほとんどいません。実際に、Coroutines の誤った理解や、技術の不適切な組み合わせも発生している状況です。

そのため、現在は推奨する技術スタックの定義と、社内で必要とされる非機能要件 (CI, Datadog, Gradle の構成など) を組み合わせたテンプレートリポジトリの提供、お手本となるサーバーサイド Kotlin プロジェクトの開発を進めています。

技術スタックの推奨は、"Coroutines に対応した Java で成熟した選択肢" のような構成となる予定ですが、まだ鋭意策定中で実証実験という段階です。やはり、Kotlin を採用する上で Coroutines の利用ができない構成は取りたくないですし、Kotlin のライブラリは Coroutines の利用が前提となっているようなライブラリも数多くあります。例えば、GraphQL Kotlin などです。さらに、Coroutines とブロッキングコードを混ぜることは危険が伴うので、採用するのであれば全て Coroutines に対応した構成を取りたいと思っています。

このような技術スタックの定義などについては今後、力を入れていきたい点の一つです。
マネーフォワードでは Kotlin エンジニアの仲間も募集しておりますので、もし興味を持たれた方、ともに議論をしたいと思った方がいらっしゃれば、ぜひ一度、気軽にお話ししましょう!

hrmos.co