1. Rest API 설계 가이드
1.1. 목적
API 호출을 통한 통신 시 일반적으로 많이 사용되는 REST API의 경우 명확한 표준이 없다. 따라서 개발자 또는 제공 시스템에 따라 다양한 형식으로 API를 개발해 사용하고 있다. 본 문서에서는 REST API 설계 표준을 정의함으로써 통일화된 API 표준을 적용할 수 있도록 한다. 이를 통해 각 시스템 간 민첩하고 일관성 있는 연계 구조를 확보할 수 있다.
1.2. 범위
REST API 설계 시 정의하게 되는 주요 항목들에 대한 표준을 제공한다. 설계 관련 항목들이기 때문에 개발 언어, 플랫폼, 프레임워크 등에 종속적이지 않다. 참고로 본 문서에서 제시하는 샘플 코드는 JAVA 기반으로 작성된 pseudo-code 이다.
2. REST API 설계 원칙
2.1. REST 정의
REST(Representational State Transfer)는 2000년 Roy Fielding이 제시한 네트워크 기반 아키텍처로, 리소스 상태(Resource State)의 표현(Representation)을 전송(Transfer)하는 방식에 대해 정의하고 있다.
웹 환경에서 제공되는 컨텐츠, DB 데이터 등을 리소스 개념으로 파악하여 각 리소스에 고유한 URI를 부여하고, 해당 리소스에 대한 CRUD 작업을 GET, POST, PUT, DELETE와 같은 HTTP 메소드를 이용해 처리하는 것을 REST라고 볼 수 있다.
REST에서는 명확하게 어떤 기술의 조합으로 통신을 할 것인지에 한 표준을 제시하고 있지 않다. 이로 인해 실제 적용하는 사이트에 따라 다양한 방법으로 REST를 구현하여 제공하고 있다는 문제점도 존재한다. 따라서 일관성 있는 REST 적용을 위해서는 REST 기본 원칙에 대한 이해가 선행되어야 하며, 이를 기반으로 API를 설계해야 한다.
2.2. REST API 설계 원칙
REST API를 설계한다고 하면 클라이언트에게 제공하고자 하는 리소스를 정의하고 해당 리소스에 대한 CRUD 기반 오퍼레이션을 정의할 것이다. 아래 그림은 customer 리소스 기반으로 CRUD 메소드를 매핑한 예제이다. 전체 customer 목록 리소스에 대해서는 /customers, 특정 customer 리소스에는 id를 구분자로 하는 /customer/{id}, 특정 customer가 가지고 있는 order 리소스에 대해서는 /customer/{id}/orders 형식의 URI를 부여하고 있다. 각 리소스 기준으로 정보 조회는 GET, 정보 변경은 PUT, 정보 생성은 POST, 정보 삭제는 DELETE 메소드를 이용하는 방식으로 REST API를 설계할 수 있다.
하지만 이는 REST 아키텍처의 가장 기본적인 내용만 반영된 API 설계로, REST API를 잘 설계하기 위해서는 다음의 REST API 설계 원칙을 준수하여야 한다.
2.2.1. <<일관성>> 있는 인터페이스 제공 (Uniform Interface)
○ 리소스 기반의 표현
각 리소스는 고유의 식별자, 즉 URI를 가져야 한다. 예를 들어 특정 고객 리소스에 대한 URI는 다음과 같이 표현할 수 있다.
/customers/1234 |
클라이언트와 API 간에는 특정 데이터 형식 기준으로 리소스 상태 정보를 교환하게 되며, 많은 경우 JSON 형식을 사용한다.
{“customerId” : 1234, “name” : “Henry”} |
○ 표준 HTTP 메소드 사용
리소스에 대한 작업은 표준 HTTP 메소드를 이용해서 표현해야 한다. CRUD 기준으로 POST, GET, PUT, DELETE 메소드를 사용한다.
○ 자기 서술성(Self-Descriptiveness)을 통한 직관성 확보
REST API는 자기 서술성이라는 특징을 가진다. 즉 REST API를 구성하는 리소스(URI), HTTP 메소드, 데이터 형식만 가지고도 직관적으로 무엇을 하는 API인지 이해할 수 있도록 설계해야 한다.
○ API와 클라이언트 간의 느슨한 결합 (Loosely Coupling)
REST API는 특정 기술에 종속적이지 않다. 기본적으로 HTTP 표준을 준수했다면 특정 플랫폼, 개발언어에 종속적이지 않다. 즉 굉장히 느슨한 결합(Loosely Coupling) 형태의 아키텍처이다.
2.2.2. Stateless
REST API는 기본적으로 Stateless 한 특성을 가진다.<API와 연관된 저장용 광역 변수가 없고, Input에 따른 Output이 같다> 즉 HTTP 요청에 대한 상태를 저장하지 않으며 정보는 리소스 자체에만 저장되어야 한다. API는 별도의 상태 정보를 관리하지 않고 요청 메시지만 가지고 비즈니스 로직을 처리하면 되기 때문에 구현을 단순화 할 수 있다.
2.2.3. Cache
REST API는 HTTP 기반으로 동작하기 때문에 HTTP에서 제공하는 캐시 기능을 활용할 수 있다. 조회 성격의 API를 설계할 때는 캐시 관련 HTTP 헤더를 이용해 캐시를 구현하여 사용하는 것을 권장한다. 아래 그림은 HTTP/1.1 기반으로 ETag 헤더를 이용한 캐시 구현 예시이다.
- 클라이언트는 특정 리소스에 대해 GET 요청을 보낸다.
- API에서는 조회 결과를 전송하면서 응답(Response) 헤더 ETag에 해당 리소스에 대한 Tag 값을 담아 전달한다.
- 클라이언트는 이후 동일 리소스에 대한 조회 요청을 할 때 현재 자신이 가지고 있는 리소스 정보를 API에 알리기 위해 <<이전에>> 전달 받은 ETag 값을 If-None-Match 헤더에 포함해서 전달한다.
- API는 클라이언트가 보내온 If-None-Match 헤더 값을 확인하여 <리소스 상태에 변경이 없는 경우>에는 304(Not Modified) 응답 코드를 전달하고, 클라이언트는 자체 캐시 해놓은 값을 사용하면 된다.
이와 같이 캐싱 정책을 적용함으로써 클라이언트-서버 간 상호 작용을 감소시켜 시스템의 성능을 향상 시킬 수 있다.
2.2.4. 클라이언트-서버 구조
REST API는 일관성 있는 인터페이스를 사용하기 때문에 클라이언트와 서버 간의 역할을 명확하게 구분할 수 있다. 서버에서는 비즈니스 로직을 처리할 수 있는 API를 제공한다. 그리고 클라이언트는 API를 호출하기 위한 인증/인가 정보, 컨텍스트 등을 직접 관리한다. 이러한 구조를 통해 클라이언트와 서버 간의 의존성을 낮출 수 있다.
2.2.5. 계층화
클라이언트는 API를 사용하고자 하면 정해진 스펙에 맞게 해당 API를 호출하면 된다. 즉 자신이 대상 서버를 직접 호출하는지, 아니면 중간에 다른 서버를 호출하는지 알 필요가 없다. 반면 REST API를 제공하는 시스템은 여러 계층 구조로 아키텍처를 구성할 수 있다. API를 통해 제공하고자 하는 비즈니스 로직을 구현한 업무 서버(WAS) 앞 단에 인증/인가, 암호화, 공유 캐시, 로드 밸런싱 등의 기능을 제공하는 서버를 추가 구성함으로써 유연하고 확장성 높은 아키텍처 구조를 가져갈 수 있다. APIM 시스템도 REST API의 계층화 특성을 대표하는 솔루션 중 하나이다.
3. 리소스 및 URI 설계
REST API는 리소스 중심으로 설계되며, 각 리소스마다 해당 리소스를 고유하게 식별할 수 있는 URI가 존재한다. 본 장에서는 리소스 및 URI를 설계할 때 준수해야 할 표준에 대해 정의한다.
3.1. 리소스 설계
REST API는 개별 주소를 가진 리소스들의 집합 개념으로 설계할 수 있다. 이때 각 노드들은 단일 리소스 또는 컬렉션 리소스로 구성된다.
- 단일 리소스 : 리소스는 상태 정보를 가지며, 0개 이상의 서브 리소스를 가짐
- 컬렉션 리소스 : 동일한 타입의 리소스 목록을 포함하며 컬렉션 내의 특정 리소스 구분자로 {id} 사용
/customers /customers/1234/phoneNumbers |
-- customer 집합 -- 특정 customer(1234)의 phone number 집합 |
리소스 설계 시에는 반드시 다음 항목을 고려해야 한다.
○ API를 통해 제공하고자 하는 비즈니스 엔터티에 집중해야 한다.
특정 업무 요건에서 제공해야 할 엔터티 및 해당 엔터티에서 수행할 수 있는 작업을 모델링 해야 한다. 여기서의 엔터티를 단일 물리적 데이터 항목과 혼돈해서는 안 된다. 예를 들어 'Customer Management'라는 리소스는 내부적으로 여러 개의 테이블로 구현되어 있을 것이다. 하지만 실제 클라이언트에게는 하나의 비즈니스 엔터티로 표시해 제공되어야 한다.
<<물리 엔터티(테이블)과 비즈니스 엔터티가 동일할 때도 있지만, 로직상 결합되어 있으면 새로운 엔터티명을 지정해야 한다.>>
URI 리소스 단위를 작게 가져갈 때와 크게 가져갈 때의 장단점
○ 단일 API 요청을 통해 필요한 정보를 검색할 수 있도록 리소스 결합을 고려해야 한다.
리소스를 너무 작게 쪼갤 경우 하나의 비즈니스 처리를 위해 여러 개의 API를 호출해야 한다. 이 경우 시스템에 부하를 줄 수 있다. 반면에 리소스를 너무 큰 개념으로 설계할 경우 해당 API에 대한 재사용성이 떨어질 수 있으므로 주의해야 한다. 즉 클라이언트 입장에서 해당 API를 얼마나 보편적으로 사용할 수 있는지에 대한 고려가 필요하다.
3.2. URI 설계
URI란 리소스를 의미하고, scheme, authority, path, query, fragment 등의 구성요소로 정의
클라이언트는 URI를 통해 API를 접하게 된다. 따라서 URI는 리소스 모델을 잘 전달할 수 있는 구조로 설계해야 한다. URI 기본 포맷은 RFC 3986 에서 정의한 기본 문법을 준수한다. URI 표준 포맷은 다음과 같다.
URI = scheme "://" authority "/" path [ "?" query ] [ "#" fragment ] |
- scheme : URI 인스턴스를 초기화하는데 사용되는 타입 반환
- authority : 서버의 DNS hostname 또는 IP 주소 및 포트 번호
- path : 해당 자원의 위치 경로를 순차적으로 의미
- query : (선택 사항) 특정 값 전달(쿼리 검색 값)
- fragment : (선택 사항) 특정 계층에서 보조 리소스 검색 시 사용
예를 들어 https://openpai.naver.com/map URI의 경우 아래와 같은 항목의 조합으로 구성된 것이다.
- scheme : https
- authority : openapi.naver.com
- path : map
3.3. 명명 규칙
리소스 및 URI에 대한 명명 규칙은 다음과 같다.
○ API 이름은 명사를 사용해야 하며, API 컨셉을 명확하게 나타낼 수 있는 특화된 이름을 사용한다.
○ URI를 구성하는 각 리소스 이름은 명사를 사용해야 하며, 소문자 또는 Camel Case를 사용한다.
/customerManagement/customer |
○ 컬렉션(Collection) 성격의 리소스는 복수 명사(s)를 사용한다
/accounts/account |
○ URI 구성 시 CRUD 성격의 메소드 이름, 즉 create, retrieve, update, delete, get, set 등과 같은 형태의 동사를 포함해서는 안 된다. 리소스 이름은 명사형으로 정의하고, CRUD 성격은 HTTP 메소드를 이용해 구분해야 한다.
/customerManagement/customer/deleteCustomer?id=8296 (X) |
○ 계층 관계를 나타내기 위해 구분자로 슬래시(/)를 사용한다. 단 URI 마지막 문자에는 슬래시(/)를 포함하지 않는다.
/customerBillManagement/customerBill/8297 (O)/customerBillManagement/customerBill/8297/ (X) |
○ URI 가독성을 높이기 위해 특수문자를 사용하고자 할 경우에는 하이픈(-)을 사용해야 한다. 밑줄(_)의 경우 글꼴에 따라 가독성이 떨어질 수 있으므로 사용하지 않도록 한다.
/customerManagement/marketing/marketing-popup (O)/customerManagement/marketing/marketing_popup (X) |
○ URI 길이 제한은 없으나 지나치게 길고 복잡할 경우 가독성이 떨어질 수 있으므로 주의한다. 업무 요건에 따라 URI를 몇 단계까지 구성할지에 대한 정책은 다르게 가져갈 수 있으며, 가독성을 고려하여 5 레벨을 넘지 않도록 한다.
4. HTTP 요청 메소드 설계
API에서 제공하고자 하는 오퍼레이션을 도출한 후에는 각 오퍼레이션 별로 HTTP 요청 메소드를 정의하여 사용해야 한다.
4.1. CRUD 기반 HTTP 요청 메소드 정의
REST 에서는 CRUD (Create, Read, Update, Delete)에 해당하는 HTTP 요청 메소드로 각각 POST, GET, PUT, DELETE를 사용한다. 만약 CRUD 성격으로 구분할 수 없는 오퍼레이션을 사용해야 할 경우 POST 메소드를 사용하도록 한다.
메소드
상세 설명
GET | Read 오퍼레이션객체의 현재 상태 값을 리턴 하고자 할 때 사용 |
POST | Create 오퍼레이션새로운 리소스를 생성하고자 할 때 사용또는 리소스에서 필요로 하는 CRUD 이외의 오퍼레이션 처리 시 사용 |
PUT | Update 오퍼레이션특정 객체 상태 값을 변경할 때 사용 |
DELETE | Delete 오퍼레이션객체를 삭제하고자 하는 경우 사용 |
일반적으로 사용되는 HTTP 메소드 설계 예제는 다음과 같다.
메소드
/customers
/customers/Henry
GET | customer 목록 리턴 | Henry 라는 이름의 customer 정보 리턴 |
POST | 새로운 customer 등록 | 에러 |
PUT | Bulk 성으로 여러 customer 정보 업데이트 | Henry 라는 이름의 customer 정보 업데이트 |
DELETE | 모든 customer 정보 삭제 | Henry 라는 이름의 customer 정보 삭제 |
4.1.1. GET 메소드
지정된 URI에 해당하는 리소스 상태 정보를 검색하여 응답 메시지 본문에 담아 전달한다.
- 작업이 정상적으로 처리된 경우 일반적으로 HTTP 응답 코드 200(OK)을 반환한다.
- 해당 리소스를 찾을 수 없는 경우 404(Not Found)를 반환한다.
4.1.2. POST 메소드
지정된 URI에 새로운 리소스를 생성하고자 할 때 사용하며, 새 리소스에 대한 세부 정보는 요청 메시지의 본문에 담아 전달한다.
- 서버는 정상적으로 리소스가 생성된 경우 HTTP 응답 코드 201(Created)를 반환한다. 또한 생성된 객체 위치 정보를 Location 응답 헤더에 담아 전달해야 한다.
- 클라이언트가 잘못된 데이터를 요청한 경우 HTTP 응답 코드 400(Bad Request)를 반환한다.
리소스에서 제공하고자 하는 오퍼레이션이 CRUD 성격으로 매핑되지 않는 경우에도 POST 메소드를 사용한다.
4.1.3. PUT 메소드
지정된 URI에 해당하는 리소스 상태 값을 변경하고자 할 때 사용한다.
- 리소스 업데이트가 성공한 경우 HTTP 응답 코드 200(OK) 또는 204(No-Content)를 반환한다.
- 리소스 충돌로 인해 업데이트가 불가능한 경우 HTTP 응답 코드 409(Conflict)를 반환한다.
4.1.4. DELETE 메소드
지정된 URI의 리소스를 삭제하고자 할 때 사용한다.
- 리소스 작업이 정상적으로 처리된 경우 HTTP 응답 코드 204(No-Content)을 반환한다.
- 해당 리소스를 찾을 수 없는 경우 404(Not Found)를 반환한다.
4.2. 기타 HTTP 요청 메소드 정의
GET, POST, PUT, DELETE 이외에 HTTP 에서 기본으로 제공하는 HEAD, OPTIONS 메소드를 사용할 수 있다.
4.2.1. HEAD 메소드
응답 데이터가 아닌, 응답 헤더 값을 조회하고자 할 때 HEAD 메소드를 사용한다. HEAD 메소드 요청 시 리소스에 대해 설명하는 응답 헤더만 반환하고 응답 메시지 본문은 빈 상태로 전달한다.
예를 들어 GET 요청을 통해 특정 리소스를 조회하기 전에 결과 데이터 크기를 사전에 파악하고 싶은 경우 HEAD 메소드를 사용하면 된다. 응답 헤더로 전달되는 Content-Length 값을 이용해 실제 데이터 크기를 확인할 수 있다.
Request
HEAD /customerBillManagement/customerBill/1234 |
Response
200 OK Content-Type: application/json Content-Length: 1036 |
4.2.2. OPTIONS 메소드
Allow 응답(Response) 헤더를 이용해 리소스에서 사용 가능한 메소드를 표기하는 용도로 사용할 수 있다.
Request
OPTIONS /customerBillManagement/customerBill |
Response
200 OK Allow: OPTIONS, GET, HEAD, POST |
4.3. 주의사항
4.3.1. GET, POST 메소드를 이용한 터널링 금지
Rest API 터널링이란? HTTP 메소드를 원래의 성격과 다르게 사용하는 방식을 의미한다.
HTTP 메소드를 원래의 성격과 다르게 사용해서는 안 된다. 다음의 케이스는 REST 안티 패턴으로 GET, POST 메소드를 다른 요청 방법을 터널링하는데 사용하는 경우이다.
[Example 1] GET을 이용한 터널링
GET /customerBillManagement/customerBill?method=update&id=8297 (X) |
업데이트 오퍼레이션을 GET 메소드로 잘못 사용하고 있는 예제이다. 조회성 오퍼레이션이 아니면서 GET 메소드를 사용해 실제 오퍼레이션 정보를 별도의 쿼리 파라미터를 이용해 전달해서는 안된다. 위와 같은 경우 PUT 메소드를 사용하고, 실제 변경하고자 하는 값은 요청 데이터로 전달하는 방식으로 구현해야 한다.
경우이다.
[Example 2] POST을 이용한 터널링
POST /customerBillManagement/customerBill (X) { “skill”: “getCustomerBill”, “id”: 8297 } |
신규 생성 성격이 아닌 오퍼레이션을 POST를 이용해 구현해서는 안 된다. 위의 예제에서는 실제 조회 성격의 오퍼레이션을 POST로 구현하면서 메소드 정보 및 조회에 필요한 키 값을 요청 데이터 형태로 전달하고 있다. 이 경우 GET /customerBillManagement/customerBill?id=8297 과 같은 형태로 사용해야 한다.
위와 같은 안티 패턴 사용 시 API의 성격을 일관성 있게 전달할 수 없다. 따라서 반드시 HTTP 메소드의 기본 성격을 기반으로 메소드를 매핑하여 사용해야 한다.
4.3.2. POST 메소드를 사용 시 멱등성(Idempotent) 고려
CRUD 성격으로 매핑이 불가능한 오퍼레이션의 경우 POST 메소드를 사용하며, 이때 반드시 멱등성(Idempotent)을 고려해야 한다.
멱등성이란? 여러 번 반복해서 오퍼레이션이 수행 되어도 결과가 같은 경우를 의미한다.
멱등성은 여러 번 반복해서 오퍼레이션이 수행 되어도 결과가 같은 경우를 의미한다. 예를 들어 동일한 리소스를 삭제하는 DELETE 메소드를 여러 번 호출하는 경우 HTTP 응답 코드는 200(OK)에서 404(Not Found)로 변하겠지만 리소스가 삭제된 결과는 동일하다. GET, PUT, DELETE는 멱등성이 보장되는 메소드이다.
반면 POST는 멱등성이 보장되지 않는다. 즉 동일한 오퍼레이션을 여러 번 수행하게 되면 매번 리소스에 상태 변화가 오게 된다. 예를 들어 특정 게시물을 조회할 때마다 조회 수를 1 증가시키는 오퍼레이션은 멱등성이 보장되지 않는 경우이다.
기본적으로 REST API는 Stateless한 특성을 가지기 때문에 개별 API에서 상태 정보를 관리하지 않는다. 따라서 POST 메소드 사용 시에는 프레임워크와 같은 공통 모듈 단에서 멱등성을 보장할 수 있는 별도의 트랜잭션 처리를 고려해야 한다. 위에서 예를 든 조회 수 증가 오퍼레이션과 같은 경우 처리 중 오류가 발생한 경우 별도의 트랜잭션 처리 로직을 통해 조회 수를 1 감소 시키는 작업을 진행해야 한다.
'[ARC] App Design > API Design' 카테고리의 다른 글
[API 설계] HATEOAS (0) | 2022.06.20 |
---|---|
[API 설계] API Versioning (0) | 2022.06.20 |
최근댓글