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
Kazuki Nara
March 01, 2019
Programming
110
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
プロジェクト開始以来継ぎ足しながら使ってきたソースを捨てた話
LT
Kazuki Nara
March 01, 2019
More Decks by Kazuki Nara
See All by Kazuki Nara
FlutterアプリでChromecastに接続する
kazukinr
2
1.1k
Room with Kotlin
kazukinr
0
33
AOSPにパッチを送ってみた
kazukinr
0
48
作ろう! Android TVアプリ
kazukinr
0
53
Other Decks in Programming
See All in Programming
SREは、MCPとSRE Agentをこう使え!
kazumax55
0
130
自作OSでスライド発表する
uyuki234
1
3.6k
A2UI という光を覗いてみる
satohjohn
1
170
OS アップデート対応の取り組み方がもっと共有されてほしい
andpad
0
110
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
270
吝嗇家のためのAI活用 / AI development for miser - ChatGPT + Issue Driven Development
tooppoo
0
150
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
220
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
320
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
150
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
780
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
280
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.6k
Featured
See All Featured
Raft: Consensus for Rubyists
vanstee
141
7.6k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
320
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
240
Become a Pro
speakerdeck
PRO
31
6k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
490
Amusing Abliteration
ianozsvald
1
220
How STYLIGHT went responsive
nonsquared
100
6.2k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
Un-Boring Meetings
codingconduct
0
330
Leo the Paperboy
mayatellez
7
1.9k
Marketing to machines
jonoalderson
1
5.5k
The Language of Interfaces
destraynor
162
27k
Transcript
プロジェクト開始以来 継ぎ足しながら使ってきた 秘伝のソースを捨てた話 Kazuki Nara @ AWA Co., Ltd.
About AWA 定額制音楽ストリーミングサービス 2014年12月1日 AWA株式会社設立 2015年5月27日 AWAリリース 2019年1月24日 フルリニューアルのV2リリース 詳しくはこちら https://awa.fm
長期運用に起因する問題(プロダクト) - 機能追加の連続により高エントロピーな画面構成 - ユーザーが何をすればいいのかわかりづらくなっている - 追加されたがほぼ使われていない機能・画面の存在 - リリース時点では洗練されていたが、3年を経て陳腐化したUI
長期運用に起因する問題(技術的負債) - 機能追加のスピードを重視したため追加される場当たり的な実装(FIXME) - 実装時期のトレンドによってまちまちな設計方針 - 機能廃止に対応できず、バージョンを上げられないライブラリ - 職人芸ハックによる可読性の低下。新規メンバーが理解しがたいコード e.g.
RealmのオレオレRx実装、RxJava1 <> RxJava2変換の連続 - 肥大化するView層 - 増大する機能追加・更新時の実装コスト - 疲弊するエンジニア
フルリニューアルの決断 - プロダクトの全面刷新が必要という判断 - アーキテクチャの見直しを行いたいエンジニアの要望
フルリニューアルの決断 - プロダクトの全面刷新が必要という判断 - アーキテクチャの見直しを行いたいエンジニアの要望 → 全面刷新するなら中身から作り替えよう、という判断
アーキテクチャ設計に影響する前提条件 差分API - クライアントはAPIの結果をキャッシュする - APIはクライアントのキャッシュと最新状態との差分を返す - キャッシュはオフライン時やAPI実行完了前の表示に利用される Realmの制約 -
クライアントアプリのデータキャッシュとしてRealmを採用している - Realmにアクセスする場合は同一Threadでopen-closeする必要がある - Realmから取得したObjectは同一Threadからしかアクセスできない
AWA V1(リリース時) View ApiClient DbClient Realm API Model
AWA V1(終盤) View Use Case ApiClient DbClient Realm SQLite API
Model Prefs Cache ViewModel
構造から見てとれる問題点 - レイヤー構造になっていない - Viewから呼び出すレイヤーがViewMode / Use Case / Modelとバラバラ
- あらゆる層がPrefsを参照している = Contextを参照している
プレイリスト情報を表示する(V1) - Realmからキャッシュを取得して表示する(main thread) - APIをコールして最新データを取得する - Realmのキャッシュを更新する(I/O thread) -
Realmの更新がmain threadに適用されるまで待つ - Realmから更新後のデータを取得して表示する(main thread)
プレイリスト情報を表示する(V1) PlaylistModel.kt fun fetchPlaylistById(playlistId: String): Single<String> = playlistApiClient.getPlaylistDetail(playlistId) .subscribeOn(Schedulers.io()) .flatMapCompletable
{ playlistDbClient.save(it) } .andThen(Single.just(playlistId)) fun getPlaylistById(playlistId: String): Maybe<Playlist> = Maybe.fromCallable { playlistDbClient.getById(playlistId).firstOrNull() }
プレイリスト情報を表示する(V1) PlaylistDetailActivity.kt fun onCreate(savedInstanceState: Bundle?) { playlistModel.getPlaylistById(playlistId) .subscribe { //
Set data to View. } playlistModel.fetchPlaylistById(playlistId) .observeOn(AndroidSchedulers.mainThread()) .waitForNextLooperEvent() .flatMapMaybe { playlistModel.getPlaylistById(it) } .subscribe { // Set data to View. } }
実装面での問題点 - 画面を表示するだけなのに手続きが多い(RxJava未習熟時の実装) - 更新処理が必要な場合はさらに手続きが増える - Viewの処理が多い - fetchとgetが紛らわしい。fetchなのに実際はDB更新してる初見殺し -
observeOnでthreadを切り替えるとクラッシュするStream
DataStore AWA V2 View ApiClient Realm API ViewModel Repository Data
Command Data Query SQLite Prefs Cache Use Case (Command) Use Case (Query)
何が変わったか - レイヤー構造を徹底し、1階層下のレイヤー以外の参照を禁止した - Contextを参照できるのはView / Repository / API のみ
- 同一レイヤー内でもCommand / Queryを完全に分離した(CQRS) - AWA特有の要件(差分APIによるキャッシュ更新)に対応するため、一般的な Repositoryと異なるDataCommand / DataQuery層を定義した
プレイリスト情報を表示する(V2) - Realmを変更監視して流れてきたデータを画面に表示する(main thread) - APIをコールして最新データを取得する - Realmのキャッシュを更新する(I/O thread)
プレイリスト情報を表示する(V2) PlaylistDataCommand.kt fun syncById(playlistId: String): Completable = playlistApiClient.getPlaylistDetail(playlistId) .subscribeOn(Schedulers.io()) .flatMapCompletable
{ playlistDbClient.save(it) } PlaylistDataQuery.kt fun getById(playlistId: String): RealmResults<Playlist> = playlistDbClient.getById(playlistId)
プレイリスト情報を表示する(V2) SyncPlaylistById.kt operator fun invoke(playlistId: String): Completable = playlistDataCommand.syncById(playlistId) ObservePlaylistById.kt
operator fun invoke(playlistId: String): Flowable<RealmResults<Playlist>> = playlistDataQuery.getById(playlistId) .asFlowable()
プレイリスト情報を表示する(V2) PlaylistDetailViewModel.kt val playlist = ObservableField<Playlist>() fun onStart() { observePlaylistById(playlistId)
.subscribe { it.firstOrNull()?.also { playlist.set(it) } } syncPlaylistById(playlistId) .subscribe() }
問題は改善されたか - 画面表示はsyncとobserveという2処理を独立して実行するだけになった - 更新処理はDBを更新するだけ(observeしているので画面は自動更新) - Viewからロジックを引き離すためViewModelを定義した - ViewModelが肥大化しないようにdomain層でユースケースを定義した -
APIコール→キャッシュ更新処理にsyncという名前を与えた - Realmを検索する処理はRealmResultsの型のままView層に戻すようにした - Contextを参照する層を限定的にしたため、ユースケース、ロジック層ではAndroid の制約を意識する必要がなくなった - 同様に、ユースケース、ロジック層ではモックを使用した高速テストが可能になった
それ以外にもこんなことやったよ - フルKotlin - Kotlin Extensionsの積極活用 - AndroidX対応 - レイアウトは原則ConstraintLayout
- AAC(Lifecycle、LiveData、Room、Paging)の適用 - 大量生産されるViewコンポーネントはtemplateを作って単純作業を削減 興味のある方は個別に聞いてください!