Development··5 min read

API 설계할 때 흔히 하는 실수들

5년간 API를 만들고 쓰면서 반복적으로 봤던 설계 실수들을 정리해봤다.

/api/proc/getData2

이전 직장에서 외부 업체가 만든 API를 연동해야 했다. 엔드포인트 이름이 /api/proc/getData2였다. getData와 getData2의 차이가 뭔지, proc이 뭘 뜻하는지 아무도 몰랐다. 문서도 없었다.

그때 느꼈다. API 설계는 코드만큼, 어쩌면 코드보다 더 중요하다고.

URL에 동사를 넣는다

/api/getUsers, /api/createOrder, /api/deleteItem/3. 초보 때 나도 이렇게 짰다.

REST에서는 HTTP 메서드가 동사 역할을 한다. URL은 명사여야 한다. GET /api/users, POST /api/orders, DELETE /api/items/3. 간단한 원칙인데 안 지키는 API를 아직도 많이 본다. 특히 사내 API에서.

응답 형식이 들쭉날쭉하다

어떤 엔드포인트는 { data: [...] }로 감싸고, 어떤 건 배열을 직접 반환한다. 에러는 { error: "..." } 또는 { message: "...", code: 500 }으로 제각각이다.

프론트엔드 개발자가 API마다 파싱 로직을 다르게 짜야 한다. 생산성이 심각하게 떨어진다.

응답 형식을 정하는 데 30분이면 되는데, 그 30분을 아껴서 앞으로 수십 시간을 날리는 거다.

HTTP 상태 코드를 대충 쓴다

모든 에러를 200으로 응답하고 body에 { success: false }를 넣는 API를 본 적 있다. 이러면 axios의 interceptor도 무용지물이다.

반대로 너무 세분화하는 것도 문제다. 422랑 400의 차이, 401이랑 403의 차이 정도는 구분하되, 418(I'm a teapot)까지 쓸 필요는 없다.

(실제로 418을 쓴 API를 봤다. 이유가 뭐였는지는 모르겠다.)

페이지네이션을 나중에 추가한다

"일단 전체 목록 내려주고, 나중에 페이지네이션 붙이자."

이 말의 문제는, 나중에 페이지네이션을 추가하면 기존 클라이언트 코드가 다 깨진다는 거다. 처음부터 { data: [...], meta: { page: 1, totalPages: 10, totalCount: 100 } } 형태로 내려줘야 한다. 데이터가 10건뿐이어도.

API는 한 번 배포하면 바꾸기가 코드 수정보다 10배 어렵다.

버전 관리를 안 한다

/api/v1/users에서 v1을 왜 붙이냐고 물어보는 사람이 있었다. 지금 v1밖에 없는데 불필요하지 않냐고.

6개월 후 응답 형식을 바꿔야 했다. 기존 모바일 앱 호환성 때문에 두 버전을 동시에 운영해야 했다. 그때 v1이 없었다면 대참사였을 것이다.

너무 많은 정보를 내려준다

사용자 목록 API에서 비밀번호 해시, 내부 ID, 관리자 메모까지 같이 내려보내는 경우를 실제로 봤다. 보안 문제도 문제지만 응답 크기가 불필요하게 커진다.

필요한 필드만 선택적으로 내려주거나, 최소한 민감한 정보는 별도 엔드포인트로 분리해야 한다.

결국 설계에 시간을 써야 한다

내가 생각하는 좋은 API는 예측 가능하고, 일관적이고, 확장 가능하다. 이 세 가지를 지키려면 구현보다 설계에 시간을 더 써야 한다.

코드를 한 줄도 안 짜고 인터페이스만 먼저 정의하는 게 결국 가장 빠른 길이다. 급하게 만든 API는 급하게 고쳐야 하고, 급하게 고친 API는 또 다른 문제를 만든다.

관련 글