Slide 1

Slide 1 text

ゲーム開発に最適な サーバーサイドKotlin 〜Kotlinの導⼊と基盤ができるまで〜 株式会社アプリボット チーフエンジニア ⽵端 尚⼈

Slide 2

Slide 2 text

⾃⼰紹介 ⽵端 尚⼈ @n_takehata (Kotlin、Java) サーバーサイドエンジニア 2006.04 2007.12 2011.01 2014.04 公務員 SES サイバーエージェント アプリボット

Slide 3

Slide 3 text

会社紹介 株式会社アプリボット ■公式HP  https://www.applibot.co.jp/ スマートフォン向けゲームアプリの企画、制作、運⽤ 2010年7⽉サイバーエージェント⼦会社として設⽴ ■事業内容 ■技術ブログ https://blog.applibot.co.jp/

Slide 4

Slide 4 text

アプリボットのサービス スマートフォン向けゲームを中⼼に、 様々な事業領域にチャレンジしています。

Slide 5

Slide 5 text

ゲームの技術セット サーバー   ・Kotlin   ・Java クライアント  ・Unity  ・Cocos-2dx インフラ   ・AWS   ・GCP

Slide 6

Slide 6 text

ゲームの技術セット サーバー   ・Kotlin   ・Java クライアント  ・Unity  ・Cocos-2dx インフラ   ・AWS   ・GCP

Slide 7

Slide 7 text

今⽇は新規のゲームプロダクトで 使っているサーバーサイドKotlin についてお話します。

Slide 8

Slide 8 text

Kotlinとは? •  JetBrains社が開発したオブジェクト指 向⾔語 •  Androidの開発⾔語として有名 •  JVM⾔語の⼀つ

Slide 9

Slide 9 text

アジェンダ 1.なぜKotlinが最適なのか? 2.Kotlinの採⽤を決意するまで 3.Kotlin導⼊の流れ、問題点 4.今後の展望

Slide 10

Slide 10 text

アジェンダ 1.なぜKotlinが最適なのか? 2.Kotlinの採⽤を決意するまで 3.Kotlin導⼊の流れ、問題点 4.今後の展望

Slide 11

Slide 11 text

1 なぜKotlinが最適なのか?

Slide 12

Slide 12 text

前提 モバイルでのソーシャルゲームで 最適という意味です

Slide 13

Slide 13 text

ソーシャルゲーム開発で 重要な要件 •  突発的な大量トラフィックに耐えうる処理 性能 •  長期運用に備えた設計 •  不具合の軽減 1-①

Slide 14

Slide 14 text

ソーシャルゲーム開発で 重要な要件 •  突発的な大量トラフィックに耐えうる処理 性能 •  長期運用に備えた設計 •  不具合の軽減 1-①

Slide 15

Slide 15 text

突発的な大量トラフィックに耐えうる 処理性能 •  コンパイル言語であり、LL言語に比べて処 理性能が高い •  同規模のLL言語で開発したアプリに比べ、 サーバー台数が半分以下の場合もあった

Slide 16

Slide 16 text

ソーシャルゲーム開発で 重要な要件 •  突発的な大量トラフィックに耐えうる処理 性能 •  長期運用に備えた設計 •  不具合の軽減 1-①

Slide 17

Slide 17 text

長期運用に備えた設計 •  静的型付け言語であり、大人数での開発に 向いている •  長期運用で関わる人数、機能追加が増加し ても安定した設計をしやすい •  シンプルでモダンな実装コード

Slide 18

Slide 18 text

ソーシャルゲーム開発で 重要な要件 •  突発的な大量トラフィックに耐えうる処理 性能 •  長期運用に備えた設計 •  不具合の軽減 1-①

Slide 19

Slide 19 text

不具合の軽減 •  Null安全 •  var、valキーワードを使用したmutable、 immutableの明示 •  コンパイルで防げるエラーが多い、安全性 の高い言語仕様になっている

Slide 20

Slide 20 text

特に魅⼒的な機能

Slide 21

Slide 21 text

KotlinのNull安全 1-① var message: String = null コンパイルエラーになる var message: String? = null 変数の値にnullを設定できる

Slide 22

Slide 22 text

var user: User? = User() user.name Null許可型のオブジェクトに直接アクセスするとコンパイルエラー if (user != null) { user.name } Nullチェックをするとアクセスできるようになる

Slide 23

Slide 23 text

•  Nullチェック漏れによるNullPointerExcepJon の減少 •  デフォルトがNull不許可になっていることによ り、不要なNull値の扱いを減らせる •  コンパイルでNull関連の不備を指摘してくれる ため、コードレビューの工数を削減できる

Slide 24

Slide 24 text

とても安全な⾔語

Slide 25

Slide 25 text

•  コンパイル言語で、処理性能がLL言語に比べて高 い •  静的型付け言語であり、大人数での開発にも向い ている •  安全性の高い言語仕様 •  モダンな言語 まとめると・・・

Slide 26

Slide 26 text

ぜひKotlinを使いましょう!

Slide 27

Slide 27 text

アジェンダ 1.なぜKotlinが最適なのか? 2.Kotlinの採⽤を決意するまで 3.Kotlin導⼊の流れ、問題点 4.今後の展望

Slide 28

Slide 28 text

2 Kotlinの採⽤を決意するまで

Slide 29

Slide 29 text

①これまでの技術 ②Kotlinに決めた理由 ③Kotlinを採⽤をすべきか?

Slide 30

Slide 30 text

2-① これまでの技術

Slide 31

Slide 31 text

2-① A.R.T.という組織 •  Applibot Root Technologiesの略 •  横軸で技術を共有、共通化、標準化、  基盤化するための組織 •  詳しくはブログをご覧ください https://blog.applibot.co.jp/2018/04/05/ vision-for-art/

Slide 32

Slide 32 text

2-① Kotlin導⼊前の基本的な 技術セット •  Java •  Spring Framework •  MyBatis etc…

Slide 33

Slide 33 text

2-① Javaを使ってきた理由 •  コンパイル⾔語であり、処理性能がLL ⾔語に⽐べて⾼い •  静的型付け⾔語であり、⼤⼈数での  開発にも向いている •  歴史の⻑い⾔語なので事例や  ノウハウも多い

Slide 34

Slide 34 text

悩みも・・・ •  歴史の⻑い⾔語でもあるがゆえ、 レガシーな部分はある •  正直新しい技術触りたい

Slide 35

Slide 35 text

新しい⾔語へチャレンジしたい

Slide 36

Slide 36 text

Kotlinがいいんじゃないか?

Slide 37

Slide 37 text

2-② Kotlinに決めた理由

Slide 38

Slide 38 text

Kotlinを考え始めたきっかけ •  Androidの公式の開発⾔語として 採⽤された •  Spring5.0のKotlinサポート 2-② これ使えそうじゃない?となる

Slide 39

Slide 39 text

Kotlinを調べ始める

Slide 40

Slide 40 text

2-② 移⾏のメリット •  モダンな開発ができる •  Javaと同等の性能が維持できる •  移⾏コストが低く抑えられる

Slide 41

Slide 41 text

モダンな開発ができる •  コードがシンプル –  型推論 –  String Template –  プロパティ –  データクラス –  etc… •  安全 –  Null安全 –  val、var 2-②

Slide 42

Slide 42 text

Javaと同等の性能が維持できる •  Java、Scala、Groovy等と同じ  JVM⾔語 •  最終的にコンパイルされた  実⾏ファイルはJavaと  ほぼ同等になる 2-②

Slide 43

Slide 43 text

移⾏コストを低く抑えられる •  JetBrains社がJavaの資産を  活かすことを想定して開発した⾔語 •  Javaとの相互互換 •  Spring5.0のKotlinサポート 2-②

Slide 44

Slide 44 text

まとめると •  モダンな実装ができる •  Javaのメリットの多くがそのまま残り、  資産(ライブラリ等)も活⽤できる •  且つ移⾏コストも低く抑えられる 2-② (Javaを使っているシステムには) いいこと尽くし!

Slide 45

Slide 45 text

Kotlinに移⾏しよう!

Slide 46

Slide 46 text

2-③ Kotlinを採⽤すべきか

Slide 47

Slide 47 text

Javaを使ってたから採⽤できた?

Slide 48

Slide 48 text

Javaを使ったことがなかったら •  Javaからの移⾏じゃないと  メリット少ない? •  Kotlin使うにはJavaも覚えなきゃいけない? •  最初からKotlin使うのは微妙? 2-③

Slide 49

Slide 49 text

そんなことはありません!

Slide 50

Slide 50 text

•  コンパイル言語で、処理性能がLL言語に比べて高 い •  静的型付け言語であり、大人数での開発にも向い ている •  安全性の高い言語仕様 •  モダンな言語 Javaは関係ありません! 先程のKotlinのメリット

Slide 51

Slide 51 text

新しい技術にありがちな問題 2-③ •  事例、情報が少ない    •  ライブラリ等が整っていない    •  技術者がいない   

Slide 52

Slide 52 text

Kotlinなら 2-③ •  事例、情報が少ない   →Javaの(⼤量にある)情報が役⽴つ •  ライブラリ等が整っていない   →Javaのライブラリ、フレームワークが使える •  技術者がいない   →Javaの技術者ならすぐに馴染める

Slide 53

Slide 53 text

盤⽯な歴史のあるJavaから ⼤きな改善を加え 誕⽣したモダンな⾔語!

Slide 54

Slide 54 text

ぜひKotlinを使いましょう!

Slide 55

Slide 55 text

アジェンダ 1.なぜKotlinが最適なのか? 2.Kotlinの採⽤を決意するまで 3.Kotlin導⼊の流れ、問題点 4.今後の展望

Slide 56

Slide 56 text

3 Kotlin導⼊の流れ、問題点

Slide 57

Slide 57 text

3 導⼊までにやったこと ①Kotlinの調査 ②サードパーティライブラリの検証 ③既存のJavaコードの変換 ④Kotlin最適化

Slide 58

Slide 58 text

3-① Kotlinの調査

Slide 59

Slide 59 text

3-① 検証項⽬ •  Kotlinの特徴 •  将来性 •  移⾏コスト •  性能

Slide 60

Slide 60 text

先程全て話したので省略します

Slide 61

Slide 61 text

3-② サードパーティライブラリ の検証

Slide 62

Slide 62 text

※2018年1⽉当時の検証結果です

Slide 63

Slide 63 text

•  O/Rマッパー •  テストフレームワーク

Slide 64

Slide 64 text

O/Rマッパーの検証

Slide 65

Slide 65 text

検証対象 •  MyBatis •  DOMA2 •  Exposed 3-②

Slide 66

Slide 66 text

検証項⽬ •  Kotlinでの挙動 •  分かりやすさ •  カスタムクエリの実装⽅法 •  ⾃動⽣成ツールの性能 •  将来性 •  情報量 実際にKotlinでCRUDを作成して検証する 3-②

Slide 67

Slide 67 text

検証対象 •  MyBatis •  DOMA2 •  Exposed 3-②

Slide 68

Slide 68 text

MyBatis •  Javaの代表的なO/Rマッパーの⼀つ •  XML等でクエリを記述し、  変数や分岐を埋め込める •  Entityクラス等の⾃動⽣成ツールも  ⽤意されている 3-②

Slide 69

Slide 69 text

検証結果 •  全てKotlin化しても  基本的な部分は動いた •  ⾃動⽣成ツールはKotlinに  対応していない 3-②

Slide 70

Slide 70 text

検証対象 •  MyBatis •  DOMA2 •  Exposed 3-②

Slide 71

Slide 71 text

DOMA2 •  Javaの代表的なO/Rマッパーの⼀つ •  クエリはsqlファイルで記述し、  ⽂中に変数や分岐を埋め込める 3-② •  もともとSeaserグループで作られており、 ⽇本語のドキュメントが豊富

Slide 72

Slide 72 text

検証結果 •  全てKotlin化して使⽤するとエラーが発⽣ •  Kotlin使われている事例はあったが、 DOMA2の部分はJavaで書いている •  公式ではKotlinを”実験的に”サポート  している 3-②

Slide 73

Slide 73 text

検証対象 •  MyBatis •  DOMA2 •  Exposed 3-②

Slide 74

Slide 74 text

Exposed •  JetBrains製のO/Rマッパー •  Kotlinで作られている •  DSLで直感的に記述できる 3-②

Slide 75

Slide 75 text

検証結果 •  まだ正式版になっていない(0.x系) •  全体的にエコシステムが整っていない •  利⽤実績もほぼない 3-②

Slide 76

Slide 76 text

MyBatisを採⽤ •  Kotlinで使⽤しても基本的な挙動は  問題なかった •  ⾃動⽣成の部分はJavaのまま使う (もともと触らない箇所なので) ・既存の基盤でも使⽤していた  →他のものに乗り換える程のメリットがなかった 3-②

Slide 77

Slide 77 text

テストフレームワークの検証

Slide 78

Slide 78 text

検証対象 •  JUnit •  Spek •  KotlinTest 3-②

Slide 79

Slide 79 text

検証対象 •  JUnit •  Spek •  KotlinTest 3-②

Slide 80

Slide 80 text

JUnit •  Javaの最もメジャーな  テストフレームワーク •  KotlinでもJavaと同様に  使⽤することが可能 3-②

Slide 81

Slide 81 text

・基本はJavaで書いた時と同じ構成で、同じように動く ・ JMockit(モック化ライブラリ)、AssertJ(検証処理)を  併せて使⽤ class SampleServiceTest {

Slide 82

Slide 82 text

Kotlin製も試してみよう

Slide 83

Slide 83 text

検証対象 •  JUnit •  Spek •  KotlinTest 3-②

Slide 84

Slide 84 text

Spek •  Kotlin製のテストフレームワーク •  ⾮常にシンプルなテストフレームワーク •  BDDフレームワークとしても使える 3-②

Slide 85

Slide 85 text

・シンプルで分かりやすい構⽂ ・基本的にgiven、on、itの3種類のブロックで構成するため、  実装者による差分が⽣まれづらい class SampleServiceTest: Spek({

Slide 86

Slide 86 text

SpringのDIができない・・・

Slide 87

Slide 87 text

検証対象 •  JUnit •  Spek •  KotlinTest 3-②

Slide 88

Slide 88 text

KotlinTest •  Kotlin製のテストフレームワーク •  多機能なフレームワーク •  StringSpec, FunSpec, ShouldSpec, WordSpec, FeatureSpec, BehaviorSpec 等、様々な書き⽅がサポートされている 3-②

Slide 89

Slide 89 text

・Spekと同様、基本的に3種類のブロックで構成するため  実装者による差分が⽣まれづらい ・上記はBehaviorSpecの例 class SampleServiceTest: BehaviorSpec() {

Slide 90

Slide 90 text

SpringのDIができない・・・

Slide 91

Slide 91 text

⼀旦JUnitを採⽤ •  既存の基盤をそのまま使える •  実績、情報ともに⼗分 •  Kotlin製のものの導⼊は  アップデートを待つ 3-②

Slide 92

Slide 92 text

と、いうのが検証当時 (2018年1⽉)

Slide 93

Slide 93 text

KotlinTestがSpringのDIに対応! 3-②

Slide 94

Slide 94 text

KotlinTestを採⽤

Slide 95

Slide 95 text

KotlinTestを採⽤ 3-② •  多機能な上、それによる煩雑さは  感じない •  パラメーターテストが便利 •  公式ドキュメントが充実

Slide 96

Slide 96 text

StringSpecを使⽤ 3-② ・シンプルな構⽂ ・開発者が推奨している形式 class SampleServiceTest: StringSpec() {

Slide 97

Slide 97 text

forallを使⽤したパラメータテストが便利! class SampleServiceTest: StringSpec() {

Slide 98

Slide 98 text

KotlinTestおすすめです

Slide 99

Slide 99 text

ここまでが検証の話

Slide 100

Slide 100 text

3-③ 既存のJavaコードの変換

Slide 101

Slide 101 text

既存の基盤 3-③ •  あくまでSpringをベースに  実装を共通化したもの •  ⾃社製フレームワークではない •  スターターキットとして  リポジトリを⽤意し、  各プロダクトがForkして開発する

Slide 102

Slide 102 text

①IntelliJのConvert Java File to Kotlin File  の機能で全てのJavaファイルを変換 ②変換後に出ていたコンパイルエラー  を全て解消 ③実⾏し、発⽣したエラーを全て解消 ⼀旦は「Javaの時と同じように動かす」ことが最優先

Slide 103

Slide 103 text

Kotlin化しない箇所 3-③ •  MyBatisの⾃動⽣成コード •  ライブラリ、フレームワーク部分 ⼿動で触らない部分はJavaのまま扱う

Slide 104

Slide 104 text

ここからが⻑い

Slide 105

Slide 105 text

•  IntelliJの変換の精度は⾼くはない •  ⼀⾒すると正しいように⾒える •  似た構⽂でも微妙に違うことがある

Slide 106

Slide 106 text

変換時にハマった差分を 紹介します

Slide 107

Slide 107 text

•  拡張プロパティ •  コンパニオンオブジェクト、@JvmStaic •  SAM変換

Slide 108

Slide 108 text

拡張プロパティ 3-③ Kotlinはクラスにプロパティを定義すると、 内部的にアクセサメソッドを⽣成する class User { var name: String = "" } val user = User() user.name = "takehata" user.name プロパティの値はアクセサメソッド経由で呼ばれる

Slide 109

Slide 109 text

・プロパティに拡張機能を持たせられる ・上記はtotalScoreがscore1とscore2の  合計値を返却する例 val score1 = 0 val score2 = 0 val totalScore get() = score1 + score2

Slide 110

Slide 110 text

public String getMessage() { UserService userService = new UserService(); return userService.createMessage(); } val message: String get() { val userService = UserService() return userService.createMessage() } ・get〜という名前の引数なしメソッドが  全てプロパティとして認識されてしまう ・なぜメソッドがプロパティに・・・と⼾惑う Java Kotlin

Slide 111

Slide 111 text

コンパニオンオブジェクト 3-③ Javaから呼び出そうとすると、Companionというオブジェクトを 経由しなくてはならなくなる @JVMStatic companion object { fun execute() { // ・・・
 } } Sample.Companion.execute(); Java Kotlin staticな関数を定義したい時、コンパニオンオブジェクトを使う

Slide 112

Slide 112 text

Sample.execute(); もともとはこう呼んでいて、コンパイルエラーになる companion object { @JvmStaJc fun execute() { // ・・・
 } } @JvmStaticを付けるとJavaと同じように呼び出せる

Slide 113

Slide 113 text

Javaからの呼び出しを考慮する際に 使えるアノテーション •  @file:JvmName •  @JvmStaJc •  @JvmOverloads

Slide 114

Slide 114 text

SAM変換 3-③ 関数型インターフェースを引数に取るメソッドには、 ラムダ式を渡すことができる public interface FuncJonSample { public Integer calc(Integer num1, Integer num2); } public Integer callFuncJon(FuncJonSample funcJon) { return funcJon.calc(1, 2); } 関数型インターフェース(単⼀メソッドのインターフェース) 関数型インターフェースを引数に取るメソッド callFuncJon((Integer num1, Integer num2) -> { return num1 + num2; });

Slide 115

Slide 115 text

callFuncJon { num1: Int, num2: Int -> num1 + num2 } ・Kotlinでも同様に呼び出すことができる ・Javaの関数型インターフェースを  Kotlinの関数型として扱ってくれる(これがSAM変換)

Slide 116

Slide 116 text

・Kotlin同⼠ではSAM変換が効かず、匿名オブジェクトを  明⽰的に書いて渡さなければならない ・関数によってラムダ式で渡せるものと渡せないものがあり、  なぜ・・・となった fun callFuncJon(funcJon: FuncJonSample): Int? { return funcJon.calc(1, 2) } 呼び出されるメソッドもKotlin化してしまうと・・・ callFuncJon(object : FuncJonSample { override fun calc(num1: Int, num2: Int): Int { return num1 + num2 } })

Slide 117

Slide 117 text

コードの変換は完了

Slide 118

Slide 118 text

3-④ Kotlin最適化

Slide 119

Slide 119 text

•  ⾼階関数 •  TypeAlias •  強制アンラップ

Slide 120

Slide 120 text

⾼階関数 3-④ •  関数型を引数や戻り値に取る関数のこと •  Kotlinでは関数型インターフェースが  存在せず、こちらが通常の形

Slide 121

Slide 121 text

⾼階関数を使わない場合 3-④ fun callFuncJon(funcJon: FuncJonSample): Int? { return funcJon.calc(1, 2) } callFuncJon(object : FuncJonSample { override fun calc(num1: Int, num2: Int): Int { return num1 + num2 } }) 匿名オブジェクトを明⽰的に書いている

Slide 122

Slide 122 text

⾼階関数を使う場合 3-④ ・ラムダ式で渡すと、匿名オブジェクトのインスタンスが  呼び出し間で再利⽤される fun callFuncJon(funcJon: (Int, Int) -> Int): Int? { return funcJon(1, 2) } callFuncJon { num1: Int, num2: Int -> num1 + num2 } ・ラムダ式を渡して呼び出せるようになる

Slide 123

Slide 123 text

Type Alias •  関数リテラルに  名前を付けることができる機能 •  付けた名前で関数の引数の型として指定 することが可能 •  ⾼階関数をよりシンプルにできる 3-④

Slide 124

Slide 124 text

通常の⾼階関数のコード 3-④ fun callFuncJon(funcJon: (Int, Int) -> Int): Int? { return funcJon(1, 2) } 引数の型に関数リテラルを書いている

Slide 125

Slide 125 text

Type Ailiasを使ったコード 3-④ typealias FuncJonSample = (Int, Int) -> Int fun callFuncJon(funcJon: FuncJonSample): Int? { return funcJon(1, 2) } ・名前を付けることで、関数リテラルの意味が分かりやすくなる ・同じ関数リテラルを複数書く必要がなくなる callFuncJon { num1: Int, num2: Int -> num1 + num2 }

Slide 126

Slide 126 text

強制アンラップ •  Null許可型の変数を強制的に  Null不許可型に変換する 3-④ •  Nullが⼊っていた場合は NullPointerExceptionが発⽣する

Slide 127

Slide 127 text

KotlinのNull安全 3-④ var user: User? = User() user.name Kotlinは型に?を付けるとNullを⼊れられるようになる Null許可型のオブジェクトに直接アクセスするとコンパイルエラー if (user != null) { user.name } Nullチェックをするとアクセスできるようになる

Slide 128

Slide 128 text

!!を付けると強制的にNull不許可型に変換する user!!.name

Slide 129

Slide 129 text

fun createUser(): User? { // ・・・
 } val user = createUser() user!!.name JavaはデフォルトでNull許可型なので、変換すると?が付く Nullチェックをしていない変数の呼び出しは、!!を付けて変換される fun createUser(): User { // ・・・
 } val user = createUser() user.name Null不許可型にして、!!も排除する

Slide 130

Slide 130 text

Kotlin基盤が(⼀旦)完成しました

Slide 131

Slide 131 text

Kotlin化してみてどうか? •  随所でシンプルにコードが書けるようになった   →コード⾏数が2割減 •  コンパイルで検出してくれるエラーが多く、 より堅牢なコードが書けるようになった •  Javaエンジニアでもすんなり書き始められた 3-④

Slide 132

Slide 132 text

Kotlin化して良かった!

Slide 133

Slide 133 text

アジェンダ 1.なぜKotlinが最適なのか? 2.Kotlinの採⽤を決意するまで 3.Kotlin導⼊の流れ、問題点 4.今後の展望

Slide 134

Slide 134 text

今後の展望 4

Slide 135

Slide 135 text

マイクロサービス 4-1 •  Kotlinでのマイクロサービスの実装 •  課⾦、認証等共通の機構をマイクロ サービス化する

Slide 136

Slide 136 text

gRPC対応 4-2 •  マイクロサービスを  Kotlin × gRPCで実装 •  Unity、サーバーKotlin間の  通信での使⽤ •  HTTP/2を標準でサポートした Google製のRPCフレームワーク

Slide 137

Slide 137 text

OSS化 4-3 •  マイクロサービス化した機構も  公開を検討中 •  作成したライブラリ等の  OSS化をする予定

Slide 138

Slide 138 text

プロダクトのリリース

Slide 139

Slide 139 text

・UNLIMITED STUDiOと協⼒して開発中 ・本⽇話した内容を全て搭載したプロダクト ・遠くない未来にリリース(予定) 鋭意開発中

Slide 140

Slide 140 text

最後に

Slide 141

Slide 141 text

•  モバイルゲーム開発において、Kotlin は有⼒な選択肢 •  Javaの資産があれば最⼤限活⽤できる •  アプリボットは今後もサーバーサイ ドKotlinに⼒を⼊れていきます

Slide 142

Slide 142 text

ぜひKotlinを使いましょう!

Slide 143

Slide 143 text

ご清聴ありがとうございました