はじめに
リポジトリとはなんぞやとや言うことで有名OSSメディアプレイヤーアプリである「VLC for Android」にあるリポジトリの実装を調べる。
当方はAndroidの素人なのでその辺はご了承を。
各種リンク
アプリ内で定義しているリポジトリ
| クラス | 概要 |
|---|---|
| BrowserFavRepository | お気に入りを保持? |
| DirectoryRepository | ディレクトリを保持? |
| ExternalSubRepository | 外部ストレージ |
| MediaMetadataRepository | メタデータ |
| MediaPersonRepository | ? |
| MoviepediaApiRepository | MoviepediaのApi |
| OpenSubtitleRepository | ? |
| PersonRepository | MoviePediaから取得した人をキャッシュ |
| SlaveRepository | ? |
データソース
どこからデータを持ってきているかを調べた結果。データベースとAPI、オンメモリーの3つのパターンがある
| クラス | データソース |
|---|---|
| BrowserFavRepository | データベース(BrowserFavDao) |
| DirectoryRepository | データベース(CustomDirectoryDao) |
| ExternalSubRepository | データベース(ExternalSubDao)、 オンメモリー |
| MediaMetadataRepository | データベース(MediaMetadataDataFullDao、MediaMetadataDao、MediaImageDao) |
| MediaPersonRepository | データベース(MediaPersonJoinDao) |
| MoviepediaApiRepository | REST API(IMoviepediaApiService) |
| OpenSubtitleRepository | REST API?(IOpenSubtitleService) |
| PersonRepository | データベース(PersonDao) |
| SlaveRepository | データベース(SlaveDao) |
ユニットテストを入れているか
| クラス | テストクラス |
|---|---|
| BrowserFavRepository | ○(BrowserFavRepositoryTest) |
| DirectoryRepository | ○(DirectoryRepositoryTest) |
| ExternalSubRepository | ○(ExternalSubRepositoryTest) |
| MediaMetadataRepository | × |
| MediaPersonRepository | × |
| MoviepediaApiRepository | × |
| OpenSubtitleRepository | ○?(SubtitlesModelTest) |
| PersonRepository | × |
| SlaveRepository | ○(SlaveRepositoryTest) |
データ取得元がデータベースの場合のクラス構成
↓はExternalSubRepository周りのクラス構成。ExternalSubクラスはデータベース、SubtitleItemはオンメモリーで管理している。
LiveData、Coroutineが絡んでいるがここでは割愛する。
ExternalSubRepositoryの責務はなんなのかというと、↓のコードをにある、Roomでは表現できない?制約にそってCRUD操作を実装している。後はテスタビリティの確保。
fun addDownloadingItem(key: Long, item: SubtitleItem) {
_downloadingSubtitles.add(key, item.copy(state = State.Downloading))
}
fun getDownloadedSubtitles(mediaUri: Uri): LiveData<List<org.videolan.vlc.mediadb.models.ExternalSub>> {
val externalSubs = externalSubDao.get(mediaUri.path!!)
return Transformations.map(externalSubs) { list ->
val existExternalSubs: MutableList<org.videolan.vlc.mediadb.models.ExternalSub> = mutableListOf()
list.forEach {
if (File(Uri.decode(it.subtitlePath)).exists())
existExternalSubs.add(it)
else
deleteSubtitle(it.mediaPath, it.idSubtitle)
}
existExternalSubs
}
}
データ取得元がREST APIの場合のクラス構成
↓はOpenSubtitleRepository周りのクラス構成。
OpenSubtitleRepositoryの責務はなんなのかというと、↓のコードにある通りRest APIのクエリーパラメータの組み立てと補正。
クエリーオブジェクト的な責務を行っている状態。後はテスタビリティの確保。
suspend fun queryWithImdbid(imdbId: Int, tag: String?, episode: Int? , season: Int?, languageId: String? ): List<OpenSubtitle> {
val actualEpisode = episode ?: 0
val actualSeason = season ?: 0
val actualLanguageId = languageId ?: ""
val actualTag = tag ?: ""
return openSubtitleService.query(
imdbId = String.format("%07d", imdbId),
tag = actualTag,
episode = actualEpisode,
season = actualSeason,
languageId = actualLanguageId)
}
ExternalSubRepositoryのユニットテスト
案の定、ExternalSubDaoをモック化してテストしている。
OpenSubtitleRepositoryのユニットテスト
SubtitlesModelTestで行っている。SubtitlesModelはViewModel派生のクラスで色々面白い事をしているがここでは触れない。
OpenSubtitleRepositoryそのもの、ExternalSubDaoをモック化してテストしている。 RetroFitのクラスはモック化していなかった。
その他リポジトリの(テスタビリティ担保以外の)責務
| クラス | (テスタビリティ担保以外の)責務 |
|---|---|
| BrowserFavRepository | サーバー保存のお気に入りとローカル保存のお気に入りの整合性をとっている? |
| DirectoryRepository | 実際にフォルダを作成、削除して、DBに同期している? |
| ExternalSubRepository | 説明済 |
| MediaMetadataRepository | クエリーの組み立て |
| MediaPersonRepository | なし |
| MoviepediaApiRepository | クエリーの組み立て |
| OpenSubtitleRepository | 説明済 |
| PersonRepository | なし |
| SlaveRepository | 様々な保存パターンに対応する。SQL例外の補正 |
まとめ
- テスタビリティ担保以外の主な責務はクエリーの組み立て、サーバーとローカルキャッシュの整合性、SQL周りの隠蔽、Retrofit周りの隠蔽
- Coroutine、LiveDataと密結合している。AndroidのUIでしか使わないから?
- シングルトンについてSingletonHolderとCompanionオブジェクトを分けているのはなに?

