2017-09-15 20 views
3

私は、HTTP要求を使用して一度に1つずつAPIから何千ものエンティティを取得しています。パイプラインの次のステップとして、それらをすべてデータベースにシャベルします。スレッドマクロでの慣用エラー/例外処理

(->> ids 
    (pmap fetch-entity) 
    (pmap store-entity) 
    (doall)) 

fetch-entityString IDを期待し、HTTP要求を使用してエンティティを取得しようといずれかMapを返すか(例えば、タイムアウトのため)は例外をスローします。

store-entityMapを想定し、データベースに格納しようとします。 Mapがデータベーススキーマと一致しない場合、またはMapがまったく受信されなかった場合など、例外がスローされる可能性があります。

無粋エラー処理

私の最初の「溶液」は、それぞれの本来の機能の例外をキャッチするラッパー関数fetch-entity'store-entity'を書くことでした。

fetch-entity'は、http要求が失敗した場合、基本的にString idに沿って渡されます。これにより、パイプライン全体がトラック運転を継続することが保証されます。

store-entity'は、その引数のタイプをチェックします。引数がMap(フェッチエンティティが成功し、Mapを返した場合)は、データベースに格納しようとします。データベースに格納する試みがerror_idsの外部Vectorに代わりMapことでしょうconjの例外をスローしたりstore-entity'場合String(ID)を通過してしまった場合は

このようにして、後でerror_idsを使用して、障害の発生頻度と影響を受けたIDを特定できます。

私がしようとしていることを達成するには、上記のような感覚的な方法はありません。たとえば、以前のパイプラインステップが成功したかどうかに応じて動作が異なるため、store-entity'は以前のパイプラインステップ(fetch-entity')の関数をコンパイルします。

store-entity'もありますので、error_idsと呼ばれる外部Vectorを気にする必要はありません。

複数のパイプラインステップがあり、そのうちのいくつかが(I/Oなどの理由で)例外をスローする可能性があるこのような状況を処理するための慣用的な方法はありますか?私はパイプラインを邪魔したくないところで、後でそれが間違っていたかどうかを後でチェックするだけです。

+1

s://github.com/adambard/failjure? –

答えて

4

cats libraryから、例えば、Tryモナドのタイプを使用することが可能である:

これは、例外が発生または正常計算された値を返すことができるいずれかの計算を表します。モナドと非常に似ていますが、意味的に違います。

成功と失敗の2つのタイプから成ります。 Success型は、Right of the Monadのような単純なラッパーです。しかし、Failure型はLeftと少し異なります。なぜなら、Throwableのインスタンス(またはJavaScriptホストに任意の値を投げることができるのでcljsの任意の値)を常にラップするからです。

(...)

try-catchブロックの類似点です。try-catchのスタックベースのエラー処理を、ヒープベースのエラー処理で置き換えます。例外がスローされ、同じスレッドですぐに処理される代わりに、エラー処理とリカバリが切断されます。

ヒープベースのエラー処理が必要です。

ここでは、fetch-entitystore-entityの例を作成しました。 fetch-entityExceptionInfoを最初のid(1)に、store-entityDivideByZeroExceptionに2番目のid(0)をスローしました。

(ns your-project.core 
    (:require [cats.core :as cats] 
      [cats.monad.exception :as exc])) 


(def ids [1 0 2]) ;; `fetch-entity` throws on 1, `store-entity` on 0, 2 works 


(defn fetch-entity 
    "Throws an exception when the id is 1..." 
    [id] 
    (if (= id 1) 
    (throw (ex-info "id is 1, help!" {:id id})) 
    id)) 


(defn store-entity 
    "Unfortunately this function still needs to be aware that it receives a Try. 
    It throws a `DivideByZeroException` when the id is 0" 
    [id-try] 
    (if (exc/success? id-try)     ; was the previous step a success? 
    (exc/try-on (/ 1 (exc/extract id-try))) ; if so: extract, apply fn, and rewrap 
    id-try))        ; else return original for later processing 


(def results 
    (->> ids 
     (pmap #(exc/try-on (fetch-entity %))) 
     (pmap store-entity))) 

今、あなたは、それぞれsuccess?またはfailure?で成功または失敗にresultsをフィルタリングし、cats-extract

(def successful-results 
    (->> results 
     (filter exc/success?) 
     (mapv cats/extract))) 

successful-results ;; => [1/2] 


(def error-messages 
    (->> results 
     (filter exc/failure?) 
     (mapv cats/extract) ; gets exceptions without raising them 
     (mapv #(.getMessage %)))) 

error-messages ;; => ["id is 1, help!" "Divide by zero"] 

注意を経由して値を取得することができますが、errorsまたはsuccessful-results上だけループする場合は、一度あなたができること以下のようにトランスデューサを使用してください:

(transduce (comp 
      (filter exc/success?) 
      (map cats/extract)) 
      conj 
      results)) 
;; => [1/2] 
1

私が最初に考えたのは、単一の操作にfetch-entitystore-entityを組み合わせることである。この作品のような

(defn fetch-and-store [id] 
    (try 
    (store-entity (fetch-entity id)) 
    (catch ... <log error msg>))) 

(doall (pmap fetch-and-store ids)) 

う何か?