SDK Building Principles
江品陞 Vincent Chiang
vincentchiang@kkbox.com
Lecturer
Developer Advocate @
- Partner & developers engagement
- Open API showcase development & articles writing
In the past, we care
Now, we should
KKBOX 是一間軟體公司
我們並不擅長
- 硬體研發設計
- 蓋工廠製造產品
- 處理通路庫存
KKBOX 是一間軟體公司
我們並不擅長
- 硬體研發設計
- 蓋工廠製造產品
- 處理通路庫存
- 怕.jpg
從軟體的思維
Topic
Experiences about how KKBOX
- Release their API
- Build a developer site
- Provide SDK
- Help people make their music product
When we meet the hardware company......
我照你們文件說的規則生成網址結果是 404
因為沒有做 URL encoding
API 回傳格式為什麼要用 JSON
不然要用什麼?
API 回傳的字串為什麼是 UTF-8 Encoding
............
我同事剛到職的時候表情是這樣
經過一個月完成受訓後
實際接觸 Partners 一個月後
再過一個月......
Again
從軟體的思維
Open API
- 全世界的人都可以找得到
- 想接的會先自己接接看
- 想要更多功能會來跟我們說
Open API
- 全世界的人都可以找得到
- 想接的會先自己接接看
- 想要更多功能會來跟我們說
- 最重要的是,那些接不起來的會自行放棄
Open API
- 全世界的人都可以找得到
- 想接的會先自己接接看
- 想要更多功能會來跟我們說
- 最重要的是,那些接不起來的會自行放棄
有沒有很聰明?
(ゝ∀・)b
Why developers like SDK?
Why developers like SDK?
Because we are very lazy!
使用 SDK 的好處
- 就不用看 RESTful API Document
- 不用處理複雜的認證流程
- IDE 有 Autocomplete
- 寫出來的程式不會有 Bug
使用 SDK 的好處
- 就不用看 RESTful API Document
- 不用處理複雜的認證流程
- IDE 有 Autocomplete
- 寫出來的程式不會有 Bug(因為是別人寫的)
Which language of SDK should we develop first?
Which language of SDK should we develop first?
Of course JavaScript!
SDK Development Principles
- 化零為整
- 化繁為簡
- 化無為有
化零為整
化零為整
import { ARTISTS as ENDPOINT } from '../Endpoint';
import Fetcher from './Fetcher';
export default class ArtistFetcher extends Fetcher {
constructor(http, territory = 'TW') {
super(http, territory);
this.artistID = undefined;
}
setArtistID(artistID) {
this.artistID = artistID;
return this;
}
// @example api.artistFetcher.setArtistID('Cnv_K6i5Ft4y41SxLy').fetchMetadata();
fetchMetadata() {
return this.http.get(ENDPOINT + '/' + this.artistID, {
territory: this.territory
});
}
// @example api.artistFetcher.setArtistID('Cnv_K6i5Ft4y41SxLy').fetchAlbums();
fetchAlbums(limit = undefined, offset = undefined) {
return this.http.get(ENDPOINT + '/' + this.artistID + '/albums', {
territory: this.territory,
limit: limit,
offset: offset
});
}
// @example api.artistFetcher.setArtistID('Cnv_K6i5Ft4y41SxLy').fetchTopTracks();
fetchTopTracks(limit = undefined, offset = undefined) {
return this.http.get(ENDPOINT + '/' + this.artistID + '/top-tracks', {
territory: this.territory,
limit: limit,
offset: offset
});
}
// @example api.artistFetcher.setArtistID('Cnv_K6i5Ft4y41SxLy').fetchRelatedArtists();
fetchRelatedArtists(limit = undefined, offset = undefined) {
return this.http.get(ENDPOINT + '/' + this.artistID + '/related-artists', {
territory: this.territory,
limit: limit,
offset: offset
});
}
}
化繁為簡
化繁為簡
/**
* Fetch KKBOX resources.
*/
export default class Api {
/**
* Need access token to initialize.
*
* @param {string} token - Get via Auth.
* @param {string} [territory = 'TW'] - ['TW', 'HK', 'SG', 'MY', 'JP'] The territory for the fetcher.
* @example new Api(token);
* @example new Api(token, 'TW');
*/
constructor(token, territory = 'TW') {
this.territory = territory;
this.httpClient = undefined;
this.setToken(token);
}
/**
* Set new token and create fetchers with the new token.
*
* @param {string} token - Get via Auth.
* @example api.setToken(token);
*/
setToken(token) {
this.httpClient = new HttpClient(token);
/**
* @type {SearchFetcher}
*/
this.searchFetcher = new SearchFetcher(this.httpClient, this.territory);
/**
* @type {TrackFetcher}
*/
this.trackFetcher = new TrackFetcher(this.httpClient, this.territory);
/**
* @type {AlbumFetcher}
*/
this.albumFetcher = new AlbumFetcher(this.httpClient, this.territory);
/**
* @type {ArtistFetcher}
*/
this.artistFetcher = new ArtistFetcher(this.httpClient, this.territory);
/**
* @type {FeaturedPlaylistFetcher}
*/
this.featuredPlaylistFetcher = new FeaturedPlaylistFetcher(
this.httpClient,
this.territory
);
/**
* @type {FeaturedPlaylistCategoryFetcher}
*/
this.featuredPlaylistCategoryFetcher = new FeaturedPlaylistCategoryFetcher(
this.httpClient,
this.territory
);
/**
* @type {NewReleaseCategoryFetcher}
*/
this.newReleaseCategoryFetcher = new NewReleaseCategoryFetcher(
this.httpClient,
this.territory
);
/**
* @type {NewHitsPlaylistFetcher}
*/
this.newHitsPlaylistFetcher = new NewHitsPlaylistFetcher(
this.httpClient,
this.territory
);
/**
* @type {GenreStationFetcher}
*/
this.genreStationFetcher = new GenreStationFetcher(
this.httpClient,
this.territory
);
/**
* @type {MoodStationFetcher}
*/
this.moodStationFetcher = new MoodStationFetcher(
this.httpClient,
this.territory
);
/**
* @type {ChartFetcher}
*/
this.chartFetcher = new ChartFetcher(this.httpClient, this.territory);
/**
* @type {SharedPlaylistFetcher}
*/
this.sharedPlaylistFetcher = new SharedPlaylistFetcher(
this.httpClient,
this.territory
);
}
}
化無為有
SDK 可以實現原本 API 做不到的功能
化無為有
漂向北方有很多人唱過
但是我只想要黃明志和鄧紫棋合唱的
import { SEARCH as ENDPOINT } from '../Endpoint';
import Fetcher from './Fetcher';
/**
* Search API.
* @see https://docs-en.kkbox.codes/v1.1/reference#search
*/
export default class SearchFetcher extends Fetcher {
/**
* Filter what you don't want when search.
*
* @param {Object} [conditions] - search conditions.
* @param {string} conditions.track - track's name.
* @param {string} conditions.album - album's name.
* @param {string} conditions.artist - artist's name.
* @param {string} conditions.playlist - playlist's title.
* @param {string} conditions.availableTerritory - tracks and albums available territory.
* @return {Search}
* @example
* api.searchFetcher
* .setSearchCriteria(q = '飄向北方', type = 'track')
* .filter({artist: '黃明志, G.E.M.鄧紫棋'})
* .fetchSearchResult();
*/
filter(conditions = {}) {
this.filterConditions = conditions;
return this;
}
}
化無為有
不能牴觸 API 原來的設計理念
KKBOX Open API 要怎麼播歌?
URI Rule
https://widget.kkbox.com/v1/?id={id}&type={type}&terr={territory}&lang={lang}&autoplay={boolean}&loop={boolean}
export default class TrackFetcher extends Fetcher {
/**
* Get KKBOX web widget uri of the track.
* @example https://widget.kkbox.com/v1/?id=8sD5pE4dV0Zqmmler6&type=song
* @return {string}
*/
getWidgetUri() {
return `https://widget.kkbox.com/v1/?id=${this.trackID}&type=song`;
}
}
export default class SharedPlaylistFetcher extends Fetcher {
/**
* Get KKBOX web widget uri of the playlist.
* @example https://widget.kkbox.com/v1/?id=KmjwNXizu5MxHFSloP&type=playlist
* @return {string}
*/
getWidgetUri() {
return `https://widget.kkbox.com/v1/?id=${this.playlistID}&type=playlist`;
}
}
三大心法
- 化零為整
- 化繁為簡
- 化無為有
Testing
Testing
import SearchFetcher from "../api/SearchFetcher";
describe('Search', () => {
const searchFetcher = new SearchFetcher(
httpClient
).setSearchCriteria('Linkin Park');
describe('#fetchSearchResult()', () => {
it('should response status 200', () => {
return searchFetcher.fetchSearchResult().then(response => {
response.status.should.be.exactly(200),
reject => should.not.exists(reject);
});
});
});
describe('#filter()', () => {
it('should get result', () => {
return searchFetcher
.filter({
artist: 'Linkin Park',
album: 'One More Light',
available_territory: 'TW'
})
.fetchSearchResult()
.then(response => {
response.data.tracks.data.length.should.be.greaterThan(0);
});
});
});
});
Outside Monitoring
Documentation
import { SEARCH as ENDPOINT } from '../Endpoint';
import Fetcher from './Fetcher';
/**
* Search API.
* @see https://docs-en.kkbox.codes/v1.1/reference#search
*/
export default class SearchFetcher extends Fetcher {
/**
* Filter what you don't want when search.
*
* @param {Object} [conditions] - search conditions.
* @param {string} conditions.track - track's name.
* @param {string} conditions.album - album's name.
* @param {string} conditions.artist - artist's name.
* @param {string} conditions.playlist - playlist's title.
* @param {string} conditions.availableTerritory - tracks and albums available territory.
* @return {Search}
* @example
* api.searchFetcher
* .setSearchCriteria('五月天 好好')
* .filter({artist: '五月天'})
* .fetchSearchResult();
*/
filter(conditions = {}) {
this.filterConditions = conditions;
return this;
}
}
Write Annotation
Generate doc automatically
Operation
- Deploy to NPM
- Deploy to GitHub
JavaScript 什麼不多,就洞最多
更新一下版號
或者不要用那個 Library 就可以了ξ( ✿>◡❛)
Marketing
你沒事會去 Google
KKBOX 有沒有 Open API
裡面有什麼資料嗎?
不會嘛!
因為你只想到你自己
What developers like?
- 看到炫炮的東西,會想研究它是怎麼做出來的
- 喜愛潮流、追求新知
For junior developers
For senior developers
澄清一下,三上悠亞是音樂家
不信你看 Wiki
Music Everywhere
SDK Building Principles
By zaoldyeck
SDK Building Principles
- 2,105