Search
📒

4-2. CRUD와 REST

앞서 간단한 형태로 CRUD 기능을 작성하여 보았습니다. 다만 저희가 작성한 것은 크게 설계와 연관된 고민 없이, 단순하게 작동만 하는 기능으로 구성되어 있습니다. 규칙없이 작성된 API는 사용하는 (Frontend) 개발자 입장에서 연동에 혼란을 야기할 가능성이 높습니다. 저희가 API를 만들 때는 URL의 각 부분을, 의도된 역할에 맞게 정보를 담아서 사용하도록 하며, 요청의 의도를 HTTP 요청 자체에서 확인하기 좋은 형태로 만들 것을 권장받게 됩니다.
이때 등장하는 개념이 REST입니다. REST라는 이론은 HTTP 개발에 참여한 Roy Fielding의 박사학위 논문에 나온 내용으로서, 저희가 API를 만들때 지켜야할 가이드라인에 대한 내용이 기재된 이론입니다. REST의 제약사항들을 만족함으로서 저희가 제작한 API는 성능, 확장성, 범용성 등의 측면에서의 향상을 기대할 수 있습니다.
REST는 기본적으로 HTTP에 한정된 이야기가 아닙니다. 어떠한 형태의 API를 구성할때든 간에 지키면 좋은 설계 원칙에 대한 이야기 입니다. 작은 규모의 기업체에서 HTTP를 활용한 API를 REST API라고 부르는 것은 흔히 저지르게 되는 오류입니다.

RESTful API의 제약사항

REST API를 만들때 지켜야 할 제약 사항들을 지켜보면 총 6가지에 대하여 이야기 합니다.
1.
Client - Server Architecture
2.
Statelessness
3.
Cacheability
4.
Layered System
5.
Code On Demand (Optional)
6.
Uniform Interface

Client - Server Architecture

기본적인 웹 개발에서 이야기하는 Client - Server 모델과 같은 이야기로서, 관심사 분리에 관한 제약사항 입니다. 간단하게 이야기하면 사용자 인터페이스와 자원의 관리에 대한 책임을 분리함으로서 서로 간의 결합성을 줄이는 것에 대한 제약사항입니다. 무엇보다 관심사가 잘 분리되어 있으면 전체 서비스를 구성하는 각각의 구성 부분들이 개별적으로 발전할 수 있다는 장점을 관철할 수 있습니다.

Statelessness

요청을 받는 대상, 흔히 말하는 서버는 상태를 저장하지 않아야 한다는 제약사항입니다. 어떤 특정한 상태를 저장해서, 예를 들면 현재의 사용자가 누구인지에 대한 정보를 저장하는 책임은, 서버가 아니라 클라이언트에게 있도록 하는 제약사항입니다. 서버에서 요청의 내용을 파악하는데 이전에 보낸 요청의 정보가 필요하게 되면 그만큼 서버의 부담으로 다가오게 되고 성능저하를 일으킨다는 점을 지적하는 부분입니다.

Cacheablility

현대의 인터넷 브라우저는 서버에서 보낸 응답을 캐싱(임시 저장)하여 후의 요청에 대하여 캐시된 데이터를 대신 사용합니다. Cacheablility 제약사항은, 사용자에게 오래된 정보를 전달하지 않기 위해서, 각 응답이 자기 자신을 언제까지 캐시에 저장하는 것이 가능한지, 또는 캐시가 불가능한지를 암시적으로 또는 명시적으로 기록하여야 합니다.

Layered System

사용자의 브라우저가 서비스를 제공하는 서버에 도달하기 까지 많은 계층이 존재합니다. Layered System 제약사항은, 클라이언트가 서비스를 사용하기 위해 해당 서버까지 가는 중간 계층을 알 필요 없어야 한다는 제약사항입니다. 예시로 proxy나 load balancer 등 요청을 분산시키는 중간 계층이 존재하더라도, 클라이언트와 서버간의 통신을 방해하는 요소가 없도록 하여야 한다는 제약사항입니다.

Code on Demand (Optional)

서버에서 일시적으로 클라이언트의 기능을 확장할 수 있어야 한다는 제약사항입니다. HTML을 기준으로 생각한다면, 인터넷 브라우저는 JS를 해석할 수 있습니다. 서버에서 application/javascript 형태의 응답을 보낸다면, HTML 상에서 해당 JS를 활용하여 사용자에게 새로운 기능을 제공할 수 있을 것입니다.
다만 해당 제약사항 같은 경우 다른 제약사항들에 비하여 덜 필수적이라고 볼 수 있습니다.

Uniform Interface

이 제약사항은 API의 Interface가 일관성 있는 구조로 이뤄져야 한다는 제약사항입니다. 웹 서비스의 Backend API의 경우, URL의 각 부분의 의도에 맞게 경로가 자원을 표현하고, HTTP를 사용하는 만큼 HTTP Method를 이용해 해당 자원에 어떤 작업을 할것인지 알 수 있는 형태로 만들어야 한다는 의미입니다.
이중 Uniform Interface 제약을 만족시킬 수 있도록 이전에 하였던 CRUD 예시를 다시 고쳐봅시다. 앞서 언급하였듯, URL의 구조 중 Path를 활용하여 저희가 다루고자 하는 자원을 표현할 수 있는 형태로 작성하고, 자원에 대하여 어떤 작업을 하고 싶은지에 따라 GetMapping, PostMapping 등을 적당한 상황에 맞춰 사용하면 됩니다.
HTTP API를 만들때 Method의 의미
먼저 URL을 작성한것을 생각해보면, URL에 자원에 하고자 하는 목적 자체가 어느정도 드러나 있습니다. URL의 경로는 자원을 구분하는 용도로 사용하는 것이 권장된다고 하였습니다. 이를 새로운 PostRestController 를 정의하여 나타내어 봅시다.
@RestController @RequestMapping("post") public class PostRestController { ...
Java
복사
PostRestController 에 작성된 @RequestMapping 은 결국 전체가 post 라는 경로에서 시작하도록 설정됩니다. 본래 PostController 에서는 이후 경로를 통하여 목적을 분류했습니다.
@PostMapping("create") public void createPost(@RequestBody PostDto postDto){ logger.info(postDto.toString()); this.postList.add(postDto); } @GetMapping("read-all") public List<PostDto> readPostAll(){ logger.info("in read all"); return this.postList; }
Java
복사
경로를 자원이 표현되게 하려면, 위의 경로 설정은 빈칸으로 두고, Method를 통해 분류하여도 충분합니다. 여기에 요청의 처리에 대한 상세한 정보를 전달하기 위해 @ResponseStatus 로 200 이외의 응답(201 Created, 204 No Content 등)을 보낼 수도 있습니다.
@PostMapping() @ResponseStatus(HttpStatus.CREATED) public void createPost(@RequestBody PostDto postDto){ logger.info(postDto.toString()); this.postList.add(postDto); } @GetMapping() public List<PostDto> readPostAll() { logger.info("in read all"); return this.postList; }
Java
복사
HTTP Status Code
생성과 전체 조회 외의 기능들은 정확히 어떤 자원을 지칭 하는지에 대한 정보가 추가로 필요합니다. 이 역시 URL에 포함을 해주면, @RequestMapping 에 경로상의 변수의 위치를 지정, 함수의 인자로 받을 수 있습니다.
@GetMapping("{id}") public PostDto readPost(@PathVariable("id") int id) { logger.info("in read one"); return this.postList.get(id); } @PutMapping("{id}") @ResponseStatus(HttpStatus.ACCEPTED) public void updatePost( @PathVariable("id") int id, @RequestBody PostDto postDto ) { logger.info("target id: " + id); logger.info("update content" + postDto); this.postList.set(id, postDto); } @DeleteMapping("{id}") @ResponseStatus(HttpStatus.ACCEPTED) public void deletePost(@PathVariable("id") int id){ this.postList.remove(id); }
Java
복사
이렇게 API Endpoint를 구성하게 되면,
1.
/post 경로에서 전체 Post 자원을 다루기 위한 모든 동작
2.
/post/id/ 경로에서 특정 Post 자원을 지칭한 동작
3.
HTTP Method를 기준으로 상세한 동작
와 같이 API를 정의하게 됩니다. 이 API의 사용자 입장에선 Post 자원을 다루기 위해서 알아야 하는 경로는 하나이며, Method를 변경해가며 사용하고자 하는 기능을 쉽게 조작할 수 있습니다. 만약 다른 자원에 대한 API를 만들때 위의 구조를 지켜준다면, 다른 자원을 대상으로 하는 같은 기능은 경로만 변경하고, Method는 원래 사용하던 형태로 남겨둘 수 있습니다. 일관된 인터페이스의 예로서 생각할 수 있습니다.