목차
1. 스프링 부트 데이터 레스트로 REST API 구현하기
2. @RepositoryRestController를 사용하여 REST API 구현하기
3. 프로젝션, 롤, 이벤트 등 세부적인 설정 처리
4. HAL 브라우저 적용하기
시작하기 전 yml 설정
- DB는 AWS RDS를 사용하였습니다.
- spring.data.rest.base-path : API의 모든 요청의 기본 경로를 지정합니다.
- spring.data.rest.default-page-size : 클라이언트가 따로 페이지 크기를 요청하지 않았을 때 적용할 기본 페이지 크기를 설정합니다.
- spring.data.rest.max-page-size : 최대 페이지 수를 설정합니다.
그 외 아래와 같은 프로퍼티 설정도 가능합니다.
이름 | 설명 |
page-param-name | 페이지를 선택하는 쿼리 파라미터명을 변경합니다. |
limit-param-name | 페이지 아이템 수를 나타내는 쿼리 파리미터형을 변경합니다. |
sort-param-name | 페이지 정렬값을 나타내는 쿼리 파라미터형을 변경합니다. |
default-media-type | 미디어 타입을 지정하지 않았을 때 사용할 기본 미디어 타입을 설정합니다. |
return-body-on-create | 새로운 엔티티를 생성한 이후에 응답 바디(Response Body) 반환 여부를 설정합니다. |
return-body-on-update | 엔티티를 수정한 이후에 응답 바디 반환 여부를 설정합니다. |
enable-enum-translation | 'rest-messages'라는 프로퍼티 파일을 만들어서 지정한 enum 값을 사용하게 해줍니다. 적합한 enum 값(DEFAULT, ALL, VISIBILITY, ANNOTATED)을 키로 사용합니다. |
detection-strategy | 리포지토리 노출 전략을 설정하는 프로퍼티값입니다. RepositoryDetectionStrategy 인터페이스 내부에 구현된 enum 값으로 설정합니다. |
detection-strategy에 RepositoryDetectionStrategy에 주어진 enum 값은 아래와 같습니다.
- ALL : 모든 유형의 리포지토리를 노출합니다.
- DEFAULT : public으로 설정된 모든 리포지토리를 노출합니다.
- ANNOTAION : @(Repository) RestResource가 설정된 리포지토리만 노출합니다. 여기서 @(Repository) RestResource가 'exported'로 설정된 flag 값이 false가 아니어야 합니다.
- VISIBILITY : public으로 설정된 인터페이스만 노출합니다.
1. 스프링 부트 데이터 레스트로 REST API 구현하기
http://localhost:8081/api/boards 결과
호출해보니 HATEOAS를 지키며 MVC 패턴을 활용한 방법보다 더 많은 링크 정보를 제공하고 있습니다.
boards 키의 내부 _links는 해당 Board와 관련된 링크 정보를 포함하고 외부 _links는 Board의 페이징 처리와 관련된 링크 정보를 포함합니다. 링크를 살펴보면 관련된 데이터를 어떻게 호출해야 할지 힌트를 얻을 수 있습니다.
이러한 정보는 키값(key : value) 형식으로 구성되어 있어서 클라이언트가 키를 참조하도록 코드를 설정한다면 서버에서 요청된 데이터의 정보가 바뀌더라도 클라이언트 입장에서는 코드를 수정할 필요가 없습니다.
2. @RepositoryRestController를 사용하여 REST API 구현하기
스프링 부트 데이터 레스트의 기본 설정을 사용해서 편리하게 정보를 구성했지만 모든 사람이 위와 같은 데이터형을 원하지는 않을 겁니다. 또느 좀 더 최적화하고 싶은 사람도 있을 겁니다. 이럴 때는 @RepositoryRestController 어노테이션을 사용하면 좋습니다. 이는 @RestController를 대체하는 요도로 사용하며 두가지 주의사항이 있습니다.
첫째, 매핑하는 URL 형식이 스프링 부트 데이터 레스트에서 정의하는 REST API 형식에 맞아야 합니다.
둘째, 기존에 기본으로 제공하는 URL 형식과 같게 제공해야 해당 컨트롤러의 메서드가 기존의 기본 API를 오버라이드합니다.
1. @GetMapping("/boards") - /api/boards로 스프링 부트 데이터 레스트에서 기본적으로 제공해주는 URL 형식을 오버라이드합니다.
2. PageMetadata - 전체 페이지 수, 현재 페이지 번호, 총 게시판 수등의 페이지 정보를 담는 PageMetadata 객체를 생성합니다.
3. PagedResources - 컬렉션의 페이지 리소스 정보를 추가적으로 제공해주는 PagedResources 객체를 만들어 반환값으로 사용합니다.
4. resources.add() - 필요한 링크를 추가합니다.
3. 프로젝션, 롤, 이벤트 등 세부적인 설정 처리
프로젝션으로 노출 필드 제한하기
특정 사용자 개인정보처럼 민감한 데이터를 다룰 때는 한정된 값만 사용하도록 제한을 걸어야 됩니다.
http://localhost:8081/api/users/1 결과
스프링 부트 데이터 레스트는 반환값을 제어하는 3가지 방법을 제공합니다.
1. 도메인 필드에 @JsonIngnore 추가
2. @Projection을 사용
3. 프로젝션을 수동으로 등록 - RepositoryRestConfigurerAdapter 클래스를 상속받아 configureRepositoryRestConfiguration 메소드를 오버라이드하여 프로젝션을 등록할 수 있습니다. 따로 예제로 살펴보지 않겠습니다.
1. 도메인 필드에 @JsonIngnore 추가
- 가장 간단합니다.
- User 도메인의 경우 다음과 같이 password 필드에 사용하면 적절합니다.
2. @Projection을 사용
- 프로젝션을 설정하여 원하는 필드만 제한할 수 있습니다. 프로젝션은 사용자에게 제공되는 정보를 줄일 수도 있고 반대로 제공하지 않던 데이터를 가져올 수도 있습니다. 다음과 같은 사항에 유의해서 사용해야 합니다.
- 프로젝션 인터페이스 생성 시 반드시 해당 도메인 클래스와 같은 패키지 경로 또는 하위 패키지 경로에 생성해야 합니다.
- @RespositoryRestResource 어노테이션의 excerptProjection 프로퍼티로 관리되는 리소스 참조는 단일 참조 시 적용되지 않습니다.
도메인 하위 경로에 프로젝션 패키지를 생성하고 원하는 노출 필드가 지정된 인터페이스를 하나 생성합니다.
User의 이름만 제공하는 인터페이스를 생성하겠습니다.
생성한 프로젝션을 UserRepository에 적용합니다. excerptProjection 파라미터값에 생성한 프로젝션 클래스를 넣어주면 됩니다.
http://localhost:8081/api/users 결과
하지만 http://localhost:8081/api/users/1은 어젼히 모든 정보가 노출됩니다.
위에서 언급한 @Projection 주의사항인 @RespositoryRestResource 어노테이션의 excerptProjection 프로퍼티로 관리되는 리소스 참조는 단일 참조 시 적용되지 않습니다.
위 경우에 대한 대책으로 http://localhost:8081/api/users/1?projection=getOnlyName 적용하면 됩니다. 또는 특정 경로에 따라 반환할 데이터가 명확한 방법인 @RepositoryRestController를 사용하는 것을 추천합니다.
각 메서드 권한 제한
가장 기본적인 방법은 @Secured 어노테이션입니다. @Secured는 순수하게 롤 기반으로 접근을 제한합니다. 대신 @Secured는 권한 지정에 있어서 유연성이 떨어집니다.
@PreAuthorize 어노테이션을 사용하면 @Secured보다 효율적으로 권한 지정을 할 수 있습니다. 이 방식은 권한 지정 외에도 EL 태그를 사용하여 유연한 관리가 가능합니다. @PreAauthorize는 메서드 레벨과 리포지토리 레벨 모두 사용할 수 있지만 두 가지를 혼합해 사용하는 방법은 추천하지 않습니다.
@PreAuthorize를 사용하여 ROLE_ADMIN 권한을 갖고 있어야한 Board를 저장할 수 있게끔 예제를 구현하겠습니다.
JpaRepository 인터페이스에서 상속받는 메서드를 오버라이드하여 권한을 지정했습니다.
이벤트 바인딩
스프링 부트 데이터 레스트에서는 여러 메소드의 이벤트 발생 시점을 가로채서 원하는 데이터를 추가하거나 검사하는 이벤트 어노테이션을 제공합니다.
BeforeCreateEvent | 생성하기 전의 이벤트 |
AfterCreateEvent | 생성한 후의 이벤트 |
BeforeSaveEvent | 수정하기 전의 이벤트 |
AfterSaveEvent | 수정한 후의 이벤트 |
BeforeDeleteEvent | 삭제하기 전의 이벤트 |
AfterDeleteEvent | 삭제한 후의 이벤트 |
BeforeLinkSaveEvent | 관계를 가진(1:1, M:M) 링크를 수정하기 전의 이벤트 |
AfterLinkSaveEvent | 관계를 가진(1:1, M:M) 링크를 수정한 후의 이벤트 |
BeforeLinkDeleteEvent | 관계를 가진(1:1, M:M) 링크를 삭제하기 전의 이벤트 |
AfterLinkDeleteEvent | 관계를 가진(1:1, M:M) 링크를 삭제한 후의 이벤트 |
게시글 생성 시 생성한 날짜와 수정한 날짜를 서버에서 설정하도록 하는 BoardEventHandler라는 클래스를 생성하겠습니다.
선언한 이벤트를 적용하는 두 가지 방법을 제공합니다.
첫 번째는 수동으로 이벤트를 적용하는 ApplicationListener를 사용하는 방법 AbstractRepositoryEventListener를 상속받고 관련 메소드를 오버라이드하여 원하는 이벤트만 등록할 수 있습니다.
두 번째는 사용한 어노테이션을 기반으로 이벤트 처리하는 방법입니다. 생성한 이벤트 핸들러 클래스에는 @RepositoryEventHandler 어노테이션이 선언되어 있어야 합니다. 이 어노테이션은 BeanPostProcessor에 클래스가 검사될 필요가 있음을 알려줍니다. 그리고 이벤트 핸들러를 등록하려면 @Component를 사용하거나 직접 ApplicationContext에 빈으로 등록해야 합니다.
ApplicationContext 클래스에 직접 빈으로 등록하는 방법을 사용하겠습니다.
테스트 코드를 통해서 확인해보겠습니다.
- @AutoConfigureTestDatabase : H2가 build.gradle의 클래스 경로에 포함되어 있으면 자동으로 H2를 테스트 데이터베이스로 지정합니다. 만약 이 어노테이션을 사용하지 않는다면 테스트에서 Board를 저장할 때마다 실제 데이터베이스에 반영될 겁니다.
- TestRestTemplatedms RestTemplate을 래핑한 객체로서 GET, POST, PUT, DELETE와 같은 HttpRequest를 편하게 테스트하도록 도와줍니다.
HAL 브라우저 적용하기
build.gradle 아래 의존성을 추가합니다.
compile('org.springframework.data:spring-data-rest-hal-browser')
서버를 구동하고 루트 경로로 들어가면 HAL 브라우저 UI 창으로 리다이렉트 됩니다.
- Explorer : 검색할 URI를 지정합니다.
- Custom Request Headers : 검색을 요청할 때 헤더를 설정할 수 있습니다.
- Properties : 페이징 처리와 같은 부가적인 프로퍼티 정보를 표현합니다.
- Links : 응답 데이터에서 제공하는 링크(_links)값의 데이터를 표현합니다.
- Response Headers : 응답 헤더를 표현합니다.
- Response Body : 응답 바디를 JSON형으로 표현합니다.
요청 정보와 설정한 정보 그리고 어떻게 응답이 왔는지 한눈에 확인할 수 있습니다. 요청마다 웹 서버를 구동시킬 필요도 별도로 REST 테스트 도구를 사용할 필요도 없습니다.
마치며
MVC 패턴은 일반적인 스프링 구성을 사용하여 개발합니다. 그러므로 디테일한 구성을 할 수 있습니다. 하지만 모든 API를 일일이 신경 써서 개발해야 하며 반복되는 작업을 피할 수 없습니다. 반면 스프링 부트 데이터 레스트에서 제공하는 방식은 반복되는 작업을 피할 수 있습니다. 불필요한 컨트롤러와 서비스 영역을 제거하여 코드를 단축시킬 수 있습니다. 하지만 상세한 구성에 제약이 있으며 어느 정도 정해진 틀에 맞춰 개발을 진행해야 합니다.
두 방식 모두 장단점이 명확히 존재하며, 상황에 맞게 선택하여 사용하는 것이 중요할 것 같습니다.
'Spring' 카테고리의 다른 글
스프링 부트 배치 - 심화 (0) | 2021.05.23 |
---|---|
스프링 부트 배치 - 기본 (2) | 2021.05.19 |
스프링 부트 데이터 레스트 (0) | 2021.05.01 |
스프링 부트 시큐리티 + OAuth2 (0) | 2021.04.20 |
스프링 부트 웹 (0) | 2021.04.11 |