REST API 설계에 대해 고민하게 된 배경
첫 개발자로서 취업 후, "API Path만 보고도 어떤 기능을 하는지 알 수 있도록 설계해야 한다."라는 말을 들었다.
이를 위해 좋은 API 설계 원칙을 고민하며 여러 책을 찾아보게 되었다.
그중 제임스 히긴보텀이 작성한 "웹 API 설계 원칙"이라는 책을 읽게 되어, 해당 내용을 기반으로 정리하게 되었다.
후반부에 들어서며 RPC, 이벤트, 마이크로서비스에 어울리는 API 설계에 대한 내용이 작성되어 있지만,
전반부에 작성된 REST API에 대한 내용만 정리하였다.
API란 무엇일까?
API(Application Programming Interface)의 맥락에서 애플리케이션이라는 단어는 고유한 기능을 가진 모든 소프트웨어를 나타낸다.
인터페이스는 두 애플리케이션 간의 서비스 계약이며 요청과 응답을 사용하여 두 애플리케이션이 서로 통신하는 방법을 정의한다.
API 문서에는 개발자가 이러한 요청과 응답을 구성하는 방법에 대한 정보가 들어있다.
REST API란 무엇일까?
REST(Representational State Transfer)는 분산 구조의 하이퍼미디어 시스템을 고려한 아키텍처 스타일을 뜻한다.
로이 토마스 필딩 박사 학위 논문에서 소개된 용어로, 아키텍처 스타일에 대한 핵심 개념과 제약 조건, 이러한 조약 조건이 WWW 설계에 적용된 방법을 설명했다.
해당 논문에서 설명한 아키텍처 속성들은 네트워크 기반 API와 같은 분산 환경의 시스템에 유연하고 점진적으로 발전 가능한 방법으로 고려 사항들을 정립하는 역할을 한다
- 클라이언트/서버 (Client-Server):
- 클라이언트와 서버는 독립적으로 동작하며, 둘 사이의 상호작용은 요청(request)과 응답(response)의 형태로 이루어진다
- 이를 통해 클라이언트와 서버는 각각 독립적으로 발전할 수 있다
- 예: 모바일 앱(클라이언트)은 서버의 내부 구현을 알 필요 없이 API 명세만 알면 된다
- 무상태: 서버는 API사용자의 어떤 정보도 저장하지 않는다. 따라서 클라이언트의 요청에는 처리에 필요한 모든 정보가 포함되어야 한다.
- 계층 구조 시스템: 클라이언트는 요청에 응답하는 실제 서버 사이에 몇 개의 계층이 있는지 알 수 없다. 실제 경로의 세부 사항을 숨겨 사용이 쉽고 일관성 있게 한다. 이는 서버나 클라이언트 측의 캐싱, 역방향 프록시 및 권한 부여 계층화를 가능하게 하는 HTTP의 핵심 원칙이다.
- 캐싱 가능 여부: 서버의 응답은 캐시가 가능한지 여부에 대한 정보를 포함해 클라이언트와 미들웨어 서버들이 API 서버에서 제공받은 데이터를 별로도 캐시할 수 있게 한다.
- 코드 온 디맨드(Code on Demand): 클라이언트는 서버에 스크립트 또는 바이너리 형태의 실행 가능한 코드를 요청할 수 있다.
- 일관된 인터페이스: 통합 인터페이스를 통한 리소스 기반의 식별은 독립적인 진화를 가능하게 한다.
REST는 CRUD에 관한 것이 아니다.
일반적으로 알려진 것과 달리 REST기반 API는 데이터 통신에 있너 JSON 형식이나 생성-읽기-변경-삭제 패턴이 반드시 필요한 것은 아니다. API 설계에 REST 스타일을 적용할 때 필딩의 논문에서 설명한 고려 사항들을 의도적으로 온전히 적용하지 않을 수 있다.
이로 인해 REST 스타일을 온전히 준수해 구현한 'RESTful'과 선택적으로 구현한 'REST enough'사이에서 혼란을 초래한다.
1. REST는 클라이언트와 서버다
클라이언트 서버 구조는 REST에서 핵심적인 고려 사항이다.
서버는 가용한 리소스들을 제공하며, 동기 방식의 작업을 지원하고, 클라이언트가 이해할 수 있는 메세지 기반의 상호 작용을 한다.
2. REST는 리소스 중심이다.
REST에서 정보 은닉의 핵심은 리소스다. JSON, XML, CSV, PDF, 이미지 및 기타 미디어와 같은 데이터 형식이다.
3. REST는 메세지 기반이다.
REST 기반 API 설계는 JSON이나 XML의 문법적 속성으로 제한되는 것이 아니며 그 이상의 맥락적인 요소들을 포함한다.
리소스에 대한 내용은 전체 메세지에 일부며 본문에 해당한다. 전송 프로토콜의 설계적 요소들 또한 REST 기반 API 설계의 일부다. URL 경로, URL 매개변수 및 HTTP 요청/응답 헤더는 모두 설계 프로세스의 일부로 다뤄야 한다. 메세지 본문에만 집중하게 되면 완성도가 낮은 API 설계가 된다.
HTTP 메서드, URL, 요청 헤더와 요청 본문의 전체적인 조합이 클라이언트에게 서버로 전송되는 요청 메세지다. 클라이언트가 무엇을 하고 싶은지 서버에게 얘기한다. 응답 헤더, 응답 상태 코드, 응답 본문의 구성은 클라이언트 요청에 대한 응답 메세지다. REST기반 API가 메세지 기반의 통신이라는 것을 이해하면 API 설계는 메세지의 고도화와 API의 성장 및 성숙에 따라 진화할 수 있다.
4. REST는 계층 구조를 지원한다.
REST 아키텍처 스타일은 계층 구조 시스템이다.
클라이언트와 서버의 통신 사이에 여러 계층이 존재할 수 있으므로 클라이언트와 서버가 직접 통신한다고 가정하면 안 된다.
캐싱, 로깅, 인증, 부하 분산 등 여러 미들웨어 계층이 있을 수 있다.
5. REST는 코드 온 디멘드를 지원한다.
클라이언트는 리소스를 요청할 때 동작하는 코드를 요청할 수 있다. 클라이언트 애플리케이션에서 별도의 업그레이드 없이 API를 통해 자체적으로 기능이나 품질이 확대될 수 있다는 것이다.
브라우저가 로컬에서 매번 실행할 자바스크립트 파일을 다운로드해 실행하는 것이다.
REST기반 웹 API에서 많이 사용되지는 않지만, 다양한 활용도가 있는 방식 중 하나다. 클라이언트 측에서 코드를 작성하거나 유지 관리할 필요 없이 여러가지 필요한 기능이 즉각적으로 실행되는 API를 설계하고 구현할 수 있다.
6. 하이퍼미디어 제어
하이퍼 미디어 API는 링크에 의해 작동하는 API다. 링크는 관련이 있는 다른 리소스들을 참조하는 다른 API작업을 가리킨다.
하이퍼미디어는 API 전반에 걸쳐 다양한 리소스를 연결해 웹으로 작동할 수 있게 한다. 검색 엔진의 결과가 클릭해서 연결할 수 있는 리소스로 제공되지 않는다면 클라이언트는 웹에서 연결된 데이터들을 깊이 있게 탐색할 기회를 놓치게 된다.
하이퍼미디어 사용의 일반적인 예는 HAL(Hypertext Application Language)기반의 응답에서 찾아볼 수 있는 페이지네이션이다.
{
"page": {
"size": 20,
"totalElements": 1423,
"totalPages": 72,
"number": 3
},
"results": [
{
"id": "43",
"title": "글또 개발자 일기",
"publishedAt": "2024-11-23T15:00:00Z"
}
// ... 더 많은 검색 결과 ...
],
"_links": {
"self": {
"href": "/api/search?keyword=geultto&page=3"
},
"first": {
"href": "/api/search?keyword=geultto&page=0"
},
"prev": {
"href": "/api/search?keyword=geultto&page=2"
},
"next": {
"href": "/api/search?keyword=geultto&page=4"
},
"last": {
"href": "/api/search?keyword=geultto&page=71"
}
}
}
HATEOAS
클라이언트가 수행할 수 있는 작업을 링크의 제공 여부로 결정하는 것을 설명한다. 서버는 클라이언트에 비해 많은 권한 부여와 관련된 데이터들을 이해하고 처리하기에 용이하기 때문이며, 이러한 고려 사항이 반영되지 않는다면 클라이언트는 서버에서 수행하는 것과 동일한 수준의 복잡한 비즈니스 로직을 직접 처리하고 그 결과를 서버에 다시 동기화해야 하는 문제를 직접 해결해야 한다.
리차드슨 성숙도 모델을 사용한 REST 수준
레벨 0: 단일 API 작업 또는 엔드포인트에 의해 수신된다. 요청에 대한 추가 내용은 매개변수를 통해 전달되거나 요청의 본문에 포함해 전달한다. 모든 요청은 POST
POST /api/endpoint?action=getUserProfile
POST /api/endpoint?action=updateUserProfile
POST /api/endpoint?action=deleteUserProfile
레벨 1: 리소스가 공유의 URL과 상호작용하지만 필요한 경우 API에 대한 추가 매개변수를 사용한다.
GET /api/users/profile?userId=123
POST /api/users/profile?userId=123
DELETE /api/users/profile?userId=123
레벨 2: GET, POST, PUT처럼 적합한 HTTP 메서드와 응답 코드를 적절히 적용해 클라이언트와 서버 상호작용을 개선한다.
리소스의 개념이 도입되었다.
GET /api/users/123
POST /api/users
PUT /api/users/123
DELETE /api/users/123
레벨 3: 서버에서 비즈니스 로직 처리 결과를 하이퍼미디어 제어를 포함하는 자기 설명적 메시지로 전달해 클라이언트는 관련 리소스들과 유기적으로 통합한다.
{
"id": "123",
"name": "pli",
"email": "pli@geultto.com",
"_links": {
"self": { "href": "/api/users/123" },
"profile": { "href": "/api/users/123/profile" },
"posts": { "href": "/api/users/123/posts" },
"followers": { "href": "/api/users/123/followers" }
}
}
API와 API 아키텍트의 수준을 평가하고 폄하하는 목적으로 이 모델을 사용하는 것은 잘못된 것이다.
성숙도를 높이기 위한 측정 방법 중 하나이며, 모든 API에 대해 REST 스타일을 준수해야 한다는 표준을 제시하는 것이 아니다. 특정 설계에 대한 수준을 평가하려는 노력보다는 클라이언트의 요구 사항을 충족시키는 데 집중해야 한다고 이야기하고 있다.
아직 실무에서 레벨 3, HATEOAS를 준수하는 API는 경험해 보지 못했다.
REST API 설계 프로세스
1 단계: 리소스 URL경로 설계
리소스 이름을 소문자로 표기하고 공백 대신 하이픈(-)을 사용해 URL에 적합한 형태로 반환한다. 슬래시로 시작하고 리소스들의 모음을 나타내고자 복수 형태의 이름으로 표기한다.
중속 관계의 리소스는 상위 항목에 중첩되므로 경로에 상위 항목의 식별자가 포함되어야 한다.
종속 관계의 리소스에 대한 주의 사항
종속 관계에 대한 리소스 중첩은 자식 리소스에 대한 접근을 부모의 범위로 제한하기 위한 것이다. API 사용에서 중첩된 리소스를 이용하려면 부모 리소스의 식별자가 경로에 반드시 포함된다는 것에 유의해야 한다.
GET /courses/{courseId}/lectures/{lectureId}/comments/{commentId}
API 사용자가 주어진 URL 경로를 이용해 특정 아이디의 task 자겁을 호출하렴녀 부모 관계인 project의 아이디와 users의 아이디도 필요하다. 종속 관계의 리소스는 설계에서 유용한 옵션이지만 API의 사용성을 향상 시키는 겨우에만 사용해야한다.
리소스 모음은 복수형으로 표기한다. 이러한 규칙이 필수는 아니지만 일반적으로 REST API설계에서 많이 사용한다.
2 단계: API 작업을 HTTP 메서드에 매핑
HTTP 메서드 | 메서드 설명 | 안정선 분류 | 안정성 설명 |
---|---|---|---|
GET | 요청 데이터 반환 | 안정적 | 리소스의 상태가 변경되지 않음 |
POST | 리소스에 대한 상태 변경부터 신규 생성까지 다양한 시나리오에서 사용 | 안전하지 않음 | 동일한 입력값으로 여러 호출에 대해 동일한 결과를 보장할 수 없음 |
PUT | 클라이언트가 리소스를 교체 | 멱등성 보장 | 리소스 전체를 입력값으로 요청하기 때문에 여러 호출에 대해 동일한 결과를 보장 |
PATCH | 리소스의 부분에 대한 변경 수행 | 안전하지 않음 | 클라이언트가 리소스의 일부에 대한 변경만 입력값으로 요청하기 때문에 여러 호출에 대해 동일한 결과를 보장할 수 없음 |
DELETE | 서버에서 리소스 삭제 | 멱등성 보장 | 동일한 리소스에 대한 삭제는 리소스의 존재 여부와 상관없이 동일한 결과를 보장 |
예제
동작 | HTTP 메서드 | 엔드포인트 | 설명 |
---|---|---|---|
목록 조회 | GET | GET /articles | 게시글 목록 조회 (페이지네이션, 필터링 가능) |
GET /articles?page=2&size=10 | 페이지네이션 적용 | ||
GET /articles?category=tech&status=published | 필터링 적용 | ||
상세 조회 | GET | GET /articles/{articleId} | 특정 게시글 상세 조회 |
생성 | POST | POST /articles | 새 게시글 작성 |
전체 수정 | PUT | PUT /articles/{articleId} | 게시글 전체 내용 수정 |
부분 수정 | PATCH | PATCH /articles/{articleId} | 게시글 일부 내용 수정 |
PATCH /articles/{articleId}/status | 게시글 상태만 수정 | ||
삭제 | DELETE | DELETE /articles/{articleId} | 특정 게시글 삭제 |
처음 코딩을 배울 때 가장 많이 실수했던 부분이 HTTP Method에 대한 이해도가 떨어져서 발생했었다.
예를 들어 삭제 API를 만들 때, DELETE
Method를 설정했음에도 불구하고 /articles/1/delete
처럼 path 내부에 동사를 사용하는 경우가 있었다.
DELETE
메서드에서 이미 '삭제'라는 의미가 있기 때문에 Path에서는 어떤 자원의 특정 식별자를 삭제할 것인지만 명시해주면 된다.
잘못된 예시
POST /articles/create # 생성을 의미하는 create 불필요
GET /articles/show/1 # 조회를 의미하는 show 불필요
PUT /articles/update/1 # 수정을 의미하는 update 불필요
DELETE /articles/delete/1 # 삭제를 의미하는 delete 불필요
3 단계: HTTP 상태 코드 지정
HTTP 상태 코드는 다음과 같이 분류됩니다:
- 2xx: 성공
- 3xx: 리다이렉션
- 4xx: 클라이언트 오류
- 5xx: 서버 오류
상태 코드 | 설명 | 사용 예시 |
---|---|---|
200 OK | 요청이 성공적으로 처리됨 | GET 요청 성공, PUT/PATCH 요청으로 리소스 수정 성공 |
201 Created | 새로운 리소스 생성 성공 | POST 요청으로 새 리소스 생성 시 |
202 Accepted | 비동기 작업 요청 접수 | 대용량 데이터 처리 요청 접수 시 |
204 No Content | 성공했지만 응답 본문 없음 | DELETE 요청 성공 시 |
400 Bad Request | 잘못된 요청 구문, 유효하지 않은 요청 | 필수 파라미터 누락, 유효하지 않은 요청 데이터 |
401 Unauthorized | 인증 필요 | 로그인이 필요한 API 접근 시 |
403 Forbidden | 권한 부족 | 관리자 권한이 필요한 API에 일반 사용자가 접근 시 |
404 Not Found | 리소스를 찾을 수 없음 | 존재하지 않는 ID로 리소스 요청 시 |
409 Conflict | 리소스 상태와 충돌 | 이미 존재하는 이메일로 회원가입 시 |
429 Too Many Requests | 요청 횟수 초과 | API 사용량 제한 초과 시 |
500 Internal Server Error | 서버 내부 오류 | 예기치 않은 서버 오류 발생 시 |
503 Service Unavailable | 서비스 일시적 사용 불가 | 서버 유지보수, 과부하 상태 |
에러 응답 형식 예시
{
"status": 400,
"code": "INVALID_PARAMETER",
"message": "Invalid parameter value",
"details": {
"field": "email",
"value": "invalid-email",
"reason": "Invalid email format"
},
"timestamp": "2024-03-19T10:30:00Z"
}
4 단계: REST API 설계 문서화
API 모델링 및 설계 프로세스 전반에 걸쳐 확인된 API 이름, 설명, 기타 세부 정보들을 활용해 문서화를 시작한다.
시퀀스 다이어그램을 작성하면 API 설계 작업, 이벤트 스토밍, API 모델링 등의 설계 과정에서 도출된 요구 사항들을 잘반영했는지 검토할 수 있다.
5 단계: 공유하고 피드백 얻기
API 설계를 공유해 팀내부의 즉각적인 피드백과 다른 API 아키텍트 또는 API를 사용할 계획이 있는 내/외부 이해관계자들의 피드백을 받는다.
REST 설계 패턴
CRUD
CRUD(create-read-update-delete) API는 리소스 컬렉션이나 개별 리소스에 대해 생성, 읽기, 변경, 삭제와 같은 라이프사이클 전체 또는 일부를 API로 제공한다.
리소스 라이프 사이클 확장
HTTP 메서드의 선택은 제한적이기 때문에 HTTP 사양에 준수하면서도 확장성을 확보할 방안을 찾아야한다.
사용자의 상태 관리 같은 CRUD에 해당하지 않는 업무 흐름을 처리해야 하는 상황을 생각하자.
POST /users/{userId}/activate # 계정 활성화
POST /users/{userId}/deactivate # 계정 비활성화
POST /users/{userId}/suspend # 계정 정지
POST /users/{userId}/verify # 이메일 인증
이러한 방법을 통해 리소스에 대해 업무 흐름에 필요한 추가적인 기능을 확장할 수 있다.
싱글톤 리소스
싱글톤 리소스는 컬렉션이 아닌 단일 인스턴스로 존재하는 리소스를 의미합니다. 주로 현재 인증된 사용자 정보나 설정과 같이 유일한 리소스를 표현할 때 사용됩니다.
GET /me # 현재 로그인한 사용자 정보 조회
GET /me/profile # 현재 사용자의 프로필 조회
PATCH /me/profile # 현재 사용자의 프로필 수정
- GET /user/{userID} 대신 사용할 수 있으며 사용자가 자신인 경우 스스로를 참조하는 가상의 리소스를 사용하고 아이디 값이 노출되지 않기 때문에 보안에 대한 공격상황서 아이디 값도용 가능성을 방지한다.
싱글톤 리소스는 이미 존재하므로 클라이언트가 미리 생성할 필요가 없다. CRUD 스타일의 전체 라이프사이클에 대한 API를 제공하지 않을 수 있지만 GET, PUT, PATCH와 같은 HTTP 메서드를 이용한 API를 제공하기도 한다.
REST에서 장기 실행 트랜잭션 처리
트랜잭션을 완료하는 데 여러 API 작업이 필요한 경우가 있다.
REST 기반 API에서는 빌더 설계 패턴을 적용해 유사한 요구사항을 처리할 수 있다.
예를 들어 이메일을 대량으로 발송하는 업무를 생각해보자.
vip 멤버들의 이메일 목록을 조회하는 용도로는 사용할 수 있다.GET /users/emails?grade=vip
하지만 4명을 위해 메일을 한 번에 발송하는 용도로는 사용할 수 없다. 4개의 독립된 API 요청이 하나의 트랜잭션으로 묶일 수 없기 때문이다.
POST /emails/user1@example.com
POST /emails/user2@example.com
POST /emails/user3@example.com <- 실패
POST /emails/user4@example.com
대신 bulk-email-campaigns 리소스로 한 번에 처리할 수 있다.
POST /bulk-email-campaigns
{
"recipients": [
"user1@example.com",
"user2@example.com",
"user3@example.com",
"user4@example.com"
],
"email": {
"title": "VIP 회원 전용 할인 쿠폰",
"content": "안녕하세요, VIP 회원님...",
"attachments": ["coupon-123"]
}
}
출처
제임스 히긴보텀이 작성한 "웹 API 설계 원칙"
'Server > ETC.' 카테고리의 다른 글
[Git] BFG Repo Cleaner 사용한 Git Commit 또는 History 삭제 (2) | 2023.07.11 |
---|---|
[Github] Code Review (1) | 2023.07.02 |
[Github] Pull Request (0) | 2023.07.02 |
[Github] Issue 생성 (0) | 2023.07.02 |
[Postman] DTO와 File을 동시에 전송하는 법 (1) | 2023.01.27 |