본문 바로가기
Refactoring/Moonkey

[졸업작품] spring boot로 작업한 뭉키를 다시 돌아보며 (2) - REST API 설계 규칙

by 수짱수짱 2023. 2. 10.

1. Rest API 설계 규칙을 지키지 않은 점. Rest API 설계 규칙을 다시 돌아보자

Rest란 Representational State Transfer의 약어로 클라이언트와 서버가 데이터를 주고 받는 방식에 대한 아키텍처 스타일이다. Rest에는 여섯 가지 기본 원칙이 있고 이 가이드를 준수한 인터페이스는 Restful하다고 표현한다.

 

  • Stateless (무상태성)
    • 서버는 Response cache-control 헤더에 해당 요청의 캐싱 가능 여부를 제공해야 한다.
    • 이를 제공한다면 클라이언트는 Response를 캐싱하여 서버와 클라이언트 간의 상호작용을 줄이고 성능과 서버 가용성을 늘릴 수 있다.
  • Uniform interface (일관된 인터페이스)
    • 보편적인 소프트웨어 엔지니어링 원칙을 component interface에 적용하면 전체적인 시스템 아키텍처는 단순화되고 각 상호작용에 대한 가시성이 개선된다.
    • 이름 그대로 일관된 인터페이스를 얻기 위해 REST에서는 4가지 인터페이스 규칙을 정의한다.
      • 1. identification of resources
        • 요청 시 개별 자원을 식별할 수 있어야 한다.
      • 2. manipulation of resource through representations
        • 어떤 자원에 대해 작업하기 위해 적절한 표현과 메타데이터를 충분히 갖고 있다면 서버는 해당 자원을 변경, 삭제할 수 있는 정보를 갖고있다는 의미
      • 3. self-descriptive messages
        • 자신을 어떻게 처리해야 하는지 정보를 포함해야 한다.
      • 4. hypermedia as the engine of application state
        • 단순 결과 뿐만 아니라 결과에 대한 정보를 포함해야 한다.
  • Layered system (다중 계층)
    • rest는 다중 계층 구조를 가질 수 있도록 허용한다.
      • Ex) api서버와 db서버 그리고 인증 서버를 따로 둘 수 있음.
    • 각 레이어는 자기와 통신하는 컴포넌트 외 레이어에 대해서 정보를 얻을 수 없다.
    • 클라이언트는 rest 서버와만 상호작용할 뿐 rest가 상호작용하는 레이어나 그 외 중간 레이어들, end server에 직접적으로 요청할 수 없으며 이들의 상호작용 또한 볼 수 없다.
  • Code on demand (Optional)
    • 서버가 클라이언트에서 실행시킬 수 있는 로직을 전송하여 클라이언트의 기능을 확장시킬 수 있다.
    • 이를 통해 클라이언트가 사전에 구현해야 하는 기능의 수를 줄여 간소화시킬 수 있다.

1.1 RESTful API Naming 규칙

URI(Uniform Resource Identifier)는 정보의 자원을 표현해야 한다. 자원에 대한 행위는 HTTP Method으로 표현한다.

단, HTTP Method나 동사 표현이 URI에 들어가면 안된다. 

/* 잘못된 사례 */
GET 	/chatrooms/get/{id}
POST 	/chatrooms/create
DELETE 	/chatrooms/delete/{id}
PUT 	/chatrooms/update/{id}


/* 올바른 사례 */
GET	/chatrooms/{id}
POST	/charooms
DELETE	/chatrooms/{id}
PUT	/chatrooms/{id}

 

  • Uniform : 리소스 식별하는 통일된 방식
  • Resource (=Representation) : 자원, URI로 식별할 수 있는 모든 것 (제한 없음)
    • URI는 Resource만 식별할 수 있다.
    • Ex) 미네랄 캐라 -> 미네랄이 리소스, 회원을 등록하고 수정하는 기능 -> 회원이라는 개념 자체가 리소스
    • 동사보다는 명사를, 대문자보다는 소문자를 사용
  • Identifier : 다른 항목과 구분하는 데 필요한 정보
 

* URL은 Uniform Resource Locatior의 약자로 URI의 하위 개념이며 컴퓨터 네트워크 상의 자원을 모두 나타낸다

구조 = cheme:[//[user:password@]host[:port]][/]path[?query][#fragment]

  • URL은 제일 앞에 자원에 접근할 방법을 정의해 둔 프로토콜 이름을 적는다. ex) http, ftp, gopher 등
  • 프로토콜 이름 다음엔 프로토콜 이름을 구분하는 구분자 ":"을 적는다.
  • 만약 IP 혹은 Domain name 정보가 필요한 프로토콜이라면 ":" 다음에 "//"를 적는다.
  • 프로토콜명 구분자안 ":" 혹은 "//" 다음에는 프로토콜 마다 특화된 정보를 넣는다.

 

예1) http://www.somehost.com/a.gif

IP 혹은 Domain name 정보가 필요한 형태 ( www.somehost.com에 있는 a.gif를 가리키고 있음 )


예2) ftp://id:pass@192.168.1.234/a.gif 

IP 혹은 Domain name 정보가 필요한 형태 ( 192.168.1.234에 있는 a.gif를 가리키고 있음 )


예3) mailto:somebody@mail.somehost.com

IP정보가 필요없는 프로토콜 ( mailto 프로토콜은 단지 메일을 받는 사람의 주소를 나타냄 )

 

 

 

< Reference: https://cron-tab.github.io/2018/05/30/http-url-domain-subdomain-hostname/ >

 

 

1) 리소스를 표현하기 위해 명사를 사용

restful uri가 가르키는 resource는 수행되는 행위가 아니라 객체이다.

이 resource는 네 가지 범주로 나눌 수 있고 어느 범주에 해당되는지 확인 후 그 범주에 맞는 네이밍 규칙을 사용해야 한다.

 

  • 문서(Document)
    • 단일 개념(파일 하나, 객체 인스턴스, 데이터베이스 row 등)
    • 단수 사용(/device-management, /user-management)
http://api.example.com/device-management/managed-devices/{device-id}
http://api.example.com/user-management/users/{id}

 

  • 컬렉션(Collection)
    • 서버가 관리하는 Resource directory
    • 서버가 리소스의 URI를 생성하고 관리
    • 복수 사용(/users)
    • POST 기반 등록 (ex. 회원 관리API)
http://api.example.com/user-management/users
http://api.example.com/user-management/users/{id}

 

  • 스토어(Store)
    • 클라이언트가 관리하는 자원 저장소
    • 클라이언트가 리소스의 URI를 알고 관리
    • PUT 기반 등록 (ex. 정적 컨텐츠 관리, 원격 파일 관리)
    • 복수 사용(/files)
http://api.example.com/files
http://api.example.com/files/new-files.txt

 

  • 컨트롤 URI
    • 문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스 실행
    • 즉, 명사로 행위를 표현하기 어려울 때 제한적으로 동사를 사용하도록 하는 것이 컨트롤 URI이다.
    • 동사를 직접 사용(/checkout, /play 등)
      • ex. GET, POST만 사용할 수 있는 HTTP FORM 경우 컨트롤 URI(동사로 된 리소스 경로 사용)를 사용
      • /members/delete
      • /members/new 등
http://api.example.com/cart-management/users/{id}/cart/checkout
http://api.example.com/song-management/users/{id}/playlist/play

 

2) 핵심은 "일관성"

  • 계층 관계 표현을 위해 `/` 를 사용
  • 마지막 부분엔 복수형을 사용
    • 불규칙 복수형(person/people, goose/geese)들은 다루지 않는 것이 좋다.
  • 소문자 사용
    • Schema, HOST에는 대소문자 구별이 없다. 단, 이 외는 대소문자가 구별된다.
    • URI = scheme "://" authority "/" path [ "?" query ] [ "#" fragment ]
    • 1번과 2번은 같은 URL이다.
      • 1. http://api.example.org/my-folder/my-doc
      • 2. HTTP://API.EXAMPLE.ORG/my-folder/my-doc
    • 1번과 3번은 다른 URL이다.
      • 3. http://api.example.org/My-folder/my-doc
  • 가독성을 위해 `-`(하이픈) 사용
  • 마지막 문자로 '/' 사용 금지
    • 많은 웹 component, framework는 아래 두 URI를 같게 인식한다.
      • http://api.canvas.com/shapes/
      • http://api.canvas.com/shapes
    • 그러나 모든 URI의 모든 글자들은 resource의 고유한 구별자로 계산된다.
    • 두 개의 다른 URI는 다른 자원을 가르킨다. 만약 URI가 다르다면 자원도 다른 것이다.
    • 따라서, 자원을 모호하게 식별하려는 client의 시도는 허용하지 않아야 하므로 후행 슬래시를 사용하지 않아야 한다.
  • `_`(언더스코어) 사용 금지
    • 일부 브라우저, 화면에서 _ 는 가려질 수 있기에 언더스코어 대신 하이픈을 사용한다.
  • 파일 확장자 사용 금지
    • ex) http://api.example.com/device-management/managed-devices.xml
    • 대신 http://api.example.com/device-management/managed-devices 로 이용
    • body의 내용이 어떻게 흐르는지 결정하는 Content-Type header를 통해 전달되는 media-type에 의존해야 한다.
  • resoure간에 연관 관계가 있는 경우
    • /리소스명/리소스 ID/연관 관계에 있는 다른 리소스명
    • ex) GET /users/{userid}/devices  -> user가 소유한 device 관계
    • 봐바 {userid}도 소문자로 표현하죠? 모르고 카멜케이스 쓰면 안됨‼ (내가 계속 까먹네)

 

 

3) CRUD 함수명 사용 금지

URI는 리소스를 가르키는 것이지 어떤 동작이 수행되는지 가르키는 것이 아니다.

리소스에 대한 작업은 HTTP Method를 이용해라.

/* BAD */
POST http://api.test.com/users/1/delete-post/1

/* GOOD */
DELETE http://api.test.com/users/1/posts/1

 

 

* HTTP Method

  • GET
    • 리소스 조회
  • POST
    •  리소스 생성
  • PUT
    • 리소스 전체 갱신 (놓겠다, 넣겠다)
  • PATCH
    • 리소스 부분 갱신 (붙이겠다)
  • DELETE
    • 리소스 삭제
  • HEAD
    • GET과 유사하나 HTTP 메시지 헤더만 반송하고 데이터 내용을 보내지 않는다
    • 즉, 헤더만 보겠다는 의미
  • OPTION
    • 통신 옵션을 통지, 조사
  • TRACE
    • Request 라인과 헤더를 그대로 클라이언트에게 반송
    • Reqeuest 치환상태를 조사할 때 사용
  • CONNECT
    • 암호화된 메시지를 Proxy로 전송

* HTTP Method 설계예시

CRUD HTTP URI
전체 리소스 조회 GET /resources
특정 리소스 조회 GET /resources/:id
리소스 생성 POST /resources
리소스 전체 수정 PUT /resources/:id
리소스 일부 수정 PATCH /resources/:id
특정 리소스 삭제 DELETE /resources/:id

 

 

 

4) 필터를 위해 쿼리 파라미터 사용

Resource에 대한 정렬, 필터링, 페이징은 신규 API를 생성하지 않고 쿼리 파라미터를 사용해라.

http://api.example.com/device-management/managed-devices
http://api.example.com/device-management/managed-devices?region=USA
http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ
http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ&sort=installation-date

< Reference >

- https://prohannah.tistory.com/156

- https://dkrnfls.tistory.com/218?category=1026591 

- https://library.gabia.com/contents/8339/

- https://one-it.tistory.com/entry/RESTful-API-%EC%84%A4%EA%B3%84-%EA%B7%9C%EC%B9%99


이렇게 위에서 정리한 내용들을 토대로

실제로 내가 작성한 코드의 URI 규칙을 검사해 보았다.

 

실제로 위 사진 부분을 지킨 url이 있었다.

 

reg라는 동사를 통해 AccountController의 post method를 수행하였다. 단, reg말고 register라고 표현했으면 좀 더 직관적이었을 것 같다.


해당 naming 규칙은 지켜지지 않은 것을 확인했다.

이는 DeliveryController의 매핑부분이다. 

대문자를 사용함과 하이픈을 사용하지 않아 가독성이 떨어지는 부분을 확인하였다.

이 부분은 아래와 같이 바꾸는 것이 restful api 설계 규칙을 준수하는 것이다.

// @GetMapping("/delivery/requestList")
@GetMapping("/delivery/request-list")

// @GetMapping("/delivery/processList")
@GetMapping("/delivery/process-list")

// @GetMapping("/delivery/completeList")
@GetMapping("delivery/complete-list")

대문자를 소문자로 바꾸고 하이픈을 사용함으로써 가독성이 많이 개선된 부분을 확인할 수 있다.

 

이는 PartyController의 매핑부분이다.

마찬가지로 대문자 사용과 하이픈 미사용으로 가독성이 떨어지는 것을 확인할 수 있다.

아래와 같이 바꾸어 restful api 설계 규칙을 준수하자.

// @GetMapping("/party/myPartyList")
@GetMapping("/party/my-party-list")

// @GetMapping("party/activatesList")
@GetMapping("party/activates-list")

대문자를 소문자로 바꾸고 하이픈을 사용함으로써 가독성이 많이 개선된 부분을 확인할 수 있다.

 

이는 StoreController의 매핑 부분이다.

대문자 사용, 하이픈 미사용으로 가독성이 떨어진다.

아래와 같이 개선하여 설계 규칙을 준수한다.

// @GetMapping("store/myList")
@GetMapping("/store/my-list")

해당 naming 규칙이 지켜지지 않은 것을 확인했다.

이는 OrderController Get method 매핑 부분이다.

URI에 get 동작이 수행된다는 것을 가르키는 부분이 존재한다. 이는 naming 규칙에 어긋나는 URI이다.

해당 부분은 아래와 같이 수정하는 것이 restful api 설계 규칙을 준수하는 것이다.

// @GetMapping("/order/{orderId}/get")
@GetMapping("order/{orderId}")

httmp method를 가르키는 부분을 URI에서 제거함으로써 어떤 동작이 수행되는지 가르키지 않게 되었다.

 

이는 ReviewController Post method 매핑 부분이다.

URI에 post 동작이 수행되는 것을 가르키는 부분이 존재하므로 naming 규칙에 어긋난다.

해당 부분은 아래와 같이 수정하는 것이 restful api 설계 규칙을 준수하는 것이다.

// @PostMapping("/review/{storeId}/post")
@PostMapping("/review/{storeId}/register")

 

이는 StoreController의  Get method 매핑 부분이다.

URI에 get 동작이 수행되는 것을 가르키는 부분이 존재하므로 naming 규칙에 어긋난다.

해당 부분은 아래와 같이 수정하는 것이 restful api 설계 규칙을 준수하는 것이다.

// @GetMapping("/store/{storeId}/get")
@GetMapping("/store/{StoreId}")

// @GetMapping("/store/get/{ownerId}")
@GetMapping("/store/owner-store/{ownerId}")