這聽起來有點瞎子摸象。
不過 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。