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を採用するための、判断材料となる情報を提供できればと思います。

このスライドはDevelopers Boostで登壇した際に使用した資料です。
https://event.shoeisha.jp/devboost/20181215/session/1894/

Caf10dd44bc2926dddb166ea4db6823f?s=128

KitazawaYoshitaka

December 15, 2018
Tweet

More Decks by KitazawaYoshitaka

Other Decks in Programming

Transcript

  1. © DMM.com DMM.comの 課金プラットフォームにおける サーバサイドKotlin事情 DMM.com 北澤由貴 / 柏熊宏幸 2018/12/15

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

    宏幸(Kashiwaguma Hiroyuki) 合同会社DMM.com ペイメントサービス部
  3. © DMM.com アジェンダ • DMM.comについてのご紹介 • DMM.comの課金プラットフォームでKotlinを採用した理由 • 使っていく中で気づいた良い点・使いにくい点 •

    課金プラットフォームのKotlin利用実績 • まとめ 3
  4. © DMM.com DMM.comについての ご紹介 4

  5. © DMM.com DMM.comについて 40以上のサービスを展開するサービスサイト 5

  6. © DMM.com DMM.comについて 6 ※DMM.com、DMM GAMES、DMM.com 証券、DMM.com OVERRIDE、DMM.com Base、他連結 ※DMM.com

    サービスの会員数
  7. © DMM.com DMM.comの課金プラットフォームについて 7

  8. © DMM.com DMM.comの課金プラットフォームについて 8 APIにKotlin つかってるで!

  9. © DMM.com DMM.comの 課金プラットフォームで Kotlinを採用した理由 9

  10. © DMM.com • 新しいことがやってみたい! • 理あにかなってさえいれば技術選定は自由 • Javaの資産を再利用、コード量の削減も見込める • 今後長く使える言語

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

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

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

    • Android公式言語に採用された • JetBrains社でメンテナンスされている • 開発終了し、メンテナンスされなくなる可能性は低い • Javaエンジニアにとって習得が容易 • 過半数がJavaエンジニア • Javaとの互換性があり、IDEでのJava→Kotlin変換も可能 なぜKotlinを採用したのか 13
  14. © DMM.com 使っていく中で気づいた 良い点・使いにくい点 14

  15. © DMM.com Javaと比べて ちょっと便利なところ3つ 15

  16. © 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 • データ格納クラスを、簡単かつコード量少なく実装できる
  17. © 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など実装が必要
  18. © 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クラスにすることで 同じ効果!
  19. © 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行!
  20. © 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
  21. © 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で改行表現できる
  22. © 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で改行表現できる
  23. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 23 Kotlin var str

    :String = nullReturnMethod() print( str.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() );
  24. © 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() );
  25. © DMM.com ③null safety • NullPointerExceptionを、コンパイル時に防げる! 25 Kotlin var str

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

    :String? = nullReturnMethod() print( str.toLowerCase() ) Java String str = nullReturnMethod(); System.out.print( str.toLowerCase() ); すると、変数参照時にコンパイルエラーに変わる
  27. © 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以外のみ関数実行)
  28. © 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の場合のデフォルト値を指定可能
  29. © 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") );
  30. © DMM.com • コードが簡潔に、短く書ける! • コードの表現力が上がった! • Null safetyで、早い段階からバグを防げる! ちょっと便利なところ3つのまとめ

    30
  31. © DMM.com Javaと比べて ラムダ式が扱いやすい 31

  32. © 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 ラムダ式
  33. © 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 ラムダ式 無名関数だと本質的でない コードが多い
  34. © 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()のオーバライドを省略 無名関数だと本質的でない コードが多い
  35. © 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
  36. © 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
  37. © 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つ受け取って 足し算結果を返す
  38. © 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つ受け取って 足し算結果を返す
  39. © 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つ受け取って 足し算結果を返す リファレンスが必要 覚えるのはツライ
  40. © 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は関数自体が オブジェクト
  41. © 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は関数自体が オブジェクト
  42. © 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
  43. © 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を自作
  44. © 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は関数自体が オブジェクト
  45. © DMM.com 改めてまとめると • Java • 関数型インタフェースを使用する必要がある • 用意されていない場合は自身で関数型インタフェースの定義が必要 •

    Kotlin • 言語として、第一級関数をサポート • 関数自体をオブジェクトとして扱うことが可能 • 関数を定義する際にインタフェース/クラス定義は不要 45
  46. © DMM.com Javaとの互換性で 使いにくい点 46

  47. © 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
  48. © 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を考慮したライブラリで 回避可能
  49. © DMM.com 課金プラットフォームの Kotlin利用実績 49

  50. © DMM.com 課金プラットフォームの言語比率 50 ※ 課金プラットフォームの24プロダクトの集計結果 Python : 4% PHP

    : 58% Java : 25% Kotlin : 13%
  51. © DMM.com 課金プラットフォームの言語比率 51 PHP : 58% Java : 25%

    DMMポイントを操作する APIの例をご紹介 Python : 4% ※ 課金プラットフォームの24プロダクトの集計結果 Kotlin : 13%
  52. © DMM.com DMMポイントとは 52 • DMMで1ポイント1円で利用できるポイントサービス • DMMの利用でポイントプレゼント • プリペイドカードなどでポイントチャージ可能

  53. © DMM.com 売上70億円 500万リクエスト DMMポイントとは 月間で... 53

  54. © DMM.com ポイントAPIをリプレイス • DMMポイントの発行や消費をするAPIをリプレイス • 一般的でない技術を使用していて可読性が悪い • レガシーなミドルウェアを撤去したい 54

  55. © DMM.com ポイントAPIをリプレイス • DMMポイントの発行や消費をするAPIをリプレイス • 一般的でない技術を使用していて可読性が悪い • レガシーなミドルウェアを撤去したい 55

    リードタイムが長い
  56. © DMM.com • DMMポイントの発行や消費をするAPIをリプレイス • 一般的でない技術を使用していて可読性が悪い • レガシーなミドルウェアを撤去したい • アーキテクチャ

    • Kotlin 1.2 • Spring Framework 5.0 • Spring Boot 2.0 • MySQL リードタイムが長い ポイントAPIをリプレイス 56
  57. © DMM.com ポイントAPIリプレイスの実績 • 全部で11種類のAPIを作成 • 開発メンバーは4名 • ビジネスロジックの開発期間は2ヶ月 57

  58. © DMM.com ポイントAPIリプレイスの実績 • 全部で11種類のAPIを作成 • 開発メンバーは4名 • ビジネスロジックの開発期間は2ヶ月 58

    全員Kotlin未経験 Javaができるエンジニア
  59. © DMM.com ポイントAPIリプレイスの実績 • 全部で11種類のAPIを作成 • 開発メンバーは4名 • ビジネスロジックの開発期間は2ヶ月 59

    全員Kotlin未経験 Javaができるエンジニア Kotlinの学習コストが低いので 短い期間で成果が出せる
  60. © DMM.com リプレイス前後のコード量の比較 60 コード量 31%削減 ※ ビジネスロジックの集計結果 Java Kotlin

  61. © DMM.com 実績のまとめ 61 • コード量が減って可読性UP • レガシーなミドルウェアを撤去 • Kotlinは学習コストが低いので短期間で成果を出せる

  62. © DMM.com 実績のまとめ 62 • コード量が減って可読性UP • レガシーなミドルウェアを撤去 • Kotlinは学習コストが低いので短期間で成果を出せる

    リードタイムの短縮!
  63. © DMM.com 実績のまとめ 63 • コード量が減って可読性UP • レガシーなミドルウェアを撤去 • Kotlinは学習コストが低いので短期間で成果を出せる

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

  65. © DMM.com まとめ • 「新しいことをやってみたい!」からKotlinにチャレンジ • ちょっと注意も必要だが多くの恩恵を受けられた • Kotlin未経験のメンバーだけで 大規模な課金プラットフォームのリプレイスに成功

    65
  66. © DMM.com 66 Join Our Team! DMMグループでは一緒に働く仲間を募集しています。 https://dmm-corp.com/recruit/engineer Serverside Engineer

    Server Engineer iOS / Android Engineer Frontend Engineer Technical Consultant UI Designer
  67. © DMM.com Thank you for your kind attention!