Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ちいさく始めるレイヤードアーキテクチャ
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
tondol
October 02, 2017
Programming
2.1k
7
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ちいさく始めるレイヤードアーキテクチャ
俺コン Vol.1 / Day.1 Track C で発表した資料になります。
#orecon_ios #kyobashiswift
tondol
October 02, 2017
More Decks by tondol
See All by tondol
RxSwift 3.3.0: Observable のフレンズが増えました!!
tondol
2
2.8k
Amazon Cloud Driveのご紹介
tondol
0
570
自家製オタクソリューションの紹介
tondol
1
560
ドはDockerのド
tondol
1
2.9k
Other Decks in Programming
See All in Programming
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.8k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
260
AI 輔助遺留系統現代化的經驗分享
jame2408
1
980
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
200
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
120
OSもどきOS
arkw
0
590
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
190
Oxcを導入して開発体験が向上した話
yug1224
4
340
Vite+ Unified Toolchain for the Web
naokihaba
0
340
さぁV100、メモリをお食べ・・・
nilpe
0
150
Webフレームワークの ベンチマークについて
yusukebe
0
180
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
130
Featured
See All Featured
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
860
Test your architecture with Archunit
thirion
1
2.3k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
540
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
160
How GitHub (no longer) Works
holman
316
150k
Producing Creativity
orderedlist
PRO
348
40k
エンジニアに許された特別な時間の終わり
watany
107
250k
Faster Mobile Websites
deanohume
310
32k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
340
Side Projects
sachag
455
43k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
310
Transcript
ちいさく始める レイヤード アーキテクチャ @tondol from Kyobashi.swift 俺コン Vol.1 / Day.1
– 2017.10.2 (Mon.) 1
このトークでは • 先⼈から受け継いだコードベースに、 レイヤードアーキテクチャを導⼊したときに 考えたことをお話します • 俺が考えた最強のアーキテクチャ!ってよりは、 実際にやるならこんぐらいのバランスも いいんでないの、くらいのテンション感で 紹介していきます
2
こんな話はしません • RxSwift の使い⽅ • サンプルコード中に⾃然に出てきます • 知らない⼈は、Observable<T> は Promise<T>
的な ものだと思って⾒てください • DDD (Domain-Driven-Design) の紹介や説明 • Tech Boosterさんの同⼈誌を読んだ程度なので、 そもそも語れるほど理解していません・・・ • レイヤードアーキテクチャetc.は 「DDD を実装に落とし込む際に便利なテク」 くらいの捉え⽅をしています 3
About Me • リクルートマーケティングパートナーズ • iOSエンジニア (ときどきJava/Kotlinも) • Kyobashi.swift 4
Kyobashi.swift • リクルートマーケティングパートナーズの オフィスが東京・京橋にあります • オフィス内のスペースで Swift/iOS の勉強会を 不定期で開催しています •
AKIBA.swift (クラスメソッドさん) や Otemachi.swift (⽇経新聞さん) とコラボも! • 最新情報は • https://kyobashi-swift.connpass.com/ 5
ここから本題 6
巷にあふれるアーキテクチャ の話題・・・ MVC MVP MVVM Flux Redux Clean Architecture DDD
Layered Architecture Fat ViewController 絶対に 殺すマン 7
iOSDCのCfPでもこんなに! アーキテクチャを移⾏する by @d_date Layered Architecture x RxSwiftを活⽤した 適切なエラーハンドリング by
@nonchalant0303 Redux+Rxを活⽤したアプリアーキテクチャ by @susieyy MVC→MVP→MVVM→Fluxの 実装の違いを⽐較してみる by @marty_suzuki 節⼦、それViewControllerやない...、 FatViewControllerや...。 by @ktanaka117 漸進的にViewControllerの肥⼤化を防ぐ by @kazuhiro494949 レッドオーシャン感 8
Clean Architecture Presenter View UseCase Translator Model DataStore Entity Presentation
Domain Infrastructure Repository 9
1画⾯に必要なクラス数 FooViewController FooPresenter FooUseCase FooTranslator FooModel FooRepository FooDataStore FooEntity Presentation/Domain
Domain/Infrastructure 8コンポーネント! 10 5分⽬安
本当にこんなに必要? • そもそも何のためにやるんだっけ? • 新規の開発ならともかく、 既存アプリのリファクタリングのために、 たくさんのクラスを実装するのは⾟い・・・ • メンテナンス性と開発速度のバランス 11
ちいさく始める レイヤード アーキテクチャ ちいさく始める: 必要最⼩限のコードで「欲しいもの」を 実現するアーキテクチャを考える 12
リファクタ前の状況 ⼤量のソースに分割されているが 密結合しまくりで 単体テストが書けないクラス群 通信も UI 更新も全部やる、 絵に描いたような FatViewController 機能追加のついでに
モダンアーキテクチャを 導⼊しよう! 設 計 13
欲しいもの • テスト可能な実装 • UIViewController や UIView のテストは厳しい • これらのコンポーネントに依存しないクラスに
アプリのロジックを書きたい • 依存するコンポーネントは外部から注⼊可能に • ⼊れ替え可能なインフラレイヤー • 永続化する先は API かもしれないし、 ローカル DB かもしれない • テストの観点からも⼊れ替え可能であるべき 14
割り切りポイント (1) • Model と Entity を共通化 • 表⽰⽤に必要なプロパティは Extension
にすれば、 ⼀旦実装を分離できる • Translator などが不要になり、 ボイラープレートコードを減らすことができる Presenter View UseCase Translator DataStore Presentation Domain Infrastructure Repository Model 共通化 15
割り切りポイント (2) • Repository を省略 • DataStore を動的に切り替えたりしないのなら、 UseCase に直接
DataStore を注⼊すれば⼗分 Presenter View UseCase DataStore Presentation Domain Infrastructure Model Repository 省略 16
割り切りポイント (3) • RxSwift の導⼊ • 「ちいさく」はないかもしれないが、 中⻑期的に⾒れば開発速度の向上に寄与するはず Presenter View
UseCase DataStore Presentation Domain Infrastructure Model この辺のやりとりに RxSwift を導⼊ 17
こうなりました Presenter View Controller UseCase DataStore Presentation Domain Infrastructure Model
18
サンプルアプリ • ニュースの⼀覧画⾯と詳細画⾯ • ⼀覧画⾯と詳細画⾯に Like ボタン 19
Presenter の実装 class NewsListPresenter: NSObject { let newsList = Variable([News]())
let showDetailViewControllerMessage = PublishSubject<Int>() private let useCase: NewsUseCase private let bag = DisposeBag() init(useCase: NewsUseCase) { self.useCase = useCase } extension NewsListPresenter: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.newsList.value.count } 最後の [News] を保持 更新時にイベント発⽣ 詳細画⾯の 表⽰イベント 依存 UseCase を イニシャライザから 注⼊ [News] を参照し Cell を提供する UITableViewDataSource 20 10分⽬安
UI と Presenter のバインド class NewsListViewController: UIViewController { private var
presenter: NewsListPresenter! ... private func setupBindings() { self.presenter.newsList .asDriver() .drive(onNext: { [unowned self] _ -> Void in self.tableView.reloadData() }) .disposed(by: self.bag) self.presenter.showDetailViewControllerMessage .asDriver(onErrorDriveWith: Driver.empty()) .drive(onNext: { [unowned self] id -> Void in self.showDetailViewController(id: id) }) .disposed(by: self.bag) } 新しい [News] が来たら reloadData を発⾏ イベントが来たら 詳細画⾯を push 21
初回ロードの流れ setupUI() viewDidLoad() fetchNewsList() fetchNewsList() Variable<[News]> NewsListPresenter: UITableViewDataSource NewsListViewController: UIViewController,
UITableViewDelegate NewsUseCaseImpl: NewsUseCase NewsStoreImpl: NewsStore tableView. reloadData() Update Observable<[News]> Observable<[News]> onNext ⾮同期処理の戻り値は Observable<T> 新しい [News] が来たら reloadData を発⾏ 22
セルタップ時の流れ selectNews() didSelectRowAt(…) PublishSubject <Int> NewsListPresenter: UITableViewDataSource NewsListViewController: UIViewController, UITableViewDelegate
NewsUseCaseImpl: NewsUseCase NewsStoreImpl: NewsStore showDetail ViewController(id: Int) onNext onNext イベントが来たら 詳細画⾯を push 23
Presenter のテスト • モックした DataStore を使い、 Presenter – UseCase を⼀気にテストする例
NewsListPresenter NewsUseCaseImpl NewsStoreMock Input (メソッドコール) Output (戻り値やイベント列) 24
⾮同期処理のテスト (1) class NewsListPresenterSpec: QuickSpec { override func spec() {
var presenter: NewsListPresenter! var bag: DisposeBag! ... _ = try! presenter.setupUI().toBlocking().first() let newsList = try! presenter.newsList.asObservable().toBlocking().first() // setupUI の完了後は News が 3 個表⽰されており、 // 先頭の News は id=1 expect(newsList).to(haveCount(3)) expect(newsList?[0].id) == 1 setupUI の完了を待機 更新後の Variable<T> を取得し Assertion 25
⾮同期処理のテスト (2) class NewsListPresenterSpec: QuickSpec { override func spec() {
var presenter: NewsListPresenter! var bag: DisposeBag! ... let scheduler = TestScheduler(initialClock: 0) let observer = scheduler.createObserver(Int.self) presenter.showDetailViewControllerMessage .subscribe(observer).disposed(by: bag) _ = try! presenter.setupUI().toBlocking().first() presenter.selectNews(indexPath: IndexPath(row: 0, section: 0)) // selectNews に先頭の IndexPath を渡すと、id=1 の News を詳細表⽰ expect(observer.events).to(haveCount(1)) expect(observer.events[0].value) == Event.next(1) テスト⽤の Observer を⽤意 得られたイベント列を Assertion 26
欲しいもの再考 • テスト可能な実装 • Presenter だけのテストもできるし、 UseCase や Store を含む横着テストもできる
• 依存コンポーネントはイニシャライザで注⼊ • ⼊れ替え可能なインフラレイヤー • UseCase と Store は疎結合なので、 必要に応じてデータレイヤーをモックしたり、 実装を差し替えたりできる • インフラレイヤーの内部実装を変更しても、 ドメインレイヤーには影響しない 27
悩みどころ • UseCase の責務が Presenter と DataStore 間の 仲介のみで、あんまり仕事をしていない気が •
API クライアント的なアプリだと特にこうなりがち • ViewController 周りの調整を⼀⼿に引き受ける Presenter が肥⼤化しがち • ViewController 同⼠のイベント送受信どうやる? • 現状は ViewController に⽣やした PublishSubject<T> を 遷移元の ViewController で subscribe している • Redux のようにアプリ全体で ひとつのイベントストリームを共有すれば解決? 28
まとめ • レイヤードアーキテクチャを⼀部導⼊した結果、 実装⼯数を抑えながら欲しいものを得られた • とはいえ、やはり銀の弾丸は存在しない • 各プロダクトで必要としているものを検討し、 アーキテクチャも取捨選択するのが⼤事 •
ソースコードは下記リポジトリから読めます! • https://github.com/tondol/LayeredArchSampl e 29
参考資料 • RxSwift? いやClean Swiftっしょ • https://qiita.com/takahia/items/67b9e122968 2127d924e • まだMVC,MVP,MVVMで消耗してるの?
iOS Clean Architectureについて • https://qiita.com/koutalou/items/07a4f9cf51a 2d13e4cdc • iOSDesignPatternSamples • https://github.com/marty- suzuki/iOSDesignPatternSamples 30 15分⽬安