一些调研与思考

怎样较好的抽象不同的资源提供方?

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 等信息。

面对这个问题,目前有两种解决方案:

  1. 将搜索接口返回的 Album 定义为 BriefAlbumModel,将详细接口返回的定义为 AlbumModel
    • pros: 清晰明了,两者有明显的区分
    • cons: 多一个 Model 就多一个概念,上层要对两者进行区分,代码更复杂
  2. 定义一个 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_getallow_batch