Save 37% off PRO during our Black Friday Sale! »

エムスリーでのKotlinへの取り組み / jjug-ccc-2018fall-kotlin-in-m3

F33a4157978cd59d5417ae69b0265943?s=47 M3 Engineering
December 15, 2018

エムスリーでのKotlinへの取り組み / jjug-ccc-2018fall-kotlin-in-m3

JJUG CCC 2018 Fallで発表した「エムスリーでのKotlinへの取り組み」の資料です。

F33a4157978cd59d5417ae69b0265943?s=128

M3 Engineering

December 15, 2018
Tweet

Transcript

  1. Copyright © 2018 M3, Inc. All Rights Reserved エムスリー Kotlinへ

    取り組み エムスリー株式会社 滝安純平 星川貴樹 @JJUG CCC 2018 Fall #jjug_ccc #ccc_a4
  2. スピーカー紹介 滝安純平 @juntaki サーバーサイド / Webフロントエンドエンジニア 星川貴樹 @oboenikui Android /

    セキュリティエンジニア
  3. エムスリーとは

  4. m3.comはこんなサービス 医師 主 無料 コンテンツを提供 • 医療関連ニュース • 薬剤情報 •

    講演会動画 仕事 合間 手軽 情報収集 きる 製薬会社等 有償 サービスを提供 • 医師へ 薬剤情報・講演 会動画 発信 • 医師向けアンケート(ニー ズ把握) 低コスト 医師 営業活動が きる
  5. 目次 1. Kotlinとエムスリー 2. サーバサイドの取り組み 3. Androidの取り組み Androidエンジニア? Kotlin使 いる?

  6. Kotlinとエムスリー

  7. Kotlinとエムスリーの歴史 社内で多くのプロジェクトがKotlinを採用しています! 2016 2017 2018 製薬プロモAndroidアプリをKotlin リニューアル開始 医師転職事業リニューアル、サーバーサイドKotlin導入 薬剤師転職事業リニューアル 製薬プロモーション事業リニューアル

    新アンケート管理システム …… 新規サーバサイドKotlin続々導入中 新規AndroidアプリもKotlin 開発中
  8. 社内エンジニアチームの紹介 • エンジニアは事業部門ごとのチームに所属 • いくつかの横断チームもある 事業 部門 ご 横断 製薬

    プロモ 新領域 サイト プロモ 転職事業 リサーチ 治験 コンシュー マ モバイル アプリ SRE セキュリティ AI機械学習 基盤 電子 カルテ QA
  9. Kotlinを使ってるチーム サーバーサイド・モバイルアプリ開発によらず使われている 製薬 プロモ 新領域 サイト プロモ 転職事業 リサーチ 治験

    コンシュー マ 事業 部門 ご 横断 モバイル アプリ SRE セキュリティ AI機械学習 基盤 電子 カルテ QA
  10. 製薬 プロモ 新領域 サイト プロモ 転職事業 リサーチ 治験 コンシュー マ

    モバイル アプリ SRE セキュリティ AI機械学習 基盤 電子 カルテ QA Kotlinを選んだ主な理由 事業 部門 ご 横断 Androidアプリ開発メンバが サーバーサイドを作る Javaエンジニアが多いチーム BetterJava し エンジニア し 新しいも 挑戦したい 他 プロジェクト も使 いた ため
  11. 技術選定の方針 チーム・システムごとに所属するエンジニアが自由に選定 1. メンバーの習熟度 2. 利用できるライブラリ 3. 既存システムとの連携

  12. サーバーサイドの取り組み

  13. サーバーサイドいろいろ • JavaからKotlinに移行する方法 ◦ いろいろな移行方法 ◦ 移行時に気をつけること ◦ リグレッションテストで品質を担保 •

    サーバーサイドKotlinで採用している要素技術紹介 ◦ SpringBoot ◦ Swagger ◦ GraphQL エムスリー あ た実際 りくみから、そ ノウハウを紹介します!
  14. JavaからKotlinへの移行 3パターンくらいの方針が取れる 徐々に変えていく  新しく作ったものだけKotlinで書いて混在させるなど なかったことにする  リニューアルと銘打って再設計 全部まとめて書き換える  IntelliJで単純に自動コンバートして、なんとかする

  15. 徐々に変えていく 8割完成済み javaプロジェクト kotlinを途中導入した話 https://speakerdeck.com/kawamataryo/8ge-wan-cheng-ji-mifalsejavapuroziekutonikotlinwotu-zhong-dao-ru-sitahua

  16. なかったことにする 10年前 レガシーシステムをサーバーサイド Kotlin フルリニューアルし いる話 https://speakerdeck.com/maeharin/10nian-qian-falseregasisisutemuwosabasaidokotlindehururiniyuar usiteiruhua-number-jjug-ccc-number-ccc-g2 入社し 最初

    プロジェクト
  17. 全部まとめて書き換える Kotlinらしいコードにはならないが、Kotlinにはなる 手動でのリファクタリングは必要 →開発が活発な段階で入れないと厳しい https://pleiades.io/help/idea/converting-a-java-file-to-kotlin-file.html

  18. 全部まとめて書き換える、ときのノウハウ • 他の機能開発と調整をする(チームでの開発の場合) • リグレッションテストをたくさん作っておく

  19. 他の機能開発と調整する コンバート前後だとファイル(.java/.kt)が違うため、 すべて差分になり、機能追加を見つけ出すことが難しい コンバート中は機能開発ストップする ここ コンバート しかやら い 開発再開!!

  20. 他の機能開発と調整する なるべく短期間で終わらせるため コンバート以外の余計なことをやらない • テストコードは一旦Javaのまま • Kotlinらしいコードにするのは機能開発と並行して るべく短く! だんだんKotlinらしい コード

    すれ 良い
  21. リグレッションテスト 自動コンバートをコードレビューしても仕方ない 品質の担保はリグレッションテストでカバーする “Golden file testing”を採用し テストを量産

  22. Golden file testingとは APIのレスポンスとGolden fileを比較して、 レスポンスが変わらないことを確認する テスト対象: Application層 ハンドラ テストコード

    レスポンスを検査 Golden file (=JSON) Response Object Request Object 自動生成 全テスト共通 ここだけ、書く必要がある DB Mock データ
  23. fun assertEqualOrSaveJsonFile( actual: Any, javaClass: Class<Any>, fileName: String ) {

    val javaClassPath = javaClass.name.replace(".", "/") val path = "src/test/resources/testdata/$javaClassPath/$fileName.json" val expected = TestForApplication::class.java.getResource(path)?.readText() if (expected == null) { saveToJsonFile(path, actual) } else { assertEqual(expected, actual) } } Golden fileを生成するコード Golden fileが • なかったら そのままJSONを書き込んで、例外投げる • あったら ファイルの中身とJSONを比較 javaClass ディレクトリ 構成も自動 決める
  24. ふつうのテストとの違い リグレッションテストに向いている よくあるケース Golden file testing ふ う テスト テストを増やそう

    普通 ファイル生成 assert地獄、あきらめる 漏れる 出力が変わる変更をした 消し 、普通 ファイル生成 assertを おし いく地獄 巨大 出力を扱う 普通 ファイル生成 Method code too large! JSON文字列をチェック 普通 ファイル生成 \”エスケープが必要\” テスト 仕様を明示したいケース 向い い い 、使い分け 大事 日付や、ランダム 生成し いる IDが含まれるよう 場合 、モック等 対応が必要
  25. サーバサイドで使っている要素技術 • SpringBoot • Swagger • GraphQL

  26. SpringBoot 新規APIで採用するフレームワークはほぼSpringBoot Spring Boot 一般ライブラリ 折り合い けかた https://speakerdeck.com/saiya_moebius/spring-boot-to-ban-raiburarifalsezhe-rihe-ifalsetukekata

  27. SpringBootはハマると難しい・・ Kotlin+SpringBootなプロジェクトが他にもあると、 そのまま構成を参考にできたり、聞ける人が多い

  28. SpringBootはAPIまで フロントエンドはReactやVue.jsを使いたい インタラクションをどうする???

  29. Swagger Codegen, UI (OpenAPI) Kotlin+SpringBootでサーバサイドを作ったら • クライアントライブラリは自動生成できる(Codegen) • ドキュメントも自動生成できる(UI) Swagger

    Codegen APIクライアントgem 自動生成 #m3kt https://speakerdeck.com/juntaki/swagger-codegende-apikuraiantogem-zi-dong-sheng-cheng-number-m3kt 生成されたドキュメント
  30. GraphQL Swagger(OpenAPI)と似たメリットがある 新サービスで導入して、ノウハウを蓄積中 • クライアントの自動生成 • ドキュメントの管理を実装と差分無く • スキーマファーストな開発 •

    複数のリクエストをまとめる フロントエンド向けの API サーバリニューアルに GraphQL を検討している話 https://www.m3tech.blog/entry/graphql-on-spring-boot-with-kotlin
  31. Androidの取り組み

  32. Androidいろいろ • チームの紹介 ◦ メンバー構成 ◦ 導入の経緯 ◦ 社内制度 •

    Android周りのKotlin導入時の懸念点 ◦ Optional or Nullable ◦ Lombok or data class • チームメンバーへの浸透
  33. モバイルアプリチームについて • モバイルアプリエンジニアはAndroid, iOS合わせて 1チームに集中 • 自称Kotlinエバンジェリストの長澤太郎さん(通称た ろうさん)が今年4月まで在籍(現在はフェロー) アプリチーム エンジニア

    Kotlin エンジニア 4 → 10 2 → 7
  34. 弊社Androidアプリ MR君アプリ • 弊社で最初に作られたMR君と いうサービス専用のアプリ • 製薬プロモーション専用 • その分m3.comアプリにはな い機能がある

    m3.comアプリ • 現在の主要アプリ • MR君を含む主要なサービスが 1まとめになっているアプリ + 未公開新規アプリ 2
  35. 導入の経緯 • 2016年1月より、たろうさんがOKRとしてMR君アプリ のリニューアルを開始 • 当時他社でも採用実績がほぼなかったKotlinを採用 ◦ 自称エバンジェリストなので ◦ 最初に使ったKotlinバージョンは1.0.0-beta-4584

    ◦ Google I/OでKotlin正式サポートが発表される前から Kotlin化したアプリをリリース
  36. OKRについて • “Objectives and Key Results” • Intel発祥、Googleでも1999年から利用 • 「目的」を設定し、それを達成するための「目標」を

    定義する ◦ Objectiveは必ずしも定量的で無くても良い ◦ Key Resultは定量的で、客観的に判断できる • (大きな目標) • 弊社の場合 ◦ 2013/11からやっている ◦ チームごとに目標設定 ◦ 目標はリファクタや新規プロダクトなど、短期的にビジネスに 繋がらなくても良い
  37. 2018年現在のAndroid×Kotlin • Google公式サポート (2017~) • 活発なライブラリはほとんどKotlin対応されている • Android公式ドキュメントもKotlin, Javaが併記さ れているものが増えている

    • Android OSのフレームワーク自体はJavaで書かれて いるが、Android 9から@NotNull, @Nullableアノ テーションがついたことでよりKotlinフレンドリーに • 勉強会やブログ記事でもKotlinが主流
  38. 弊社アプリのKotlin対応の歴史 2016/1 2017/5 2018/6 Kotlinで フルリニューアル 徐々に書き換え 初めから Kotlinで 新規

    アプリ Java Kotlin
  39. 弊社アプリのKotlin対応の歴史 2016/1 2017/5 2018/6 Kotlinで フルリニューアル 徐々に書き換え 初めから Kotlinで 2017/5/16

    フルKotlinで リリース! 新規 アプリ Java Kotlin
  40. 弊社アプリのKotlin対応の歴史 2016/1 2017/5 2018/6 Kotlinで フルリニューアル 徐々に書き換え 2017/5/18 Google I/O

    2017で AndroidのKotlin公式 サポート発表 初めから Kotlinで 新規 アプリ Java Kotlin
  41. 弊社アプリのKotlin対応の歴史 2016/1 2017/5 2018/6 Kotlinで フルリニューアル 徐々に書き換え 初めから Kotlinで 2018/6

    開発合宿で新アプリ作成 (2泊3日) 新規 アプリ Java Kotlin
  42. 弊社アプリKotlin化の現状 フルリプレイス済(2016~2017) • 前述のリニューアル by たろうさん 部分的に変えていく方針(2018~) • 新しく作ったクラスについてKotlinで書 いていく

    • 大きく変更が入るクラスもKotlin化して から編集 • data classへの移行などに応じて関連ク ラスもKotlin化
  43. 書き換えの手間 余分に工数・手間が発生するために敷居が上がる ◦ 大幅に書き方の変わるLombok → data class移行 ◦ よりKotlinらしいコードへの書き換え etc…

    チームのOKRの目標としてKotlin化を定める 勉強会などでKotlinの利便性、楽しさを布教
  44. Javaとの共存の問題 • 使用するクラス、ライブラリの違い ◦ Optional or Nullable ◦ Lombok or

    data class • コンパイル順の問題
  45. Optional in Java • Javaでnullは扱いづらいのでOptionalを用いたい • 弊チームのAndroidアプリは、フレームワークやライ ブラリの仕様上必要な場合を除き、Optionalを使うな どをしていて、全くnullがコードに出現しない •

    Android 6までOptionalがなかったので、Guavaの Optionalを使っている String text = Optional.fromNullable(nullableInt) .transform(Integer::toString) .or("");
  46. Nullable in Kotlin • KotlinではNullableをうまく扱う仕組みがある (Null安全) • 余計なインスタンスを生成しない分Optionalよりも オーバーヘッドが少ない val

    text = nullableInt?.toString() ?: ""
  47. Optional in Kotlin • GuavaのOptionalは正直使い勝手が悪い ◦ nullになるはずのない部分が@Nullableになっていたり ◦ ラムダを渡してもinline展開されなかったり ◦

    代替案としてはArrowというFPライブラリののOptionがある • orNull()を呼べば、Kotlin上ではNullable型とし て処理可能 // Optionalのまま処理 val text = intOptional .transform { it!!.toString() } // なぜかNullable .or("") // Nullableに変換 val text = intOptional.orNull()?.toString() ?: ""
  48. Optional or Nullable チーム内コーディング規約 • Javaから呼び出されるKotlinクラスのpublicメソッ ドはOptionalでやりとりする • Kotlin内で閉じている場合はNullableにしてOK •

    RxJavaではnullを流せないので、どうしても使う場 合のみOptional ◦ Single<Optional<T>>はケースによってはMaybe<T>に ◦ コルーチンはJava側から触れないので
  49. Lombok or data class • JavaではLombokを使用していた • Kotlinにはdata classという、自動でequals, hashCodeメソッドを作ってくれる機能がある

    data class User( val name: String, val sex: Sex, val age: Int, val prefectureCode: Int ) @Value @Builder @Wither public class User { @NonNull String name; @NonNull Sex sex; int age; int prefectureCode; }
  50. Lombok or data class • Kotlinでは、Builderパターンなどの代わりに名前付 き引数を用いる User.builder() .name("oboenikui") .sex(Sex.MAN)

    .age(25) .prefectureCode(12) .build(); val user = User( name = "oboenikui", sex = Sex.MAN, age = 25, prefectureCode = 12 )
  51. Lombok or data class • Kotlinでは、Builderパターンなどの代わりに名前付 き引数を用いる user.withAge( user.getAge() +

    1 ); user.copy ( age = user.age + 1 )
  52. Javaからdata classの呼び出し val user = User( name = "oboenikui", sex

    = Sex.MAN, age = 25, prefectureCode = 12 ) User user = new User( "Takaki Hoshikawa", Sex.MAN, 25, 12 ); 年齢? 県コード? ビルダーや名前付き引数の機能がないので コンストラクタなどに渡す変数がわかりにくい
  53. Javaからdata classの呼び出し user.copy ( age = user.age + 1 )

    user.copy( user.getName(), user.getSex(), user.getAge() + 1, user.getPrefectureCode() ); 名前付き引数がないので copyメソッドが使いにくい
  54. KotlinからLombokの呼び出し User.builder() .name("oboenikui") .sex(Sex.MAN) .age(25) .prefectureCode(12) .build() User.builder() .name("oboenikui") .sex(Sex.MAN)

    .age(25) .prefectureCode(12) .build(); ERROR LombokとKotlinは同じモジュール内で共存できない (モジュールを分けたりDelombokする必要がある)
  55. その他 User.builder() // .name("oboenikui") .sex(Sex.MAN) .age(25) .prefectureCode(12) .build(); val user

    = User( // name = "oboenikui", sex = Sex.MAN, age = 25, prefectureCode = 12 ) 引数が足りないので シンタックスエラー build()実行時にエラー Builderパターンはdata classに比べて厳密にならない
  56. data classとLombokの使用方針 • 他のクラスより慎重にKotlin化する ◦ 多くの箇所で生成するものは後回しにする ◦ data class化したら、使っているクラスも合わせてKotlin 化

    ◦ 利用するwitherのみ定義する ◦ JavaのUTで使う場合はコメントを入れる data class User( /* 略 */ ) { fun withAge(age: Int): User { return copy(age = age) } }
  57. data classとLombokの使用方針 • 他のクラスより慎重にKotlin化する ◦ 多くの箇所で生成するものは後回しにする ◦ data class化したら、使っているクラスも合わせてKotlin 化

    ◦ 利用するwitherのみ定義する ◦ JavaのUTで使う場合はコメントを入れる User testUser = new User( "Takaki Hoshikawa", // name Sex.MAN, // sex 25, // age 12 // prefectureCode );
  58. data classとLombokの使用方針 • Lombokを使うモジュールを隔離 / Delombok ◦ マルチモジュール化でビルド速度改善も期待できる ◦ Lombokクラスからはdata

    classを利用できない module A (w/ Lombok) module B (data class) main module & 依存方向 モジュール隔離
  59. data classとLombokの使用方針 • Lombokを使うモジュールを隔離 / Delombok ◦ マルチモジュール化でビルド速度改善も期待できる ◦ Lombokクラスからはdata

    classを利用できない Delombok @Value @Builder @Wither public class User { @NonNull String name; @NonNull Sex sex; int age; int prefectureCode; } public class User { /* 略 */ public String getName() { return name; } /* 略 */ }
  60. アプリサイズの増加 • kotlin-stdlib (Kotlin独自のメソッドやクラス) のサイズは1.1MBある • ProGuard (難読化 & 未使用コード削除)

    でサイズ を減らせる ◦ m3.comアプリ導入時は約0.1MBの増加に抑えられた
  61. メンバーへの浸透 コードレビュー • Javaを書けるエンジニアであればKotlinの動くコー ドを書くことは容易 • 「Kotlinらしさ」をLintやコードレビューで学習 社内Wiki・Slack • コードレビューで指摘した内容を中心に、Wikiに解説

    を含めてまとめる • 細かいスタイルについては積極的にSlackの#kotlin チャンネルで議論
  62. コーディング規約・Lint • 著名なスタイルガイド2種を踏襲 ◦ JetBrainsスタイルガイド ◦ Googleスタイルガイド • Lintでスタイルガイドに準拠しているかチェック ◦

    ktlint ◦ detekt • IntelliJ IDEAのインスペクタ・フォーマッタも Kotlin 1.3からスタイルガイドに準拠できるように • スタイルガイドにないものはゆるい規約として社内 Wikiで共有
  63. Android Unit Test • JUnit4 + Robolectric + AssertJ +

    MockK • バッククォートで囲むことで可読性向上 @Test fun `getFoo should return foo bar`() { /* Test code */ } @Test public void getFoo_should_return_foo_bar() { /* Test code */ }
  64. 外部発信

  65. 勉強会 次回は2月中旬予定!

  66. テックブログ Advent Calendarもやってます! https://www.m3tech.blog

  67. スポンサー活動 • JJUG CCC ◦ ゴールドスポンサー ◦ コーヒースポンサー ◦ ブーススポンサー

    ← NOW!! • Kotlin Fest • ScalaMatsuri • Ruby Kaigi • Vue Fes Japan • DroidKaigi 2019 • try! Swift Tokyo 2019 JJUG CCC 2017 Springのコップ
  68. We are hiring!

  69. We are hiring! • グローバルで働いてみたいエンジニア ◦ US出張やってます! • Androidエンジニア ◦

    新規アプリはGraphQLを使って開発中! • サーバーサイドエンジニア ◦ Spring Boot + Kotlin + GraphQL ◦ Java, Scalaエンジニアも募集中! https://bit.ly/ccc2018m3
  70. Thank you for listening!