JSON Layout에서 API 응답 경로를 잘못 참조해 Batch 요청이 422로 실패한 문제와 해결

humanpchumanpc ·
09-2512
Glitter Project Hub의 게시판 목록에 Glitter Translate의 게시글 제목 번역을 연동하면서 Batch API 요청이 422 Unprocessable Content로 실패하는 문제가 있었습니다. 이번 문제는 Glitter Translate의 Batch API나 게시판 API 자체의 문제가 아니라, G7 JSON Layout에서 DataSource의 onSuccess에 전달되는 응답 구조를 잘못 참조한 것이 원인이었습니다. ■ 문제 상황 게시판 목록을 불러온 뒤 현재 페이지에 표시되는 게시글 ID를 모아 Glitter Translate의 Batch API로 한 번에 전달하도록 구성했습니다. 의도한 흐름은 다음과 같습니다. 게시글 목록 조회 → 현재 페이지의 게시글 ID 수집 → 번역 Batch API 1회 호출 → 번역된 제목 표시 → 번역이 없으면 원문 제목 표시 게시글마다 번역 API를 개별 호출하지 않고 현재 페이지의 게시글을 한 번에 처리하기 위한 구조입니다. 그런데 실제 브라우저에서는 번역 Batch 요청이 다음 오류와 함께 실패했습니다. 422 Unprocessable Content 서버에서 반환한 메시지는 다음과 같았습니다. The post ids field must be an array. Chrome 개발자 도구의 Network에서 실제 요청 Payload를 확인해 보니 post_ids에 게시글 ID 배열이 들어간 것이 아니라 다음 expression 문자열 자체가 전달되고 있었습니다. {{response.data.data.map(post => post.id)}} ■ 처음 의심했던 부분 처음에는 map()에서 사용하는 arrow function 문법이 현재 Template Engine에서 정상적으로 평가되지 않는 문제일 가능성을 생각했습니다. 그래서 다음과 같이 일반 function 형태로 변경해 보았습니다. {{response.data.data.map(function(post){return post.id;})}} 하지만 결과는 같았습니다. post_ids에는 여전히 평가된 배열이 아니라 expression 문자열이 전달됐고 Batch API는 422를 반환했습니다. 이 과정에서 문제는 arrow function이나 function callback의 차이가 아니라, map()을 실행하려는 대상 자체에 있다는 것을 확인했습니다. ■ 실제 원인 게시글 API의 실제 응답 구조와 DataSource의 onSuccess에 전달되는 response 구조를 다시 확인했습니다. 여기에서 중요한 차이가 있었습니다. API가 반환한 body가 onSuccess의 response 자체가 아니라 response.data 안에 들어가 있었고, 게시글 목록은 pagination 객체 안에 다시 포함되어 있었습니다. 따라서 실제 게시글 배열의 위치는 다음과 같았습니다. response.data.data.data 하지만 기존 expression에서는 다음 경로를 사용하고 있었습니다. response.data.data response.data.data는 게시글 배열이 아니라 board, pagination, data 등의 정보를 포함하는 객체였습니다. 즉 기존 expression은 실질적으로 배열이 아닌 객체에 map()을 실행하려고 하고 있었습니다. 이 때문에 expression 평가가 실패했습니다. ■ 그런데 왜 expression 오류가 아니라 422 오류가 나타났을까? 이번 문제에서 특히 확인할 가치가 있었던 부분입니다. expression 평가에 실패했다고 해서 해당 시점에서 전체 처리가 바로 중단되는 것은 아니었습니다. 평가에 실패한 expression의 원래 문자열이 그대로 남았고, 그 문자열이 local state의 boardPostIds에 저장됐습니다. 이후 Glitter Translate의 Batch DataSource가 boardPostIds를 post_ids 값으로 사용하면서 문제가 다음 단계까지 전달됐습니다. 전체 과정을 정리하면 다음과 같습니다. 게시글 API → onSuccess 실행 → 잘못된 response 경로 참조 → expression 평가 실패 → expression 원문 문자열이 local state에 저장 → 해당 문자열이 Batch API의 post_ids로 전달 → post_ids 배열 validation 실패 → HTTP 422 따라서 브라우저에서 최종적으로 발견된 오류는 Glitter Translate Batch API의 422였지만, 실제 문제는 그보다 앞선 게시글 DataSource의 onSuccess expression에서 발생하고 있었습니다. ■ 해결 기존 expression은 다음과 같았습니다. {{response.data.data.map(function(post){return post.id;})}} 실제 게시글 배열의 경로에 맞게 다음과 같이 수정했습니다. {{response.data.data.data.map(function(post){return post.id;})}} 즉 .data 한 단계를 추가했습니다. 수정 후에는 실제 게시글 배열에 map()이 실행되면서 현재 페이지의 게시글 ID가 배열로 만들어졌습니다. 예: [65, 51, 50, 27, 23, 7, 3] 이 배열이 local state에 저장되고 Glitter Translate의 Batch API에는 실제 배열 타입의 post_ids가 전달됩니다. ■ 최종 동작 구조 정상화된 전체 흐름은 다음과 같습니다. 게시글 목록 API 1회 → 현재 페이지의 게시글 ID 배열 생성 → local state에 ID 배열 저장 → Glitter Translate Batch API 1회 호출 → 해당 게시글들의 번역 정보 조회 → 번역된 제목이 있으면 번역 제목 표시 → 번역이 없으면 기존 원문 제목 표시 실제 화면에서도 번역 정보가 있는 게시글은 번역된 제목으로 표시되고, 번역이 없는 게시글은 기존 원문 제목으로 유지되는 것을 확인했습니다. ■ N+1 요청은 발생하지 않음 이번 연동에서는 게시글마다 번역 API를 하나씩 호출하지 않습니다. 예를 들어 현재 페이지에 게시글 7개가 있더라도 다음과 같이 동작합니다. 게시글 목록 요청 1회 + 번역 Batch 요청 1회 게시글 7개에 대해 번역 요청을 7번 보내는 구조가 아닙니다. 현재 페이지의 게시글 ID를 먼저 배열로 만든 뒤 한 번의 Batch 요청으로 처리합니다. ■ 이번 과정에서 함께 확인한 점 처음 구현할 때 다른 G7 템플릿에서 비슷한 구조를 사용하는 코드를 참고했습니다. 하지만 조사 과정에서 기존 템플릿에 같은 코드가 존재한다는 사실만으로 현재 Runtime에서도 정상 동작하는 구현이라고 판단해서는 안 된다는 점을 확인했습니다. 참고했던 다른 템플릿에서도 다음과 비슷한 형태를 사용하고 있었습니다. {{response.data.data.map(post => post.id)}} 하지만 이번에 실제 API 응답과 onSuccess context를 확인한 결과, 동일한 API 응답 구조를 사용한다면 이 경로 역시 별도로 검증할 필요가 있었습니다. 즉, “저장소에 이미 같은 코드가 있다” 와 “현재 Runtime에서 실제로 정상 동작하는 코드다” 는 별개의 문제였습니다. 기존 구현을 참고하더라도 실제 API 응답 구조와 현재 Runtime에서의 동작을 함께 확인하는 것이 필요합니다. ■ 비슷한 문제가 발생했을 때 확인할 부분 G7 JSON Layout에서 한 DataSource의 응답을 가공하여 다른 DataSource의 요청값으로 사용하는 경우에는 다음 순서로 확인하는 것이 도움이 됐습니다. 1. Chrome Network에서 실제 API 응답 구조를 확인합니다. 2. API body 자체와 onSuccess에 전달되는 response context를 구분해서 확인합니다. 3. expression이 참조하는 값이 실제 배열인지 객체인지 확인합니다. 4. expression이 평가되지 않고 {{...}} 형태 그대로 전달되고 있지 않은지 확인합니다. 5. 후속 API의 Request Payload에서 값뿐만 아니라 실제 데이터 타입도 확인합니다. 특히 다음 두 값은 겉으로 비슷해 보여도 완전히 다릅니다. [65, 51, 50] 위 값은 배열입니다. "[65,51,50]" 위 값은 문자열입니다. Batch API가 배열을 요구한다면 반드시 실제 배열 타입으로 전달되어야 합니다. ■ 정리 이번 문제는 단순히 .data 하나를 빠뜨린 문제로 끝나는 사례는 아니었습니다. 브라우저에서 최종적으로 나타난 오류는 Glitter Translate Batch API의 422였지만, 실제 원인은 그보다 앞선 Glitter Project Hub의 JSON Layout에서 잘못된 response 경로를 참조한 것이었습니다. 즉, 오류가 나타난 위치 = Glitter Translate Batch API 실제 문제가 발생한 위치 = Glitter Project Hub의 DataSource onSuccess expression 이 서로 달랐습니다. 최종적으로 Glitter Translate, sirsoft-board, G7 Core는 수정하지 않았습니다. Glitter Project Hub의 JSON Layout에서 실제 게시글 배열을 가리키도록 데이터 경로만 수정하여 해결했습니다. 이번 사례를 통해 API 연동 과정에서 후속 요청이 validation 오류를 반환하더라도 해당 API만 살펴보기보다는, 그 요청값이 만들어지는 앞 단계의 DataSource와 expression까지 함께 추적할 필요가 있다는 것을 확인할 수 있었습니다.

Comments and actions are deferred

Load the comment and action area when you need it. Comments: 0