Slide 1

Slide 1 text

分析用コードをアプリから 切り離す設計の実現 pixiv Inc. makun 2019.11.22

Slide 2

Slide 2 text

2 自己紹介 ● 18年 新卒入社 ● Androidアプリ開発を担当 ● エンジニア採用を担当 ● 好きなのは設計やアイデアだし ● 苦手なのは収束させること makun アプリエンジニア

Slide 3

Slide 3 text

3 ● マンガサービス ● 講談社と協業 ● 女性がターゲット ● 女性誌特集なども ● Android、iOS

Slide 4

Slide 4 text

アーキテクチャ 4

Slide 5

Slide 5 text

5 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server Repository Database Entity Action Action Item Entity Remote Model Local Model

Slide 6

Slide 6 text

6 Database Entity Core Feature Production App Repository Development App Feature Feature Repository Repository Database Database Remote Model Local Model Presentation Domain Data Resources

Slide 7

Slide 7 text

分析コードとは 7

Slide 8

Slide 8 text

ユーザーの行動を把握しアプリのマーケティン グやパフォーマンス改善に関する 意思決定を行うためのデータを得ることのでき るコード、もしくは得るためのコード 8

Slide 9

Slide 9 text

分析コードを 実装するとは 9

Slide 10

Slide 10 text

意思決定を行うためのデータを得ることのでき るコード、もしくは得るためのコードが動作する よう記述する 10

Slide 11

Slide 11 text

パルシィのコードに 分析コードを実装してみる 11

Slide 12

Slide 12 text

12

Slide 13

Slide 13 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch { … } } } 13

Slide 14

Slide 14 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics .getInstance(context.applicationContext) .logEvent(“follow_comic”, bundleOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 14

Slide 15

Slide 15 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro.track(“【フォロ】コミック”, mapOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 15

Slide 16

Slide 16 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro.track(…) AppsFlyerLib.getInstance().trackEvent( context.applicationContext, “key_follow_comic”, mapOf( “comicId” to comic.id, “comicTitle” to comic.title )) launch { … } } } 16

Slide 17

Slide 17 text

本当にこの実装でいいんだっけ? 17

Slide 18

Slide 18 text

Context受け取るようになったけど テストのことちゃんと考慮してる? 18

Slide 19

Slide 19 text

同じ分析コードを埋め込む箇所が 増えたときはどうする? 19

Slide 20

Slide 20 text

機能モジュールの依存増えてない? ビルド時間とかも大丈夫? 20

Slide 21

Slide 21 text

分析ツールが 増えたり減ったりしたときは どうする? 21

Slide 22

Slide 22 text

そもそもなんでこんなに 分析ツールあんの? 22

Slide 23

Slide 23 text

23

Slide 24

Slide 24 text

24 ツール 目的 Firebase ピクシブ全体で積極的に利用している BigQueryにデータをあげて全体的な分析に利用 Repro 協業先に利用実績があり 特定のアクションに対してアプリ内メッセージをだしたりに利用 AppsFlyer 協業先に利用実績があり アプリの流入や経路別のコンバージョンをみるために利用 分析用ツールを複数使う理由

Slide 25

Slide 25 text

分析コードを実装する場 合の設計を考える 25

Slide 26

Slide 26 text

26 それぞれの視点で考える
 設計時にやること

Slide 27

Slide 27 text

27 機能を開発する人の視点
 


Slide 28

Slide 28 text

28 分析コードを追加する人の視点
 


Slide 29

Slide 29 text

29 今回は触れない視点 ● アプリを利用するユーザーの視点 ● テストの視点 ● リファクタリングの視点

Slide 30

Slide 30 text

30 設計次以外のメリット ● コードを書くときにも有効 ● 視点ごとにプルリクをわけられる ● 実装時の思考コストが減る ● レビュー時の思考コストが減る ● 意味のあるまとまりで開発をすすめられる

Slide 31

Slide 31 text

機能を開発する人の視点でみてみる 31

Slide 32

Slide 32 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 32

Slide 33

Slide 33 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 33 ユーザーがボタンをタップし たときの処理をここにつくる

Slide 34

Slide 34 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 34 要求だとFirebase、Repro、 AppsFlyerにイベントを送信す る必要があるぞ

Slide 35

Slide 35 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 35 分析コードの実装のために Contextを受け取らないと いけないぞ

Slide 36

Slide 36 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 36 今回の要求だとComicの Entityだけでデータはおくれ そうだぞ

Slide 37

Slide 37 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 37 Context追加しちゃったから メソッドを実行してる部分に もContextわたさないといけ ないぞ

Slide 38

Slide 38 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 38 あとテストも書き直さないと いけないなぁ

Slide 39

Slide 39 text

39 Entity Core Feature Production App Feature Feature Firebase Repro AppsFlyer

Slide 40

Slide 40 text

分析コードを実装する人の視点でみてみる 40

Slide 41

Slide 41 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 41 まずはこのコードまでたどりつく必要がある

Slide 42

Slide 42 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 42 フォローボタンをタップした ときで、すでにフォローして いたかどうかが判定されて いない

Slide 43

Slide 43 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { if (comic.isFollowed.not()) { FirebaseAnalytics… Repro… AppsFlyerLib… } launch { … } } } 43

Slide 44

Slide 44 text

44 分析コードはロジックをもつ


Slide 45

Slide 45 text

45 ユーザー体験とは関係ないはずなのに・・ ● ユーザー体験にかかわるところ (Featureモジュール) にコードをかく ● 既存の処理を変更したりしないといけないことがある ● 変更をくわえちゃうから実機のテストもちゃんとしておきたくなる ● テストもちゃんとかいておきたくなる ● 考えないといけないところが増えた気がする

Slide 46

Slide 46 text

パルシィでの設計 46

Slide 47

Slide 47 text

Flux + Tracker 47

Slide 48

Slide 48 text

48 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server Repository Database Entity Action Action Item Entity Remote Model Local Model

Slide 49

Slide 49 text

49 Dispatcher Store (ViewModel) ActionCreator (ViewModel) View (Activity, Fragment) Server Repository Database Tracker Entity Action Action Item Action Entity Remote Model Local Model

Slide 50

Slide 50 text

// Actionを受け取れる概念をinterfaceにする interface ActionReceiver { fun receive(action: Action) } 50

Slide 51

Slide 51 text

// Dispatcherを継承する本番用Dispatcher class MainDispatcher( // ActionReceiverを実装したクラスを受け取る private vararg val receivers: ActionReceiver ) : Dispatcher() { override fun dispatch(action: Action) { // 全てのActionReceiverにActionを送信する receivers.forEach { it.receive(action) } super.dispatch(action) } } 51

Slide 52

Slide 52 text

// Dispatcherを継承する本番用Dispatcher class MainDispatcher( // ActionReceiverを実装したクラスを受け取る private vararg val receivers: ActionReceiver ) : Dispatcher() { override fun dispatch(action: Action) { // 全てのActionReceiverにActionを送信する receivers.forEach { it.receive(action) } super.dispatch(action) } } 52

Slide 53

Slide 53 text

// Firebase用にActionReceiverを実装したクラス // このクラスのインスタンスをMainDispatcherにわたす // パルシィではKoinでBeanをつくる class FirebaseTracker( private val app: Application ) : ActionReceiver { override fun receive(action: Action) { FirebaseAnalytics… } } 53

Slide 54

Slide 54 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch { dispatch(TapFollowComicAction(comic)) // 以下に実際のフォロー処理をかく … } } } 54

Slide 55

Slide 55 text

55 Entity Core Feature Production App Feature Feature Firebase Repro AppsFlyer

Slide 56

Slide 56 text

56 Entity Core Feature Production App Feature Feature Tracker

Slide 57

Slide 57 text

結果と考察 57

Slide 58

Slide 58 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(context: Context, comic: Comic) { FirebaseAnalytics… Repro… AppsFlyerLib… launch { … } } } 58

Slide 59

Slide 59 text

class ComicActionCreator : ViewModel() { fun tapFollowComic(comic: Comic) { launch { dispatch(TapFollowComicAction(comic)) // 以下に実際のフォロー処理をかく … } } } 59 ・分析コードをかく必要がない ・機能開発だけに集中できる ・レビューコストが減る

Slide 60

Slide 60 text

60 Entity Core Feature Production App Feature Feature Firebase Repro AppsFlyer

Slide 61

Slide 61 text

61 Entity Core Feature Production App Feature Feature Tracker 機能開発がモジュール内だけで完結

Slide 62

Slide 62 text

62 Entity Core Feature Production App Feature Feature Tracker 機能開発がモジュール内だけで完結 分析コードのロジックが Trackerモジュールにまとまる

Slide 63

Slide 63 text

63 Firebase Tracker Repro Tracker AppsFlyer Tracker Tracker ツールそれぞれのロジックが 各モジュールにまとまる Production App

Slide 64

Slide 64 text

64 Feature Tracker Production App Firebase Tracker 分析コード実装時の考える依存が少ない

Slide 65

Slide 65 text

65 Production App Firebase Tracker 分析コード実装時の手を加えたり、考えるス クープはさらに小さい

Slide 66

Slide 66 text

快適な実装 66

Slide 67

Slide 67 text

ミスの少ない実装 67

Slide 68

Slide 68 text

最高 68