스프링 데이터 REST
- 스프링 데이터는 코드에 정의한 인터페이스를 기반으로 Repository를 자동으로 생성하고 필요한 기능을 수행한다.
- 스프링 데이터 REST는 스프링 데이터의 모듈 중 하나로, 스프링 데이터가 생성하는 Repository의 REST API를 자동으로 생성한다.
- 따라서, 스프링 데이터 REST를 빌드에 추가하면 정의한 각 리포지토리 인터페이스를 사용하는 API를 얻을 수 있다.
의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
- 빌드에 해당 의존성만 지정해주면 이미 스프링 데이터를 사용 중인 프로젝트에서 REST API를 노출시킬 수 있다.
- 스프링 데이터 REST가 생성하는 REST 엔드포인트를 사용하려면 @RestController 어노테이션이 지정된 모든 클래스들을 제거해야 한다.
기본 경로 설정
- 스프링 데이터 REST가 생성한 엔드포인트들은 GET, POST, PUT, DELETE 메서드 모두 지원해주므로, 데이터의 추가 삭제 또한 가능하다.
- 다만, 해당 API의 엔드포인트가 다른 컨트롤러와 충돌하는 것을 막기 위해 기본 경로를 지정해주는 것이 좋다.
- 스프링 데이터 REST가 자동 생성한 API의 기본 경로는 spring.data.rest.base-path 속성에 설정한다.
// 스프링 데이터 REST 엔드포인트는 /api로 시작
spring:
data:
rest:
base-path: /api
예) /api/ingredients GET 요청 결과
{
"_embedded": {
"ingredients": [
{
"name": "Flour Tortilla",
"type": "WRAP",
"_links": {
"self": {
"href": "http://localhost:8080/api/ingredients/FLTO"
},
"ingredient": {
"href": "http://localhost:8080/api/ingredients/FLTO"
}
}
},
...
{
"name": "Sour Cream",
"type": "SAUCE",
"_links": {
"self": {
"href": "http://localhost:8080/api/ingredients/SRCR"
},
"ingredient": {
"href": "http://localhost:8080/api/ingredients/SRCR"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/ingredients"
},
"profile": {
"href": "http://localhost:8080/api/profile/ingredients"
}
},
"page": {
"size": 20,
"totalElements": 10,
"totalPages": 1,
"number": 0
}
}
리소스 경로와 관계 이름 조정
- 스프링 데이터 리포지토리의 엔드포인트를 생성할 때 스프링 데이터 REST는 해당 엔드포인트와 관련된 엔티티 클래스 이름의 복수형을 사용한다.
- 예) Member -> members, Order -> orders
- 그러나 기대하는 값과 스프링 데이터 REST가 복수형으로 바꾼 값이 다른 경우가 발생할 수도 있다.
- 이러한 스프링 데이터 REST의 복수형 관련 문제점은 @RestResource 어노테이션으로 해결할 수 있다.
- rel 속성에는 원하는 관계 이름을, path 속성에는 원하는 경로를 입력
@Entity
@RestResource(rel = "users", path = "users")
public class Member {
...
}
※ 참고: API 기본 경로로 GET 요청을 하면, 노출된 모든 엔드포인트의 링크를 갖는 홈 리소스 내역을 얻을 수 있다.
{
"_links": {
"users": {
"href": "http://localhost:8080/api/users{?page,size,sort}",
"templated": true
},
"orders": {
"href": "http://localhost:8080/api/orders{?page,size,sort}",
"templated": true
},
"ingredients": {
"href": "http://localhost:8080/api/ingredients{?page,size,sort}",
"templated": true
},
"tacos": {
"href": "http://localhost:8080/api/tacos{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://localhost:8080/api/profile"
}
}
}
페이징과 정렬
- 홈 리소스의 모든 링크는 선택적 매개변수인 page, size, sort를 제공한다.
- 컬렉션 리소스를 요청하면 기본적으로 한 페이지당 20개의 항목이 반환되며, page와 size 매개변수를 통해 요청에 포함될 페이지 번호와 페이지 크기를 조정할 수 있다.
- 예를 들어, 페이지 크기가 5인 두 번째 페이지를 요청하는 경우 아래와 같이 GET 요청을 하면 된다. (페이지 번호는 0번부터 시작)
http://localhost:8080/api/users?size=5&page=1
※ 참고: HATEOAS는 처음, 마지막, 다음, 이전 페이지의 링크를 요청 응답에 제공하므로, API의 클라이언트는 현재 페이지가 어딘지 계속 파악하면서 매개변수와 URL을 연관시킬 필요가 없다.
- 또한 sort 매개변수를 지정하면 엔티티의 속성을 기준으로 결과 리스트를 정렬할 수 있다.
- 예를 들어, 최근 생성된 12개의 주문을 가져오는 경우 아래와 같이 페이징과 정렬 매개변수를 같이 지정할 수 있다.
http://localhost:8080/api/orders?sort=createdAt,desc&page=0&size=12
- 그러나 이러한 매개변수들을 사용해서 리스트를 요청하기 위한 UI 코드가 하드코딩되어야 한다는 문제가 존재한다.
- 만약 API 요청 URL이 변경된다면 하드코딩으로 인해 클라이언트 코드가 실행되지 않을 수도 있기 때문에, 이러한 문제점을 해결하기 위해 클라이언트가 링크 리스트에서 URL을 찾는 방법을 이용한다.
커스텀 엔드포인트 추가
- 기본적인 CRUD API가 아닌 나에게 필요한 엔드포인트를 생성해야 하는 경우 @RestController 어노테이션이 지정된 빈을 구현해서 스프링 데이터 REST가 자동 생성하는 엔드포인트에 추가할 수 있다.
- 이때 두 가지 고려사항이 존재한다.
- 내가 추가한 엔드포인트 컨트롤러는 스프링 데이터 REST의 기본 경로로 매핑되지 않는다.
- 내가 컨트롤러에 정의한 엔드포인트는 스프링 데이터 REST 엔드포인트에서 반환되는 리소스의 하이퍼링크에 자동으로 포함되지 않는다. 즉, 클라이언트가 관계 이름을 사용해서 커스텀 엔드포인트를 찾을 수 없다.
1. 기본 경로 문제 해결
- 스프링 데이터 REST는 스프링 데이터 REST 엔드포인트에 구성되는 것과 동일한 기본 경로로의 매핑을 지원하는 @RepositoryRestController 어노테이션을 제공한다.
- @RepositoryRestController가 지정된 컨트롤러의 모든 경로 매핑은 spring.data.rest.base-path 속성의 값이 앞에 붙은 경로를 갖는다.
- 한 가지 주의할 점은 @RepositoryRestController는 메서드의 반환값을 응답 바디에 자동으로 담아주지 않기 때문에, 해당 메서드에 @ResponseBody 어노테이션을 지정하거나, 해당 메서드에서 응답 데이터를 포함하는 ResponseEntity를 반환해야 한다.
2. 커스텀 하이퍼링크 추가하기
- 스프링 HATEOAS는 리소스 프로세서 빈을 선언하면 스프링 데이터 REST가 자동으로 포함시키는 링크 리스트에 해당 링크를 추가할 수 있는 RepresentationModelProcessor를 제공한다.
- RepresentationModelProcessor는 API를 통해 리소스가 반환되기 전에 리소스를 조작하는 인터페이스이다.
@Configuration
public class SpringDataRestApiConfig {
@Bean
public RepresentationModelProcessor<PagedModel<EntityModel<Member>>> memberProcessor(EntityLinks links) {
return new RepresentationModelProcessor<PagedModel<EntityModel<Member>>>() {
@Override
public PagedModel<EntityModel<Member>> process(PagedModel<EntityModel<Member>> model) {
model.add(
links.linkFor(Member.class)
.slash("recent")
.withRel("recents"));
return model;
}
};
}
}
'Spring > Rest API' 카테고리의 다른 글
비동기 메시지 전송 (JMS, RabbitMQ) (0) | 2022.02.09 |
---|---|
REST 서비스 사용 (0) | 2022.01.30 |
REST 엔드포인트 정의 (0) | 2022.01.30 |
RestContoller 요청과 응답 방법 (0) | 2022.01.20 |
REST API 인증 기법 (0) | 2022.01.20 |