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

0→1開発における迅速なプロダクト改善を支える技術【DeNA TechCon 2021】/techcon2021_20

0→1開発における迅速なプロダクト改善を支える技術【DeNA TechCon 2021】/techcon2021_20

新たなプロダクトをゼロから開発する”0→1開発”では、スピーディーにプロダクト改善を繰り返すことが重要ですが、多くの場合プロダクトを担当するエンジニアは少なく、1つ1つの機能開発にあてられる時間は短いです。また、多くの仕様変更に迅速に対応しなければなりませんが、安全の担保も求められます。

これらの課題に対して、プロトレーナーによる指導がオンラインで受けられるサービス、「WITH Fitness(ウィズフィットネス)」のプロダクト開発では、バックエンド開発を「最小工数で安全に実現」し、フロントエンド開発を「最速で機能実装、仕様変更」する方針のもと、少人数のエンジニアで迅速な開発、プロダクト改善を行ってきました。

ただ初期プロダクト案を実現するだけでなく、開発と並行してユーザーインタビューやベータテスト、チーム内でのUX議論を重ね、効果的なアイデアを迅速に受け入れる開発体制をどのように構築し開発を進めてきたか、本セッションでご紹介します。

8a84268593355816432ceaf78777d585?s=128

DeNA_Tech

March 03, 2021
Tweet

Transcript

  1. Minami Baba 1

  2. 2020年11月にリリースされました 2

  3. ※0→1開発とは(今回の話では)開発スタートからローンチまでを指します 今日話すこと 3

  4. プロの による指導を で に受けられる 毎日・アプリで 毎週・ビデオ通話で 4

  5. (delight-ventures.com) ・DeNA発のベンチャーキャピタル ・スタートアップへの投資 ・早期独立を目指した  ・常時5~10プロジェクトが進行中 5

  6. 開発開始から 正式版リリースまで 約7ヶ月間 6

  7. トレーナーのみ なさん 開発にご協力 いただいてる エンジニアのみ なさん Delight Venturesの みなさん and

    more…! 7
  8. 今日話すこと 8

  9. 今日話すこと 9 様々な技術や開発手法をご紹介していき ますが、詳細な導入手順や実装等は割 愛させていただきます。 専門知識がなくても理解いただける 内容 になっています。

  10. 今日伝えたいこと 10

  11. 11

  12. 導入 12

  13. 13

  14. ・より良いものをリリースできる ・リリース後の改善サイクルに繋がる 14

  15. ・より良いものをリリースできる ・リリース後の改善サイクルに繋がる ・ユーザーインタビュー ・オンラインでのトレーナー指導を体験 ・開発段階でのこまめなデモ / 試用 15

  16. 16

  17. 17

  18. ・初期要件の実装に時間がかかってしまう ・機能改善案に対応することが難しい 18

  19. ・初期要件の実装に時間がかかってしまう ・機能改善案に対応することが難しい "プロダクト改善を支える技術"について バックエンド / フロントエンド の両面からご紹介 19

  20. 前編 20

  21. ※XaaSとは、情報処理に用いられるコンピューティング資源を、 インターネットを通じて提供しているサービスの総称。 (例:IaaS, mBaaS, SaaS) 21

  22. データベース 画像ストレージ ユーザー認証 バックエンド処理 決済機能          データ暗号化 APIキー管理 分析データ収集

    分析データ集計 分析データ可視化 22
  23. 時系列に沿って各サービスの 具体的な活用事例をご紹介 23

  24. ベータ版を利用してもらい コア体験の満足度を検証したい 24

  25. 多くのプロダクトに必要な 日々の取り組みをトレーナーに報告 トレーナーへいつでも質問できる 25

  26. 多くのプロダクトに必要な 日々の取り組みをトレーナーに報告 トレーナーへいつでも質問できる 26

  27. 多くのプロダクトに必要な 日々の取り組みをトレーナーに報告 トレーナーへいつでも質問できる 27

  28. 多くのプロダクトに必要な 日々の取り組みをトレーナーに報告 トレーナーへいつでも質問できる 28

  29. 7月に納得できる状態で リリースするためには 1ヶ月以内にはざっくり動 くもの作りたいな... 実装が重そうなのは ユーザー認証・画像アッ プロード・双方向通信あ たりだな... 29

  30. 7月に納得できる状態で リリースするためには 1ヶ月以内にはざっくり動 くもの作りたいな... 実装が重そうなのは ユーザー認証・画像アッ プロード・双方向通信あ たりだな... 30

  31. 7月に納得できる状態で リリースするためには 1ヶ月以内にはざっくり動 くもの作りたいな... 実装が重そうなのは ユーザー認証・画像アッ プロード・双方向通信あ たりだな... 31

  32. 32 データベース https://firebase.google.com/docs/firestore 画像ストレージ https://firebase.google.com/docs/storage ユーザー認証 https://firebase.google.com/docs/auth

  33. 33 データベース https://firebase.google.com/docs/firestore 画像ストレージ https://firebase.google.com/docs/storage ユーザー認証 https://firebase.google.com/docs/auth

  34. データの読み書き db.collection("users").document().setData(["name": "minami"]) db.collection("users").document("0123").getDocument {} 34 データベース https://firebase.google.com/docs/firestore 画像ストレージ https://firebase.google.com/docs/storage

    ユーザー認証 https://firebase.google.com/docs/auth
  35. 画像動画のやり取り データの読み書き db.collection("users").document().setData(["name": "minami"]) db.collection("users").document("0123").getDocument {} let riversRef = storage.reference().child("images/rivers.jpg")

    riversRef.putData(data, metadata: nil) {} 35 データベース https://firebase.google.com/docs/firestore 画像ストレージ https://firebase.google.com/docs/storage ユーザー認証 https://firebase.google.com/docs/auth
  36. 会員登録・ログイン 画像動画のやり取り データの読み書き db.collection("users").document().setData(["name": "minami"]) db.collection("users").document("0123").getDocument {} let riversRef =

    storage.reference().child("images/rivers.jpg") riversRef.putData(data, metadata: nil) {} Auth.auth().createUser(withEmail: email, password: password) {} Auth.auth().signIn(withEmail: email, password: password) {} 36 データベース https://firebase.google.com/docs/firestore 画像ストレージ https://firebase.google.com/docs/storage ユーザー認証 https://firebase.google.com/docs/auth
  37. Cloud Firestore・Cloud Storage・Firebase Authentication 37

  38. 会員登録・ログイン 画像動画のやり取り データの読み書き ・環境構築にかかる工数を削減できる ・1つのプロジェクト内での実装に集中できる データベース 画像ストレージ ユーザー認証 38

  39. ・パスワード ・Google ・Facebook ・Apple ・Twitter ・会員登録 ・ログイン ・本人確認メール送信 ・メールアドレス再設定 ・パスワード再発行

    ・必要な実装は提供されたAPIを呼び出すのみ 39
  40. Cloud Firestore・Cloud Storage・Firebase Authentication 40

  41. ・悪意のあるデータ操作が行われないように  データ毎にアクセス権限を制御する必要がある ・NoSQLであるというだけでなく、”サブコレクション”  といった概念やクエリの特徴を踏まえた設計が必要 41 “自分が書いたレポートは その本人のみ読み込める ” “システム設定値は 管理者しか追加できない

    ” 例 例 何回も作っては壊して を繰り返しました...
  42. ・悪意のあるデータ操作が行われないように  データ毎にアクセス権限を制御する必要がある ・NoSQLであるというだけでなく、”サブコレクション”  といった概念やクエリの特徴を踏まえた設計が必要 何回も作っては壊して を繰り返しました... 42 “自分が書いたレポートは その本人のみ読み込める ”

    “システム設定値は 管理者しか追加できない ” 例 例
  43. ・悪意のあるデータ操作が行われないように  データ毎にアクセス権限を制御する必要がある ・NoSQLであるというだけでなく、”サブコレクション”  といった概念やクエリの特徴を踏まえた設計が必要 何回も作っては壊して を繰り返しました... 43 “自分が書いたレポートは その本人のみ読み込める ”

    “システム設定値は 管理者しか追加できない ” 例 例
  44. Cloud Firestore・Cloud Storage・Firebase Authentication 44

  45. 45

  46. 月額課金プラン契約で指導が受けられる チケットを使ってオンラインレッスン 46

  47. 月額課金プラン契約で指導が受けられる チケットを使ってオンラインレッスン 47

  48. 月額課金プラン契約で指導が受けられる チケットを使ってオンラインレッスン 48

  49. 開発工数が大きくて 障害発生時のリスク が 高そうな機能ばかりだ ... 49 実装が重そうなのは 決済処理・サブスク管理 ・チケット管理あたりだ な...

  50. 開発工数が大きくて 障害発生時のリスク が 高そうな機能ばかりだ ... 50 実装が重そうなのは 決済処理・サブスク管理 ・チケット管理あたりだ な...

  51. 開発工数が大きくて 障害発生時のリスク が 高そうな機能ばかりだ ... 51 実装が重そうなのは 決済処理・サブスク管理 ・チケット管理あたりだ な...

  52. バックエンド処理 https://firebase.google.com/docs/functions 決済機能 https://stripe.com/jp 52

  53. バックエンド処理 https://firebase.google.com/docs/functions 決済機能 https://stripe.com/jp 53 HTTPS通信 データ 読み書き 決済処理 Stripe

  54. バックエンド処理 https://firebase.google.com/docs/functions 決済機能 https://stripe.com/jp 54 HTTPS通信 データ 読み書き 決済処理 “テクノロジーファースト

    ” を掲げている点が 採用を決定した決め手! Stripe
  55. Cloud Functions・Stripe 55

  56. バックエンド処理 HTTPS通信 ・Stripeを始めとする外部サービスとの連携 ・データ整合性を安全に守ることができる  ・”レッスン予約と同時にチケットを使用済みに” データベース データ操作 サーバーレスで 任意処理を実行 56

  57. バックエンド処理 HTTPS通信 ・Stripeを始めとする外部サービスとの連携 ・データ整合性を安全に守ることができる  ・”レッスン予約と同時にチケットを使用済みに” データベース データ操作 サーバーレスで 任意処理を実行 57

  58. バックエンド処理 HTTPS通信 ・Stripeを始めとする外部サービスとの連携 ・データ整合性を安全に守ることができる  ・”レッスン予約と同時にチケットを使用済みに” データベース データ操作 サーバーレスで 任意処理を実行 58

  59. ・月毎に自動的に決済 ・複雑な料金プランに対応 ・有効期限や使用回数の制限 ・手動でもAPIからでも発行可能 59 Stripe

  60. ・月毎に自動的に決済 ・複雑な料金プランに対応 ・有効期限や使用回数の制限 ・手動でもAPIからでも発行可能 60 Stripe

  61. ・月毎に自動的に決済 ・複雑な料金プランに対応 ・有効期限や使用回数の制限 ・手動でもAPIからでも発行可能 61 Stripe

  62. ・月毎に自動的に決済 ・複雑な料金プランに対応 ・有効期限や使用回数の制限 ・手動でもAPIからでも発行可能 62 Stripe

  63. 63 ・多様な料金プランやクーポンによる割引は  有料会員を増やすために取り得る施策の一つ Stripe

  64. 64 Stripe

  65. ・発生した全ての処理をAPIベースで閲覧できる ・ほとんどの機能の操作をGUI上から行える 65 Stripe

  66. ・発生した全ての処理をAPIベースで閲覧できる ・ほとんどの機能の操作をGUI上から行える 66 ※開発環境のデータ ※開発環境のデータ Stripe

  67. ・発生した全ての処理をAPIベースで閲覧できる ・ほとんどの機能の操作をGUI上から行える 67 Stripe

  68. ・本番と同等機能をすぐに使い始められる ・様々なパターンを想定したテスト用カード番号を用意 68 Stripe

  69. ・本番と同等機能をすぐに使い始められる ・様々なパターンを想定したテスト用カード番号を用意 69 Stripe

  70. ・本番と同等機能をすぐに使い始められる ・様々なパターンを想定したテスト用カード番号を用意 70 https://stripe.com/docs/testing Stripe

  71. ・複雑な処理フローも非常に理解しやすい ・障害発生リスク軽減にも繋がる 71 Stripe

  72. Cloud Functions 72

  73. HTTPS通信 ・どんなデータが書き込まれているか全て把握するためには  フロントエンドとCloud Functionsの両方を追う必要がある ・チーム内での分業が進んでくると開発しにくくなる データベース データ読み書き バックエンド処理 データ読み書き 73

  74. 最近、開発チームにジョインしてくれたAさん Cloud Functions に関わる開発をお願いしました そのデータはフロントエンドから 直接Firestoreへ書き込んでいたのでした。 WITH Fitness エピソード 74

  75. Stripe・Cloud Functions 75

  76. 76

  77. お客様の名前を安全に管理したい Stripeへの権限を安全に管理したい リリース後の改善のために 77

  78. リリースはもう目の前! 限 られた工数の中でもしっ かりと非機能要件を実現 していきたい... 78

  79. 79 データ暗号化 https://cloud.google.com/security-key-management 分析データ収集 https://firebase.google.com/docs/analytics APIキー管理 https://cloud.google.com/secret-manager 分析データ集計 https://cloud.google.com/bigquery/?hl=ja 分析データ可視化

    https://datastudio.google.com/
  80. await client.encrypt({ name: keyName, plaintext: text }) await client.decrypt({ name:

    keyName, ciphertext: text }) 80 データ暗号化 https://cloud.google.com/security-key-management 分析データ収集 https://firebase.google.com/docs/analytics APIキー管理 https://cloud.google.com/secret-manager 分析データ集計 https://cloud.google.com/bigquery/?hl=ja 分析データ可視化 https://datastudio.google.com/
  81. await client.encrypt({ name: keyName, plaintext: text }) await client.decrypt({ name:

    keyName, ciphertext: text }) await client.accessSecretVersion({ name: keyName }) 81 データ暗号化 https://cloud.google.com/security-key-management 分析データ収集 https://firebase.google.com/docs/analytics APIキー管理 https://cloud.google.com/secret-manager 分析データ集計 https://cloud.google.com/bigquery/?hl=ja 分析データ可視化 https://datastudio.google.com/
  82. 分析基盤に関して詳しくは DeNA Advent Calendar 2020 24日目 をご覧ください await client.encrypt({ name:

    keyName, plaintext: text }) await client.decrypt({ name: keyName, ciphertext: text }) await client.accessSecretVersion({ name: keyName }) 82 データ暗号化 https://cloud.google.com/security-key-management 分析データ収集 https://firebase.google.com/docs/analytics APIキー管理 https://cloud.google.com/secret-manager 分析データ集計 https://cloud.google.com/bigquery/?hl=ja 分析データ可視化 https://datastudio.google.com/
  83. 83

  84.  自前実装を始める前に”XaaS”の存在を思い出してみる  ”XaaS”を使いこなすためにもスキルが必要なので  「フロントエンドさえ分かればアプリが作れる」訳ではない 84

  85. 85

  86. 後編 86

  87. 後編 87 WITH Fitness iOSアプリでの 取り組みをご紹介します。 AndroidやWebにも応用できる 内容になっています。

  88. 88

  89. 全ての型の基礎となる 機能毎に実装を分割 身に付けた型を素早く生み出す         89 ❶ ❷ ❸

  90. 時系列に沿って"開発の型"の 具体的な導入事例をご紹介 90

  91. 91

  92. 急いでプロダクトを 作り上げないと! 設計に時間かける? すぐ手を動かす? 採用するなら どのアーキテクチャ? Firebase使うと フロントエンド責務が大 きくなりそう... 92

  93. 急いでプロダクトを 作り上げないと! 設計に時間かける? すぐ手を動かす? 採用するなら どのアーキテクチャ? Firebase使うと フロントエンド責務が大 きくなりそう... 93

  94. 急いでプロダクトを 作り上げないと! 設計に時間かける? すぐ手を動かす? 採用するなら どのアーキテクチャ? Firebase使うと フロントエンド責務が大 きくなりそう... 94

  95. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    VIPERを基にした アーキテクチャ 95
  96. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    食事レポート 保存ボタン押さ れた VIPERを基にした アーキテクチャ 96
  97. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    食事レポート 保存ボタン押さ れた VIPERを基にした アーキテクチャ 97
  98. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    食事レポート 保存ボタン押さ れた ①ボタン押された VIPERを基にした アーキテクチャ 98
  99. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    食事レポート 保存ボタン押さ れた ①ボタン押された ②保存処理せよ VIPERを基にした アーキテクチャ 99
  100. ビジネスロジック Interactor バックエンド通信 Repository 画面遷移 Router 表示ロジック Presenter UI実装 View

    食事レポート 保存ボタン押さ れた ①ボタン押された ②保存処理せよ ③データ作成せよ VIPERを基にした アーキテクチャ 100
  101. 責務が明確なアーキテクチャ 101

  102. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View どの粒度でメ ソッド分割す る?

    これは何をし てる実装だっ け? どういう方針 で実装しよ う? 実装を分割 する粒度も明 確だな 通信関連の 処理はこの 辺りにある パターンに 沿って実装し ていこう 102
  103. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View どの粒度でメ ソッド分割す る?

    これは何をし てる実装だっ け? どういう方針 で実装しよ う? 実装を分割 する粒度も明 確だな 通信関連の 処理はこの 辺りにある パターンに 沿って実装し ていこう 103
  104. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View どの粒度でメ ソッド分割す る?

    これは何をし てる実装だっ け? どういう方針 で実装しよ う? 実装を分割 する粒度も明 確だな 通信関連の 処理はこの 辺りにある パターンに 沿って実装し ていこう 104
  105. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View データベース に書き込ん で...

    完了したら画 面遷移して... 保存ボタンが 押されたら... 送信処理を 呼び出す データベース 処理を呼び 出す 保存ボタンが 押された 105
  106. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View データベース に書き込ん で...

    完了したら画 面遷移して... 保存ボタンが 押されたら... 送信処理を 呼び出す データベース 処理を呼び 出す 保存ボタンが 押された 106
  107. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View データベース に書き込ん で...

    完了したら画 面遷移して... 保存ボタンが 押されたら... 送信処理を 呼び出す データベース 処理を呼び 出す 保存ボタンが 押された 107
  108. 責務が明確なアーキテクチャ 108

  109. ・曖昧なまま実装を進めていくことは迷いを生むことに繋がる ・開発が進むにつれて最適なルールが定まっていくはず ビジネスロジック Interactor UI実装 View Date型のデータを 文字列に変換するのは どこでやればいい...? 表示ロジック

    Presenter 109
  110. 責務が明確なアーキテクチャ 110

  111. 111

  112. VIPER導入してるけど、 それでも各クラスの記述 量が増えてきたな... 一画面に色んな機能が あるから、どれがどの 実装か分かりづらくなっ てきたな... 112

  113. VIPER導入してるけど、 それでも各クラスの記述 量が増えてきたな... 一画面に色んな機能が あるから、どれがどの 実装か分かりづらくなっ てきたな... 113

  114. VIPER導入してるけど、 それでも各クラスの記述 量が増えてきたな... 一画面に色んな機能が あるから、どれがどの 実装か分かりづらくなっ てきたな... 114

  115. View Presenter Interactor View Presenter Interactor UI実装から ビジネスロジックまで まるっと処理を切り出す 親コンポーネントは

    子コンポーネントを 生成して配置するだけ 115
  116. 116

  117. 積極的なコンポーネント化 117

  118. View Feature A View Feature B View Feature C Presenter

    Feature A Presenter Feature B Presenter Feature C Interactor Feature A Interactor Feature B Interactor Feature C これは何をし てる実装だっ け? どういう方針 で実装しよ う? View Feature A Feature B Feature C Presenter Feature A Feature B Feature C Interactor Feature A Feature B Feature C この機能のこ の処理はここ にある 実装を分割 する粒度が 明確だな 118
  119. View Feature A View Feature B View Feature C Presenter

    Feature A Presenter Feature B Presenter Feature C Interactor Feature A Interactor Feature B Interactor Feature C これは何をし てる実装だっ け? どういう方針 で実装しよ う? View Feature A Feature B Feature C Presenter Feature A Feature B Feature C Interactor Feature A Feature B Feature C この機能のこ の処理はここ にある 実装を分割 する粒度が 明確だな 119
  120. View Feature A View Feature B View Feature C Presenter

    Feature A Presenter Feature B Presenter Feature C Interactor Feature A Interactor Feature B Interactor Feature C これは何をし てる実装だっ け? どういう方針 で実装しよ う? View Feature A Feature B Feature C Presenter Feature A Feature B Feature C Interactor Feature A Feature B Feature C この機能のこ の処理はここ にある 実装を分割 する粒度が 明確だな 120
  121. 121

  122. 122

  123. 123

  124. 124

  125. 125

  126. 積極的なコンポーネント化 126

  127. 127

  128. VIPERやコンポーネント 化は便利だけどファイル 生成量やテンプレコード 量が多くなってきたな... 開発の型が決まってき たおかげで似たような コードを何度も書いて いるな... 128

  129. VIPERやコンポーネント 化は便利だけどファイル 生成量やテンプレコード 量が多くなってきたな... 開発の型が決まってき たおかげで似たような コードを何度も書いて いるな... 129

  130. VIPERやコンポーネント 化は便利だけどファイル 生成量やテンプレコード 量が多くなってきたな... 開発の型が決まってき たおかげで似たような コードを何度も書いて いるな... 130

  131. $ generamba gen ReportList viper $ generamba gen [コンポーネント名] [テンプレート名]

    generambaを利用 import UIKit import RxSwift class ReportListViewController: UIViewController { var presenter: ReportListPresenter! override func viewDidLoad() { super.viewDidLoad() } } View Presenter Interactor Router これはほんの一部で 共通化できそうな部分は 積極的にテンプレートに追加 用途に応じて テンプレートを 複数用意している 131
  132. $ generamba gen ReportList viper $ generamba gen [コンポーネント名] [テンプレート名]

    generambaを利用 import UIKit import RxSwift class ReportListViewController: UIViewController { var presenter: ReportListPresenter! override func viewDidLoad() { super.viewDidLoad() } } View Presenter Interactor Router これはほんの一部で 共通化できそうな部分は 積極的にテンプレートに追加 用途に応じて テンプレートを 複数用意している 132
  133. $ generamba gen ReportList viper $ generamba gen [コンポーネント名] [テンプレート名]

    generambaを利用 import UIKit import RxSwift class ReportListViewController: UIViewController { var presenter: ReportListPresenter! override func viewDidLoad() { super.viewDidLoad() } } View Presenter Interactor Router これはほんの一部で 共通化できそうな部分は 積極的にテンプレートに追加 用途に応じて テンプレートを 複数用意している 133
  134. 134 // // {{ module_info.file_name }} // {{ module_info.project_name }}

    // // Created by {{ developer.name }} on {{ date }}. // Copyright © {{ year }} {{ developer.company }}. All rights reserved. // import UIKit import RxSwift import RxCocoa class {{ module_info.name }}ViewController: UIViewController { var presenter: {{ module_info.name }}Presenter! let disposeBag = DisposeBag() public static func get() -> {{ module_info.name }}ViewController { return UIStoryboard(name: "{{ module_info.name }}", bundle: nil) .instantiateInitialViewController() as! {{ module_info.name }}ViewController } override func viewDidLoad() { super.viewDidLoad() setupButtonAction() bindToPresenter() observePresenter() } // private func setupCustomComponent() { // let view = presenter.getCustomComponentCreator().create() // customComponentContainerView.addSubview(view) // view.adjustParentConstraint(parent: customComponentContainerView) // } private func setupButtonAction() {} private func bindToPresenter() {} private func observePresenter() {} }
  135. 135 // // {{ module_info.file_name }} // {{ module_info.project_name }}

    // // Created by {{ developer.name }} on {{ date }}. // Copyright © {{ year }} {{ developer.company }}. All rights reserved. // import Foundation import RxSwift import RxCocoa class {{ module_info.name }}Presenter { private let router: {{ module_info.name }}Router private let interactor: {{ module_info.name }}Interactor init(router: {{ module_info.name }}Router, interactor: {{ module_info.name }}Interactor) { self.router = router self.interactor = interactor } // func getCustomComponentCreator() -> CustomComponentCreator { // return CustomComponentCreator() // } }
  136. 136 // // {{ module_info.file_name }} // {{ module_info.project_name }}

    // // Created by {{ developer.name }} on {{ date }}. // Copyright © {{ year }} {{ developer.company }}. All rights reserved. // import Foundation import RxSwift class {{ module_info.name }}Interactor { }
  137. 137 // // {{ module_info.file_name }} // {{ module_info.project_name }}

    // // Created by {{ developer.name }} on {{ date }}. // Copyright © {{ year }} {{ developer.company }}. All rights reserved. // import UIKit class {{ module_info.name }}Router: Router { static func assembleModules() -> UIViewController { let view = {{ module_info.name }}ViewController.get() let router = {{ module_info.name }}Router(viewController: view) let interactor = {{ module_info.name }}Interactor() let presenter = {{ module_info.name }}Presenter(router: router, interactor: interactor) view.presenter = presenter return view } }
  138. 自分たちに特化したコード自動生成 138

  139. View Presenter Interactor View Presenter Interactor View Presenter Interactor Cell

    Cell ViewModel DataSource あとは残り 必要な部分を 穴埋めしていく だけ これまで一機能2 週間かかってたと ころが 平均3日に! コマンドを 1行打つだけで 7割は実装できて いる感覚 139
  140. ユーザー視点に立つためにチームメンバーみんなで トレーナーによるオンライン指導を3週間体験してみることに でもリリース予定日まであと1ヶ月。 納得いく形でベータ版リリースを迎えることできた WITH Fitness エピソード 140

  141. 自分たち特化のコード自動生成 141

  142. 142

  143.  自分のサービス、自分の開発スタイルに合った  ”開発の型”を作り上げていきましょう  安定するまでは一定時間がかかると思います。  3ヶ月以上開発する予定があるサービスであれば  最初1ヶ月は試行錯誤していても良いと思います。 143

  144. 144

  145. バックエンド開発 フロントエンド開発 145

  146. 今日伝えたいこと 146

  147. 147

  148. オンライン指導に興味を持った方! 無料体験レッスンでお待ちしております with-fit.com 148

  149. プロダクト開発に興味を持った方! ご連絡お待ちしております with-fit.com 149

  150. 150

  151. 151