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議論を重ね、効果的なアイデアを迅速に受け入れる開発体制をどのように構築し開発を進めてきたか、本セッションでご紹介します。

DeNA_Tech

March 03, 2021
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. 11

  2. 13

  3. 16

  4. 17

  5. 画像動画のやり取り データの読み書き 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
  6. 会員登録・ログイン 画像動画のやり取り データの読み書き 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
  7. 45

  8. 76

  9. 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/
  10. 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/
  11. 分析基盤に関して詳しくは 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/
  12. 83

  13. 85

  14. 88

  15. 91

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

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

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

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

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

    食事レポート 保存ボタン押さ れた ①ボタン押された ②保存処理せよ ③データ作成せよ VIPERを基にした アーキテクチャ 100
  21. View ビジネスロジック Interactor 表示ロジック Presenter UI実装 View どの粒度でメ ソッド分割す る?

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

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

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

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

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

    完了したら画 面遷移して... 保存ボタンが 押されたら... 送信処理を 呼び出す データベース 処理を呼び 出す 保存ボタンが 押された 107
  27. 111

  28. 116

  29. 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
  30. 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
  31. 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
  32. 121

  33. 122

  34. 123

  35. 124

  36. 125

  37. 127

  38. $ 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
  39. $ 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
  40. $ 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
  41. 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() {} }
  42. 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() // } }
  43. 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 { }
  44. 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 } }
  45. View Presenter Interactor View Presenter Interactor View Presenter Interactor Cell

    Cell ViewModel DataSource あとは残り 必要な部分を 穴埋めしていく だけ これまで一機能2 週間かかってたと ころが 平均3日に! コマンドを 1行打つだけで 7割は実装できて いる感覚 139
  46. 142

  47. 144

  48. 147

  49. 150

  50. 151