What have you found for these years?

2011-05-28

rest-core (0)

事情是這樣的,前幾週看到 faraday, 驚覺這是個比較好的方法。
不過他的實作我不太喜歡,總有種 "rails" 味的感覺,但其實也沒有
那麼嚴重。於是這陣子我一直有些掙扎,不知道到底該不該重做這塊。
這期間的一些心境過程先跳過,現在先描述目前的狀況與碰到的問題。

前幾天才正式開始弄 rest-core, 希望能更有效組織 rest-graph 的功能,
並提供 linkedin 等其他 service 所需要用到的東西。其中一個原因也是
rest-graph 做到後期,我自己都覺得有點亂,並沒有很單純了(難過)。
很多不同功能的程式交錯在不同的 method 中。雖然靠著 test case 與
耐心最終是能把想變的東西變出來(好幾個小時只改了幾行),但在製作
偽造 POST 這個功能時,一直覺得自己處於某種 "twisted mind" 的
狀態中...

所以如果能改善這部份的話,我會很高興的。

目前的成果就是仿造 rack middleware/app 的模式,來把各種功能掛
上去。我沒細看 faraday 的程式,因為我想直接仿效 rack 的單純,而
faraday 乍看之下並沒有那麼單純就不想看了。

所以 middleware/app 的結構大概是類似這樣:
(middleware (middleware (middleware app)))
如果 middlewares 是一個 list, 那這就會是
foldr (:new.to_proc) app middlewares (mixed haskell and ruby)
不明白我的意思的話沒關係,請直接看 Rack::Builder#to_app

所以對於一個 middleware 而言,他可以掌控的流程大概是:
def call env
  do_something
  response = app.call(env)
  do_something_again
end
比方說,RestCore::Cache#call 是這樣:
def call env
  cache_get(env) || cache_for(env, app.call(env))
end
單單都是這樣的話,一切都很好。不過 RestCore::Cache 並不能只是這樣。
當 server response 回來,透過 RestCore::ErrorDetector 會知道這個
response 是我們要的或是不要的。比方說 server 掛了,或是 request
limit reached, 這時候這個 response 則不該被 cache, 以利下次重試。

目前,我讓 middleware 實作 fail 這個 method, 以下是 RestCore::Cache#fail
def fail env
  cache_assign(env, nil)
  app.fail(env)
end
也就是說,有錯誤發生時,要把 cache 清掉。不過這樣做確實是很奇怪,
(感謝 jaime!) 有以下幾個理由:

0) 由於這也是透過 middleware 串起來的,而 middleware 本身是個
cons list (linked list) (所以 app 其實是某種 nil), 因此順序變得很重要。
不同的順序會有截然不同的結果,這不是我想要的。

1) 如果發生錯誤要把 cache 清掉,那是不是應該寫成,如果發生錯誤,
就不要 cache, 而不是事後把他清掉?

用 exception 來做這件事,是有可能的。可是我後來想來想去,還是覺得
很難把 exception 套上去。原因是我是希望把 cache 清掉,但不希望有
exception. 那麼應該 raise 什麼?又該由誰來 rescue? 其他不在乎這件事的
middleware, 碰上 exception 又該如何處理?似乎把問題變得更複雜了。

所以我想我要的應該是,某種 error notification, 告訴 middleware 說,
現在發生了某件事情,可以處理,也可以不處理。原本的 fail 應該能達成
這個問題,但順序變得太過重要,使得安排 middleware 變成一種麻煩。
另一方面,有沒有可能透過其他方式,達成上面 (1) 所提到的,不是事後,
而是當初就不該 cache?

Cache 這個 middleware 本身就應該放在前面,因為如果 cache hit
那就要直接出去了。因此在 Cache 的 call 裡面,是否需要某種方式知道
ErrorDetector 確認了這邊有錯誤產生,因此不要 cache? 在 env 裡留下
某種線索嗎?像這樣?
def call env
  cache_get(env) || if (response = app.call(env)) && response['error']
                      response
                    else
                      cache_for(env, response)
                    end
end
仔細想想,說不定這樣做確實比較好。包含 logging 的部份。確實 rack spec
中有明確規範 rack.logger 這樣東西。但其實我是想做得抽象一點,不是直接
操作 logger, 而是使用某些 Event struct, 這樣也能確保輸出格式。

另一方面,以上可能還只是小問題。另一個我現在沒什麼頭緒的問題是,
許多 middleware 會去調整 env 的內容,也就是說會改變 request URI.
那麼我應該用什麼方式,去取得這個最終的 request URI, 而不用真的呼叫
call 呢?這個問題是之前 rest-graph 有這樣的 method, 為了相容性,
再加上這確實很方便,所以還是希望能夠實作這個。但如果像現在這樣,
最終資訊在於 list 的最裡面,那我要怎麼取得那個資訊?

我先是試著在 env 裡面加入個 DRY_CALL 之類的 flag 表達不要 request,
但覺得這樣會使得 middleware 太難實作。後來想說要有些 method 可以去
詢問修改後的 env, 但要嘛就變成得實作兩次 (call + ask env), 要嘛就會變成
很怪的狀況,在 call 裡面呼叫 ask, 但真正的 ask 需要詢問 app, 在原本的
call 裡面又不用。

所以目前有點卡在這...

愈寫愈長就乾脆寫在 blog 上了,莫怪 @@"
懶得看也是完全可以理解的,沒關係 XD 感謝詢問!

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0