이거왜오름?api호출 예외 작성
내가 생각하는 api호출실패의 경우의수는
- 에러코드가 0이 아닐때 처리
- 컨텐츠타입이 xml이면 에러
response.headers.contentType== MediaType.TEXT_XML
이후 에러메시지와 에러코드 담아서 로깅 - 이떄 에러코드를 보고 이쪽문제인지 저쪽문제인지 확인해서 나눠서 로깅
- 컨텐츠타입이 xml이면 에러
- 결과값의 갯수가 0일때 처리(이쪽이 값을 잘못보냈거나)
- http코드 예외발생시 처리
- 400일경우
- 500일경우
- 400일경우
이렇게 크게 3개가 있음
아무래도 에러코드관련은 좀 지엽적인,api하나하나마다 다 다를 가능성이 크니,이것도 최하위 구현클래스쪽에서 구현하고,상위에서는 템플릿메서드로 가져다 쓰는게 좋을거같음
ApiFetcher의 apiCall의 예외처리부분을 추상메서드로 뽑아내고
private fun apiCall(
restTemplate: RestTemplate,
url: URI,
isinCode: String,
): Map<String, String> {
val log = LoggerFactory.getLogger(this.javaClass)
try {
val response = fetchApiResponse(restTemplate, url)
responseErrorCheck(response)
log.info("성공 법인코드={}", isinCode)
return extractResponseAsMap(response)
} catch (e: Exception) {
log.warn("${javaClass.name}통신에 문제발생 메시지={} 스택트레이스={}", e.message, e.stackTrace)
return emptyMap()
}
}
protected abstract fun responseErrorCheck(response: ResponseEntity<String>)
BasicFetch에서는 이걸 구현해주면됨
override fun responseErrorCheck(response: ResponseEntity<String>) {
checkHttpStatus(response)
checkBodyType(response)
}
private fun checkBodyType(response: ResponseEntity<String>) {
if (response.headers.contentType.toString() == "text/xml;charset=UTF-8") {
val responseBody = response.body ?: ""
val responseInputStream = responseBody.byteInputStream(Charsets.UTF_8)
val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val document = documentBuilder.parse(responseInputStream)
document.documentElement.normalize()
val errorCode = document.getElementsByTagName("returnReasonCode").item(0).textContent
val errorMsg = document.getElementsByTagName("returnAuthMsg").item(0).textContent
throw RuntimeException("""${javaClass.name} api 통신예외발생 코드:$errorCode 메세지:$errorMsg""")
}
}
private fun checkHttpStatus(response: ResponseEntity<String>) {
if (response.statusCode.value() != 200) {
throw RuntimeException("${javaClass.name} http상태코드가 200이 아님")
}
}
checkBodyType는,해당 api가 결과값이 xml로 리턴되면 에러페이지라서 이런식으로 했고,나중에 변경이 일어나도 이 api만 영향을 받고,그중에서도 저 부분만 영향을 받으니 저메서드만 수정하면되게 만듬
결과값의 갯수체크는 이쪽보다는 결과값추출메서드쪽에서 하는게 맞는거같아서 그쪽으로 넘겼음
override fun extractResponseAsMap(response: ResponseEntity<String>): Map<String, String> {
val resMap = HashMap<String, String>()
val itemNode = getItemNode(response)
resMap["assetName"] = extractNodeValue(itemNode, "itmsNm")
return resMap
}
private fun extractNodeValue(
node: JsonNode,
key: String,
): String {
try {
return node.get(key).asText()
} catch (e: Exception) {
throw NoSuchElementException("${javaClass.name}에서 해당하는 노드의 키값이 없음 키:$key ")
}
}
private fun getItemNode(response: ResponseEntity<String>): JsonNode {
val om = jacksonObjectMapper()
val readTree = om.readTree(response.body)
try {
return readTree
.get("response")
.get("body")
.get("items")
.get("item")
.toList()[0]
} catch (e: IndexOutOfBoundsException) {
throw NoSuchElementException("${javaClass.name}가 조회에 실패함 ${response.headers.eTag}")
}
}
이런식으로 최하단 구현클래스에서 예외가 발생하면,상단의 추상클래스에서
private fun apiCall(
restTemplate: RestTemplate,
url: URI,
isinCode: String,
): Map<String, String> {
val log = LoggerFactory.getLogger(this.javaClass)
try {
val response = fetchApiResponse(restTemplate, url)
responseErrorCheck(response)//예외체크하는 추상메서드
log.info("성공 법인코드={}", isinCode)
return extractResponseAsMap(response)
} catch (e: Exception) { //예외발생시 처리되는곳
log.warn("${javaClass.name}통신에 문제발생 메시지={} 스택트레이스={}", e.message, e.stackTrace)
return emptyMap()
}
}
catch에서 받아서 로그를 띄우고,빈맵을 던짐
이렇게 로그만 띄우고 패스하는 이유는,이 클래스 자체가 외부api통신이라 항상불안정할수 있다는걸 전제로 깔아야하고,추가로 외부api가 고장난다고 동작의 정확성이 좀 떨어질순 있어도 동작이 불가능해 지는게 아닌데,이거때문에
동작 자체가 뻗으면 안되기때문에 이렇게 처리했음
리스트로 ApiFetcher를 여러개 받아와서 한번에 돌릴껀데 그중에 하나 터질확률이 좀 되기도 하니까..
이렇게 처리하면
AssetFetcher(최상위인터페이스)-ApiFetcher(상위 추상클래스)-BasicFetcher(하위 구현클래스)
최상위 인터페이스는 클라이언트가 이쪽 클래스에게 시킬 일의 메시지(책임)만 표현하고
상위 추상클래스는 이 클래스의 고정적인 처리방식만 표현하고
하위 구현클래스는 이 클래스의 지엽적인 세부사항들을 표현하게되는
이런 구조가 아직도 깨지지 않게됨