Scala 3用のApache Kafka®の準備

<font color="Grey">*[Preparing Apache Kafka® for Scala 3](https://aiven.io/blog/preparing-apache-kafka-for-scala-3)の翻訳です。*</font>

2021年12月7日

# Scala 3向けApache Kafka®の準備

AivenのOSPOはApache KafkaのScala 3への移行に取り組んでいます。彼らがどのように管理したかをご覧ください！

Apache Kafkaは分散イベントストリーミングプラットフォームで、オープンソースです。今日の投稿は、Kafkaを使ってできるクールなことについてではありません。今回はKafkaの内部についてです。具体的には、Aivenのオープンソースプログラムオフィスで行われている最新のタスクの1つ、Apache KafkaのScala 3への移行についてです。


## Scalaとは？


[Scala](https://scala-lang.org/)は、[オブジェクト指向](https://en.wikipedia.org/wiki/Object-oriented_programming)と[関数型プログラミング](https://en.wikipedia.org/wiki/Functional_programming)のパラダイムを組み合わせた[強力な静的型付け](https://en.wikipedia.org/wiki/Type_system#STATIC)プログラミング言語です。


Scalaのコードは、[JVM](https://en.wikipedia.org/wiki/Java_virtual_machine)、[JavaScriptエンジン](https://en.wikipedia.org/wiki/JavaScript_engine)、あるいは[LLVM](https://en.wikipedia.org/wiki/LLVM)コンパイラ上で実行できるようにコンパイルすることができます。


### Scalaの特徴


Scalaはメジャーバージョン間（例えば`2.12`から`2.13`へ）の移行が面倒なことで知られている。つまり、Scalaプロジェクトを新しいメジャーバージョンに移行するには、まずすべての依存関係を移行する必要があり、大規模なプロジェクトでは大幅な遅延が発生する。しかしScala 3では、コンパイラがScala `2.13`用にコンパイルされた依存関係ファイルを読めるようになったため、この状況は大きく変わった[1](#脚注-1)。


## Apache Kafka と Scala の関係は？


Apache Kafkaの内部コードに詳しくない方は、ScalaとKafkaにどんな関係があるのかと思うかもしれません。答えはいたってシンプルだ：KafkaはJavaとScalaで書かれています。しかし、Kafkaのコードベースに占めるScalaコードの割合はバージョンごとに減少しており、Apache Kafka 0.7では約50％だったのが、現在は23％[2]になっている（#脚注-2）。Apache 3.1.0の時点で、Scalaで書かれた最大かつ最も重要なモジュールは`core`で、その名の通りKafkaの心臓部だ。Scalaで書かれた他のモジュールは、Kafka Streams用のScala APIモジュールである。  

しかしKafkaは、Scalaの*事実上*標準的なツール（ビルド・ツール、テスト・ライブラリ...）のほとんどを使っていない。また、Kafkaで書かれたScalaのコードは、広く受け入れられている慣用的な方法で書かれていないという意見もあるかもしれない。


## なぜScala 3にアップデートするのか？


多くのライブラリや他の多くの言語と同様に、バージョンが古すぎるとアップデートが受けられなくなります。これは、新たに発見された脆弱性が修正されなかったり、そのバージョンに新たな改良が加えられなかったりすることを意味します。Scalaも例外ではない。そのため、Scala 3に移行することで、セキュリティ・アップデートや今後の機能、パフォーマンスの改善に対応することができるのです。


さらに、Scala 3に移行することで、ScalaでコンパイルされたKafkaのアーティファクトに依存している人たちの作業が容易になります。


## Scala 3への移行


Scala 3への移行の実現可能性を確認するための概念実証中に、私たちはいくつかの問題に遭遇しました。ビルド・ツール、バイトコード生成、そしてJavaの相互運用性です。それぞれの点について、さらに詳しく説明します。


### Gradleの強化


Scala 3への移行というタスクに直面したとき、私たちが最初に直面した問題は、Kafkaで使用されているビルドツールである[Gradle](https://gradle.org/)がまだScala 3をサポートしていないということでした。Gradleのチケッティング・システムにはすでに機能拡張のリクエストがあったのですが、コミュニティから誰かが参加してくれるのを待つばかりで、作業は進められていませんでした。


私たちは[Scalaセンター](https://scala.epfl.ch/)[3](#footnote-3)に、Gradleのサポートに貢献できる帯域幅があるかどうかを尋ねました。そのメンバーの一人である [Virtus Lab](https://virtuslab.com/) の Tomasz Godzik 氏は、様々なビルドツールや IDE サポートの仕事で Scala コミュニティでは非常に有名であり、このタスクに興味を示し、Gradle に Scala 3 サポートを提供してくれました。この機能を提供してくれた Tomasz に本当に感謝しています！


### マイグレーションそのもの（構文）


Scala3のサポートがGradleで（ナイトリービルドとして）利用可能になると、移行を本格的に開始することができました。発見された問題の大部分は、KafkaのScalaコードの書き方に関するもので、現在では推奨されていない機能や機能を使用していました。


#### 括弧が多すぎる


Scalaでは、パラメータを取らないメソッドを括弧なしメソッドとして定義できる。Scala 2 では、このようなメソッドは括弧の有無にかかわらず呼び出すことができました。しかし Scala 3 では、いくつかの特殊な状況下で、コンパイラーは括弧が括弧なしメソッドに属すると正しく仮定することができず、結果に括弧を適用しようとします。これは、括弧のないメソッドが括弧付きで呼び出されるすべての例に適用されるわけではなく、特定の例にのみ適用されます。


ここでは、関連する部分のみを表示した例を見ることができます：


与えられた



```
クラス KafkaConfig private(doLog：Boolean, val props: java.util.Map[˶_,˶_], dynamicConfigOverride：オプション[DynamicBrokerConfig])
 extends AbstractConfig(KafkaConfig.configDef, props, doLog) with Logging { ...
 ...
 オーバーライド def originals: util.Map[String, AnyRef] =
 if (this eq currentConfig) super.originals else currentConfig.originals
 ...
}` クリップボードにコピー
```

ここで、`AbstractConfig` は以下のJavaクラスである：



```
publicクラス AbstractConfig { ...
 ...
 public Map<String, Object> originals() { ...
 Map<String, Object> copy = new RecordingMap<>()；
 copy.putAll(originals)；
 copy.putAll(originals); return copy；
 }
 ...
クリップボードにコピーする
```

以下のコードはScala 2ではコンパイルされるが、Scala 3ではコンパイルされない。



```
クラス ReplicaManager(val config：KafkaConfig、
 ...
 ){
 ...
 protected def createReplicaSelector()：Option[ReplicaSelector] = { ...
 config.replicaSelectorClassName.mapの{ className =>のようにします。
 val tmpReplicaSelector：ReplicaSelector = CoreUtils.createObject[ReplicaSelector](className)
 tmpReplicaSelector.configure(config.originals())
 tmpReplicaSelector
 }
 }
 ...
クリップボードにコピー
```

このエラーは、Scala 3 のコンパイラが `originals` 呼び出しの余分な括弧が、たまたま `Map` であった戻り値の型を参照していると判断したことに起因する。Scala でオブジェクトに対して `()` を呼び出すと、`apply()` に変換されます。このメソッドは存在しないので、コンパイラー・エラーになる。  

Scala 2 は賢いのか寛容なのか、余分な括弧が `originals` メソッドを参照していると推測した。


#### シャドーイング


シャドーイングとは、あるスコープに以前存在した名前を宣言すると、以前の名前が見えなくなる状況を指します。


Scala 2ではスコープ内で以前に定義された名前をシャドウイングすることができたが、Scala 3ではその領域が厳しくなり、二重定義とみなされるようになった。一般的な例をここで見ることができる：



```
クラス Shadow (shadowedName: String) { シャドウ (shadowedName: String)

 def shadowedName()：文字列 = shadowedName

クリップボードにコピー
```

### 自動変換が多すぎる


以下のコードはScala 2では動作するが、Scala 3ではコンパイルに失敗する。



```
クラス TooManyImplicitsAtOnce () {

 val shortNumber1：short = 1
 val shortNumber2：Short = 4

 val range = shortNumber1 to shortNumber2

クリップボードにコピーする
```

その理由は、Scala 3はScala 2に比べて、暗黙的な変換や拡張メソッドをいくつも連続して適用することが少ないように思われるからだ。Scala 3では暗黙変換の仕組みが少し見直された[^4]。Scala 2では、`to`メソッドが存在しないため、提供された`Short`は自動的に`Int`に拡張され、コンパイラはコンパイルを満たすためにスコープ内で可能な暗黙の変換を探していた。Scala 2 は `Int` を `RichInt` にラップする変換を見つけた。


### バイトコード生成の問題


Scalaの歴史的な弱点の一つは、JavaからScalaのコードを利用することだった。この分野ではここ数年の間に多くの改善が行われ、Scala `2.13`ではJavaコードの中からScalaにアクセスできるようになりました。しかし、Scala 3が開発される間に（Scala 2と並行して開発されたことを覚えておいてください）、これらの改善の一部は失われてしまったようです。


大半のケースでは、回避策が可能であり、コードベースの移行を成功させるためにはJavaコードに最小限の変更を加えるだけでよかった。これらの問題の本質は技術的に深く、おそらく別のブログ記事を書く価値がある。


これらのバグがScala 3の開発中ではなく今になって発見された主な理由のひとつは、Scalaの新バージョンはすべて、ほぼ完全にScalaで書かれたプロジェクトのコーパスに対してテストされるからです。つまり、新しいScalaリリースの品質保証テストでは、ScalaとJavaの相互運用性のようないくつかの側面が見落とされる可能性があるということです。Scalaのオープン・ソース・プロジェクトのほとんどは、完全にScalaで書かれているか、Javaクライアントの使い勝手を良くするために薄いAPIレイヤーだけがJavaで書かれている。


Apache Kafkaは、コードのほとんどがJavaで書かれ、Scala 3への移行を試みた最大のプロジェクトであることは間違いない。つまり、KafkaにはScalaのコードを使ったJavaのコードが多くあり、これはScalaのバイトコードがどのように生成されるかという深い内部構造に依存している。


このScala 3への移行を評価する作業のおかげで、Scala 3の将来のバージョンで修正される（または修正される予定の）ユーザビリティの問題をいくつか発見することができました。


## もっと見る


もし、この移行の詳細を知りたいのであれば、[PR-11350](https://github.com/apache/kafka/pull/11350) を見てください。また、この移行について議論しているメーリングリストの [スレッド](https://lists.apache.org/x/thread.html/r4ee305bef0e65e1352c358016eb4055b323b7f12df13c16b124aa5f1@%3Cdev.kafka.apache.org%3E) も読んでみてください。


また、Aivenブログにも[Apache Kafka関連のコンテンツ](https://aiven.io/blog/category/kafka)がたくさんあります。


*脚注


[1]:Scala `2.13.4`でコンパイルされたアーティファクトは、Scala 3コンパイラで消費できる。


[2]:Apache Kafka `3.1.0` の時点で。


[3]:Scala Centerは、Scalaのツールや教育コンテンツに取り組む非営利団体である。


[4]:詳しくはこちら](https://www.scala-lang.org/2020/11/06/explicit-term-inference-in-scala-3.html)


## まとめ


Aivenのサービスをまだご利用でない方は<https://console.aiven.io/signup>から無料トライアルにお申し込みください！


また、[changelog](https://aiven.io/changelog)と[blog](https://aiven.io/blog)のRSSフィード、または[LinkedIn](https://www.linkedin.com/company/aiven/)と[Twitter](https://twitter.com/aiven_io)のアカウントをフォローし、製品や機能関連の最新情報をご確認ください。