2017-09-15 6 views
3

나는 HTTP 요청을 사용하여 한 번에 하나씩 API에서 수천 개의 엔티티를 가져옵니다. 파이프 라인의 다음 단계로 모든 것을 데이터베이스에 삽화하고 싶습니다.스레딩 매크로를 사용한 관용적 오류/예외 처리

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

fetch-entityString ID를 예상하고, HTTP 요청을 사용하는 엔티티를 검색하려고 시도하고 Map 중 하나를 리턴하거나 (예컨대 인해 타임 아웃) 예외를 던진다.

store-entityMap이 필요하며 데이터베이스에 저장하려고합니다. 예외 (예 : Map이 데이터베이스 스키마와 일치하지 않거나 Map을 전혀받지 못한 경우)이 throw 될 수 있습니다.

우아 오류 처리

나의 첫번째 "해결책은"각각의 원래 기능의 예외를 잡기 위해 래퍼 함수 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을 알고 있어야합니다. 전혀 느끼지 않습니다.

일부 파이프 라인 단계에서 예외를 throw 할 수있는 상황 (예 : I/O) 때문에 이러한 상황을 처리하는 관용적 인 방법이 있습니까? 예를 들어 조건자를 쉽게 사용할 수없는 경우 예측할 수있는 행동을하고 어디에서 파이프 라인을 방해하고 싶지 않은지 나중에 나중에 잘못 확인합니다.

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

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

겠습니까 뭔가를이 작품처럼 :

+1

보셨나요? s : //github.com/adambard/failjure? –

답변

4

cats library에서 예 Try 모나드의 형태를 사용할 수있다 :

그것은 예외가 발생하거나 성공적 계산 값을 반환 할 수도 있고 연산을 나타낸다. 하나의 모나드와 매우 비슷하지만 의미 상으로 다릅니다.

성공과 실패의 두 가지 유형으로 구성됩니다. Success 형은 Right of the Monad와 같은 간단한 래퍼입니다. 하지만 Failure 유형은 Left와 약간 다릅니다. Throwable의 인스턴스 (또는 JavaScript 호스트에서 임의의 값을 throw 할 수 있으므로 cljs의 값)를 항상 래핑하기 때문입니다.



이것은 try-catch 블록과 유사합니다. try-catch의 스택 기반 오류 처리를 힙 기반 오류 처리으로 바꿉니다. 예외가 발생하고 동일한 스레드에서 즉시 처리해야하는 대신 오류 처리 및 복구를 중단합니다.

힙 기반 오류 처리가 원하는 것입니다.

이하는 fetch-entitystore-entity의 예를 작성했습니다. 나는 fetch-entity을 첫 번째 id (1)에 ExceptionInfo 던졌으며 store-entity은 두 번째 id (0)에 DivideByZeroException을 던졌습니다.

(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을 결합하는 것입니다?