[TIL - 2024-09-09] REST, RESTful API, 실습
서론
오늘은 REST 와 RESTful API 에 대하여 공부를 하고 내용을 정리해보았다.
본론
REST 개념 정리
REST(Representational State Transfer)는 웹 애플리케이션에서 클라이언트와 서버 간의 데이터를 주고받는 방식을 정의한 아키텍처 스타일을 말한다. REST는 자원을 URI로 식별하고, 자원에 대한 다양한 작업을 HTTP 메서드를 통해 수행한다, 즉 자원을 고유한 이름(URI)로 구분하고, 그 자원의 상태를 주고 받는 방식을 말한다.
자원(Resource)
여기서 말하는 자원은 서버에서 관리하는 모든 데이터를 의미하며, 각 자원은 고유한 URI(Uniform Resource Identifier)로 식별 된다. 자원의 종류는 다양하며 자원의 종류와 URI를 예를 들어보았다.
자원의 종류
- 이미지 파일
- 서버에 저장된 이미지, 아이콘, 그림 등
- "https://example.com/images/photo.jpg"
- 이 URI는 "photo.jpg"라는 이미지 파일 자원을 가리킨다.
- 텍스트 파일
- 서버에 저장된 .txt, .md와 같은 파일
- "https://example.com/files/readme.md"
- 이 URI는 "readme.md" 라는 텍스트 파일을 가리킨다.
- 문서
- Word, PDF와 같은 파일
- "https://example.com/documents/report.pdf"
- 이 URI는 "report.pdf"라는 문서 자원을 가리킨다.
- 사용자 정보
- 사용자 이름, 이메일, 주소 등 개인 정보
- "https://example.com/users/123"
- 이 URI는 "ID 123번 사용자"라는 자원을 가리킨다.
- 상품 목록
- 온라인 쇼핑몰에서 판매하는 상품 정보.
- "https://example.com/products"
- 이 URI는 전체 상품 목록이라는 자원을 가리킨다.
- 게시글
- 블로그나 소셜 미디어에 올린 글.
- "https://example.com/posts/45"
- 이 URI는 "ID 45번 게시글"이라는 자원을 가리킨다.
이처럼 자원은 매우 다양한게 존재할 수 있으며, 서버에 저장되는 모든 데이터를 자원이라고 표현한다. 각 자원은 고유한 URI를 가지며, 이를 통해 클라이언트는 자원에 접근할 수 있게된다. 또한 고유한 자원에 대해 HTTP 메서드를 통해 다양한 작업을 수행할 수 있게 된다.
HTTP 메서드
다음으로 HTTP 메서드이다. HTTP 메서드는 클라이언트가 서버에게 해당 자원(URI)에 대해 어떤 작업을 수행하고 싶은지 명확하게 전달하는 역할을한다. 이를 통해 클라이언트는 자원에 대해 조회, 생성, 수정, 삭제 등의 작업을 서버에 요청할 수 있다.
HTTP 메서드 종류
- GET
- 서버에 자원 조회를 요청할 때 사용한다.
- 데이터를 읽어오는 작업만 수행하며 서버나 데이터의 영향을 주지 않는다.
- "GET https://example.com/users/1" : ID가 1인 사용자의 정보를 가져온다.
- "GET https://example.com/products" : 모든 상품 목록을 가져온다.
- POST
- 서버에 새로운 자원 생성을 요청할 때 사용한다.
- 주로 서버에 데이터를 추가하거나 새로운 정보를 생성할 때 사용된다.
- "POST https://example.com/users" : 새로운 사용자를 추가한다.
- "POST https://example.com/order" : 새로운 주문 정보를 생성한다.
- PUT
- 서버에 기존 자원 대체하거나 수정을 요청할 때 사용된다. PUT은 전체자원을 새 데이터로 덮어씌운다.
- " PUT https://example.com/users/1" : ID가 1인 사용자의 정보를 전체적으로 업데이트 한다.
- "PUT https://example.com/products/101" : 상품 번호가 101인 상품 정보를 수정한다.
- PATCH
- 서버의 기존 자원 일부를 수정하는데 사용된다. PUT과 달리 자원의 일부부만 변경할 때 사용된다.
- "PATCH https://example.com/users/1" : ID가 1인 사용자의 일부 정보를 수정한다.
- DELETE
- 서버에서 기존 자원을 삭제하는데 사용된다.
- "DELETE https://example.com/users/1" : ID가 1인 사용자를 삭제한다.
- "DELETE https://example.com/products/101" 상품 번호가 101인 상품 정보를 삭제한다.
이처럼 클라이언트가 서버에게 자원(URI)에 대하여 어떤 작업을 수행하고 싶은지 명확하게 전달하는 역할을 한다. 이제 서버는 이 요청에 대해 어떻게 응답을 할까?
서버 응답
서버는 클라이언트에게 HTTP 응답을 통해 데이터를 반환하거나, 요청이 성공 했는지 실패했는지 알린다. 서버의 응답에는 상태코드, 헤더 정보, 본문 데이터가 포함되고 여러 구성 요소가 추가 될 수 있다.
서버 응답 구성 요소
- HTTP 상태 코드
- 클라이언트의 요청이 성공했는지, 실패했는지, 추가적인 작업이 필요한지 등을 나타낸다.
- 클라이언트의 요청이 성공했는지, 실패했는지, 추가적인 작업이 필요한지 등을 나타낸다.
- 헤더 정보
- 응답에 대한 추가 정보와 메타데이터를 포함한다.
- Cotent-type : 응답 데이터 형식 (application/json, text/html) 등
- Content-Lengh : 응답 본문의 크기
- Cache-Control: 응답이 캐시될 수 있는지 여부
- Set-Cookie: 쿠키 설정
- Authorization: 인증 관련 정보
- 응답에 대한 추가 정보와 메타데이터를 포함한다.
- 본문 데이터
- 서버가 클라이언트에게 반환하는 실제 데이터를 말한다.
- 일반적으로 JSON, XML, HTML, 텍스트 등 다양한 형식으로 제공된다.
- 그 외에도 쿠키나, 리다이렉션 등등 여러가지 요소가 포함 될 수 있다.
REST는 데이터 중심으로 설계된 방식이기 때문에, 데이터를 전송하는데 주로 JSON, 이나 XML과 같은 형식을 사용한다.
REST의 설계 원칙
- 클라이언트 - 서버 구조
- 클라이언트는 사용자 인터페이스를 담당하고, 서버는 데이터 처리와 저장을 담당한다.
- 이렇게 역할을 분리하면, 클라이언트와 서버가 서로 독립적으로 발전할 수 있고, 클라이언트가 서버의 내부 구조를 알 필요 없이 요청만 보내면 된다.
- 무상태성
- 서버는 클라이언트의 상태를 기억하지 않는다.
- 각 요청은 완전히 독립적이며, 클라이언트가 필요한 정보를 매번 요청에 포함해서 보내야 한다.
- 예를 들어, 로그인 상태를 서버가 기억하는 대신, 클라이언트가 매 요청마다 인증 정보를 보내는 것이다.
- 캐시 가능성
- 웹 표준 HTTP 프로토콜을 그대로 사용하므로 캐싱 기능을 사용할 수 있다.
- 서버 응답을 캐시할 수 있어야 한다. 자주 바뀌지 않는 데이터는 캐시를 통해 성능을 높일 수 있다.
- 예를 들어, 자주 업데이트되지 않는 제품 목록은 캐시를 이용해 클라이언트가 서버에 반복해서 요청하지 않도록 할 수 있다.
- 계층적 구조
- 클라이언트와 서버 사이에 중간 계층(예: 보안, 로드 밸런서)이 있을 수 있다.
- 클라이언트는 서버에 직접 요청을 보내는 대신, 중간에 다른 서버들이 있을 수 있다는 사실을 알아야 한다.
- 예를 들어, 요청이 여러 서버를 거쳐도 클라이언트는 이를 신경 쓰지 않고, 마지막에 요청을 처리한 서버로부터 응답을 받는다.
- 일관된 인터페이스
- API의 구조가 일정해야 한다.
- 이 원칙을 따르면, 클라이언트는 서버 내부 구조를 몰라도 쉽게 요청을 보낼 수 있다.
- 이 원칙을 실현하기 위한 하위 원칙:
- 자원의 식별: 모든 데이터(자원)는 고유한 URL로 식별된다.
- 예: https://example.com/users/123 → 123번 사용자를 식별하는 URL
- 자원 조작
- 클라이언트는 JSON이나 XML 같은 데이터 표현을 통해 자원을 조작한다.
- 자체 설명적 메시지:
- 요청과 응답에는 모든 필요한 정보가 포함되어야 하며, 추가 설명이 없어도 그 의미를 알 수 있어야 한다.
- HATEOAS (Hypermedia as the Engine of Application State):
- 응답에 다른 관련 자원으로 이동할 수 있는 링크가 포함되어야 한다.
- 예: 사용자의 정보를 요청했을 때, "사용자 정보 수정" 링크를 함께 받는 경우.
- 자원의 식별: 모든 데이터(자원)는 고유한 URL로 식별된다.
https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html
[Network] REST란? REST API란? RESTful이란? - Heee's Development Blog
Step by step goes a long way.
gmlwjd9405.github.io
https://gamhongshi.tistory.com/22
REST 개념 정리 및 논문 분석
'REST하다. RESTful하다' 라는 게 정확히 뭔데? 얼마 전에 회사에서 API를 만든 후, 코드 리뷰를 할 때, '이 코드는 Rest 하지 못한 거 같다.'라는 말을 들을 때, 그게 무슨 뜻인지 얼추 짐작은 가지만 정
gamhongshi.tistory.com
REST API 개념
REST API란 이런 REST의 설계 원칙을 준수하여 설계된 API를 RESTful API 또는 REST API라고 부른다.
API란
API는 "Application Programming Interface)의 약자로 소프트웨어 간의 상호작용을 가능하게 하는 인터페이스이다, 즉 API는 한 프로그램이 다른 프로그램의 기능을 사용할 수 있도록 도와주는 규칙과 도구라고 말할 수 있다.
예를 들어, API는 자판기와 비슷하게 동작한다고 생각할 수 있다. 사용자가 자판기에 돈을 넣고 원하는 음료 버튼을 누르면, 자판기는 음료를 제공하게 된다. 이 과정에서 사용자는 음료가 어떻게 보관되고 제공되는지에 대해 알 필요가 없다. 단순히 버튼을 눌러서 음료를 받는 것처럼, 프로그램도 API를 통해 서버에 요청을 보내고, 결과를 받아오는 과정을 거친다.
API는 사용자가 요청한 작업을 서버나 응용 프로그램에 전달해주는 역할을 한다. 마치 자판기가 버튼을 눌러 요청된 음료를 꺼내 주는 것처럼, API는 프로그램의 요청을 서버에 전달하고, 서버가 처리한 결과를 프로그램에 전달한다. 사용자는 자판기 내부에서 음료가 어떻게 만들어지는지 알지 않아도 버튼만 누르면 원하는 음료를 얻을 수 있듯이, API를 사용하는 프로그램도 복잡한 내부 동작 방식을 몰라도 요청만 하면 결과를 받을 수 있게된다.
즉, API는 프로그램들이 서로 데이터를 주고받고 작업을 요청할 수 있도록 하는 도구로, 그 과정을 간단하고 효율적으로 만들어주는 역할을 한다.
REST API 설계 규칙
REST API를 설계할 때에는 일관성 있고 확장가능한 API를 만들기 위해 몇 가지 중요한 규칙을 따라야 한다.
- 명확하고 직관적인 URI 설계
- URI는 자원을 식별하는 고유한 경로이다.
- URI는 자원을 명확하게 식별할 수 있어야한다.
- 동사가 아닌 명사를 사용해 자원을 나타내는것이 일반적이다.
- 잘못된 URI: /getUserData
- 올바른 URI: /users : 사용자의 자원 목록을 나타냄
- 특정 자원 접근: /users/123 : ID가 123인 사용자의 자원
- HTTP 메서드 사용
- HTTP 메서드를 적절히 사용하여 자원에 대한 CURD 작업을 구분한다.
- GET: 자원 조회
- POST: 새로운 자원 생성
- PUT: 기존 자원 수정 (전체 업데이트)
- PATCH: 기존 자원의 일부 수정
- DELETE: 자원 삭제
- GET /users: 모든 사용자 목록 조회
- POST /users: 새로운 사용자 추가
- PUT /users/123: ID가 123인 사용자의 전체 정보 수정
- DELETE /users/123: ID가 123인 사용자를 삭제
- HTTP 메서드를 적절히 사용하여 자원에 대한 CURD 작업을 구분한다.
- 자원과의 관계를 URI로 표현
- 관련된 자원 간의 관계는 URI 경로를 통해 명확하게 표현해야 한다.
- 특정 사용자의 주문 정보: /users/123/orders
- 특정 주문의 세부 정보: /users/123/orders/456
- 관련된 자원 간의 관계는 URI 경로를 통해 명확하게 표현해야 한다.
- 복수형 사용
- 자원은 복수형을 사용하여 API가 여러 개의 자원을 다루고 있음을 명확히 한다.
- /users (올바름) vs. /user (잘못됨)
- 자원은 복수형을 사용하여 API가 여러 개의 자원을 다루고 있음을 명확히 한다.
- HTTP 상태 코드 사용
- 클라이언트 요청에 대한 결과는 적절한 HTTP 상태 코드를 반환한다.
- 자원의 상태를 표현하는 HTTP 응답
- 응답 본문은 자원의 현재 상태를 표현해야 하며, 보통 JSON 또는 XML 형식으로 반환한다.
Todo 리스트 API 만들어보기
목표 : 스프링 프레임워크를 사용하여, 간단한 Todo 리스트 API를 구현하기
주요 기능 :
1. 할 일 목록 조회 (GET)
2. 새로운 할 일 추가 (POST)
3. 특정 할 일 조회 (GET)
4. 할 일 수정 (PUT / PATCH)
5. 할 일 삭제 (DELETE)
엔드 포인트 :
1. GET /todos - 모든 할 일 목록 조회
- 클라이언트는 /todos 엔드포인트로 요청을 보내, 서버에서 전체 할 일 목록을 받는다.
2. POST /todos - 새로운 할 일 추가
- 클라이언트는 /todos로 새로운 할 일을 추가하는 요청을 보낸다.
3. GET /todos/{id} - 특정 할 일 조회
- 클라이언트는 /todos/1과 같은 형식으로 요청을 보내, 특정 ID에 해당하는 할 일을 조회한다.
4. PUT /todos/{id} - 특정 할 일 수정 (전체 업데이트)
- 클라이언트는 /todos/1에 대한 요청을 보내, ID가 1인 할 일의 전체 정보를 수정한다.
5. DELETE /todos/{id} - 특정 할 일 삭제
- 클라이언트는 /todos/1 요청을 보내, ID가 1인 할 일을 서버에서 삭제한다.
데이터 저장 :
데이터베이스를 사용하지 않고 메모리에 저장할 예정이다.
Todo
`Todo` 클래스는 할 일(Todo) 정보를 가지고 있는 역할을 하며, 할 일에 대한 아이디(id), 제목(title), 그리고 설명(description)을 멤버 변수로 가지고 있다.
@Getter
@Setter
public class Todo {
private Long id;
private String title;
private String description;
public Todo(Long id, String title, String description) {
this.id = id;
this.title = title;
this.description = description;
}
}
TodoRepository
TodoRepository는 할 일(Todo)에 대한 데이터를 저장, 조회, 수정, 삭제하는 역할을 담당한다. `Map`을 이용하여 Todo를 데이터를 메모리에 저장하고, 할 일에 대한 기본적인 CURD 작업을 수행한다.
@Repository
public class TodoRepository {
private static final Map<Long, Todo> repository = new HashMap<>();
private static Long sequence = 0L;
public Todo createTodo(Todo todo) {
todo.setId(++sequence);
repository.put(todo.getId(), todo);
return todo;
}
public Todo findById(Long id) {
return repository.get(id);
}
public List<Todo> findAll() {
ArrayList<Todo> list = new ArrayList<>();
for (Long id : repository.keySet()) {
list.add(repository.get(id));
}
;
return list;
}
public Todo updateTodo(Long id, Todo todo) {
Todo findTodo = findById(id);
findTodo.setTitle(todo.getTitle());
findTodo.setDescription(todo.getDescription());
return findTodo;
}
public void deleteTodo(Long id) {
repository.remove(id);
}
}
TodoController
TodoController는 클라이언트로부터 들어오는 HTTP 요청을 처리하는 역할을 하며, CRUD 작업을 수행하는 엔드포인트를 제공한다. 각 메서드는 GET, POST, PUT, DELETE와 같은 HTTP 메서드에 맞게 설계되어, 할 일 목록의 조회, 추가, 수정, 삭제 등의 작업을 처리할 수 있다.
@RequestMapping("/todolist")
@RequiredArgsConstructor
@RestController
public class TodoController {
private final TodoRepository todoRepository;
@GetMapping
public List<Todo> getTotalTodos() {
return todoRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodo(@PathVariable Long id) {
Todo todo = todoRepository.findById(id);
if (todo == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(todo);
}
@PostMapping
public ResponseEntity<Todo> createTodo(@RequestBody Todo todo) {
if (todo.getTitle() == null || todo.getTitle().isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Todo newTodo = todoRepository.createTodo(todo);
return ResponseEntity.status(HttpStatus.CREATED).body(newTodo);
}
@PutMapping("/{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todo) {
Todo updatedTodo = todoRepository.updateTodo(id, todo);
if (updatedTodo == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
return ResponseEntity.ok(updatedTodo);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
Todo todo = todoRepository.findById(id);
if (todo == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
todoRepository.deleteTodo(id);
return ResponseEntity.noContent().build();
}
}
응답 결과 - Todo 추가
응답 결과 - Todo 조회
응답 결과 - Todo 모두 조회
응답 결과 - Todo 수정
응답 결과 - Todo 삭제
결론
개념적으로 공부를하고 실습을 통해 REST와 RESTful API의 개념에 대해 조금이나마 이해할 수 있는 시간이였다. 주말이나 쉬는날을 이용하여 일주일간 배운내용을 기록하는 WIL도 작성해봐야겠다.