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

ABEMAのリアーキテクチャ

 ABEMAのリアーキテクチャ

2019/5/19 CA.swift #14

Koki Hirokawa

May 19, 2021
Tweet

More Decks by Koki Hirokawa

Other Decks in Technology

Transcript

  1. Ϟνϕʔγϣϯ  w ڞ௨ͷ࢓༷ͱ͸ʁ w υϝΠϯϞσϧ w αʔϏεશମͰڞ௨ w Ϣʔεέʔε

    w ಉ͡ωΠςΟϒΞϓϦͳΒۙ͘͠ͳΔ͸ͣ ▶︎ ڞ௨Խ͢΂͖࢓༷Λɺ࣮૷ͷதͰ໌֬Խ͍ͨ͠
  2. Ϟνϕʔγϣϯ  w ڞ௨ͷ࢓༷ͱ͸ʁ w υϝΠϯϞσϧ w αʔϏεશମͰڞ௨ w Ϣʔεέʔε

    w ಉ͡ωΠςΟϒΞϓϦͳΒۙ͘͠ͳΔ͸ͣ ▶︎ υϝΠϯϞσϧͱϢʔεέʔεΛຬ࣮ͨ͢૷Λɺ໌֬Խ͍ͨ͠
  3. ՝୊  w .77. 'MVY w 7JFX4USFBN w ը໘Ͱऩ·Δσʔλͷ؅ཧ 7JFX

    7JFX4USFBN 7JFX 7JFX4USFBN "DUJPO %JTQBUDIFS 4UPSF
  4. ՝୊  w .77. 'MVY w 7JFX4USFBN w ը໘Ͱऩ·Δσʔλͷ؅ཧ 7JFX

    7JFX4USFBN 7JFX 7JFX4USFBN "DUJPO %JTQBUDIFS 4UPSF 7JFX 7JFX4USFBN 7JFX 7JFX4USFBN "DUJPO %JTQBUDIFS 4UPSF w .77. 'MVY w 7JFX4USFBN w ը໘Ͱऩ·Δσʔλͷ؅ཧ w 'MVY w ը໘είʔϓΛ௒͑Δσʔλ w ΞϓϦશମͰ࢖༻͢Δσʔλ
  5. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w ϓϥοτϑΥʔϜʹґଘ͠ͳ͍ 
 ڞ௨Խ͞ΕͨυϝΠϯϞσϧ w 3FQPTJUPSZͷΠϯλʔϑΣʔε
  6. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w ϓϥοτϑΥʔϜຖʹҟͳΔΞϓϦέʔγϣϯ ϩδοΫ w υϝΠϯϞσϧΛ༻͍ͯɺΞϓϦͷ࢓༷Λ࣮૷ ͢Δ w Ϣʔεέʔε͸ଞͷϢʔεέʔεΛݺ͹ͳ͍
  7. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w 6TF$BTFͷΠϯλʔϑΣʔε w 6*-PHJDͱ΍ΓͱΓ͢ΔͨΊͷ%50 w ϓϥοτϑΥʔϜຖʹҟͳΔΞϓϦέʔγϣϯ ϩδοΫ w υϝΠϯϞσϧΛ༻͍ͯɺΞϓϦͷ࢓༷Λ࣮૷ ͢Δ w Ϣʔεέʔε͸ଞͷϢʔεέʔεΛݺ͹ͳ͍
  8. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w 7JFX4USFBNͷΠϯλʔϑΣʔε w *OQVUʢ7JFX͔ΒͷΠϕϯτʣ w 0VUQVUʢ7JFX΁൓ө͢Δσʔλʣ w 6*$PNQPOFOUͱ΍ΓͱΓ͢ΔͨΊͷ%50 w 7JFX4USFBN w ԿΛը໘ʹදࣔ͢Δ͔ɺΛႊΊΔ
  9. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w υϝΠϯϞσϧͷӬଓԽ w αʔό΍ϩʔΧϧετϨʔδ͔ΒσʔλΛऔಘ͠ɺ υϝΠϯϞσϧ΁ม׵
  10. Ϟδϡʔϧߏ੒  3FQPTJUPSZ 6TF$BTF 6TF$BTF*OUFSGBDF 6*-PHJD*OUFSGBDF 6*$PNQPOFOU %PNBJO 6*-PHJD "QQ

    w "QQ5BSHFU w ͢΂ͯͷίϯϙʔωϯτʹґଘ͠ɺ֤ίϯϙʔωϯτͷ Πϯελϯεੜ੒΍%FQFOEFODZ*OKFDUJPOΛߦ͏ɻ
  11. %PNBJO  struct RentalSalesItem { let id: ID let title:

    String let startAt: Date let endAt: Date let price: Int func isOnSale(date: Date) -> Bool { startAt..<endAt ~= date } }
  12. %PNBJO  struct RentalSalesItem { let id: ID let title:

    String let startAt: Date let endAt: Date let price: Int func isOnSale(date: Date) -> Bool { startAt..<endAt ~= date } } w υϝΠϯϩδοΫ w Ϩϯλϧ঎඼͕ൢചத͔ʁ w FOE"U͸ൢചظؒʹؚ·Εͳ͍
  13. 3FQPTJUPSZ*OUFSGBDF  protocol RentalSalesItemRepositoryType: AnyObject { func fetchRentalSalesItems(programID: Program.ID) ->

    RxSwift.Single<[RentalSalesItem]> } w 3FQPTJUPSZ͸υϝΠϯϞσϧͷӬଓԽΛߦ͏ w QSPHSBN*%Λड͚औΓɺ3FOUBM4BMFT*UFNͷ഑ྻΛฦ͢ w σʔλͷऔಘ͕ͲͷΑ͏ʹߦΘΕΔ͔͸෼͔Βͳ͍
  14. 3FQPTJUPSZ  class RentalSalesItemRepository: RentalSalesItemRepositoryType { func fetchRentalSalesItems(programID: Program.ID) ->

    RxSwift.Single<[RentalSalesItem]> { let request = ApiSession.Rental_SalesItemsRequest(programID: programID.rawValue) return apiSession.send(request) .map { response in response.items.map(RentalSalesItem.init) } } }
  15. 3FQPTJUPSZ  class RentalSalesItemRepository: RentalSalesItemRepositoryType { func fetchRentalSalesItems(programID: Program.ID) ->

    RxSwift.Single<[RentalSalesItem]> { let request = ApiSession.Rental_SalesItemsRequest(programID: programID.rawValue) return apiSession.send(request) .map { response in response.items.map(RentalSalesItem.init) } } } w ϦΫΤετͷ࡞੒ w "1*4FTTJPOΛར༻ͨ͠σʔλͷऔಘ
  16. 3FQPTJUPSZ  class RentalSalesItemRepository: RentalSalesItemRepositoryType { func fetchRentalSalesItems(programID: Program.ID) ->

    RxSwift.Single<[RentalSalesItem]> { let request = ApiSession.Rental_SalesItemsRequest(programID: programID.rawValue) return apiSession.send(request) .map { response in response.items.map(RentalSalesItem.init) } } } w औಘͨ͠σʔλΛυϝΠϯϞσϧ΁ม׵
  17. 3FQPTJUPSZ  extension RentalSalesItem { init(response: Rental_SalesItem) { let startAt

    = Date(timeIntervalSince1970: TimeInterval(response.startAt)) let endAt = Date(timeIntervalSince1970: TimeInterval(response.endAt)) return RentalSalesItem( id: response.id, title: response.title, startAt: startAt, endAt: endAt, price: response.price ) } } w Ϩεϙϯε͔ΒυϝΠϯϞσϧΛੜ੒͢Δॲཧ͸ɺ3FQPTJUPSZ಺Ͱߦ͏ w %PNBJO͸֎෦ʹґଘ͠ͳ͍ͨΊ
  18. 6TF$BTF*OUFSGBDF  protocol RentalConfirmUseCaseType: AnyObject { typealias UseCaseModel = RentalConfirmUseCaseModel

    func listSalesItems(programID: String, date: Date) -> RxSwift.Single<UseCaseModel.SalesItems> }
  19. 6TF$BTF*OUFSGBDF  protocol RentalConfirmUseCaseType: AnyObject { typealias UseCaseModel = RentalConfirmUseCaseModel

    func listSalesItems(programID: String, date: Date) -> RxSwift.Single<UseCaseModel.SalesItems> } w ʮϨϯλϧ঎඼ΛҰཡදࣔ͢ΔʯϢʔεέʔε w ϢʔεέʔεΛදݱ͢Δϝιου໊
  20. 6TF$BTF*OUFSGBDF  protocol RentalConfirmUseCaseType: AnyObject { typealias UseCaseModel = RentalConfirmUseCaseModel

    func listSalesItems(programID: String, date: Date) -> RxSwift.Single<UseCaseModel.SalesItems> } w 6*-PHJDͱ΍ΓͱΓ͢ΔͨΊͷ%50 w %PNBJOʹґଘͤ͞ͳ͍ͨΊ%PNBJO3FOUBM4BMFT*UFN͸௚઀ฦ͞ͳ͍
  21. 6TF$BTF*OUFSGBDF  enum RentalConfirmUseCaseModel { enum SalesItems { case items([SalesItem])

    case empty } struct SalesItem { let id: String let title: String let price: Int } }
  22. 6TF$BTF*OUFSGBDF  enum RentalConfirmUseCaseModel { enum SalesItems { case items([SalesItem])

    case empty } struct SalesItem { let id: String let title: String let price: Int } } w ఆٛ͸࢓༷ʹΑͬͯมԽ͢Δ
  23. 6TF$BTF*OUFSGBDF  enum RentalConfirmUseCaseModel { enum SalesItems { case items([SalesItem])

    case empty } struct SalesItem { let id: String let title: String let price: Int } } w ఆٛ͸࢓༷ʹΑͬͯมԽ͢Δ w ࢓༷ w ൢചதͷϨϯλϧ঎඼ͷΈΛදࣔ͢Δ w 6TF$BTFʹͯϑΟϧλϦϯά w TUBSU"UͱFOE"U͸ෆཁ
  24. 6TF$BTF*OUFSGBDF  enum RentalConfirmUseCaseModel { enum SalesItems { case items([SalesItem])

    case empty } struct SalesItem { let id: String let title: String let price: Int } } w ఆٛ͸࢓༷ʹΑͬͯมԽ͢Δ w ࢓༷ w ൢചதͷϨϯλϧ঎඼ͷΈΛදࣔ͢Δ w 6TF$BTFʹͯϑΟϧλϦϯά w TUBSU"UͱFOE"U͸ෆཁ w දࣔͰ͖ΔϨϯλϧ঎඼͕ͳ͍৔߹͸Τϥʔ Λදࣔ͢Δ
  25. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF 
  26. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF  w 3FQPTJUPSZ͔ΒϨϯλϧ঎඼ҰཡΛऔಘ w ฦΓ஋͸<%PNBJO3FOUBM4BMFT*UFN>
  27. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF  w ࢓༷ w ൢചதͷϨϯλϧ঎඼ͷΈΛදࣔ͢Δ w υϝΠϯϩδοΫΛར༻
  28. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF  w %50΁ͷม׵
  29. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF  w ࢓༷ w දࣔͰ͖ΔϨϯλϧ঎඼͕ͳ͍৔߹͸Τ ϥʔΛදࣔ͢Δ w 4BMFT*UFNTFNQUZͱͯ͠දݱ
  30. class RentalConfirmUseCase: RentalConfirmUseCaseType { func listSalesItems(programID: String, date: Date) ->

    RxSwift.Single<UseCaseModel.SalesItems> { rentalSalesItemRepository.fetchRentalSalesItems(programID: programID) .map { items in let availableItems = items.compactMap { item -> UseCaseModel.SalesItem in guard item.isOnSale(date: date) else { return nil } return UseCaseModel.SalesItem( id: item.id, title: item.title, price: item.price ) } guard !availableItems.isEmpty else { return .empty } return .items(availableItems) } .catchErrorJustReturn(.empty) } } 6TF$BTF  w λΠϜΞ΢τΤϥʔͳͲͷϋϯυϦϯά
  31. 6*-PHJD*OUFSGBDF  protocol RentalConfirmViewStreamType: AnyObject { var input: InputWrapper<RentalConfirmViewStreamInput> {

    get } var output: OutputWrapper<RentalConfirmViewStreamOutput> { get } } struct RentalConfirmViewStreamInput: InputType { let viewDidAppear = RxRelay.PublishRelay<Void>() } struct RentalConfirmViewStreamOutput: OutputType { let sectionModels: RxSwift.Observable<[RentalConfirmSectionModel]> let showAlert: RxSwift.Observable<Void> }
  32. 6*-PHJD*OUFSGBDF  w *OQVUͷఆٛ w 7JFX͔Β7JFX4USFBN΁ͷΠϕϯτ w WJFX%JE-PBE΍WJFX%JE"QQFBSɺϢʔβʹΑΔϘλϯͷԡԼͳͲ protocol RentalConfirmViewStreamType:

    AnyObject { var input: InputWrapper<RentalConfirmViewStreamInput> { get } var output: OutputWrapper<RentalConfirmViewStreamOutput> { get } } struct RentalConfirmViewStreamInput: InputType { let viewDidAppear = RxRelay.PublishRelay<Void>() } struct RentalConfirmViewStreamOutput: OutputType { let sectionModels: RxSwift.Observable<[RentalConfirmSectionModel]> let showAlert: RxSwift.Observable<Void> }
  33. 6*-PHJD*OUFSGBDF  w 0VUQVUͷఆٛ w 7JFX΁൓ө͢Δσʔλ w Ϙλϯͷঢ়ଶɺΞϥʔτͷදࣔλΠϛϯάͷ௨஌ͳͲ protocol RentalConfirmViewStreamType:

    AnyObject { var input: InputWrapper<RentalConfirmViewStreamInput> { get } var output: OutputWrapper<RentalConfirmViewStreamOutput> { get } } struct RentalConfirmViewStreamInput: InputType { let viewDidAppear = RxRelay.PublishRelay<Void>() } struct RentalConfirmViewStreamOutput: OutputType { let sectionModels: RxSwift.Observable<[RentalConfirmSectionModel]> let showAlert: RxSwift.Observable<Void> }
  34. 6*-PHJD  let salesItems = useCase.listSalesItems(programID: programID, date: getCurrentDate()) salesItems

    .flatMap { salesItems -> Observable<[RentalConfirmSectionItem]> in guard case let .items(_items) = salesItems else { return .empty() } let sectionModels = _items.map { ... } return .just(items) } .bind(to: state.sectionModels) dependency.inputObservables.viewDidAppear .flatMap { _ -> Observable<RentalConfirmAlert> in salesItems .flatMap { items -> Observable<Void> in guard items == .empty else { return .empty() } return .just(()) } } .bind(to: state.showAlert) .disposed(by: disposeBag)
  35. 6*-PHJD  let salesItems = useCase.listSalesItems(programID: programID, date: getCurrentDate()) salesItems

    .flatMap { salesItems -> Observable<[RentalConfirmSectionItem]> in guard case let .items(_items) = salesItems else { return .empty() } let sectionModels = _items.map { ... } return .just(items) } .bind(to: state.sectionModels) dependency.inputObservables.viewDidAppear .flatMap { _ -> Observable<RentalConfirmAlert> in salesItems .flatMap { items -> Observable<Void> in guard items == .empty else { return .empty() } return .just(()) } } .bind(to: state.showAlert) .disposed(by: disposeBag) w 6TF$BTF͔ΒϨϯλϧ঎඼ҰཡΛऔಘ
  36. 6*-PHJD  let salesItems = useCase.listSalesItems(programID: programID, date: getCurrentDate()) salesItems

    .flatMap { salesItems -> Observable<[RentalConfirmSectionItem]> in guard case let .items(_items) = salesItems else { return .empty() } let sectionModels = _items.map { ... } return .just(items) } .bind(to: state.sectionModels) dependency.inputObservables.viewDidAppear .flatMap { _ -> Observable<RentalConfirmAlert> in salesItems .flatMap { items -> Observable<Void> in guard items == .empty else { return .empty() } return .just(()) } } .bind(to: state.showAlert) .disposed(by: disposeBag) w Ϩϯλϧ঎඼͕͋Δ৔߹ w 4FDUJPO.PEFMʢ7JFXͱ΍ΓͱΓ͢Δ %50ʣ΁ม׵
  37. 6*-PHJD  let salesItems = useCase.listSalesItems(programID: programID, date: getCurrentDate()) salesItems

    .flatMap { salesItems -> Observable<[RentalConfirmSectionItem]> in guard case let .items(_items) = salesItems else { return .empty() } let sectionModels = _items.map { ... } return .just(items) } .bind(to: state.sectionModels) dependency.inputObservables.viewDidAppear .flatMap { _ -> Observable<RentalConfirmAlert> in salesItems .flatMap { items -> Observable<Void> in guard items == .empty else { return .empty() } return .just(()) } } .bind(to: state.showAlert) .disposed(by: disposeBag) w Ϩϯλϧ঎඼͕ͳ͍৔߹ w ΞϥʔτΛදࣔ͢ΔͨΊͷΠϕϯτ ΛൃՐ
  38. 6*$PNQPOFOU  override func viewDidLoad() { super.viewDidLoad() viewStream.output.sectionModels .bind(to: tableView.rx.items(dataSource:

    dataSource)) .disposed(by: disposeBag) viewStream.output.showAlert .observeOn(ConcurrentMainScheduler.instance) .subscribe(onNext: { [weak self] in guard let me = self else { return } let alert = UIAlertController(title: "💀", message: nil, preferredStyle: .alert) me.present(alert, animated: true) }) .disposed(by: disposeBag) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear() viewStream.input.viewDidAppear(()) }
  39. 6*$PNQPOFOU  override func viewDidLoad() { super.viewDidLoad() viewStream.output.sectionModels .bind(to: tableView.rx.items(dataSource:

    dataSource)) .disposed(by: disposeBag) viewStream.output.showAlert .observeOn(ConcurrentMainScheduler.instance) .subscribe(onNext: { [weak self] in guard let me = self else { return } let alert = UIAlertController(title: "💀", message: nil, preferredStyle: .alert) me.present(alert, animated: true) }) .disposed(by: disposeBag) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear() viewStream.input.viewDidAppear(()) } w 7JFX4USFBN΁ͷ*OQVU
  40. override func viewDidLoad() { super.viewDidLoad() viewStream.output.sectionModels .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by:

    disposeBag) viewStream.output.showAlert .observeOn(ConcurrentMainScheduler.instance) .subscribe(onNext: { [weak self] in guard let me = self else { return } let alert = UIAlertController(title: "💀", message: nil, preferredStyle: .alert) me.present(alert, animated: true) }) .disposed(by: disposeBag) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear() viewStream.input.viewDidAppear(()) } 6*$PNQPOFOU  w 4FDUJPO.PEFMTΛ6*5BCMF7JFX΁CJOE
  41. override func viewDidLoad() { super.viewDidLoad() viewStream.output.sectionModels .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by:

    disposeBag) viewStream.output.showAlert .observeOn(ConcurrentMainScheduler.instance) .subscribe(onNext: { [weak self] in guard let me = self else { return } let alert = UIAlertController(title: "💀", message: nil, preferredStyle: .alert) me.present(alert, animated: true) }) .disposed(by: disposeBag) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear() viewStream.input.viewDidAppear(()) } 6*$PNQPOFOU  w Ξϥʔτͷදࣔ
  42. "QQ  let rentalSalesItemRepository = RentalConfirmRepository(apiSession: ApiSession.shared) let useCase =

    RentalConfirmUseCase(rentalSalesItemRepository: rentalSalesItemRepository) let viewStream = RentalConfirmViewStream( programID: programID, useCase: useCase, getCurrentDate: { Date() } ) let viewController = RentalConfirmViewController(viewStream: viewStream) present(viewController)