Spring REST 서비스를위한 HATEOAS

REST 상단

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인

1. 개요

이 기사는 Spring REST 서비스에서 발견 가능성을 구현 하고 HATEOAS 제약 조건을 충족시키는 데 초점을 맞출 것 입니다.

이 기사는 Spring MVC에 중점을 둡니다. 우리의 기사 An Intro to Spring HATEOAS는 Spring Boot에서 HATEOAS를 사용하는 방법을 설명합니다.

2. 이벤트를 통한 발견 가능성 분리

웹 계층의 개별적인 측면 또는 관심사로서의 검색 가능성 은 HTTP 요청을 처리하는 컨트롤러에서 분리되어야합니다 . 이를 위해 컨트롤러는 응답의 추가 조작이 필요한 모든 작업에 대해 이벤트를 발생시킵니다.

먼저 이벤트를 생성 해 보겠습니다.

public class SingleResourceRetrieved extends ApplicationEvent { private HttpServletResponse response; public SingleResourceRetrieved(Object source, HttpServletResponse response) { super(source); this.response = response; } public HttpServletResponse getResponse() { return response; } } public class ResourceCreated extends ApplicationEvent { private HttpServletResponse response; private long idOfNewResource; public ResourceCreated(Object source, HttpServletResponse response, long idOfNewResource) { super(source); this.response = response; this.idOfNewResource = idOfNewResource; } public HttpServletResponse getResponse() { return response; } public long getIdOfNewResource() { return idOfNewResource; } }

그런 다음 컨트롤러는 2 개의 간단한 작업 으로 ID로 찾기생성 :

@RestController @RequestMapping(value = "/foos") public class FooController { @Autowired private ApplicationEventPublisher eventPublisher; @Autowired private IFooService service; @GetMapping(value = "foos/{id}") public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) { Foo resourceById = Preconditions.checkNotNull(service.findOne(id)); eventPublisher.publishEvent(new SingleResourceRetrieved(this, response)); return resourceById; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody Foo resource, HttpServletResponse response) { Preconditions.checkNotNull(resource); Long newId = service.create(resource).getId(); eventPublisher.publishEvent(new ResourceCreated(this, response, newId)); } }

그런 다음 몇 개의 분리 된 리스너로 이러한 이벤트를 처리 할 수 ​​있습니다. 이들 각각은 고유 한 경우에 초점을 맞추고 전반적인 HATEOAS 제약을 충족하는 데 도움이 될 수 있습니다.

리스너는 호출 스택의 마지막 객체 여야하며 직접 액세스 할 필요가 없습니다. 따라서 그들은 공개되지 않습니다.

3. 새로 생성 된 리소스의 URI를 검색 가능하게 만들기

HATEOAS의 이전 게시물에서 논의했듯이 새 리소스를 만드는 작업은 응답 Location HTTP 헤더해당 리소스의 URI를 반환해야합니다 .

리스너를 사용하여이를 처리합니다.

@Component class ResourceCreatedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(ResourceCreated resourceCreatedEvent){ Preconditions.checkNotNull(resourceCreatedEvent); HttpServletResponse response = resourceCreatedEvent.getResponse(); long idOfNewResource = resourceCreatedEvent.getIdOfNewResource(); addLinkHeaderOnResourceCreation(response, idOfNewResource); } void addLinkHeaderOnResourceCreation (HttpServletResponse response, long idOfNewResource){ URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri(). path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri(); response.setHeader("Location", uri.toASCIIString()); } }

이 예에서 우리가 사용하고있는 ServletUriComponentsBuilder 현재 요청을 사용하여 도움 -. 이렇게하면 아무 것도 전달할 필요가 없으며 단순히 정적으로 액세스 할 수 있습니다.

API가 ResponseEntity를 반환 하면 위치 지원을 사용할 수도 있습니다 .

4. 단일 리소스 얻기

단일 리소스 를 검색 할 때 클라이언트는 URI를 검색하여 해당 유형의 모든 리소스를 가져올 수 있어야 합니다.

@Component class SingleResourceRetrievedDiscoverabilityListener implements ApplicationListener{ @Override public void onApplicationEvent(SingleResourceRetrieved resourceRetrievedEvent){ Preconditions.checkNotNull(resourceRetrievedEvent); HttpServletResponse response = resourceRetrievedEvent.getResponse(); addLinkHeaderOnSingleResourceRetrieval(request, response); } void addLinkHeaderOnSingleResourceRetrieval(HttpServletResponse response){ String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri(). build().toUri().toASCIIString(); int positionOfLastSlash = requestURL.lastIndexOf("/"); String uriForResourceCreation = requestURL.substring(0, positionOfLastSlash); String linkHeaderValue = LinkUtil .createLinkHeader(uriForResourceCreation, "collection"); response.addHeader(LINK_HEADER, linkHeaderValue); } }

링크 관계의 의미는 여러 마이크로 포맷으로 지정 및 사용되지만 아직 표준화되지 않은 "컬렉션" 관계 유형을 사용합니다.

링크 헤더는 대부분의 HTTP 헤더를 사용 중 하나 인 검색 기능의 목적을 위해. 이 헤더를 만드는 유틸리티는 간단합니다.

public class LinkUtil { public static String createLinkHeader(String uri, String rel) { return "; rel=\"" + rel + "\""; } }

5. 근본적인 발견 가능성

루트는 전체 서비스의 진입 점입니다.이 루트는 API를 처음 사용할 때 클라이언트가 접촉하는 지점입니다.

HATEOAS 제약이 전체적으로 고려되고 구현되어야한다면 여기에서 시작해야합니다. 따라서 시스템의 모든 주요 URI는 루트에서 검색 할 수 있어야합니다.

이제 컨트롤러를 살펴 보겠습니다.

@GetMapping("/") @ResponseStatus(value = HttpStatus.NO_CONTENT) public void adminRoot(final HttpServletRequest request, final HttpServletResponse response) { String rootUri = request.getRequestURL().toString(); URI fooUri = new UriTemplate("{rootUri}{resource}").expand(rootUri, "foos"); String linkToFoos = LinkUtil.createLinkHeader(fooUri.toASCIIString(), "collection"); response.addHeader("Link", linkToFoos); }

물론 이것은 Foo Resources 에 대한 단일 샘플 URI에 초점을 맞춘 개념의 예시입니다 . 실제 구현은 마찬가지로 클라이언트에 게시 된 모든 리소스에 대한 URI를 추가해야합니다.

5.1. 검색 가능성은 URI 변경에 관한 것이 아닙니다.

이것은 논란의 여지가있는 부분이 될 수 있습니다. 한편으로 HATEOAS의 목적은 클라이언트가 API의 URI를 발견하게하고 하드 코딩 된 값에 의존하지 않도록하는 것입니다. 반면에 이것은 웹이 작동하는 방식이 아닙니다. 예, URI가 발견되지만 북마크도 있습니다.

미묘하지만 중요한 차이점은 API의 진화입니다. 이전 URI는 계속 작동해야하지만 API를 발견하는 모든 클라이언트는 새로운 URI를 발견해야합니다. 이렇게하면 API가 동적으로 변경되고 좋은 클라이언트는 API 변경.

결론적으로-RESTful 웹 서비스의 모든 URI가 쿨 URI로 간주되어야하기 때문에 (쿨 URI는 변경되지 않음) API를 발전시킬 때 HATEOAS 제약 조건을 준수하는 것이 그다지 유용하지 않다는 의미는 아닙니다.

6. 검색 가능성에 대한주의 사항

이전 기사에 대한 일부 토론에서 언급했듯이 검색 가능성의 첫 번째 목표는 문서를 최소한으로 사용하거나 전혀 사용하지 않고 클라이언트가 응답을 통해 API를 사용하는 방법을 배우고 이해하도록하는 것입니다.

사실, 이것은 문서 없이는 우리가 모든 새로운 웹 페이지를 소비하는 방식입니다. 따라서 REST의 맥락에서 개념이 더 문제가되는 경우 가능 여부에 대한 질문이 아니라 기술 구현의 문제 여야합니다.

즉, 기술적으로 우리는 아직 완전히 작동하는 솔루션과는 거리가 멀습니다. 사양 및 프레임 워크 지원은 여전히 ​​진화하고 있으며, 그 때문에 약간의 타협을해야합니다.

7. 결론

이 기사에서는 Spring MVC를 사용하는 RESTful 서비스의 맥락에서 발견 가능성의 일부 특성을 구현하고 루트에서 발견 가능성의 개념을 다루었습니다.

이러한 모든 예제 및 코드 스 니펫의 구현은 GitHub에서 찾을 수 있습니다. 이것은 Maven 기반 프로젝트이므로 그대로 가져 와서 실행할 수 있어야합니다.

REST 바닥

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인