一些调研与思考¶
怎样较好的抽象不同的资源提供方?¶
feeluown 的一个主要目标就是将各个资源进行抽象,统一上层使用资源的方式。 但各个资源提供方提供的 API 差异较大,功能差别不小,比如网易云音乐 会提供批量接口(根据歌曲 id 批量获取歌曲详情),而虾米和 QQ 音乐就没有 类似接口,这给 feeluown 的实现和设计带来了挑战。
问题一:同一平台,不同接口的返回信息有的比较完整,有的不完整¶
我们以网易云音乐的 专辑详细信息接口 和 搜索接口 为例。 它的搜索接口返回的专辑信息大致如下:
"album": {
"artist": {
"id": 0,
"alias": [],
"img1v1": 0,
"name": "",
"picUrl": null,
"picId": 0,
},
"id": 2960228,
"name": "\u5218\u5fb7\u534e Unforgettable Concert 2010",
"picId": 2540971374328644,
...
}
它没有专辑封面的链接,也没有专辑歌曲、歌手信息也不完整信息。 而专辑详细信息接口中,它就有 songs , artists , picUrl 等信息。
面对这个问题,目前有两种解决方案:
- 将搜索接口返回的 Album 定义为 BriefAlbumModel,将详细接口返回的定义为 AlbumModel
- pros: 清晰明了,两者有明显的区分
- cons: 多一个 Model 就多一个概念,上层要对两者进行区分,代码更复杂
- 定义一个 AlbumModel,创建 Model 实例的时候不要求所有字段都有值,
一些字段的值在之后在被真正用到的时候再自动获取。
- cons: 比较隐晦
- cons: 上层不也方便确认哪些字段是已经有值了,哪些会在调用的时候获取
Note
UPDATE 2019-05-04: 第二种方案使用已经半年了,我们发现它有一个让人头疼的问题:
类似 model.xxx
这样的代码可能会导致整个线程 block,而对于一个 GUI 程序来说,
block(主)线程是不可接受的。为了不阻塞,我们使用的方案是让
model.xxx
这个操作跑在另一个线程中,这样的代码目前在 songs_table_container.py
中有较多使用。但这样的代码看起来很丑,性能也比较差(见 research/bench_getattr.py
)。
另外,尽管我们在开发 feeluown 的时候可以额外的注意,让程序不因此卡住,
但是其它插件开发者并不一定完全了解这个机制,很容易写成“坏”的代码。
为了让整体代码更简单,目前使用的是第二种方案。上层假设 identifier/name 等字段是一开始就有了,url/artists 等字段需要之后调用接口才会有值。 (尽管这种方案看起来也有明显的缺点,但目前看来可以接受,也没想到更好的方法。欢迎大家讨论新的方案)。
问题二:不同平台,同一接口的返回信息有的比较完整,有的不完整¶
在虾米音乐中,在获取歌曲详细信息的时候,就可以获取这歌曲的播放链接。 但是在网易云音乐,需要单独调用一个接口来获取歌曲的播放链接。
在虾米音乐的 API 中,要获取一个歌手的所有信息,我们需要调用它的多个接口: 一个是歌手详情接口;另一个是歌手歌曲详情接口;还有歌手专辑接口等。
问题三:平台能力方面和开发体验¶
另一方面,就算各音乐平台都提供一样的 API,开发者在开发相关插件的时候, 也不一定会一次性把所有功能都完成,那时,也会存在一个问题: A 插件有某功能,但是 B 插件没有。
所以,当 B 插件没有该功能的时候,系统内部应该怎样处理?又怎样将该 问题呈现给用户呢?
举个例子,对于网易云音乐来说,它的批量获取歌曲功能可以这样实现:
NeteaseSongModel.list(song_ids): -> list<SongModel>
但是我们不能给虾米音乐和 QQ 音乐实现这样的功能,那怎么办, 目前有如下方法:
1. XiamiSongModel.list(song_ids): -> raise NotSupportedError
2. XiamiSongModel.list -> AttributeError # 好像不太优雅
3. XiamiSongModel.allow_batch -> 加一个标记字段
目前使用的是第三种方案,加一个标记字段, allow_get
和 allow_batch
。