這聽起來有點瞎子摸象。
不過 Jamie Sa 提供了個有趣的點子:如果我們可以寫個模擬的 REST API service,並且透過 YAML 定義簡單的規格的話,我們就可以利用這個模擬 REST API 比較真實的跟 web service 銜接了 :)
正巧手邊大家正在研究 node.js,不免俗的我們就用了它來實作了初版的 API Simulator,Jamie 把它叫做 mockingBird。這個時候 YAML spec 看起來像這樣:
# APIs in V1 meta: version: draft v1_articles: timestamp: '2011-07-19T08:54Z' is_end: true articles: - { article: 1 } - { article: 2 } - { article: 3 } article_count: 3
而用瀏覽器開啟 http://127.0.0.1:8080/v1/articles 則會輸出以下結果:
初版的時候我們用 underline "_" 來分割版本,並且將假資料直接放在 YAML 裡面。而當我們需要模擬取得多筆資料的時候還可以處理,但如果需要類似 http://HOST/
做了一連串的 hacking 後,終於完成了一個較有彈性的架構,不過當然還是沒辦法模擬太過於複雜的狀況 :P
MockingBird 會將 JSON 格式的假資料讀取進來,而可以在 YAML 裡面調用 dummy data 以及使用簡單的 Javascript 做計算。
比如說我要模擬 Social network 的 API,
首先要先產生 dummy.json 假資料檔,這邊我用一個 python script 產生 dummy.json。
import uuid import json from random import randint, choice from datetime import datetime, timedelta firstnames = ['Ericka', 'Amie', 'Annabelle', 'Hugh', 'Carmella'] lastnames = ['Hilts', 'Kowalsky', 'Cincotta', 'Gerken', 'Stults'] devicenames = ['iPad', 'Android', 'Web'] basetime = datetime.today() lipsum='Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sollicitudin elementum tristique. Nullam gravida bibendum magna viverra gravida. Cras nec mi a est malesuada dictum.' def gen_id(): return uuid.uuid1().hex[:24] def gen_users(num=10): users = [] for i in range(num): user = {} user['id'] = gen_id() user['email'] = 'a%s@example.com' % randint(0,1000000) user['nickname'] = "%s %s" % \ (choice(firstnames), \ choice(lastnames)) users.append(user) return users def gen_comment(users, article_id, timestamp): c = {} c['id'] = gen_id() c['creator_id'] = choice(users)['id'] c['creation_device_name'] = choice(devicenames) c['article_id'] = article_id c['timestamp'] = timestamp c['text'] = lipsum return c def gen_articles(users, num=20): articles = [] for i in range(num): user = choice(users) timestamp = basetime+timedelta(0,i*10) article = {} article['id'] = gen_id() article['creator_id'] = user['id'] article['creation_device_name'] = choice(devicenames) article['timestamp'] = timestamp.isoformat() article['text'] = lipsum article['comment_count'] = randint(0,5) article['comments'] = [] for j in range(article['comment_count']): c = gen_comment(users, article['id'], \ (timestamp+timedelta(0,j*10)).isoformat()) article['comments'].append(c) articles.append(article) return articles if __name__ == '__main__': data = {} data['users'] = gen_users() data['articles'] = gen_articles(data['users']) print json.dumps(data, indent=2)
這個 dummy json 會 load 進去 node.js 的主程式裡面,可以由程式或者是 YAML 裡面使用。現在的 YAML 檔案則是長成這樣:
version: 0 api: articles: prefix: api url: "articles.*" http_method: GET response: timestamp: "timestamp" is_end: "true" article_count: "dummy['articles'].length" articles: "params['limit'] ? dummy['articles'].slice(0,params['limit']) : dummy['articles']" article: prefix: api url: "article/(\w+)$" http_method: GET response: article: "findById(dummy['articles'], match)" users: prefix: api url: "users" http_method: GET response: users: "dummy['users']" post_article: prefix: api url: article http_method: POST response: creator_id: "params.creator_id" creation_device_name: "params.creation_device_name" text: "params.text" timestamp: "timestamp" comment_count: "0" comments: "[]" id: "generateId()" post_comment: prefix: api url: comment http_method: POST response: creator_id: "params.creator_id" creation_device_name: "params.creation_device_name" article_id: "params.article_id" text: "params.text" id: "generateId()"
裡面有些關鍵字解釋一下:
- version: 最後 mockingBird 的網址會是 http://HOST
/PREFIX /VERSION /method,如 http://localhost/api/0/articles,用來區分 API 版本用的變數 - prefix: 可以針對 API 的用途使用不同的 prefix,在這邊我們只用了 "api" 這個 prefix。
- url: url 的 match pattern
- http_method: 可以指定要用 POST 或是 GET 的 HTTP Method
- response: 要回應的 JSON 訊息。
- dummy: 在 YAML 裡面可以利用 dummy 取得假資料,比如說 dummy['articles'] 就是所有的 articles。
- params: 使用 GET/POST 的時候丟進來的參數。比如下達了 http://localhost/api/0/articles?limit=2 時,YAML 可以使用參數 params.limit 取得這個 limit 數值,可以參考下面的片段
- findById: 用 id 來搜尋物件的 method
articles: prefix: api url: "articles.*" http_method: GET response: timestamp: "timestamp" is_end: "true" article_count: "dummy['articles'].length" articles: "params['limit'] ? dummy['articles'].slice(0,params['limit']) : dummy['articles']"
當用瀏覽器開啟 http://localhost:8080/api/0/articles?limit=1 的時候,就可以獲得以下結果:
如此一來,你的 mobile app 就可以開幹了,而有了這個實體的 SPEC,web service 的 developer 也可以依此為目標,最終做成跟這個相同的 API。
下一篇再來詳細講解怎麼使用這個工具還有 node.js 裡面是怎麼實作的。但重點是:有人有興趣嗎?出個聲吧 :P
感覺這樣的mocking工具還蠻有趣阿~不知道對Mobile App Developer來說,用ROR/Django mocking一個,還是用這樣的架構mocking一個快(或容易)?
回覆刪除其實是一樣的道理,我也是用 url routing 做一組 API 出來。只是用的工具是 node.js 而已。
回覆刪除不過當然還是很享受做一個有趣的工具啦 XD
實際上也有幫到忙就是了
>> 但重點是:有人有興趣嗎?出個聲吧 :P
回覆刪除相當之很有興趣 :D
很有趣!!
回覆刪除另種想法,把方向轉一下,當Mobile App還沒實作完之前,Web Service, 可用MockUp APP 套用YAML&Json, 來開發Web Service, 當然這個MackUp App 也可是個Automation testing tool for Web Service。