Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
ちいさく始めるレイヤードアーキテクチャ
Search
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
590
自家製オタクソリューションの紹介
tondol
1
570
ドはDockerのド
tondol
1
2.9k
Other Decks in Programming
See All in Programming
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
180
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
430
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
210
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
5
4.7k
クラウドエージェントは、 何がそんなに便利なのか / what makes cloud agents so useful
rkaga
3
560
Lambda durable functionsでHuman in the Loopを楽に…楽に……
har1101
1
200
graphql-ruby を Spinel で速くしようとした話
mtsmfm
0
120
Inertia Rails: The One-Person Framework with React & Friends
skryukov
0
130
気持ちぃ〜角丸 Squircle
ryu1013
0
340
最近”作らせてる” ローカルAIアプリの話
akit37
0
500
Vue Fes Japan の “作る側”に関わってみて
naokihaba
1
190
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
7k
Featured
See All Featured
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
Heart Work Chapter 1 - Part 1
lfama
PRO
10
37k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
360
Into the Great Unknown - MozCon
thekraken
41
2.8k
Designing for Performance
lara
612
70k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.4k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
2
490
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
310
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
Typedesign – Prime Four
hannesfritz
42
3.2k
Six Lessons from altMBA
skipperchong
29
4.5k
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分⽬安