Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DMM.comの課金プラットフォームにおけるサーバーサイドKotlin事情

 DMM.comの課金プラットフォームにおけるサーバーサイドKotlin事情

現在、DMM.comは40を超えるサービスを展開しております。
私たち課金プラットフォームは、様々なサービスで利用できる決済手段などを提供しています。
課金プラットフォームには元々Javaエンジニアが多く在籍していますが、最近はプロダクトにKotlinを積極的に採用しています。
本セッションでは、なぜKotlinを採用したのか?また、実際に複数プロダクトで採用した経験からJavaと比較したメリット・デメリットについてご紹介します。
プロダクトにKotlinを採用するための、判断材料となる情報を提供できればと思います。

https://event.shoeisha.jp/devboost/20181215/session/1894/

kashiwaguma-hiro

December 15, 2018
Tweet

More Decks by kashiwaguma-hiro

Other Decks in Technology

Transcript

  1. © DMM.com 自己紹介 北澤 由貴(Kitazawa Yoshitaka) 合同会社DMM.com ペイメントサービス部 2 柏熊

    宏幸(Kashiwaguma Hiroyuki) 合同会社DMM.com ペイメントサービス部
  2. © DMM.com • 新しいことがやってみたい! • 理あにかなってさえいれば技術選定は自由 • Javaの資産を再利用、コード量の削減も見込める • 今後長く使える言語

    • Android公式言語に採用された • 企業(JetBrains社)でメンテナンスされている • 開発終了し、メンテナンスされなくなる可能性は低い • Javaエンジニアにとって習得が容易 • 過半数がJavaエンジニア • Javaとの互換性がある、IDEでJava→Kotlin変換 なぜKotlinを採用したのか 10
  3. © DMM.com • 新しいことがやってみたい! • 理にかなってさえいれば技術選定は自由 • Javaの資産を再利用、コード量の削減も見込める • 今後長く使える言語

    • Android公式言語に採用された • 企業(JetBrains社)でメンテナンスされている • 開発終了し、メンテナンスされなくなる可能性は低い • Javaエンジニアにとって習得が容易 • 過半数がJavaエンジニア • Javaとの互換性がある、IDEでJava→Kotlin変換 なぜKotlinを採用したのか 11
  4. © DMM.com • 新しいことがやってみたい! • 理にかなってさえいれば技術選定は自由 • Javaの資産を再利用、コード量の削減も見込める • 今後長く使える言語

    • Android公式言語に採用された • JetBrains社でメンテナンスされている • 開発終了し、メンテナンスされなくなる可能性は低い • Javaエンジニアにとって習得が容易 • 過半数がJavaエンジニア • Javaとの互換性がある、IDEでJava→Kotlin変換 なぜKotlinを採用したのか 12
  5. © DMM.com • 新しいことがやってみたい! • 理にかなってさえいれば技術選定は自由 • Javaの資産を再利用、コード量の削減も見込める • 今後長く使える言語

    • Android公式言語に採用された • JetBrains社でメンテナンスされている • 開発終了し、メンテナンスされなくなる可能性は低い • Javaエンジニアにとって習得が容易 • 過半数がJavaエンジニア • Javaとの互換性があり、IDEでのJava→Kotlin変換も可能 なぜKotlinを採用したのか 13
  6. © DMM.com ①DataClass 16 public class User { private String

    name; private int age; public void setName(String name) { this.name = name; } ~~~ 略 ~~~ @Override public boolean equals(Object o) { ~~~ 略 ~~~ @Override public String toString() { return "User{name='"+name+'\''+",age="+age +'}'; } } data class User(var name: String, var age: Int) Kotlin Java • データ格納クラスを、簡単かつコード量少なく実装できる
  7. © DMM.com ①DataClass • データ格納クラスを、簡単かつコード量少なく実装できる 17 public class User {

    private String name; private int age; public void setName(String name) { this.name = name; } ~~~ 略 ~~~ @Override public boolean equals(Object o) { ~~~ 略 ~~~ @Override public String toString() { return "User{name='"+name+'\''+",age="+age +'}'; } } data class User(var name: String, var age: Int) Kotlin Java getter/ setter equalsなど実装が必要
  8. © DMM.com ①DataClass • データ格納クラスを、簡単かつコード量少なく実装できる 18 public class User {

    private String name; private int age; public void setName(String name) { this.name = name; } ~~~ 略 ~~~ @Override public boolean equals(Object o) { ~~~ 略 ~~~ @Override public String toString() { return "User{name='"+name+'\''+",age="+age +'}'; } } data class User(var name: String, var age: Int) Kotlin Java getter/ setter equalsなど実装が必要 dataクラスにすることで 同じ効果!
  9. © DMM.com ①DataClass • データ格納クラスを、簡単かつコード量少なく実装できる 19 public class User {

    private String name; private int age; public void setName(String name) { this.name = name; } ~~~ 略 ~~~ @Override public boolean equals(Object o) { ~~~ 略 ~~~ @Override public String toString() { return "User{name='"+name+'\''+",age="+age +'}'; } } data class User(var name: String, var age: Int) Kotlin Java getter/ setter equalsなど実装が必要 dataクラスにすることで 同じ効果! Java : 51行     ↓ Kotlin: 1行!
  10. © DMM.com ②raw string • コード上でSQLやJSONを表記したいとき、見やすく表現できる 20 @Query("SELECT" + "

    * " + "FROM" + " history_detail" + "WHERE" + "transaction_id = :transaction_id" + "AND valid_flag = 1", nativeQuery = true) public List<HistoryDetail> findByTransactionId(@Param("transaction_id") String transactionId); @Query(""" SELECT * FROM history_detail WHERE transaction_id = :transaction_id AND valid_flag = 1 """, nativeQuery = true) fun findByTransactionId(@Param("transaction_id") transactionId: String): List<HistoryDetail> Kotlin Java
  11. © DMM.com ②raw string • コード上でSQLやJSONを表記したいとき、見やすく表現できる 21 @Query("SELECT" + "

    * " + "FROM" + " history_detail" + "WHERE" + "transaction_id = :transaction_id" + "AND valid_flag = 1", nativeQuery = true) public List<HistoryDetail> findByTransactionId(@Param("transaction_id") String transactionId); @Query(""" SELECT * FROM history_detail WHERE transaction_id = :transaction_id AND valid_flag = 1 """, nativeQuery = true) fun findByTransactionId(@Param("transaction_id") transactionId: String): List<HistoryDetail> Kotlin Java Javaは改行すると見づらい Kotlinはraw stringで改行表現できる
  12. © DMM.com ②raw string • コード上でSQLやJSONを表記したいとき、見やすく表現できる 22 @Query("SELECT" + "

    * " + "FROM" + " history_detail" + "WHERE" + "transaction_id = :transaction_id" + "AND valid_flag = 1", nativeQuery = true) public List<HistoryDetail> findByTransactionId(@Param("transaction_id") String transactionId); @Query(""" SELECT * FROM history_detail WHERE transaction_id = :transaction_id AND valid_flag = 1 """, nativeQuery = true) fun findByTransactionId(@Param("transaction_id") transactionId: String): List<HistoryDetail> Kotlin Java 実はスペース開け忘れ! 実行時エラー! Javaは改行すると見づらい Kotlinはraw stringで改行表現できる
  13. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 23 Kotlin var str

    :String = nullReturnMethod() print( str.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() );
  14. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 24 Kotlin Javaは実行時エラー!(NullPointerException) Kotlinはコンパイル時エラー!(変数代入時に)

    var str :String = nullReturnMethod() print( str.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() );
  15. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 25 Kotlin var str

    :String? = nullReturnMethod() print( str.toLowerCase() ) nullが入る可能性がある場合、変数に「?」を付与 Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() );
  16. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 26 Kotlin var str

    :String? = nullReturnMethod() print( str.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() ); すると、変数参照時にコンパイルエラーに変わる
  17. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 27 Kotlin var str

    :String? = nullReturnMethod() print( str?.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() ); コンパイルエラーを解消するには、 safe call演算子を使う(null以外のみ関数実行)
  18. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 28 Kotlin var str

    :String? = nullReturnMethod() print( str?.toLowerCase() ?: "default" ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() ); また、エルビス演算子を使うことで、 nullの場合のデフォルト値を指定可能
  19. © DMM.com • NullPointerExceptionを、コンパイル時に防げる! var str :String? = nullReturnMethod() print(

    str?.toLowerCase() ?: "default" ) ③null safety 29 Kotlin ちなみに、Javaでも同じようなコードを実現できるが コード量が多くつらい... Java Optional<String> str = Optional.ofNullable(nullReturnMethod()); str.ifPresentOrElse( s->System.out.print( str.toLowerCase() ), ()->System.out.print("default") );
  20. © DMM.com 32 そもそもラムダ式とは? • 無名関数の記述方法のひとつ • Java8からラムダ式で簡潔に書ける public class

    Main { public static void main(String[] args) { Runnable r = new Runnable(){ @Override public void run() { System.out.println("Hello world."); } }; r.run(); } } public class Main { public static void main(String[] args) { Runnable r = () -> { System.out.println("Hello world."); }; r.run(); } } Java 無名関数 Java ラムダ式
  21. © DMM.com 33 そもそもラムダ式とは? • 無名関数の記述方法のひとつ • Java8からラムダ式で簡潔に書ける public class

    Main { public static void main(String[] args) { Runnable r = new Runnable(){ @Override public void run() { System.out.println("Hello world."); } }; r.run(); } } public class Main { public static void main(String[] args) { Runnable r = () -> { System.out.println("Hello world."); }; r.run(); } } Java 無名関数 Java ラムダ式 無名関数だと本質的でない コードが多い
  22. © DMM.com 34 そもそもラムダ式とは? • 無名関数の記述方法のひとつ • Java8からラムダ式で簡潔に書ける public class

    Main { public static void main(String[] args) { Runnable r = new Runnable(){ @Override public void run() { System.out.println("Hello world."); } }; r.run(); } } public class Main { public static void main(String[] args) { Runnable r = () -> { System.out.println("Hello world."); }; r.run(); } } Java 無名関数 Java ラムダ式 newとrun()のオーバライドを省略 無名関数だと本質的でない コードが多い
  23. © DMM.com 35 ラムダ式をつかった例を比較 public class Main { public static

    void main(String[] args) { Runnable r = () -> { System.out.println("Hello world."); }; r.run(); } } public class Main { fun main(args: Array<String>) { val r = { print("Hello world.") } r() } } • Kotlinでもラムダ式を利用できる • JavaもKotlinも、この状態だと大差ない Kotlin Java
  24. © DMM.com 36 例1. Intを2つ受け取って演算 • JavaではBiFunctionという関数型インタフェースを使用 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java
  25. © DMM.com 37 例1. Intを2つ受け取って演算 • JavaではBiFunctionという関数型インタフェースを使用 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java Integerを2つ受け取って 足し算結果を返す
  26. © DMM.com 38 例1. Intを2つ受け取って演算 • JavaではBiFunctionという関数型インタフェースを使用 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java 型名はBiFunction Integerを2つ受け取って 足し算結果を返す
  27. © DMM.com • JavaではBiFunctionという関数型インタフェースを使用 39 例1. Intを2つ受け取って演算 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java 型名はBiFunction Integerを2つ受け取って 足し算結果を返す リファレンスが必要 覚えるのはツライ
  28. © DMM.com • JavaではBiFunctionという関数型インタフェースを使用 40 例1. Intを2つ受け取って演算 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java 型名はBiFunction Integerを2つ受け取って 足し算結果を返す リファレンスが必要 覚えるのはツライ Kotlinは関数自体が オブジェクト
  29. © DMM.com • JavaではBiFunctionという関数型インタフェースを使用 41 例1. Intを2つ受け取って演算 public class Main

    { fun main(args: Array<String>) { val op = { a:Int, b:Int -> a+b} calc(1,2,op) // 3 } fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int { return op(a, b) } } public class Main { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> op = (a, b) -> a+b; calc(1, 2, op); // 3 } public static Integer calc(Integer a, Integer b,      BiFunction<Integer, Integer, Integer> op){ return op.apply(a,b); } } Kotlin Java 型名はBiFunction Integerを2つ受け取って 足し算結果を返す リファレンスが必要 覚えるのはツライ 定義したラムダ式の型 を指定 Kotlinは関数自体が オブジェクト
  30. © DMM.com 42 例2. Intを3つ受け取って演算 fun main(args: Array<String>) { val

    op = {a:Int,b:Int,c:Int -> a+b+c} calc(1,2,3,op) // 6 } fun calc(a: Int, b: Int, c: Int, op: (Int, Int, Int) -> Int): Int { return op(a, b, c) } public static void main(String[] args) { TriFunction<Integer, Integer, Integer, Integer> op = (a, b, c) -> a+b+c; calc(1, 2, 3, op); //6 } public static Integer calc(Integer a,Integer b,Integer c,   TriFunction<Integer, Integer, Integer, Integer> op){ return op.apply(a,b,c); } @FunctionalInterface interface TriFunction<A, B, C, R>{ R apply(A a, B b, C c); } • Javaでは3つ受け取る関数型インタフェース定義が必要 Kotlin Java
  31. © DMM.com 43 例2. Intを3つ受け取って演算 fun main(args: Array<String>) { val

    op = {a:Int,b:Int,c:Int -> a+b+c} calc(1,2,3,op) // 6 } fun calc(a: Int, b: Int, c: Int, op: (Int, Int, Int) -> Int): Int { return op(a, b, c) } public static void main(String[] args) { TriFunction<Integer, Integer, Integer, Integer> op = (a, b, c) -> a+b+c; calc(1, 2, 3, op); //6 } public static Integer calc(Integer a,Integer b,Integer c,   TriFunction<Integer, Integer, Integer, Integer> op){ return op.apply(a,b,c); } @FunctionalInterface interface TriFunction<A, B, C, R>{ R apply(A a, B b, C c); } • Javaでは3つ受け取る関数型インタフェース定義が必要 Kotlin Java TriFunctionを自作
  32. © DMM.com 44 例2. Intを3つ受け取って演算 fun main(args: Array<String>) { val

    op = {a:Int,b:Int,c:Int -> a+b+c} calc(1,2,3,op) // 6 } fun calc(a: Int, b: Int, c: Int, op: (Int, Int, Int) -> Int): Int { return op(a, b, c) } public static void main(String[] args) { TriFunction<Integer, Integer, Integer, Integer> op = (a, b, c) -> a+b+c; calc(1, 2, 3, op); //6 } public static Integer calc(Integer a,Integer b,Integer c,   TriFunction<Integer, Integer, Integer, Integer> op){ return op.apply(a,b,c); } @FunctionalInterface interface TriFunction<A, B, C, R>{ R apply(A a, B b, C c); } • Javaでは3つ受け取る関数型インタフェース定義が必要 Kotlin Java TriFunctionを自作 定義したラムダ式の型 を指定 Kotlinは関数自体が オブジェクト
  33. © DMM.com 改めてまとめると • Java • 関数型インタフェースを使用する必要がある • 用意されていない場合は自身で関数型インタフェースの定義が必要 •

    Kotlin • 言語として、第一級関数をサポート • 関数自体をオブジェクトとして扱うことが可能 • 関数を定義する際にインタフェース/クラス定義は不要 45
  34. © DMM.com Nullを扱うライブラリと相性が良くない • テストコードでMockitoを利用して一部の挙動をMock化したい • Mockito.any()はnullをreturnする • Kotlinではnullを許容していない場合例外になる 47

    class HistoryServiceTest { @Mock lateinit var historyRepository: HistoryRepository @Test fun searchTest() { Mockito.doReturn(History("T001", ~)) .`when`(historyRepository).findByTransactionId(Mockito.any()) } } Kotlin java.lang.IllegalStateException: Mockito.any() must not be null
  35. © DMM.com Nullを扱うライブラリと相性が良くない 48 Kotlin • テストコードでMockitoを利用して一部の挙動をMock化したい • Mockito.any()はnullをreturnする •

    Kotlinではnullを許容していない場合例外になる class HistoryServiceTest { @Mock lateinit var historyRepository: HistoryRepository @Test fun searchTest() { Mockito.doReturn(History("T001", ~)) .`when`(historyRepository).findByTransactionId(com.nhaarman.mockito_kotlin.any()) } } nullを考慮したライブラリで 回避可能
  36. © DMM.com 課金プラットフォームの言語比率 51 PHP : 58% Java : 25%

    DMMポイントを操作する APIの例をご紹介 Python : 4% ※ 課金プラットフォームの24プロダクトの集計結果 Kotlin : 13%
  37. © DMM.com ポイントAPIリプレイスの実績 • 全部で11種類のAPIを作成 • 開発メンバーは4名 • ビジネスロジックの開発期間は2ヶ月 59

    全員Kotlin未経験 Javaができるエンジニア Kotlinの学習コストが低いので 短い期間で成果が出せる
  38. © DMM.com 実績のまとめ 63 • コード量が減って可読性UP • レガシーなミドルウェアを撤去 • Kotlinは学習コストが低いので短期間で成果を出せる

    サーバサイドKotlinは月間70億円を売り上げる DMM.comの大規模課金プラットフォームを支えている! リードタイムの短縮!