這是今天在 OSDC.TW 2012 演講的投影片:
這個題目是近年來第一次脫離 Ruby 舒適圈的演講(抖),準備的時候就發現 Functional Programming 不但講不完(30分鐘的演講教不會寫FP啦),有些概念又很難,真要講到 “Pure” 的 Functional Programming 我連 Monad 都還搞不清楚…orz 所以本來準備的內容在前一天又狠狠砍掉 1/3,開頭改成用 Concurrency Program 來引人入勝,希望這樣的目的有達成 :)
😆 👨🏻💻 📚 🚀 💰 ✨
這是今天在 OSDC.TW 2012 演講的投影片:
這個題目是近年來第一次脫離 Ruby 舒適圈的演講(抖),準備的時候就發現 Functional Programming 不但講不完(30分鐘的演講教不會寫FP啦),有些概念又很難,真要講到 “Pure” 的 Functional Programming 我連 Monad 都還搞不清楚…orz 所以本來準備的內容在前一天又狠狠砍掉 1/3,開頭改成用 Concurrency Program 來引人入勝,希望這樣的目的有達成 :)
關於 HTTP 基本知識,可以參考我的 網路概論3: HTTP
在初學REST的這幾年,我都認為這幾個 HTTP Verbs 就是對應 CRUD:
後來在設計 API only 的 Web service 時,常常搞不清楚到底要用 PUT 還是 POST,才發現我被 Rails 的鷹架範例誤導了(被框架框住想法了?),所謂的 PUT 其實也可以用到新增,而且還有一個蠻新的 HTTP Verb 叫做 PATCH,像 Github API 和 Rails 4 都開始採用。
PUT 比較正確的定義是 Replace (Create or Update),例如PUT /items/1
的意思是替換/items/1
,如果已經存在就替換,沒有就新增。PUT 必須包含items/1
的所有屬性資料。
但是這個行為似乎不怎麼好用,如果只是為了更新items/1
的其中一個屬性,就需要重傳所有items/1
的屬性也太浪費頻寬了,所以後來又有新的 PATCH Method 標準,可以用來做部分更新(Partial Update)。
用幾個 Ruby code 來舉例吧:
POST 新增:
# POST /items
def create
@item = Item.new
@item.attributes = { :name => params[:name],
:image => params[:image] }
@item.save
end
PUT 替換(新增或完整更新),此例中如果image參數沒有傳,會被更新成空:
# PUT /items/{id}
def replace
@item = Item.find_by_id(params[:id])
unless @item # if @item.nil?
@item = Item.new
@item.id = params[:id]
end
@item.attributes = { :name => params[:name],
:image => params[:image] }
@item.save
end
PATCH 部分更新,此例中如果image參數沒有傳,就不會被更新:
# PATCH /items/{id}
def patch
@item = Item.find(params[:id])
@item.attributes = params.slice(:name, :image)
@item.save
end
DELETE 刪除,此例中無論如何 items/1 最後都不存在了
# DELETE /items/{id}
def destroy
@item = Item.find_by_id(params[:id])
@item.destroy if @item
end
有時候拘泥於”語意”這件事情不容易想清楚設計 REST API 時要用哪一個 HTTP 方法,因為有時候不一定是CRUD的形式。我認為 9.1 Safe and Idempotent Methods 定義中的 “Idempotent” 特性蠻實用的。idempotent 的意思是如果相同的操作再執行第二遍第三遍,結果還是跟第一遍的結果一樣 (也就是說不管執行幾次,結果都跟只有執行一次一樣)。根據 HTTP 的規格,GET, HEAD, PUT 和 DELETE 是 idempotent,相同的 Request 再執行一次,結果還是一樣。只有 POST 和 PATCH 不是 idempotent,POST 再執行一遍,會再新增一筆資料,PATCH 則是有不能保證 idempotent 的可能性(徵求例子)。POST 和 PATCH 都不是 idempotent 的操作,這也是為什麼 Github API 裡用 POST 當做 PATCH 的相容取代方案。
另一個 HTTP Methods 特性是”Safe”,這比較簡單,只有 GET 和 HEAD 是 Safe 操作。Safe 特性會影響是否可以快取(POST/PUT/PATCH/DELETE 一定都不可以快取)。而 Idempotent 特性則是會影響可否 Retry (重試,反正結果一樣)。
Safe? | Idempotent? | |
---|---|---|
GET | Y | Y |
POST | N | N |
PATCH | N | N |
PUT | N | Y |
DELETE | N | Y |
透過 Idempotent 的特性,有時候可以幫助你判斷該用哪一個 HTTP Methods。回到前面講 PUT 好像不太好用,例如以瀏覽器為主的 HTML 應用表單,要麻是 POST 新增資料,要麻就是用 PATCH 部分更新已經存在的資料(你不會希望用 PUT 修改個人資料的時候,每次都要重傳照片吧),因此比較少用到 PUT。這也是為什麼 Rails 4 把表單修改的 PUT 改成 PATCH Method,透過 Rails 鷹架產生出來的 update,其實符合的是 PATCH 行為而不是 PUT。
不過還是有一些我認為蠻適合用PUT的情境,例如訂閱東西該用POST還是PUT呢?
POST /subscriptions
# 還是
PUT /subscriptions/{something_id}
訂閱東西只有兩個狀態,”已訂閱”或”沒有訂閱”,這個訂閱的操作再重送幾次,還是”已訂閱”,所以我認為蠻符合 PUT 的 idempotent 特性。而對應的取消訂閱 API 想當然就是
DELETE /subscriptions/{something_id}
另外一個我覺得有趣又實用的 PUT 例子是,設計 API 给可以離線 offline 使用的行動裝置(例如iPhone)。支援 offline 產生的資料,通常會使用 UUID 來產生 ID,這樣就不需要透過中央伺服器管控 ID,方便裝置之間的同步。這樣的情境下,新增資料的 REST API 其實可以提供兩種:
POST /items # 參數帶 uuid=4937973d-e349-460a-a6ad-38625125b24a。如果不帶uuid則由server來產生uuid
# 和
PUT /items/4937973d-e349-460a-a6ad-38625125b24a
對行動裝置的 client 來說,用POST的問題在於離線環境的不穩定,有可能POST之後沒有收到回傳,因此行動裝置不確定有沒有同步成功,這時候要再重試(retry),但是用 POST 就爆炸了,因為 server 會再新建一筆 uuid 重複的資料。但是用 PUT 就沒有問題了,PUT 是 Idempotent 的操作,可以重送沒有關係 (可以再搭配 Conditional PUT 的機制,用 ETag 和 Last-Modified Headers 確保沒有覆蓋衝突)
如果是沒有 offline 需求的 client,例如第三方應用,那麼就可以用 POST /items 這個 API,交由 server 來產生 uuid。
最近把 JRuby 納入開發的武器之一,幾個你可能會想用 JRuby 的考量:
JRuby 的安裝應該是所有 Ruby 實作中最沒有跨平台問題的吧(笑)。只要 JVM 裝好,去 JRuby Download 下載,把 jruby/bin 加到 PATH 就可以用了。如果用 RVM 只要 rvm install jruby 即可。
以 Ubuntu 來說:
sudo apt-get install openjdk-6-jre wget http://jruby.org.s3.amazonaws.com/downloads/1.6.6/jruby-bin-1.6.6.tar.gz tar zxvf jruby-bin-1.6.6.tar.gz sudo mv jruby-1.6.6 /opt/jruby sudo vi /etc/environment 加上 /opt/jruby/bin
Mac上其實已經有裝 Apple Inc. 發行的 Java SE 6,不過 Apple 已經宣布不再維護其 Mac 版本了,並把他們的程式貢獻到 Java 的開源版本 OpenJDK 上。也就是說 Mac 上如果想要裝新版的JDK 7 或 8,就是得用 OpenJDK 啦。請參考 OpenJDK 7 and 8 for OS/X Snow and Lion,下載 .dmg 安裝,然後你可以透過設定環境變數 JAVA_HOME 來指定 Mac 使用這個版本,或是透過 Utilities > Java Preferences 做全域的預設設定。
根據 JRuby 官網的這一篇Getting Started with JRuby and Java 7,Java 7 開始支援動態語言的特性,所以跑起 JRuby 效能更好唷。
一年一度的台灣 Open Source 界盛事 OSDC.TW 又到了。在連續講了好幾年 Ruby 和 Rails 之後,今年我的研究主題決定什麼都學學看來點不一樣,其中 Functional Programming 是我很看好的一個趨勢,所以來挑戰看看。
以下是我今年的演講摘要:
Topic: 那些 Functional Programming 教我的事
Abstract:
隨著 CPU 多核和雲端時代的來臨,並行(concurrency)程式和處理巨量資料的需求日益增加,Functional Programming 開始逐漸嶄露頭角。在這場演講之中,我將介紹什麼是 Functional Programming 典範(paradigm)以及為什麼它開始變的重要,以 Scala, Erlang, Haskell, Java, Ruby, JavaScript 等程式語言為例,希望能夠讓您重新思考OO的本質,屆此拋磚引玉。
Speaker:
張文鈿,網路上的代號為ihower,軟體設計師和Ruby on Rails源碼貢獻者,目前是熱情豆行動樂活科技的技術長。曾擔任日本RubyKaigi 2011、中國RubyConf China 2010、台灣RubyConf Taiwan 2010和OSDC.TW歷屆講者。他也是Ruby Taiwan社群創辦人和RubyConf Taiwan大會的主辦人。
活動結束兩個多月了,應該要來記錄一下想法跟心得,有點雜只好條列了:
最後,在這場 Conference 中,我有兩個非常感動的時刻,一個是聽 Qing 大大演講時,另一個是 Lightning Talks (沒有料到可以有這麼多精彩的LT啊)。前輩大師的經驗分享,後輩們快樂地 Hacking,那一瞬間我與現場一百多人的感動和歡樂,我永遠不會忘記。
rubykoans.com/ 是 EdgeCase 所推出的 Ruby 學習工具。它的特別之處在於它是用單元測試填空題的方式來認識Ruby語法。要執行很簡單,下載之後,輸入 rake 就會開始跑測試了(如何安裝Ruby請參考這裡),例如以下的測試:
def test_arrays_and_ranges
assert_equal __, (1..5).class
assert_not_equal Array, (1..5)
assert_equal __, (1..5).to_a
assert_equal __, (1...5).to_a
end
其中 __ 的地方就是你要解答的部份,因為是空的,所以顯然測試不會通過(Red),它會提示你那一行出錯,然後實際的值是什麼:
~/koans] (master) .rvm-$ rake
/Users/ihower/.rvm/rubies/ruby-1.9.2-p290/bin/ruby path_to_enlightenment.rb
AboutArrays#test_arrays_and_ranges has damaged your karma.
The Master says:
You have not yet reached enlightenment.
You are progressing. Excellent. 20 completed.
The answers you seek...
<"FILL ME IN"> expected but was .
Please meditate on the following code:
/Users/ihower/koans/about_arrays.rb:48:in `test_arrays_and_ranges'
mountains are merely mountains
your path thus far [...X______________________________________________] 20/274
當你填入答案之後,就會通過進到下一個測試。你可以看到總共有274題,要寫完也是要花點時間。這模式有趣吧,練完之後應該會對Ruby語法有扎實的認識。